Tracking radioactive decays to normalize activity?

Geant4 Version: geant4-v11.3.1
Operating System: Fedora 31
_Compiler/Version: 12.2.1 20221121 _
CMake Version: 3.27.7


Hi all. I’m working on using GEANT4 to simulate an “alpha sail” spacecraft propulsion system driven by the decay of 232U, with alphas captured by a backing film in one direction but free to escape in the other (plus some complications, such as allowing for partial radon escape - see Note 1). The goal is to calculate the heat deposition and momentum change. To do this, I simulate a large number of events, each with a primary particle randomly chosen in terms of location and type (based on the isotopic distribution within the active material of the sail), and let GEANT4 simulate it for a fixed period (say, a day):

    void UserSteppingAction(const G4Step* step) override {
        if (!step || !fRunAction) return;
        G4Track* track = step->GetTrack();
        if (!track) return;
        G4double time = track->GetGlobalTime();
        if (time > Config::SimTimePerEvent) {
            track->SetTrackStatus(fStopAndKill);
            return;

… which should allow for primary decays and, often (if there is a primary decay), secondaries. I then have to normalize the energy and momentum recorded to the calculated number of decays during that timeperiod, so that I can then compute heating, equilibrium temperature, and acceleration. But this has proven very difficult. My core approach to getting a scaling factor was:

        const G4double calculatedDecaysOverSimTime = calculatedDecaysPerSecond * simSeconds;
        const G4double calculatedDecaysPerAtomOverSimTime = calculatedDecaysOverSimTime / atoms;
        const G4double expectedGEANT4DecayEventsOccurred = nEventsProcessed * calculatedDecaysPerAtomOverSimTime;
        const G4double scaleFactor = calculatedDecaysOverSimTime / expectedGEANT4DecayEventsOccurred;

However, not only were GEANT4’s numbers not in the ballpark of my manually calculated numbers, but they didn’t scale linearly with simulation time. With a 1d simulation time:

Average Heating Power: 5079826.411588 W [2405.454463 W/kg, 507.982641 W/m²]

But with a 10d simulation time:

Average Heating Power: 3109996.728445 W [1472.679360 W/kg, 310.999673 W/m²]

Code of relevance for the above:

        G4double scaledEnergyDeposit = fMasterData.energyDeposit * scaleFactor;

        G4double heatingPower = (scaledEnergyDeposit / CLHEP::joule / simSeconds) * CLHEP::watt;

        G4cout << "Average Heating Power: " << G4BestUnit(heatingPower, "Power") << " ["
               << ((heatingPower / CLHEP::watt) / (Config::ActiveMass / CLHEP::kg)) << " W/kg, "
               << ((heatingPower / CLHEP::watt) / (sailArea / CLHEP::m2)) << " W/m²]\n";

My expectations were that if my code were correct, changing the time from 1d to 10d should make no difference; and if my code were wrong, the calculation should grow by 10x or shrink by 10x. But the results halving the heating power makes little sense to me. I track the number of primary decays, and they rise from 287 to 1178… so the increase in primary decays is not linear, but that’s expected (we’re talking primaries here, and short-lived primaries are pretty much guaranteed to decay whether it’s 1d or 10d, with the change only being in long-lived primaries)… but I expect that with a longer simulation time, each primary also averages a higher number of secondaries, and that the total number of simulated decays (primary + secondary) rises 10x. Yet the energy deposited doesn’t seem to rise 10x, it seems to rise ~4x, and the total energy deposited seems to rise, ~6x - not 10x.

I tried finding a way to directly count secondary decays, but nothing has worked. As an example, as a user stepping action, I tried:

        G4StepPoint* postStepPoint = step->GetPostStepPoint();

        if (postStepPoint) {
             const G4VProcess* process = postStepPoint->GetProcessDefinedStep();
             // Check if the process that *ended* this step was a decay
             if (process && process->GetProcessName() == "RadioactiveDecay") {
                  // Increment total decay counter associated with RunAction
                  fRunAction->AddTotalDecay();
             }
        }

But that doesn’t work. GetProcessName is always “NoProcess”. I tried a number of other things, but nothing worked out. And I can’t see why my original approach wouldn’t work anyway, which makes me wonder if I’m making a conceptual mistake. I’ve tried removing my radon special casing (both the handling of radon within GEANT4, and the equilibrium decay distribution starting point), and while that changes the numbers, the same problem continues to exist.

1d: Average Heating Power: 7072060.753646 W [2900.978239 W/kg, 707.206075 W/m²]
10d: Average Heating Power: 3482075.117464 W [1428.356528 W/kg, 348.207512 W/m²]

Ideas?

Full current (very rough and ugly!) code here: Paste.gd: Copy and Paste Text Tool

*** Note 1: the design attempts to minimize 232U’s problem of intense gammas from 208Tl decay via using a urania aerogel (as per Reibold et al 2002) and maximizing the equilibrium temperature, which my calculations suggest should allow the lion’s share of the radon to diffuse out (pre-deployment: into helium coolant and then a cold trap; post-deployment: into space). This does eliminate a lot of the decay chain energy, but has an offsetting side bonus of not retaining the lead mass. In GEANT4 I model this first off by adjusting the equilibrium distribution, and secondly - to account for the possibility that decay near the sail will lead to sending alphas, polonium, lead etc back into the sail or payload - I add in special cases for radon handling, both as daughter products (in the user stepping action - “if (track->GetDefinition()->GetParticleName() == “Rn220”) {” - and if 220Rn is chosen in GeneratePrimaries (“if (Z == 86 && A == 220)”). In each case, my approach has been to pick a random thermal velocity at escape; a random escape trajectory from the surface; and a random time to decay (minus the time to escape the aerogel); then to position the particle at the projected decay location with zero velocity, so that - so long as the simulation time is long enough - GEANT4 should almost certainly decay it there. Does this sound like the most reasonable approach? Either way, though, the above problem I’m facing exists whether I have radon special-cased or not.

Hi @Karen_Roberts,

Thank you for your post.

To check whether the issue lies in the input data or the Geant4 model, it is recommended using the provided Geant4 examples as a starting point for debugging. In this case, I think the rdecay01 example is well suited to rule out a problem on the Geant4 side (link).

Could you please try running the following macro file with that example and examine the output for any inconsistencies?

# Macro for rdecay01
/control/cout/ignoreThreadsExcept 0
/control/verbose 2
/run/verbose 1

# Set a very high time threshold to allow all decays to occur
/process/had/rdm/thresholdForVeryLongDecayTime 1.0e+60 year

/gun/particle ion
/gun/ion 92 232

# Uncomment the next line to limit the decay chain to 232U only
# /process/had/rdm/nucleusLimits 232 232 92 92
/rdecay01/fullChain true

/analysis/setFileName fullchain232U
/analysis/h1/set 1  100  0. 4000 keV    # e+ e-
/analysis/h1/set 2  100  0. 4000 keV    # neutrino
/analysis/h1/set 3  100  0. 4000 keV    # gamma
/analysis/h1/set 4  100  0. 10   MeV    # alpha
/analysis/h1/set 5  100  0. 200  keV    # recoil ion
/analysis/h1/set 6  100  0. 8    MeV    # EkinTot (Q)
/analysis/h1/set 7  100  0. 300  MeV    # P balance
/analysis/h1/set 8  100  0. 100  y      # time of life
/analysis/h1/set 9  100  0. 100. MeV    # EvisTot

/run/printProgress 10000
/run/beamOn 100000

Let us know if you observe anything unusual in the output.

Best regards,
Alvaro

Thank you Alvaro. I ran it:

Nb of generated particles:

        Bi212:  100000  Emean =  385.6 meV  ( 3.522 meV --> 2.187 eV )    mean life = 1.456 h  

Bi212[115.183]: 4882 Emean = 378.4 meV ( 228.1 meV → 1.001 eV )
Bi212[238.632]: 81375 Emean = 412.7 meV ( 0 eV → 1.059 eV )
Bi212[415.272]: 5066 Emean = 138.5 meV ( 0 eV → 401.1 meV)
Pb208: 100000 Emean = 108.3 keV ( 17.65 eV → 169.3 keV) stable
Pb208[2614.522]: 36037 Emean = 1.049 eV ( 878 meV → 7.853 eV )
Pb208[3197.711]: 31187 Emean = 4.212 eV ( 18.92 meV → 12.84 eV )
Pb208[3475.078]: 8235 Emean = 5.004 eV ( 12.43 meV → 9.768 eV )
Pb208[3708.451]: 9376 Emean = 3.629 eV ( 3.958 meV → 7.539 eV )
Pb208[3919.966]: 217 Emean = 2.994 eV ( 17.61 meV → 5.392 eV )
Pb208[3946.578]: 17 Emean = 2.536 eV ( 82.51 meV → 5.237 eV )
Pb208[3961.162]: 1153 Emean = 2.641 eV ( 17.4 meV → 5.268 eV )
Pb208[3995.438]: 3 Emean = 2.235 eV ( 1.876 eV → 2.856 eV )
Pb208[4125.347]: 87 Emean = 1.683 eV ( 75.67 meV → 3.759 eV )
Pb208[4180.414]: 90 Emean = 1.69 eV ( 43.51 meV → 3.64 eV )
Pb208[4296.560]: 33 Emean = 1.295 eV ( 55.76 meV → 2.909 eV )
Pb208[4323.946]: 4 Emean = 1.24 eV ( 587.7 meV → 1.957 eV )
Pb208[4358.670]: 8 Emean = 1.152 eV ( 205.5 meV → 1.98 eV )
Pb208[4383.285]: 6 Emean = 760.2 meV ( 294 meV → 1.4 eV )
Pb208[4480.746]: 10 Emean = 1.054 eV ( 71.97 meV → 1.675 eV )
Pb212: 100000 Emean = 128.1 keV ( 1.641 eV → 128.1 keV) mean life = 15.32 h
Pb212[804.900]: 2 Emean = 113.1 keV ( 113.1 keV → 113.1 keV)
Po212: 63959 Emean = 9.021 eV ( 28.64 meV → 18.35 eV ) mean life = 424.6 ns
Po212[1512.700]: 1443 Emean = 1.474 eV ( 30.21 meV → 3.092 eV )
Po212[1620.739]: 1875 Emean = 1.131 eV ( 18.92 meV → 2.521 eV )
Po212[1679.452]: 211 Emean = 896.9 meV ( 83.41 meV → 2.044 eV )
Po212[1800.910]: 37 Emean = 676.3 meV ( 48.52 meV → 1.454 eV )
Po212[1805.950]: 624 Emean = 671.1 meV ( 24.51 meV → 1.529 eV )
Po212[727.330]: 6803 Emean = 4.039 eV ( 32.65 meV → 9.618 eV )
Po216: 100000 Emean = 116.5 keV ( 751.2 meV → 116.6 keV) mean life = 209.2 ms
Po216[549.760]: 114 Emean = 106.6 keV ( 106.6 keV → 106.6 keV)
Ra224: 100000 Emean = 70.96 keV ( 17.05 meV → 96.96 keV) mean life = 5.239 d
Ra224[215.985]: 409 Emean = 92.03 keV ( 144.4 meV → 93.16 keV)
Ra224[250.782]: 178 Emean = 92.55 keV ( 92.55 keV → 92.55 keV)
Ra224[290.352]: 22 Emean = 91.86 keV ( 91.86 keV → 91.86 keV)
Ra224[84.372]: 26575 Emean = 94.19 keV ( 41.5 meV → 95.48 keV) mean life = 1.079 ns
Rn220: 100000 Emean = 98.32 keV ( 141.7 meV → 103.5 keV) mean life = 1.337 min
Rn220[240.986]: 5003 Emean = 98.98 keV ( 209.1 meV → 99.19 keV)
Rn220[533.680]: 9 Emean = 93.96 keV ( 93.96 keV → 93.96 keV)
Rn220[645.440]: 4 Emean = 91.96 keV ( 91.96 keV → 91.96 keV)
Rn220[663.030]: 1 Emean = 91.64 keV ( 91.64 keV → 91.64 keV)
Th228: 100000 Emean = 63.53 keV ( 7.858 meV → 93.45 keV) mean life = 2.76 y
Th228[186.838]: 336 Emean = 90.22 keV ( 90.22 keV → 90.22 keV)
Th228[328.019]: 5 Emean = 87.78 keV ( 87.78 keV → 87.78 keV)
Th228[57.773]: 32016 Emean = 91.47 keV ( 39.23 meV → 92.45 keV)
Tl208: 36041 Emean = 31.49 keV ( 4.075 meV → 117.3 keV) mean life = 4.405 min
Tl208[328.040]: 831 Emean = 80.07 keV ( 54.54 meV → 111.1 keV)
Tl208[39.858]: 26114 Emean = 112.8 keV ( 214.4 meV → 116.5 keV)
Tl208[473.400]: 63 Emean = 108.3 keV ( 108.3 keV → 108.3 keV)
Tl208[492.840]: 410 Emean = 108 keV ( 108 keV → 108 keV)
Tl208[620.400]: 5 Emean = 105.6 keV ( 105.6 keV → 105.6 keV)
U232: 100000 Emean = 0 eV ( 0 eV → 0 eV ) mean life = 99.47 y
alpha: 600000 Emean = 6.207 MeV ( 4.998 MeV → 8.785 MeV) stable
anti_nu_e: 200000 Emean = 709.5 keV ( 1 eV → 2.234 MeV) stable
e-: 331585 Emean = 281.6 keV ( 4.532 eV → 2.614 MeV) stable
gamma: 149291 Emean = 968 keV ( 39.86 keV → 3.708 MeV) stable

Ekin Total (Q single decay): mean = 3.86 MeV ( 19.41 keV → 8.954 MeV)

Momentum balance (excluding gamma desexcitation): mean = 104.3 MeV ( 0.858 meV → 225.5 MeV)

Total time of life (full chain): mean = 102.4 y half-life = 70.96 y ( 47.74 d → 926.1 y )

Total visible energy (full chain) : mean = 40.31 MeV ( 38.99 MeV → 41.7 MeV)

Activity of U232 = 8.265e+11 Bq/g (22.34 Ci/g)

Everything appears reasonable at a glance - mean life, energy distributions, etc - but I’m not sure how this is supposed to help with my problem?

In my code, if I set my active isotopes to be, say:

    // Parameters: Z, A, molar ratio, half life, ...
    std::vector<IsotopeInfo> ActiveIsotopes = {
        {92, 232, 0.9043, 2.209e9, 6, -1.0, -1.0, -1.0},
        {90, 228, 0.02505, 6.032e7, 4, -1.0, -1.0, -1.0},
        {88, 224, 1.303e-4, 3.137e5, 2, -1.0, -1.0, -1.0},
        {86, 220, 2.308e-8, 55.6, 0, -1.0, -1.0, -1.0},
        {84, 216, 6.020e-11, 0.145, 6, -1.0, -1.0, -1.0},
        {82, 212, 1.59e-5, 3.8304e4, 4, -1.0, -1.0, -1.0},
        {83, 212, 1.508e-6, 3.633e3, 5, -1.0, -1.0, -1.0},
        {81, 208, 2.738e-8, 183.18, 3, -1.0, -1.0, -1.0},
        {84, 212, 7.971e-17, 2.99e-7, 6, -1.0, -1.0, -1.0},
        {82, 208, 0.0957, 1e99, 4, -1.0, -1.0, -1.0},
    };

… representing 10y of 232U decay (with no loss of Rn or its daughters), so each isotope’s activity matches out to ~1.8565e+06 TBq except for the branches (which sum to ~1.8565e+06 TBq)… and then simulate 3m events, each of which track until track->GetGlobalTime() > Config::SimTimePerEvent, for 10 days and 1 day of sim time respectively.. .I get:

10d time limit: 4124 primary decays, 92.5154 GeV deposited
1d time limit: 1227 primary decays, 19.4817 GeV deposited

The fundamental question here is: why is the 10d energy deposit not 10x the 1d deposited energy? If it’s an equilibrium mixture, should that not fundamentally be the case, so long as the timescales involved are not sufficiently long so as to alter the ratios? Something seems very wrong.

I’d like to see what exactly it’s doing to try to debug this, but debug isn’t showing up for some reason.

class PhysicsList : public G4VModularPhysicsList {
public:
    PhysicsList()
    {
        SetVerboseLevel(1);
        RegisterPhysics(new G4EmStandardPhysics());
        G4RadioactiveDecayPhysics* decayPhysics = new G4RadioactiveDecayPhysics();
        decayPhysics->SetVerboseLevel(1);
        RegisterPhysics(decayPhysics);
    }
    ~PhysicsList() override = default;
}

Verbose level=1, level=2, level=200… it makes no difference. All I see after BeamOn is:

Starting BeamOn…
RunAction: BeginOfRun. Sail Area=10000 m2
G4WT4 > Thread 4 data: Energy=1.16914 GeV SailMomentumChangeZ=4.38608 GeV/c PrimaryDecays=72
G4WT1 > Thread 1 data: Energy=943.601 MeV SailMomentumChangeZ=2.14156 GeV/c PrimaryDecays=59
G4WT18 > Thread 18 data: Energy=760.525 MeV SailMomentumChangeZ=1.50015 GeV/c PrimaryDecays=47
G4WT10 > Thread 10 data: Energy=992.307 MeV SailMomentumChangeZ=1.69916 GeV/c PrimaryDecays=62
G4WT14 > Thread 14 data: Energy=1.04447 GeV SailMomentumChangeZ=2.45074 GeV/c PrimaryDecays=61
G4WT3 > Thread 3 data: Energy=851.896 MeV SailMomentumChangeZ=2.1904 GeV/c PrimaryDecays=57
G4WT12 > Thread 12 data: Energy=1.10564 GeV SailMomentumChangeZ=1.91855 GeV/c PrimaryDecays=65
G4WT8 > Thread 8 data: Energy=1.11323 GeV SailMomentumChangeZ=2.58423 GeV/c PrimaryDecays=62
G4WT11 > Thread 11 data: Energy=1.02538 GeV SailMomentumChangeZ=2.50117 GeV/c PrimaryDecays=72
G4WT9 > Thread 9 data: Energy=1.16962 GeV SailMomentumChangeZ=2.18401 GeV/c PrimaryDecays=66
G4WT16 > Thread 16 data: Energy=1.11071 GeV SailMomentumChangeZ=2.15954 GeV/c PrimaryDecays=71
G4WT6 > Thread 6 data: Energy=939.762 MeV SailMomentumChangeZ=2.37192 GeV/c PrimaryDecays=56
G4WT7 > Thread 7 data: Energy=1.21993 GeV SailMomentumChangeZ=2.27237 GeV/c PrimaryDecays=73
G4WT0 > Thread 0 data: Energy=978.386 MeV SailMomentumChangeZ=2.75032 GeV/c PrimaryDecays=60
G4WT19 > Thread 19 data: Energy=858.507 MeV SailMomentumChangeZ=1.6448 GeV/c PrimaryDecays=59
G4WT13 > Thread 13 data: Energy=839.059 MeV SailMomentumChangeZ=2.84087 GeV/c PrimaryDecays=56
G4WT2 > Thread 2 data: Energy=768.322 MeV SailMomentumChangeZ=2.00991 GeV/c PrimaryDecays=50
G4WT15 > Thread 15 data: Energy=867.316 MeV SailMomentumChangeZ=2.35174 GeV/c PrimaryDecays=60
G4WT5 > Thread 5 data: Energy=911.195 MeV SailMomentumChangeZ=1.9422 GeV/c PrimaryDecays=62
G4WT17 > Thread 17 data: Energy=812.677 MeV SailMomentumChangeZ=1.96492 GeV/c PrimaryDecays=57
Thread -1 data: Energy=0 eV SailMomentumChangeZ=0 eV/c PrimaryDecays=0

RunAction: EndOfRun. Master thread reporting.

All of that is my debug, none of it straight from GEANT4. So I can’t see what it’s doing. And I can’t detect which events are radioactive decay because GetProcessName is always “NoProcess”. Basically, I’m blind as to what’s going on here.

Hi @Karen_Roberts,

Thanks for checking the output of the example. A good practice when debugging or trying to understand complex behavior in Geant4 is to start with a minimal working application and progressively build complexity, validating each step along the way.

From your message, it sounds like the Geant4 input configuration is in order. Looking at your code, I noticed that you’re using a regular G4ParticleGun, which is designed to inject one particle (type) per event.

However, I also saw that you’re working with a vector of isotopes (ActiveIsotopes), each with an associated molar concentration. This setup suggests you want to sample from a set of nuclides. However, if I am not mistaken, only one ion is selected and injected per event in the current implementation.

If your intention is to inject multiple ions in a single event (e.g. to simulate simultaneous decay heat contributions), you’ll need to loop over the desired isotopes and create multiple primary vertices manually within GeneratePrimaries(), or switch to using a more flexible source like G4GeneralParticleSource.

Thank you for your time.

Best,
Alvaro

Thank you Alvaro - I appreciate your time here.

One ion is indeed injected at a time (“runManager->BeamOn(nEvents);”), where nEvents is a large number of events to simulate to statistically sample the sail. There is no need for multiple primaries to be considered at once; the flux is not high enough for that to have any meaningful impact.

To reiterate, the scenario is: We start with an equilibrium mixture of daughters of 232U at 10y. So there’s some quite long-lived isotopes (like 232U), and some vanishingly small ones (like 212Po). Since it’s equlilibrium, there should be tiny amounts of the short-lived stuff but they give off a lot of radioactivity per atom, and this all cancels out to approximately the same Bq per isotope (except for branches, but they sum up to the same Bq).

Now, we randomly pick a large number of atoms to use as primaries, one at a time. Say a billion. So it’ll most commonly pick the 232U, but every so often the rarer stuff. We then simulate each of said atoms for 1 day, including any daughter products, up until the day completes. Most of the 232U does nothing, but we picked so many of them that they should equal out to the rarely-picked but almost-certain-to-decay rare choices. And all of their short-lived daughters decay as well.

Premise #1: If we normalize the number of decays of our billion simulated atoms by multiplying by (ActualNumberOfAtoms / 1 billion), it should match the expected decay of our source material over 1 day.

Next, we do the same thing, but each atom is simulated for 10 days. Now, this should not increase the number primary decays by 10x - sure, long-lived things like 232U should increase approximately 10x, but if it picked say 212Po, that was already essentially certain to decay, and giving it more time doesn’t change that it was 1 primary before, and is still 1 primary. However, increasing the time also gives time for more secondaries to occur. For example, if 228Th decayed on a 1d simulation, its daughter - 224Ra (3,7d half life) - was unlikely to also decay during a 1d simulation, but now in a 10d simulation, it’s likely to.

Premise #2: While the number of primaries should not rise 10x, the total number of decays in the 10d scenario - both primary and secondary combined - should be ten times that of the 1d scenario, and the energy deposited in the sail, also 10x.

GEANT4 outcome: The number of primary decays in the 10d scenario are ~4x that of the 1d scenario (not unreasonable), BUT the energy of the primary+secondary decays is ~6x that of the 1d scenario (not ~10x).

So I face two possibilities: either (A) my logic is wrong (and I need to find a new strategy to accurately simulate the real-world mix of decays in the sail), or (B) my simulation is doing something wrong. But, re: (B), if my simulation is doing something wrong:

  • I get no useful debug from GEANT4
  • I can’t find a way to manually track secondary decay counts

So I can’t move forward on trying to diagnose possibility (B), but also don’t see the issue that could pose an error in possibility (A).

Hmm. Maybe a possibility to bypass having to figure out what’s going on above, but to still figure out how much energy and momentum the whole sail is experiencing per unit time:

I could create, for each isotope in the mixture, say 10k particles of that isotope, simulated the fate of each of those primaries (but not their secondaries) without a time limit, and then normalized the impacts with respect to (A) time and (B) how abundant the isotope in question is in the source material.

I think that would work, but the need to not consider secondaries introduces new complications. Is there a way to “shut off the decay” of secondaries? I can’t just stop as soon as the initial decay occurs, because I need some time to allow the alphas, etc to interact and for the secondaries to lose their recoil energy. I could “set a timer” as soon as a secondary forms and give it such a short period of time that it’d be unlikely to decay (though 212Po would be a challenge, with its 0,3us half life). Or I could try to detect its decay, and kill the particle immediately before it happens. Or… hmm. I’m not sure I know how to do any of these things in GEANT4.

Oh hmm… maybe, for the potential above approach, I could shut off decays of secondaries manually in PhysicsList by increasing their half-lives, like:

G4ParticleTable* particleTable = G4ParticleTable::GetParticleTable();
G4ParticleDefinition* th228 = particleTable->FindParticle(“Th228”);
if (th228) th228->SetPDGLifeTime(1e38 * second);

?

Then I could just have like 10k of each isotope decay and normalize the results across time and molar abundance without having to worry about any of this. Surely that would work…

(I still really wish I could actually get debug working so I could see what’s actually going on, though…)

(ED: Just figured out debug! Setting it in the PhysicsList didn’t work, but this did! G4UImanager* UImanager = G4UImanager::GetUIpointer();
UImanager->ApplyCommand(“/run/verbose 1”);
UImanager->ApplyCommand(“/tracking/verbose 1”);
UImanager->ApplyCommand(“/process/verbose 1”);
)

In the old G4RadioactiveDecay process, there’s a UI command /process/had/rdm/nucleusLimits, which maps to G4RadioactiveDecay::SetNucleusLimits(), taking a G4NucleusLimits object as argument. You can set this to bound just the one nucleus you want to decay, and not do the whole chain each time. If you’re choosing different nuclei per event, then in your primary generator, you’d grab (just once!) the RDM process from G4GenericIon, and call SetNucleusLimits() for each event. It’s a bit of pain, but we do it in our simulation framework.

There is also the new G4Radioactivation process, which does the decay without actually producing secondary nuclei (it looks up the decay tables and invokes them all in one step). I’ve never used it, so I don’t know whether it can be restricted to “just the top decay” or not. I don’t see an obvious UI command for that.

A second issue for you, if you are creating the radioisotope as a primary. When Geant4 does that decay, it will assign the timestamp of the daughters according to the decay half-life. So the very first real tracks in the event (i.e., the alpha) are going to have timestamps of millions of years. This is usually not very helpful. In CDMS, we implemented a G4WrapperProcess around G4RadioactiveDecay, where if the decaying nucleus is a primary (track.ParentID()==0), then we reset the secondaries with GlobalTime=0. Any decays in the event after that are treated normally.

1 Like

To build on @mkelsey’s answer, the cleanest way to restrict decays can be done in the macro:

/process/had/rdm/nucleusLimits Al Ah Zl Zh

Where the decays will be restricted to all nuclei with A between (Al, Ah) and Z between (Zl, Zh). The timestamp problem is a nuisance, but you can do this in the PostUserTrackingAction:

if (track->GetParentID() == 0) { // This track belongs to a primary vertex
    // ============Setting secondary tracks to zero global time==============
    G4TrackVector* secondaries = fpTrackingManager->GimmeSecondaries();
    size_t nSeco = secondaries->size();
         if (nSeco > 0){
            for(size_t i=0; i < nSeco; i++) {
                (*secondaries)[i]->SetGlobalTime(0.);
                }
            }
        }
    }

This will set the time of all secondaries to be relative to the initial decay rather than initial decay + half life of the parent.

1 Like