Files
geant4/source/processes/hadronic/models/radioactive_decay/src/G4RadioactiveDecay.cc
T
2022-05-25 15:50:57 +02:00

1331 lines
51 KiB
C++

//
// ********************************************************************
// * License and Disclaimer *
// * *
// * The Geant4 software is copyright of the Copyright Holders of *
// * the Geant4 Collaboration. It is provided under the terms and *
// * conditions of the Geant4 Software License, included in the file *
// * LICENSE and available at http://cern.ch/geant4/license . These *
// * include a list of copyright holders. *
// * *
// * Neither the authors of this software system, nor their employing *
// * institutes,nor the agencies providing financial support for this *
// * work make any representation or warranty, express or implied, *
// * regarding this software system or assume any liability for its *
// * use. Please see the license in the file LICENSE and URL above *
// * for the full disclaimer and the limitation of liability. *
// * *
// * This code implementation is the result of the scientific and *
// * technical work of the GEANT4 collaboration. *
// * By using, copying, modifying or distributing the software (or *
// * any work based on the software) you agree to acknowledge its *
// * use in resulting scientific publications, and indicate your *
// * acceptance of all terms of the Geant4 Software license. *
// ********************************************************************
//
////////////////////////////////////////////////////////////////////////////////
// //
// File: G4RadioactiveDecay.cc //
// Author: D.H. Wright (SLAC) //
// Date: 9 August 2017 //
// Description: version the G4RadioactiveDecay process by F. Lei and //
// P.R. Truscott with biasing and activation calculations //
// removed to a derived class. It performs alpha, beta, //
// electron capture and isomeric transition decays of //
// radioactive nuclei. //
// //
////////////////////////////////////////////////////////////////////////////////
#include "G4RadioactiveDecay.hh"
#include "G4RadioactiveDecayMessenger.hh"
#include "G4SystemOfUnits.hh"
#include "G4DynamicParticle.hh"
#include "G4DecayProducts.hh"
#include "G4DecayTable.hh"
#include "G4ParticleChangeForRadDecay.hh"
#include "G4ITDecay.hh"
#include "G4BetaDecayType.hh"
#include "G4BetaMinusDecay.hh"
#include "G4BetaPlusDecay.hh"
#include "G4ECDecay.hh"
#include "G4AlphaDecay.hh"
#include "G4TritonDecay.hh"
#include "G4ProtonDecay.hh"
#include "G4NeutronDecay.hh"
#include "G4SFDecay.hh"
#include "G4VDecayChannel.hh"
#include "G4NuclearDecay.hh"
#include "G4RadioactiveDecayMode.hh"
#include "G4Fragment.hh"
#include "G4Ions.hh"
#include "G4IonTable.hh"
#include "G4BetaDecayType.hh"
#include "Randomize.hh"
#include "G4LogicalVolumeStore.hh"
#include "G4NuclearLevelData.hh"
#include "G4DeexPrecoParameters.hh"
#include "G4LevelManager.hh"
#include "G4ThreeVector.hh"
#include "G4Electron.hh"
#include "G4Positron.hh"
#include "G4Neutron.hh"
#include "G4Gamma.hh"
#include "G4Alpha.hh"
#include "G4Triton.hh"
#include "G4Proton.hh"
#include "G4HadronicProcessType.hh"
#include "G4HadronicProcessStore.hh"
#include "G4HadronicException.hh"
#include "G4LossTableManager.hh"
#include "G4VAtomDeexcitation.hh"
#include "G4UAtomicDeexcitation.hh"
#include "G4PhotonEvaporation.hh"
#include "G4HadronicParameters.hh"
#include <vector>
#include <sstream>
#include <algorithm>
#include <fstream>
#include "G4PhysicsModelCatalog.hh"
using namespace CLHEP;
const G4double G4RadioactiveDecay::levelTolerance = 10.0*eV;
const G4ThreeVector G4RadioactiveDecay::origin(0.,0.,0.);
#ifdef G4MULTITHREADED
#include "G4AutoLock.hh"
G4Mutex G4RadioactiveDecay::radioactiveDecayMutex = G4MUTEX_INITIALIZER;
DecayTableMap* G4RadioactiveDecay::master_dkmap = 0;
G4int& G4RadioactiveDecay::NumberOfInstances()
{
static G4int numberOfInstances = 0;
return numberOfInstances;
}
#endif
G4RadioactiveDecay::G4RadioactiveDecay(const G4String& processName)
: G4VRestDiscreteProcess(processName, fDecay), isInitialised(false),
forceDecayDirection(0.,0.,0.), forceDecayHalfAngle(0.*deg), dirPath(""),
verboseLevel(1),
fThresholdForVeryLongDecayTime( 1.0e+27*CLHEP::nanosecond ) // Longer than twice Universe's age
{
#ifdef G4VERBOSE
if (GetVerboseLevel() > 1) {
G4cout << "G4RadioactiveDecay constructor: processName = " << processName
<< G4endl;
}
#endif
SetProcessSubType(fRadioactiveDecay);
theRadioactiveDecayMessenger = new G4RadioactiveDecayMessenger(this);
pParticleChange = &fParticleChangeForRadDecay;
// Set up photon evaporation for use in G4ITDecay
photonEvaporation = new G4PhotonEvaporation();
photonEvaporation->RDMForced(true);
photonEvaporation->SetICM(true);
// DHW G4DeexPrecoParameters* deex = G4NuclearLevelData::GetInstance()->GetParameters();
// DHW deex->SetCorrelatedGamma(true);
// Check data directory
char* path_var = std::getenv("G4RADIOACTIVEDATA");
if (!path_var) {
G4Exception("G4RadioactiveDecay()","HAD_RDM_200",FatalException,
"Environment variable G4RADIOACTIVEDATA is not set");
} else {
dirPath = path_var; // convert to string
std::ostringstream os;
os << dirPath << "/z1.a3"; // used as a dummy
std::ifstream testFile;
testFile.open(os.str() );
if (!testFile.is_open() )
G4Exception("G4RadioactiveDecay()","HAD_RDM_201",FatalException,
"Environment variable G4RADIOACTIVEDATA is set, but does not point to correct directory");
}
// Reset the list of user defined data files
theUserRadioactiveDataFiles.clear();
// Instantiate the map of decay tables
#ifdef G4MULTITHREADED
G4AutoLock lk(&G4RadioactiveDecay::radioactiveDecayMutex);
NumberOfInstances()++;
if(!master_dkmap) master_dkmap = new DecayTableMap;
#endif
dkmap = new DecayTableMap;
// Apply default values
applyARM = true;
applyICM = true; // Always on; keep only for backward compatibility
// RDM applies to all logical volumes by default
isAllVolumesMode = true;
SelectAllVolumes();
G4HadronicProcessStore::Instance()->RegisterExtraProcess(this);
}
void G4RadioactiveDecay::ProcessDescription(std::ostream& outFile) const
{
outFile << "The radioactive decay process (G4RadioactiveDecay) handles the\n"
<< "alpha, beta+, beta-, electron capture and isomeric transition\n"
<< "decays of nuclei (G4GenericIon) with masses A > 4.\n"
<< "The required half-lives and decay schemes are retrieved from\n"
<< "the RadioactiveDecay database which was derived from ENSDF.\n";
}
G4RadioactiveDecay::~G4RadioactiveDecay()
{
delete theRadioactiveDecayMessenger;
delete photonEvaporation;
for (DecayTableMap::iterator i = dkmap->begin(); i != dkmap->end(); i++) {
delete i->second;
}
dkmap->clear();
delete dkmap;
#ifdef G4MULTITHREADED
G4AutoLock lk(&G4RadioactiveDecay::radioactiveDecayMutex);
--NumberOfInstances();
if(NumberOfInstances()==0)
{
for (DecayTableMap::iterator i = master_dkmap->begin(); i != master_dkmap->end(); i++) {
delete i->second;
}
master_dkmap->clear();
delete master_dkmap;
}
#endif
}
G4bool G4RadioactiveDecay::IsApplicable(const G4ParticleDefinition& aParticle)
{
// All particles other than G4Ions, are rejected by default
if (((const G4Ions*)(&aParticle))->GetExcitationEnergy() > 0.) {return true;}
if (aParticle.GetParticleName() == "GenericIon") {
return true;
} else if (!(aParticle.GetParticleType() == "nucleus")
|| aParticle.GetPDGLifeTime() < 0. ) {
return false;
}
// Determine whether the nuclide falls into the correct A and Z range
G4int A = ((const G4Ions*) (&aParticle))->GetAtomicMass();
G4int Z = ((const G4Ions*) (&aParticle))->GetAtomicNumber();
if (A > theNucleusLimits.GetAMax() || A < theNucleusLimits.GetAMin())
{return false;}
else if (Z > theNucleusLimits.GetZMax() || Z < theNucleusLimits.GetZMin())
{return false;}
return true;
}
G4DecayTable* G4RadioactiveDecay::GetDecayTable(const G4ParticleDefinition* aNucleus)
{
G4String key = aNucleus->GetParticleName();
DecayTableMap::iterator table_ptr = dkmap->find(key);
G4DecayTable* theDecayTable = 0;
if (table_ptr == dkmap->end() ) { // If table not there,
theDecayTable = LoadDecayTable(*aNucleus); // load from file and
if(theDecayTable) (*dkmap)[key] = theDecayTable; // store in library
} else {
theDecayTable = table_ptr->second;
}
return theDecayTable;
}
void G4RadioactiveDecay::SelectAVolume(const G4String& aVolume)
{
G4LogicalVolumeStore* theLogicalVolumes = G4LogicalVolumeStore::GetInstance();
G4LogicalVolume* volume = nullptr;
volume = theLogicalVolumes->GetVolume(aVolume);
if (volume != nullptr)
{
ValidVolumes.push_back(aVolume);
std::sort(ValidVolumes.begin(), ValidVolumes.end());
// sort need for performing binary_search
if (GetVerboseLevel() > 0)
G4cout << " Radioactive decay applied to " << aVolume << G4endl;
}
else
{
G4ExceptionDescription ed;
ed << aVolume << " is not a valid logical volume name."
<< " Decay not activated for it."
<< G4endl;
G4Exception("G4RadioactiveDecay::SelectAVolume()", "HAD_RDM_300",
JustWarning, ed);
}
}
void G4RadioactiveDecay::DeselectAVolume(const G4String& aVolume)
{
G4LogicalVolumeStore* theLogicalVolumes = G4LogicalVolumeStore::GetInstance();
G4LogicalVolume* volume = nullptr;
volume = theLogicalVolumes->GetVolume(aVolume);
if (volume != nullptr)
{
auto location= std::find(ValidVolumes.cbegin(),ValidVolumes.cend(),aVolume);
if (location != ValidVolumes.cend() )
{
ValidVolumes.erase(location);
std::sort(ValidVolumes.begin(), ValidVolumes.end());
isAllVolumesMode = false;
if (GetVerboseLevel() > 0)
G4cout << " G4RadioactiveDecay::DeselectAVolume: " << aVolume
<< " is removed from list " << G4endl;
}
else
{
G4ExceptionDescription ed;
ed << aVolume << " is not in the list. No action taken." << G4endl;
G4Exception("G4RadioactiveDecay::DeselectAVolume()", "HAD_RDM_300",
JustWarning, ed);
}
}
else
{
G4ExceptionDescription ed;
ed << aVolume << " is not a valid logical volume name. No action taken."
<< G4endl;
G4Exception("G4RadioactiveDecay::DeselectAVolume()", "HAD_RDM_300",
JustWarning, ed);
}
}
void G4RadioactiveDecay::SelectAllVolumes()
{
G4LogicalVolumeStore* theLogicalVolumes = G4LogicalVolumeStore::GetInstance();
G4LogicalVolume* volume = nullptr;
ValidVolumes.clear();
#ifdef G4VERBOSE
if (GetVerboseLevel()>1)
G4cout << " RDM Applies to all Volumes" << G4endl;
#endif
for (std::size_t i = 0; i < theLogicalVolumes->size(); ++i){
volume = (*theLogicalVolumes)[i];
ValidVolumes.push_back(volume->GetName());
#ifdef G4VERBOSE
if (GetVerboseLevel()>1)
G4cout << " RDM Applies to Volume " << volume->GetName() << G4endl;
#endif
}
std::sort(ValidVolumes.begin(), ValidVolumes.end());
// sort needed in order to allow binary_search
isAllVolumesMode=true;
}
void G4RadioactiveDecay::DeselectAllVolumes()
{
ValidVolumes.clear();
isAllVolumesMode=false;
#ifdef G4VERBOSE
if (GetVerboseLevel() > 1) G4cout << "RDM removed from all volumes" << G4endl;
#endif
}
////////////////////////////////////////////////////////////////////////////////
// //
// GetMeanLifeTime (required by the base class) //
// //
////////////////////////////////////////////////////////////////////////////////
G4double G4RadioactiveDecay::GetMeanLifeTime(const G4Track& theTrack,
G4ForceCondition*)
{
G4double meanlife = 0.;
const G4DynamicParticle* theParticle = theTrack.GetDynamicParticle();
const G4ParticleDefinition* theParticleDef = theParticle->GetDefinition();
G4double theLife = theParticleDef->GetPDGLifeTime();
#ifdef G4VERBOSE
if (GetVerboseLevel() > 2) {
G4cout << "G4RadioactiveDecay::GetMeanLifeTime() " << G4endl;
G4cout << "KineticEnergy: " << theParticle->GetKineticEnergy()/GeV
<< " GeV, Mass: " << theParticle->GetMass()/GeV
<< " GeV, Life time: " << theLife/ns << " ns " << G4endl;
}
#endif
if (theParticleDef->GetPDGStable()) {meanlife = DBL_MAX;}
else if (theLife < 0.0) {meanlife = DBL_MAX;}
else {meanlife = theLife;}
// Set meanlife to zero for excited istopes which are not in the
// RDM database
if (((const G4Ions*)(theParticleDef))->GetExcitationEnergy() > 0. &&
meanlife == DBL_MAX) {meanlife = 0.;}
#ifdef G4VERBOSE
if (GetVerboseLevel() > 2)
G4cout << " mean life time: " << meanlife/s << " s " << G4endl;
#endif
return meanlife;
}
////////////////////////////////////////////////////////////////////////////////
// //
// GetMeanFreePath for decay in flight //
// //
////////////////////////////////////////////////////////////////////////////////
G4double G4RadioactiveDecay::GetMeanFreePath(const G4Track& aTrack, G4double,
G4ForceCondition*)
{
const G4DynamicParticle* aParticle = aTrack.GetDynamicParticle();
const G4ParticleDefinition* aParticleDef = aParticle->GetDefinition();
G4double tau = aParticleDef->GetPDGLifeTime();
G4double aMass = aParticle->GetMass();
#ifdef G4VERBOSE
if (GetVerboseLevel() > 2) {
G4cout << "G4RadioactiveDecay::GetMeanFreePath() " << G4endl;
G4cout << " KineticEnergy: " << aParticle->GetKineticEnergy()/GeV
<< " GeV, Mass: " << aMass/GeV << " GeV, tau: " << tau << " ns "
<< G4endl;
}
#endif
G4double pathlength = DBL_MAX;
if (tau != -1) {
// Ion can decay
if (tau < -1000.0) {
pathlength = DBL_MIN; // nuclide had very short lifetime or wasn't in table
} else if (tau < 0.0) {
G4cout << aParticleDef->GetParticleName() << " has lifetime " << tau << G4endl;
G4ExceptionDescription ed;
ed << "Ion has negative lifetime " << tau
<< " but is not stable. Setting mean free path to DBL_MAX" << G4endl;
G4Exception("G4RadioactiveDecay::GetMeanFreePath()", "HAD_RDM_011",
JustWarning, ed);
pathlength = DBL_MAX;
} else {
// Calculate mean free path
G4double betaGamma = aParticle->GetTotalMomentum()/aMass;
pathlength = c_light*tau*betaGamma;
if (pathlength < DBL_MIN) {
pathlength = DBL_MIN;
#ifdef G4VERBOSE
if (GetVerboseLevel() > 2) {
G4cout << "G4Decay::GetMeanFreePath: "
<< aParticleDef->GetParticleName()
<< " stops, kinetic energy = "
<< aParticle->GetKineticEnergy()/keV <<" keV " << G4endl;
}
#endif
}
}
}
#ifdef G4VERBOSE
if (GetVerboseLevel() > 2) {
G4cout << "mean free path: "<< pathlength/m << " m" << G4endl;
}
#endif
return pathlength;
}
////////////////////////////////////////////////////////////////////////////////
// //
// BuildPhysicsTable - initialization of atomic de-excitation //
// //
////////////////////////////////////////////////////////////////////////////////
void G4RadioactiveDecay::BuildPhysicsTable(const G4ParticleDefinition&)
{
if (!isInitialised) {
isInitialised = true;
#ifdef G4VERBOSE
if(G4HadronicParameters::Instance()->GetVerboseLevel() > 0 &&
G4Threading::IsMasterThread()) { StreamInfo(G4cout, "\n"); }
#endif
}
G4HadronicProcessStore::
Instance()->RegisterParticleForExtraProcess(this,G4GenericIon::GenericIon());
}
////////////////////////////////////////////////////////////////////////////////
// //
// StreamInfo - stream out parameters //
// //
////////////////////////////////////////////////////////////////////////////////
void
G4RadioactiveDecay::StreamInfo(std::ostream& os, const G4String& endline)
{
G4DeexPrecoParameters* deex =
G4NuclearLevelData::GetInstance()->GetParameters();
G4EmParameters* emparam = G4EmParameters::Instance();
G4int prec = os.precision(5);
os << "======================================================================"
<< endline;
os << "====== Radioactive Decay Physics Parameters ======="
<< endline;
os << "======================================================================"
<< endline;
os << "Max life time "
<< deex->GetMaxLifeTime()/CLHEP::ps << " ps" << endline;
os << "Internal e- conversion flag "
<< deex->GetInternalConversionFlag() << endline;
os << "Stored internal conversion coefficients "
<< deex->StoreICLevelData() << endline;
os << "Enable correlated gamma emission "
<< deex->CorrelatedGamma() << endline;
os << "Max 2J for sampling of angular correlations "
<< deex->GetTwoJMAX() << endline;
os << "Atomic de-excitation enabled "
<< emparam->Fluo() << endline;
os << "Auger electron emission enabled "
<< emparam->Auger() << endline;
os << "Check EM cuts disabled for atomic de-excitation "
<< emparam->DeexcitationIgnoreCut() << endline;
os << "Use Bearden atomic level energies "
<< emparam->BeardenFluoDir() << endline;
os << "Use ANSTO fluorescence model "
<< emparam->ANSTOFluoDir() << endline;
os << "Threshold for very long decay time at rest "
<< fThresholdForVeryLongDecayTime/CLHEP::ns << " ns" << endline;
os << "======================================================================"
<< G4endl;
os.precision(prec);
}
////////////////////////////////////////////////////////////////////////////////
// //
// LoadDecayTable loads the decay scheme from the RadioactiveDecay database //
// for the parent nucleus. //
// //
////////////////////////////////////////////////////////////////////////////////
G4DecayTable*
G4RadioactiveDecay::LoadDecayTable(const G4ParticleDefinition& theParentNucleus)
{
// Generate input data file name using Z and A of the parent nucleus
// file containing radioactive decay data.
G4int A = ((const G4Ions*)(&theParentNucleus))->GetAtomicMass();
G4int Z = ((const G4Ions*)(&theParentNucleus))->GetAtomicNumber();
G4double levelEnergy = ((const G4Ions*)(&theParentNucleus))->GetExcitationEnergy();
G4Ions::G4FloatLevelBase floatingLevel =
((const G4Ions*)(&theParentNucleus))->GetFloatLevelBase();
#ifdef G4MULTITHREADED
G4AutoLock lk(&G4RadioactiveDecay::radioactiveDecayMutex);
G4String key = theParentNucleus.GetParticleName();
DecayTableMap::iterator master_table_ptr = master_dkmap->find(key);
if (master_table_ptr != master_dkmap->end() ) { // If table is there
return master_table_ptr->second;
}
#endif
//Check if data have been provided by the user
G4String file = theUserRadioactiveDataFiles[1000*A+Z];
if (file == "") {
std::ostringstream os;
os << dirPath << "/z" << Z << ".a" << A << '\0';
file = os.str();
}
G4DecayTable* theDecayTable = new G4DecayTable();
G4bool found(false); // True if energy level matches one in table
std::ifstream DecaySchemeFile;
DecaySchemeFile.open(file);
if (DecaySchemeFile.good()) {
// Initialize variables used for reading in radioactive decay data
G4bool floatMatch(false);
const G4int nMode = G4RadioactiveDecayModeSize;
G4double modeTotalBR[nMode] = {0.0};
G4double modeSumBR[nMode];
for (G4int i = 0; i < nMode; i++) {
modeSumBR[i] = 0.0;
}
char inputChars[120]={' '};
G4String inputLine;
G4String recordType("");
G4String floatingFlag("");
G4String daughterFloatFlag("");
G4Ions::G4FloatLevelBase daughterFloatLevel;
G4RadioactiveDecayMode theDecayMode;
G4double decayModeTotal(0.0);
G4double parentExcitation(0.0);
G4double a(0.0);
G4double b(0.0);
G4double c(0.0);
G4double dummy(0.0);
G4BetaDecayType betaType(allowed);
// Loop through each data file record until you identify the decay
// data relating to the nuclide of concern.
G4bool complete(false); // bool insures only one set of values read for any
// given parent energy level
G4int loop = 0;
while (!complete && !DecaySchemeFile.getline(inputChars, 120).eof()) { /* Loop checking, 01.09.2015, D.Wright */
loop++;
if (loop > 100000) {
G4Exception("G4RadioactiveDecay::LoadDecayTable()", "HAD_RDM_100",
JustWarning, "While loop count exceeded");
break;
}
inputLine = inputChars;
G4StrUtil::rstrip(inputLine);
if (inputChars[0] != '#' && inputLine.length() != 0) {
std::istringstream tmpStream(inputLine);
if (inputChars[0] == 'P') {
// Nucleus is a parent type. Check excitation level to see if it
// matches that of theParentNucleus
tmpStream >> recordType >> parentExcitation >> floatingFlag >> dummy;
// "dummy" takes the place of half-life
// Now read in from ENSDFSTATE in particle category
if (found) {
complete = true;
} else {
// Take first level which matches excitation energy regardless of floating level
found = (std::abs(parentExcitation*keV - levelEnergy) < levelTolerance);
if (floatingLevel != noFloat) {
// If floating level specificed, require match of both energy and floating level
floatMatch = (floatingLevel == G4Ions::FloatLevelBase(floatingFlag.back()) );
if (!floatMatch) found = false;
}
}
} else if (found) {
// The right part of the radioactive decay data file has been found. Search
// through it to determine the mode of decay of the subsequent records.
// Store for later the total decay probability for each decay mode
if (inputLine.length() < 72) {
tmpStream >> theDecayMode >> dummy >> decayModeTotal;
switch (theDecayMode) {
case IT:
{
G4ITDecay* anITChannel = new G4ITDecay(&theParentNucleus, decayModeTotal,
0.0, 0.0, photonEvaporation);
// anITChannel->SetHLThreshold(halflifethreshold);
anITChannel->SetARM(applyARM);
theDecayTable->Insert(anITChannel);
// anITChannel->DumpNuclearInfo();
}
break;
case BetaMinus:
modeTotalBR[BetaMinus] = decayModeTotal; break;
case BetaPlus:
modeTotalBR[BetaPlus] = decayModeTotal; break;
case KshellEC:
modeTotalBR[KshellEC] = decayModeTotal; break;
case LshellEC:
modeTotalBR[LshellEC] = decayModeTotal; break;
case MshellEC:
modeTotalBR[MshellEC] = decayModeTotal; break;
case NshellEC:
modeTotalBR[NshellEC] = decayModeTotal; break;
case Alpha:
modeTotalBR[Alpha] = decayModeTotal; break;
case Proton:
modeTotalBR[Proton] = decayModeTotal; break;
case Neutron:
modeTotalBR[Neutron] = decayModeTotal; break;
case SpFission:
modeTotalBR[SpFission] = decayModeTotal; break;
case BDProton:
/* Not yet implemented */ break;
case BDNeutron:
/* Not yet implemented */ break;
case Beta2Minus:
/* Not yet implemented */ break;
case Beta2Plus:
/* Not yet implemented */ break;
case Proton2:
/* Not yet implemented */ break;
case Neutron2:
/* Not yet implemented */ break;
case Triton:
modeTotalBR[Triton] = decayModeTotal; break;
case RDM_ERROR:
default:
G4Exception("G4RadioactiveDecay::LoadDecayTable()", "HAD_RDM_000",
FatalException, "Selected decay mode does not exist");
} // switch
} else {
if (inputLine.length() < 84) {
tmpStream >> theDecayMode >> a >> daughterFloatFlag >> b >> c;
betaType = allowed;
} else {
tmpStream >> theDecayMode >> a >> daughterFloatFlag >> b >> c >> betaType;
}
// Allowed transitions are the default. Forbidden transitions are
// indicated in the last column.
a /= 1000.;
c /= 1000.;
b /= 100.;
daughterFloatLevel = G4Ions::FloatLevelBase(daughterFloatFlag.back());
switch (theDecayMode) {
case BetaMinus:
{
G4BetaMinusDecay* aBetaMinusChannel =
new G4BetaMinusDecay(&theParentNucleus, b, c*MeV, a*MeV,
daughterFloatLevel, betaType);
// aBetaMinusChannel->DumpNuclearInfo();
// aBetaMinusChannel->SetHLThreshold(halflifethreshold);
theDecayTable->Insert(aBetaMinusChannel);
modeSumBR[BetaMinus] += b;
}
break;
case BetaPlus:
{
G4BetaPlusDecay* aBetaPlusChannel =
new G4BetaPlusDecay(&theParentNucleus, b, c*MeV, a*MeV,
daughterFloatLevel, betaType);
// aBetaPlusChannel->DumpNuclearInfo();
// aBetaPlusChannel->SetHLThreshold(halflifethreshold);
theDecayTable->Insert(aBetaPlusChannel);
modeSumBR[BetaPlus] += b;
}
break;
case KshellEC: // K-shell electron capture
{
G4ECDecay* aKECChannel =
new G4ECDecay(&theParentNucleus, b, c*MeV, a*MeV,
daughterFloatLevel, KshellEC);
// aKECChannel->DumpNuclearInfo();
// aKECChannel->SetHLThreshold(halflifethreshold);
aKECChannel->SetARM(applyARM);
theDecayTable->Insert(aKECChannel);
modeSumBR[KshellEC] += b;
}
break;
case LshellEC: // L-shell electron capture
{
G4ECDecay* aLECChannel =
new G4ECDecay(&theParentNucleus, b, c*MeV, a*MeV,
daughterFloatLevel, LshellEC);
// aLECChannel->DumpNuclearInfo();
// aLECChannel->SetHLThreshold(halflifethreshold);
aLECChannel->SetARM(applyARM);
theDecayTable->Insert(aLECChannel);
modeSumBR[LshellEC] += b;
}
break;
case MshellEC: // M-shell electron capture
{
G4ECDecay* aMECChannel =
new G4ECDecay(&theParentNucleus, b, c*MeV, a*MeV,
daughterFloatLevel, MshellEC);
// aMECChannel->DumpNuclearInfo();
// aMECChannel->SetHLThreshold(halflifethreshold);
aMECChannel->SetARM(applyARM);
theDecayTable->Insert(aMECChannel);
modeSumBR[MshellEC] += b;
}
break;
case NshellEC: // N-shell electron capture
{
G4ECDecay* aNECChannel =
new G4ECDecay(&theParentNucleus, b, c*MeV, a*MeV,
daughterFloatLevel, NshellEC);
// aNECChannel->DumpNuclearInfo();
// aNECChannel->SetHLThreshold(halflifethreshold);
aNECChannel->SetARM(applyARM);
theDecayTable->Insert(aNECChannel);
modeSumBR[NshellEC] += b;
}
break;
case Alpha:
{
G4AlphaDecay* anAlphaChannel =
new G4AlphaDecay(&theParentNucleus, b, c*MeV, a*MeV,
daughterFloatLevel);
// anAlphaChannel->DumpNuclearInfo();
// anAlphaChannel->SetHLThreshold(halflifethreshold);
theDecayTable->Insert(anAlphaChannel);
modeSumBR[Alpha] += b;
}
break;
case Proton:
{
G4ProtonDecay* aProtonChannel =
new G4ProtonDecay(&theParentNucleus, b, c*MeV, a*MeV,
daughterFloatLevel);
// aProtonChannel->DumpNuclearInfo();
// aProtonChannel->SetHLThreshold(halflifethreshold);
theDecayTable->Insert(aProtonChannel);
modeSumBR[Proton] += b;
}
break;
case Neutron:
{
G4NeutronDecay* aNeutronChannel =
new G4NeutronDecay(&theParentNucleus, b, c*MeV, a*MeV,
daughterFloatLevel);
// aNeutronChannel->DumpNuclearInfo();
// aNeutronChannel->SetHLThreshold(halflifethreshold);
theDecayTable->Insert(aNeutronChannel);
modeSumBR[Neutron] += b;
}
break;
case SpFission:
{
G4SFDecay* aSpontFissChannel =
// new G4SFDecay(&theParentNucleus, decayModeTotal, 0.0, 0.0);
new G4SFDecay(&theParentNucleus, b, c*MeV, a*MeV,
daughterFloatLevel);
theDecayTable->Insert(aSpontFissChannel);
modeSumBR[SpFission] += b;
}
break;
case BDProton:
// Not yet implemented
// G4cout << " beta-delayed proton decay, a = " << a << ", b = " << b << ", c = " << c << G4endl;
break;
case BDNeutron:
// Not yet implemented
// G4cout << " beta-delayed neutron decay, a = " << a << ", b = " << b << ", c = " << c << G4endl;
break;
case Beta2Minus:
// Not yet implemented
// G4cout << " Double beta- decay, a = " << a << ", b = " << b << ", c = " << c << G4endl;
break;
case Beta2Plus:
// Not yet implemented
// G4cout << " Double beta+ decay, a = " << a << ", b = " << b << ", c = " << c << G4endl;
break;
case Proton2:
// Not yet implemented
// G4cout << " Double proton decay, a = " << a << ", b = " << b << ", c = " << c << G4endl;
break;
case Neutron2:
// Not yet implemented
// G4cout << " Double beta- decay, a = " << a << ", b = " << b << ", c = " << c << G4endl;
break;
case Triton:
{
G4TritonDecay* aTritonChannel =
new G4TritonDecay(&theParentNucleus, b, c*MeV, a*MeV,
daughterFloatLevel);
// anAlphaChannel->DumpNuclearInfo();
// anAlphaChannel->SetHLThreshold(halflifethreshold);
theDecayTable->Insert(aTritonChannel);
modeSumBR[Triton] += b;
}
break;
case RDM_ERROR:
default:
G4Exception("G4RadioactiveDecay::LoadDecayTable()", "HAD_RDM_000",
FatalException, "Selected decay mode does not exist");
} // switch
} // line < 72
} // if char == P
} // if char != #
} // While
// Go through the decay table and make sure that the branching ratios are
// correctly normalised.
G4VDecayChannel* theChannel = 0;
G4NuclearDecay* theNuclearDecayChannel = 0;
G4String mode = "";
G4double theBR = 0.0;
for (G4int i = 0; i < theDecayTable->entries(); i++) {
theChannel = theDecayTable->GetDecayChannel(i);
theNuclearDecayChannel = static_cast<G4NuclearDecay*>(theChannel);
theDecayMode = theNuclearDecayChannel->GetDecayMode();
if (theDecayMode != IT) {
theBR = theChannel->GetBR();
theChannel->SetBR(theBR*modeTotalBR[theDecayMode]/modeSumBR[theDecayMode]);
}
}
} // decay file exists
DecaySchemeFile.close();
if (!found && levelEnergy > 0) {
// Case where IT cascade for excited isotopes has no entries in RDM database
// Decay mode is isomeric transition.
G4ITDecay* anITChannel = new G4ITDecay(&theParentNucleus, 1.0, 0.0, 0.0,
photonEvaporation);
// anITChannel->SetHLThreshold(halflifethreshold);
anITChannel->SetARM(applyARM);
theDecayTable->Insert(anITChannel);
}
if (theDecayTable && GetVerboseLevel() > 1) {
theDecayTable->DumpInfo();
}
#ifdef G4MULTITHREADED
//(*master_dkmap)[key] = theDecayTable; // store in master library
#endif
return theDecayTable;
}
void
G4RadioactiveDecay::AddUserDecayDataFile(G4int Z, G4int A, G4String filename)
{
if (Z < 1 || A < 2) G4cout << "Z and A not valid!" << G4endl;
std::ifstream DecaySchemeFile(filename);
if (DecaySchemeFile) {
G4int ID_ion = A*1000 + Z;
theUserRadioactiveDataFiles[ID_ion] = filename;
} else {
G4ExceptionDescription ed;
ed << filename << " does not exist! " << G4endl;
G4Exception("G4RadioactiveDecay::AddUserDecayDataFile()", "HAD_RDM_001",
FatalException, ed);
}
}
////////////////////////////////////////////////////////////////////////////////
// //
// DecayIt //
// //
////////////////////////////////////////////////////////////////////////////////
G4VParticleChange*
G4RadioactiveDecay::DecayIt(const G4Track& theTrack, const G4Step&)
{
// Initialize G4ParticleChange object, get particle details and decay table
fParticleChangeForRadDecay.Initialize(theTrack);
fParticleChangeForRadDecay.ProposeWeight(theTrack.GetWeight());
const G4DynamicParticle* theParticle = theTrack.GetDynamicParticle();
const G4ParticleDefinition* theParticleDef = theParticle->GetDefinition();
// First check whether RDM applies to the current logical volume
if (!isAllVolumesMode) {
if (!std::binary_search(ValidVolumes.begin(), ValidVolumes.end(),
theTrack.GetVolume()->GetLogicalVolume()->GetName())) {
#ifdef G4VERBOSE
if (GetVerboseLevel()>1) {
G4cout <<"G4RadioactiveDecay::DecayIt : "
<< theTrack.GetVolume()->GetLogicalVolume()->GetName()
<< " is not selected for the RDM"<< G4endl;
G4cout << " There are " << ValidVolumes.size() << " volumes" << G4endl;
G4cout << " The Valid volumes are " << G4endl;
for (size_t i = 0; i< ValidVolumes.size(); i++)
G4cout << ValidVolumes[i] << G4endl;
}
#endif
fParticleChangeForRadDecay.SetNumberOfSecondaries(0);
// Kill the parent particle.
fParticleChangeForRadDecay.ProposeTrackStatus(fStopAndKill) ;
fParticleChangeForRadDecay.ProposeLocalEnergyDeposit(0.0);
ClearNumberOfInteractionLengthLeft();
return &fParticleChangeForRadDecay;
}
}
// Now check if particle is valid for RDM
if (!(IsApplicable(*theParticleDef) ) ) {
// Particle is not an ion or is outside the nucleuslimits for decay
#ifdef G4VERBOSE
if (GetVerboseLevel() > 1) {
G4cout << "G4RadioactiveDecay::DecayIt : "
<< theParticleDef->GetParticleName()
<< " is not an ion or is outside (Z,A) limits set for the decay. "
<< " Set particle change accordingly. "
<< G4endl;
}
#endif
fParticleChangeForRadDecay.SetNumberOfSecondaries(0);
// Kill the parent particle
fParticleChangeForRadDecay.ProposeTrackStatus(fStopAndKill) ;
fParticleChangeForRadDecay.ProposeLocalEnergyDeposit(0.0);
ClearNumberOfInteractionLengthLeft();
return &fParticleChangeForRadDecay;
}
G4DecayTable* theDecayTable = GetDecayTable(theParticleDef);
if (theDecayTable == 0 || theDecayTable->entries() == 0) {
// No data in the decay table. Set particle change parameters
// to indicate this.
#ifdef G4VERBOSE
if (GetVerboseLevel() > 1) {
G4cout << "G4RadioactiveDecay::DecayIt : "
<< "decay table not defined for "
<< theParticleDef->GetParticleName()
<< ". Set particle change accordingly. "
<< G4endl;
}
#endif
fParticleChangeForRadDecay.SetNumberOfSecondaries(0);
// Kill the parent particle.
fParticleChangeForRadDecay.ProposeTrackStatus(fStopAndKill) ;
fParticleChangeForRadDecay.ProposeLocalEnergyDeposit(0.0);
ClearNumberOfInteractionLengthLeft();
return &fParticleChangeForRadDecay;
} else {
// Data found. Try to decay nucleus
/*
G4double energyDeposit = 0.0;
G4double finalGlobalTime = theTrack.GetGlobalTime();
G4double finalLocalTime = theTrack.GetLocalTime();
G4int index;
G4ThreeVector currentPosition;
currentPosition = theTrack.GetPosition();
G4DecayProducts* products = DoDecay(*theParticleDef);
// If the product is the same as the input kill the track if
// necessary to prevent infinite loop (11/05/10, F.Lei)
if (products->entries() == 1) {
fParticleChangeForRadDecay.SetNumberOfSecondaries(0);
fParticleChangeForRadDecay.ProposeTrackStatus(fStopAndKill);
fParticleChangeForRadDecay.ProposeLocalEnergyDeposit(0.0);
ClearNumberOfInteractionLengthLeft();
return &fParticleChangeForRadDecay;
}
// Get parent particle information and boost the decay products to the
// laboratory frame based on this information.
// The Parent Energy used for the boost should be the total energy of
// the nucleus of the parent ion without the energy of the shell electrons
// (correction for bug 1359 by L. Desorgher)
G4double ParentEnergy = theParticle->GetKineticEnergy()
+ theParticle->GetParticleDefinition()->GetPDGMass();
G4ThreeVector ParentDirection(theParticle->GetMomentumDirection());
if (theTrack.GetTrackStatus() == fStopButAlive) {
// This condition seems to be always True, further investigation is needed
// (L.Desorgher)
// The particle is decayed at rest.
// since the time is still for rest particle in G4 we need to add the
// additional time lapsed between the particle come to rest and the
// actual decay. This time is simply sampled with the mean-life of
// the particle. But we need to protect the case PDGTime < 0.
// (F.Lei 11/05/10)
G4double temptime = -std::log( G4UniformRand())
*theParticleDef->GetPDGLifeTime();
if (temptime < 0.) temptime = 0.;
finalGlobalTime += temptime;
finalLocalTime += temptime;
energyDeposit += theParticle->GetKineticEnergy();
}
products->Boost(ParentEnergy, ParentDirection);
// Add products in theParticleChangeForRadDecay.
G4int numberOfSecondaries = products->entries();
fParticleChangeForRadDecay.SetNumberOfSecondaries(numberOfSecondaries);
#ifdef G4VERBOSE
if (GetVerboseLevel()>1) {
G4cout <<"G4RadioactiveDecay::DecayIt : Decay vertex :";
G4cout <<" Time: " <<finalGlobalTime/ns <<"[ns]";
G4cout <<" X:" <<(theTrack.GetPosition()).x() /cm <<"[cm]";
G4cout <<" Y:" <<(theTrack.GetPosition()).y() /cm <<"[cm]";
G4cout <<" Z:" <<(theTrack.GetPosition()).z() /cm <<"[cm]";
G4cout << G4endl;
G4cout <<"G4Decay::DecayIt : decay products in Lab. Frame" <<G4endl;
products->DumpInfo();
products->IsChecked();
}
#endif
for (index=0; index < numberOfSecondaries; index++) {
G4Track* secondary = new G4Track(products->PopProducts(),
finalGlobalTime, currentPosition);
secondary->SetGoodForTrackingFlag();
secondary->SetTouchableHandle(theTrack.GetTouchableHandle());
fParticleChangeForRadDecay.AddSecondary(secondary);
}
delete products;
// Kill the parent particle
fParticleChangeForRadDecay.ProposeTrackStatus(fStopAndKill) ;
fParticleChangeForRadDecay.ProposeLocalEnergyDeposit(energyDeposit);
fParticleChangeForRadDecay.ProposeLocalTime(finalLocalTime);
// Reset NumberOfInteractionLengthLeft.
ClearNumberOfInteractionLengthLeft();
*/
// Decay without variance reduction
DecayAnalog(theTrack);
return &fParticleChangeForRadDecay ;
}
}
void G4RadioactiveDecay::DecayAnalog(const G4Track& theTrack)
{
const G4DynamicParticle* theParticle = theTrack.GetDynamicParticle();
const G4ParticleDefinition* theParticleDef = theParticle->GetDefinition();
G4DecayProducts* products = DoDecay(*theParticleDef);
// Check if the product is the same as input and kill the track if
// necessary to prevent infinite loop (11/05/10, F.Lei)
if (products->entries() == 1) {
fParticleChangeForRadDecay.SetNumberOfSecondaries(0);
fParticleChangeForRadDecay.ProposeTrackStatus(fStopAndKill);
fParticleChangeForRadDecay.ProposeLocalEnergyDeposit(0.0);
ClearNumberOfInteractionLengthLeft();
delete products;
return;
}
G4double energyDeposit = 0.0;
G4double finalGlobalTime = theTrack.GetGlobalTime();
G4double finalLocalTime = theTrack.GetLocalTime();
// Get parent particle information and boost the decay products to the
// laboratory frame
// ParentEnergy used for the boost should be the total energy of the nucleus
// of the parent ion without the energy of the shell electrons
// (correction for bug 1359 by L. Desorgher)
G4double ParentEnergy = theParticle->GetKineticEnergy()
+ theParticle->GetParticleDefinition()->GetPDGMass();
G4ThreeVector ParentDirection(theParticle->GetMomentumDirection());
if (theTrack.GetTrackStatus() == fStopButAlive) {
// this condition seems to be always True, further investigation is needed (L.Desorgher)
// The particle is decayed at rest
// Since the time is for the particle at rest, need to add additional time
// lapsed between particle coming to rest and the actual decay. This time
// is sampled with the mean-life of the particle. Need to protect the case
// PDGTime < 0. (F.Lei 11/05/10)
G4double temptime = -std::log(G4UniformRand() ) *
theParticleDef->GetPDGLifeTime();
if (temptime < 0.) temptime = 0.;
finalGlobalTime += temptime;
finalLocalTime += temptime;
energyDeposit += theParticle->GetKineticEnergy();
// Kill the parent particle, and ignore its decay, if it decays later than the
// threshold fThresholdForVeryLongDecayTime (whose default value corresponds
// to more than twice the age of the universe).
// This kind of cut has been introduced (in April 2021) in order to avoid to
// account energy depositions happening after many billions of years in
// ordinary materials used in calorimetry, in particular Tungsten and Lead
// (via their natural unstable, but very long lived, isotopes, such as
// W183, W180 and Pb204).
// Note that the cut is not on the average, mean lifetime, but on the actual
// sampled global decay time.
if ( finalGlobalTime > fThresholdForVeryLongDecayTime ) {
fParticleChangeForRadDecay.SetNumberOfSecondaries(0);
fParticleChangeForRadDecay.ProposeTrackStatus(fStopAndKill) ;
fParticleChangeForRadDecay.ProposeLocalEnergyDeposit(0.0);
ClearNumberOfInteractionLengthLeft();
delete products;
return;
}
}
products->Boost(ParentEnergy, ParentDirection);
// Add products in theParticleChangeForRadDecay.
G4int numberOfSecondaries = products->entries();
fParticleChangeForRadDecay.SetNumberOfSecondaries(numberOfSecondaries);
if (GetVerboseLevel() > 1) {
G4cout << "G4RadioactiveDecay::DecayAnalog: Decay vertex :";
G4cout << " Time: " << finalGlobalTime/ns << "[ns]";
G4cout << " X:" << (theTrack.GetPosition()).x() /cm << "[cm]";
G4cout << " Y:" << (theTrack.GetPosition()).y() /cm << "[cm]";
G4cout << " Z:" << (theTrack.GetPosition()).z() /cm << "[cm]";
G4cout << G4endl;
G4cout << "G4Decay::DecayIt : decay products in Lab. Frame" << G4endl;
products->DumpInfo();
products->IsChecked();
}
const G4int modelID_forIT = G4PhysicsModelCatalog::GetModelID( "model_RDM_IT" );
G4int modelID = modelID_forIT + 10*theRadDecayMode;
const G4int modelID_forAtomicRelaxation =
G4PhysicsModelCatalog::GetModelID( "model_RDM_AtomicRelaxation" );
for ( G4int index = 0; index < numberOfSecondaries; ++index ) {
G4Track* secondary = new G4Track( products->PopProducts(), finalGlobalTime,
theTrack.GetPosition() );
secondary->SetWeight( theTrack.GetWeight() );
secondary->SetCreatorModelID( modelID );
// Change for atomics relaxation
if ( theRadDecayMode == IT && index > 0 ) {
if ( index == numberOfSecondaries-1 ) {
secondary->SetCreatorModelID( modelID_forIT );
} else {
secondary->SetCreatorModelID( modelID_forAtomicRelaxation) ;
}
} else if ( theRadDecayMode >= KshellEC && theRadDecayMode <= NshellEC &&
index < numberOfSecondaries-1 ) {
secondary->SetCreatorModelID( modelID_forAtomicRelaxation );
}
secondary->SetGoodForTrackingFlag();
secondary->SetTouchableHandle( theTrack.GetTouchableHandle() );
fParticleChangeForRadDecay.AddSecondary( secondary );
}
delete products;
// Kill the parent particle
fParticleChangeForRadDecay.ProposeTrackStatus(fStopAndKill) ;
fParticleChangeForRadDecay.ProposeLocalEnergyDeposit(energyDeposit);
fParticleChangeForRadDecay.ProposeLocalTime(finalLocalTime);
// Reset NumberOfInteractionLengthLeft.
ClearNumberOfInteractionLengthLeft();
}
G4DecayProducts*
G4RadioactiveDecay::DoDecay(const G4ParticleDefinition& theParticleDef)
{
G4DecayProducts* products = 0;
G4DecayTable* theDecayTable = GetDecayTable(&theParticleDef);
// Choose a decay channel.
// G4DecayTable::SelectADecayChannel checks to see if sum of daughter masses
// exceeds parent mass. Pass it the parent mass + maximum Q value to account
// for difference in mass defect.
G4double parentPlusQ = theParticleDef.GetPDGMass() + 30.*MeV;
G4VDecayChannel* theDecayChannel = theDecayTable->SelectADecayChannel(parentPlusQ);
if (theDecayChannel == 0) {
// Decay channel not found.
G4ExceptionDescription ed;
ed << " Cannot determine decay channel for " << theParticleDef.GetParticleName() << G4endl;
G4Exception("G4RadioactiveDecay::DoDecay", "HAD_RDM_013",
FatalException, ed);
} else {
// A decay channel has been identified, so execute the DecayIt.
#ifdef G4VERBOSE
if (GetVerboseLevel() > 1) {
G4cout << "G4RadioactiveDecay::DoIt : selected decay channel addr: "
<< theDecayChannel << G4endl;
}
#endif
theRadDecayMode = (static_cast<G4NuclearDecay*>(theDecayChannel))->GetDecayMode();
products = theDecayChannel->DecayIt(theParticleDef.GetPDGMass() );
// Apply directional bias if requested by user
CollimateDecay(products);
}
return products;
}
// Apply directional bias for "visible" daughters (e+-, gamma, n, p, alpha)
void G4RadioactiveDecay::CollimateDecay(G4DecayProducts* products) {
if (origin == forceDecayDirection) return; // No collimation requested
if (180.*deg == forceDecayHalfAngle) return;
if (0 == products || 0 == products->entries()) return;
#ifdef G4VERBOSE
if (GetVerboseLevel() > 1) G4cout << "Begin of CollimateDecay..." << G4endl;
#endif
// Particles suitable for directional biasing (for if-blocks below)
static const G4ParticleDefinition* electron = G4Electron::Definition();
static const G4ParticleDefinition* positron = G4Positron::Definition();
static const G4ParticleDefinition* neutron = G4Neutron::Definition();
static const G4ParticleDefinition* gamma = G4Gamma::Definition();
static const G4ParticleDefinition* alpha = G4Alpha::Definition();
static const G4ParticleDefinition* triton = G4Triton::Definition();
static const G4ParticleDefinition* proton = G4Proton::Definition();
G4ThreeVector newDirection; // Re-use to avoid memory churn
for (G4int i=0; i<products->entries(); i++) {
G4DynamicParticle* daughter = (*products)[i];
const G4ParticleDefinition* daughterType =
daughter->GetParticleDefinition();
if (daughterType == electron || daughterType == positron ||
daughterType == neutron || daughterType == gamma ||
daughterType == alpha || daughterType == triton || daughterType == proton) CollimateDecayProduct(daughter);
}
}
void G4RadioactiveDecay::CollimateDecayProduct(G4DynamicParticle* daughter) {
#ifdef G4VERBOSE
if (GetVerboseLevel() > 1) {
G4cout << "CollimateDecayProduct for daughter "
<< daughter->GetParticleDefinition()->GetParticleName() << G4endl;
}
#endif
G4ThreeVector collimate = ChooseCollimationDirection();
if (origin != collimate) daughter->SetMomentumDirection(collimate);
}
// Choose random direction within collimation cone
G4ThreeVector G4RadioactiveDecay::ChooseCollimationDirection() const {
if (origin == forceDecayDirection) return origin; // Don't do collimation
if (forceDecayHalfAngle == 180.*deg) return origin;
G4ThreeVector dir = forceDecayDirection;
// Return direction offset by random throw
if (forceDecayHalfAngle > 0.) {
// Generate uniform direction around central axis
G4double phi = 2.*pi*G4UniformRand();
G4double cosMin = std::cos(forceDecayHalfAngle);
G4double cosTheta = (1.-cosMin)*G4UniformRand() + cosMin; // [cosMin,1.)
dir.setPhi(dir.phi()+phi);
dir.setTheta(dir.theta()+std::acos(cosTheta));
}
#ifdef G4VERBOSE
if (GetVerboseLevel()>1)
G4cout << " ChooseCollimationDirection returns " << dir << G4endl;
#endif
return dir;
}