From e1f6234fea6594db0991ec320aedaca723830c2a Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Mon, 19 Feb 2024 18:47:12 +0100 Subject: [PATCH 01/82] Creation of a pseudo-transportation actor for photons --- core/opengate_core/opengate_core.cpp | 6 +- ...ateOptnComptSplittingForTransportation.cpp | 200 +++++++++++++ .../GateOptnComptSplittingForTransportation.h | 141 ++++++++++ .../opengate_lib/GateOptnForceFreeFlight.cpp | 129 +++++++++ .../opengate_lib/GateOptnForceFreeFlight.h | 107 +++++++ ...GateOptrComptPseudoTransportationActor.cpp | 264 ++++++++++++++++++ .../GateOptrComptPseudoTransportationActor.h | 121 ++++++++ ...GateOptrComptPseudoTransportationActor.cpp | 19 ++ opengate/actors/actorbuilders.py | 2 + opengate/actors/miscactors.py | 23 ++ .../src/test072_pseudo_transportation.py | 178 ++++++++++++ 11 files changed, 1187 insertions(+), 3 deletions(-) create mode 100644 core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp create mode 100644 core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h create mode 100644 core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp create mode 100644 core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h create mode 100644 core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp create mode 100644 core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h create mode 100644 core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp create mode 100644 opengate/tests/src/test072_pseudo_transportation.py diff --git a/core/opengate_core/opengate_core.cpp b/core/opengate_core/opengate_core.cpp index caa789c86..24f95db23 100644 --- a/core/opengate_core/opengate_core.cpp +++ b/core/opengate_core/opengate_core.cpp @@ -289,10 +289,10 @@ void init_GateSimulationStatisticsActor(py::module &); void init_GatePhaseSpaceActor(py::module &); -// void init_GateComptonSplittingActor(py::module &); - void init_GateOptrComptSplittingActor(py::module &m); +void init_GateOptrComptPseudoTransportationActor(py::module &m); + void init_GateBOptrBremSplittingActor(py::module &m); void init_G4VBiasingOperator(py::module &m); @@ -487,8 +487,8 @@ PYBIND11_MODULE(opengate_core, m) { init_GateLETActor(m); init_GateSimulationStatisticsActor(m); init_GatePhaseSpaceActor(m); - // init_GateComptonSplittingActor(m); init_GateBOptrBremSplittingActor(m); + init_GateOptrComptPseudoTransportationActor(m); init_GateOptrComptSplittingActor(m); init_GateHitsCollectionActor(m); init_GateMotionVolumeActor(m); diff --git a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp b/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp new file mode 100644 index 000000000..bb3ac7112 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp @@ -0,0 +1,200 @@ +// +// ******************************************************************** +// * 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 GateOptnComptSplittingForTransportation.cc +/// \brief Implementation of the GateOptnComptSplittingForTransportation class + +#include "GateOptnComptSplittingForTransportation.h" +#include "G4BiasingProcessInterface.hh" + +#include "G4DynamicParticle.hh" +#include "G4SystemOfUnits.hh" +#include "G4Gamma.hh" +#include "G4ParticleChange.hh" +#include "G4ParticleChangeForGamma.hh" +#include "G4VEmProcess.hh" +#include"G4Gamma.hh" +#include "G4ComptonScattering.hh" +#include "G4PhotoElectricEffect.hh" +#include "G4RayleighScattering.hh" +#include "G4GammaConversion.hh" +#include "G4Exception.hh" +#include "G4TrackStatus.hh" +#include "G4ProcessType.hh" +#include <memory> +#include "G4TrackingManager.hh" + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptnComptSplittingForTransportation::GateOptnComptSplittingForTransportation(G4String name) + : G4VBiasingOperation(name), fSplittingFactor(1), fRussianRoulette(false), fParticleChange() {} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptnComptSplittingForTransportation::~GateOptnComptSplittingForTransportation() { +} + + + +G4VParticleChange *GateOptnComptSplittingForTransportation::ApplyFinalStateBiasing(const G4BiasingProcessInterface *callingProcess, const G4Track *track,const G4Step *step, G4bool &) { + +//Here we generate for the first the compton process, given that this function (ApplyFinalStateBiasing) is called when there is a compton interaction +//Then the interaction location of the compton process will always be the same + + + G4double globalTime = step->GetTrack()->GetGlobalTime(); + const G4ThreeVector position = step->GetPostStepPoint()->GetPosition(); + G4int nCalls = 1; + G4int splittingFactor = ceil(fSplittingFactor); + G4double survivalProbabilitySplitting = 1 - (splittingFactor - fSplittingFactor)/splittingFactor; + G4bool isRightAngle = false; + G4double gammaWeight = 0; + G4int nbSecondaries = 0; + G4VParticleChange* processFinalState = nullptr; + G4ParticleChangeForGamma* castedProcessInitFinalState = nullptr; + + processFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + // In case we don't want to split (a bit faster) i.e no biaising or no splitting low weights particles. + + if ((fSplittingFactor == 1 && fRussianRoulette == false) || track->GetWeight() < fWeightThreshold) + return processFinalState; + + castedProcessInitFinalState = (G4ParticleChangeForGamma*) processFinalState; + nbSecondaries = processFinalState->GetNumberOfSecondaries(); + + + fParticleChange.Initialize(*track); + fParticleChange.ProposeWeight(track->GetWeight()); + fParticleChange.ProposeTrackStatus(castedProcessInitFinalState->GetTrackStatus()); + fParticleChange.ProposeEnergy(castedProcessInitFinalState->GetProposedKineticEnergy()); + fParticleChange.ProposeMomentumDirection(castedProcessInitFinalState->GetProposedMomentumDirection()); + fParticleChange.SetSecondaryWeightByProcess(true); + + + + +//If there is cut on secondary particles, there is a probability that the electron is not simulated +//Then, if the compton process created it, we add the gien electron to the ParticleChange object + if (nbSecondaries == 1) { + G4Track* initElectronTrack = castedProcessInitFinalState->GetSecondary(0); + initElectronTrack->SetWeight(track->GetWeight()); + fParticleChange.AddSecondary(initElectronTrack); + } + + processFinalState->Clear(); + castedProcessInitFinalState->Clear(); + + //There is here the biasing process : + // Since G4VParticleChange class does not allow to retrieve scattered gamma information, we need to cast the type G4ParticleChangeForGamma + // to the G4VParticleChange object. We then call the process (biasWrapper(compt)) fSplittingFactor times (Here, the difference with the other version + // of splitting is the primary particle will be killed and its weight does not count) to generate, at last, fSplittingFactor gamma + // according to the compton interaction process. If the gamma track is ok regarding the russian roulette algorithm (no russian roulette + //, or within the acceptance angle, or not killed by the RR process), we add it to the primary track. + // If an electron is generated (above the range cut), we also generate it. + // A tremendous advantage is there is no need to use by ourself Klein-Nishina formula or other. So, if the physics list used takes into account + // the doppler broadening or other fine effects, this will be also taken into account by the MC simulation. + // PS : The first gamma is then the primary particle, but all the other splitted particle (electron of course AND gamma) must be considered + // as secondary particles, even though generated gamma will not be cut here by the applied cut. + + G4int simulationTrackID = 0; + while (nCalls <= splittingFactor) { + gammaWeight = track->GetWeight() / fSplittingFactor; + G4double initGammaWeight = track->GetWeight(); + G4VParticleChange* processGammaSplittedFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + G4ParticleChangeForGamma* castedProcessGammaSplittedFinalState = (G4ParticleChangeForGamma*) processGammaSplittedFinalState; + const G4ThreeVector momentum = castedProcessGammaSplittedFinalState-> GetProposedMomentumDirection(); + G4double energy = castedProcessGammaSplittedFinalState-> GetProposedKineticEnergy(); + G4double cosTheta = fVectorDirector * castedProcessInitFinalState->GetProposedMomentumDirection(); + G4double theta = std::acos(cosTheta); + G4double splittingProbability = G4UniformRand(); + + if (splittingProbability <= survivalProbabilitySplitting || survivalProbabilitySplitting == 1) { + if ((fRussianRoulette == true) && (theta > fMaxTheta)) { + G4double probability = G4UniformRand(); + if (probability < 1/fSplittingFactor) { + // Specific case where the russian roulette probability is 1/splitting. Each particle generated, with a 1/split probability + //will have a 1/split probability to survive with a final weight of Initial weights * 1/split * split = Initial weight + gammaWeight = gammaWeight*fSplittingFactor; + G4Track* gammaTrack = new G4Track(*track); + gammaTrack->SetWeight(gammaWeight); + gammaTrack->SetKineticEnergy(energy); + gammaTrack->SetMomentumDirection(momentum); + gammaTrack->SetPosition(position); + fParticleChange.AddSecondary(gammaTrack); + simulationTrackID ++; + if (processGammaSplittedFinalState->GetNumberOfSecondaries() == 1) { + G4Track* electronTrack = processGammaSplittedFinalState->GetSecondary(0); + electronTrack->SetWeight(gammaWeight); + fParticleChange.AddSecondary(electronTrack); + simulationTrackID ++; + } + } + } + + + if ((fRussianRoulette == false) || ((fRussianRoulette == true) && (theta <= fMaxTheta))) { + G4Track* gammaTrack = new G4Track(*track); + gammaTrack->SetWeight(gammaWeight); + gammaTrack->SetKineticEnergy(energy); + gammaTrack->SetMomentumDirection(momentum); + gammaTrack->SetPosition(position); + fParticleChange.AddSecondary(gammaTrack); + simulationTrackID ++; + if (processGammaSplittedFinalState->GetNumberOfSecondaries() == 1) { + G4Track* electronTrack = processGammaSplittedFinalState->GetSecondary(0); + electronTrack->SetWeight(gammaWeight); + fParticleChange.AddSecondary(electronTrack); + simulationTrackID ++; + } + } + } + nCalls++; + processGammaSplittedFinalState->Clear(); + castedProcessGammaSplittedFinalState->Clear(); + + + } + //Probe generation, sent in 5 directions, in order to provide informations before the track of the real photons about + //the geometries they will cross. + if (fUseProbes) { + std::vector<G4ThreeVector> v = {{0,0,-1},{0,1,0},{0,-1,0},{1,0,0},{-1,0,0}}; + + for (G4int nbProbe = 0; nbProbe < 5; nbProbe++) { + G4Track* gammaTrack = new G4Track(*track); + gammaTrack->SetGoodForTrackingFlag(1); + gammaTrack->SetWeight(track->GetWeight() / fSplittingFactor); + gammaTrack->SetKineticEnergy(track->GetKineticEnergy()); + gammaTrack->SetMomentumDirection(fRot * v[nbProbe]); + gammaTrack->SetPosition(position); + fParticleChange.AddSecondary(gammaTrack); + simulationTrackID ++; + + } + } + return &fParticleChange; +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h b/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h new file mode 100644 index 000000000..c0d8580aa --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h @@ -0,0 +1,141 @@ +// +// ******************************************************************** +// * 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 GateOptnComptSplittingForTransportation.h +/// \brief Definition of the GateOptnComptSplittingForTransportation class +// + +#ifndef GateOptnComptSplittingForTransportation_h +#define GateOptnComptSplittingForTransportation_h 1 + +#include "G4ParticleChange.hh" +#include "G4VBiasingOperation.hh" + +class GateOptnComptSplittingForTransportation : public G4VBiasingOperation { +public: + // -- Constructor : + GateOptnComptSplittingForTransportation(G4String name); + + // -- destructor: + virtual ~GateOptnComptSplittingForTransportation(); + +public: + // ---------------------------------------------- + // -- Methods from G4VBiasingOperation interface: + // ---------------------------------------------- + // -- Unused: + virtual const G4VBiasingInteractionLaw * + ProvideOccurenceBiasingInteractionLaw(const G4BiasingProcessInterface *, + G4ForceCondition &) { + return 0; + } + + // --Used: + virtual G4VParticleChange * + ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, + const G4Step *, G4bool &); + + // -- Unsued: + virtual G4double DistanceToApplyOperation(const G4Track *, G4double, + G4ForceCondition *) { + return DBL_MAX; + } + virtual G4VParticleChange *GenerateBiasingFinalState(const G4Track *, + const G4Step *) { + return 0; + } + +public: + // ---------------------------------------------- + // -- Additional methods, specific to this class: + // ---------------------------------------------- + // -- Splitting factor: + void SetSplittingFactor(G4double splittingFactor) { + fSplittingFactor = splittingFactor; + } + G4double GetSplittingFactor() const { return fSplittingFactor; } + + +void SetWeightThreshold(G4double weightThreshold) { + fWeightThreshold = weightThreshold; + } + G4double GetWeightThreshold() const { return fWeightThreshold; } + + + void SetRussianRoulette(G4bool russianRoulette){ + fRussianRoulette = russianRoulette; + } + + G4bool GetRussianRoulette() const { return fRussianRoulette; } + + void SetVectorDirector(G4ThreeVector vectorDirector){ + fVectorDirector = vectorDirector; + } + + void SetRotationMatrix(G4RotationMatrix rot){ + fRot = rot; + } + + G4ThreeVector GetVectorDirector() const {return fVectorDirector;} + + + void SetMaxTheta(G4double maxTheta){ + fMaxTheta = maxTheta; + } + + G4double GetMaxTheta() const {return fMaxTheta;} + + void SetUseOfProbes(G4bool p){fUseProbes = p;} + + + + void SetMinWeightOfParticle(G4double minWeightOfParticle){ + fMinWeightOfParticle= minWeightOfParticle; + } + + G4double GetMinWeightOfParticle() const {return fMinWeightOfParticle;} + + + G4VParticleChange* GetParticleChange() { + G4VParticleChange* particleChange = &fParticleChange; + return particleChange;} + + +private: + G4double fSplittingFactor; + G4double fWeightThreshold; + G4ParticleChange fParticleChange; + G4bool fRussianRoulette; + G4ThreeVector fVectorDirector; + G4double fMaxTheta; + G4double fMinWeightOfParticle; + G4bool fUseProbes; + G4RotationMatrix fRot; + //G4DynamicParticle* fSplitParticle; + //G4Track* fGammaTrack; +}; + +#endif diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp new file mode 100644 index 000000000..af0828b4e --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp @@ -0,0 +1,129 @@ +// +// ******************************************************************** +// * 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. * +// ******************************************************************** +// +#include "GateOptnForceFreeFlight.h" +#include "G4ILawForceFreeFlight.hh" +#include "G4BiasingProcessInterface.hh" +#include "G4Step.hh" + + + +// This operator is used to transport the particle without interaction, and then correct the weight of the particle +//according the probablity for the photon to not interact within the matter. To do that, we use a GEANT4 modifed Interaction Law (G4ILawForceFreeFlight), which modify, +//for the biased process the probability to occur : Never. + +GateOptnForceFreeFlight ::GateOptnForceFreeFlight (G4String name) + : G4VBiasingOperation ( name ), + fOperationComplete ( true ) +{ + fForceFreeFlightInteractionLaw = new G4ILawForceFreeFlight("LawForOperation"+name); +} + +GateOptnForceFreeFlight ::~GateOptnForceFreeFlight () +{ + if ( fForceFreeFlightInteractionLaw ) delete fForceFreeFlightInteractionLaw; +} + + + +const G4VBiasingInteractionLaw* GateOptnForceFreeFlight ::ProvideOccurenceBiasingInteractionLaw +( const G4BiasingProcessInterface*, G4ForceCondition& proposeForceCondition ) +{ + fOperationComplete = false; + proposeForceCondition = Forced; + return fForceFreeFlightInteractionLaw; +} + +G4VParticleChange* GateOptnForceFreeFlight ::ApplyFinalStateBiasing( const G4BiasingProcessInterface* callingProcess, + const G4Track* track, + const G4Step* step, + G4bool& forceFinalState) +{ + + + // -- If the track is reaching the volume boundary, its free flight ends. In this case, its zero + // -- weight is brought back to non-zero value: its initial weight is restored by the first + // -- ApplyFinalStateBiasing operation called, and the weight for force free flight is applied + // -- is applied by each operation. + // -- If the track is not reaching the volume boundary, it zero weight flight continues. + fParticleChange.Initialize( *track ); + forceFinalState = true; + + fProposedWeight *= fWeightChange[callingProcess->GetWrappedProcess()->GetProcessName()]; + if (fUseProbes){ + if (track->IsGoodForTracking() ==0){ + + if (fProposedWeight < fRussianRouletteProbability * fMinWeight){ + fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); + return &fParticleChange; + } + + if ((fProposedWeight < fMinWeight) && (fProposedWeight >= fRussianRouletteProbability * fMinWeight)) { + G4double probability = G4UniformRand(); + if (probability > fRussianRouletteProbability){ + fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); + return &fParticleChange; + } + else { + fProposedWeight = fProposedWeight/fRussianRouletteProbability; + } + } + + fParticleChange.ProposeWeight(fProposedWeight); + fOperationComplete = true; + if (track->IsGoodForTracking() ==1){ + fParticleChange.ProposeWeight(fProposedWeight); + fOperationComplete = true; + } + } + } + else { + if (fProposedWeight < fRussianRouletteProbability * fMinWeight){ + fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); + return &fParticleChange; + } + + if ((fProposedWeight < fMinWeight) && (fProposedWeight >= fRussianRouletteProbability * fMinWeight)) { + G4double probability = G4UniformRand(); + if (probability > fRussianRouletteProbability){ + fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); + return &fParticleChange; + } + else { + fProposedWeight = fProposedWeight/fRussianRouletteProbability; + } + } + + fParticleChange.ProposeWeight(fProposedWeight); + fOperationComplete = true; + } +return &fParticleChange; +} + +void GateOptnForceFreeFlight ::AlongMoveBy( const G4BiasingProcessInterface* callingProcess, const G4Step*, G4double weightChange ) + +{ + fWeightChange[callingProcess->GetWrappedProcess()->GetProcessName()] = weightChange; +} diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h new file mode 100644 index 000000000..30c32692d --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h @@ -0,0 +1,107 @@ +// +// ******************************************************************** +// * 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. * +// ******************************************************************** +// +// +// +//--------------------------------------------------------------- +// +// GateOptnForceFreeFlight +// +// Class Description: +// A G4VBiasingOperation physics-based biasing operation. +// If forces the physics process to not act on the track. +// In this implementation (meant for the ForceCollision +// operator) the free flight is done under zero weight for +// the track, and the action is meant to accumulate the weight +// change for making this uninteracting flight, +// cumulatedWeightChange. +// When the track reaches the current volume boundary, its +// weight is restored with value : +// initialWeight * cumulatedWeightChange +// +//--------------------------------------------------------------- +// Initial version Nov. 2013 M. Verderi +#ifndef GateOptnForceFreeFlight_h +#define GateOptnForceFreeFlight_h 1 + +#include "G4VBiasingOperation.hh" +#include "G4ForceCondition.hh" +#include "G4ParticleChange.hh" // -- ยงยง should add a dedicated "weight change only" particle change +class G4ILawForceFreeFlight; + + +class GateOptnForceFreeFlight : public G4VBiasingOperation { +public: + // -- Constructor : + GateOptnForceFreeFlight (G4String name); + // -- destructor: + virtual ~GateOptnForceFreeFlight (); + +public: + // -- Methods from G4VBiasingOperation interface: + // ------------------------------------------- + // -- Used: + virtual const G4VBiasingInteractionLaw* ProvideOccurenceBiasingInteractionLaw( const G4BiasingProcessInterface*, G4ForceCondition& ); + virtual void AlongMoveBy( const G4BiasingProcessInterface*, const G4Step*, G4double ); + virtual G4VParticleChange* ApplyFinalStateBiasing( const G4BiasingProcessInterface*, const G4Track*, const G4Step*, G4bool&); + // -- Unused: + virtual G4double DistanceToApplyOperation( const G4Track*, + G4double, + G4ForceCondition*) {return DBL_MAX;} + virtual G4VParticleChange* GenerateBiasingFinalState( const G4Track*, + const G4Step* ) {return 0;} + + +public: + // -- Additional methods, specific to this class: + // ---------------------------------------------- + // -- return concrete type of interaction law: + G4ILawForceFreeFlight* GetForceFreeFlightLaw() { + return fForceFreeFlightInteractionLaw; + } + // -- initialization for weight: + //void ResetInitialTrackWeight(G4double w) {fInitialTrackWeight = w; fCumulatedWeightChange = 1.0;} + + + void SetMinWeight(G4double w){fMinWeight = w;} + void SetUseOfProbes(G4bool p){fUseProbes = p;} + G4double GetTrackWeight(){return fProposedWeight;} + void SetTrackWeight(G4double w){fProposedWeight = w;} + void SetRussianRouletteProbability(G4double p){fRussianRouletteProbability= p;} + G4bool OperationComplete() const { return fOperationComplete; } + +private: + G4ILawForceFreeFlight* fForceFreeFlightInteractionLaw; + std::map<G4String,G4double> fWeightChange; + G4bool fUseProbes; + G4double fMinWeight, + fRussianRouletteProbability; + G4ParticleChange fParticleChange; + G4bool fOperationComplete; + G4double fProposedWeight; + +}; + +#endif diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp new file mode 100644 index 000000000..0a9d3799b --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp @@ -0,0 +1,264 @@ +// +// ******************************************************************** +// * 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 GateOptrComptPseudoTransportationActor.cc +/// \brief Implementation of the GateOptrComptPseudoTransportationActor class + +#include "GateHelpersDict.h" +#include "GateHelpersImage.h" + +#include "G4BiasingProcessInterface.hh" +#include "G4Gamma.hh" +#include "G4LogicalVolumeStore.hh" +#include "G4PhysicalVolumeStore.hh" +#include "GateOptnComptSplittingForTransportation.h" +#include "GateOptrComptPseudoTransportationActor.h" +#include "G4ProcessManager.hh" +#include "G4VEmProcess.hh" +#include "G4EmCalculator.hh" +#include "G4ProcessVector.hh" +#include "G4ParticleTable.hh" +#include "G4Gamma.hh" +#include "CLHEP/Units/SystemOfUnits.h" +#include "G4TrackStatus.hh" +#include "G4UserTrackingAction.hh" +#include "G4RunManager.hh" + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptrComptPseudoTransportationActor::GateOptrComptPseudoTransportationActor(py::dict &user_info) + : G4VBiasingOperator("ComptSplittingOperator"), + GateVActor(user_info, false) { + fMotherVolumeName = DictGetStr(user_info, "mother"); + fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); + fWeightThreshold = DictGetDouble(user_info, "weight_threshold"); + fRelativeMinWeightOfParticle = DictGetDouble(user_info, "relative_min_weight_of_particle"); + //Since the russian roulette uses as a probability 1/splitting, we need to have a double, + //but the splitting factor provided by the user is logically an int, so we need to change the type. + fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); + fRussianRoulette = DictGetBool(user_info, "russian_roulette"); + fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); + fMaxTheta = DictGetDouble(user_info,"max_theta"); + fRussianRouletteForFreeFlight = DictGetDouble(user_info,"russian_roulette_for_free_flight"); + fFreeFlightOperation = new GateOptnForceFreeFlight("freeFlightOperation"); + fComptSplittingOperation = new GateOptnComptSplittingForTransportation("comptSplittingOperation"); + fUseProbes = DictGetBool(user_info,"use_probes"); + fActions.insert("StartSimulationAction"); + fActions.insert("SteppingAction"); + fActions.insert("BeginOfEventAction"); + fActions.insert("PostUserTrackingAction"); + isSplitted = false; +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +void GateOptrComptPseudoTransportationActor::AttachAllLogicalDaughtersVolumes(G4LogicalVolume* volume){ + AttachTo(volume); + G4int nbOfDaughters = volume->GetNoDaughters(); + if (nbOfDaughters >0 ){ + for (int i = 0; i< nbOfDaughters;i++){ + G4LogicalVolume* logicalDaughtersVolume = volume->GetDaughter(i)->GetLogicalVolume(); + AttachAllLogicalDaughtersVolumes(logicalDaughtersVolume); + } + } +} + +void GateOptrComptPseudoTransportationActor::StartSimulationAction(){ + G4LogicalVolume* biasingVolume = G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + + //Here we need to attach all the daughters and daughters of daughters (...) to the biasing operator. + //To do that, I use the function AttachAllLogicalDaughtersVolumes. + AttachAllLogicalDaughtersVolumes(biasingVolume); + fComptSplittingOperation->SetSplittingFactor(fSplittingFactor); + fComptSplittingOperation->SetWeightThreshold(fWeightThreshold); + fComptSplittingOperation->SetMaxTheta(fMaxTheta); + fComptSplittingOperation->SetRussianRoulette(fRussianRoulette); + fComptSplittingOperation->SetUseOfProbes(fUseProbes); + fFreeFlightOperation->SetRussianRouletteProbability(fRussianRouletteForFreeFlight); + fFreeFlightOperation->SetUseOfProbes(fUseProbes); + + +} + +void GateOptrComptPseudoTransportationActor::StartRun() { + + // The way to behave of the russian roulette is the following : + // we provide a vector director and the theta angle acceptance, where theta = 0 is a vector colinear to the vector director + // Then if the track generated is on the acceptance angle, we add it to the primary track, and if it's not the case, we launch the russian roulette + if (fRotationVectorDirector){ + G4VPhysicalVolume* physBiasingVolume = G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + auto rot = physBiasingVolume -> GetObjectRotationValue(); + fVectorDirector = rot * fVectorDirector; + fComptSplittingOperation->SetRotationMatrix(rot); + } + + fComptSplittingOperation->SetVectorDirector(fVectorDirector); + + +} + + + + +void GateOptrComptPseudoTransportationActor::SteppingAction (G4Step *step) { + + //The stepping action is used to kill particle we have too kill : + // - If the primary particle reach the biased boudary + // - KIll all the probes exiting the volume + // - Kill, if probes, particles wich have a weilght lower than the probes one + + if (fUseProbes) { + if ((fKillOthersParticles) && (step->GetTrack()->IsGoodForTracking() ==0)) + { + step->GetTrack()->SetTrackStatus(G4TrackStatus::fStopAndKill); + } + + if (step->GetPostStepPoint()->GetStepStatus()!= fWorldBoundary){ + if ((step->GetPostStepPoint()->GetPhysicalVolume ()->GetName() == "world") && (step->GetTrack()->IsGoodForTracking() == 1)){ + step->GetTrack()->SetTrackStatus(G4TrackStatus::fStopAndKill); + } + } + } + + if ((isSplitted ==true) && (step->GetPostStepPoint()->GetStepStatus()!= fWorldBoundary)){ + if ((step->GetPostStepPoint()->GetPhysicalVolume ()->GetName() == "world")) + { + step->GetTrack()->SetTrackStatus(G4TrackStatus::fStopAndKill); + isSplitted =false; + } + } + + + + +} + +void GateOptrComptPseudoTransportationActor::BeginOfEventAction(const G4Event *event) { + fKillOthersParticles = false; +} + +void GateOptrComptPseudoTransportationActor::StartTracking(const G4Track *track) { +fInitialWeight = track->GetWeight(); +} + + +//For the following operation the idea is the following : +//All the potential photon processes are biased. If a particle undergoes a compton interaction, we splitted it +//(ComptonSplittingForTransportation operation) and the particle generated are pseudo-transported with the ForceFreeFLight operation +// Since the occurence Biaising operation is called at the beginning of each track, and propose a different way to track the particle +//(with modified physics), it here returns other thing than 0 if we want to pseudo-transport the particle, so if its creatorProcess is the +//modified compton interaction + +G4VBiasingOperation * +GateOptrComptPseudoTransportationActor::ProposeOccurenceBiasingOperation(const G4Track* track, const G4BiasingProcessInterface* callingProcess) +{ + + +if (track->GetCreatorProcess () !=0){ + if (track->GetCreatorProcess ()->GetProcessName() == "biasWrapper(compt)"){ + fFreeFlightOperation->SetMinWeight(fInitialWeight/fRelativeMinWeightOfParticle); + fFreeFlightOperation->SetTrackWeight(track->GetWeight()); + return fFreeFlightOperation; + } + + } + return 0; +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +// Here we call the final state biasing operation called if one of the biased interaction (all photon interaction here) occurs. +//That's why we need here to apply some conditions to just split the initial track. + +G4VBiasingOperation * +GateOptrComptPseudoTransportationActor::ProposeFinalStateBiasingOperation( + const G4Track *track, const G4BiasingProcessInterface *callingProcess) { + G4String callingProcessName = "biasWrapper(compt)"; + + if (callingProcess->GetWrappedProcess()->GetProcessName() == "compt") + { + if (track->GetCreatorProcess() ==0) { + isSplitted = true; + return fComptSplittingOperation; + } + if (track->GetCreatorProcess()-> GetProcessName() != "biasWrapper(compt)"){ + isSplitted = true; + return fComptSplittingOperation; + } + } + /* + */ + if (track->GetCreatorProcess() !=0){ + if(track->GetCreatorProcess()-> GetProcessName() == "biasWrapper(compt)"){ + return callingProcess->GetCurrentOccurenceBiasingOperation(); + } + } + + return 0; + + //return 0; +} + + + +void GateOptrComptPseudoTransportationActor::EndTracking() { +isSplitted =false; +} + + +void GateOptrComptPseudoTransportationActor::PostUserTrackingAction(const G4Track *track) { + +if (fUseProbes){ + if (track->IsGoodForTracking() == 1){ + G4double tmpWeight = fFreeFlightOperation->GetTrackWeight(); + if (NbOfProbe == 1) + { + fKillOthersParticles = false; + weight = tmpWeight; + } + else{ + if (tmpWeight > weight){ + weight = tmpWeight; + } + } + + NbOfProbe ++; + if ((NbOfProbe == 6)){ + if (weight < fInitialWeight/fRelativeMinWeightOfParticle){ + fKillOthersParticles = true; + } + NbOfProbe = 1; + weight = 0; + + } + } +} +} + + + + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h new file mode 100644 index 000000000..e4a7d1e26 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h @@ -0,0 +1,121 @@ +// +// ******************************************************************** +// * 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 GateOptrComptPseudoTransportationActor.h +/// \brief Definition of the GateOptrComptPseudoTransportationActor class +#ifndef GateOptrComptPseudoTransportationActor_h +#define GateOptrComptPseudoTransportationActor_h 1 + +#include "G4VBiasingOperator.hh" +#include "GateOptnForceFreeFlight.h" +#include "G4EmCalculator.hh" + +#include "GateVActor.h" +#include <iostream> +#include <pybind11/stl.h> +namespace py = pybind11; + +class GateOptnComptSplittingForTransportation; + +class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, + public GateVActor { +public: + GateOptrComptPseudoTransportationActor(py::dict &user_info); + virtual ~GateOptrComptPseudoTransportationActor() {} + +public: + // ------------------------- + // Optional from base class: + // ------------------------- + // -- Call at run start: + // virtual void BeginOfRunAction(const G4Run *run); + + // virtual void SteppingAction(G4Step* step); + + // -- Call at each track starting: + // virtual void PreUserTrackingAction( const G4Track* track ); + + G4double fSplittingFactor; + G4double fInitialWeight; + G4double fRelativeMinWeightOfParticle; + G4double fWeightThreshold; + G4bool fBiasPrimaryOnly; + G4bool fBiasOnlyOnce; + G4int fNInteractions = 0; + G4bool fRussianRoulette; + G4double fRussianRouletteForFreeFlight; + G4bool fRotationVectorDirector; + G4ThreeVector fVectorDirector; + G4double fMaxTheta; + G4bool isSplitted; + G4int NbOfTrack = 0; + G4int NbOfProbe = 1; + G4double weight = 0; + G4bool fKillOthersParticles = false; + G4bool fUseProbes = false; + // Unused but mandatory + + virtual void StartSimulationAction(); + virtual void StartRun(); + virtual void StartTracking(const G4Track *); + virtual void PostUserTrackingAction(const G4Track * track); + virtual void SteppingAction(G4Step*); + virtual void BeginOfEventAction(const G4Event*); + virtual void EndTracking(); + + +protected: + // ----------------------------- + // -- Mandatory from base class: + // ----------------------------- + // -- Unused: + void AttachAllLogicalDaughtersVolumes(G4LogicalVolume*); + virtual G4VBiasingOperation *ProposeNonPhysicsBiasingOperation( + const G4Track * /* track */, + const G4BiasingProcessInterface * /* callingProcess */) { + return 0; + } + + + // -- Used: + virtual G4VBiasingOperation *ProposeOccurenceBiasingOperation( + const G4Track * /* track */, + const G4BiasingProcessInterface * /* callingProcess */); + + virtual G4VBiasingOperation *ProposeFinalStateBiasingOperation( + const G4Track *track, const G4BiasingProcessInterface *callingProcess); + +private: + // -- Avoid compiler complaining for (wrong) method shadowing, + // -- this is because other virtual method with same name exists. + using G4VBiasingOperator::OperationApplied; + +private: + GateOptnForceFreeFlight *fFreeFlightOperation ; + GateOptnComptSplittingForTransportation *fComptSplittingOperation ; +}; + +#endif diff --git a/core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp new file mode 100644 index 000000000..ae0344c08 --- /dev/null +++ b/core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp @@ -0,0 +1,19 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ +#include <pybind11/pybind11.h> + +namespace py = pybind11; +#include "G4VBiasingOperator.hh" +#include "GateOptrComptPseudoTransportationActor.h" + +void init_GateOptrComptPseudoTransportationActor(py::module &m) { + + py::class_<GateOptrComptPseudoTransportationActor, G4VBiasingOperator, GateVActor, + std::unique_ptr<GateOptrComptPseudoTransportationActor, py::nodelete>>( + m, "GateOptrComptPseudoTransportationActor") + .def(py::init<py::dict &>()); +} diff --git a/opengate/actors/actorbuilders.py b/opengate/actors/actorbuilders.py index b93c54eb9..66d43925e 100644 --- a/opengate/actors/actorbuilders.py +++ b/opengate/actors/actorbuilders.py @@ -19,6 +19,7 @@ KillActor, BremSplittingActor, ComptSplittingActor, + ComptPseudoTransportationActor, ) from ..utility import make_builders @@ -44,5 +45,6 @@ KillActor, BremSplittingActor, ComptSplittingActor, + ComptPseudoTransportationActor, } actor_builders = make_builders(actor_type_names) diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 9d3aa9f35..0ad3ac911 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -463,6 +463,29 @@ def __init__(self, user_info): g4.GateOptrComptSplittingActor.__init__(self, user_info.__dict__) +class ComptPseudoTransportationActor(g4.GateOptrComptPseudoTransportationActor, ActorBase): + type_name = "ComptPseudoTransportationActor" + def set_default_user_info(user_info): + ActorBase.set_default_user_info(user_info) + deg = g4_units.deg + user_info.splitting_factor = 1 + user_info.weight_threshold = 0 + user_info.bias_primary_only = True + user_info.relative_min_weight_of_particle = 0 + user_info.bias_only_once = True + user_info.processes = ["compt","phot","conv","Rayl"] + user_info.russian_roulette = False + user_info.rotation_vector_director = False + user_info.vector_director = [0,0,1] + user_info.max_theta = 90*deg + user_info.russian_roulette_for_free_flight = 1/10 + user_info.use_probes = False; + + def __init__(self, user_info): + ActorBase.__init__(self, user_info) + g4.GateOptrComptPseudoTransportationActor.__init__(self, user_info.__dict__) + + class BremSplittingActor(g4.GateBOptrBremSplittingActor, ActorBase): type_name = "BremSplittingActor" diff --git a/opengate/tests/src/test072_pseudo_transportation.py b/opengate/tests/src/test072_pseudo_transportation.py new file mode 100644 index 000000000..967d8c4d0 --- /dev/null +++ b/opengate/tests/src/test072_pseudo_transportation.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +import uproot +import numpy as np +import matplotlib.pyplot as plt +import matplotlib as mpl +import scipy +from scipy.spatial.transform import Rotation +from opengate.tests import utility + + + +def validation_test(arr,NIST_data,nb_split,tol = 0.01): + tab_ekin = NIST_data[:, 0] + mu_att = NIST_data[:, -2] + + log_ekin = np.log(tab_ekin) + log_mu = np.log(mu_att) + + f_mu = scipy.interpolate.interp1d(log_ekin, log_mu, kind='cubic') + # print(arr["TrackCreatorProcess"]) + Tracks = arr[(arr["TrackCreatorProcess"] == 'biasWrapper(compt)') & (arr["KineticEnergy"] > 0.1) & (arr["ParticleName"] == "gamma") & (arr["Weight"] > 10**(-20))] + ekin_tracks = Tracks['KineticEnergy'] + x_vertex = Tracks["TrackVertexPosition_X"] + y_vertex = Tracks["TrackVertexPosition_Y"] + z_vertex = Tracks["TrackVertexPosition_Z"] + x = Tracks["PrePosition_X"] + y = Tracks["PrePosition_Y"] + z = Tracks["PrePosition_Z"] + weights = Tracks["Weight"] * nb_split + dist = np.sqrt((x-x_vertex)**2 + (y-y_vertex)**2 + (z-z_vertex)**2) + + G4_mu = -np.log(weights)/(0.1*dist*19.3) + X_com_mu = np.exp(f_mu(np.log(ekin_tracks))) + diff = (G4_mu - X_com_mu)/G4_mu + print("Median difference between mu calculated from XCOM database and from GEANT4 free flight operation:", np.round(100*np.median(diff),1),"%") + return np.median(diff) < tol + + + +if __name__ == "__main__": + paths = utility.get_default_test_paths( + __file__, "test072_pseudo_transportation", output_folder="test072" + ) + + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + + # ui.visu = True + # ui.visu_type = "vrml" + ui.check_volumes_overlap = False + # ui.running_verbose_level = gate.EVENT + ui.number_of_threads = 1 + ui.random_seed = "auto" + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + um = gate.g4_units.um + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + gcm3 = gate.g4_units.g / gate.g4_units.cm3 + deg = gate.g4_units.deg + + # adapt world size + world = sim.world + world.size = [1.2 * m, 1.2 * m, 2 * m] + world.material = "G4_Galactic" + + ####### GEOMETRY TO IRRADIATE ############# + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + simple_collimation = sim.add_volume("Box", "colli_box") + simple_collimation.material = "G4_Galactic" + simple_collimation.mother = world.name + simple_collimation.size = [1 * m, 1 * m, 40 * cm] + simple_collimation.color = [0.3, 0.1, 0.8, 1] + + W_leaf = sim.add_volume("Box", "W_leaf") + W_leaf.mother = simple_collimation.name + W_leaf.size = [1*m,1*m,2*cm] + W_leaf.material = 'Tungsten' + leaf_translation =[] + for i in range(10): + leaf_translation.append([0,0, - 0.5*simple_collimation.size[2] + 0.5* W_leaf.size[2] + i * W_leaf.size[2]]) + print(leaf_translation) + W_leaf.translation =leaf_translation + W_leaf.color = [0.8, 0.2, 0.1, 1] + + ######## pseudo_transportation ACTOR ######### + nb_split = 5 + pseudo_transportation_actor = sim.add_actor("ComptPseudoTransportationActor", "pseudo_transportation_actor") + pseudo_transportation_actor.mother = simple_collimation.name + pseudo_transportation_actor.splitting_factor = nb_split + pseudo_transportation_actor.relative_min_weight_of_particle = np.inf + list_processes_to_bias = pseudo_transportation_actor.processes + + ##### PHASE SPACE plan ######" + plan= sim.add_volume("Box", "phsp") + plan.material = "G4_Galactic" + plan.mother = world.name + plan.size = [1*m,1*m,1*nm] + plan.color = [0.2, 1, 0.8, 1] + plan.translation = [0,0,- 20*cm - 1*nm] + + ####### gamma source ########### + source = sim.add_source("GenericSource", "source1") + source.particle = "gamma" + source.n = 1000 + source.position.type = "sphere" + source.position.radius = 1 * nm + source.direction.type = "momentum" + source.direction.momentum = [0, 0, -1] + source.energy.type = "mono" + source.energy.mono = 6 * MeV + source.position.translation = [0,0,18*cm] + + ####### PHASE SPACE ACTOR ############## + + phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + phsp_actor.mother = plan.name + phsp_actor.attributes = [ + "EventID", + "TrackCreatorProcess", + "TrackVertexPosition", + "PrePosition", + "Weight", + "KineticEnergy", + "ParentID", + "ParticleName" + ] + + phsp_actor.output = paths.output / "test072_output_data.root" + + ##### MODIFIED PHYSICS LIST ############### + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + ### Perhaps avoid the user to call the below boolean function ? ### + sim.physics_manager.special_physics_constructors.G4GenericBiasingPhysics = True + sim.physics_manager.processes_to_bias.gamma = list_processes_to_bias + s = f"/process/em/UseGeneralProcess false" + sim.add_g4_command_before_init(s) + + + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1 * km + sim.physics_manager.global_production_cuts.positron = 1 * km + + output = sim.run() + + # + # # print results + stats = sim.output.get_actor("Stats") + h = sim.output.get_actor("PhaseSpace") + print(stats) + # + f_phsp = uproot.open(paths.output / "test072_output_data.root") + data_NIST_W = np.loadtxt(paths.data / "NIST_W.txt",delimiter = '|') + arr = f_phsp["PhaseSpace"].arrays() + # + is_ok = validation_test(arr,data_NIST_W,nb_split) + utility.test_ok(is_ok) From 7758dbd284133772df3533f9a834f3770a683c29 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Wed, 21 Feb 2024 17:12:05 +0100 Subject: [PATCH 02/82] Correction of a bug linked to the russian roulette of particle weights --- .../opengate_lib/GateOptnForceFreeFlight.cpp | 50 +++++++++---------- .../opengate_lib/GateOptnForceFreeFlight.h | 5 ++ ...GateOptrComptPseudoTransportationActor.cpp | 5 +- .../GateOptrComptPseudoTransportationActor.h | 1 + 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp index af0828b4e..4bef07ce3 100644 --- a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp +++ b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp @@ -31,8 +31,10 @@ // This operator is used to transport the particle without interaction, and then correct the weight of the particle -//according the probablity for the photon to not interact within the matter. To do that, we use a GEANT4 modifed Interaction Law (G4ILawForceFreeFlight), which modify, -//for the biased process the probability to occur : Never. +//according to the probablity for the photon to not interact within the matter. To do that, we use a GEANT4 modifed Interaction Law (G4ILawForceFreeFlight), which modify, +//for the biased process the probability for the interaction to occur : Never. +//This occurence is called during the tracking for each step. Here the step is the largest possible, and one step correspond to the path of +//particle in the media. GateOptnForceFreeFlight ::GateOptnForceFreeFlight (G4String name) : G4VBiasingOperation ( name ), @@ -63,59 +65,57 @@ G4VParticleChange* GateOptnForceFreeFlight ::ApplyFinalStateBiasing( const G4Bia { - // -- If the track is reaching the volume boundary, its free flight ends. In this case, its zero - // -- weight is brought back to non-zero value: its initial weight is restored by the first - // -- ApplyFinalStateBiasing operation called, and the weight for force free flight is applied - // -- is applied by each operation. - // -- If the track is not reaching the volume boundary, it zero weight flight continues. fParticleChange.Initialize( *track ); forceFinalState = true; + fCountProcess ++; fProposedWeight *= fWeightChange[callingProcess->GetWrappedProcess()->GetProcessName()]; if (fUseProbes){ if (track->IsGoodForTracking() ==0){ - if (fProposedWeight < fRussianRouletteProbability * fMinWeight){ - fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); - return &fParticleChange; - } - - if ((fProposedWeight < fMinWeight) && (fProposedWeight >= fRussianRouletteProbability * fMinWeight)) { - G4double probability = G4UniformRand(); - if (probability > fRussianRouletteProbability){ + if (fProposedWeight < fRussianRouletteProbability * fMinWeight){ fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); return &fParticleChange; } - else { - fProposedWeight = fProposedWeight/fRussianRouletteProbability; + + if ((fProposedWeight < fMinWeight) && (fProposedWeight >= fRussianRouletteProbability * fMinWeight)) { + G4double probability = G4UniformRand(); + if (probability > fRussianRouletteProbability){ + fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); + return &fParticleChange; + } + else { + fProposedWeight = fProposedWeight/fRussianRouletteProbability; + } } - } - fParticleChange.ProposeWeight(fProposedWeight); - fOperationComplete = true; - if (track->IsGoodForTracking() ==1){ fParticleChange.ProposeWeight(fProposedWeight); fOperationComplete = true; } - } + if (track->IsGoodForTracking() ==1){ + fParticleChange.ProposeWeight(fProposedWeight); + fOperationComplete = true; + } } else { - if (fProposedWeight < fRussianRouletteProbability * fMinWeight){ + if ((fProposedWeight < fRussianRouletteProbability * fMinWeight)|| ((fProposedWeight < fMinWeight) && (fSurvivedToRR == true))) { fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); return &fParticleChange; } - if ((fProposedWeight < fMinWeight) && (fProposedWeight >= fRussianRouletteProbability * fMinWeight)) { + if ((fProposedWeight < fMinWeight) && (fProposedWeight >= fRussianRouletteProbability * fMinWeight) && (fCountProcess == 4) && (fSurvivedToRR == false)) { G4double probability = G4UniformRand(); + if (probability > fRussianRouletteProbability){ fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); return &fParticleChange; } else { fProposedWeight = fProposedWeight/fRussianRouletteProbability; + //std::cout<<"lim "<< fMinWeight << " RR " <<fRussianRouletteProbability<< " weight " <<fProposedWeight<<std::endl; + fSurvivedToRR = true; } } - fParticleChange.ProposeWeight(fProposedWeight); fOperationComplete = true; } diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h index 30c32692d..cf00f0964 100644 --- a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h +++ b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h @@ -90,6 +90,9 @@ class GateOptnForceFreeFlight : public G4VBiasingOperation { G4double GetTrackWeight(){return fProposedWeight;} void SetTrackWeight(G4double w){fProposedWeight = w;} void SetRussianRouletteProbability(G4double p){fRussianRouletteProbability= p;} + void SetCountProcess(G4int N){fCountProcess = N;} + void SetSurvivedToRR(G4bool b){fSurvivedToRR = b;} + G4bool GetSurvivedToRR(){return fSurvivedToRR;} G4bool OperationComplete() const { return fOperationComplete; } private: @@ -101,6 +104,8 @@ class GateOptnForceFreeFlight : public G4VBiasingOperation { G4ParticleChange fParticleChange; G4bool fOperationComplete; G4double fProposedWeight; + G4int fCountProcess; + G4bool fSurvivedToRR; }; diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp index 0a9d3799b..79c5cb772 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp @@ -162,6 +162,7 @@ void GateOptrComptPseudoTransportationActor::BeginOfEventAction(const G4Event *e void GateOptrComptPseudoTransportationActor::StartTracking(const G4Track *track) { fInitialWeight = track->GetWeight(); +fFreeFlightOperation->SetSurvivedToRR(false); } @@ -175,12 +176,11 @@ fInitialWeight = track->GetWeight(); G4VBiasingOperation * GateOptrComptPseudoTransportationActor::ProposeOccurenceBiasingOperation(const G4Track* track, const G4BiasingProcessInterface* callingProcess) { - - if (track->GetCreatorProcess () !=0){ if (track->GetCreatorProcess ()->GetProcessName() == "biasWrapper(compt)"){ fFreeFlightOperation->SetMinWeight(fInitialWeight/fRelativeMinWeightOfParticle); fFreeFlightOperation->SetTrackWeight(track->GetWeight()); + fFreeFlightOperation->SetCountProcess(0); return fFreeFlightOperation; } @@ -198,6 +198,7 @@ GateOptrComptPseudoTransportationActor::ProposeFinalStateBiasingOperation( const G4Track *track, const G4BiasingProcessInterface *callingProcess) { G4String callingProcessName = "biasWrapper(compt)"; + if (callingProcess->GetWrappedProcess()->GetProcessName() == "compt") { if (track->GetCreatorProcess() ==0) { diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h index e4a7d1e26..020ef2d73 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h @@ -76,6 +76,7 @@ class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, G4double weight = 0; G4bool fKillOthersParticles = false; G4bool fUseProbes = false; + G4bool fSurvivedRR = false; // Unused but mandatory virtual void StartSimulationAction(); From ad4544b0a5beba9cf6cee81975ccaaefea04a61d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:16:25 +0000 Subject: [PATCH 03/82] [pre-commit.ci] Automatic python and c++ formatting --- .../opengate_lib/GateOptnComptSplitting.cpp | 187 ++++++++------ .../opengate_lib/GateOptnComptSplitting.h | 38 ++- ...ateOptnComptSplittingForTransportation.cpp | 187 ++++++++------ .../GateOptnComptSplittingForTransportation.h | 46 ++-- .../opengate_lib/GateOptnForceFreeFlight.cpp | 145 ++++++----- .../opengate_lib/GateOptnForceFreeFlight.h | 82 +++--- ...GateOptrComptPseudoTransportationActor.cpp | 242 +++++++++--------- .../GateOptrComptPseudoTransportationActor.h | 20 +- .../GateOptrComptSplittingActor.cpp | 63 +++-- .../GateOptrComptSplittingActor.h | 3 +- ...GateOptrComptPseudoTransportationActor.cpp | 5 +- opengate/actors/miscactors.py | 20 +- opengate/managers.py | 6 +- .../src/test071_operator_russian_roulette.py | 70 ++--- .../src/test072_pseudo_transportation.py | 66 +++-- 15 files changed, 623 insertions(+), 557 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateOptnComptSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnComptSplitting.cpp index c4a5cde0b..8ee0ff264 100644 --- a/core/opengate_core/opengate_lib/GateOptnComptSplitting.cpp +++ b/core/opengate_core/opengate_lib/GateOptnComptSplitting.cpp @@ -31,98 +31,114 @@ #include "G4BiasingProcessInterface.hh" #include "G4DynamicParticle.hh" -#include "G4SystemOfUnits.hh" +#include "G4Exception.hh" +#include "G4Gamma.hh" #include "G4Gamma.hh" #include "G4ParticleChange.hh" #include "G4ParticleChangeForGamma.hh" -#include"G4Gamma.hh" -#include "G4Exception.hh" +#include "G4SystemOfUnits.hh" #include "G4TrackStatus.hh" #include <memory> //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... GateOptnComptSplitting::GateOptnComptSplitting(G4String name) - : G4VBiasingOperation(name), fSplittingFactor(1), fRussianRoulette(false), fParticleChange() {} + : G4VBiasingOperation(name), fSplittingFactor(1), fRussianRoulette(false), + fParticleChange() {} //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptnComptSplitting::~GateOptnComptSplitting() { -} +GateOptnComptSplitting::~GateOptnComptSplitting() {} -G4VParticleChange *GateOptnComptSplitting::ApplyFinalStateBiasing(const G4BiasingProcessInterface *callingProcess, const G4Track *track,const G4Step *step, G4bool &) { +G4VParticleChange *GateOptnComptSplitting::ApplyFinalStateBiasing( + const G4BiasingProcessInterface *callingProcess, const G4Track *track, + const G4Step *step, G4bool &) { -//Here we generate for the first the "fake" compton process, given that this function (ApplyFinalStateBiasing) is called when there is a compton interaction -//Then the interaction location of the compton process will always be the same + // Here we generate for the first the "fake" compton process, given that this + // function (ApplyFinalStateBiasing) is called when there is a compton + // interaction Then the interaction location of the compton process will + // always be the same + // Initialisation of parameter for the split, because the photon is the + // primary particle, so it's a bit tricky -// Initialisation of parameter for the split, because the photon is the primary particle, so it's a bit tricky - G4double globalTime = step->GetTrack()->GetGlobalTime(); const G4ThreeVector position = step->GetPostStepPoint()->GetPosition(); G4int nCalls = 0; G4int splittingFactor = ceil(fSplittingFactor); - G4double survivalProbabilitySplitting = 1 - (splittingFactor - fSplittingFactor)/splittingFactor; + G4double survivalProbabilitySplitting = + 1 - (splittingFactor - fSplittingFactor) / splittingFactor; G4bool isRightAngle = false; G4double gammaWeight = 0; G4int nbSecondaries = 0; - G4VParticleChange* processFinalState = nullptr; - G4ParticleChangeForGamma* castedProcessInitFinalState = nullptr; - + G4VParticleChange *processFinalState = nullptr; + G4ParticleChangeForGamma *castedProcessInitFinalState = nullptr; - while(isRightAngle ==false){ + while (isRightAngle == false) { gammaWeight = track->GetWeight() / fSplittingFactor; - processFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - // In case we don't want to split (a bit faster) i.e no biaising or no splitting low weights particles. - - if ((fSplittingFactor == 1 && fRussianRoulette == false) || track->GetWeight() < fWeightThreshold) + processFinalState = + callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + // In case we don't want to split (a bit faster) i.e no biaising or no + // splitting low weights particles. + + if ((fSplittingFactor == 1 && fRussianRoulette == false) || + track->GetWeight() < fWeightThreshold) return processFinalState; - - castedProcessInitFinalState = (G4ParticleChangeForGamma*) processFinalState; + + castedProcessInitFinalState = (G4ParticleChangeForGamma *)processFinalState; nbSecondaries = processFinalState->GetNumberOfSecondaries(); - G4ThreeVector initMomentum = castedProcessInitFinalState->GetProposedMomentumDirection(); - G4double cosTheta = fVectorDirector * initMomentum; + G4ThreeVector initMomentum = + castedProcessInitFinalState->GetProposedMomentumDirection(); + G4double cosTheta = fVectorDirector * initMomentum; G4double theta = std::acos(cosTheta); G4double splittingProbability = G4UniformRand(); - if (splittingProbability <= survivalProbabilitySplitting || survivalProbabilitySplitting == 1) { + if (splittingProbability <= survivalProbabilitySplitting || + survivalProbabilitySplitting == 1) { - // If the number of compton interaction is too high, we simply return the process, instead of generating very low weight particles. + // If the number of compton interaction is too high, we simply return the + // process, instead of generating very low weight particles. if (track->GetWeight() <= fMinWeightOfParticle) { return processFinalState; } - // If the russian roulette is activated, we need to initialize the track with a primary particle which have the right angle - // That's why nCall is also incremented here, to avoid any bias in te number of gamma generated + // If the russian roulette is activated, we need to initialize the track + // with a primary particle which have the right angle That's why nCall is + // also incremented here, to avoid any bias in te number of gamma + // generated if ((fRussianRoulette == true) && (theta > fMaxTheta)) { G4double probability = G4UniformRand(); - if (probability < 1/fSplittingFactor) { - gammaWeight = gammaWeight*fSplittingFactor; + if (probability < 1 / fSplittingFactor) { + gammaWeight = gammaWeight * fSplittingFactor; isRightAngle = true; } } - if ((fRussianRoulette == false) || ((fRussianRoulette == true) && (theta <= fMaxTheta))) + if ((fRussianRoulette == false) || + ((fRussianRoulette == true) && (theta <= fMaxTheta))) isRightAngle = true; } - if (isRightAngle ==false) + if (isRightAngle == false) processFinalState->Clear(); - - // Little exception, if the splitting factor is too low compared to the acceptance angle, it's therefore possible to attain the splitting factor without - // any first track. For the moment, we kill the particle, since the russian roulette phenomena is applied and normally guaranties a non-biased operation. - if (nCalls >= fSplittingFactor){ + + // Little exception, if the splitting factor is too low compared to the + // acceptance angle, it's therefore possible to attain the splitting factor + // without any first track. For the moment, we kill the particle, since the + // russian roulette phenomena is applied and normally guaranties a + // non-biased operation. + if (nCalls >= fSplittingFactor) { fParticleChange.Initialize(*track); fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); return &fParticleChange; } - nCalls ++; + nCalls++; } - //Initialisation of the information about the track. - //We store the first gamma as the departure track, The first gamma is a primary particle but with its weight modified - //, since it can be one of the detected particles - + // Initialisation of the information about the track. + // We store the first gamma as the departure track, The first gamma is a + // primary particle but with its weight modified , since it can be one of the + //detected particles fParticleChange.Initialize(*track); fParticleChange.ProposeWeight(gammaWeight); @@ -135,11 +151,11 @@ G4VParticleChange *GateOptnComptSplitting::ApplyFinalStateBiasing(const G4Biasin fParticleChange.SetSecondaryWeightByProcess(true); - -//If there is cut on secondary particles, there is a probability that the electron is not simulated -//Then, if the compton process created it, we add the gien electron to the ParticleChange object + // If there is cut on secondary particles, there is a probability that the + // electron is not simulated Then, if the compton process created it, we add + // the gien electron to the ParticleChange object if (nbSecondaries == 1) { - G4Track* initElectronTrack = castedProcessInitFinalState->GetSecondary(0); + G4Track *initElectronTrack = castedProcessInitFinalState->GetSecondary(0); initElectronTrack->SetWeight(gammaWeight); fParticleChange.AddSecondary(initElectronTrack); } @@ -147,61 +163,78 @@ G4VParticleChange *GateOptnComptSplitting::ApplyFinalStateBiasing(const G4Biasin processFinalState->Clear(); castedProcessInitFinalState->Clear(); - //There is here the biasing process : - // Since G4VParticleChange class does not allow to retrieve scattered gamma information, we need to cast the type G4ParticleChangeForGamma - // to the G4VParticleChange object. We then call the process (biasWrapper(compt)) fSplittingFactor -1 times (minus the number of call - // for the generation of the primary particle) to generate, at last, fSplittingFactor gamma - // according to the compton interaction process. If the gamma track is ok regarding the russian roulette algorithm (no russian roulette - //, or within the acceptance angle, or not killed by the RR process), we add it to the primary track. - // If an electron is generated (above the range cut), we also generate it. - // A tremendous advantage is there is no need to use by ourself Klein-Nishina formula or other. So, if the physics list used takes into account - // the doppler broadening or other fine effects, this will be also taken into account by the MC simulation. - // PS : The first gamma is then the primary particle, but all the other splitted particle (electron of course AND gamma) must be considered - // as secondary particles, even though generated gamma will not be cut here by the applied cut. - - + // There is here the biasing process : + // Since G4VParticleChange class does not allow to retrieve scattered gamma + // information, we need to cast the type G4ParticleChangeForGamma to the + // G4VParticleChange object. We then call the process (biasWrapper(compt)) + // fSplittingFactor -1 times (minus the number of call for the generation of + // the primary particle) to generate, at last, fSplittingFactor gamma + // according to the compton interaction process. If the gamma track is ok + // regarding the russian roulette algorithm (no russian roulette + //, or within the acceptance angle, or not killed by the RR process), we add + //it to the primary track. + // If an electron is generated (above the range cut), we also generate it. + // A tremendous advantage is there is no need to use by ourself Klein-Nishina + // formula or other. So, if the physics list used takes into account the + // doppler broadening or other fine effects, this will be also taken into + // account by the MC simulation. PS : The first gamma is then the primary + // particle, but all the other splitted particle (electron of course AND + // gamma) must be considered as secondary particles, even though generated + // gamma will not be cut here by the applied cut. + while (nCalls < splittingFactor) { gammaWeight = track->GetWeight() / fSplittingFactor; G4double initGammaWeight = track->GetWeight(); - G4VParticleChange* processGammaSplittedFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - G4ParticleChangeForGamma* castedProcessGammaSplittedFinalState = (G4ParticleChangeForGamma*) processGammaSplittedFinalState; - const G4ThreeVector momentum = castedProcessGammaSplittedFinalState-> GetProposedMomentumDirection(); - G4double energy = castedProcessGammaSplittedFinalState-> GetProposedKineticEnergy(); - G4double cosTheta = fVectorDirector * castedProcessInitFinalState->GetProposedMomentumDirection(); + G4VParticleChange *processGammaSplittedFinalState = + callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + G4ParticleChangeForGamma *castedProcessGammaSplittedFinalState = + (G4ParticleChangeForGamma *)processGammaSplittedFinalState; + const G4ThreeVector momentum = + castedProcessGammaSplittedFinalState->GetProposedMomentumDirection(); + G4double energy = + castedProcessGammaSplittedFinalState->GetProposedKineticEnergy(); + G4double cosTheta = + fVectorDirector * + castedProcessInitFinalState->GetProposedMomentumDirection(); G4double theta = std::acos(cosTheta); G4double splittingProbability = G4UniformRand(); - - if (splittingProbability <= survivalProbabilitySplitting || survivalProbabilitySplitting == 1) { - if ((fRussianRoulette == true) && (theta > fMaxTheta)) { + + if (splittingProbability <= survivalProbabilitySplitting || + survivalProbabilitySplitting == 1) { + if ((fRussianRoulette == true) && (theta > fMaxTheta)) { G4double probability = G4UniformRand(); - if (probability < 1/fSplittingFactor) { - // Specific case where the russian roulette probability is 1/splitting. Each particle generated, with a 1/split probability - //will have a 1/split probability to survive with a final weight of Initial weights * 1/split * split = Initial weight - gammaWeight = gammaWeight*fSplittingFactor; - G4Track* gammaTrack = new G4Track(*track); + if (probability < 1 / fSplittingFactor) { + // Specific case where the russian roulette probability is + // 1/splitting. Each particle generated, with a 1/split probability + // will have a 1/split probability to survive with a final weight of + // Initial weights * 1/split * split = Initial weight + gammaWeight = gammaWeight * fSplittingFactor; + G4Track *gammaTrack = new G4Track(*track); gammaTrack->SetWeight(gammaWeight); gammaTrack->SetKineticEnergy(energy); gammaTrack->SetMomentumDirection(momentum); gammaTrack->SetPosition(position); fParticleChange.AddSecondary(gammaTrack); if (processGammaSplittedFinalState->GetNumberOfSecondaries() == 1) { - G4Track* electronTrack = processGammaSplittedFinalState->GetSecondary(0); + G4Track *electronTrack = + processGammaSplittedFinalState->GetSecondary(0); electronTrack->SetWeight(gammaWeight); fParticleChange.AddSecondary(electronTrack); } } } - - if ((fRussianRoulette == false) || ((fRussianRoulette == true) && (theta <= fMaxTheta))) { - G4Track* gammaTrack = new G4Track(*track); + if ((fRussianRoulette == false) || + ((fRussianRoulette == true) && (theta <= fMaxTheta))) { + G4Track *gammaTrack = new G4Track(*track); gammaTrack->SetWeight(gammaWeight); gammaTrack->SetKineticEnergy(energy); gammaTrack->SetMomentumDirection(momentum); gammaTrack->SetPosition(position); fParticleChange.AddSecondary(gammaTrack); if (processGammaSplittedFinalState->GetNumberOfSecondaries() == 1) { - G4Track* electronTrack = processGammaSplittedFinalState->GetSecondary(0); + G4Track *electronTrack = + processGammaSplittedFinalState->GetSecondary(0); electronTrack->SetWeight(gammaWeight); fParticleChange.AddSecondary(electronTrack); } @@ -210,8 +243,6 @@ G4VParticleChange *GateOptnComptSplitting::ApplyFinalStateBiasing(const G4Biasin nCalls++; processGammaSplittedFinalState->Clear(); castedProcessGammaSplittedFinalState->Clear(); - - } return &fParticleChange; } diff --git a/core/opengate_core/opengate_lib/GateOptnComptSplitting.h b/core/opengate_core/opengate_lib/GateOptnComptSplitting.h index e814d4ad4..5b3397b59 100644 --- a/core/opengate_core/opengate_lib/GateOptnComptSplitting.h +++ b/core/opengate_core/opengate_lib/GateOptnComptSplitting.h @@ -77,45 +77,37 @@ class GateOptnComptSplitting : public G4VBiasingOperation { } G4double GetSplittingFactor() const { return fSplittingFactor; } - -void SetWeightThreshold(G4double weightThreshold) { + void SetWeightThreshold(G4double weightThreshold) { fWeightThreshold = weightThreshold; } G4double GetWeightThreshold() const { return fWeightThreshold; } - - void SetRussianRoulette(G4bool russianRoulette){ + void SetRussianRoulette(G4bool russianRoulette) { fRussianRoulette = russianRoulette; } G4bool GetRussianRoulette() const { return fRussianRoulette; } - void SetVectorDirector(G4ThreeVector vectorDirector){ + void SetVectorDirector(G4ThreeVector vectorDirector) { fVectorDirector = vectorDirector; } - G4ThreeVector GetVectorDirector() const {return fVectorDirector;} - - - void SetMaxTheta(G4double maxTheta){ - fMaxTheta = maxTheta; - } + G4ThreeVector GetVectorDirector() const { return fVectorDirector; } - G4double GetMaxTheta() const {return fMaxTheta;} + void SetMaxTheta(G4double maxTheta) { fMaxTheta = maxTheta; } + G4double GetMaxTheta() const { return fMaxTheta; } - - void SetMinWeightOfParticle(G4double minWeightOfParticle){ - fMinWeightOfParticle= minWeightOfParticle; + void SetMinWeightOfParticle(G4double minWeightOfParticle) { + fMinWeightOfParticle = minWeightOfParticle; } - G4double GetMinWeightOfParticle() const {return fMinWeightOfParticle;} - - - G4VParticleChange* GetParticleChange() { - G4VParticleChange* particleChange = &fParticleChange; - return particleChange;} + G4double GetMinWeightOfParticle() const { return fMinWeightOfParticle; } + G4VParticleChange *GetParticleChange() { + G4VParticleChange *particleChange = &fParticleChange; + return particleChange; + } private: G4double fSplittingFactor; @@ -125,8 +117,8 @@ void SetWeightThreshold(G4double weightThreshold) { G4ThreeVector fVectorDirector; G4double fMaxTheta; G4double fMinWeightOfParticle; - //G4DynamicParticle* fSplitParticle; - //G4Track* fGammaTrack; + // G4DynamicParticle* fSplitParticle; + // G4Track* fGammaTrack; }; #endif diff --git a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp b/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp index bb3ac7112..5d14e2866 100644 --- a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp +++ b/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp @@ -30,76 +30,84 @@ #include "GateOptnComptSplittingForTransportation.h" #include "G4BiasingProcessInterface.hh" +#include "G4ComptonScattering.hh" #include "G4DynamicParticle.hh" -#include "G4SystemOfUnits.hh" +#include "G4Exception.hh" +#include "G4Gamma.hh" #include "G4Gamma.hh" +#include "G4GammaConversion.hh" #include "G4ParticleChange.hh" #include "G4ParticleChangeForGamma.hh" -#include "G4VEmProcess.hh" -#include"G4Gamma.hh" -#include "G4ComptonScattering.hh" #include "G4PhotoElectricEffect.hh" +#include "G4ProcessType.hh" #include "G4RayleighScattering.hh" -#include "G4GammaConversion.hh" -#include "G4Exception.hh" +#include "G4SystemOfUnits.hh" #include "G4TrackStatus.hh" -#include "G4ProcessType.hh" -#include <memory> #include "G4TrackingManager.hh" +#include "G4VEmProcess.hh" +#include <memory> //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptnComptSplittingForTransportation::GateOptnComptSplittingForTransportation(G4String name) - : G4VBiasingOperation(name), fSplittingFactor(1), fRussianRoulette(false), fParticleChange() {} +GateOptnComptSplittingForTransportation:: + GateOptnComptSplittingForTransportation(G4String name) + : G4VBiasingOperation(name), fSplittingFactor(1), fRussianRoulette(false), + fParticleChange() {} //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptnComptSplittingForTransportation::~GateOptnComptSplittingForTransportation() { -} - - +GateOptnComptSplittingForTransportation:: + ~GateOptnComptSplittingForTransportation() {} -G4VParticleChange *GateOptnComptSplittingForTransportation::ApplyFinalStateBiasing(const G4BiasingProcessInterface *callingProcess, const G4Track *track,const G4Step *step, G4bool &) { - -//Here we generate for the first the compton process, given that this function (ApplyFinalStateBiasing) is called when there is a compton interaction -//Then the interaction location of the compton process will always be the same +G4VParticleChange * +GateOptnComptSplittingForTransportation::ApplyFinalStateBiasing( + const G4BiasingProcessInterface *callingProcess, const G4Track *track, + const G4Step *step, G4bool &) { + // Here we generate for the first the compton process, given that this + // function (ApplyFinalStateBiasing) is called when there is a compton + // interaction Then the interaction location of the compton process will + // always be the same G4double globalTime = step->GetTrack()->GetGlobalTime(); const G4ThreeVector position = step->GetPostStepPoint()->GetPosition(); G4int nCalls = 1; G4int splittingFactor = ceil(fSplittingFactor); - G4double survivalProbabilitySplitting = 1 - (splittingFactor - fSplittingFactor)/splittingFactor; + G4double survivalProbabilitySplitting = + 1 - (splittingFactor - fSplittingFactor) / splittingFactor; G4bool isRightAngle = false; G4double gammaWeight = 0; G4int nbSecondaries = 0; - G4VParticleChange* processFinalState = nullptr; - G4ParticleChangeForGamma* castedProcessInitFinalState = nullptr; + G4VParticleChange *processFinalState = nullptr; + G4ParticleChangeForGamma *castedProcessInitFinalState = nullptr; + + processFinalState = + callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + // In case we don't want to split (a bit faster) i.e no biaising or no + // splitting low weights particles. - processFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - // In case we don't want to split (a bit faster) i.e no biaising or no splitting low weights particles. - - if ((fSplittingFactor == 1 && fRussianRoulette == false) || track->GetWeight() < fWeightThreshold) + if ((fSplittingFactor == 1 && fRussianRoulette == false) || + track->GetWeight() < fWeightThreshold) return processFinalState; - - castedProcessInitFinalState = (G4ParticleChangeForGamma*) processFinalState; + + castedProcessInitFinalState = (G4ParticleChangeForGamma *)processFinalState; nbSecondaries = processFinalState->GetNumberOfSecondaries(); - fParticleChange.Initialize(*track); fParticleChange.ProposeWeight(track->GetWeight()); - fParticleChange.ProposeTrackStatus(castedProcessInitFinalState->GetTrackStatus()); - fParticleChange.ProposeEnergy(castedProcessInitFinalState->GetProposedKineticEnergy()); - fParticleChange.ProposeMomentumDirection(castedProcessInitFinalState->GetProposedMomentumDirection()); + fParticleChange.ProposeTrackStatus( + castedProcessInitFinalState->GetTrackStatus()); + fParticleChange.ProposeEnergy( + castedProcessInitFinalState->GetProposedKineticEnergy()); + fParticleChange.ProposeMomentumDirection( + castedProcessInitFinalState->GetProposedMomentumDirection()); fParticleChange.SetSecondaryWeightByProcess(true); - - - -//If there is cut on secondary particles, there is a probability that the electron is not simulated -//Then, if the compton process created it, we add the gien electron to the ParticleChange object + // If there is cut on secondary particles, there is a probability that the + // electron is not simulated Then, if the compton process created it, we add + // the gien electron to the ParticleChange object if (nbSecondaries == 1) { - G4Track* initElectronTrack = castedProcessInitFinalState->GetSecondary(0); + G4Track *initElectronTrack = castedProcessInitFinalState->GetSecondary(0); initElectronTrack->SetWeight(track->GetWeight()); fParticleChange.AddSecondary(initElectronTrack); } @@ -107,92 +115,109 @@ G4VParticleChange *GateOptnComptSplittingForTransportation::ApplyFinalStateBiasi processFinalState->Clear(); castedProcessInitFinalState->Clear(); - //There is here the biasing process : - // Since G4VParticleChange class does not allow to retrieve scattered gamma information, we need to cast the type G4ParticleChangeForGamma - // to the G4VParticleChange object. We then call the process (biasWrapper(compt)) fSplittingFactor times (Here, the difference with the other version - // of splitting is the primary particle will be killed and its weight does not count) to generate, at last, fSplittingFactor gamma - // according to the compton interaction process. If the gamma track is ok regarding the russian roulette algorithm (no russian roulette - //, or within the acceptance angle, or not killed by the RR process), we add it to the primary track. - // If an electron is generated (above the range cut), we also generate it. - // A tremendous advantage is there is no need to use by ourself Klein-Nishina formula or other. So, if the physics list used takes into account - // the doppler broadening or other fine effects, this will be also taken into account by the MC simulation. - // PS : The first gamma is then the primary particle, but all the other splitted particle (electron of course AND gamma) must be considered - // as secondary particles, even though generated gamma will not be cut here by the applied cut. - - G4int simulationTrackID = 0; + // There is here the biasing process : + // Since G4VParticleChange class does not allow to retrieve scattered gamma + // information, we need to cast the type G4ParticleChangeForGamma to the + // G4VParticleChange object. We then call the process (biasWrapper(compt)) + // fSplittingFactor times (Here, the difference with the other version of + // splitting is the primary particle will be killed and its weight does not + // count) to generate, at last, fSplittingFactor gamma according to the + // compton interaction process. If the gamma track is ok regarding the + // russian roulette algorithm (no russian roulette + //, or within the acceptance angle, or not killed by the RR process), we add + //it to the primary track. + // If an electron is generated (above the range cut), we also generate it. + // A tremendous advantage is there is no need to use by ourself Klein-Nishina + // formula or other. So, if the physics list used takes into account the + // doppler broadening or other fine effects, this will be also taken into + // account by the MC simulation. PS : The first gamma is then the primary + // particle, but all the other splitted particle (electron of course AND + // gamma) must be considered as secondary particles, even though generated + // gamma will not be cut here by the applied cut. + + G4int simulationTrackID = 0; while (nCalls <= splittingFactor) { gammaWeight = track->GetWeight() / fSplittingFactor; G4double initGammaWeight = track->GetWeight(); - G4VParticleChange* processGammaSplittedFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - G4ParticleChangeForGamma* castedProcessGammaSplittedFinalState = (G4ParticleChangeForGamma*) processGammaSplittedFinalState; - const G4ThreeVector momentum = castedProcessGammaSplittedFinalState-> GetProposedMomentumDirection(); - G4double energy = castedProcessGammaSplittedFinalState-> GetProposedKineticEnergy(); - G4double cosTheta = fVectorDirector * castedProcessInitFinalState->GetProposedMomentumDirection(); + G4VParticleChange *processGammaSplittedFinalState = + callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + G4ParticleChangeForGamma *castedProcessGammaSplittedFinalState = + (G4ParticleChangeForGamma *)processGammaSplittedFinalState; + const G4ThreeVector momentum = + castedProcessGammaSplittedFinalState->GetProposedMomentumDirection(); + G4double energy = + castedProcessGammaSplittedFinalState->GetProposedKineticEnergy(); + G4double cosTheta = + fVectorDirector * + castedProcessInitFinalState->GetProposedMomentumDirection(); G4double theta = std::acos(cosTheta); G4double splittingProbability = G4UniformRand(); - - if (splittingProbability <= survivalProbabilitySplitting || survivalProbabilitySplitting == 1) { - if ((fRussianRoulette == true) && (theta > fMaxTheta)) { + + if (splittingProbability <= survivalProbabilitySplitting || + survivalProbabilitySplitting == 1) { + if ((fRussianRoulette == true) && (theta > fMaxTheta)) { G4double probability = G4UniformRand(); - if (probability < 1/fSplittingFactor) { - // Specific case where the russian roulette probability is 1/splitting. Each particle generated, with a 1/split probability - //will have a 1/split probability to survive with a final weight of Initial weights * 1/split * split = Initial weight - gammaWeight = gammaWeight*fSplittingFactor; - G4Track* gammaTrack = new G4Track(*track); + if (probability < 1 / fSplittingFactor) { + // Specific case where the russian roulette probability is + // 1/splitting. Each particle generated, with a 1/split probability + // will have a 1/split probability to survive with a final weight of + // Initial weights * 1/split * split = Initial weight + gammaWeight = gammaWeight * fSplittingFactor; + G4Track *gammaTrack = new G4Track(*track); gammaTrack->SetWeight(gammaWeight); gammaTrack->SetKineticEnergy(energy); gammaTrack->SetMomentumDirection(momentum); gammaTrack->SetPosition(position); fParticleChange.AddSecondary(gammaTrack); - simulationTrackID ++; + simulationTrackID++; if (processGammaSplittedFinalState->GetNumberOfSecondaries() == 1) { - G4Track* electronTrack = processGammaSplittedFinalState->GetSecondary(0); + G4Track *electronTrack = + processGammaSplittedFinalState->GetSecondary(0); electronTrack->SetWeight(gammaWeight); fParticleChange.AddSecondary(electronTrack); - simulationTrackID ++; + simulationTrackID++; } } } - - if ((fRussianRoulette == false) || ((fRussianRoulette == true) && (theta <= fMaxTheta))) { - G4Track* gammaTrack = new G4Track(*track); + if ((fRussianRoulette == false) || + ((fRussianRoulette == true) && (theta <= fMaxTheta))) { + G4Track *gammaTrack = new G4Track(*track); gammaTrack->SetWeight(gammaWeight); gammaTrack->SetKineticEnergy(energy); gammaTrack->SetMomentumDirection(momentum); gammaTrack->SetPosition(position); fParticleChange.AddSecondary(gammaTrack); - simulationTrackID ++; + simulationTrackID++; if (processGammaSplittedFinalState->GetNumberOfSecondaries() == 1) { - G4Track* electronTrack = processGammaSplittedFinalState->GetSecondary(0); + G4Track *electronTrack = + processGammaSplittedFinalState->GetSecondary(0); electronTrack->SetWeight(gammaWeight); fParticleChange.AddSecondary(electronTrack); - simulationTrackID ++; + simulationTrackID++; } } } nCalls++; processGammaSplittedFinalState->Clear(); castedProcessGammaSplittedFinalState->Clear(); - - } - //Probe generation, sent in 5 directions, in order to provide informations before the track of the real photons about - //the geometries they will cross. + // Probe generation, sent in 5 directions, in order to provide informations + // before the track of the real photons about the geometries they will cross. if (fUseProbes) { - std::vector<G4ThreeVector> v = {{0,0,-1},{0,1,0},{0,-1,0},{1,0,0},{-1,0,0}}; + std::vector<G4ThreeVector> v = { + {0, 0, -1}, {0, 1, 0}, {0, -1, 0}, {1, 0, 0}, {-1, 0, 0}}; for (G4int nbProbe = 0; nbProbe < 5; nbProbe++) { - G4Track* gammaTrack = new G4Track(*track); + G4Track *gammaTrack = new G4Track(*track); gammaTrack->SetGoodForTrackingFlag(1); gammaTrack->SetWeight(track->GetWeight() / fSplittingFactor); gammaTrack->SetKineticEnergy(track->GetKineticEnergy()); gammaTrack->SetMomentumDirection(fRot * v[nbProbe]); gammaTrack->SetPosition(position); fParticleChange.AddSecondary(gammaTrack); - simulationTrackID ++; - - } + simulationTrackID++; + } } return &fParticleChange; } diff --git a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h b/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h index c0d8580aa..09bef7f9f 100644 --- a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h +++ b/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h @@ -38,7 +38,7 @@ class GateOptnComptSplittingForTransportation : public G4VBiasingOperation { public: // -- Constructor : GateOptnComptSplittingForTransportation(G4String name); - + // -- destructor: virtual ~GateOptnComptSplittingForTransportation(); @@ -78,51 +78,41 @@ class GateOptnComptSplittingForTransportation : public G4VBiasingOperation { } G4double GetSplittingFactor() const { return fSplittingFactor; } - -void SetWeightThreshold(G4double weightThreshold) { + void SetWeightThreshold(G4double weightThreshold) { fWeightThreshold = weightThreshold; } G4double GetWeightThreshold() const { return fWeightThreshold; } - - void SetRussianRoulette(G4bool russianRoulette){ + void SetRussianRoulette(G4bool russianRoulette) { fRussianRoulette = russianRoulette; } G4bool GetRussianRoulette() const { return fRussianRoulette; } - void SetVectorDirector(G4ThreeVector vectorDirector){ + void SetVectorDirector(G4ThreeVector vectorDirector) { fVectorDirector = vectorDirector; } - void SetRotationMatrix(G4RotationMatrix rot){ - fRot = rot; - } - - G4ThreeVector GetVectorDirector() const {return fVectorDirector;} + void SetRotationMatrix(G4RotationMatrix rot) { fRot = rot; } + G4ThreeVector GetVectorDirector() const { return fVectorDirector; } - void SetMaxTheta(G4double maxTheta){ - fMaxTheta = maxTheta; - } + void SetMaxTheta(G4double maxTheta) { fMaxTheta = maxTheta; } - G4double GetMaxTheta() const {return fMaxTheta;} + G4double GetMaxTheta() const { return fMaxTheta; } - void SetUseOfProbes(G4bool p){fUseProbes = p;} + void SetUseOfProbes(G4bool p) { fUseProbes = p; } - - - void SetMinWeightOfParticle(G4double minWeightOfParticle){ - fMinWeightOfParticle= minWeightOfParticle; + void SetMinWeightOfParticle(G4double minWeightOfParticle) { + fMinWeightOfParticle = minWeightOfParticle; } - G4double GetMinWeightOfParticle() const {return fMinWeightOfParticle;} - - - G4VParticleChange* GetParticleChange() { - G4VParticleChange* particleChange = &fParticleChange; - return particleChange;} + G4double GetMinWeightOfParticle() const { return fMinWeightOfParticle; } + G4VParticleChange *GetParticleChange() { + G4VParticleChange *particleChange = &fParticleChange; + return particleChange; + } private: G4double fSplittingFactor; @@ -134,8 +124,8 @@ void SetWeightThreshold(G4double weightThreshold) { G4double fMinWeightOfParticle; G4bool fUseProbes; G4RotationMatrix fRot; - //G4DynamicParticle* fSplitParticle; - //G4Track* fGammaTrack; + // G4DynamicParticle* fSplitParticle; + // G4Track* fGammaTrack; }; #endif diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp index 4bef07ce3..3942cf1a2 100644 --- a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp +++ b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp @@ -24,106 +24,109 @@ // ******************************************************************** // #include "GateOptnForceFreeFlight.h" -#include "G4ILawForceFreeFlight.hh" #include "G4BiasingProcessInterface.hh" +#include "G4ILawForceFreeFlight.hh" #include "G4Step.hh" - - -// This operator is used to transport the particle without interaction, and then correct the weight of the particle -//according to the probablity for the photon to not interact within the matter. To do that, we use a GEANT4 modifed Interaction Law (G4ILawForceFreeFlight), which modify, -//for the biased process the probability for the interaction to occur : Never. -//This occurence is called during the tracking for each step. Here the step is the largest possible, and one step correspond to the path of -//particle in the media. - -GateOptnForceFreeFlight ::GateOptnForceFreeFlight (G4String name) - : G4VBiasingOperation ( name ), - fOperationComplete ( true ) -{ - fForceFreeFlightInteractionLaw = new G4ILawForceFreeFlight("LawForOperation"+name); +// This operator is used to transport the particle without interaction, and then +// correct the weight of the particle +// according to the probablity for the photon to not interact within the matter. +// To do that, we use a GEANT4 modifed Interaction Law (G4ILawForceFreeFlight), +// which modify, for the biased process the probability for the interaction to +// occur : Never. This occurence is called during the tracking for each step. +// Here the step is the largest possible, and one step correspond to the path of +// particle in the media. + +GateOptnForceFreeFlight ::GateOptnForceFreeFlight(G4String name) + : G4VBiasingOperation(name), fOperationComplete(true) { + fForceFreeFlightInteractionLaw = + new G4ILawForceFreeFlight("LawForOperation" + name); } -GateOptnForceFreeFlight ::~GateOptnForceFreeFlight () -{ - if ( fForceFreeFlightInteractionLaw ) delete fForceFreeFlightInteractionLaw; +GateOptnForceFreeFlight ::~GateOptnForceFreeFlight() { + if (fForceFreeFlightInteractionLaw) + delete fForceFreeFlightInteractionLaw; } - - -const G4VBiasingInteractionLaw* GateOptnForceFreeFlight ::ProvideOccurenceBiasingInteractionLaw -( const G4BiasingProcessInterface*, G4ForceCondition& proposeForceCondition ) -{ +const G4VBiasingInteractionLaw * +GateOptnForceFreeFlight ::ProvideOccurenceBiasingInteractionLaw( + const G4BiasingProcessInterface *, + G4ForceCondition &proposeForceCondition) { fOperationComplete = false; proposeForceCondition = Forced; return fForceFreeFlightInteractionLaw; } -G4VParticleChange* GateOptnForceFreeFlight ::ApplyFinalStateBiasing( const G4BiasingProcessInterface* callingProcess, - const G4Track* track, - const G4Step* step, - G4bool& forceFinalState) -{ +G4VParticleChange *GateOptnForceFreeFlight ::ApplyFinalStateBiasing( + const G4BiasingProcessInterface *callingProcess, const G4Track *track, + const G4Step *step, G4bool &forceFinalState) { + fParticleChange.Initialize(*track); + forceFinalState = true; + fCountProcess++; - fParticleChange.Initialize( *track ); - forceFinalState = true; - fCountProcess ++; + fProposedWeight *= + fWeightChange[callingProcess->GetWrappedProcess()->GetProcessName()]; + if (fUseProbes) { + if (track->IsGoodForTracking() == 0) { - fProposedWeight *= fWeightChange[callingProcess->GetWrappedProcess()->GetProcessName()]; - if (fUseProbes){ - if (track->IsGoodForTracking() ==0){ - - if (fProposedWeight < fRussianRouletteProbability * fMinWeight){ - fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); - return &fParticleChange; - } - - if ((fProposedWeight < fMinWeight) && (fProposedWeight >= fRussianRouletteProbability * fMinWeight)) { - G4double probability = G4UniformRand(); - if (probability > fRussianRouletteProbability){ - fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); - return &fParticleChange; - } - else { - fProposedWeight = fProposedWeight/fRussianRouletteProbability; - } - } - - fParticleChange.ProposeWeight(fProposedWeight); - fOperationComplete = true; - } - if (track->IsGoodForTracking() ==1){ - fParticleChange.ProposeWeight(fProposedWeight); - fOperationComplete = true; - } - } - else { - if ((fProposedWeight < fRussianRouletteProbability * fMinWeight)|| ((fProposedWeight < fMinWeight) && (fSurvivedToRR == true))) { + if (fProposedWeight < fRussianRouletteProbability * fMinWeight) { fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); return &fParticleChange; } - if ((fProposedWeight < fMinWeight) && (fProposedWeight >= fRussianRouletteProbability * fMinWeight) && (fCountProcess == 4) && (fSurvivedToRR == false)) { + if ((fProposedWeight < fMinWeight) && + (fProposedWeight >= fRussianRouletteProbability * fMinWeight)) { G4double probability = G4UniformRand(); - - if (probability > fRussianRouletteProbability){ + if (probability > fRussianRouletteProbability) { fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); return &fParticleChange; - } - else { - fProposedWeight = fProposedWeight/fRussianRouletteProbability; - //std::cout<<"lim "<< fMinWeight << " RR " <<fRussianRouletteProbability<< " weight " <<fProposedWeight<<std::endl; - fSurvivedToRR = true; + } else { + fProposedWeight = fProposedWeight / fRussianRouletteProbability; } } + + fParticleChange.ProposeWeight(fProposedWeight); + fOperationComplete = true; + } + if (track->IsGoodForTracking() == 1) { fParticleChange.ProposeWeight(fProposedWeight); fOperationComplete = true; + } + } else { + if ((fProposedWeight < fRussianRouletteProbability * fMinWeight) || + ((fProposedWeight < fMinWeight) && (fSurvivedToRR == true))) { + fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); + return &fParticleChange; + } + + if ((fProposedWeight < fMinWeight) && + (fProposedWeight >= fRussianRouletteProbability * fMinWeight) && + (fCountProcess == 4) && (fSurvivedToRR == false)) { + G4double probability = G4UniformRand(); + + if (probability > fRussianRouletteProbability) { + fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); + return &fParticleChange; + } else { + fProposedWeight = fProposedWeight / fRussianRouletteProbability; + // std::cout<<"lim "<< fMinWeight << " RR " + // <<fRussianRouletteProbability<< " weight " + // <<fProposedWeight<<std::endl; + fSurvivedToRR = true; + } + } + fParticleChange.ProposeWeight(fProposedWeight); + fOperationComplete = true; } -return &fParticleChange; + return &fParticleChange; } -void GateOptnForceFreeFlight ::AlongMoveBy( const G4BiasingProcessInterface* callingProcess, const G4Step*, G4double weightChange ) +void GateOptnForceFreeFlight ::AlongMoveBy( + const G4BiasingProcessInterface *callingProcess, const G4Step *, + G4double weightChange) { - fWeightChange[callingProcess->GetWrappedProcess()->GetProcessName()] = weightChange; + fWeightChange[callingProcess->GetWrappedProcess()->GetProcessName()] = + weightChange; } diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h index cf00f0964..3a1682d71 100644 --- a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h +++ b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h @@ -27,7 +27,7 @@ // //--------------------------------------------------------------- // -// GateOptnForceFreeFlight +// GateOptnForceFreeFlight // // Class Description: // A G4VBiasingOperation physics-based biasing operation. @@ -46,67 +46,73 @@ #ifndef GateOptnForceFreeFlight_h #define GateOptnForceFreeFlight_h 1 -#include "G4VBiasingOperation.hh" #include "G4ForceCondition.hh" #include "G4ParticleChange.hh" // -- ยงยง should add a dedicated "weight change only" particle change +#include "G4VBiasingOperation.hh" class G4ILawForceFreeFlight; - -class GateOptnForceFreeFlight : public G4VBiasingOperation { +class GateOptnForceFreeFlight : public G4VBiasingOperation { public: // -- Constructor : - GateOptnForceFreeFlight (G4String name); + GateOptnForceFreeFlight(G4String name); // -- destructor: - virtual ~GateOptnForceFreeFlight (); - + virtual ~GateOptnForceFreeFlight(); + public: // -- Methods from G4VBiasingOperation interface: // ------------------------------------------- // -- Used: - virtual const G4VBiasingInteractionLaw* ProvideOccurenceBiasingInteractionLaw( const G4BiasingProcessInterface*, G4ForceCondition& ); - virtual void AlongMoveBy( const G4BiasingProcessInterface*, const G4Step*, G4double ); - virtual G4VParticleChange* ApplyFinalStateBiasing( const G4BiasingProcessInterface*, const G4Track*, const G4Step*, G4bool&); + virtual const G4VBiasingInteractionLaw * + ProvideOccurenceBiasingInteractionLaw(const G4BiasingProcessInterface *, + G4ForceCondition &); + virtual void AlongMoveBy(const G4BiasingProcessInterface *, const G4Step *, + G4double); + virtual G4VParticleChange * + ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, + const G4Step *, G4bool &); // -- Unused: - virtual G4double DistanceToApplyOperation( const G4Track*, - G4double, - G4ForceCondition*) {return DBL_MAX;} - virtual G4VParticleChange* GenerateBiasingFinalState( const G4Track*, - const G4Step* ) {return 0;} - + virtual G4double DistanceToApplyOperation(const G4Track *, G4double, + G4ForceCondition *) { + return DBL_MAX; + } + virtual G4VParticleChange *GenerateBiasingFinalState(const G4Track *, + const G4Step *) { + return 0; + } public: // -- Additional methods, specific to this class: // ---------------------------------------------- // -- return concrete type of interaction law: - G4ILawForceFreeFlight* GetForceFreeFlightLaw() { + G4ILawForceFreeFlight *GetForceFreeFlightLaw() { return fForceFreeFlightInteractionLaw; } // -- initialization for weight: - //void ResetInitialTrackWeight(G4double w) {fInitialTrackWeight = w; fCumulatedWeightChange = 1.0;} - + // void ResetInitialTrackWeight(G4double w) {fInitialTrackWeight = w; + // fCumulatedWeightChange = 1.0;} - void SetMinWeight(G4double w){fMinWeight = w;} - void SetUseOfProbes(G4bool p){fUseProbes = p;} - G4double GetTrackWeight(){return fProposedWeight;} - void SetTrackWeight(G4double w){fProposedWeight = w;} - void SetRussianRouletteProbability(G4double p){fRussianRouletteProbability= p;} - void SetCountProcess(G4int N){fCountProcess = N;} - void SetSurvivedToRR(G4bool b){fSurvivedToRR = b;} - G4bool GetSurvivedToRR(){return fSurvivedToRR;} + void SetMinWeight(G4double w) { fMinWeight = w; } + void SetUseOfProbes(G4bool p) { fUseProbes = p; } + G4double GetTrackWeight() { return fProposedWeight; } + void SetTrackWeight(G4double w) { fProposedWeight = w; } + void SetRussianRouletteProbability(G4double p) { + fRussianRouletteProbability = p; + } + void SetCountProcess(G4int N) { fCountProcess = N; } + void SetSurvivedToRR(G4bool b) { fSurvivedToRR = b; } + G4bool GetSurvivedToRR() { return fSurvivedToRR; } G4bool OperationComplete() const { return fOperationComplete; } - + private: - G4ILawForceFreeFlight* fForceFreeFlightInteractionLaw; - std::map<G4String,G4double> fWeightChange; + G4ILawForceFreeFlight *fForceFreeFlightInteractionLaw; + std::map<G4String, G4double> fWeightChange; G4bool fUseProbes; - G4double fMinWeight, - fRussianRouletteProbability; - G4ParticleChange fParticleChange; - G4bool fOperationComplete; - G4double fProposedWeight; - G4int fCountProcess; - G4bool fSurvivedToRR; - + G4double fMinWeight, fRussianRouletteProbability; + G4ParticleChange fParticleChange; + G4bool fOperationComplete; + G4double fProposedWeight; + G4int fCountProcess; + G4bool fSurvivedToRR; }; #endif diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp index 79c5cb772..9db37db28 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp @@ -30,42 +30,46 @@ #include "GateHelpersDict.h" #include "GateHelpersImage.h" +#include "CLHEP/Units/SystemOfUnits.h" #include "G4BiasingProcessInterface.hh" +#include "G4EmCalculator.hh" #include "G4Gamma.hh" #include "G4LogicalVolumeStore.hh" +#include "G4ParticleTable.hh" #include "G4PhysicalVolumeStore.hh" -#include "GateOptnComptSplittingForTransportation.h" -#include "GateOptrComptPseudoTransportationActor.h" #include "G4ProcessManager.hh" -#include "G4VEmProcess.hh" -#include "G4EmCalculator.hh" #include "G4ProcessVector.hh" -#include "G4ParticleTable.hh" -#include "G4Gamma.hh" -#include "CLHEP/Units/SystemOfUnits.h" +#include "G4RunManager.hh" #include "G4TrackStatus.hh" #include "G4UserTrackingAction.hh" -#include "G4RunManager.hh" +#include "G4VEmProcess.hh" +#include "GateOptnComptSplittingForTransportation.h" +#include "GateOptrComptPseudoTransportationActor.h" //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptrComptPseudoTransportationActor::GateOptrComptPseudoTransportationActor(py::dict &user_info) +GateOptrComptPseudoTransportationActor::GateOptrComptPseudoTransportationActor( + py::dict &user_info) : G4VBiasingOperator("ComptSplittingOperator"), GateVActor(user_info, false) { fMotherVolumeName = DictGetStr(user_info, "mother"); fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); fWeightThreshold = DictGetDouble(user_info, "weight_threshold"); - fRelativeMinWeightOfParticle = DictGetDouble(user_info, "relative_min_weight_of_particle"); - //Since the russian roulette uses as a probability 1/splitting, we need to have a double, - //but the splitting factor provided by the user is logically an int, so we need to change the type. + fRelativeMinWeightOfParticle = + DictGetDouble(user_info, "relative_min_weight_of_particle"); + // Since the russian roulette uses as a probability 1/splitting, we need to + // have a double, but the splitting factor provided by the user is logically + // an int, so we need to change the type. fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); fRussianRoulette = DictGetBool(user_info, "russian_roulette"); fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); - fMaxTheta = DictGetDouble(user_info,"max_theta"); - fRussianRouletteForFreeFlight = DictGetDouble(user_info,"russian_roulette_for_free_flight"); + fMaxTheta = DictGetDouble(user_info, "max_theta"); + fRussianRouletteForFreeFlight = + DictGetDouble(user_info, "russian_roulette_for_free_flight"); fFreeFlightOperation = new GateOptnForceFreeFlight("freeFlightOperation"); - fComptSplittingOperation = new GateOptnComptSplittingForTransportation("comptSplittingOperation"); - fUseProbes = DictGetBool(user_info,"use_probes"); + fComptSplittingOperation = + new GateOptnComptSplittingForTransportation("comptSplittingOperation"); + fUseProbes = DictGetBool(user_info, "use_probes"); fActions.insert("StartSimulationAction"); fActions.insert("SteppingAction"); fActions.insert("BeginOfEventAction"); @@ -75,191 +79,187 @@ GateOptrComptPseudoTransportationActor::GateOptrComptPseudoTransportationActor(p //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -void GateOptrComptPseudoTransportationActor::AttachAllLogicalDaughtersVolumes(G4LogicalVolume* volume){ +void GateOptrComptPseudoTransportationActor::AttachAllLogicalDaughtersVolumes( + G4LogicalVolume *volume) { AttachTo(volume); G4int nbOfDaughters = volume->GetNoDaughters(); - if (nbOfDaughters >0 ){ - for (int i = 0; i< nbOfDaughters;i++){ - G4LogicalVolume* logicalDaughtersVolume = volume->GetDaughter(i)->GetLogicalVolume(); + if (nbOfDaughters > 0) { + for (int i = 0; i < nbOfDaughters; i++) { + G4LogicalVolume *logicalDaughtersVolume = + volume->GetDaughter(i)->GetLogicalVolume(); AttachAllLogicalDaughtersVolumes(logicalDaughtersVolume); } } } -void GateOptrComptPseudoTransportationActor::StartSimulationAction(){ - G4LogicalVolume* biasingVolume = G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); +void GateOptrComptPseudoTransportationActor::StartSimulationAction() { + G4LogicalVolume *biasingVolume = + G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); - //Here we need to attach all the daughters and daughters of daughters (...) to the biasing operator. - //To do that, I use the function AttachAllLogicalDaughtersVolumes. + // Here we need to attach all the daughters and daughters of daughters (...) + // to the biasing operator. To do that, I use the function + // AttachAllLogicalDaughtersVolumes. AttachAllLogicalDaughtersVolumes(biasingVolume); fComptSplittingOperation->SetSplittingFactor(fSplittingFactor); fComptSplittingOperation->SetWeightThreshold(fWeightThreshold); fComptSplittingOperation->SetMaxTheta(fMaxTheta); fComptSplittingOperation->SetRussianRoulette(fRussianRoulette); fComptSplittingOperation->SetUseOfProbes(fUseProbes); - fFreeFlightOperation->SetRussianRouletteProbability(fRussianRouletteForFreeFlight); + fFreeFlightOperation->SetRussianRouletteProbability( + fRussianRouletteForFreeFlight); fFreeFlightOperation->SetUseOfProbes(fUseProbes); - - } void GateOptrComptPseudoTransportationActor::StartRun() { // The way to behave of the russian roulette is the following : - // we provide a vector director and the theta angle acceptance, where theta = 0 is a vector colinear to the vector director - // Then if the track generated is on the acceptance angle, we add it to the primary track, and if it's not the case, we launch the russian roulette - if (fRotationVectorDirector){ - G4VPhysicalVolume* physBiasingVolume = G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); - auto rot = physBiasingVolume -> GetObjectRotationValue(); + // we provide a vector director and the theta angle acceptance, where theta = + // 0 is a vector colinear to the vector director Then if the track generated + // is on the acceptance angle, we add it to the primary track, and if it's not + // the case, we launch the russian roulette + if (fRotationVectorDirector) { + G4VPhysicalVolume *physBiasingVolume = + G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + auto rot = physBiasingVolume->GetObjectRotationValue(); fVectorDirector = rot * fVectorDirector; fComptSplittingOperation->SetRotationMatrix(rot); } - + fComptSplittingOperation->SetVectorDirector(fVectorDirector); - - } +void GateOptrComptPseudoTransportationActor::SteppingAction(G4Step *step) { + // The stepping action is used to kill particle we have too kill : + // - If the primary particle reach the biased boudary + // - KIll all the probes exiting the volume + // - Kill, if probes, particles wich have a weilght lower than the probes one - -void GateOptrComptPseudoTransportationActor::SteppingAction (G4Step *step) { - - //The stepping action is used to kill particle we have too kill : - // - If the primary particle reach the biased boudary - // - KIll all the probes exiting the volume - // - Kill, if probes, particles wich have a weilght lower than the probes one - if (fUseProbes) { - if ((fKillOthersParticles) && (step->GetTrack()->IsGoodForTracking() ==0)) - { + if ((fKillOthersParticles) && + (step->GetTrack()->IsGoodForTracking() == 0)) { step->GetTrack()->SetTrackStatus(G4TrackStatus::fStopAndKill); } - if (step->GetPostStepPoint()->GetStepStatus()!= fWorldBoundary){ - if ((step->GetPostStepPoint()->GetPhysicalVolume ()->GetName() == "world") && (step->GetTrack()->IsGoodForTracking() == 1)){ + if (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary) { + if ((step->GetPostStepPoint()->GetPhysicalVolume()->GetName() == + "world") && + (step->GetTrack()->IsGoodForTracking() == 1)) { step->GetTrack()->SetTrackStatus(G4TrackStatus::fStopAndKill); } } } - if ((isSplitted ==true) && (step->GetPostStepPoint()->GetStepStatus()!= fWorldBoundary)){ - if ((step->GetPostStepPoint()->GetPhysicalVolume ()->GetName() == "world")) - { + if ((isSplitted == true) && + (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary)) { + if ((step->GetPostStepPoint()->GetPhysicalVolume()->GetName() == "world")) { step->GetTrack()->SetTrackStatus(G4TrackStatus::fStopAndKill); - isSplitted =false; + isSplitted = false; } } - - - - } -void GateOptrComptPseudoTransportationActor::BeginOfEventAction(const G4Event *event) { +void GateOptrComptPseudoTransportationActor::BeginOfEventAction( + const G4Event *event) { fKillOthersParticles = false; } -void GateOptrComptPseudoTransportationActor::StartTracking(const G4Track *track) { -fInitialWeight = track->GetWeight(); -fFreeFlightOperation->SetSurvivedToRR(false); +void GateOptrComptPseudoTransportationActor::StartTracking( + const G4Track *track) { + fInitialWeight = track->GetWeight(); + fFreeFlightOperation->SetSurvivedToRR(false); } - -//For the following operation the idea is the following : -//All the potential photon processes are biased. If a particle undergoes a compton interaction, we splitted it -//(ComptonSplittingForTransportation operation) and the particle generated are pseudo-transported with the ForceFreeFLight operation -// Since the occurence Biaising operation is called at the beginning of each track, and propose a different way to track the particle -//(with modified physics), it here returns other thing than 0 if we want to pseudo-transport the particle, so if its creatorProcess is the -//modified compton interaction +// For the following operation the idea is the following : +// All the potential photon processes are biased. If a particle undergoes a +// compton interaction, we splitted it (ComptonSplittingForTransportation +//operation) and the particle generated are pseudo-transported with the +//ForceFreeFLight operation +// Since the occurence Biaising operation is called at the beginning of each +// track, and propose a different way to track the particle +//(with modified physics), it here returns other thing than 0 if we want to +//pseudo-transport the particle, so if its creatorProcess is the modified +// compton interaction G4VBiasingOperation * -GateOptrComptPseudoTransportationActor::ProposeOccurenceBiasingOperation(const G4Track* track, const G4BiasingProcessInterface* callingProcess) -{ -if (track->GetCreatorProcess () !=0){ - if (track->GetCreatorProcess ()->GetProcessName() == "biasWrapper(compt)"){ - fFreeFlightOperation->SetMinWeight(fInitialWeight/fRelativeMinWeightOfParticle); +GateOptrComptPseudoTransportationActor::ProposeOccurenceBiasingOperation( + const G4Track *track, const G4BiasingProcessInterface *callingProcess) { + if (track->GetCreatorProcess() != 0) { + if (track->GetCreatorProcess()->GetProcessName() == "biasWrapper(compt)") { + fFreeFlightOperation->SetMinWeight(fInitialWeight / + fRelativeMinWeightOfParticle); fFreeFlightOperation->SetTrackWeight(track->GetWeight()); fFreeFlightOperation->SetCountProcess(0); return fFreeFlightOperation; } - } return 0; } //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -// Here we call the final state biasing operation called if one of the biased interaction (all photon interaction here) occurs. -//That's why we need here to apply some conditions to just split the initial track. +// Here we call the final state biasing operation called if one of the biased +// interaction (all photon interaction here) occurs. +// That's why we need here to apply some conditions to just split the initial +// track. G4VBiasingOperation * GateOptrComptPseudoTransportationActor::ProposeFinalStateBiasingOperation( const G4Track *track, const G4BiasingProcessInterface *callingProcess) { - G4String callingProcessName = "biasWrapper(compt)"; - + G4String callingProcessName = "biasWrapper(compt)"; - if (callingProcess->GetWrappedProcess()->GetProcessName() == "compt") - { - if (track->GetCreatorProcess() ==0) { - isSplitted = true; - return fComptSplittingOperation; - } - if (track->GetCreatorProcess()-> GetProcessName() != "biasWrapper(compt)"){ - isSplitted = true; - return fComptSplittingOperation; + if (callingProcess->GetWrappedProcess()->GetProcessName() == "compt") { + if (track->GetCreatorProcess() == 0) { + isSplitted = true; + return fComptSplittingOperation; + } + if (track->GetCreatorProcess()->GetProcessName() != "biasWrapper(compt)") { + isSplitted = true; + return fComptSplittingOperation; + } } - } - /* - */ - if (track->GetCreatorProcess() !=0){ - if(track->GetCreatorProcess()-> GetProcessName() == "biasWrapper(compt)"){ - return callingProcess->GetCurrentOccurenceBiasingOperation(); + /* + */ + if (track->GetCreatorProcess() != 0) { + if (track->GetCreatorProcess()->GetProcessName() == "biasWrapper(compt)") { + return callingProcess->GetCurrentOccurenceBiasingOperation(); + } } - } - - return 0; - - //return 0; -} + return 0; + // return 0; +} void GateOptrComptPseudoTransportationActor::EndTracking() { -isSplitted =false; + isSplitted = false; } +void GateOptrComptPseudoTransportationActor::PostUserTrackingAction( + const G4Track *track) { -void GateOptrComptPseudoTransportationActor::PostUserTrackingAction(const G4Track *track) { - -if (fUseProbes){ - if (track->IsGoodForTracking() == 1){ - G4double tmpWeight = fFreeFlightOperation->GetTrackWeight(); - if (NbOfProbe == 1) - { - fKillOthersParticles = false; - weight = tmpWeight; - } - else{ - if (tmpWeight > weight){ + if (fUseProbes) { + if (track->IsGoodForTracking() == 1) { + G4double tmpWeight = fFreeFlightOperation->GetTrackWeight(); + if (NbOfProbe == 1) { + fKillOthersParticles = false; weight = tmpWeight; + } else { + if (tmpWeight > weight) { + weight = tmpWeight; + } } - } - NbOfProbe ++; - if ((NbOfProbe == 6)){ - if (weight < fInitialWeight/fRelativeMinWeightOfParticle){ - fKillOthersParticles = true; - } + NbOfProbe++; + if ((NbOfProbe == 6)) { + if (weight < fInitialWeight / fRelativeMinWeightOfParticle) { + fKillOthersParticles = true; + } NbOfProbe = 1; weight = 0; - + } } } } -} - - - //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h index 020ef2d73..31cf53846 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h @@ -29,9 +29,9 @@ #ifndef GateOptrComptPseudoTransportationActor_h #define GateOptrComptPseudoTransportationActor_h 1 +#include "G4EmCalculator.hh" #include "G4VBiasingOperator.hh" #include "GateOptnForceFreeFlight.h" -#include "G4EmCalculator.hh" #include "GateVActor.h" #include <iostream> @@ -41,7 +41,7 @@ namespace py = pybind11; class GateOptnComptSplittingForTransportation; class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, - public GateVActor { + public GateVActor { public: GateOptrComptPseudoTransportationActor(py::dict &user_info); virtual ~GateOptrComptPseudoTransportationActor() {} @@ -82,27 +82,25 @@ class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, virtual void StartSimulationAction(); virtual void StartRun(); virtual void StartTracking(const G4Track *); - virtual void PostUserTrackingAction(const G4Track * track); - virtual void SteppingAction(G4Step*); - virtual void BeginOfEventAction(const G4Event*); + virtual void PostUserTrackingAction(const G4Track *track); + virtual void SteppingAction(G4Step *); + virtual void BeginOfEventAction(const G4Event *); virtual void EndTracking(); - protected: // ----------------------------- // -- Mandatory from base class: // ----------------------------- // -- Unused: - void AttachAllLogicalDaughtersVolumes(G4LogicalVolume*); + void AttachAllLogicalDaughtersVolumes(G4LogicalVolume *); virtual G4VBiasingOperation *ProposeNonPhysicsBiasingOperation( const G4Track * /* track */, const G4BiasingProcessInterface * /* callingProcess */) { return 0; } - // -- Used: - virtual G4VBiasingOperation *ProposeOccurenceBiasingOperation( + virtual G4VBiasingOperation *ProposeOccurenceBiasingOperation( const G4Track * /* track */, const G4BiasingProcessInterface * /* callingProcess */); @@ -115,8 +113,8 @@ class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, using G4VBiasingOperator::OperationApplied; private: - GateOptnForceFreeFlight *fFreeFlightOperation ; - GateOptnComptSplittingForTransportation *fComptSplittingOperation ; + GateOptnForceFreeFlight *fFreeFlightOperation; + GateOptnComptSplittingForTransportation *fComptSplittingOperation; }; #endif diff --git a/core/opengate_core/opengate_lib/GateOptrComptSplittingActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptSplittingActor.cpp index 430e46178..c17e5ccfb 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateOptrComptSplittingActor.cpp @@ -30,17 +30,16 @@ #include "GateHelpersDict.h" #include "GateHelpersImage.h" +#include "CLHEP/Units/SystemOfUnits.h" #include "G4BiasingProcessInterface.hh" #include "G4Gamma.hh" #include "G4LogicalVolumeStore.hh" +#include "G4ParticleTable.hh" #include "G4PhysicalVolumeStore.hh" -#include "GateOptnComptSplitting.h" -#include "GateOptrComptSplittingActor.h" #include "G4ProcessManager.hh" #include "G4ProcessVector.hh" -#include "G4ParticleTable.hh" -#include "G4Gamma.hh" -#include "CLHEP/Units/SystemOfUnits.h" +#include "GateOptnComptSplitting.h" +#include "GateOptrComptSplittingActor.h" //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... @@ -51,14 +50,15 @@ GateOptrComptSplittingActor::GateOptrComptSplittingActor(py::dict &user_info) fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); fWeightThreshold = DictGetDouble(user_info, "weight_threshold"); fMinWeightOfParticle = DictGetDouble(user_info, "min_weight_of_particle"); - //Since the russian roulette uses as a probablity 1/splitting, we need to have a double, - //but the splitting factor provided by the user is logically an int, so we need to change the type. + // Since the russian roulette uses as a probablity 1/splitting, we need to + // have a double, but the splitting factor provided by the user is logically + // an int, so we need to change the type. fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); fBiasPrimaryOnly = DictGetBool(user_info, "bias_primary_only"); fBiasOnlyOnce = DictGetBool(user_info, "bias_only_once"); fRussianRoulette = DictGetBool(user_info, "russian_roulette"); fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); - fMaxTheta = DictGetDouble(user_info,"max_theta"); + fMaxTheta = DictGetDouble(user_info, "max_theta"); fComptSplittingOperation = new GateOptnComptSplitting("ComptSplittingOperation"); fActions.insert("StartSimulationAction"); @@ -66,22 +66,26 @@ GateOptrComptSplittingActor::GateOptrComptSplittingActor(py::dict &user_info) //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -void GateOptrComptSplittingActor::AttachAllLogicalDaughtersVolumes(G4LogicalVolume* volume){ +void GateOptrComptSplittingActor::AttachAllLogicalDaughtersVolumes( + G4LogicalVolume *volume) { AttachTo(volume); G4int nbOfDaughters = volume->GetNoDaughters(); - if (nbOfDaughters >0 ){ - for (int i = 0; i< nbOfDaughters;i++){ - G4LogicalVolume* logicalDaughtersVolume = volume->GetDaughter(i)->GetLogicalVolume(); + if (nbOfDaughters > 0) { + for (int i = 0; i < nbOfDaughters; i++) { + G4LogicalVolume *logicalDaughtersVolume = + volume->GetDaughter(i)->GetLogicalVolume(); AttachAllLogicalDaughtersVolumes(logicalDaughtersVolume); } } } -void GateOptrComptSplittingActor::StartSimulationAction(){ - G4LogicalVolume* biasingVolume = G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); +void GateOptrComptSplittingActor::StartSimulationAction() { + G4LogicalVolume *biasingVolume = + G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); - //Here we need to attach all the daughters and daughters of daughters (...) to the biasing operator. - //To do that, I use the function AttachAllLogicalDaughtersVolumes. + // Here we need to attach all the daughters and daughters of daughters (...) + // to the biasing operator. To do that, I use the function + // AttachAllLogicalDaughtersVolumes. AttachAllLogicalDaughtersVolumes(biasingVolume); fComptSplittingOperation->SetSplittingFactor(fSplittingFactor); fComptSplittingOperation->SetWeightThreshold(fWeightThreshold); @@ -93,26 +97,24 @@ void GateOptrComptSplittingActor::StartSimulationAction(){ void GateOptrComptSplittingActor::StartRun() { // The way to behave of the russian roulette is the following : - // we provide a vector director and the theta angle acceptance, where theta = 0 is a vector colinear to the vector director - // Then if the track generated is on the acceptance angle, we add it to the primary track, and if it's not the case, we launch the russian roulette - if (fRotationVectorDirector){ - G4VPhysicalVolume* physBiasingVolume = G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); - auto rot = physBiasingVolume -> GetObjectRotationValue(); + // we provide a vector director and the theta angle acceptance, where theta = + // 0 is a vector colinear to the vector director Then if the track generated + // is on the acceptance angle, we add it to the primary track, and if it's not + // the case, we launch the russian roulette + if (fRotationVectorDirector) { + G4VPhysicalVolume *physBiasingVolume = + G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + auto rot = physBiasingVolume->GetObjectRotationValue(); fVectorDirector = rot * fVectorDirector; } - + fComptSplittingOperation->SetVectorDirector(fVectorDirector); - } - void GateOptrComptSplittingActor::StartTracking(const G4Track *track) { fNInteractions = 0; - } - - //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... G4VBiasingOperation * @@ -122,13 +124,8 @@ GateOptrComptSplittingActor::ProposeFinalStateBiasingOperation( return 0; if (fBiasOnlyOnce && (fNInteractions > 0)) return 0; - fNInteractions ++; + fNInteractions++; return fComptSplittingOperation; - } - - - - //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptrComptSplittingActor.h b/core/opengate_core/opengate_lib/GateOptrComptSplittingActor.h index fc376a3d1..c5f4ead04 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateOptrComptSplittingActor.h @@ -73,13 +73,12 @@ class GateOptrComptSplittingActor : public G4VBiasingOperator, virtual void StartTracking(const G4Track *); virtual void EndTracking() {} - protected: // ----------------------------- // -- Mandatory from base class: // ----------------------------- // -- Unused: - void AttachAllLogicalDaughtersVolumes(G4LogicalVolume*); + void AttachAllLogicalDaughtersVolumes(G4LogicalVolume *); virtual G4VBiasingOperation *ProposeNonPhysicsBiasingOperation( const G4Track * /* track */, const G4BiasingProcessInterface * /* callingProcess */) { diff --git a/core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp index ae0344c08..a5cb86c35 100644 --- a/core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp @@ -12,8 +12,9 @@ namespace py = pybind11; void init_GateOptrComptPseudoTransportationActor(py::module &m) { - py::class_<GateOptrComptPseudoTransportationActor, G4VBiasingOperator, GateVActor, - std::unique_ptr<GateOptrComptPseudoTransportationActor, py::nodelete>>( + py::class_< + GateOptrComptPseudoTransportationActor, G4VBiasingOperator, GateVActor, + std::unique_ptr<GateOptrComptPseudoTransportationActor, py::nodelete>>( m, "GateOptrComptPseudoTransportationActor") .def(py::init<py::dict &>()); } diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 10fb305c5..501147cc5 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -444,6 +444,7 @@ def __init__(self, user_info): class ComptSplittingActor(g4.GateOptrComptSplittingActor, ActorBase): type_name = "ComptSplittingActor" + def set_default_user_info(user_info): ActorBase.set_default_user_info(user_info) deg = g4_units.deg @@ -455,16 +456,19 @@ def set_default_user_info(user_info): user_info.processes = ["compt"] user_info.russian_roulette = False user_info.rotation_vector_director = False - user_info.vector_director = [0,0,1] - user_info.max_theta = 90*deg + user_info.vector_director = [0, 0, 1] + user_info.max_theta = 90 * deg def __init__(self, user_info): ActorBase.__init__(self, user_info) g4.GateOptrComptSplittingActor.__init__(self, user_info.__dict__) -class ComptPseudoTransportationActor(g4.GateOptrComptPseudoTransportationActor, ActorBase): +class ComptPseudoTransportationActor( + g4.GateOptrComptPseudoTransportationActor, ActorBase +): type_name = "ComptPseudoTransportationActor" + def set_default_user_info(user_info): ActorBase.set_default_user_info(user_info) deg = g4_units.deg @@ -473,13 +477,13 @@ def set_default_user_info(user_info): user_info.bias_primary_only = True user_info.relative_min_weight_of_particle = 0 user_info.bias_only_once = True - user_info.processes = ["compt","phot","conv","Rayl"] + user_info.processes = ["compt", "phot", "conv", "Rayl"] user_info.russian_roulette = False user_info.rotation_vector_director = False - user_info.vector_director = [0,0,1] - user_info.max_theta = 90*deg - user_info.russian_roulette_for_free_flight = 1/10 - user_info.use_probes = False; + user_info.vector_director = [0, 0, 1] + user_info.max_theta = 90 * deg + user_info.russian_roulette_for_free_flight = 1 / 10 + user_info.use_probes = False def __init__(self, user_info): ActorBase.__init__(self, user_info) diff --git a/opengate/managers.py b/opengate/managers.py index 53134d638..826430808 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -346,9 +346,9 @@ class PhysicsListManager(GateObject): ) special_physics_constructor_classes["G4OpticalPhysics"] = g4.G4OpticalPhysics special_physics_constructor_classes["G4EmDNAPhysics"] = g4.G4EmDNAPhysics - special_physics_constructor_classes[ - "G4GenericBiasingPhysics" - ] = g4.G4GenericBiasingPhysics + special_physics_constructor_classes["G4GenericBiasingPhysics"] = ( + g4.G4GenericBiasingPhysics + ) def __init__(self, physics_manager, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/opengate/tests/src/test071_operator_russian_roulette.py b/opengate/tests/src/test071_operator_russian_roulette.py index efa78636d..6aea92929 100644 --- a/opengate/tests/src/test071_operator_russian_roulette.py +++ b/opengate/tests/src/test071_operator_russian_roulette.py @@ -8,52 +8,57 @@ from opengate.tests import utility - -def validation_test_RR(arr_data,rotation,vector_of_direction,theta,nb_splitting,tol_weights = 0.08): - arr_data = arr_data[(arr_data["TrackCreatorProcess"] != 'phot') & (arr_data["TrackCreatorProcess"] != 'eBrem') & ( - arr_data["TrackCreatorProcess"] != 'eIoni') & (arr_data['ParticleName'] == 'gamma')] +def validation_test_RR( + arr_data, rotation, vector_of_direction, theta, nb_splitting, tol_weights=0.08 +): + arr_data = arr_data[ + (arr_data["TrackCreatorProcess"] != "phot") + & (arr_data["TrackCreatorProcess"] != "eBrem") + & (arr_data["TrackCreatorProcess"] != "eIoni") + & (arr_data["ParticleName"] == "gamma") + ] EventID = arr_data["EventID"] list_of_weights = [] weights = 0 for i in range(len(EventID)): if i == 0: - weights+= arr_data["Weight"][i] - else : - if EventID[i] == EventID[i -1]: + weights += arr_data["Weight"][i] + else: + if EventID[i] == EventID[i - 1]: weights += arr_data["Weight"][i] - else : + else: list_of_weights.append(weights) - weights =arr_data["Weight"][i] + weights = arr_data["Weight"][i] list_of_weights = np.array(list_of_weights) mean_weights = np.mean(list_of_weights) bool_weight = False - if 1-tol_weights <mean_weights < 1 + tol_weights: + if 1 - tol_weights < mean_weights < 1 + tol_weights: bool_weight = True weights = arr_data["Weight"][EventID == EventID[0]] - rotated_vector = np.dot(rotation,np.array(vector_of_direction)) - #2.418766 + rotated_vector = np.dot(rotation, np.array(vector_of_direction)) + # 2.418766 arr_first_evt = arr_data[EventID == EventID[0]] - arr_first_evt_dir_X =arr_first_evt["PreDirection_X"].to_numpy() + arr_first_evt_dir_X = arr_first_evt["PreDirection_X"].to_numpy() arr_first_evt_dir_Y = arr_first_evt["PreDirection_Y"].to_numpy() arr_first_evt_dir_Z = arr_first_evt["PreDirection_Z"].to_numpy() - arr_first_evt_dir = np.transpose(np.array([arr_first_evt_dir_X,arr_first_evt_dir_Y,arr_first_evt_dir_Z])) - tab_costheta = np.sum(rotated_vector*arr_first_evt_dir,axis=1) - tab_theta = np.arccos(tab_costheta) *180/np.pi *deg - + arr_first_evt_dir = np.transpose( + np.array([arr_first_evt_dir_X, arr_first_evt_dir_Y, arr_first_evt_dir_Z]) + ) + tab_costheta = np.sum(rotated_vector * arr_first_evt_dir, axis=1) + tab_theta = np.arccos(tab_costheta) * 180 / np.pi * deg - bool_russian_roulette_1 = bool(1 - np.sum((tab_theta[weights == 1/nb_splitting] > theta))) + bool_russian_roulette_1 = bool( + 1 - np.sum((tab_theta[weights == 1 / nb_splitting] > theta)) + ) bool_russian_roulette_2 = bool(1 - np.sum((tab_theta[weights == 1] <= theta))) - print('Average weight of :',mean_weights) + print("Average weight of :", mean_weights) if bool_russian_roulette_1 and bool_russian_roulette_2 and bool_weight: return True - else : + else: return False - - - if __name__ == "__main__": paths = utility.get_default_test_paths( __file__, "test071test_operator_compt_splitting_RR", output_folder="test071" @@ -68,7 +73,7 @@ def validation_test_RR(arr_data,rotation,vector_of_direction,theta,nb_splitting, # ui.visu = True # ui.visu_type = "vrml" ui.check_volumes_overlap = False - #ui.running_verbose_level = gate.logger.EVENT + # ui.running_verbose_level = gate.logger.EVENT ui.number_of_threads = 1 ui.random_seed = "auto" @@ -85,7 +90,6 @@ def validation_test_RR(arr_data,rotation,vector_of_direction,theta,nb_splitting, gcm3 = gate.g4_units.g / gate.g4_units.cm3 deg = gate.g4_units.deg - # adapt world size world = sim.world world.size = [0.25 * m, 0.25 * m, 0.25 * m] @@ -121,13 +125,13 @@ def validation_test_RR(arr_data,rotation,vector_of_direction,theta,nb_splitting, ####### Compton Splitting ACTOR ######### nb_split = 19.4 - theta_max = 90*deg + theta_max = 90 * deg compt_splitting_actor = sim.add_actor("ComptSplittingActor", "ComptSplittingW") compt_splitting_actor.mother = W_tubs.name compt_splitting_actor.splitting_factor = nb_split compt_splitting_actor.russian_roulette = True compt_splitting_actor.rotation_vector_director = True - compt_splitting_actor.vector_director = [0,0,-1] + compt_splitting_actor.vector_director = [0, 0, -1] compt_splitting_actor.max_theta = theta_max list_processes_to_bias = compt_splitting_actor.processes @@ -137,7 +141,7 @@ def validation_test_RR(arr_data,rotation,vector_of_direction,theta,nb_splitting, plan_tubs.material = "G4_Galactic" plan_tubs.mother = world.name plan_tubs.rmin = W_tubs.rmax - plan_tubs.rmax = plan_tubs.rmin + 1 * nm + plan_tubs.rmax = plan_tubs.rmin + 1 * nm plan_tubs.dz = 0.5 * m plan_tubs.color = [0.2, 1, 0.8, 1] plan_tubs.rotation = rotation @@ -176,19 +180,17 @@ def validation_test_RR(arr_data,rotation,vector_of_direction,theta,nb_splitting, sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option2" ## Perhaps avoid the user to call the below boolean function ? ### sim.physics_manager.special_physics_constructors.G4GenericBiasingPhysics = True - sim.physics_manager.processes_to_bias.gamma= list_processes_to_bias + sim.physics_manager.processes_to_bias.gamma = list_processes_to_bias #### Extremely important, it seems that GEANT4, for almost all physics lists, encompass all the photon processes in GammaGeneralProc #### Therefore if we provide the name of the real process (here compt) without deactivating GammaGeneralProcess, it will not find the #### process to bias and the biasing will fail s = f"/process/em/UseGeneralProcess false" sim.add_g4_command_before_init(s) - sim.physics_manager.global_production_cuts.gamma = 1 *m + sim.physics_manager.global_production_cuts.gamma = 1 * m sim.physics_manager.global_production_cuts.electron = 1 * um sim.physics_manager.global_production_cuts.positron = 1 * km - - output = sim.run() # @@ -199,5 +201,7 @@ def validation_test_RR(arr_data,rotation,vector_of_direction,theta,nb_splitting, f_data = uproot.open(paths.output / "test071_output_data_RR.root") arr_data = f_data["PhaseSpace"].arrays() - is_ok = validation_test_RR(arr_data,rotation,compt_splitting_actor.vector_director,theta_max,nb_split) + is_ok = validation_test_RR( + arr_data, rotation, compt_splitting_actor.vector_director, theta_max, nb_split + ) utility.test_ok(is_ok) diff --git a/opengate/tests/src/test072_pseudo_transportation.py b/opengate/tests/src/test072_pseudo_transportation.py index 967d8c4d0..ea981641f 100644 --- a/opengate/tests/src/test072_pseudo_transportation.py +++ b/opengate/tests/src/test072_pseudo_transportation.py @@ -11,18 +11,22 @@ from opengate.tests import utility - -def validation_test(arr,NIST_data,nb_split,tol = 0.01): +def validation_test(arr, NIST_data, nb_split, tol=0.01): tab_ekin = NIST_data[:, 0] mu_att = NIST_data[:, -2] log_ekin = np.log(tab_ekin) log_mu = np.log(mu_att) - f_mu = scipy.interpolate.interp1d(log_ekin, log_mu, kind='cubic') + f_mu = scipy.interpolate.interp1d(log_ekin, log_mu, kind="cubic") # print(arr["TrackCreatorProcess"]) - Tracks = arr[(arr["TrackCreatorProcess"] == 'biasWrapper(compt)') & (arr["KineticEnergy"] > 0.1) & (arr["ParticleName"] == "gamma") & (arr["Weight"] > 10**(-20))] - ekin_tracks = Tracks['KineticEnergy'] + Tracks = arr[ + (arr["TrackCreatorProcess"] == "biasWrapper(compt)") + & (arr["KineticEnergy"] > 0.1) + & (arr["ParticleName"] == "gamma") + & (arr["Weight"] > 10 ** (-20)) + ] + ekin_tracks = Tracks["KineticEnergy"] x_vertex = Tracks["TrackVertexPosition_X"] y_vertex = Tracks["TrackVertexPosition_Y"] z_vertex = Tracks["TrackVertexPosition_Z"] @@ -30,16 +34,19 @@ def validation_test(arr,NIST_data,nb_split,tol = 0.01): y = Tracks["PrePosition_Y"] z = Tracks["PrePosition_Z"] weights = Tracks["Weight"] * nb_split - dist = np.sqrt((x-x_vertex)**2 + (y-y_vertex)**2 + (z-z_vertex)**2) + dist = np.sqrt((x - x_vertex) ** 2 + (y - y_vertex) ** 2 + (z - z_vertex) ** 2) - G4_mu = -np.log(weights)/(0.1*dist*19.3) + G4_mu = -np.log(weights) / (0.1 * dist * 19.3) X_com_mu = np.exp(f_mu(np.log(ekin_tracks))) - diff = (G4_mu - X_com_mu)/G4_mu - print("Median difference between mu calculated from XCOM database and from GEANT4 free flight operation:", np.round(100*np.median(diff),1),"%") + diff = (G4_mu - X_com_mu) / G4_mu + print( + "Median difference between mu calculated from XCOM database and from GEANT4 free flight operation:", + np.round(100 * np.median(diff), 1), + "%", + ) return np.median(diff) < tol - if __name__ == "__main__": paths = utility.get_default_test_paths( __file__, "test072_pseudo_transportation", output_folder="test072" @@ -91,31 +98,41 @@ def validation_test(arr,NIST_data,nb_split,tol = 0.01): simple_collimation.color = [0.3, 0.1, 0.8, 1] W_leaf = sim.add_volume("Box", "W_leaf") - W_leaf.mother = simple_collimation.name - W_leaf.size = [1*m,1*m,2*cm] - W_leaf.material = 'Tungsten' - leaf_translation =[] + W_leaf.mother = simple_collimation.name + W_leaf.size = [1 * m, 1 * m, 2 * cm] + W_leaf.material = "Tungsten" + leaf_translation = [] for i in range(10): - leaf_translation.append([0,0, - 0.5*simple_collimation.size[2] + 0.5* W_leaf.size[2] + i * W_leaf.size[2]]) + leaf_translation.append( + [ + 0, + 0, + -0.5 * simple_collimation.size[2] + + 0.5 * W_leaf.size[2] + + i * W_leaf.size[2], + ] + ) print(leaf_translation) - W_leaf.translation =leaf_translation + W_leaf.translation = leaf_translation W_leaf.color = [0.8, 0.2, 0.1, 1] ######## pseudo_transportation ACTOR ######### nb_split = 5 - pseudo_transportation_actor = sim.add_actor("ComptPseudoTransportationActor", "pseudo_transportation_actor") + pseudo_transportation_actor = sim.add_actor( + "ComptPseudoTransportationActor", "pseudo_transportation_actor" + ) pseudo_transportation_actor.mother = simple_collimation.name pseudo_transportation_actor.splitting_factor = nb_split pseudo_transportation_actor.relative_min_weight_of_particle = np.inf list_processes_to_bias = pseudo_transportation_actor.processes ##### PHASE SPACE plan ######" - plan= sim.add_volume("Box", "phsp") + plan = sim.add_volume("Box", "phsp") plan.material = "G4_Galactic" plan.mother = world.name - plan.size = [1*m,1*m,1*nm] + plan.size = [1 * m, 1 * m, 1 * nm] plan.color = [0.2, 1, 0.8, 1] - plan.translation = [0,0,- 20*cm - 1*nm] + plan.translation = [0, 0, -20 * cm - 1 * nm] ####### gamma source ########### source = sim.add_source("GenericSource", "source1") @@ -127,7 +144,7 @@ def validation_test(arr,NIST_data,nb_split,tol = 0.01): source.direction.momentum = [0, 0, -1] source.energy.type = "mono" source.energy.mono = 6 * MeV - source.position.translation = [0,0,18*cm] + source.position.translation = [0, 0, 18 * cm] ####### PHASE SPACE ACTOR ############## @@ -141,7 +158,7 @@ def validation_test(arr,NIST_data,nb_split,tol = 0.01): "Weight", "KineticEnergy", "ParentID", - "ParticleName" + "ParticleName", ] phsp_actor.output = paths.output / "test072_output_data.root" @@ -157,7 +174,6 @@ def validation_test(arr,NIST_data,nb_split,tol = 0.01): s = f"/process/em/UseGeneralProcess false" sim.add_g4_command_before_init(s) - sim.physics_manager.global_production_cuts.gamma = 1 * mm sim.physics_manager.global_production_cuts.electron = 1 * km sim.physics_manager.global_production_cuts.positron = 1 * km @@ -171,8 +187,8 @@ def validation_test(arr,NIST_data,nb_split,tol = 0.01): print(stats) # f_phsp = uproot.open(paths.output / "test072_output_data.root") - data_NIST_W = np.loadtxt(paths.data / "NIST_W.txt",delimiter = '|') + data_NIST_W = np.loadtxt(paths.data / "NIST_W.txt", delimiter="|") arr = f_phsp["PhaseSpace"].arrays() # - is_ok = validation_test(arr,data_NIST_W,nb_split) + is_ok = validation_test(arr, data_NIST_W, nb_split) utility.test_ok(is_ok) From 07779d6a63514adf3ad9a96cd6690a2450d0aa49 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Mon, 26 Feb 2024 18:26:46 +0100 Subject: [PATCH 04/82] Modification of users command line to use the pseudo transporation actor, suppression of probe system and improvement of the russian roulette on the particle weights --- ...ateOptnComptSplittingForTransportation.cpp | 28 ++----- .../GateOptnComptSplittingForTransportation.h | 22 +----- .../opengate_lib/GateOptnForceFreeFlight.cpp | 73 ++++++++----------- .../opengate_lib/GateOptnForceFreeFlight.h | 8 +- ...GateOptrComptPseudoTransportationActor.cpp | 64 ++-------------- .../GateOptrComptPseudoTransportationActor.h | 6 +- 6 files changed, 55 insertions(+), 146 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp b/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp index 5d14e2866..81ed83525 100644 --- a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp +++ b/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp @@ -51,7 +51,7 @@ GateOptnComptSplittingForTransportation:: GateOptnComptSplittingForTransportation(G4String name) - : G4VBiasingOperation(name), fSplittingFactor(1), fRussianRoulette(false), + : G4VBiasingOperation(name), fSplittingFactor(1), fRussianRouletteForAngle(false), fParticleChange() {} //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... @@ -86,8 +86,7 @@ GateOptnComptSplittingForTransportation::ApplyFinalStateBiasing( // In case we don't want to split (a bit faster) i.e no biaising or no // splitting low weights particles. - if ((fSplittingFactor == 1 && fRussianRoulette == false) || - track->GetWeight() < fWeightThreshold) + if (fSplittingFactor == 1 && fRussianRouletteForAngle == false) return processFinalState; castedProcessInitFinalState = (G4ParticleChangeForGamma *)processFinalState; @@ -155,7 +154,7 @@ GateOptnComptSplittingForTransportation::ApplyFinalStateBiasing( if (splittingProbability <= survivalProbabilitySplitting || survivalProbabilitySplitting == 1) { - if ((fRussianRoulette == true) && (theta > fMaxTheta)) { + if ((fRussianRouletteForAngle == true) && (theta > fMaxTheta)) { G4double probability = G4UniformRand(); if (probability < 1 / fSplittingFactor) { // Specific case where the russian roulette probability is @@ -180,8 +179,8 @@ GateOptnComptSplittingForTransportation::ApplyFinalStateBiasing( } } - if ((fRussianRoulette == false) || - ((fRussianRoulette == true) && (theta <= fMaxTheta))) { + if ((fRussianRouletteForAngle == false) || + ((fRussianRouletteForAngle == true) && (theta <= fMaxTheta))) { G4Track *gammaTrack = new G4Track(*track); gammaTrack->SetWeight(gammaWeight); gammaTrack->SetKineticEnergy(energy); @@ -202,23 +201,6 @@ GateOptnComptSplittingForTransportation::ApplyFinalStateBiasing( processGammaSplittedFinalState->Clear(); castedProcessGammaSplittedFinalState->Clear(); } - // Probe generation, sent in 5 directions, in order to provide informations - // before the track of the real photons about the geometries they will cross. - if (fUseProbes) { - std::vector<G4ThreeVector> v = { - {0, 0, -1}, {0, 1, 0}, {0, -1, 0}, {1, 0, 0}, {-1, 0, 0}}; - - for (G4int nbProbe = 0; nbProbe < 5; nbProbe++) { - G4Track *gammaTrack = new G4Track(*track); - gammaTrack->SetGoodForTrackingFlag(1); - gammaTrack->SetWeight(track->GetWeight() / fSplittingFactor); - gammaTrack->SetKineticEnergy(track->GetKineticEnergy()); - gammaTrack->SetMomentumDirection(fRot * v[nbProbe]); - gammaTrack->SetPosition(position); - fParticleChange.AddSecondary(gammaTrack); - simulationTrackID++; - } - } return &fParticleChange; } diff --git a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h b/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h index 09bef7f9f..72ee18bdb 100644 --- a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h +++ b/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h @@ -78,16 +78,10 @@ class GateOptnComptSplittingForTransportation : public G4VBiasingOperation { } G4double GetSplittingFactor() const { return fSplittingFactor; } - void SetWeightThreshold(G4double weightThreshold) { - fWeightThreshold = weightThreshold; + void SetRussianRouletteForAngle(G4bool russianRoulette) { + fRussianRouletteForAngle = russianRoulette; } - G4double GetWeightThreshold() const { return fWeightThreshold; } - void SetRussianRoulette(G4bool russianRoulette) { - fRussianRoulette = russianRoulette; - } - - G4bool GetRussianRoulette() const { return fRussianRoulette; } void SetVectorDirector(G4ThreeVector vectorDirector) { fVectorDirector = vectorDirector; @@ -101,13 +95,6 @@ class GateOptnComptSplittingForTransportation : public G4VBiasingOperation { G4double GetMaxTheta() const { return fMaxTheta; } - void SetUseOfProbes(G4bool p) { fUseProbes = p; } - - void SetMinWeightOfParticle(G4double minWeightOfParticle) { - fMinWeightOfParticle = minWeightOfParticle; - } - - G4double GetMinWeightOfParticle() const { return fMinWeightOfParticle; } G4VParticleChange *GetParticleChange() { G4VParticleChange *particleChange = &fParticleChange; @@ -116,13 +103,10 @@ class GateOptnComptSplittingForTransportation : public G4VBiasingOperation { private: G4double fSplittingFactor; - G4double fWeightThreshold; G4ParticleChange fParticleChange; - G4bool fRussianRoulette; + G4bool fRussianRouletteForAngle; G4ThreeVector fVectorDirector; G4double fMaxTheta; - G4double fMinWeightOfParticle; - G4bool fUseProbes; G4RotationMatrix fRot; // G4DynamicParticle* fSplitParticle; // G4Track* fGammaTrack; diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp index 3942cf1a2..125797d55 100644 --- a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp +++ b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp @@ -67,58 +67,49 @@ G4VParticleChange *GateOptnForceFreeFlight ::ApplyFinalStateBiasing( fProposedWeight *= fWeightChange[callingProcess->GetWrappedProcess()->GetProcessName()]; - if (fUseProbes) { - if (track->IsGoodForTracking() == 0) { - if (fProposedWeight < fRussianRouletteProbability * fMinWeight) { - fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); - return &fParticleChange; - } - if ((fProposedWeight < fMinWeight) && - (fProposedWeight >= fRussianRouletteProbability * fMinWeight)) { - G4double probability = G4UniformRand(); - if (probability > fRussianRouletteProbability) { - fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); - return &fParticleChange; - } else { - fProposedWeight = fProposedWeight / fRussianRouletteProbability; - } - } + if (fRussianRouletteForWeights){ + G4int nbOfOrderOfMagnitude =std::log10(fInitialWeight/fMinWeight); - fParticleChange.ProposeWeight(fProposedWeight); - fOperationComplete = true; - } - if (track->IsGoodForTracking() == 1) { - fParticleChange.ProposeWeight(fProposedWeight); - fOperationComplete = true; - } - } else { - if ((fProposedWeight < fRussianRouletteProbability * fMinWeight) || - ((fProposedWeight < fMinWeight) && (fSurvivedToRR == true))) { + if ((fProposedWeight < fMinWeight) || ((fProposedWeight < 0.1 *fInitialWeight) && (fNbOfRussianRoulette == nbOfOrderOfMagnitude - 1))) { fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); + fNbOfRussianRoulette = 0; return &fParticleChange; } - if ((fProposedWeight < fMinWeight) && - (fProposedWeight >= fRussianRouletteProbability * fMinWeight) && - (fCountProcess == 4) && (fSurvivedToRR == false)) { - G4double probability = G4UniformRand(); - if (probability > fRussianRouletteProbability) { - fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); - return &fParticleChange; - } else { - fProposedWeight = fProposedWeight / fRussianRouletteProbability; - // std::cout<<"lim "<< fMinWeight << " RR " - // <<fRussianRouletteProbability<< " weight " - // <<fProposedWeight<<std::endl; - fSurvivedToRR = true; + if ((fProposedWeight < 0.1 *fInitialWeight) && (fCountProcess == 4)) { + G4double probability = G4UniformRand(); + for (int i = 2; i <= nbOfOrderOfMagnitude; i++) { + G4double RRprobability = 1/std::pow(10,i); + if (fProposedWeight * 1/std::pow(10,fNbOfRussianRoulette) < 10*RRprobability*fMinWeight ){ + fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); + fNbOfRussianRoulette = 0; + return &fParticleChange; + } + if ((fProposedWeight >= RRprobability *fInitialWeight) && (fProposedWeight < 10*RRprobability *fInitialWeight)){ + if (probability > 10*RRprobability){ + fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); + fNbOfRussianRoulette = 0; + return &fParticleChange; + } + else{ + fProposedWeight = fProposedWeight/(10*RRprobability); + fNbOfRussianRoulette= fNbOfRussianRoulette + i -1; + } + } } } - fParticleChange.ProposeWeight(fProposedWeight); - fOperationComplete = true; } + else{ + if (fProposedWeight < fMinWeight) { + fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); + return &fParticleChange; + } + } + fParticleChange.ProposeWeight(fProposedWeight); + fOperationComplete = true; return &fParticleChange; } diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h index 3a1682d71..c631d7b9e 100644 --- a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h +++ b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h @@ -92,7 +92,7 @@ class GateOptnForceFreeFlight : public G4VBiasingOperation { // fCumulatedWeightChange = 1.0;} void SetMinWeight(G4double w) { fMinWeight = w; } - void SetUseOfProbes(G4bool p) { fUseProbes = p; } + void SetInitialWeight(G4double w) { fInitialWeight = w; } G4double GetTrackWeight() { return fProposedWeight; } void SetTrackWeight(G4double w) { fProposedWeight = w; } void SetRussianRouletteProbability(G4double p) { @@ -100,19 +100,21 @@ class GateOptnForceFreeFlight : public G4VBiasingOperation { } void SetCountProcess(G4int N) { fCountProcess = N; } void SetSurvivedToRR(G4bool b) { fSurvivedToRR = b; } + void SetRussianRouletteForWeights(G4bool rr) { fRussianRouletteForWeights = rr; } G4bool GetSurvivedToRR() { return fSurvivedToRR; } G4bool OperationComplete() const { return fOperationComplete; } private: G4ILawForceFreeFlight *fForceFreeFlightInteractionLaw; std::map<G4String, G4double> fWeightChange; - G4bool fUseProbes; - G4double fMinWeight, fRussianRouletteProbability; + G4bool fRussianRouletteForWeights; + G4double fMinWeight, fRussianRouletteProbability,fInitialWeight; G4ParticleChange fParticleChange; G4bool fOperationComplete; G4double fProposedWeight; G4int fCountProcess; G4bool fSurvivedToRR; + G4int fNbOfRussianRoulette; }; #endif diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp index 9db37db28..5a610d445 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp @@ -54,26 +54,22 @@ GateOptrComptPseudoTransportationActor::GateOptrComptPseudoTransportationActor( GateVActor(user_info, false) { fMotherVolumeName = DictGetStr(user_info, "mother"); fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); - fWeightThreshold = DictGetDouble(user_info, "weight_threshold"); fRelativeMinWeightOfParticle = DictGetDouble(user_info, "relative_min_weight_of_particle"); // Since the russian roulette uses as a probability 1/splitting, we need to // have a double, but the splitting factor provided by the user is logically // an int, so we need to change the type. fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); - fRussianRoulette = DictGetBool(user_info, "russian_roulette"); + fRussianRouletteForAngle = DictGetBool(user_info, "russian_roulette_for_angle"); fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); + fRussianRouletteForWeights = DictGetBool(user_info, "russian_roulette_for_weights"); fMaxTheta = DictGetDouble(user_info, "max_theta"); - fRussianRouletteForFreeFlight = - DictGetDouble(user_info, "russian_roulette_for_free_flight"); fFreeFlightOperation = new GateOptnForceFreeFlight("freeFlightOperation"); fComptSplittingOperation = new GateOptnComptSplittingForTransportation("comptSplittingOperation"); - fUseProbes = DictGetBool(user_info, "use_probes"); fActions.insert("StartSimulationAction"); fActions.insert("SteppingAction"); fActions.insert("BeginOfEventAction"); - fActions.insert("PostUserTrackingAction"); isSplitted = false; } @@ -101,13 +97,9 @@ void GateOptrComptPseudoTransportationActor::StartSimulationAction() { // AttachAllLogicalDaughtersVolumes. AttachAllLogicalDaughtersVolumes(biasingVolume); fComptSplittingOperation->SetSplittingFactor(fSplittingFactor); - fComptSplittingOperation->SetWeightThreshold(fWeightThreshold); fComptSplittingOperation->SetMaxTheta(fMaxTheta); - fComptSplittingOperation->SetRussianRoulette(fRussianRoulette); - fComptSplittingOperation->SetUseOfProbes(fUseProbes); - fFreeFlightOperation->SetRussianRouletteProbability( - fRussianRouletteForFreeFlight); - fFreeFlightOperation->SetUseOfProbes(fUseProbes); + fComptSplittingOperation->SetRussianRouletteForAngle(fRussianRouletteForAngle); + fFreeFlightOperation->SetRussianRouletteForWeights(fRussianRouletteForWeights); } void GateOptrComptPseudoTransportationActor::StartRun() { @@ -130,26 +122,6 @@ void GateOptrComptPseudoTransportationActor::StartRun() { void GateOptrComptPseudoTransportationActor::SteppingAction(G4Step *step) { - // The stepping action is used to kill particle we have too kill : - // - If the primary particle reach the biased boudary - // - KIll all the probes exiting the volume - // - Kill, if probes, particles wich have a weilght lower than the probes one - - if (fUseProbes) { - if ((fKillOthersParticles) && - (step->GetTrack()->IsGoodForTracking() == 0)) { - step->GetTrack()->SetTrackStatus(G4TrackStatus::fStopAndKill); - } - - if (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary) { - if ((step->GetPostStepPoint()->GetPhysicalVolume()->GetName() == - "world") && - (step->GetTrack()->IsGoodForTracking() == 1)) { - step->GetTrack()->SetTrackStatus(G4TrackStatus::fStopAndKill); - } - } - } - if ((isSplitted == true) && (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary)) { if ((step->GetPostStepPoint()->GetPhysicalVolume()->GetName() == "world")) { @@ -166,8 +138,10 @@ void GateOptrComptPseudoTransportationActor::BeginOfEventAction( void GateOptrComptPseudoTransportationActor::StartTracking( const G4Track *track) { + //std::cout << "Begin o Track"<<std::endl; fInitialWeight = track->GetWeight(); - fFreeFlightOperation->SetSurvivedToRR(false); + fFreeFlightOperation->SetInitialWeight(fInitialWeight); + //fFreeFlightOperation->SetSurvivedToRR(false); } // For the following operation the idea is the following : @@ -235,31 +209,7 @@ void GateOptrComptPseudoTransportationActor::EndTracking() { isSplitted = false; } -void GateOptrComptPseudoTransportationActor::PostUserTrackingAction( - const G4Track *track) { - if (fUseProbes) { - if (track->IsGoodForTracking() == 1) { - G4double tmpWeight = fFreeFlightOperation->GetTrackWeight(); - if (NbOfProbe == 1) { - fKillOthersParticles = false; - weight = tmpWeight; - } else { - if (tmpWeight > weight) { - weight = tmpWeight; - } - } - NbOfProbe++; - if ((NbOfProbe == 6)) { - if (weight < fInitialWeight / fRelativeMinWeightOfParticle) { - fKillOthersParticles = true; - } - NbOfProbe = 1; - weight = 0; - } - } - } -} //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h index 31cf53846..576a41dc7 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h @@ -65,8 +65,8 @@ class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, G4bool fBiasPrimaryOnly; G4bool fBiasOnlyOnce; G4int fNInteractions = 0; - G4bool fRussianRoulette; - G4double fRussianRouletteForFreeFlight; + G4bool fRussianRouletteForAngle; + G4bool fRussianRouletteForWeights; G4bool fRotationVectorDirector; G4ThreeVector fVectorDirector; G4double fMaxTheta; @@ -77,12 +77,12 @@ class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, G4bool fKillOthersParticles = false; G4bool fUseProbes = false; G4bool fSurvivedRR = false; + // Unused but mandatory virtual void StartSimulationAction(); virtual void StartRun(); virtual void StartTracking(const G4Track *); - virtual void PostUserTrackingAction(const G4Track *track); virtual void SteppingAction(G4Step *); virtual void BeginOfEventAction(const G4Event *); virtual void EndTracking(); From f065c7c96656c048b467a8ebaa6f3eed7d3f5e2e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:27:26 +0000 Subject: [PATCH 05/82] [pre-commit.ci] Automatic python and c++ formatting --- .../opengate_lib/GateOptnComptSplitting.cpp | 5 ++- ...ateOptnComptSplittingForTransportation.cpp | 7 ++-- .../GateOptnComptSplittingForTransportation.h | 2 -- .../opengate_lib/GateOptnForceFreeFlight.cpp | 34 +++++++++---------- .../opengate_lib/GateOptnForceFreeFlight.h | 6 ++-- ...GateOptrComptPseudoTransportationActor.cpp | 25 +++++++------- .../GateOptrComptPseudoTransportationActor.h | 2 +- 7 files changed, 40 insertions(+), 41 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateOptnComptSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnComptSplitting.cpp index 8ee0ff264..be7799cd8 100644 --- a/core/opengate_core/opengate_lib/GateOptnComptSplitting.cpp +++ b/core/opengate_core/opengate_lib/GateOptnComptSplitting.cpp @@ -33,7 +33,6 @@ #include "G4DynamicParticle.hh" #include "G4Exception.hh" #include "G4Gamma.hh" -#include "G4Gamma.hh" #include "G4ParticleChange.hh" #include "G4ParticleChangeForGamma.hh" #include "G4SystemOfUnits.hh" @@ -138,7 +137,7 @@ G4VParticleChange *GateOptnComptSplitting::ApplyFinalStateBiasing( // Initialisation of the information about the track. // We store the first gamma as the departure track, The first gamma is a // primary particle but with its weight modified , since it can be one of the - //detected particles + // detected particles fParticleChange.Initialize(*track); fParticleChange.ProposeWeight(gammaWeight); @@ -172,7 +171,7 @@ G4VParticleChange *GateOptnComptSplitting::ApplyFinalStateBiasing( // according to the compton interaction process. If the gamma track is ok // regarding the russian roulette algorithm (no russian roulette //, or within the acceptance angle, or not killed by the RR process), we add - //it to the primary track. + // it to the primary track. // If an electron is generated (above the range cut), we also generate it. // A tremendous advantage is there is no need to use by ourself Klein-Nishina // formula or other. So, if the physics list used takes into account the diff --git a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp b/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp index 81ed83525..6bdec6e6b 100644 --- a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp +++ b/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp @@ -34,7 +34,6 @@ #include "G4DynamicParticle.hh" #include "G4Exception.hh" #include "G4Gamma.hh" -#include "G4Gamma.hh" #include "G4GammaConversion.hh" #include "G4ParticleChange.hh" #include "G4ParticleChangeForGamma.hh" @@ -51,8 +50,8 @@ GateOptnComptSplittingForTransportation:: GateOptnComptSplittingForTransportation(G4String name) - : G4VBiasingOperation(name), fSplittingFactor(1), fRussianRouletteForAngle(false), - fParticleChange() {} + : G4VBiasingOperation(name), fSplittingFactor(1), + fRussianRouletteForAngle(false), fParticleChange() {} //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... @@ -124,7 +123,7 @@ GateOptnComptSplittingForTransportation::ApplyFinalStateBiasing( // compton interaction process. If the gamma track is ok regarding the // russian roulette algorithm (no russian roulette //, or within the acceptance angle, or not killed by the RR process), we add - //it to the primary track. + // it to the primary track. // If an electron is generated (above the range cut), we also generate it. // A tremendous advantage is there is no need to use by ourself Klein-Nishina // formula or other. So, if the physics list used takes into account the diff --git a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h b/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h index 72ee18bdb..5dff8f3f1 100644 --- a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h +++ b/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h @@ -82,7 +82,6 @@ class GateOptnComptSplittingForTransportation : public G4VBiasingOperation { fRussianRouletteForAngle = russianRoulette; } - void SetVectorDirector(G4ThreeVector vectorDirector) { fVectorDirector = vectorDirector; } @@ -95,7 +94,6 @@ class GateOptnComptSplittingForTransportation : public G4VBiasingOperation { G4double GetMaxTheta() const { return fMaxTheta; } - G4VParticleChange *GetParticleChange() { G4VParticleChange *particleChange = &fParticleChange; return particleChange; diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp index 125797d55..53d6ba3a9 100644 --- a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp +++ b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp @@ -68,41 +68,41 @@ G4VParticleChange *GateOptnForceFreeFlight ::ApplyFinalStateBiasing( fProposedWeight *= fWeightChange[callingProcess->GetWrappedProcess()->GetProcessName()]; + if (fRussianRouletteForWeights) { + G4int nbOfOrderOfMagnitude = std::log10(fInitialWeight / fMinWeight); - if (fRussianRouletteForWeights){ - G4int nbOfOrderOfMagnitude =std::log10(fInitialWeight/fMinWeight); - - if ((fProposedWeight < fMinWeight) || ((fProposedWeight < 0.1 *fInitialWeight) && (fNbOfRussianRoulette == nbOfOrderOfMagnitude - 1))) { + if ((fProposedWeight < fMinWeight) || + ((fProposedWeight < 0.1 * fInitialWeight) && + (fNbOfRussianRoulette == nbOfOrderOfMagnitude - 1))) { fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); fNbOfRussianRoulette = 0; return &fParticleChange; } - - if ((fProposedWeight < 0.1 *fInitialWeight) && (fCountProcess == 4)) { + if ((fProposedWeight < 0.1 * fInitialWeight) && (fCountProcess == 4)) { G4double probability = G4UniformRand(); for (int i = 2; i <= nbOfOrderOfMagnitude; i++) { - G4double RRprobability = 1/std::pow(10,i); - if (fProposedWeight * 1/std::pow(10,fNbOfRussianRoulette) < 10*RRprobability*fMinWeight ){ + G4double RRprobability = 1 / std::pow(10, i); + if (fProposedWeight * 1 / std::pow(10, fNbOfRussianRoulette) < + 10 * RRprobability * fMinWeight) { fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); fNbOfRussianRoulette = 0; return &fParticleChange; } - if ((fProposedWeight >= RRprobability *fInitialWeight) && (fProposedWeight < 10*RRprobability *fInitialWeight)){ - if (probability > 10*RRprobability){ + if ((fProposedWeight >= RRprobability * fInitialWeight) && + (fProposedWeight < 10 * RRprobability * fInitialWeight)) { + if (probability > 10 * RRprobability) { fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); fNbOfRussianRoulette = 0; return &fParticleChange; + } else { + fProposedWeight = fProposedWeight / (10 * RRprobability); + fNbOfRussianRoulette = fNbOfRussianRoulette + i - 1; } - else{ - fProposedWeight = fProposedWeight/(10*RRprobability); - fNbOfRussianRoulette= fNbOfRussianRoulette + i -1; - } - } + } } } - } - else{ + } else { if (fProposedWeight < fMinWeight) { fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); return &fParticleChange; diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h index c631d7b9e..398f27851 100644 --- a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h +++ b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h @@ -100,7 +100,9 @@ class GateOptnForceFreeFlight : public G4VBiasingOperation { } void SetCountProcess(G4int N) { fCountProcess = N; } void SetSurvivedToRR(G4bool b) { fSurvivedToRR = b; } - void SetRussianRouletteForWeights(G4bool rr) { fRussianRouletteForWeights = rr; } + void SetRussianRouletteForWeights(G4bool rr) { + fRussianRouletteForWeights = rr; + } G4bool GetSurvivedToRR() { return fSurvivedToRR; } G4bool OperationComplete() const { return fOperationComplete; } @@ -108,7 +110,7 @@ class GateOptnForceFreeFlight : public G4VBiasingOperation { G4ILawForceFreeFlight *fForceFreeFlightInteractionLaw; std::map<G4String, G4double> fWeightChange; G4bool fRussianRouletteForWeights; - G4double fMinWeight, fRussianRouletteProbability,fInitialWeight; + G4double fMinWeight, fRussianRouletteProbability, fInitialWeight; G4ParticleChange fParticleChange; G4bool fOperationComplete; G4double fProposedWeight; diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp index 5a610d445..5e8bf38ac 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp @@ -60,9 +60,11 @@ GateOptrComptPseudoTransportationActor::GateOptrComptPseudoTransportationActor( // have a double, but the splitting factor provided by the user is logically // an int, so we need to change the type. fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); - fRussianRouletteForAngle = DictGetBool(user_info, "russian_roulette_for_angle"); + fRussianRouletteForAngle = + DictGetBool(user_info, "russian_roulette_for_angle"); fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); - fRussianRouletteForWeights = DictGetBool(user_info, "russian_roulette_for_weights"); + fRussianRouletteForWeights = + DictGetBool(user_info, "russian_roulette_for_weights"); fMaxTheta = DictGetDouble(user_info, "max_theta"); fFreeFlightOperation = new GateOptnForceFreeFlight("freeFlightOperation"); fComptSplittingOperation = @@ -98,8 +100,10 @@ void GateOptrComptPseudoTransportationActor::StartSimulationAction() { AttachAllLogicalDaughtersVolumes(biasingVolume); fComptSplittingOperation->SetSplittingFactor(fSplittingFactor); fComptSplittingOperation->SetMaxTheta(fMaxTheta); - fComptSplittingOperation->SetRussianRouletteForAngle(fRussianRouletteForAngle); - fFreeFlightOperation->SetRussianRouletteForWeights(fRussianRouletteForWeights); + fComptSplittingOperation->SetRussianRouletteForAngle( + fRussianRouletteForAngle); + fFreeFlightOperation->SetRussianRouletteForWeights( + fRussianRouletteForWeights); } void GateOptrComptPseudoTransportationActor::StartRun() { @@ -138,21 +142,21 @@ void GateOptrComptPseudoTransportationActor::BeginOfEventAction( void GateOptrComptPseudoTransportationActor::StartTracking( const G4Track *track) { - //std::cout << "Begin o Track"<<std::endl; + // std::cout << "Begin o Track"<<std::endl; fInitialWeight = track->GetWeight(); fFreeFlightOperation->SetInitialWeight(fInitialWeight); - //fFreeFlightOperation->SetSurvivedToRR(false); + // fFreeFlightOperation->SetSurvivedToRR(false); } // For the following operation the idea is the following : // All the potential photon processes are biased. If a particle undergoes a // compton interaction, we splitted it (ComptonSplittingForTransportation -//operation) and the particle generated are pseudo-transported with the -//ForceFreeFLight operation +// operation) and the particle generated are pseudo-transported with the +// ForceFreeFLight operation // Since the occurence Biaising operation is called at the beginning of each // track, and propose a different way to track the particle //(with modified physics), it here returns other thing than 0 if we want to -//pseudo-transport the particle, so if its creatorProcess is the modified +// pseudo-transport the particle, so if its creatorProcess is the modified // compton interaction G4VBiasingOperation * @@ -209,7 +213,4 @@ void GateOptrComptPseudoTransportationActor::EndTracking() { isSplitted = false; } - - - //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h index 576a41dc7..b4c96de5d 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h @@ -77,7 +77,7 @@ class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, G4bool fKillOthersParticles = false; G4bool fUseProbes = false; G4bool fSurvivedRR = false; - + // Unused but mandatory virtual void StartSimulationAction(); From af172fafe86ab436a34b9eda68cc58a351bf4208 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 27 Feb 2024 18:16:52 +0100 Subject: [PATCH 06/82] Add of a test to verify the correctness of the russian roulette and add of a user function to remove or not a geometry holder (e.g air box) encompassing the volumes to bias --- ...GateOptrComptPseudoTransportationActor.cpp | 33 ++- .../GateOptrComptPseudoTransportationActor.h | 1 + opengate/actors/miscactors.py | 11 +- opengate/tests/data | 2 +- .../tests/src/test072_pseudo_transport_RR.py | 221 ++++++++++++++++++ 5 files changed, 254 insertions(+), 14 deletions(-) create mode 100644 opengate/tests/src/test072_pseudo_transport_RR.py diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp index 5a610d445..464ae192a 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp @@ -45,6 +45,7 @@ #include "G4VEmProcess.hh" #include "GateOptnComptSplittingForTransportation.h" #include "GateOptrComptPseudoTransportationActor.h" +#include "G4Exception.hh" //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... @@ -53,6 +54,7 @@ GateOptrComptPseudoTransportationActor::GateOptrComptPseudoTransportationActor( : G4VBiasingOperator("ComptSplittingOperator"), GateVActor(user_info, false) { fMotherVolumeName = DictGetStr(user_info, "mother"); + fAttachToLogicalHolder = DictGetBool(user_info, "attach_to_logical_holder"); fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); fRelativeMinWeightOfParticle = DictGetDouble(user_info, "relative_min_weight_of_particle"); @@ -77,7 +79,13 @@ GateOptrComptPseudoTransportationActor::GateOptrComptPseudoTransportationActor( void GateOptrComptPseudoTransportationActor::AttachAllLogicalDaughtersVolumes( G4LogicalVolume *volume) { - AttachTo(volume); + if (fAttachToLogicalHolder == false){ + if (volume->GetName() != G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName)->GetName()){ + AttachTo(volume); + } + } + if (fAttachToLogicalHolder == true) + AttachTo(volume); G4int nbOfDaughters = volume->GetNoDaughters(); if (nbOfDaughters > 0) { for (int i = 0; i < nbOfDaughters; i++) { @@ -138,10 +146,23 @@ void GateOptrComptPseudoTransportationActor::BeginOfEventAction( void GateOptrComptPseudoTransportationActor::StartTracking( const G4Track *track) { - //std::cout << "Begin o Track"<<std::endl; - fInitialWeight = track->GetWeight(); + /* +if (fInitialWeight < 5*std::pow(10,-5)){ + std::cout<<" WEIIIIIGGGHHT BUUUGGG, begin of track, weight :"<< fInitialWeight << " Energy : "<<track->GetKineticEnergy()<< " Creation process : " <<track->GetCreatorProcess ()->GetProcessName() << " track ID : "<< track->GetTrackID()<<" ParentID : "<<track->GetParentID() <<" particle name : " <<track->GetParticleDefinition()->GetParticleName()<< " volume of creation : " <<track->GetVolume()->GetName()<<std::endl; + fFreeFlightOperation->SetInitialWeight(fInitialWeight); - //fFreeFlightOperation->SetSurvivedToRR(false); + G4Exception("", "", + FatalException, + ""); + +} +*/ + if (track->GetCreatorProcess() != 0) { + if ((track->GetCreatorProcess()->GetProcessName() == "biasWrapper(compt)") &&(track->GetParticleDefinition()->GetParticleName() == "gamma")) { + fInitialWeight = track->GetWeight(); + fFreeFlightOperation->SetInitialWeight(fInitialWeight); + } + } } // For the following operation the idea is the following : @@ -159,7 +180,7 @@ G4VBiasingOperation * GateOptrComptPseudoTransportationActor::ProposeOccurenceBiasingOperation( const G4Track *track, const G4BiasingProcessInterface *callingProcess) { if (track->GetCreatorProcess() != 0) { - if (track->GetCreatorProcess()->GetProcessName() == "biasWrapper(compt)") { + if ((track->GetCreatorProcess()->GetProcessName() == "biasWrapper(compt)") && (track->GetParticleDefinition()->GetParticleName() == "gamma")){ fFreeFlightOperation->SetMinWeight(fInitialWeight / fRelativeMinWeightOfParticle); fFreeFlightOperation->SetTrackWeight(track->GetWeight()); @@ -195,7 +216,7 @@ GateOptrComptPseudoTransportationActor::ProposeFinalStateBiasingOperation( /* */ if (track->GetCreatorProcess() != 0) { - if (track->GetCreatorProcess()->GetProcessName() == "biasWrapper(compt)") { + if ((track->GetCreatorProcess()->GetProcessName() == "biasWrapper(compt)") &&(track->GetParticleDefinition()->GetParticleName() == "gamma")) { return callingProcess->GetCurrentOccurenceBiasingOperation(); } } diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h index 576a41dc7..f69eab760 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h @@ -77,6 +77,7 @@ class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, G4bool fKillOthersParticles = false; G4bool fUseProbes = false; G4bool fSurvivedRR = false; + G4bool fAttachToLogicalHolder = true; // Unused but mandatory diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 501147cc5..adf8e966c 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -472,18 +472,15 @@ class ComptPseudoTransportationActor( def set_default_user_info(user_info): ActorBase.set_default_user_info(user_info) deg = g4_units.deg + user_info.attach_to_logical_holder = True user_info.splitting_factor = 1 - user_info.weight_threshold = 0 - user_info.bias_primary_only = True - user_info.relative_min_weight_of_particle = 0 - user_info.bias_only_once = True + user_info.relative_min_weight_of_particle = np.inf user_info.processes = ["compt", "phot", "conv", "Rayl"] - user_info.russian_roulette = False + user_info.russian_roulette_for_angle = False user_info.rotation_vector_director = False user_info.vector_director = [0, 0, 1] user_info.max_theta = 90 * deg - user_info.russian_roulette_for_free_flight = 1 / 10 - user_info.use_probes = False + user_info.russian_roulette_for_weights= False def __init__(self, user_info): ActorBase.__init__(self, user_info) diff --git a/opengate/tests/data b/opengate/tests/data index 6cb4e7d3b..d1c61ed4e 160000 --- a/opengate/tests/data +++ b/opengate/tests/data @@ -1 +1 @@ -Subproject commit 6cb4e7d3b5f2bcc0fbca58bf6ef8fc958e4cb6c8 +Subproject commit d1c61ed4e7bade0067ed9fa20660d52abcc28744 diff --git a/opengate/tests/src/test072_pseudo_transport_RR.py b/opengate/tests/src/test072_pseudo_transport_RR.py new file mode 100644 index 000000000..d4d7f8769 --- /dev/null +++ b/opengate/tests/src/test072_pseudo_transport_RR.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +import uproot +import numpy as np +import matplotlib.pyplot as plt +import matplotlib as mpl +import scipy +from scipy.spatial.transform import Rotation +from opengate.tests import utility + +def Ekin_per_event(weight,EventID,Ekin) : + l_edep = [] + Edep_per_event = 0 + last_EventID = 0 + for i in range(len(weight)): + if i > 0: + last_EventID = EventID[i - 1] + if EventID[i] != last_EventID and i > 0: + l_edep.append(Edep_per_event) + Edep_per_event = 0 + Edep_per_event += Ekin[i] * weight[i] + return(np.asarray(l_edep)) +def validation_test(arr_no_RR,arr_RR,nb_event,splitting_factor, tol=0.15): + + Tracks_no_RR = arr_no_RR[ + (arr_no_RR["TrackCreatorProcess"] == "biasWrapper(compt)") + & (arr_no_RR["ParticleName"] == "gamma")] + + Tracks_RR = arr_RR[ + (arr_RR["TrackCreatorProcess"] == "biasWrapper(compt)") + & (arr_RR["ParticleName"] == "gamma")] + + + Ekin_no_RR = Tracks_no_RR["KineticEnergy"] + w_no_RR = Tracks_no_RR["Weight"] + Event_ID_no_RR = Tracks_no_RR["EventID"] + + Ekin_per_event_no_RR = Ekin_per_event(w_no_RR,Event_ID_no_RR,Ekin_no_RR) + + + Ekin_RR = Tracks_RR["KineticEnergy"] + w_RR = Tracks_RR["Weight"] + Event_ID_RR = Tracks_RR["EventID"] + + bool_RR = (np.min(w_RR) > 0.1*1/splitting_factor) & (np.max(w_RR) < 1/splitting_factor) + + Ekin_per_event_RR = Ekin_per_event(w_RR, Event_ID_RR, Ekin_RR) + + mean_ekin_event_no_RR = np.sum(Ekin_per_event_no_RR)/nb_event + mean_ekin_event_RR = np.sum(Ekin_per_event_RR)/nb_event + diff = (mean_ekin_event_RR - mean_ekin_event_no_RR) / mean_ekin_event_RR + print("Mean kinetic energy per event without russian roulette :",np.round(1000*mean_ekin_event_no_RR,1), 'keV') + print("Mean kinetic energy per event with russian roulette :", np.round(1000*mean_ekin_event_RR,1), 'keV') + print("Percentage of difference :",diff*100,"%") + + return (abs(diff) < tol) &( bool_RR) + # mean_energy_per_history_no_RR = np.sum(Tracks_no_RR["KineticEnergy"]*Tracks_no_RR["Weight"])/nb_event + # mean_energy_per_history_RR = np.sum(Tracks_RR["KineticEnergy"] * Tracks_RR["Weight"]) / nb_event + + + # print(mean_energy_per_history_no_RR,mean_energy_per_history_RR) + + +if __name__ == "__main__": + + for j in range(2): + paths = utility.get_default_test_paths( + __file__, "test072_pseudo_transportation", output_folder="test072" + ) + + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + + # ui.visu = True + # ui.visu_type = "vrml" + ui.check_volumes_overlap = False + # ui.running_verbose_level = gate.EVENT + ui.number_of_threads = 1 + ui.random_seed = "auto" + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + um = gate.g4_units.um + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + gcm3 = gate.g4_units.g / gate.g4_units.cm3 + deg = gate.g4_units.deg + + # adapt world size + world = sim.world + world.size = [1.2 * m, 1.2 * m, 2 * m] + world.material = "G4_Galactic" + + ####### GEOMETRY TO IRRADIATE ############# + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + simple_collimation = sim.add_volume("Box", "colli_box") + simple_collimation.material = "G4_Galactic" + simple_collimation.mother = world.name + simple_collimation.size = [1 * m, 1 * m, 40 * cm] + simple_collimation.color = [0.3, 0.1, 0.8, 1] + + W_leaf = sim.add_volume("Box", "W_leaf") + W_leaf.mother = simple_collimation.name + W_leaf.size = [1 * m, 1 * m, 0.5 * cm] + W_leaf.material = "Tungsten" + leaf_translation = [] + for i in range(10): + leaf_translation.append( + [ + 0, + 0, + -0.5 * simple_collimation.size[2] + + 0.5 * W_leaf.size[2] + + i * W_leaf.size[2], + ] + ) + W_leaf.translation = leaf_translation + W_leaf.color = [0.8, 0.2, 0.1, 1] + + ######## pseudo_transportation ACTOR ######### + nb_split = 5 + pseudo_transportation_actor = sim.add_actor( + "ComptPseudoTransportationActor", "pseudo_transportation_actor" + ) + pseudo_transportation_actor.mother = simple_collimation.name + pseudo_transportation_actor.splitting_factor = nb_split + pseudo_transportation_actor.relative_min_weight_of_particle = 10000 + if j ==0: + pseudo_transportation_actor.russian_roulette_for_weights = False + if j ==1 : + pseudo_transportation_actor.russian_roulette_for_weights = True + list_processes_to_bias = pseudo_transportation_actor.processes + + ##### PHASE SPACE plan ######" + plan = sim.add_volume("Box", "phsp") + plan.material = "G4_Galactic" + plan.mother = world.name + plan.size = [1 * m, 1 * m, 1 * nm] + plan.color = [0.2, 1, 0.8, 1] + plan.translation = [0, 0, -20 * cm - 1 * nm] + + ####### gamma source ########### + nb_event = 20000 + source = sim.add_source("GenericSource", "source1") + source.particle = "gamma" + source.n = nb_event + source.position.type = "sphere" + source.position.radius = 1 * nm + source.direction.type = "momentum" + source.direction.momentum = [0, 0, -1] + source.energy.type = "mono" + source.energy.mono = 6 * MeV + source.position.translation = [0, 0, 18 * cm] + + ####### PHASE SPACE ACTOR ############## + + phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + phsp_actor.mother = plan.name + phsp_actor.attributes = [ + "EventID", + "TrackCreatorProcess", + "TrackVertexPosition", + "PrePosition", + "Weight", + "KineticEnergy", + "ParentID", + "ParticleName", + ] + data_name = "test072_output_data_RR_"+ str(j)+".root" + phsp_actor.output = paths.output / data_name + + ##### MODIFIED PHYSICS LIST ############### + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + ### Perhaps avoid the user to call the below boolean function ? ### + sim.physics_manager.special_physics_constructors.G4GenericBiasingPhysics = True + sim.physics_manager.processes_to_bias.gamma = list_processes_to_bias + s = f"/process/em/UseGeneralProcess false" + sim.add_g4_command_before_init(s) + + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1 * km + sim.physics_manager.global_production_cuts.positron = 1 * km + + output = sim.run(True) + + # + # # print results + stats = sim.output.get_actor("Stats") + h = sim.output.get_actor("PhaseSpace") + print(stats) + # + + f_1 = uproot.open(paths.output / "test072_output_data_RR_0.root") + f_2 = uproot.open(paths.output / "test072_output_data_RR_1.root") + + + arr_no_RR = f_1["PhaseSpace"].arrays() + arr_RR = f_2["PhaseSpace"].arrays() + + + is_ok = validation_test(arr_no_RR,arr_RR,nb_event,nb_split) + utility.test_ok(is_ok) From bbd1dbfcc27ab5b9380f26c93f742f93a3a01dfd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 17:24:08 +0000 Subject: [PATCH 07/82] [pre-commit.ci] Automatic python and c++ formatting --- ...GateOptrComptPseudoTransportationActor.cpp | 20 +++++-- .../GateOptrComptPseudoTransportationActor.h | 2 +- opengate/actors/miscactors.py | 2 +- .../tests/src/test072_pseudo_transport_RR.py | 56 +++++++++++-------- 4 files changed, 49 insertions(+), 31 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp index 214b5e513..a2ea6d4c1 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp @@ -33,6 +33,7 @@ #include "CLHEP/Units/SystemOfUnits.h" #include "G4BiasingProcessInterface.hh" #include "G4EmCalculator.hh" +#include "G4Exception.hh" #include "G4Gamma.hh" #include "G4LogicalVolumeStore.hh" #include "G4ParticleTable.hh" @@ -45,7 +46,6 @@ #include "G4VEmProcess.hh" #include "GateOptnComptSplittingForTransportation.h" #include "GateOptrComptPseudoTransportationActor.h" -#include "G4Exception.hh" //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... @@ -81,8 +81,10 @@ GateOptrComptPseudoTransportationActor::GateOptrComptPseudoTransportationActor( void GateOptrComptPseudoTransportationActor::AttachAllLogicalDaughtersVolumes( G4LogicalVolume *volume) { - if (fAttachToLogicalHolder == false){ - if (volume->GetName() != G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName)->GetName()){ + if (fAttachToLogicalHolder == false) { + if (volume->GetName() != G4LogicalVolumeStore::GetInstance() + ->GetVolume(fMotherVolumeName) + ->GetName()) { AttachTo(volume); } } @@ -152,7 +154,9 @@ void GateOptrComptPseudoTransportationActor::StartTracking( const G4Track *track) { if (track->GetCreatorProcess() != 0) { - if ((track->GetCreatorProcess()->GetProcessName() == "biasWrapper(compt)") &&(track->GetParticleDefinition()->GetParticleName() == "gamma")) { + if ((track->GetCreatorProcess()->GetProcessName() == + "biasWrapper(compt)") && + (track->GetParticleDefinition()->GetParticleName() == "gamma")) { fInitialWeight = track->GetWeight(); fFreeFlightOperation->SetInitialWeight(fInitialWeight); } @@ -174,7 +178,9 @@ G4VBiasingOperation * GateOptrComptPseudoTransportationActor::ProposeOccurenceBiasingOperation( const G4Track *track, const G4BiasingProcessInterface *callingProcess) { if (track->GetCreatorProcess() != 0) { - if ((track->GetCreatorProcess()->GetProcessName() == "biasWrapper(compt)") && (track->GetParticleDefinition()->GetParticleName() == "gamma")){ + if ((track->GetCreatorProcess()->GetProcessName() == + "biasWrapper(compt)") && + (track->GetParticleDefinition()->GetParticleName() == "gamma")) { fFreeFlightOperation->SetMinWeight(fInitialWeight / fRelativeMinWeightOfParticle); fFreeFlightOperation->SetTrackWeight(track->GetWeight()); @@ -210,7 +216,9 @@ GateOptrComptPseudoTransportationActor::ProposeFinalStateBiasingOperation( /* */ if (track->GetCreatorProcess() != 0) { - if ((track->GetCreatorProcess()->GetProcessName() == "biasWrapper(compt)") &&(track->GetParticleDefinition()->GetParticleName() == "gamma")) { + if ((track->GetCreatorProcess()->GetProcessName() == + "biasWrapper(compt)") && + (track->GetParticleDefinition()->GetParticleName() == "gamma")) { return callingProcess->GetCurrentOccurenceBiasingOperation(); } } diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h index f69eab760..3b7d6b457 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h @@ -78,7 +78,7 @@ class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, G4bool fUseProbes = false; G4bool fSurvivedRR = false; G4bool fAttachToLogicalHolder = true; - + // Unused but mandatory virtual void StartSimulationAction(); diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index adf8e966c..eb9970b36 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -480,7 +480,7 @@ def set_default_user_info(user_info): user_info.rotation_vector_director = False user_info.vector_director = [0, 0, 1] user_info.max_theta = 90 * deg - user_info.russian_roulette_for_weights= False + user_info.russian_roulette_for_weights = False def __init__(self, user_info): ActorBase.__init__(self, user_info) diff --git a/opengate/tests/src/test072_pseudo_transport_RR.py b/opengate/tests/src/test072_pseudo_transport_RR.py index d4d7f8769..e39832182 100644 --- a/opengate/tests/src/test072_pseudo_transport_RR.py +++ b/opengate/tests/src/test072_pseudo_transport_RR.py @@ -10,7 +10,8 @@ from scipy.spatial.transform import Rotation from opengate.tests import utility -def Ekin_per_event(weight,EventID,Ekin) : + +def Ekin_per_event(weight, EventID, Ekin): l_edep = [] Edep_per_event = 0 last_EventID = 0 @@ -21,45 +22,56 @@ def Ekin_per_event(weight,EventID,Ekin) : l_edep.append(Edep_per_event) Edep_per_event = 0 Edep_per_event += Ekin[i] * weight[i] - return(np.asarray(l_edep)) -def validation_test(arr_no_RR,arr_RR,nb_event,splitting_factor, tol=0.15): + return np.asarray(l_edep) + + +def validation_test(arr_no_RR, arr_RR, nb_event, splitting_factor, tol=0.15): Tracks_no_RR = arr_no_RR[ (arr_no_RR["TrackCreatorProcess"] == "biasWrapper(compt)") - & (arr_no_RR["ParticleName"] == "gamma")] + & (arr_no_RR["ParticleName"] == "gamma") + ] Tracks_RR = arr_RR[ (arr_RR["TrackCreatorProcess"] == "biasWrapper(compt)") - & (arr_RR["ParticleName"] == "gamma")] - + & (arr_RR["ParticleName"] == "gamma") + ] Ekin_no_RR = Tracks_no_RR["KineticEnergy"] w_no_RR = Tracks_no_RR["Weight"] Event_ID_no_RR = Tracks_no_RR["EventID"] - Ekin_per_event_no_RR = Ekin_per_event(w_no_RR,Event_ID_no_RR,Ekin_no_RR) - + Ekin_per_event_no_RR = Ekin_per_event(w_no_RR, Event_ID_no_RR, Ekin_no_RR) Ekin_RR = Tracks_RR["KineticEnergy"] w_RR = Tracks_RR["Weight"] Event_ID_RR = Tracks_RR["EventID"] - bool_RR = (np.min(w_RR) > 0.1*1/splitting_factor) & (np.max(w_RR) < 1/splitting_factor) + bool_RR = (np.min(w_RR) > 0.1 * 1 / splitting_factor) & ( + np.max(w_RR) < 1 / splitting_factor + ) Ekin_per_event_RR = Ekin_per_event(w_RR, Event_ID_RR, Ekin_RR) - mean_ekin_event_no_RR = np.sum(Ekin_per_event_no_RR)/nb_event - mean_ekin_event_RR = np.sum(Ekin_per_event_RR)/nb_event + mean_ekin_event_no_RR = np.sum(Ekin_per_event_no_RR) / nb_event + mean_ekin_event_RR = np.sum(Ekin_per_event_RR) / nb_event diff = (mean_ekin_event_RR - mean_ekin_event_no_RR) / mean_ekin_event_RR - print("Mean kinetic energy per event without russian roulette :",np.round(1000*mean_ekin_event_no_RR,1), 'keV') - print("Mean kinetic energy per event with russian roulette :", np.round(1000*mean_ekin_event_RR,1), 'keV') - print("Percentage of difference :",diff*100,"%") - - return (abs(diff) < tol) &( bool_RR) + print( + "Mean kinetic energy per event without russian roulette :", + np.round(1000 * mean_ekin_event_no_RR, 1), + "keV", + ) + print( + "Mean kinetic energy per event with russian roulette :", + np.round(1000 * mean_ekin_event_RR, 1), + "keV", + ) + print("Percentage of difference :", diff * 100, "%") + + return (abs(diff) < tol) & (bool_RR) # mean_energy_per_history_no_RR = np.sum(Tracks_no_RR["KineticEnergy"]*Tracks_no_RR["Weight"])/nb_event # mean_energy_per_history_RR = np.sum(Tracks_RR["KineticEnergy"] * Tracks_RR["Weight"]) / nb_event - # print(mean_energy_per_history_no_RR,mean_energy_per_history_RR) @@ -141,9 +153,9 @@ def validation_test(arr_no_RR,arr_RR,nb_event,splitting_factor, tol=0.15): pseudo_transportation_actor.mother = simple_collimation.name pseudo_transportation_actor.splitting_factor = nb_split pseudo_transportation_actor.relative_min_weight_of_particle = 10000 - if j ==0: + if j == 0: pseudo_transportation_actor.russian_roulette_for_weights = False - if j ==1 : + if j == 1: pseudo_transportation_actor.russian_roulette_for_weights = True list_processes_to_bias = pseudo_transportation_actor.processes @@ -182,7 +194,7 @@ def validation_test(arr_no_RR,arr_RR,nb_event,splitting_factor, tol=0.15): "ParentID", "ParticleName", ] - data_name = "test072_output_data_RR_"+ str(j)+".root" + data_name = "test072_output_data_RR_" + str(j) + ".root" phsp_actor.output = paths.output / data_name ##### MODIFIED PHYSICS LIST ############### @@ -212,10 +224,8 @@ def validation_test(arr_no_RR,arr_RR,nb_event,splitting_factor, tol=0.15): f_1 = uproot.open(paths.output / "test072_output_data_RR_0.root") f_2 = uproot.open(paths.output / "test072_output_data_RR_1.root") - arr_no_RR = f_1["PhaseSpace"].arrays() arr_RR = f_2["PhaseSpace"].arrays() - - is_ok = validation_test(arr_no_RR,arr_RR,nb_event,nb_split) + is_ok = validation_test(arr_no_RR, arr_RR, nb_event, nb_split) utility.test_ok(is_ok) From d0ff62142481d5cf7113e1fc3ba3262198bb79f7 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Mon, 18 Mar 2024 10:09:38 +0100 Subject: [PATCH 08/82] Improvement of the pseudo-transportation. Each process creating gamma (except fluorescence) is split. Need to provide some tests to verify all features. --- .../GateOptnPairProdSplitting.cpp | 102 +++++++++++ .../opengate_lib/GateOptnPairProdSplitting.h | 53 ++++++ ...pp => GateOptnScatteredGammaSplitting.cpp} | 112 ++++-------- .../GateOptnScatteredGammaSplitting.h | 56 ++++++ .../GateOptnVGenericSplitting.cpp | 105 +++++++++++ ...ortation.h => GateOptnVGenericSplitting.h} | 28 +-- .../opengate_lib/GateOptneBremSplitting.cpp | 107 ++++++++++++ .../opengate_lib/GateOptneBremSplitting.h | 56 ++++++ ...GateOptrComptPseudoTransportationActor.cpp | 164 +++++++++++++----- .../GateOptrComptPseudoTransportationActor.h | 23 ++- opengate/actors/miscactors.py | 4 +- opengate/tests/data | 2 +- .../tests/src/test072_pseudo_transport_RR.py | 2 +- .../src/test072_pseudo_transportation.py | 2 +- 14 files changed, 671 insertions(+), 145 deletions(-) create mode 100644 core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp create mode 100644 core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h rename core/opengate_core/opengate_lib/{GateOptnComptSplittingForTransportation.cpp => GateOptnScatteredGammaSplitting.cpp} (60%) create mode 100644 core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h create mode 100644 core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp rename core/opengate_core/opengate_lib/{GateOptnComptSplittingForTransportation.h => GateOptnVGenericSplitting.h} (80%) create mode 100644 core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp create mode 100644 core/opengate_core/opengate_lib/GateOptneBremSplitting.h diff --git a/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp new file mode 100644 index 000000000..767f8a1be --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp @@ -0,0 +1,102 @@ +// +// ******************************************************************** +// * 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 GateOptnPairProdSplitting.cc +/// \brief Implementation of the GateOptnPairProdSplitting class + +#include "GateOptnPairProdSplitting.h" +#include "G4BiasingProcessInterface.hh" + +#include "G4ComptonScattering.hh" +#include "G4DynamicParticle.hh" +#include "G4Exception.hh" +#include "G4Gamma.hh" +#include "G4GammaConversion.hh" +#include "G4ParticleChange.hh" +#include "G4ParticleChangeForGamma.hh" +#include "G4ParticleChangeForLoss.hh" +#include "G4PhotoElectricEffect.hh" +#include "G4ProcessType.hh" +#include "G4RayleighScattering.hh" +#include "G4SystemOfUnits.hh" +#include "G4TrackStatus.hh" +#include "G4TrackingManager.hh" +#include "G4VEmProcess.hh" +#include <memory> + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptnPairProdSplitting:: + GateOptnPairProdSplitting(G4String name) + : GateOptnVGenericSplitting(name), fParticleChange() {} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptnPairProdSplitting:: + ~GateOptnPairProdSplitting() {} + +G4VParticleChange * +GateOptnPairProdSplitting::ApplyFinalStateBiasing( const G4BiasingProcessInterface *callingProcess, const G4Track *track, const G4Step *step, G4bool &) { + + + G4int splittingFactor = ceil(fSplittingFactor); + G4double survivalProbabilitySplitting = 1 - (splittingFactor - fSplittingFactor) / splittingFactor; + G4double particleWeight = 0; + + G4VParticleChange* processFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + + if (fSplittingFactor == 1 && fRussianRouletteForAngle == false) return processFinalState; + TrackInitializationGamma(&fParticleChange,processFinalState,track,fSplittingFactor); + + processFinalState->Clear(); + + G4int nCalls = 1; + while (nCalls <= splittingFactor) { + G4double splittingProbability = G4UniformRand(); + if (splittingProbability <= survivalProbabilitySplitting || survivalProbabilitySplitting == 1) { + particleWeight = track->GetWeight() / fSplittingFactor; + G4VParticleChange *processFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + if (processFinalState->GetNumberOfSecondaries() >= 1 ) { + for(int i =0; i < processFinalState->GetNumberOfSecondaries();i++){ + G4Track *SecondaryTrack =processFinalState->GetSecondary(i); + SecondaryTrack->SetWeight(particleWeight); + fParticleChange.AddSecondary(SecondaryTrack); + } + } + } + nCalls++; + } + return &fParticleChange; +} + + + + + + + + + diff --git a/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h b/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h new file mode 100644 index 000000000..fd1f172c5 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h @@ -0,0 +1,53 @@ +// +// ******************************************************************** +// * 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 GateOptnPairProdSplitting.h +/// \brief Definition of the GateOptnPairProdSplitting class +// + +#ifndef GateOptnPairProdSplitting_h +#define GateOptnPairProdSplitting_h 1 + +#include "GateOptnVGenericSplitting.h" +#include "G4ParticleChange.hh" + +class GateOptnPairProdSplitting : public GateOptnVGenericSplitting { +public: + // -- Constructor : + GateOptnPairProdSplitting(G4String name); + + // -- destructor: + virtual ~GateOptnPairProdSplitting(); + + virtual G4VParticleChange * + ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, + const G4Step *, G4bool &); + +G4ParticleChange fParticleChange; + +}; + +#endif diff --git a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp b/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp similarity index 60% rename from core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp rename to core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp index 6bdec6e6b..8dbb41be6 100644 --- a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.cpp +++ b/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp @@ -24,10 +24,10 @@ // ******************************************************************** // // -/// \file GateOptnComptSplittingForTransportation.cc -/// \brief Implementation of the GateOptnComptSplittingForTransportation class +/// \file GateOptnScatteredGammaSplitting.cc +/// \brief Implementation of the GateOptnScatteredGammaSplitting class -#include "GateOptnComptSplittingForTransportation.h" +#include "GateOptnScatteredGammaSplitting.h" #include "G4BiasingProcessInterface.hh" #include "G4ComptonScattering.hh" @@ -37,6 +37,7 @@ #include "G4GammaConversion.hh" #include "G4ParticleChange.hh" #include "G4ParticleChangeForGamma.hh" +#include "GateOptnVGenericSplitting.h" #include "G4PhotoElectricEffect.hh" #include "G4ProcessType.hh" #include "G4RayleighScattering.hh" @@ -48,18 +49,17 @@ //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptnComptSplittingForTransportation:: - GateOptnComptSplittingForTransportation(G4String name) - : G4VBiasingOperation(name), fSplittingFactor(1), - fRussianRouletteForAngle(false), fParticleChange() {} +GateOptnScatteredGammaSplitting:: + GateOptnScatteredGammaSplitting(G4String name) + : GateOptnVGenericSplitting(name),fParticleChange() {} //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptnComptSplittingForTransportation:: - ~GateOptnComptSplittingForTransportation() {} +GateOptnScatteredGammaSplitting:: + ~GateOptnScatteredGammaSplitting() {} G4VParticleChange * -GateOptnComptSplittingForTransportation::ApplyFinalStateBiasing( +GateOptnScatteredGammaSplitting::ApplyFinalStateBiasing( const G4BiasingProcessInterface *callingProcess, const G4Track *track, const G4Step *step, G4bool &) { @@ -68,50 +68,22 @@ GateOptnComptSplittingForTransportation::ApplyFinalStateBiasing( // interaction Then the interaction location of the compton process will // always be the same - G4double globalTime = step->GetTrack()->GetGlobalTime(); const G4ThreeVector position = step->GetPostStepPoint()->GetPosition(); - G4int nCalls = 1; G4int splittingFactor = ceil(fSplittingFactor); - G4double survivalProbabilitySplitting = - 1 - (splittingFactor - fSplittingFactor) / splittingFactor; - G4bool isRightAngle = false; + G4double survivalProbabilitySplitting = 1 - (splittingFactor - fSplittingFactor) / splittingFactor; G4double gammaWeight = 0; - G4int nbSecondaries = 0; - G4VParticleChange *processFinalState = nullptr; - G4ParticleChangeForGamma *castedProcessInitFinalState = nullptr; - processFinalState = - callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + + + G4VParticleChange* processFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); // In case we don't want to split (a bit faster) i.e no biaising or no // splitting low weights particles. - if (fSplittingFactor == 1 && fRussianRouletteForAngle == false) - return processFinalState; - - castedProcessInitFinalState = (G4ParticleChangeForGamma *)processFinalState; - nbSecondaries = processFinalState->GetNumberOfSecondaries(); - - fParticleChange.Initialize(*track); - fParticleChange.ProposeWeight(track->GetWeight()); - fParticleChange.ProposeTrackStatus( - castedProcessInitFinalState->GetTrackStatus()); - fParticleChange.ProposeEnergy( - castedProcessInitFinalState->GetProposedKineticEnergy()); - fParticleChange.ProposeMomentumDirection( - castedProcessInitFinalState->GetProposedMomentumDirection()); - fParticleChange.SetSecondaryWeightByProcess(true); - - // If there is cut on secondary particles, there is a probability that the - // electron is not simulated Then, if the compton process created it, we add - // the gien electron to the ParticleChange object - if (nbSecondaries == 1) { - G4Track *initElectronTrack = castedProcessInitFinalState->GetSecondary(0); - initElectronTrack->SetWeight(track->GetWeight()); - fParticleChange.AddSecondary(initElectronTrack); - } + if (fSplittingFactor == 1 && fRussianRouletteForAngle == false) return processFinalState; + TrackInitializationGamma(&fParticleChange,processFinalState,track,fSplittingFactor); processFinalState->Clear(); - castedProcessInitFinalState->Clear(); + // There is here the biasing process : // Since G4VParticleChange class does not allow to retrieve scattered gamma @@ -133,66 +105,48 @@ GateOptnComptSplittingForTransportation::ApplyFinalStateBiasing( // gamma) must be considered as secondary particles, even though generated // gamma will not be cut here by the applied cut. - G4int simulationTrackID = 0; + + G4int nCalls = 1; while (nCalls <= splittingFactor) { gammaWeight = track->GetWeight() / fSplittingFactor; - G4double initGammaWeight = track->GetWeight(); - G4VParticleChange *processGammaSplittedFinalState = - callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - G4ParticleChangeForGamma *castedProcessGammaSplittedFinalState = - (G4ParticleChangeForGamma *)processGammaSplittedFinalState; - const G4ThreeVector momentum = - castedProcessGammaSplittedFinalState->GetProposedMomentumDirection(); - G4double energy = - castedProcessGammaSplittedFinalState->GetProposedKineticEnergy(); - G4double cosTheta = - fVectorDirector * - castedProcessInitFinalState->GetProposedMomentumDirection(); - G4double theta = std::acos(cosTheta); + G4VParticleChange *processGammaSplittedFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + G4ParticleChangeForGamma *castedProcessGammaSplittedFinalState = (G4ParticleChangeForGamma *)processGammaSplittedFinalState; + const G4ThreeVector momentum = castedProcessGammaSplittedFinalState->GetProposedMomentumDirection(); + G4double energy = castedProcessGammaSplittedFinalState->GetProposedKineticEnergy(); G4double splittingProbability = G4UniformRand(); - if (splittingProbability <= survivalProbabilitySplitting || - survivalProbabilitySplitting == 1) { - if ((fRussianRouletteForAngle == true) && (theta > fMaxTheta)) { - G4double probability = G4UniformRand(); - if (probability < 1 / fSplittingFactor) { - // Specific case where the russian roulette probability is - // 1/splitting. Each particle generated, with a 1/split probability - // will have a 1/split probability to survive with a final weight of - // Initial weights * 1/split * split = Initial weight - gammaWeight = gammaWeight * fSplittingFactor; + if (splittingProbability <= survivalProbabilitySplitting || survivalProbabilitySplitting == 1) { + if (fRussianRouletteForAngle == true){ + G4double weightToApply = RussianRouletteForAngleSurvival(castedProcessGammaSplittedFinalState->GetProposedMomentumDirection(),fVectorDirector,fMaxTheta,fSplittingFactor); + if (weightToApply != 0){ + gammaWeight = gammaWeight * weightToApply; G4Track *gammaTrack = new G4Track(*track); gammaTrack->SetWeight(gammaWeight); gammaTrack->SetKineticEnergy(energy); gammaTrack->SetMomentumDirection(momentum); gammaTrack->SetPosition(position); fParticleChange.AddSecondary(gammaTrack); - simulationTrackID++; if (processGammaSplittedFinalState->GetNumberOfSecondaries() == 1) { - G4Track *electronTrack = - processGammaSplittedFinalState->GetSecondary(0); + G4Track *electronTrack = processGammaSplittedFinalState->GetSecondary(0); electronTrack->SetWeight(gammaWeight); fParticleChange.AddSecondary(electronTrack); - simulationTrackID++; } } } - if ((fRussianRouletteForAngle == false) || - ((fRussianRouletteForAngle == true) && (theta <= fMaxTheta))) { + + else{ G4Track *gammaTrack = new G4Track(*track); gammaTrack->SetWeight(gammaWeight); gammaTrack->SetKineticEnergy(energy); gammaTrack->SetMomentumDirection(momentum); gammaTrack->SetPosition(position); fParticleChange.AddSecondary(gammaTrack); - simulationTrackID++; if (processGammaSplittedFinalState->GetNumberOfSecondaries() == 1) { - G4Track *electronTrack = - processGammaSplittedFinalState->GetSecondary(0); + G4Track *electronTrack = processGammaSplittedFinalState->GetSecondary(0); electronTrack->SetWeight(gammaWeight); fParticleChange.AddSecondary(electronTrack); - simulationTrackID++; + } } } diff --git a/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h b/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h new file mode 100644 index 000000000..56a6650cc --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h @@ -0,0 +1,56 @@ +// +// ******************************************************************** +// * 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 GateOptnScatteredGammaSplitting.h +/// \brief Definition of the GateOptnScatteredGammaSplitting class +// + +#ifndef GateOptnScatteredGammaSplitting_h +#define GateOptnScatteredGammaSplitting_h 1 + +#include "G4ParticleChange.hh" +#include "G4VBiasingOperation.hh" +#include "GateOptnVGenericSplitting.h" + +class GateOptnScatteredGammaSplitting : public GateOptnVGenericSplitting { +public: + // -- Constructor : + GateOptnScatteredGammaSplitting(G4String name); + + // -- destructor: + virtual ~GateOptnScatteredGammaSplitting(); + + + virtual G4VParticleChange * + ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, + const G4Step *, G4bool &); + + G4ParticleChange fParticleChange; + + +}; + +#endif diff --git a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp new file mode 100644 index 000000000..349a393ff --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp @@ -0,0 +1,105 @@ +// +// ******************************************************************** +// * 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 GateOptnVGenericSplitting.cc +/// \brief Implementation of the GateOptnVGenericSplitting class + +#include "GateOptnVGenericSplitting.h" +#include "G4BiasingProcessInterface.hh" + +#include "G4ComptonScattering.hh" +#include "G4DynamicParticle.hh" +#include "G4Exception.hh" +#include "G4Gamma.hh" +#include "G4GammaConversion.hh" +#include "G4ParticleChange.hh" +#include "G4ParticleChangeForGamma.hh" +#include "G4ParticleChangeForLoss.hh" +#include "G4PhotoElectricEffect.hh" +#include "G4ProcessType.hh" +#include "G4RayleighScattering.hh" +#include "G4SystemOfUnits.hh" +#include "G4TrackStatus.hh" +#include "G4TrackingManager.hh" +#include "G4VEmProcess.hh" +#include <memory> + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptnVGenericSplitting:: + GateOptnVGenericSplitting(G4String name) + : G4VBiasingOperation(name), fParticleChange() {} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptnVGenericSplitting:: + ~GateOptnVGenericSplitting() {} + + +void GateOptnVGenericSplitting::TrackInitializationChargedParticle(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split) { + + G4ParticleChangeForLoss* processFinalStateForLoss =( G4ParticleChangeForLoss* ) processFinalState ; + particleChange->Initialize(*track); + particleChange->ProposeTrackStatus(processFinalStateForLoss->GetTrackStatus() ); + particleChange->ProposeEnergy(processFinalStateForLoss->GetProposedKineticEnergy() ); + particleChange->ProposeMomentumDirection(processFinalStateForLoss->GetProposedMomentumDirection()); + particleChange->SetNumberOfSecondaries(fSplittingFactor); + particleChange->SetSecondaryWeightByProcess(true); + processFinalStateForLoss->Clear(); +} + +void GateOptnVGenericSplitting::TrackInitializationGamma(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split) { + G4ParticleChangeForGamma* processFinalStateForGamma = (G4ParticleChangeForGamma *)processFinalState; + particleChange->Initialize(*track); + particleChange->ProposeTrackStatus(processFinalStateForGamma->GetTrackStatus() ); + particleChange->ProposeEnergy(processFinalStateForGamma->GetProposedKineticEnergy() ); + particleChange->ProposeMomentumDirection(processFinalStateForGamma->GetProposedMomentumDirection() ); + particleChange->SetNumberOfSecondaries(fSplittingFactor); + particleChange->SetSecondaryWeightByProcess(true); + processFinalStateForGamma->Clear(); + + +} + +G4double GateOptnVGenericSplitting::RussianRouletteForAngleSurvival(G4ThreeVector dir,G4ThreeVector vectorDirector,G4double maxTheta,G4double split){ +G4double cosTheta =vectorDirector * dir; +G4double theta = std::acos(cosTheta); +G4double weightToApply = 1; +if (theta > fMaxTheta){ + G4double probability = G4UniformRand(); + if (probability <= 1 / split) { + weightToApply = split; + } + else{ + G4double weightToApply = 0; + } +} +return weightToApply; + +} + + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h similarity index 80% rename from core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h rename to core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h index 5dff8f3f1..ed75c9edd 100644 --- a/core/opengate_core/opengate_lib/GateOptnComptSplittingForTransportation.h +++ b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h @@ -24,23 +24,23 @@ // ******************************************************************** // // -/// \file GateOptnComptSplittingForTransportation.h -/// \brief Definition of the GateOptnComptSplittingForTransportation class +/// \file GateOptnVGenericSplitting.h +/// \brief Definition of the GateOptnVGenericSplitting class // -#ifndef GateOptnComptSplittingForTransportation_h -#define GateOptnComptSplittingForTransportation_h 1 +#ifndef GateOptnVGenericSplitting_h +#define GateOptnVGenericSplitting_h 1 #include "G4ParticleChange.hh" #include "G4VBiasingOperation.hh" -class GateOptnComptSplittingForTransportation : public G4VBiasingOperation { +class GateOptnVGenericSplitting : public G4VBiasingOperation { public: // -- Constructor : - GateOptnComptSplittingForTransportation(G4String name); + GateOptnVGenericSplitting(G4String name); // -- destructor: - virtual ~GateOptnComptSplittingForTransportation(); + virtual ~GateOptnVGenericSplitting(); public: // ---------------------------------------------- @@ -56,7 +56,7 @@ class GateOptnComptSplittingForTransportation : public G4VBiasingOperation { // --Used: virtual G4VParticleChange * ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, - const G4Step *, G4bool &); + const G4Step *, G4bool &){return 0;}; // -- Unsued: virtual G4double DistanceToApplyOperation(const G4Track *, G4double, @@ -68,6 +68,15 @@ class GateOptnComptSplittingForTransportation : public G4VBiasingOperation { return 0; } +// ---------------------------------------------- +// -- Methods for the generic splitting +// ---------------------------------------------- + +void TrackInitializationChargedParticle(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split); +void TrackInitializationGamma(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split); +G4double RussianRouletteForAngleSurvival(G4ThreeVector dir,G4ThreeVector vectorDirector,G4double maxTheta,G4double split); + + public: // ---------------------------------------------- // -- Additional methods, specific to this class: @@ -99,15 +108,12 @@ class GateOptnComptSplittingForTransportation : public G4VBiasingOperation { return particleChange; } -private: G4double fSplittingFactor; G4ParticleChange fParticleChange; G4bool fRussianRouletteForAngle; G4ThreeVector fVectorDirector; G4double fMaxTheta; G4RotationMatrix fRot; - // G4DynamicParticle* fSplitParticle; - // G4Track* fGammaTrack; }; #endif diff --git a/core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp b/core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp new file mode 100644 index 000000000..f067220f9 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp @@ -0,0 +1,107 @@ +// +// ******************************************************************** +// * 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 GateOptneBremSplitting.cc +/// \brief Implementation of the GateOptneBremSplitting class + +#include "GateOptneBremSplitting.h" +#include "G4BiasingProcessInterface.hh" + +#include "G4ComptonScattering.hh" +#include "G4DynamicParticle.hh" +#include "G4Exception.hh" +#include "G4Gamma.hh" +#include "G4GammaConversion.hh" +#include "G4ParticleChange.hh" +#include "GateOptnVGenericSplitting.h" +#include "G4ParticleChangeForGamma.hh" +#include "G4ParticleChangeForLoss.hh" +#include "G4PhotoElectricEffect.hh" +#include "G4ProcessType.hh" +#include "G4RayleighScattering.hh" +#include "G4SystemOfUnits.hh" +#include "G4TrackStatus.hh" +#include "G4TrackingManager.hh" +#include "G4VEmProcess.hh" +#include <memory> + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptneBremSplitting:: + GateOptneBremSplitting(G4String name) + :GateOptnVGenericSplitting(name), fParticleChange() {} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptneBremSplitting:: + ~GateOptneBremSplitting() {} + +G4VParticleChange * +GateOptneBremSplitting::ApplyFinalStateBiasing(const G4BiasingProcessInterface *callingProcess, const G4Track *track, const G4Step *step, G4bool &) { + + G4int splittingFactor = ceil(fSplittingFactor); + G4double survivalProbabilitySplitting = 1 - (splittingFactor - fSplittingFactor) / splittingFactor; + G4VParticleChange* processFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + if ( fSplittingFactor == 1 ) return processFinalState; + if ( processFinalState->GetNumberOfSecondaries() == 0 ) return processFinalState; + + TrackInitializationChargedParticle(&fParticleChange,processFinalState, track,fSplittingFactor); + + + processFinalState->Clear(); + + + G4int nCalls = 1; + while ( nCalls <= fSplittingFactor ){ + G4double splittingProbability = G4UniformRand(); + if (splittingProbability <= survivalProbabilitySplitting || survivalProbabilitySplitting == 1) { + processFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + if ( processFinalState->GetNumberOfSecondaries() >= 1 ) { + for(int i =0; i < processFinalState->GetNumberOfSecondaries();i++){ + G4double gammaWeight = track->GetWeight() / fSplittingFactor; + G4Track* gammaTrack = processFinalState->GetSecondary(i); + if (fRussianRouletteForAngle == true){ + G4double weightToApply = RussianRouletteForAngleSurvival(gammaTrack->GetMomentumDirection(),fVectorDirector,fMaxTheta,fSplittingFactor); + if (weightToApply != 0){ + gammaWeight = gammaWeight * weightToApply; + gammaTrack->SetWeight( gammaWeight); + fParticleChange.AddSecondary( gammaTrack ); + } + } + else { + gammaTrack->SetWeight( gammaWeight); + fParticleChange.AddSecondary(gammaTrack); + } + } + } + processFinalState->Clear(); + } + nCalls++; + } + return &fParticleChange; +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptneBremSplitting.h b/core/opengate_core/opengate_lib/GateOptneBremSplitting.h new file mode 100644 index 000000000..cddd10ece --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptneBremSplitting.h @@ -0,0 +1,56 @@ +// +// ******************************************************************** +// * 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 GateOptneBremSplitting.h +/// \brief Definition of the GateOptneBremSplitting class +// + +#ifndef GateOptneBremSplitting_h +#define GateOptneBremSplitting_h 1 + +#include "G4ParticleChange.hh" +#include "G4VBiasingOperation.hh" +#include "GateOptnVGenericSplitting.h" + +class GateOptneBremSplitting : public GateOptnVGenericSplitting { +public: + // -- Constructor : + GateOptneBremSplitting(G4String name); + + // -- destructor: + virtual ~GateOptneBremSplitting(); + +public: + + virtual G4VParticleChange * + ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, + const G4Step *, G4bool &); + + G4ParticleChange fParticleChange; + +}; + +#endif diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp index a2ea6d4c1..e7a2d1e5a 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp @@ -35,6 +35,8 @@ #include "G4EmCalculator.hh" #include "G4Exception.hh" #include "G4Gamma.hh" +#include "G4Positron.hh" +#include "G4Electron.hh" #include "G4LogicalVolumeStore.hh" #include "G4ParticleTable.hh" #include "G4PhysicalVolumeStore.hh" @@ -44,8 +46,12 @@ #include "G4TrackStatus.hh" #include "G4UserTrackingAction.hh" #include "G4VEmProcess.hh" -#include "GateOptnComptSplittingForTransportation.h" +#include "GateOptnScatteredGammaSplitting.h" +#include "GateOptneBremSplitting.h" +#include "GateOptnPairProdSplitting.h" #include "GateOptrComptPseudoTransportationActor.h" +#include "G4UImanager.hh" +#include "G4eplusAnnihilation.hh" //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... @@ -69,12 +75,15 @@ GateOptrComptPseudoTransportationActor::GateOptrComptPseudoTransportationActor( DictGetBool(user_info, "russian_roulette_for_weights"); fMaxTheta = DictGetDouble(user_info, "max_theta"); fFreeFlightOperation = new GateOptnForceFreeFlight("freeFlightOperation"); - fComptSplittingOperation = - new GateOptnComptSplittingForTransportation("comptSplittingOperation"); + fScatteredGammaSplittingOperation = new GateOptnScatteredGammaSplitting("comptSplittingOperation"); + feBremSplittingOperation = new GateOptneBremSplitting("eBremSplittingOperation"); + fPairProdSplittingOperation = new GateOptnPairProdSplitting("PairProdSplittingOperation"); fActions.insert("StartSimulationAction"); fActions.insert("SteppingAction"); fActions.insert("BeginOfEventAction"); isSplitted = false; + + } //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... @@ -88,14 +97,18 @@ void GateOptrComptPseudoTransportationActor::AttachAllLogicalDaughtersVolumes( AttachTo(volume); } } - if (fAttachToLogicalHolder == true) + if (fAttachToLogicalHolder == true){ AttachTo(volume); + } G4int nbOfDaughters = volume->GetNoDaughters(); if (nbOfDaughters > 0) { for (int i = 0; i < nbOfDaughters; i++) { + G4String LogicalVolumeName = volume->GetDaughter(i)->GetLogicalVolume()->GetName(); G4LogicalVolume *logicalDaughtersVolume = volume->GetDaughter(i)->GetLogicalVolume(); AttachAllLogicalDaughtersVolumes(logicalDaughtersVolume); + if (!(std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeName ) !=fNameOfBiasedLogicalVolume.end())) + fNameOfBiasedLogicalVolume.push_back(volume->GetDaughter(i)->GetLogicalVolume()->GetName()); } } } @@ -108,12 +121,22 @@ void GateOptrComptPseudoTransportationActor::StartSimulationAction() { // to the biasing operator. To do that, I use the function // AttachAllLogicalDaughtersVolumes. AttachAllLogicalDaughtersVolumes(biasingVolume); - fComptSplittingOperation->SetSplittingFactor(fSplittingFactor); - fComptSplittingOperation->SetMaxTheta(fMaxTheta); - fComptSplittingOperation->SetRussianRouletteForAngle( - fRussianRouletteForAngle); - fFreeFlightOperation->SetRussianRouletteForWeights( - fRussianRouletteForWeights); + for (int i = 0; i < fNameOfBiasedLogicalVolume.size(); i++) + fScatteredGammaSplittingOperation->SetSplittingFactor(fSplittingFactor); + fScatteredGammaSplittingOperation->SetMaxTheta(fMaxTheta); + fScatteredGammaSplittingOperation->SetRussianRouletteForAngle(fRussianRouletteForAngle); + + feBremSplittingOperation->SetSplittingFactor(fSplittingFactor); + feBremSplittingOperation->SetMaxTheta(fMaxTheta); + feBremSplittingOperation->SetRussianRouletteForAngle(fRussianRouletteForAngle); + + fPairProdSplittingOperation->SetSplittingFactor(fSplittingFactor); + + + + + fFreeFlightOperation->SetRussianRouletteForWeights(fRussianRouletteForWeights); + } void GateOptrComptPseudoTransportationActor::StartRun() { @@ -128,19 +151,48 @@ void GateOptrComptPseudoTransportationActor::StartRun() { G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); auto rot = physBiasingVolume->GetObjectRotationValue(); fVectorDirector = rot * fVectorDirector; - fComptSplittingOperation->SetRotationMatrix(rot); + fScatteredGammaSplittingOperation->SetRotationMatrix(rot); + feBremSplittingOperation->SetRotationMatrix(rot); } - fComptSplittingOperation->SetVectorDirector(fVectorDirector); + fScatteredGammaSplittingOperation->SetVectorDirector(fVectorDirector); + feBremSplittingOperation->SetVectorDirector(fVectorDirector); } + + void GateOptrComptPseudoTransportationActor::SteppingAction(G4Step *step) { - if ((isSplitted == true) && - (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary)) { - if ((step->GetPostStepPoint()->GetPhysicalVolume()->GetName() == "world")) { - step->GetTrack()->SetTrackStatus(G4TrackStatus::fStopAndKill); - isSplitted = false; + + if ((isSplitted == true) && (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary)) { + G4String LogicalVolumeName = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + if (!(std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeName ) !=fNameOfBiasedLogicalVolume.end()) + && (LogicalVolumeName != fMotherVolumeName)) { + step->GetTrack()->SetTrackStatus(G4TrackStatus::fStopAndKill); + isSplitted = false; + } + } + + + if (fKillPrimaries) { + + G4String LogicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + G4String LogicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + if (fPassedByABiasedVolume == false){ + if (LogicalVolumeNamePreStep == fMotherVolumeName){ + fPassedByABiasedVolume =true; + fKineticEnergyAtTheEntrance = step->GetPreStepPoint()->GetKineticEnergy(); + ftrackIDAtTheEntrance = step->GetTrack()->GetTrackID(); + } + } + + if ((fPassedByABiasedVolume)){ + if ((step->GetTrack()->GetTrackID() == ftrackIDAtTheEntrance) && (step->GetPostStepPoint()->GetKineticEnergy() == fKineticEnergyAtTheEntrance)){ + if ((!(std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeNamePostStep ) !=fNameOfBiasedLogicalVolume.end())) && (LogicalVolumeNamePostStep != fMotherVolumeName)){ + //std::cout<<fEventID<<" "<<LogicalVolumeNamePostStep<<std::endl; + step->GetTrack()->SetTrackStatus(G4TrackStatus::fStopAndKill); + } + } } } } @@ -148,15 +200,24 @@ void GateOptrComptPseudoTransportationActor::SteppingAction(G4Step *step) { void GateOptrComptPseudoTransportationActor::BeginOfEventAction( const G4Event *event) { fKillOthersParticles = false; + fPassedByABiasedVolume=false; + fEventID = event->GetEventID(); + fEventIDKineticEnergy = event->GetPrimaryVertex(0)->GetPrimary(0)->GetKineticEnergy(); + + } void GateOptrComptPseudoTransportationActor::StartTracking( const G4Track *track) { - - if (track->GetCreatorProcess() != 0) { - if ((track->GetCreatorProcess()->GetProcessName() == - "biasWrapper(compt)") && - (track->GetParticleDefinition()->GetParticleName() == "gamma")) { + G4String CreationProcessName = "None"; + + if (track->GetCreatorProcess() != 0){ + CreationProcessName = track->GetCreatorProcess()->GetProcessName(); + } + + if (track->GetParticleDefinition()->GetParticleName() == "gamma"){ + G4String LogicalVolumeNameOfCreation = track->GetLogicalVolumeAtVertex()->GetName(); + if (std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeNameOfCreation ) !=fNameOfBiasedLogicalVolume.end()){ fInitialWeight = track->GetWeight(); fFreeFlightOperation->SetInitialWeight(fInitialWeight); } @@ -177,12 +238,11 @@ void GateOptrComptPseudoTransportationActor::StartTracking( G4VBiasingOperation * GateOptrComptPseudoTransportationActor::ProposeOccurenceBiasingOperation( const G4Track *track, const G4BiasingProcessInterface *callingProcess) { - if (track->GetCreatorProcess() != 0) { - if ((track->GetCreatorProcess()->GetProcessName() == - "biasWrapper(compt)") && - (track->GetParticleDefinition()->GetParticleName() == "gamma")) { - fFreeFlightOperation->SetMinWeight(fInitialWeight / - fRelativeMinWeightOfParticle); + + if (track->GetParticleDefinition()->GetParticleName() == "gamma"){ + G4String LogicalVolumeNameOfCreation = track->GetLogicalVolumeAtVertex()->GetName(); + if (std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeNameOfCreation ) !=fNameOfBiasedLogicalVolume.end()){ + fFreeFlightOperation->SetMinWeight(fInitialWeight/fRelativeMinWeightOfParticle); fFreeFlightOperation->SetTrackWeight(track->GetWeight()); fFreeFlightOperation->SetCountProcess(0); return fFreeFlightOperation; @@ -201,35 +261,45 @@ GateOptrComptPseudoTransportationActor::ProposeOccurenceBiasingOperation( G4VBiasingOperation * GateOptrComptPseudoTransportationActor::ProposeFinalStateBiasingOperation( const G4Track *track, const G4BiasingProcessInterface *callingProcess) { - G4String callingProcessName = "biasWrapper(compt)"; + G4String particleName = track->GetParticleDefinition()->GetParticleName(); + + G4String LogicalVolumeNameOfCreation = track->GetLogicalVolumeAtVertex()->GetName(); + G4String CreationProcessName = ""; + if (track->GetCreatorProcess() != 0){ + CreationProcessName = track->GetCreatorProcess()->GetProcessName(); + } + - if (callingProcess->GetWrappedProcess()->GetProcessName() == "compt") { - if (track->GetCreatorProcess() == 0) { - isSplitted = true; - return fComptSplittingOperation; + if (track->GetParticleDefinition()->GetParticleName() == "gamma"){ + if (std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeNameOfCreation ) !=fNameOfBiasedLogicalVolume.end()){ + return callingProcess->GetCurrentOccurenceBiasingOperation(); } - if (track->GetCreatorProcess()->GetProcessName() != "biasWrapper(compt)") { + } + + + if (((CreationProcessName != "biasWrapper(conv)") && (CreationProcessName != "biasWrapper(compt)")) + && (callingProcess->GetWrappedProcess()->GetProcessName() == "eBrem")){ + return feBremSplittingOperation; + } + + + + if (!(std::find(fCreationProcessNameList.begin(), fCreationProcessNameList.end(),CreationProcessName) != fCreationProcessNameList.end())){ + if ((callingProcess->GetWrappedProcess()->GetProcessName() == "compt") || (callingProcess->GetWrappedProcess()->GetProcessName() == "Rayl")){ isSplitted = true; - return fComptSplittingOperation; + return fScatteredGammaSplittingOperation; } - } - /* - */ - if (track->GetCreatorProcess() != 0) { - if ((track->GetCreatorProcess()->GetProcessName() == - "biasWrapper(compt)") && - (track->GetParticleDefinition()->GetParticleName() == "gamma")) { - return callingProcess->GetCurrentOccurenceBiasingOperation(); + if ((callingProcess->GetWrappedProcess()->GetProcessName() == "conv")){ + return fPairProdSplittingOperation; } + } + return 0; } - return 0; - - // return 0; -} void GateOptrComptPseudoTransportationActor::EndTracking() { isSplitted = false; } + //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h index 3b7d6b457..d3871d079 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h @@ -32,13 +32,15 @@ #include "G4EmCalculator.hh" #include "G4VBiasingOperator.hh" #include "GateOptnForceFreeFlight.h" +#include "GateOptneBremSplitting.h" +#include "GateOptnPairProdSplitting.h" #include "GateVActor.h" #include <iostream> #include <pybind11/stl.h> namespace py = pybind11; -class GateOptnComptSplittingForTransportation; +class GateOptnScatteredGammaSplitting; class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, public GateVActor { @@ -78,7 +80,18 @@ class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, G4bool fUseProbes = false; G4bool fSurvivedRR = false; G4bool fAttachToLogicalHolder = true; - + G4bool fKillPrimaries = true; + G4bool fPassedByABiasedVolume= false; + G4double fKineticEnergyAtTheEntrance; + G4int ftrackIDAtTheEntrance; + G4int fEventID; + G4double fEventIDKineticEnergy; + G4bool ftestbool= false; + const G4VProcess* fAnnihilation =nullptr; + + std::vector<G4String> fNameOfBiasedLogicalVolume = {}; + std::vector<G4int> v_EventID = {}; + std::vector<G4String> fCreationProcessNameList = {"biasWrapper(compt)","biasWrapper(Rayl)", "biasWrapper(eBrem)","biasWrapper(annihil)"}; // Unused but mandatory virtual void StartSimulationAction(); @@ -114,8 +127,10 @@ class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, using G4VBiasingOperator::OperationApplied; private: - GateOptnForceFreeFlight *fFreeFlightOperation; - GateOptnComptSplittingForTransportation *fComptSplittingOperation; + GateOptnForceFreeFlight* fFreeFlightOperation; + GateOptnScatteredGammaSplitting* fScatteredGammaSplittingOperation; + GateOptneBremSplitting* feBremSplittingOperation; + GateOptnPairProdSplitting* fPairProdSplittingOperation; }; #endif diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index eb9970b36..f8e082ec3 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -475,7 +475,9 @@ def set_default_user_info(user_info): user_info.attach_to_logical_holder = True user_info.splitting_factor = 1 user_info.relative_min_weight_of_particle = np.inf - user_info.processes = ["compt", "phot", "conv", "Rayl"] + user_info.gamma_processes = ["compt", "phot", "conv", "Rayl"] + user_info.electron_processes = ["eBrem"] + user_info.positron_processes = ["annihil","eBrem"] user_info.russian_roulette_for_angle = False user_info.rotation_vector_director = False user_info.vector_director = [0, 0, 1] diff --git a/opengate/tests/data b/opengate/tests/data index d1c61ed4e..8613c9fa7 160000 --- a/opengate/tests/data +++ b/opengate/tests/data @@ -1 +1 @@ -Subproject commit d1c61ed4e7bade0067ed9fa20660d52abcc28744 +Subproject commit 8613c9fa705acd90018239b0cd8e53180e871ebc diff --git a/opengate/tests/src/test072_pseudo_transport_RR.py b/opengate/tests/src/test072_pseudo_transport_RR.py index e39832182..3ed0038e4 100644 --- a/opengate/tests/src/test072_pseudo_transport_RR.py +++ b/opengate/tests/src/test072_pseudo_transport_RR.py @@ -157,7 +157,7 @@ def validation_test(arr_no_RR, arr_RR, nb_event, splitting_factor, tol=0.15): pseudo_transportation_actor.russian_roulette_for_weights = False if j == 1: pseudo_transportation_actor.russian_roulette_for_weights = True - list_processes_to_bias = pseudo_transportation_actor.processes + list_processes_to_bias = pseudo_transportation_actor.gamma_processes ##### PHASE SPACE plan ######" plan = sim.add_volume("Box", "phsp") diff --git a/opengate/tests/src/test072_pseudo_transportation.py b/opengate/tests/src/test072_pseudo_transportation.py index ea981641f..64f8e822f 100644 --- a/opengate/tests/src/test072_pseudo_transportation.py +++ b/opengate/tests/src/test072_pseudo_transportation.py @@ -124,7 +124,7 @@ def validation_test(arr, NIST_data, nb_split, tol=0.01): pseudo_transportation_actor.mother = simple_collimation.name pseudo_transportation_actor.splitting_factor = nb_split pseudo_transportation_actor.relative_min_weight_of_particle = np.inf - list_processes_to_bias = pseudo_transportation_actor.processes + list_processes_to_bias = pseudo_transportation_actor.gamma_processes ##### PHASE SPACE plan ######" plan = sim.add_volume("Box", "phsp") From dd00658ae9f838ad7f15e26384ab7bb2bd603140 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 19 Mar 2024 16:02:16 +0100 Subject: [PATCH 09/82] Development of an actor which kill a track if this one passes throught the actor volume without any interaction --- core/opengate_core/opengate_core.cpp | 3 + .../GateKillNonInteractingParticleActor.cpp | 65 ++++++ .../GateKillNonInteractingParticleActor.h | 43 ++++ .../pyGateKillNonInteractingParticleActor.cpp | 19 ++ opengate/actors/actorbuilders.py | 2 + opengate/actors/miscactors.py | 36 ++++ opengate/managers.py | 3 + .../test072_kill_non_interacting_particles.py | 201 ++++++++++++++++++ 8 files changed, 372 insertions(+) create mode 100644 core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp create mode 100644 core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.h create mode 100644 core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp create mode 100644 opengate/tests/src/test072_kill_non_interacting_particles.py diff --git a/core/opengate_core/opengate_core.cpp b/core/opengate_core/opengate_core.cpp index 86e314219..6ef2a9603 100644 --- a/core/opengate_core/opengate_core.cpp +++ b/core/opengate_core/opengate_core.cpp @@ -259,6 +259,8 @@ void init_GateARFTrainingDatasetActor(py::module &m); void init_GateKillActor(py::module &); +void init_GateKillNonInteractingParticleActor(py::module &); + void init_itk_image(py::module &); void init_GateImageNestedParameterisation(py::module &); @@ -493,6 +495,7 @@ PYBIND11_MODULE(opengate_core, m) { init_GateARFActor(m); init_GateARFTrainingDatasetActor(m); init_GateKillActor(m); + init_GateKillNonInteractingParticleActor(m); init_GateDigiAttributeManager(m); init_GateVDigiAttribute(m); init_GateExceptionHandler(m); diff --git a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp new file mode 100644 index 000000000..b71198e0e --- /dev/null +++ b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp @@ -0,0 +1,65 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + ------------------------------------ -------------- */ + +#include "GateKillNonInteractingParticleActor.h" +#include "G4ios.hh" +#include "GateHelpers.h" +#include "GateHelpersDict.h" +#include "G4PhysicalVolumeStore.hh" +#include "G4LogicalVolumeStore.hh" + + +GateKillNonInteractingParticleActor::GateKillNonInteractingParticleActor(py::dict &user_info) + : GateVActor(user_info, false) { + fActions.insert("StartSimulationAction"); + fActions.insert("SteppingAction"); + fActions.insert("PreUserTrackingAction"); +} + + + + +void GateKillNonInteractingParticleActor::ActorInitialize() {} + +void GateKillNonInteractingParticleActor::StartSimulationAction() { + fNbOfKilledParticles = 0; +} + + +void GateKillNonInteractingParticleActor::PreUserTrackingAction(const G4Track* track) { + fIsFirstStep = true; + fKineticEnergyAtTheEntrance = 0; + ftrackIDAtTheEntrance = 0; + fPassedByTheMotherVolume = false; + +} + +void GateKillNonInteractingParticleActor::SteppingAction(G4Step *step) { + + G4String logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + if ((fPassedByTheMotherVolume) && (step->GetPostStepPoint()->GetStepStatus() == 1)){ + if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(),logicalVolumeNamePostStep ) !=fListOfVolumeAncestor.end()){ + if ((step->GetTrack()->GetTrackID() == ftrackIDAtTheEntrance) && (step->GetPostStepPoint()->GetKineticEnergy() == fKineticEnergyAtTheEntrance)){ + auto track = step->GetTrack(); + track->SetTrackStatus(fStopAndKill); + fNbOfKilledParticles++; + } + } +} + + G4String logNameMotherVolume = G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName)->GetName(); + G4String physicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetName(); + if ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != logNameMotherVolume) && (fIsFirstStep)){ + if ((fPassedByTheMotherVolume == false) && (physicalVolumeNamePreStep == fMotherVolumeName) && (step->GetPreStepPoint()->GetStepStatus() == 1)){ + fPassedByTheMotherVolume =true; + fKineticEnergyAtTheEntrance = step->GetPreStepPoint()->GetKineticEnergy(); + ftrackIDAtTheEntrance = step->GetTrack()->GetTrackID(); + } + } + + fIsFirstStep = false; +} diff --git a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.h b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.h new file mode 100644 index 000000000..a7cf58ccb --- /dev/null +++ b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.h @@ -0,0 +1,43 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#ifndef GateKillNonInteractingParticleActor_h +#define GateKillNonInteractingParticleActor_h + +#include "G4Cache.hh" +#include "GateVActor.h" +#include <pybind11/stl.h> + +namespace py = pybind11; + +class GateKillNonInteractingParticleActor : public GateVActor { + +public: + // Constructor + GateKillNonInteractingParticleActor(py::dict &user_info); + + void ActorInitialize() override; + + void StartSimulationAction() override; + + // Main function called every step in attached volume + void SteppingAction(G4Step *) override; + + void PreUserTrackingAction(const G4Track*) override; + + std::vector<G4String> fParticlesTypeToKill; + G4bool fPassedByTheMotherVolume= false; + G4double fKineticEnergyAtTheEntrance = 0; + G4int ftrackIDAtTheEntrance = 0; + G4bool fIsFirstStep = true; + std::vector<std::string> fListOfVolumeAncestor; + + + long fNbOfKilledParticles{}; +}; + +#endif diff --git a/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp new file mode 100644 index 000000000..6b5cee6a2 --- /dev/null +++ b/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp @@ -0,0 +1,19 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include <pybind11/pybind11.h> + +namespace py = pybind11; + +#include "GateKillNonInteractingParticleActor.h" + +void init_GateKillNonInteractingParticleActor(py::module &m) { + py::class_<GateKillNonInteractingParticleActor, std::unique_ptr<GateKillNonInteractingParticleActor, py::nodelete>, + GateVActor>(m, "GateKillNonInteractingParticleActor") + .def_readwrite("fListOfVolumeAncestor", &GateKillNonInteractingParticleActor::fListOfVolumeAncestor) + .def(py::init<py::dict &>()); +} diff --git a/opengate/actors/actorbuilders.py b/opengate/actors/actorbuilders.py index 1756e64c1..122fea524 100644 --- a/opengate/actors/actorbuilders.py +++ b/opengate/actors/actorbuilders.py @@ -17,6 +17,7 @@ SourceInfoActor, TestActor, KillActor, + KillNonInteractingParticleActor, ) from .dynamicactors import DynamicGeometryActor from ..utility import make_builders @@ -42,6 +43,7 @@ ARFTrainingDatasetActor, TestActor, KillActor, + KillNonInteractingParticleActor, DynamicGeometryActor, } actor_builders = make_builders(actor_type_names) diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 5b12f1566..ec1316ccc 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -5,6 +5,7 @@ import numpy as np import time import platform +from anytree import Node, RenderTree import opengate_core as g4 from .base import ActorBase from ..exception import fatal @@ -427,3 +428,38 @@ def set_default_user_info(user_info): def __init__(self, user_info): ActorBase.__init__(self, user_info) g4.GateKillActor.__init__(self, user_info.__dict__) + + +class KillNonInteractingParticleActor(g4.GateKillNonInteractingParticleActor, ActorBase): + type_name = "KillNonInteractingParticleActor" + + def set_default_user_info(user_info): + ActorBase.set_default_user_info(user_info) + user_info.list_of_volume_name = [] + + + def __init__(self, user_info): + ActorBase.__init__(self, user_info) + g4.GateKillNonInteractingParticleActor.__init__(self, user_info.__dict__) + self.list_of_volume_name = user_info.list_of_volume_name + self.user_info.mother = user_info.mother + + + def initialize(self, volume_engine=None): + + super().initialize(volume_engine) + volume_tree = self.simulation.volume_manager.get_volume_tree() + dico_of_volume_tree = {} + for pre, _, node in RenderTree(volume_tree): + dico_of_volume_tree[str(node.name)] = node + volume_name = self.user_info.mother + while volume_name != 'world': + node = dico_of_volume_tree[volume_name] + volume_name = node.mother + self.list_of_volume_name.append(volume_name) + self.fListOfVolumeAncestor = self.list_of_volume_name + + + + + diff --git a/opengate/managers.py b/opengate/managers.py index 95d364fd4..332a8d488 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -887,6 +887,9 @@ def dump_volume_types(self): def dump_material_database_names(self): return list(self.material_database.filenames) + + def get_volume_tree(self): + return (self.volume_tree_root) def setter_hook_verbose_level(self, verbose_level): diff --git a/opengate/tests/src/test072_kill_non_interacting_particles.py b/opengate/tests/src/test072_kill_non_interacting_particles.py new file mode 100644 index 000000000..9e3d55729 --- /dev/null +++ b/opengate/tests/src/test072_kill_non_interacting_particles.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +from opengate.tests import utility +from scipy.spatial.transform import Rotation +import numpy as np +from anytree import Node,RenderTree +import uproot + + + + + + +def test072_test(entry_data, exit_data_1,exit_data_2): + liste_ekin =[] + liste_evtID = [] + liste_trackID = [] + evt_ID_entry_data = entry_data["EventID"] + j = 0 + i = 0 + while i < len(evt_ID_entry_data): + if j < len(exit_data_1["EventID"]) and evt_ID_entry_data[i] == exit_data_1["EventID"][j]: + TID_entry = entry_data["TrackID"][i] + TID_exit = exit_data_1["TrackID"][j] + Ekin_entry = entry_data["KineticEnergy"][i] + Ekin_exit = exit_data_1["KineticEnergy"][j] + + if (TID_entry == TID_exit) and (Ekin_exit == Ekin_entry): + liste_ekin.append(exit_data_1["KineticEnergy"][j]) + liste_evtID.append(exit_data_1["EventID"][j]) + liste_trackID.append(exit_data_1["TrackID"][j]) + if (j < len(exit_data_1["EventID"]) -1) and (exit_data_1["EventID"][j] == exit_data_1["EventID"][j+1]) : + i = i-1 + j+=1 + i+=1 + liste_ekin =np.asarray(liste_ekin) + print("Number of tracks to kill =", len(liste_ekin)) + print("Number of killed tracks =",(len(exit_data_1["EventID"]) - len(exit_data_2["EventID"]))) + + + return len(liste_ekin) == (len(exit_data_1["EventID"]) - len(exit_data_2["EventID"])) + + + + + + + + + +if __name__ == "__main__": + paths = utility.get_default_test_paths(__file__) + output_path = paths.output + + print(output_path) + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + # ui.visu = True + ui.visu_type = "vrml" + ui.check_volumes_overlap = False + # ui.running_verbose_level = gate.logger.EVENT + ui.number_of_threads = 1 + ui.random_seed = "auto" + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + sec = gate.g4_units.s + gcm3 = gate.g4_units["g/cm3"] + + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + + # adapt world size + world = sim.world + world.size = [1 * m, 1 * m, 1 * m] + world.material = "G4_AIR" + + big_box = sim.add_volume("Box","big_box") + big_box.mother = world.name + big_box.material = 'G4_AIR' + big_box.size = [0.8*m,0.8*m,0.8*m] + + actor_box = sim.add_volume("Box", "actor_box") + actor_box.mother = big_box.name + actor_box.material = 'G4_AIR' + actor_box.size = [0.6 * m, 0.6 * m, 0.6 * m] + actor_box.translation = [0,0,-0.1*m] + + + + + source = sim.add_source("GenericSource", "photon_source") + source.particle = "gamma" + source.position.type = "box" + source.mother = world.name + source.position.size = [6 * cm, 6 * cm, 6 * cm] + source.position.translation = [0,0,0.3*m] + source.direction.type = "momentum" + source.force_rotation = True + # source1.direction.focus_point = [0*cm, 0*cm, -5 *cm] + source.direction.momentum = [0, 0, -1] + source.energy.type = "mono" + source.energy.mono = 6 * MeV + source.n= 1000 + + + + tungsten_leaves =sim.add_volume("Box","tungsten_leaves") + tungsten_leaves.mother = actor_box + tungsten_leaves.size = [0.6*m,0.6*m,0.3*cm] + tungsten_leaves.material = "Tungsten" + liste_translation_W = [] + for i in range(7) : + liste_translation_W.append([0,0,0.25*m - i*6*cm]) + tungsten_leaves.translation = liste_translation_W + tungsten_leaves.color = [0.9,0.,0.4,0.8] + + + kill_No_int_act = sim.add_actor("KillNonInteractingParticleActor","killact") + kill_No_int_act.mother = actor_box.name + + + entry_phase_space = sim.add_volume("Box","entry_phase_space") + entry_phase_space.mother = big_box + entry_phase_space.size = [0.8*m,0.8*m,1*nm] + entry_phase_space.material = "G4_AIR" + entry_phase_space.translation = [0,0,0.21*m] + entry_phase_space.color = [0.5,0.9,0.3,1] + + exit_phase_space_1 = sim.add_volume("Box","exit_phase_space_1") + exit_phase_space_1.mother = actor_box + exit_phase_space_1.size = [0.6 * m, 0.6 * m, 1 * nm] + exit_phase_space_1.material = "G4_AIR" + exit_phase_space_1.translation = [0, 0, -0.3 * m + 1*nm] + exit_phase_space_1.color = [0.5, 0.9, 0.3, 1] + + exit_phase_space_2 = sim.add_volume("Box", "exit_phase_space_2") + exit_phase_space_2.mother = world.name + exit_phase_space_2.size = [0.6 * m, 0.6 * m, 1 * nm] + exit_phase_space_2.material = "G4_AIR" + exit_phase_space_2.translation = [0, 0, -0.4 * m - 1 * nm] + exit_phase_space_2.color = [0.5, 0.9, 0.3, 1] + + # print(sim.volume_manager.dump_volume_tree()) + liste_phase_space_name = [entry_phase_space.name,exit_phase_space_1.name,exit_phase_space_2.name] + for name in liste_phase_space_name: + + phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_"+name) + phsp.mother = name + phsp.attributes = ["EventID","TrackID","KineticEnergy"] + name_phsp = "test072_" +name+".root" + phsp.output = output_path / name_phsp + + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + sim.physics_manager.enable_decay = False + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1 * mm + sim.physics_manager.global_production_cuts.positron = 1 * mm + + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + + + # go ! + sim.run() + output = sim.output + stats = sim.output.get_actor("Stats") + print(stats) + + + entry_phsp = uproot.open(str(output_path) + "/test072_" + liste_phase_space_name[0] + ".root" +":PhaseSpace_" + liste_phase_space_name[0]) + exit_phase_space_1 = uproot.open(str(output_path) + "/test072_" + liste_phase_space_name[1] + ".root" + ":PhaseSpace_" + liste_phase_space_name[1]) + exit_phase_space_2 = uproot.open( str(output_path) + "/test072_" + liste_phase_space_name[2] + ".root" + ":PhaseSpace_" + liste_phase_space_name[2]) + + + df_entry = entry_phsp.arrays() + df_exit_1 = exit_phase_space_1.arrays() + df_exit_2 = exit_phase_space_2.arrays() + + is_ok = test072_test(df_entry, df_exit_1, df_exit_2) + + utility.test_ok(is_ok) \ No newline at end of file From 1045e37182a0763a9c6a75bf1f49bfd9a0e71f85 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Wed, 20 Mar 2024 15:50:34 +0100 Subject: [PATCH 10/82] Development of an actor which split particles at the entrance and/or at the exit of the volume --- core/opengate_core/opengate_core.cpp | 3 + .../GateSurfaceSplittingActor.cpp | 86 ++++++++++ .../opengate_lib/GateSurfaceSplittingActor.h | 45 +++++ .../pyGateSurfaceSplittingActor.cpp | 19 +++ opengate/actors/actorbuilders.py | 2 + opengate/actors/miscactors.py | 33 ++++ opengate/managers.py | 3 + ...73_geometrical_splitting_volume_surface.py | 161 ++++++++++++++++++ 8 files changed, 352 insertions(+) create mode 100644 core/opengate_core/opengate_lib/GateSurfaceSplittingActor.cpp create mode 100644 core/opengate_core/opengate_lib/GateSurfaceSplittingActor.h create mode 100644 core/opengate_core/opengate_lib/pyGateSurfaceSplittingActor.cpp create mode 100644 opengate/tests/src/test073_geometrical_splitting_volume_surface.py diff --git a/core/opengate_core/opengate_core.cpp b/core/opengate_core/opengate_core.cpp index 86e314219..74506b5e4 100644 --- a/core/opengate_core/opengate_core.cpp +++ b/core/opengate_core/opengate_core.cpp @@ -259,6 +259,8 @@ void init_GateARFTrainingDatasetActor(py::module &m); void init_GateKillActor(py::module &); +void init_GateSurfaceSplittingActor(py::module &); + void init_itk_image(py::module &); void init_GateImageNestedParameterisation(py::module &); @@ -493,6 +495,7 @@ PYBIND11_MODULE(opengate_core, m) { init_GateARFActor(m); init_GateARFTrainingDatasetActor(m); init_GateKillActor(m); + init_GateSurfaceSplittingActor(m); init_GateDigiAttributeManager(m); init_GateVDigiAttribute(m); init_GateExceptionHandler(m); diff --git a/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.cpp b/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.cpp new file mode 100644 index 000000000..53b88c804 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.cpp @@ -0,0 +1,86 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + ------------------------------------ -------------- */ + +#include "GateSurfaceSplittingActor.h" +#include "G4ios.hh" +#include "GateHelpers.h" +#include "GateHelpersDict.h" +#include "G4LogicalVolumeStore.hh" + +GateSurfaceSplittingActor::GateSurfaceSplittingActor(py::dict &user_info) + : GateVActor(user_info, false) { + fActions.insert("StartSimulationAction"); + fActions.insert("SteppingAction"); + fActions.insert("PreUserTrackingAction"); + fMotherVolumeName = DictGetStr(user_info,"mother"); + fWeightThreshold = DictGetBool(user_info,"weight_threshold"); + fSplittingFactor = DictGetInt(user_info,"splitting_factor"); + fSplitEnteringParticles = DictGetBool(user_info,"split_entering_particles"); + fSplitExitingParticles = DictGetBool(user_info,"split_exiting_particles"); +} + +void GateSurfaceSplittingActor::ActorInitialize() {} + +void GateSurfaceSplittingActor::StartSimulationAction() { fNbOfKilledParticles = 0; } + +void GateSurfaceSplittingActor::PreUserTrackingAction(const G4Track* track){ + + fIsFirstStep = true; +} + +void GateSurfaceSplittingActor::SteppingAction(G4Step *step) { + auto track = step->GetTrack(); + auto weight = track->GetWeight(); + + + if (weight >= fWeightThreshold){ + if (fSplitEnteringParticles){ + G4String logicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + if (((fIsFirstStep) && (step->GetPreStepPoint()->GetStepStatus() ==1) && (logicalVolumeNamePreStep == fMotherVolumeName)) + || ((fIsFirstStep) && (track->GetLogicalVolumeAtVertex()->GetName() != logicalVolumeNamePreStep) && (track->GetLogicalVolumeAtVertex()->GetName() != fMotherVolumeName))) { + G4ThreeVector position = step->GetPreStepPoint()->GetPosition(); + G4ThreeVector momentum = step->GetPreStepPoint()->GetMomentum(); + G4double ekin = step->GetPreStepPoint()->GetKineticEnergy(); + + const G4DynamicParticle* particleType = track->GetDynamicParticle(); + G4double time = step->GetPreStepPoint()->GetGlobalTime(); + G4TrackVector *trackVector = step->GetfSecondary(); + + for (int i = 0; i < fSplittingFactor -1 ; i++) { + G4DynamicParticle* particleTypeToAdd = new G4DynamicParticle(*particleType); + G4Track* clone = new G4Track(particleTypeToAdd ,time,position); + clone->SetKineticEnergy(ekin); + clone->SetMomentumDirection(momentum); + clone->SetWeight( weight/fSplittingFactor); + trackVector->push_back(clone); + } + step->GetPreStepPoint()->SetWeight(weight/fSplittingFactor); + step->GetPostStepPoint()->SetWeight(weight/fSplittingFactor); + track->SetWeight(weight/fSplittingFactor); + + + } + } + + + if(fSplitExitingParticles){ + G4String logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(),logicalVolumeNamePostStep) !=fListOfVolumeAncestor.end()){ + G4TrackVector *trackVector = step->GetfSecondary(); + for (int i = 0; i < fSplittingFactor-1; i++) { + G4Track* clone = new G4Track(*track); + clone->SetWeight( weight/fSplittingFactor); + trackVector->push_back(clone); + } + step->GetPostStepPoint()->SetWeight(weight/fSplittingFactor); + track->SetWeight(weight/fSplittingFactor); + } + } + + } + fIsFirstStep =false; +} diff --git a/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.h b/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.h new file mode 100644 index 000000000..871342f7c --- /dev/null +++ b/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.h @@ -0,0 +1,45 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#ifndef GateSurfaceSplittingActor_h +#define GateSurfaceSplittingActor_h + +#include "G4Cache.hh" +#include "GateVActor.h" +#include <pybind11/stl.h> + +namespace py = pybind11; + +class GateSurfaceSplittingActor : public GateVActor { + +public: + // Constructor + GateSurfaceSplittingActor(py::dict &user_info); + + void ActorInitialize() override; + + void StartSimulationAction() override; + + // Main function called every step in attached volume + void SteppingAction(G4Step *) override; + + void PreUserTrackingAction(const G4Track *) override; + + + G4bool fSplitEnteringParticles =false; + G4bool fSplitExitingParticles =false ; + G4int fSplittingFactor; + G4bool fIsFirstStep; + G4bool fWeightThreshold; + G4String fMotherVolumeName; + std::vector<std::string> fListOfVolumeAncestor; + + + long fNbOfKilledParticles{}; +}; + +#endif diff --git a/core/opengate_core/opengate_lib/pyGateSurfaceSplittingActor.cpp b/core/opengate_core/opengate_lib/pyGateSurfaceSplittingActor.cpp new file mode 100644 index 000000000..984e7a937 --- /dev/null +++ b/core/opengate_core/opengate_lib/pyGateSurfaceSplittingActor.cpp @@ -0,0 +1,19 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include <pybind11/pybind11.h> + +namespace py = pybind11; + +#include "GateSurfaceSplittingActor.h" + +void init_GateSurfaceSplittingActor(py::module &m) { + py::class_<GateSurfaceSplittingActor, std::unique_ptr<GateSurfaceSplittingActor, py::nodelete>,GateVActor>(m, "GateSurfaceSplittingActor") + .def(py::init<py::dict &>()) + .def_readwrite("fListOfVolumeAncestor",&GateSurfaceSplittingActor::fListOfVolumeAncestor) + .def(py::init<py::dict &>()); +} diff --git a/opengate/actors/actorbuilders.py b/opengate/actors/actorbuilders.py index 1756e64c1..1ffc79547 100644 --- a/opengate/actors/actorbuilders.py +++ b/opengate/actors/actorbuilders.py @@ -17,6 +17,7 @@ SourceInfoActor, TestActor, KillActor, + SurfaceSplittingActor, ) from .dynamicactors import DynamicGeometryActor from ..utility import make_builders @@ -42,6 +43,7 @@ ARFTrainingDatasetActor, TestActor, KillActor, + SurfaceSplittingActor, DynamicGeometryActor, } actor_builders = make_builders(actor_type_names) diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 5b12f1566..48f148fde 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -5,6 +5,7 @@ import numpy as np import time import platform +from anytree import Node, RenderTree import opengate_core as g4 from .base import ActorBase from ..exception import fatal @@ -427,3 +428,35 @@ def set_default_user_info(user_info): def __init__(self, user_info): ActorBase.__init__(self, user_info) g4.GateKillActor.__init__(self, user_info.__dict__) + +class SurfaceSplittingActor(g4.GateSurfaceSplittingActor, ActorBase): + type_name = "SurfaceSplittingActor" + + def set_default_user_info(user_info): + ActorBase.set_default_user_info(user_info) + user_info.list_of_volume_name = [] + user_info.splitting_factor = 1 + user_info.split_entering_particles = False + user_info.split_exiting_particles = False + user_info.weight_threshold = 0 + + def __init__(self, user_info): + ActorBase.__init__(self, user_info) + g4.GateSurfaceSplittingActor.__init__(self, user_info.__dict__) + self.list_of_volume_name = user_info.list_of_volume_name + self.user_info.mother = user_info.mother + + + def initialize(self, volume_engine=None): + + super().initialize(volume_engine) + volume_tree = self.simulation.volume_manager.get_volume_tree() + dico_of_volume_tree = {} + for pre, _, node in RenderTree(volume_tree): + dico_of_volume_tree[str(node.name)] = node + volume_name = self.user_info.mother + while volume_name != 'world': + node = dico_of_volume_tree[volume_name] + volume_name = node.mother + self.list_of_volume_name.append(volume_name) + self.fListOfVolumeAncestor = self.list_of_volume_name diff --git a/opengate/managers.py b/opengate/managers.py index 95d364fd4..332a8d488 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -887,6 +887,9 @@ def dump_volume_types(self): def dump_material_database_names(self): return list(self.material_database.filenames) + + def get_volume_tree(self): + return (self.volume_tree_root) def setter_hook_verbose_level(self, verbose_level): diff --git a/opengate/tests/src/test073_geometrical_splitting_volume_surface.py b/opengate/tests/src/test073_geometrical_splitting_volume_surface.py new file mode 100644 index 000000000..f3b2d8278 --- /dev/null +++ b/opengate/tests/src/test073_geometrical_splitting_volume_surface.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +from opengate.tests import utility +from scipy.spatial.transform import Rotation +import numpy as np +from anytree import Node,RenderTree +import uproot + + +def test073(entry_data, exit_data, splitting_factor): + splitted_particle_data_entry = entry_data[entry_data["TrackCreatorProcess"] == "none"] + splitted_particle_data_exit = exit_data[exit_data["TrackCreatorProcess"] == "none"] + + array_weight_1 = splitted_particle_data_entry["Weight"] + array_weight_2 = splitted_particle_data_exit["Weight"] + + + if (np.round(np.sum(array_weight_1),3) == 1) and len(array_weight_1) == splitting_factor: + if (np.round(np.sum(array_weight_2),3) == 1) and len(array_weight_2) == splitting_factor: + return True + else : return False + else : return False + + +if __name__ == "__main__": + paths = utility.get_default_test_paths(__file__) + output_path = paths.output + + print(output_path) + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + ui.visu = True + ui.visu_type = "vrml" + ui.check_volumes_overlap = False + # ui.running_verbose_level = gate.logger.EVENT + ui.number_of_threads = 1 + ui.random_seed = "auto" + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + sec = gate.g4_units.s + gcm3 = gate.g4_units["g/cm3"] + + + # adapt world size + world = sim.world + world.size = [1 * m, 1 * m, 1 * m] + world.material = "G4_Galactic" + + big_box = sim.add_volume("Box","big_box") + big_box.mother = world.name + big_box.material = "G4_Galactic" + big_box.size = [0.8*m,0.8*m,0.8*m] + + actor_box = sim.add_volume("Box", "actor_box") + actor_box.mother = big_box.name + actor_box.material = "G4_Galactic" + actor_box.size = [0.6 * m, 0.6 * m, 0.6 * m] + actor_box.translation = [0,0,-0.1*m] + + + + + source_1 = sim.add_source("GenericSource", "elec_source_1") + source_1.particle = "e-" + source_1.position.type = "box" + source_1.mother = big_box.name + source_1.position.size = [1*cm,1*cm,1*cm] + source_1.position.translation = [0,0.35*m,0] + source_1.direction.type = "momentum" + source_1.direction.momentum = [0,-1,0] + source_1.energy.type = "mono" + source_1.energy.mono = 10 * MeV + source_1.n= 1 + + source_2 = sim.add_source("GenericSource", "elec_source_2") + source_2.particle = "e-" + source_2.position.type = "box" + source_2.mother = big_box.name + source_2.position.size = [1 * cm, 1 * cm, 1 * cm] + source_2.position.translation = [0, 0, -0.39 * m] + source_2.direction.type = "momentum" + source_2.direction.momentum = [0, 0, -1] + source_2.energy.type = "mono" + source_2.energy.mono = 10 * MeV + source_2.n = 1 + + + geom_splitting = sim.add_actor("SurfaceSplittingActor","splitting_act") + geom_splitting.mother = actor_box.name + geom_splitting.splitting_factor = 10 + geom_splitting.weight_threshold = 1 + geom_splitting.split_entering_particles = True + geom_splitting.split_exiting_particles = True + + + entry_phase_space = sim.add_volume("Box","entry_phase_space") + entry_phase_space.mother = actor_box + entry_phase_space.size = [0.6*m,1*nm,0.6*m] + entry_phase_space.material = "G4_Galactic" + entry_phase_space.translation = [0,0.3*m - 0.5*nm,0] + entry_phase_space.color = [0.5,0.9,0.3,1] + + exit_phase_space = sim.add_volume("Box", "exit_phase_space") + exit_phase_space.mother = world.name + exit_phase_space.size = [0.6 * m, 0.6 * m, 1 * nm] + exit_phase_space.material = "G4_Galactic" + exit_phase_space.translation = [0, 0, -0.4 * m - 1 * nm] + exit_phase_space.color = [0.5, 0.9, 0.3, 1] + + # # print(sim.volume_manager.dump_volume_tree()) + liste_phase_space_name = [entry_phase_space.name,exit_phase_space.name,] + for name in liste_phase_space_name: + + phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_"+name) + phsp.mother = name + phsp.attributes = ["EventID","TrackID","Weight","PDGCode","TrackCreatorProcess"] + name_phsp = "test073_" +name+".root" + phsp.output = output_path / name_phsp + + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + sim.physics_manager.enable_decay = False + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1 * mm + sim.physics_manager.global_production_cuts.positron = 1 * mm + + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + + + # go ! + sim.run() + output = sim.output + stats = sim.output.get_actor("Stats") + print(stats) + + # + entry_phsp = uproot.open(str(output_path) + "/test073_" + liste_phase_space_name[0] + ".root" +":PhaseSpace_" + liste_phase_space_name[0]) + exit_phase_space = uproot.open(str(output_path) + "/test073_" + liste_phase_space_name[1] + ".root" + ":PhaseSpace_" + liste_phase_space_name[1]) + # + df_entry = entry_phsp.arrays() + df_exit = exit_phase_space.arrays() + # + is_ok = test073(df_entry, df_exit,geom_splitting.splitting_factor) + # + utility.test_ok(is_ok) \ No newline at end of file From 71e276da8607bc5eb47b8d345fb5da91f1f8dda7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:00:33 +0000 Subject: [PATCH 11/82] [pre-commit.ci] Automatic python and c++ formatting --- .../GateSurfaceSplittingActor.cpp | 84 +++++++++-------- .../opengate_lib/GateSurfaceSplittingActor.h | 6 +- .../pyGateSurfaceSplittingActor.cpp | 7 +- opengate/actors/miscactors.py | 8 +- opengate/managers.py | 4 +- ...73_geometrical_splitting_volume_surface.py | 93 ++++++++++++------- 6 files changed, 117 insertions(+), 85 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.cpp b/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.cpp index 53b88c804..1afbbf25d 100644 --- a/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.cpp @@ -6,28 +6,30 @@ ------------------------------------ -------------- */ #include "GateSurfaceSplittingActor.h" +#include "G4LogicalVolumeStore.hh" #include "G4ios.hh" #include "GateHelpers.h" #include "GateHelpersDict.h" -#include "G4LogicalVolumeStore.hh" GateSurfaceSplittingActor::GateSurfaceSplittingActor(py::dict &user_info) : GateVActor(user_info, false) { fActions.insert("StartSimulationAction"); fActions.insert("SteppingAction"); fActions.insert("PreUserTrackingAction"); - fMotherVolumeName = DictGetStr(user_info,"mother"); - fWeightThreshold = DictGetBool(user_info,"weight_threshold"); - fSplittingFactor = DictGetInt(user_info,"splitting_factor"); - fSplitEnteringParticles = DictGetBool(user_info,"split_entering_particles"); - fSplitExitingParticles = DictGetBool(user_info,"split_exiting_particles"); + fMotherVolumeName = DictGetStr(user_info, "mother"); + fWeightThreshold = DictGetBool(user_info, "weight_threshold"); + fSplittingFactor = DictGetInt(user_info, "splitting_factor"); + fSplitEnteringParticles = DictGetBool(user_info, "split_entering_particles"); + fSplitExitingParticles = DictGetBool(user_info, "split_exiting_particles"); } void GateSurfaceSplittingActor::ActorInitialize() {} -void GateSurfaceSplittingActor::StartSimulationAction() { fNbOfKilledParticles = 0; } +void GateSurfaceSplittingActor::StartSimulationAction() { + fNbOfKilledParticles = 0; +} -void GateSurfaceSplittingActor::PreUserTrackingAction(const G4Track* track){ +void GateSurfaceSplittingActor::PreUserTrackingAction(const G4Track *track) { fIsFirstStep = true; } @@ -36,51 +38,59 @@ void GateSurfaceSplittingActor::SteppingAction(G4Step *step) { auto track = step->GetTrack(); auto weight = track->GetWeight(); - - if (weight >= fWeightThreshold){ - if (fSplitEnteringParticles){ - G4String logicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - if (((fIsFirstStep) && (step->GetPreStepPoint()->GetStepStatus() ==1) && (logicalVolumeNamePreStep == fMotherVolumeName)) - || ((fIsFirstStep) && (track->GetLogicalVolumeAtVertex()->GetName() != logicalVolumeNamePreStep) && (track->GetLogicalVolumeAtVertex()->GetName() != fMotherVolumeName))) { + if (weight >= fWeightThreshold) { + if (fSplitEnteringParticles) { + G4String logicalVolumeNamePreStep = step->GetPreStepPoint() + ->GetPhysicalVolume() + ->GetLogicalVolume() + ->GetName(); + if (((fIsFirstStep) && (step->GetPreStepPoint()->GetStepStatus() == 1) && + (logicalVolumeNamePreStep == fMotherVolumeName)) || + ((fIsFirstStep) && + (track->GetLogicalVolumeAtVertex()->GetName() != + logicalVolumeNamePreStep) && + (track->GetLogicalVolumeAtVertex()->GetName() != + fMotherVolumeName))) { G4ThreeVector position = step->GetPreStepPoint()->GetPosition(); G4ThreeVector momentum = step->GetPreStepPoint()->GetMomentum(); G4double ekin = step->GetPreStepPoint()->GetKineticEnergy(); - - const G4DynamicParticle* particleType = track->GetDynamicParticle(); + + const G4DynamicParticle *particleType = track->GetDynamicParticle(); G4double time = step->GetPreStepPoint()->GetGlobalTime(); G4TrackVector *trackVector = step->GetfSecondary(); - - for (int i = 0; i < fSplittingFactor -1 ; i++) { - G4DynamicParticle* particleTypeToAdd = new G4DynamicParticle(*particleType); - G4Track* clone = new G4Track(particleTypeToAdd ,time,position); + + for (int i = 0; i < fSplittingFactor - 1; i++) { + G4DynamicParticle *particleTypeToAdd = + new G4DynamicParticle(*particleType); + G4Track *clone = new G4Track(particleTypeToAdd, time, position); clone->SetKineticEnergy(ekin); clone->SetMomentumDirection(momentum); - clone->SetWeight( weight/fSplittingFactor); + clone->SetWeight(weight / fSplittingFactor); trackVector->push_back(clone); } - step->GetPreStepPoint()->SetWeight(weight/fSplittingFactor); - step->GetPostStepPoint()->SetWeight(weight/fSplittingFactor); - track->SetWeight(weight/fSplittingFactor); - - + step->GetPreStepPoint()->SetWeight(weight / fSplittingFactor); + step->GetPostStepPoint()->SetWeight(weight / fSplittingFactor); + track->SetWeight(weight / fSplittingFactor); } } - - if(fSplitExitingParticles){ - G4String logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(),logicalVolumeNamePostStep) !=fListOfVolumeAncestor.end()){ + if (fSplitExitingParticles) { + G4String logicalVolumeNamePostStep = step->GetPostStepPoint() + ->GetPhysicalVolume() + ->GetLogicalVolume() + ->GetName(); + if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), + logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { G4TrackVector *trackVector = step->GetfSecondary(); - for (int i = 0; i < fSplittingFactor-1; i++) { - G4Track* clone = new G4Track(*track); - clone->SetWeight( weight/fSplittingFactor); + for (int i = 0; i < fSplittingFactor - 1; i++) { + G4Track *clone = new G4Track(*track); + clone->SetWeight(weight / fSplittingFactor); trackVector->push_back(clone); } - step->GetPostStepPoint()->SetWeight(weight/fSplittingFactor); - track->SetWeight(weight/fSplittingFactor); + step->GetPostStepPoint()->SetWeight(weight / fSplittingFactor); + track->SetWeight(weight / fSplittingFactor); } } - } - fIsFirstStep =false; + fIsFirstStep = false; } diff --git a/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.h b/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.h index 871342f7c..6adce44d1 100644 --- a/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.h @@ -29,16 +29,14 @@ class GateSurfaceSplittingActor : public GateVActor { void PreUserTrackingAction(const G4Track *) override; - - G4bool fSplitEnteringParticles =false; - G4bool fSplitExitingParticles =false ; + G4bool fSplitEnteringParticles = false; + G4bool fSplitExitingParticles = false; G4int fSplittingFactor; G4bool fIsFirstStep; G4bool fWeightThreshold; G4String fMotherVolumeName; std::vector<std::string> fListOfVolumeAncestor; - long fNbOfKilledParticles{}; }; diff --git a/core/opengate_core/opengate_lib/pyGateSurfaceSplittingActor.cpp b/core/opengate_core/opengate_lib/pyGateSurfaceSplittingActor.cpp index 984e7a937..0d6fc94e9 100644 --- a/core/opengate_core/opengate_lib/pyGateSurfaceSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateSurfaceSplittingActor.cpp @@ -12,8 +12,11 @@ namespace py = pybind11; #include "GateSurfaceSplittingActor.h" void init_GateSurfaceSplittingActor(py::module &m) { - py::class_<GateSurfaceSplittingActor, std::unique_ptr<GateSurfaceSplittingActor, py::nodelete>,GateVActor>(m, "GateSurfaceSplittingActor") + py::class_<GateSurfaceSplittingActor, + std::unique_ptr<GateSurfaceSplittingActor, py::nodelete>, + GateVActor>(m, "GateSurfaceSplittingActor") .def(py::init<py::dict &>()) - .def_readwrite("fListOfVolumeAncestor",&GateSurfaceSplittingActor::fListOfVolumeAncestor) + .def_readwrite("fListOfVolumeAncestor", + &GateSurfaceSplittingActor::fListOfVolumeAncestor) .def(py::init<py::dict &>()); } diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 48f148fde..54ef398f8 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -428,7 +428,8 @@ def set_default_user_info(user_info): def __init__(self, user_info): ActorBase.__init__(self, user_info) g4.GateKillActor.__init__(self, user_info.__dict__) - + + class SurfaceSplittingActor(g4.GateSurfaceSplittingActor, ActorBase): type_name = "SurfaceSplittingActor" @@ -445,8 +446,7 @@ def __init__(self, user_info): g4.GateSurfaceSplittingActor.__init__(self, user_info.__dict__) self.list_of_volume_name = user_info.list_of_volume_name self.user_info.mother = user_info.mother - - + def initialize(self, volume_engine=None): super().initialize(volume_engine) @@ -455,7 +455,7 @@ def initialize(self, volume_engine=None): for pre, _, node in RenderTree(volume_tree): dico_of_volume_tree[str(node.name)] = node volume_name = self.user_info.mother - while volume_name != 'world': + while volume_name != "world": node = dico_of_volume_tree[volume_name] volume_name = node.mother self.list_of_volume_name.append(volume_name) diff --git a/opengate/managers.py b/opengate/managers.py index bf3fca344..910e3be10 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -889,9 +889,9 @@ def dump_volume_types(self): def dump_material_database_names(self): return list(self.material_database.filenames) - + def get_volume_tree(self): - return (self.volume_tree_root) + return self.volume_tree_root def setter_hook_verbose_level(self, verbose_level): diff --git a/opengate/tests/src/test073_geometrical_splitting_volume_surface.py b/opengate/tests/src/test073_geometrical_splitting_volume_surface.py index f3b2d8278..30ea10d67 100644 --- a/opengate/tests/src/test073_geometrical_splitting_volume_surface.py +++ b/opengate/tests/src/test073_geometrical_splitting_volume_surface.py @@ -5,23 +5,30 @@ from opengate.tests import utility from scipy.spatial.transform import Rotation import numpy as np -from anytree import Node,RenderTree +from anytree import Node, RenderTree import uproot def test073(entry_data, exit_data, splitting_factor): - splitted_particle_data_entry = entry_data[entry_data["TrackCreatorProcess"] == "none"] + splitted_particle_data_entry = entry_data[ + entry_data["TrackCreatorProcess"] == "none" + ] splitted_particle_data_exit = exit_data[exit_data["TrackCreatorProcess"] == "none"] array_weight_1 = splitted_particle_data_entry["Weight"] array_weight_2 = splitted_particle_data_exit["Weight"] - - if (np.round(np.sum(array_weight_1),3) == 1) and len(array_weight_1) == splitting_factor: - if (np.round(np.sum(array_weight_2),3) == 1) and len(array_weight_2) == splitting_factor: + if (np.round(np.sum(array_weight_1), 3) == 1) and len( + array_weight_1 + ) == splitting_factor: + if (np.round(np.sum(array_weight_2), 3) == 1) and len( + array_weight_2 + ) == splitting_factor: return True - else : return False - else : return False + else: + return False + else: + return False if __name__ == "__main__": @@ -54,37 +61,33 @@ def test073(entry_data, exit_data, splitting_factor): sec = gate.g4_units.s gcm3 = gate.g4_units["g/cm3"] - # adapt world size world = sim.world world.size = [1 * m, 1 * m, 1 * m] world.material = "G4_Galactic" - big_box = sim.add_volume("Box","big_box") + big_box = sim.add_volume("Box", "big_box") big_box.mother = world.name big_box.material = "G4_Galactic" - big_box.size = [0.8*m,0.8*m,0.8*m] + big_box.size = [0.8 * m, 0.8 * m, 0.8 * m] actor_box = sim.add_volume("Box", "actor_box") actor_box.mother = big_box.name actor_box.material = "G4_Galactic" actor_box.size = [0.6 * m, 0.6 * m, 0.6 * m] - actor_box.translation = [0,0,-0.1*m] - - - + actor_box.translation = [0, 0, -0.1 * m] source_1 = sim.add_source("GenericSource", "elec_source_1") source_1.particle = "e-" source_1.position.type = "box" source_1.mother = big_box.name - source_1.position.size = [1*cm,1*cm,1*cm] - source_1.position.translation = [0,0.35*m,0] + source_1.position.size = [1 * cm, 1 * cm, 1 * cm] + source_1.position.translation = [0, 0.35 * m, 0] source_1.direction.type = "momentum" - source_1.direction.momentum = [0,-1,0] + source_1.direction.momentum = [0, -1, 0] source_1.energy.type = "mono" source_1.energy.mono = 10 * MeV - source_1.n= 1 + source_1.n = 1 source_2 = sim.add_source("GenericSource", "elec_source_2") source_2.particle = "e-" @@ -98,21 +101,19 @@ def test073(entry_data, exit_data, splitting_factor): source_2.energy.mono = 10 * MeV source_2.n = 1 - - geom_splitting = sim.add_actor("SurfaceSplittingActor","splitting_act") + geom_splitting = sim.add_actor("SurfaceSplittingActor", "splitting_act") geom_splitting.mother = actor_box.name geom_splitting.splitting_factor = 10 geom_splitting.weight_threshold = 1 geom_splitting.split_entering_particles = True geom_splitting.split_exiting_particles = True - - entry_phase_space = sim.add_volume("Box","entry_phase_space") + entry_phase_space = sim.add_volume("Box", "entry_phase_space") entry_phase_space.mother = actor_box - entry_phase_space.size = [0.6*m,1*nm,0.6*m] + entry_phase_space.size = [0.6 * m, 1 * nm, 0.6 * m] entry_phase_space.material = "G4_Galactic" - entry_phase_space.translation = [0,0.3*m - 0.5*nm,0] - entry_phase_space.color = [0.5,0.9,0.3,1] + entry_phase_space.translation = [0, 0.3 * m - 0.5 * nm, 0] + entry_phase_space.color = [0.5, 0.9, 0.3, 1] exit_phase_space = sim.add_volume("Box", "exit_phase_space") exit_phase_space.mother = world.name @@ -122,27 +123,33 @@ def test073(entry_data, exit_data, splitting_factor): exit_phase_space.color = [0.5, 0.9, 0.3, 1] # # print(sim.volume_manager.dump_volume_tree()) - liste_phase_space_name = [entry_phase_space.name,exit_phase_space.name,] + liste_phase_space_name = [ + entry_phase_space.name, + exit_phase_space.name, + ] for name in liste_phase_space_name: - phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_"+name) + phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_" + name) phsp.mother = name - phsp.attributes = ["EventID","TrackID","Weight","PDGCode","TrackCreatorProcess"] - name_phsp = "test073_" +name+".root" + phsp.attributes = [ + "EventID", + "TrackID", + "Weight", + "PDGCode", + "TrackCreatorProcess", + ] + name_phsp = "test073_" + name + ".root" phsp.output = output_path / name_phsp - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" sim.physics_manager.enable_decay = False sim.physics_manager.global_production_cuts.gamma = 1 * mm sim.physics_manager.global_production_cuts.electron = 1 * mm sim.physics_manager.global_production_cuts.positron = 1 * mm - s = sim.add_actor("SimulationStatisticsActor", "Stats") s.track_types_flag = True - # go ! sim.run() output = sim.output @@ -150,12 +157,26 @@ def test073(entry_data, exit_data, splitting_factor): print(stats) # - entry_phsp = uproot.open(str(output_path) + "/test073_" + liste_phase_space_name[0] + ".root" +":PhaseSpace_" + liste_phase_space_name[0]) - exit_phase_space = uproot.open(str(output_path) + "/test073_" + liste_phase_space_name[1] + ".root" + ":PhaseSpace_" + liste_phase_space_name[1]) + entry_phsp = uproot.open( + str(output_path) + + "/test073_" + + liste_phase_space_name[0] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[0] + ) + exit_phase_space = uproot.open( + str(output_path) + + "/test073_" + + liste_phase_space_name[1] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[1] + ) # df_entry = entry_phsp.arrays() df_exit = exit_phase_space.arrays() # - is_ok = test073(df_entry, df_exit,geom_splitting.splitting_factor) + is_ok = test073(df_entry, df_exit, geom_splitting.splitting_factor) # - utility.test_ok(is_ok) \ No newline at end of file + utility.test_ok(is_ok) From 01041fa5358e28ea179f98472ae26928e3209dc1 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Wed, 20 Mar 2024 16:08:50 +0100 Subject: [PATCH 12/82] Update of the test name --- ....py => test074_kill_non_interacting_particles.py} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename opengate/tests/src/{test072_kill_non_interacting_particles.py => test074_kill_non_interacting_particles.py} (94%) diff --git a/opengate/tests/src/test072_kill_non_interacting_particles.py b/opengate/tests/src/test074_kill_non_interacting_particles.py similarity index 94% rename from opengate/tests/src/test072_kill_non_interacting_particles.py rename to opengate/tests/src/test074_kill_non_interacting_particles.py index 9e3d55729..7653c20fd 100644 --- a/opengate/tests/src/test072_kill_non_interacting_particles.py +++ b/opengate/tests/src/test074_kill_non_interacting_particles.py @@ -13,7 +13,7 @@ -def test072_test(entry_data, exit_data_1,exit_data_2): +def test074_test(entry_data, exit_data_1,exit_data_2): liste_ekin =[] liste_evtID = [] liste_trackID = [] @@ -165,7 +165,7 @@ def test072_test(entry_data, exit_data_1,exit_data_2): phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_"+name) phsp.mother = name phsp.attributes = ["EventID","TrackID","KineticEnergy"] - name_phsp = "test072_" +name+".root" + name_phsp = "test074_" +name+".root" phsp.output = output_path / name_phsp @@ -187,15 +187,15 @@ def test072_test(entry_data, exit_data_1,exit_data_2): print(stats) - entry_phsp = uproot.open(str(output_path) + "/test072_" + liste_phase_space_name[0] + ".root" +":PhaseSpace_" + liste_phase_space_name[0]) - exit_phase_space_1 = uproot.open(str(output_path) + "/test072_" + liste_phase_space_name[1] + ".root" + ":PhaseSpace_" + liste_phase_space_name[1]) - exit_phase_space_2 = uproot.open( str(output_path) + "/test072_" + liste_phase_space_name[2] + ".root" + ":PhaseSpace_" + liste_phase_space_name[2]) + entry_phsp = uproot.open(str(output_path) + "/test074_" + liste_phase_space_name[0] + ".root" +":PhaseSpace_" + liste_phase_space_name[0]) + exit_phase_space_1 = uproot.open(str(output_path) + "/test074_" + liste_phase_space_name[1] + ".root" + ":PhaseSpace_" + liste_phase_space_name[1]) + exit_phase_space_2 = uproot.open( str(output_path) + "/test074_" + liste_phase_space_name[2] + ".root" + ":PhaseSpace_" + liste_phase_space_name[2]) df_entry = entry_phsp.arrays() df_exit_1 = exit_phase_space_1.arrays() df_exit_2 = exit_phase_space_2.arrays() - is_ok = test072_test(df_entry, df_exit_1, df_exit_2) + is_ok = test074_test(df_entry, df_exit_1, df_exit_2) utility.test_ok(is_ok) \ No newline at end of file From 1483f92c07a3f8936e1e9f8c1dabb501092d1a87 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Wed, 20 Mar 2024 16:12:31 +0100 Subject: [PATCH 13/82] Update of the test name --- ... test075_geometrical_splitting_volume_surface.py} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename opengate/tests/src/{test073_geometrical_splitting_volume_surface.py => test075_geometrical_splitting_volume_surface.py} (94%) diff --git a/opengate/tests/src/test073_geometrical_splitting_volume_surface.py b/opengate/tests/src/test075_geometrical_splitting_volume_surface.py similarity index 94% rename from opengate/tests/src/test073_geometrical_splitting_volume_surface.py rename to opengate/tests/src/test075_geometrical_splitting_volume_surface.py index f3b2d8278..5c7e044eb 100644 --- a/opengate/tests/src/test073_geometrical_splitting_volume_surface.py +++ b/opengate/tests/src/test075_geometrical_splitting_volume_surface.py @@ -9,7 +9,7 @@ import uproot -def test073(entry_data, exit_data, splitting_factor): +def test075(entry_data, exit_data, splitting_factor): splitted_particle_data_entry = entry_data[entry_data["TrackCreatorProcess"] == "none"] splitted_particle_data_exit = exit_data[exit_data["TrackCreatorProcess"] == "none"] @@ -35,7 +35,7 @@ def test073(entry_data, exit_data, splitting_factor): # main options ui = sim.user_info ui.g4_verbose = False - ui.visu = True + # ui.visu = True ui.visu_type = "vrml" ui.check_volumes_overlap = False # ui.running_verbose_level = gate.logger.EVENT @@ -128,7 +128,7 @@ def test073(entry_data, exit_data, splitting_factor): phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_"+name) phsp.mother = name phsp.attributes = ["EventID","TrackID","Weight","PDGCode","TrackCreatorProcess"] - name_phsp = "test073_" +name+".root" + name_phsp = "test075_" +name+".root" phsp.output = output_path / name_phsp @@ -150,12 +150,12 @@ def test073(entry_data, exit_data, splitting_factor): print(stats) # - entry_phsp = uproot.open(str(output_path) + "/test073_" + liste_phase_space_name[0] + ".root" +":PhaseSpace_" + liste_phase_space_name[0]) - exit_phase_space = uproot.open(str(output_path) + "/test073_" + liste_phase_space_name[1] + ".root" + ":PhaseSpace_" + liste_phase_space_name[1]) + entry_phsp = uproot.open(str(output_path) + "/test075_" + liste_phase_space_name[0] + ".root" +":PhaseSpace_" + liste_phase_space_name[0]) + exit_phase_space = uproot.open(str(output_path) + "/test075_" + liste_phase_space_name[1] + ".root" + ":PhaseSpace_" + liste_phase_space_name[1]) # df_entry = entry_phsp.arrays() df_exit = exit_phase_space.arrays() # - is_ok = test073(df_entry, df_exit,geom_splitting.splitting_factor) + is_ok = test075(df_entry, df_exit,geom_splitting.splitting_factor) # utility.test_ok(is_ok) \ No newline at end of file From 32ae380c2978dd41f8486c422d3362071043f071 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Fri, 19 Apr 2024 15:10:57 +0200 Subject: [PATCH 14/82] work update --- .../GateKillNonInteractingParticleActor.cpp | 20 ++++++++-------- ...GateOptrComptPseudoTransportationActor.cpp | 23 ------------------- .../GateOptrComptPseudoTransportationActor.h | 1 - 3 files changed, 10 insertions(+), 34 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp index b71198e0e..bd264d37b 100644 --- a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp +++ b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp @@ -40,6 +40,16 @@ void GateKillNonInteractingParticleActor::PreUserTrackingAction(const G4Track* t void GateKillNonInteractingParticleActor::SteppingAction(G4Step *step) { + G4String logNameMotherVolume = G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName)->GetName(); + G4String physicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetName(); + if ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != logNameMotherVolume) && (fIsFirstStep)){ + if ((fPassedByTheMotherVolume == false) && (physicalVolumeNamePreStep == fMotherVolumeName) && (step->GetPreStepPoint()->GetStepStatus() == 1)){ + fPassedByTheMotherVolume =true; + fKineticEnergyAtTheEntrance = step->GetPreStepPoint()->GetKineticEnergy(); + ftrackIDAtTheEntrance = step->GetTrack()->GetTrackID(); + } + } + G4String logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); if ((fPassedByTheMotherVolume) && (step->GetPostStepPoint()->GetStepStatus() == 1)){ if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(),logicalVolumeNamePostStep ) !=fListOfVolumeAncestor.end()){ @@ -50,16 +60,6 @@ void GateKillNonInteractingParticleActor::SteppingAction(G4Step *step) { } } } - - G4String logNameMotherVolume = G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName)->GetName(); - G4String physicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetName(); - if ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != logNameMotherVolume) && (fIsFirstStep)){ - if ((fPassedByTheMotherVolume == false) && (physicalVolumeNamePreStep == fMotherVolumeName) && (step->GetPreStepPoint()->GetStepStatus() == 1)){ - fPassedByTheMotherVolume =true; - fKineticEnergyAtTheEntrance = step->GetPreStepPoint()->GetKineticEnergy(); - ftrackIDAtTheEntrance = step->GetTrack()->GetTrackID(); - } - } fIsFirstStep = false; } diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp index e7a2d1e5a..141d73b77 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp @@ -172,29 +172,6 @@ void GateOptrComptPseudoTransportationActor::SteppingAction(G4Step *step) { isSplitted = false; } } - - - if (fKillPrimaries) { - - G4String LogicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - G4String LogicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - if (fPassedByABiasedVolume == false){ - if (LogicalVolumeNamePreStep == fMotherVolumeName){ - fPassedByABiasedVolume =true; - fKineticEnergyAtTheEntrance = step->GetPreStepPoint()->GetKineticEnergy(); - ftrackIDAtTheEntrance = step->GetTrack()->GetTrackID(); - } - } - - if ((fPassedByABiasedVolume)){ - if ((step->GetTrack()->GetTrackID() == ftrackIDAtTheEntrance) && (step->GetPostStepPoint()->GetKineticEnergy() == fKineticEnergyAtTheEntrance)){ - if ((!(std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeNamePostStep ) !=fNameOfBiasedLogicalVolume.end())) && (LogicalVolumeNamePostStep != fMotherVolumeName)){ - //std::cout<<fEventID<<" "<<LogicalVolumeNamePostStep<<std::endl; - step->GetTrack()->SetTrackStatus(G4TrackStatus::fStopAndKill); - } - } - } - } } void GateOptrComptPseudoTransportationActor::BeginOfEventAction( diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h index d3871d079..d60e88ad9 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h @@ -80,7 +80,6 @@ class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, G4bool fUseProbes = false; G4bool fSurvivedRR = false; G4bool fAttachToLogicalHolder = true; - G4bool fKillPrimaries = true; G4bool fPassedByABiasedVolume= false; G4double fKineticEnergyAtTheEntrance; G4int ftrackIDAtTheEntrance; From 8abfb780941736b29100480cacd559df91eca506 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Fri, 19 Apr 2024 15:12:03 +0200 Subject: [PATCH 15/82] Update of work --- opengate/actors/miscactors.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 20bffa9c6..297892e0b 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -430,7 +430,6 @@ def __init__(self, user_info): g4.GateKillActor.__init__(self, user_info.__dict__) -<<<<<<< HEAD class ComptSplittingActor(g4.GateOptrComptSplittingActor, ActorBase): type_name = "ComptSplittingActor" @@ -553,4 +552,4 @@ def initialize(self, volume_engine=None): node = dico_of_volume_tree[volume_name] volume_name = node.mother self.list_of_volume_name.append(volume_name) - self.fListOfVolumeAncestor = self.list_of_volume_name \ No newline at end of file + self.fListOfVolumeAncestor = self.list_of_volume_name From ecf35b33376bac54d43f4c85252c913126fd84e4 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Sat, 20 Apr 2024 17:40:32 +0200 Subject: [PATCH 16/82] correction of a bug after the merge --- opengate/actors/miscactors.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 297892e0b..fc2a53541 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -529,16 +529,17 @@ class SurfaceSplittingActor(g4.GateSurfaceSplittingActor, ActorBase): def set_default_user_info(user_info): ActorBase.set_default_user_info(user_info) user_info.list_of_volume_name = [] + user_info.splitting_factor = 1 + user_info.split_entering_particles = False + user_info.split_exiting_particles = False + user_info.weight_threshold = 0 def __init__(self, user_info): ActorBase.__init__(self, user_info) g4.GateSurfaceSplittingActor.__init__(self, user_info.__dict__) self.list_of_volume_name = user_info.list_of_volume_name self.user_info.mother = user_info.mother - user_info.splitting_factor = 1 - user_info.split_entering_particles = False - user_info.split_exiting_particles = False - user_info.weight_threshold = 0 + def initialize(self, volume_engine=None): From 89213352448e9de47a83004270bd9f937003cd46 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 20 Apr 2024 15:41:03 +0000 Subject: [PATCH 17/82] [pre-commit.ci] Automatic python and c++ formatting --- .../GateKillNonInteractingParticleActor.cpp | 58 ++++--- .../GateKillNonInteractingParticleActor.h | 5 +- .../GateOptnPairProdSplitting.cpp | 49 +++--- .../opengate_lib/GateOptnPairProdSplitting.h | 7 +- .../GateOptnScatteredGammaSplitting.cpp | 63 ++++---- .../GateOptnScatteredGammaSplitting.h | 3 - .../GateOptnVGenericSplitting.cpp | 70 ++++---- .../opengate_lib/GateOptnVGenericSplitting.h | 23 ++- .../opengate_lib/GateOptneBremSplitting.cpp | 69 ++++---- .../opengate_lib/GateOptneBremSplitting.h | 4 +- ...GateOptrComptPseudoTransportationActor.cpp | 149 ++++++++++-------- .../GateOptrComptPseudoTransportationActor.h | 20 +-- .../pyGateKillNonInteractingParticleActor.cpp | 7 +- opengate/actors/miscactors.py | 17 +- opengate/managers.py | 4 +- .../test074_kill_non_interacting_particles.py | 135 ++++++++-------- ...75_geometrical_splitting_volume_surface.py | 36 ++++- 17 files changed, 396 insertions(+), 323 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp index bd264d37b..5a6b544c7 100644 --- a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp +++ b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp @@ -6,60 +6,70 @@ ------------------------------------ -------------- */ #include "GateKillNonInteractingParticleActor.h" +#include "G4LogicalVolumeStore.hh" +#include "G4PhysicalVolumeStore.hh" #include "G4ios.hh" #include "GateHelpers.h" #include "GateHelpersDict.h" -#include "G4PhysicalVolumeStore.hh" -#include "G4LogicalVolumeStore.hh" - -GateKillNonInteractingParticleActor::GateKillNonInteractingParticleActor(py::dict &user_info) +GateKillNonInteractingParticleActor::GateKillNonInteractingParticleActor( + py::dict &user_info) : GateVActor(user_info, false) { fActions.insert("StartSimulationAction"); fActions.insert("SteppingAction"); fActions.insert("PreUserTrackingAction"); } - - - void GateKillNonInteractingParticleActor::ActorInitialize() {} void GateKillNonInteractingParticleActor::StartSimulationAction() { - fNbOfKilledParticles = 0; + fNbOfKilledParticles = 0; } - -void GateKillNonInteractingParticleActor::PreUserTrackingAction(const G4Track* track) { +void GateKillNonInteractingParticleActor::PreUserTrackingAction( + const G4Track *track) { fIsFirstStep = true; fKineticEnergyAtTheEntrance = 0; ftrackIDAtTheEntrance = 0; fPassedByTheMotherVolume = false; - } void GateKillNonInteractingParticleActor::SteppingAction(G4Step *step) { - G4String logNameMotherVolume = G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName)->GetName(); - G4String physicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetName(); - if ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != logNameMotherVolume) && (fIsFirstStep)){ - if ((fPassedByTheMotherVolume == false) && (physicalVolumeNamePreStep == fMotherVolumeName) && (step->GetPreStepPoint()->GetStepStatus() == 1)){ - fPassedByTheMotherVolume =true; - fKineticEnergyAtTheEntrance = step->GetPreStepPoint()->GetKineticEnergy(); - ftrackIDAtTheEntrance = step->GetTrack()->GetTrackID(); + G4String logNameMotherVolume = G4LogicalVolumeStore::GetInstance() + ->GetVolume(fMotherVolumeName) + ->GetName(); + G4String physicalVolumeNamePreStep = + step->GetPreStepPoint()->GetPhysicalVolume()->GetName(); + if ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != + logNameMotherVolume) && + (fIsFirstStep)) { + if ((fPassedByTheMotherVolume == false) && + (physicalVolumeNamePreStep == fMotherVolumeName) && + (step->GetPreStepPoint()->GetStepStatus() == 1)) { + fPassedByTheMotherVolume = true; + fKineticEnergyAtTheEntrance = step->GetPreStepPoint()->GetKineticEnergy(); + ftrackIDAtTheEntrance = step->GetTrack()->GetTrackID(); } } - G4String logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - if ((fPassedByTheMotherVolume) && (step->GetPostStepPoint()->GetStepStatus() == 1)){ - if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(),logicalVolumeNamePostStep ) !=fListOfVolumeAncestor.end()){ - if ((step->GetTrack()->GetTrackID() == ftrackIDAtTheEntrance) && (step->GetPostStepPoint()->GetKineticEnergy() == fKineticEnergyAtTheEntrance)){ + G4String logicalVolumeNamePostStep = step->GetPostStepPoint() + ->GetPhysicalVolume() + ->GetLogicalVolume() + ->GetName(); + if ((fPassedByTheMotherVolume) && + (step->GetPostStepPoint()->GetStepStatus() == 1)) { + if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), + logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { + if ((step->GetTrack()->GetTrackID() == ftrackIDAtTheEntrance) && + (step->GetPostStepPoint()->GetKineticEnergy() == + fKineticEnergyAtTheEntrance)) { auto track = step->GetTrack(); track->SetTrackStatus(fStopAndKill); fNbOfKilledParticles++; + } } } -} - + fIsFirstStep = false; } diff --git a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.h b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.h index a7cf58ccb..bb7aabe7a 100644 --- a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.h +++ b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.h @@ -27,16 +27,15 @@ class GateKillNonInteractingParticleActor : public GateVActor { // Main function called every step in attached volume void SteppingAction(G4Step *) override; - void PreUserTrackingAction(const G4Track*) override; + void PreUserTrackingAction(const G4Track *) override; std::vector<G4String> fParticlesTypeToKill; - G4bool fPassedByTheMotherVolume= false; + G4bool fPassedByTheMotherVolume = false; G4double fKineticEnergyAtTheEntrance = 0; G4int ftrackIDAtTheEntrance = 0; G4bool fIsFirstStep = true; std::vector<std::string> fListOfVolumeAncestor; - long fNbOfKilledParticles{}; }; diff --git a/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp index 767f8a1be..cf30c3a7d 100644 --- a/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp +++ b/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp @@ -49,54 +49,49 @@ //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptnPairProdSplitting:: - GateOptnPairProdSplitting(G4String name) +GateOptnPairProdSplitting::GateOptnPairProdSplitting(G4String name) : GateOptnVGenericSplitting(name), fParticleChange() {} //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptnPairProdSplitting:: - ~GateOptnPairProdSplitting() {} +GateOptnPairProdSplitting::~GateOptnPairProdSplitting() {} -G4VParticleChange * -GateOptnPairProdSplitting::ApplyFinalStateBiasing( const G4BiasingProcessInterface *callingProcess, const G4Track *track, const G4Step *step, G4bool &) { +G4VParticleChange *GateOptnPairProdSplitting::ApplyFinalStateBiasing( + const G4BiasingProcessInterface *callingProcess, const G4Track *track, + const G4Step *step, G4bool &) { - G4int splittingFactor = ceil(fSplittingFactor); - G4double survivalProbabilitySplitting = 1 - (splittingFactor - fSplittingFactor) / splittingFactor; + G4double survivalProbabilitySplitting = + 1 - (splittingFactor - fSplittingFactor) / splittingFactor; G4double particleWeight = 0; - G4VParticleChange* processFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + G4VParticleChange *processFinalState = + callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - if (fSplittingFactor == 1 && fRussianRouletteForAngle == false) return processFinalState; - TrackInitializationGamma(&fParticleChange,processFinalState,track,fSplittingFactor); + if (fSplittingFactor == 1 && fRussianRouletteForAngle == false) + return processFinalState; + TrackInitializationGamma(&fParticleChange, processFinalState, track, + fSplittingFactor); processFinalState->Clear(); G4int nCalls = 1; while (nCalls <= splittingFactor) { G4double splittingProbability = G4UniformRand(); - if (splittingProbability <= survivalProbabilitySplitting || survivalProbabilitySplitting == 1) { + if (splittingProbability <= survivalProbabilitySplitting || + survivalProbabilitySplitting == 1) { particleWeight = track->GetWeight() / fSplittingFactor; - G4VParticleChange *processFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - if (processFinalState->GetNumberOfSecondaries() >= 1 ) { - for(int i =0; i < processFinalState->GetNumberOfSecondaries();i++){ - G4Track *SecondaryTrack =processFinalState->GetSecondary(i); + G4VParticleChange *processFinalState = + callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + if (processFinalState->GetNumberOfSecondaries() >= 1) { + for (int i = 0; i < processFinalState->GetNumberOfSecondaries(); i++) { + G4Track *SecondaryTrack = processFinalState->GetSecondary(i); SecondaryTrack->SetWeight(particleWeight); fParticleChange.AddSecondary(SecondaryTrack); - } - } + } + } } nCalls++; } return &fParticleChange; } - - - - - - - - - diff --git a/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h b/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h index fd1f172c5..ef4128a81 100644 --- a/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h +++ b/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h @@ -31,8 +31,8 @@ #ifndef GateOptnPairProdSplitting_h #define GateOptnPairProdSplitting_h 1 -#include "GateOptnVGenericSplitting.h" #include "G4ParticleChange.hh" +#include "GateOptnVGenericSplitting.h" class GateOptnPairProdSplitting : public GateOptnVGenericSplitting { public: @@ -42,12 +42,11 @@ class GateOptnPairProdSplitting : public GateOptnVGenericSplitting { // -- destructor: virtual ~GateOptnPairProdSplitting(); - virtual G4VParticleChange * + virtual G4VParticleChange * ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, const G4Step *, G4bool &); -G4ParticleChange fParticleChange; - + G4ParticleChange fParticleChange; }; #endif diff --git a/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp index 8dbb41be6..bbfe8acff 100644 --- a/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp +++ b/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp @@ -37,7 +37,6 @@ #include "G4GammaConversion.hh" #include "G4ParticleChange.hh" #include "G4ParticleChangeForGamma.hh" -#include "GateOptnVGenericSplitting.h" #include "G4PhotoElectricEffect.hh" #include "G4ProcessType.hh" #include "G4RayleighScattering.hh" @@ -45,21 +44,19 @@ #include "G4TrackStatus.hh" #include "G4TrackingManager.hh" #include "G4VEmProcess.hh" +#include "GateOptnVGenericSplitting.h" #include <memory> //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptnScatteredGammaSplitting:: - GateOptnScatteredGammaSplitting(G4String name) - : GateOptnVGenericSplitting(name),fParticleChange() {} +GateOptnScatteredGammaSplitting::GateOptnScatteredGammaSplitting(G4String name) + : GateOptnVGenericSplitting(name), fParticleChange() {} //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptnScatteredGammaSplitting:: - ~GateOptnScatteredGammaSplitting() {} +GateOptnScatteredGammaSplitting::~GateOptnScatteredGammaSplitting() {} -G4VParticleChange * -GateOptnScatteredGammaSplitting::ApplyFinalStateBiasing( +G4VParticleChange *GateOptnScatteredGammaSplitting::ApplyFinalStateBiasing( const G4BiasingProcessInterface *callingProcess, const G4Track *track, const G4Step *step, G4bool &) { @@ -70,21 +67,22 @@ GateOptnScatteredGammaSplitting::ApplyFinalStateBiasing( const G4ThreeVector position = step->GetPostStepPoint()->GetPosition(); G4int splittingFactor = ceil(fSplittingFactor); - G4double survivalProbabilitySplitting = 1 - (splittingFactor - fSplittingFactor) / splittingFactor; + G4double survivalProbabilitySplitting = + 1 - (splittingFactor - fSplittingFactor) / splittingFactor; G4double gammaWeight = 0; - - - G4VParticleChange* processFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + G4VParticleChange *processFinalState = + callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); // In case we don't want to split (a bit faster) i.e no biaising or no // splitting low weights particles. - if (fSplittingFactor == 1 && fRussianRouletteForAngle == false) return processFinalState; + if (fSplittingFactor == 1 && fRussianRouletteForAngle == false) + return processFinalState; - TrackInitializationGamma(&fParticleChange,processFinalState,track,fSplittingFactor); + TrackInitializationGamma(&fParticleChange, processFinalState, track, + fSplittingFactor); processFinalState->Clear(); - // There is here the biasing process : // Since G4VParticleChange class does not allow to retrieve scattered gamma // information, we need to cast the type G4ParticleChangeForGamma to the @@ -105,20 +103,27 @@ GateOptnScatteredGammaSplitting::ApplyFinalStateBiasing( // gamma) must be considered as secondary particles, even though generated // gamma will not be cut here by the applied cut. - G4int nCalls = 1; while (nCalls <= splittingFactor) { gammaWeight = track->GetWeight() / fSplittingFactor; - G4VParticleChange *processGammaSplittedFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - G4ParticleChangeForGamma *castedProcessGammaSplittedFinalState = (G4ParticleChangeForGamma *)processGammaSplittedFinalState; - const G4ThreeVector momentum = castedProcessGammaSplittedFinalState->GetProposedMomentumDirection(); - G4double energy = castedProcessGammaSplittedFinalState->GetProposedKineticEnergy(); + G4VParticleChange *processGammaSplittedFinalState = + callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + G4ParticleChangeForGamma *castedProcessGammaSplittedFinalState = + (G4ParticleChangeForGamma *)processGammaSplittedFinalState; + const G4ThreeVector momentum = + castedProcessGammaSplittedFinalState->GetProposedMomentumDirection(); + G4double energy = + castedProcessGammaSplittedFinalState->GetProposedKineticEnergy(); G4double splittingProbability = G4UniformRand(); - if (splittingProbability <= survivalProbabilitySplitting || survivalProbabilitySplitting == 1) { - if (fRussianRouletteForAngle == true){ - G4double weightToApply = RussianRouletteForAngleSurvival(castedProcessGammaSplittedFinalState->GetProposedMomentumDirection(),fVectorDirector,fMaxTheta,fSplittingFactor); - if (weightToApply != 0){ + if (splittingProbability <= survivalProbabilitySplitting || + survivalProbabilitySplitting == 1) { + if (fRussianRouletteForAngle == true) { + G4double weightToApply = RussianRouletteForAngleSurvival( + castedProcessGammaSplittedFinalState + ->GetProposedMomentumDirection(), + fVectorDirector, fMaxTheta, fSplittingFactor); + if (weightToApply != 0) { gammaWeight = gammaWeight * weightToApply; G4Track *gammaTrack = new G4Track(*track); gammaTrack->SetWeight(gammaWeight); @@ -127,15 +132,15 @@ GateOptnScatteredGammaSplitting::ApplyFinalStateBiasing( gammaTrack->SetPosition(position); fParticleChange.AddSecondary(gammaTrack); if (processGammaSplittedFinalState->GetNumberOfSecondaries() == 1) { - G4Track *electronTrack = processGammaSplittedFinalState->GetSecondary(0); + G4Track *electronTrack = + processGammaSplittedFinalState->GetSecondary(0); electronTrack->SetWeight(gammaWeight); fParticleChange.AddSecondary(electronTrack); } } } - - else{ + else { G4Track *gammaTrack = new G4Track(*track); gammaTrack->SetWeight(gammaWeight); gammaTrack->SetKineticEnergy(energy); @@ -143,10 +148,10 @@ GateOptnScatteredGammaSplitting::ApplyFinalStateBiasing( gammaTrack->SetPosition(position); fParticleChange.AddSecondary(gammaTrack); if (processGammaSplittedFinalState->GetNumberOfSecondaries() == 1) { - G4Track *electronTrack = processGammaSplittedFinalState->GetSecondary(0); + G4Track *electronTrack = + processGammaSplittedFinalState->GetSecondary(0); electronTrack->SetWeight(gammaWeight); fParticleChange.AddSecondary(electronTrack); - } } } diff --git a/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h b/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h index 56a6650cc..ff0a6b623 100644 --- a/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h +++ b/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h @@ -43,14 +43,11 @@ class GateOptnScatteredGammaSplitting : public GateOptnVGenericSplitting { // -- destructor: virtual ~GateOptnScatteredGammaSplitting(); - virtual G4VParticleChange * ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, const G4Step *, G4bool &); G4ParticleChange fParticleChange; - - }; #endif diff --git a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp index 349a393ff..63f673ddf 100644 --- a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp +++ b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp @@ -49,57 +49,63 @@ //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptnVGenericSplitting:: - GateOptnVGenericSplitting(G4String name) +GateOptnVGenericSplitting::GateOptnVGenericSplitting(G4String name) : G4VBiasingOperation(name), fParticleChange() {} //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptnVGenericSplitting:: - ~GateOptnVGenericSplitting() {} +GateOptnVGenericSplitting::~GateOptnVGenericSplitting() {} +void GateOptnVGenericSplitting::TrackInitializationChargedParticle( + G4ParticleChange *particleChange, G4VParticleChange *processFinalState, + const G4Track *track, G4double split) { -void GateOptnVGenericSplitting::TrackInitializationChargedParticle(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split) { - - G4ParticleChangeForLoss* processFinalStateForLoss =( G4ParticleChangeForLoss* ) processFinalState ; + G4ParticleChangeForLoss *processFinalStateForLoss = + (G4ParticleChangeForLoss *)processFinalState; particleChange->Initialize(*track); - particleChange->ProposeTrackStatus(processFinalStateForLoss->GetTrackStatus() ); - particleChange->ProposeEnergy(processFinalStateForLoss->GetProposedKineticEnergy() ); - particleChange->ProposeMomentumDirection(processFinalStateForLoss->GetProposedMomentumDirection()); + particleChange->ProposeTrackStatus( + processFinalStateForLoss->GetTrackStatus()); + particleChange->ProposeEnergy( + processFinalStateForLoss->GetProposedKineticEnergy()); + particleChange->ProposeMomentumDirection( + processFinalStateForLoss->GetProposedMomentumDirection()); particleChange->SetNumberOfSecondaries(fSplittingFactor); particleChange->SetSecondaryWeightByProcess(true); processFinalStateForLoss->Clear(); } -void GateOptnVGenericSplitting::TrackInitializationGamma(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split) { - G4ParticleChangeForGamma* processFinalStateForGamma = (G4ParticleChangeForGamma *)processFinalState; +void GateOptnVGenericSplitting::TrackInitializationGamma( + G4ParticleChange *particleChange, G4VParticleChange *processFinalState, + const G4Track *track, G4double split) { + G4ParticleChangeForGamma *processFinalStateForGamma = + (G4ParticleChangeForGamma *)processFinalState; particleChange->Initialize(*track); - particleChange->ProposeTrackStatus(processFinalStateForGamma->GetTrackStatus() ); - particleChange->ProposeEnergy(processFinalStateForGamma->GetProposedKineticEnergy() ); - particleChange->ProposeMomentumDirection(processFinalStateForGamma->GetProposedMomentumDirection() ); + particleChange->ProposeTrackStatus( + processFinalStateForGamma->GetTrackStatus()); + particleChange->ProposeEnergy( + processFinalStateForGamma->GetProposedKineticEnergy()); + particleChange->ProposeMomentumDirection( + processFinalStateForGamma->GetProposedMomentumDirection()); particleChange->SetNumberOfSecondaries(fSplittingFactor); particleChange->SetSecondaryWeightByProcess(true); processFinalStateForGamma->Clear(); - - } -G4double GateOptnVGenericSplitting::RussianRouletteForAngleSurvival(G4ThreeVector dir,G4ThreeVector vectorDirector,G4double maxTheta,G4double split){ -G4double cosTheta =vectorDirector * dir; -G4double theta = std::acos(cosTheta); -G4double weightToApply = 1; -if (theta > fMaxTheta){ - G4double probability = G4UniformRand(); - if (probability <= 1 / split) { - weightToApply = split; +G4double GateOptnVGenericSplitting::RussianRouletteForAngleSurvival( + G4ThreeVector dir, G4ThreeVector vectorDirector, G4double maxTheta, + G4double split) { + G4double cosTheta = vectorDirector * dir; + G4double theta = std::acos(cosTheta); + G4double weightToApply = 1; + if (theta > fMaxTheta) { + G4double probability = G4UniformRand(); + if (probability <= 1 / split) { + weightToApply = split; + } else { + G4double weightToApply = 0; + } } - else{ - G4double weightToApply = 0; - } -} -return weightToApply; - + return weightToApply; } - //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h index ed75c9edd..b27819519 100644 --- a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h +++ b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h @@ -56,7 +56,9 @@ class GateOptnVGenericSplitting : public G4VBiasingOperation { // --Used: virtual G4VParticleChange * ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, - const G4Step *, G4bool &){return 0;}; + const G4Step *, G4bool &) { + return 0; + }; // -- Unsued: virtual G4double DistanceToApplyOperation(const G4Track *, G4double, @@ -68,14 +70,19 @@ class GateOptnVGenericSplitting : public G4VBiasingOperation { return 0; } -// ---------------------------------------------- -// -- Methods for the generic splitting -// ---------------------------------------------- - -void TrackInitializationChargedParticle(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split); -void TrackInitializationGamma(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split); -G4double RussianRouletteForAngleSurvival(G4ThreeVector dir,G4ThreeVector vectorDirector,G4double maxTheta,G4double split); + // ---------------------------------------------- + // -- Methods for the generic splitting + // ---------------------------------------------- + void TrackInitializationChargedParticle(G4ParticleChange *particleChange, + G4VParticleChange *processFinalState, + const G4Track *track, G4double split); + void TrackInitializationGamma(G4ParticleChange *particleChange, + G4VParticleChange *processFinalState, + const G4Track *track, G4double split); + G4double RussianRouletteForAngleSurvival(G4ThreeVector dir, + G4ThreeVector vectorDirector, + G4double maxTheta, G4double split); public: // ---------------------------------------------- diff --git a/core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp b/core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp index f067220f9..ce9ff2324 100644 --- a/core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp +++ b/core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp @@ -36,7 +36,6 @@ #include "G4Gamma.hh" #include "G4GammaConversion.hh" #include "G4ParticleChange.hh" -#include "GateOptnVGenericSplitting.h" #include "G4ParticleChangeForGamma.hh" #include "G4ParticleChangeForLoss.hh" #include "G4PhotoElectricEffect.hh" @@ -46,53 +45,59 @@ #include "G4TrackStatus.hh" #include "G4TrackingManager.hh" #include "G4VEmProcess.hh" +#include "GateOptnVGenericSplitting.h" #include <memory> //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptneBremSplitting:: - GateOptneBremSplitting(G4String name) - :GateOptnVGenericSplitting(name), fParticleChange() {} +GateOptneBremSplitting::GateOptneBremSplitting(G4String name) + : GateOptnVGenericSplitting(name), fParticleChange() {} //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptneBremSplitting:: - ~GateOptneBremSplitting() {} +GateOptneBremSplitting::~GateOptneBremSplitting() {} -G4VParticleChange * -GateOptneBremSplitting::ApplyFinalStateBiasing(const G4BiasingProcessInterface *callingProcess, const G4Track *track, const G4Step *step, G4bool &) { +G4VParticleChange *GateOptneBremSplitting::ApplyFinalStateBiasing( + const G4BiasingProcessInterface *callingProcess, const G4Track *track, + const G4Step *step, G4bool &) { G4int splittingFactor = ceil(fSplittingFactor); - G4double survivalProbabilitySplitting = 1 - (splittingFactor - fSplittingFactor) / splittingFactor; - G4VParticleChange* processFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - if ( fSplittingFactor == 1 ) return processFinalState; - if ( processFinalState->GetNumberOfSecondaries() == 0 ) return processFinalState; - - TrackInitializationChargedParticle(&fParticleChange,processFinalState, track,fSplittingFactor); - + G4double survivalProbabilitySplitting = + 1 - (splittingFactor - fSplittingFactor) / splittingFactor; + G4VParticleChange *processFinalState = + callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + if (fSplittingFactor == 1) + return processFinalState; + if (processFinalState->GetNumberOfSecondaries() == 0) + return processFinalState; - processFinalState->Clear(); + TrackInitializationChargedParticle(&fParticleChange, processFinalState, track, + fSplittingFactor); + processFinalState->Clear(); G4int nCalls = 1; - while ( nCalls <= fSplittingFactor ){ + while (nCalls <= fSplittingFactor) { G4double splittingProbability = G4UniformRand(); - if (splittingProbability <= survivalProbabilitySplitting || survivalProbabilitySplitting == 1) { - processFinalState = callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - if ( processFinalState->GetNumberOfSecondaries() >= 1 ) { - for(int i =0; i < processFinalState->GetNumberOfSecondaries();i++){ + if (splittingProbability <= survivalProbabilitySplitting || + survivalProbabilitySplitting == 1) { + processFinalState = + callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + if (processFinalState->GetNumberOfSecondaries() >= 1) { + for (int i = 0; i < processFinalState->GetNumberOfSecondaries(); i++) { G4double gammaWeight = track->GetWeight() / fSplittingFactor; - G4Track* gammaTrack = processFinalState->GetSecondary(i); - if (fRussianRouletteForAngle == true){ - G4double weightToApply = RussianRouletteForAngleSurvival(gammaTrack->GetMomentumDirection(),fVectorDirector,fMaxTheta,fSplittingFactor); - if (weightToApply != 0){ + G4Track *gammaTrack = processFinalState->GetSecondary(i); + if (fRussianRouletteForAngle == true) { + G4double weightToApply = RussianRouletteForAngleSurvival( + gammaTrack->GetMomentumDirection(), fVectorDirector, fMaxTheta, + fSplittingFactor); + if (weightToApply != 0) { gammaWeight = gammaWeight * weightToApply; - gammaTrack->SetWeight( gammaWeight); - fParticleChange.AddSecondary( gammaTrack ); + gammaTrack->SetWeight(gammaWeight); + fParticleChange.AddSecondary(gammaTrack); } - } - else { - gammaTrack->SetWeight( gammaWeight); + } else { + gammaTrack->SetWeight(gammaWeight); fParticleChange.AddSecondary(gammaTrack); } } @@ -100,8 +105,8 @@ GateOptneBremSplitting::ApplyFinalStateBiasing(const G4BiasingProcessInterface * processFinalState->Clear(); } nCalls++; - } - return &fParticleChange; + } + return &fParticleChange; } //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptneBremSplitting.h b/core/opengate_core/opengate_lib/GateOptneBremSplitting.h index cddd10ece..47319e536 100644 --- a/core/opengate_core/opengate_lib/GateOptneBremSplitting.h +++ b/core/opengate_core/opengate_lib/GateOptneBremSplitting.h @@ -44,13 +44,11 @@ class GateOptneBremSplitting : public GateOptnVGenericSplitting { virtual ~GateOptneBremSplitting(); public: - virtual G4VParticleChange * - ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, + ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, const G4Step *, G4bool &); G4ParticleChange fParticleChange; - }; #endif diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp index 141d73b77..62c6b503b 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp @@ -32,26 +32,26 @@ #include "CLHEP/Units/SystemOfUnits.h" #include "G4BiasingProcessInterface.hh" +#include "G4Electron.hh" #include "G4EmCalculator.hh" #include "G4Exception.hh" #include "G4Gamma.hh" -#include "G4Positron.hh" -#include "G4Electron.hh" #include "G4LogicalVolumeStore.hh" #include "G4ParticleTable.hh" #include "G4PhysicalVolumeStore.hh" +#include "G4Positron.hh" #include "G4ProcessManager.hh" #include "G4ProcessVector.hh" #include "G4RunManager.hh" #include "G4TrackStatus.hh" +#include "G4UImanager.hh" #include "G4UserTrackingAction.hh" #include "G4VEmProcess.hh" +#include "G4eplusAnnihilation.hh" +#include "GateOptnPairProdSplitting.h" #include "GateOptnScatteredGammaSplitting.h" #include "GateOptneBremSplitting.h" -#include "GateOptnPairProdSplitting.h" #include "GateOptrComptPseudoTransportationActor.h" -#include "G4UImanager.hh" -#include "G4eplusAnnihilation.hh" //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... @@ -75,15 +75,16 @@ GateOptrComptPseudoTransportationActor::GateOptrComptPseudoTransportationActor( DictGetBool(user_info, "russian_roulette_for_weights"); fMaxTheta = DictGetDouble(user_info, "max_theta"); fFreeFlightOperation = new GateOptnForceFreeFlight("freeFlightOperation"); - fScatteredGammaSplittingOperation = new GateOptnScatteredGammaSplitting("comptSplittingOperation"); - feBremSplittingOperation = new GateOptneBremSplitting("eBremSplittingOperation"); - fPairProdSplittingOperation = new GateOptnPairProdSplitting("PairProdSplittingOperation"); + fScatteredGammaSplittingOperation = + new GateOptnScatteredGammaSplitting("comptSplittingOperation"); + feBremSplittingOperation = + new GateOptneBremSplitting("eBremSplittingOperation"); + fPairProdSplittingOperation = + new GateOptnPairProdSplitting("PairProdSplittingOperation"); fActions.insert("StartSimulationAction"); fActions.insert("SteppingAction"); fActions.insert("BeginOfEventAction"); isSplitted = false; - - } //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... @@ -97,18 +98,22 @@ void GateOptrComptPseudoTransportationActor::AttachAllLogicalDaughtersVolumes( AttachTo(volume); } } - if (fAttachToLogicalHolder == true){ + if (fAttachToLogicalHolder == true) { AttachTo(volume); } G4int nbOfDaughters = volume->GetNoDaughters(); if (nbOfDaughters > 0) { for (int i = 0; i < nbOfDaughters; i++) { - G4String LogicalVolumeName = volume->GetDaughter(i)->GetLogicalVolume()->GetName(); + G4String LogicalVolumeName = + volume->GetDaughter(i)->GetLogicalVolume()->GetName(); G4LogicalVolume *logicalDaughtersVolume = volume->GetDaughter(i)->GetLogicalVolume(); AttachAllLogicalDaughtersVolumes(logicalDaughtersVolume); - if (!(std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeName ) !=fNameOfBiasedLogicalVolume.end())) - fNameOfBiasedLogicalVolume.push_back(volume->GetDaughter(i)->GetLogicalVolume()->GetName()); + if (!(std::find(fNameOfBiasedLogicalVolume.begin(), + fNameOfBiasedLogicalVolume.end(), + LogicalVolumeName) != fNameOfBiasedLogicalVolume.end())) + fNameOfBiasedLogicalVolume.push_back( + volume->GetDaughter(i)->GetLogicalVolume()->GetName()); } } } @@ -122,21 +127,20 @@ void GateOptrComptPseudoTransportationActor::StartSimulationAction() { // AttachAllLogicalDaughtersVolumes. AttachAllLogicalDaughtersVolumes(biasingVolume); for (int i = 0; i < fNameOfBiasedLogicalVolume.size(); i++) - fScatteredGammaSplittingOperation->SetSplittingFactor(fSplittingFactor); + fScatteredGammaSplittingOperation->SetSplittingFactor(fSplittingFactor); fScatteredGammaSplittingOperation->SetMaxTheta(fMaxTheta); - fScatteredGammaSplittingOperation->SetRussianRouletteForAngle(fRussianRouletteForAngle); + fScatteredGammaSplittingOperation->SetRussianRouletteForAngle( + fRussianRouletteForAngle); feBremSplittingOperation->SetSplittingFactor(fSplittingFactor); feBremSplittingOperation->SetMaxTheta(fMaxTheta); - feBremSplittingOperation->SetRussianRouletteForAngle(fRussianRouletteForAngle); + feBremSplittingOperation->SetRussianRouletteForAngle( + fRussianRouletteForAngle); fPairProdSplittingOperation->SetSplittingFactor(fSplittingFactor); - - - - fFreeFlightOperation->SetRussianRouletteForWeights(fRussianRouletteForWeights); - + fFreeFlightOperation->SetRussianRouletteForWeights( + fRussianRouletteForWeights); } void GateOptrComptPseudoTransportationActor::StartRun() { @@ -159,17 +163,20 @@ void GateOptrComptPseudoTransportationActor::StartRun() { feBremSplittingOperation->SetVectorDirector(fVectorDirector); } - - void GateOptrComptPseudoTransportationActor::SteppingAction(G4Step *step) { - - if ((isSplitted == true) && (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary)) { - G4String LogicalVolumeName = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - if (!(std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeName ) !=fNameOfBiasedLogicalVolume.end()) - && (LogicalVolumeName != fMotherVolumeName)) { - step->GetTrack()->SetTrackStatus(G4TrackStatus::fStopAndKill); - isSplitted = false; + if ((isSplitted == true) && + (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary)) { + G4String LogicalVolumeName = step->GetPostStepPoint() + ->GetPhysicalVolume() + ->GetLogicalVolume() + ->GetName(); + if (!(std::find(fNameOfBiasedLogicalVolume.begin(), + fNameOfBiasedLogicalVolume.end(), + LogicalVolumeName) != fNameOfBiasedLogicalVolume.end()) && + (LogicalVolumeName != fMotherVolumeName)) { + step->GetTrack()->SetTrackStatus(G4TrackStatus::fStopAndKill); + isSplitted = false; } } } @@ -177,24 +184,27 @@ void GateOptrComptPseudoTransportationActor::SteppingAction(G4Step *step) { void GateOptrComptPseudoTransportationActor::BeginOfEventAction( const G4Event *event) { fKillOthersParticles = false; - fPassedByABiasedVolume=false; + fPassedByABiasedVolume = false; fEventID = event->GetEventID(); - fEventIDKineticEnergy = event->GetPrimaryVertex(0)->GetPrimary(0)->GetKineticEnergy(); - - + fEventIDKineticEnergy = + event->GetPrimaryVertex(0)->GetPrimary(0)->GetKineticEnergy(); } void GateOptrComptPseudoTransportationActor::StartTracking( const G4Track *track) { G4String CreationProcessName = "None"; - - if (track->GetCreatorProcess() != 0){ + + if (track->GetCreatorProcess() != 0) { CreationProcessName = track->GetCreatorProcess()->GetProcessName(); } - - if (track->GetParticleDefinition()->GetParticleName() == "gamma"){ - G4String LogicalVolumeNameOfCreation = track->GetLogicalVolumeAtVertex()->GetName(); - if (std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeNameOfCreation ) !=fNameOfBiasedLogicalVolume.end()){ + + if (track->GetParticleDefinition()->GetParticleName() == "gamma") { + G4String LogicalVolumeNameOfCreation = + track->GetLogicalVolumeAtVertex()->GetName(); + if (std::find(fNameOfBiasedLogicalVolume.begin(), + fNameOfBiasedLogicalVolume.end(), + LogicalVolumeNameOfCreation) != + fNameOfBiasedLogicalVolume.end()) { fInitialWeight = track->GetWeight(); fFreeFlightOperation->SetInitialWeight(fInitialWeight); } @@ -215,11 +225,16 @@ void GateOptrComptPseudoTransportationActor::StartTracking( G4VBiasingOperation * GateOptrComptPseudoTransportationActor::ProposeOccurenceBiasingOperation( const G4Track *track, const G4BiasingProcessInterface *callingProcess) { - - if (track->GetParticleDefinition()->GetParticleName() == "gamma"){ - G4String LogicalVolumeNameOfCreation = track->GetLogicalVolumeAtVertex()->GetName(); - if (std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeNameOfCreation ) !=fNameOfBiasedLogicalVolume.end()){ - fFreeFlightOperation->SetMinWeight(fInitialWeight/fRelativeMinWeightOfParticle); + + if (track->GetParticleDefinition()->GetParticleName() == "gamma") { + G4String LogicalVolumeNameOfCreation = + track->GetLogicalVolumeAtVertex()->GetName(); + if (std::find(fNameOfBiasedLogicalVolume.begin(), + fNameOfBiasedLogicalVolume.end(), + LogicalVolumeNameOfCreation) != + fNameOfBiasedLogicalVolume.end()) { + fFreeFlightOperation->SetMinWeight(fInitialWeight / + fRelativeMinWeightOfParticle); fFreeFlightOperation->SetTrackWeight(track->GetWeight()); fFreeFlightOperation->SetCountProcess(0); return fFreeFlightOperation; @@ -239,44 +254,46 @@ G4VBiasingOperation * GateOptrComptPseudoTransportationActor::ProposeFinalStateBiasingOperation( const G4Track *track, const G4BiasingProcessInterface *callingProcess) { G4String particleName = track->GetParticleDefinition()->GetParticleName(); - - G4String LogicalVolumeNameOfCreation = track->GetLogicalVolumeAtVertex()->GetName(); + + G4String LogicalVolumeNameOfCreation = + track->GetLogicalVolumeAtVertex()->GetName(); G4String CreationProcessName = ""; - if (track->GetCreatorProcess() != 0){ + if (track->GetCreatorProcess() != 0) { CreationProcessName = track->GetCreatorProcess()->GetProcessName(); } - - if (track->GetParticleDefinition()->GetParticleName() == "gamma"){ - if (std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeNameOfCreation ) !=fNameOfBiasedLogicalVolume.end()){ + if (track->GetParticleDefinition()->GetParticleName() == "gamma") { + if (std::find(fNameOfBiasedLogicalVolume.begin(), + fNameOfBiasedLogicalVolume.end(), + LogicalVolumeNameOfCreation) != + fNameOfBiasedLogicalVolume.end()) { return callingProcess->GetCurrentOccurenceBiasingOperation(); } } - - if (((CreationProcessName != "biasWrapper(conv)") && (CreationProcessName != "biasWrapper(compt)")) - && (callingProcess->GetWrappedProcess()->GetProcessName() == "eBrem")){ + if (((CreationProcessName != "biasWrapper(conv)") && + (CreationProcessName != "biasWrapper(compt)")) && + (callingProcess->GetWrappedProcess()->GetProcessName() == "eBrem")) { return feBremSplittingOperation; - } + } - - - if (!(std::find(fCreationProcessNameList.begin(), fCreationProcessNameList.end(),CreationProcessName) != fCreationProcessNameList.end())){ - if ((callingProcess->GetWrappedProcess()->GetProcessName() == "compt") || (callingProcess->GetWrappedProcess()->GetProcessName() == "Rayl")){ + if (!(std::find(fCreationProcessNameList.begin(), + fCreationProcessNameList.end(), + CreationProcessName) != fCreationProcessNameList.end())) { + if ((callingProcess->GetWrappedProcess()->GetProcessName() == "compt") || + (callingProcess->GetWrappedProcess()->GetProcessName() == "Rayl")) { isSplitted = true; return fScatteredGammaSplittingOperation; } - if ((callingProcess->GetWrappedProcess()->GetProcessName() == "conv")){ - return fPairProdSplittingOperation; + if ((callingProcess->GetWrappedProcess()->GetProcessName() == "conv")) { + return fPairProdSplittingOperation; } - } - return 0; } - + return 0; +} void GateOptrComptPseudoTransportationActor::EndTracking() { isSplitted = false; } - //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h index d60e88ad9..728651958 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h @@ -32,8 +32,8 @@ #include "G4EmCalculator.hh" #include "G4VBiasingOperator.hh" #include "GateOptnForceFreeFlight.h" -#include "GateOptneBremSplitting.h" #include "GateOptnPairProdSplitting.h" +#include "GateOptneBremSplitting.h" #include "GateVActor.h" #include <iostream> @@ -80,17 +80,19 @@ class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, G4bool fUseProbes = false; G4bool fSurvivedRR = false; G4bool fAttachToLogicalHolder = true; - G4bool fPassedByABiasedVolume= false; + G4bool fPassedByABiasedVolume = false; G4double fKineticEnergyAtTheEntrance; G4int ftrackIDAtTheEntrance; G4int fEventID; G4double fEventIDKineticEnergy; - G4bool ftestbool= false; - const G4VProcess* fAnnihilation =nullptr; + G4bool ftestbool = false; + const G4VProcess *fAnnihilation = nullptr; std::vector<G4String> fNameOfBiasedLogicalVolume = {}; std::vector<G4int> v_EventID = {}; - std::vector<G4String> fCreationProcessNameList = {"biasWrapper(compt)","biasWrapper(Rayl)", "biasWrapper(eBrem)","biasWrapper(annihil)"}; + std::vector<G4String> fCreationProcessNameList = { + "biasWrapper(compt)", "biasWrapper(Rayl)", "biasWrapper(eBrem)", + "biasWrapper(annihil)"}; // Unused but mandatory virtual void StartSimulationAction(); @@ -126,10 +128,10 @@ class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, using G4VBiasingOperator::OperationApplied; private: - GateOptnForceFreeFlight* fFreeFlightOperation; - GateOptnScatteredGammaSplitting* fScatteredGammaSplittingOperation; - GateOptneBremSplitting* feBremSplittingOperation; - GateOptnPairProdSplitting* fPairProdSplittingOperation; + GateOptnForceFreeFlight *fFreeFlightOperation; + GateOptnScatteredGammaSplitting *fScatteredGammaSplittingOperation; + GateOptneBremSplitting *feBremSplittingOperation; + GateOptnPairProdSplitting *fPairProdSplittingOperation; }; #endif diff --git a/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp index 6b5cee6a2..5de9fe12b 100644 --- a/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp @@ -12,8 +12,11 @@ namespace py = pybind11; #include "GateKillNonInteractingParticleActor.h" void init_GateKillNonInteractingParticleActor(py::module &m) { - py::class_<GateKillNonInteractingParticleActor, std::unique_ptr<GateKillNonInteractingParticleActor, py::nodelete>, + py::class_<GateKillNonInteractingParticleActor, + std::unique_ptr<GateKillNonInteractingParticleActor, py::nodelete>, GateVActor>(m, "GateKillNonInteractingParticleActor") - .def_readwrite("fListOfVolumeAncestor", &GateKillNonInteractingParticleActor::fListOfVolumeAncestor) + .def_readwrite( + "fListOfVolumeAncestor", + &GateKillNonInteractingParticleActor::fListOfVolumeAncestor) .def(py::init<py::dict &>()); } diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index fc2a53541..daf48fdd2 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -465,7 +465,7 @@ def set_default_user_info(user_info): user_info.relative_min_weight_of_particle = np.inf user_info.gamma_processes = ["compt", "phot", "conv", "Rayl"] user_info.electron_processes = ["eBrem"] - user_info.positron_processes = ["annihil","eBrem"] + user_info.positron_processes = ["annihil", "eBrem"] user_info.russian_roulette_for_angle = False user_info.rotation_vector_director = False user_info.vector_director = [0, 0, 1] @@ -490,23 +490,23 @@ def set_default_user_info(user_info): def __init__(self, user_info): ActorBase.__init__(self, user_info) g4.GateBOptrBremSplittingActor.__init__(self, user_info.__dict__) - -class KillNonInteractingParticleActor(g4.GateKillNonInteractingParticleActor, ActorBase): + +class KillNonInteractingParticleActor( + g4.GateKillNonInteractingParticleActor, ActorBase +): type_name = "KillNonInteractingParticleActor" def set_default_user_info(user_info): ActorBase.set_default_user_info(user_info) user_info.list_of_volume_name = [] - def __init__(self, user_info): ActorBase.__init__(self, user_info) g4.GateKillNonInteractingParticleActor.__init__(self, user_info.__dict__) self.list_of_volume_name = user_info.list_of_volume_name self.user_info.mother = user_info.mother - def initialize(self, volume_engine=None): super().initialize(volume_engine) @@ -515,7 +515,7 @@ def initialize(self, volume_engine=None): for pre, _, node in RenderTree(volume_tree): dico_of_volume_tree[str(node.name)] = node volume_name = self.user_info.mother - while volume_name != 'world': + while volume_name != "world": node = dico_of_volume_tree[volume_name] volume_name = node.mother self.list_of_volume_name.append(volume_name) @@ -525,7 +525,6 @@ def initialize(self, volume_engine=None): class SurfaceSplittingActor(g4.GateSurfaceSplittingActor, ActorBase): type_name = "SurfaceSplittingActor" - def set_default_user_info(user_info): ActorBase.set_default_user_info(user_info) user_info.list_of_volume_name = [] @@ -539,8 +538,6 @@ def __init__(self, user_info): g4.GateSurfaceSplittingActor.__init__(self, user_info.__dict__) self.list_of_volume_name = user_info.list_of_volume_name self.user_info.mother = user_info.mother - - def initialize(self, volume_engine=None): super().initialize(volume_engine) @@ -549,7 +546,7 @@ def initialize(self, volume_engine=None): for pre, _, node in RenderTree(volume_tree): dico_of_volume_tree[str(node.name)] = node volume_name = self.user_info.mother - while volume_name != 'world': + while volume_name != "world": node = dico_of_volume_tree[volume_name] volume_name = node.mother self.list_of_volume_name.append(volume_name) diff --git a/opengate/managers.py b/opengate/managers.py index 6add752ba..af762e6bb 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -988,9 +988,9 @@ def dump_volume_types(self): def dump_material_database_names(self): return list(self.material_database.filenames) - + def get_volume_tree(self): - return (self.volume_tree_root) + return self.volume_tree_root def get_volume_tree(self): return self.volume_tree_root diff --git a/opengate/tests/src/test074_kill_non_interacting_particles.py b/opengate/tests/src/test074_kill_non_interacting_particles.py index 7653c20fd..36616f210 100644 --- a/opengate/tests/src/test074_kill_non_interacting_particles.py +++ b/opengate/tests/src/test074_kill_non_interacting_particles.py @@ -5,23 +5,22 @@ from opengate.tests import utility from scipy.spatial.transform import Rotation import numpy as np -from anytree import Node,RenderTree +from anytree import Node, RenderTree import uproot - - - - -def test074_test(entry_data, exit_data_1,exit_data_2): - liste_ekin =[] +def test074_test(entry_data, exit_data_1, exit_data_2): + liste_ekin = [] liste_evtID = [] liste_trackID = [] evt_ID_entry_data = entry_data["EventID"] j = 0 i = 0 while i < len(evt_ID_entry_data): - if j < len(exit_data_1["EventID"]) and evt_ID_entry_data[i] == exit_data_1["EventID"][j]: + if ( + j < len(exit_data_1["EventID"]) + and evt_ID_entry_data[i] == exit_data_1["EventID"][j] + ): TID_entry = entry_data["TrackID"][i] TID_exit = exit_data_1["TrackID"][j] Ekin_entry = entry_data["KineticEnergy"][i] @@ -31,23 +30,22 @@ def test074_test(entry_data, exit_data_1,exit_data_2): liste_ekin.append(exit_data_1["KineticEnergy"][j]) liste_evtID.append(exit_data_1["EventID"][j]) liste_trackID.append(exit_data_1["TrackID"][j]) - if (j < len(exit_data_1["EventID"]) -1) and (exit_data_1["EventID"][j] == exit_data_1["EventID"][j+1]) : - i = i-1 - j+=1 - i+=1 - liste_ekin =np.asarray(liste_ekin) + if (j < len(exit_data_1["EventID"]) - 1) and ( + exit_data_1["EventID"][j] == exit_data_1["EventID"][j + 1] + ): + i = i - 1 + j += 1 + i += 1 + liste_ekin = np.asarray(liste_ekin) print("Number of tracks to kill =", len(liste_ekin)) - print("Number of killed tracks =",(len(exit_data_1["EventID"]) - len(exit_data_2["EventID"]))) - - - return len(liste_ekin) == (len(exit_data_1["EventID"]) - len(exit_data_2["EventID"])) - - - - - - + print( + "Number of killed tracks =", + (len(exit_data_1["EventID"]) - len(exit_data_2["EventID"])), + ) + return len(liste_ekin) == ( + len(exit_data_1["EventID"]) - len(exit_data_2["EventID"]) + ) if __name__ == "__main__": @@ -92,63 +90,56 @@ def test074_test(entry_data, exit_data_1,exit_data_2): world.size = [1 * m, 1 * m, 1 * m] world.material = "G4_AIR" - big_box = sim.add_volume("Box","big_box") + big_box = sim.add_volume("Box", "big_box") big_box.mother = world.name - big_box.material = 'G4_AIR' - big_box.size = [0.8*m,0.8*m,0.8*m] + big_box.material = "G4_AIR" + big_box.size = [0.8 * m, 0.8 * m, 0.8 * m] actor_box = sim.add_volume("Box", "actor_box") actor_box.mother = big_box.name - actor_box.material = 'G4_AIR' + actor_box.material = "G4_AIR" actor_box.size = [0.6 * m, 0.6 * m, 0.6 * m] - actor_box.translation = [0,0,-0.1*m] - - - + actor_box.translation = [0, 0, -0.1 * m] source = sim.add_source("GenericSource", "photon_source") source.particle = "gamma" source.position.type = "box" source.mother = world.name source.position.size = [6 * cm, 6 * cm, 6 * cm] - source.position.translation = [0,0,0.3*m] + source.position.translation = [0, 0, 0.3 * m] source.direction.type = "momentum" source.force_rotation = True # source1.direction.focus_point = [0*cm, 0*cm, -5 *cm] source.direction.momentum = [0, 0, -1] source.energy.type = "mono" source.energy.mono = 6 * MeV - source.n= 1000 - - + source.n = 1000 - tungsten_leaves =sim.add_volume("Box","tungsten_leaves") + tungsten_leaves = sim.add_volume("Box", "tungsten_leaves") tungsten_leaves.mother = actor_box - tungsten_leaves.size = [0.6*m,0.6*m,0.3*cm] + tungsten_leaves.size = [0.6 * m, 0.6 * m, 0.3 * cm] tungsten_leaves.material = "Tungsten" liste_translation_W = [] - for i in range(7) : - liste_translation_W.append([0,0,0.25*m - i*6*cm]) + for i in range(7): + liste_translation_W.append([0, 0, 0.25 * m - i * 6 * cm]) tungsten_leaves.translation = liste_translation_W - tungsten_leaves.color = [0.9,0.,0.4,0.8] + tungsten_leaves.color = [0.9, 0.0, 0.4, 0.8] - - kill_No_int_act = sim.add_actor("KillNonInteractingParticleActor","killact") + kill_No_int_act = sim.add_actor("KillNonInteractingParticleActor", "killact") kill_No_int_act.mother = actor_box.name - - entry_phase_space = sim.add_volume("Box","entry_phase_space") + entry_phase_space = sim.add_volume("Box", "entry_phase_space") entry_phase_space.mother = big_box - entry_phase_space.size = [0.8*m,0.8*m,1*nm] + entry_phase_space.size = [0.8 * m, 0.8 * m, 1 * nm] entry_phase_space.material = "G4_AIR" - entry_phase_space.translation = [0,0,0.21*m] - entry_phase_space.color = [0.5,0.9,0.3,1] + entry_phase_space.translation = [0, 0, 0.21 * m] + entry_phase_space.color = [0.5, 0.9, 0.3, 1] - exit_phase_space_1 = sim.add_volume("Box","exit_phase_space_1") + exit_phase_space_1 = sim.add_volume("Box", "exit_phase_space_1") exit_phase_space_1.mother = actor_box exit_phase_space_1.size = [0.6 * m, 0.6 * m, 1 * nm] exit_phase_space_1.material = "G4_AIR" - exit_phase_space_1.translation = [0, 0, -0.3 * m + 1*nm] + exit_phase_space_1.translation = [0, 0, -0.3 * m + 1 * nm] exit_phase_space_1.color = [0.5, 0.9, 0.3, 1] exit_phase_space_2 = sim.add_volume("Box", "exit_phase_space_2") @@ -159,38 +150,58 @@ def test074_test(entry_data, exit_data_1,exit_data_2): exit_phase_space_2.color = [0.5, 0.9, 0.3, 1] # print(sim.volume_manager.dump_volume_tree()) - liste_phase_space_name = [entry_phase_space.name,exit_phase_space_1.name,exit_phase_space_2.name] + liste_phase_space_name = [ + entry_phase_space.name, + exit_phase_space_1.name, + exit_phase_space_2.name, + ] for name in liste_phase_space_name: - phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_"+name) + phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_" + name) phsp.mother = name - phsp.attributes = ["EventID","TrackID","KineticEnergy"] - name_phsp = "test074_" +name+".root" + phsp.attributes = ["EventID", "TrackID", "KineticEnergy"] + name_phsp = "test074_" + name + ".root" phsp.output = output_path / name_phsp - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" sim.physics_manager.enable_decay = False sim.physics_manager.global_production_cuts.gamma = 1 * mm sim.physics_manager.global_production_cuts.electron = 1 * mm sim.physics_manager.global_production_cuts.positron = 1 * mm - s = sim.add_actor("SimulationStatisticsActor", "Stats") s.track_types_flag = True - # go ! sim.run() output = sim.output stats = sim.output.get_actor("Stats") print(stats) - - entry_phsp = uproot.open(str(output_path) + "/test074_" + liste_phase_space_name[0] + ".root" +":PhaseSpace_" + liste_phase_space_name[0]) - exit_phase_space_1 = uproot.open(str(output_path) + "/test074_" + liste_phase_space_name[1] + ".root" + ":PhaseSpace_" + liste_phase_space_name[1]) - exit_phase_space_2 = uproot.open( str(output_path) + "/test074_" + liste_phase_space_name[2] + ".root" + ":PhaseSpace_" + liste_phase_space_name[2]) - + entry_phsp = uproot.open( + str(output_path) + + "/test074_" + + liste_phase_space_name[0] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[0] + ) + exit_phase_space_1 = uproot.open( + str(output_path) + + "/test074_" + + liste_phase_space_name[1] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[1] + ) + exit_phase_space_2 = uproot.open( + str(output_path) + + "/test074_" + + liste_phase_space_name[2] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[2] + ) df_entry = entry_phsp.arrays() df_exit_1 = exit_phase_space_1.arrays() @@ -198,4 +209,4 @@ def test074_test(entry_data, exit_data_1,exit_data_2): is_ok = test074_test(df_entry, df_exit_1, df_exit_2) - utility.test_ok(is_ok) \ No newline at end of file + utility.test_ok(is_ok) diff --git a/opengate/tests/src/test075_geometrical_splitting_volume_surface.py b/opengate/tests/src/test075_geometrical_splitting_volume_surface.py index 44a22ecc7..58af57f28 100644 --- a/opengate/tests/src/test075_geometrical_splitting_volume_surface.py +++ b/opengate/tests/src/test075_geometrical_splitting_volume_surface.py @@ -10,7 +10,9 @@ def test075(entry_data, exit_data, splitting_factor): - splitted_particle_data_entry = entry_data[entry_data["TrackCreatorProcess"] == "none"] + splitted_particle_data_entry = entry_data[ + entry_data["TrackCreatorProcess"] == "none" + ] splitted_particle_data_exit = exit_data[exit_data["TrackCreatorProcess"] == "none"] @@ -130,8 +132,14 @@ def test075(entry_data, exit_data, splitting_factor): phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_" + name) phsp.mother = name - phsp.attributes = ["EventID","TrackID","Weight","PDGCode","TrackCreatorProcess"] - name_phsp = "test075_" +name+".root" + phsp.attributes = [ + "EventID", + "TrackID", + "Weight", + "PDGCode", + "TrackCreatorProcess", + ] + name_phsp = "test075_" + name + ".root" phsp.output = output_path / name_phsp sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" @@ -150,14 +158,28 @@ def test075(entry_data, exit_data, splitting_factor): print(stats) # - entry_phsp = uproot.open(str(output_path) + "/test075_" + liste_phase_space_name[0] + ".root" +":PhaseSpace_" + liste_phase_space_name[0]) - exit_phase_space = uproot.open(str(output_path) + "/test075_" + liste_phase_space_name[1] + ".root" + ":PhaseSpace_" + liste_phase_space_name[1]) + entry_phsp = uproot.open( + str(output_path) + + "/test075_" + + liste_phase_space_name[0] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[0] + ) + exit_phase_space = uproot.open( + str(output_path) + + "/test075_" + + liste_phase_space_name[1] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[1] + ) # df_entry = entry_phsp.arrays() df_exit = exit_phase_space.arrays() # - is_ok = test075(df_entry, df_exit,geom_splitting.splitting_factor) - + is_ok = test075(df_entry, df_exit, geom_splitting.splitting_factor) + utility.test_ok(is_ok) From 9447e826120e9f973da17f8ba319dfeacabe3272 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 14 May 2024 15:11:29 +0200 Subject: [PATCH 18/82] Actor killing a particle at the volume exit if interaction --- core/opengate_core/opengate_core.cpp | 3 + .../GateKillInteractingParticleActor.cpp | 70 +++++++++++++++++++ .../GateKillInteractingParticleActor.h | 41 +++++++++++ .../pyGateKillInteractingParticleActor.cpp | 22 ++++++ opengate/actors/actorbuilders.py | 2 + opengate/actors/miscactors.py | 30 ++++++++ opengate/managers.py | 3 + 7 files changed, 171 insertions(+) create mode 100644 core/opengate_core/opengate_lib/GateKillInteractingParticleActor.cpp create mode 100644 core/opengate_core/opengate_lib/GateKillInteractingParticleActor.h create mode 100644 core/opengate_core/opengate_lib/pyGateKillInteractingParticleActor.cpp diff --git a/core/opengate_core/opengate_core.cpp b/core/opengate_core/opengate_core.cpp index 1952ba68e..972eca4d3 100644 --- a/core/opengate_core/opengate_core.cpp +++ b/core/opengate_core/opengate_core.cpp @@ -294,6 +294,8 @@ void init_GateARFTrainingDatasetActor(py::module &m); void init_GateKillActor(py::module &); +void init_GateKillInteractingParticleActor(py::module &); + void init_itk_image(py::module &); void init_GateImageNestedParameterisation(py::module &); @@ -561,6 +563,7 @@ PYBIND11_MODULE(opengate_core, m) { init_GateARFActor(m); init_GateARFTrainingDatasetActor(m); init_GateKillActor(m); + init_GateKillInteractingParticleActor(m); init_GateDigiAttributeManager(m); init_GateVDigiAttribute(m); init_GateExceptionHandler(m); diff --git a/core/opengate_core/opengate_lib/GateKillInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/GateKillInteractingParticleActor.cpp new file mode 100644 index 000000000..b6b4b4ba9 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateKillInteractingParticleActor.cpp @@ -0,0 +1,70 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + ------------------------------------ -------------- */ + +#include "GateKillInteractingParticleActor.h" +#include "G4LogicalVolumeStore.hh" +#include "G4PhysicalVolumeStore.hh" +#include "G4ios.hh" +#include "GateHelpers.h" +#include "GateHelpersDict.h" + +GateKillInteractingParticleActor::GateKillInteractingParticleActor( + py::dict &user_info) + : GateVActor(user_info, false) { + fActions.insert("StartSimulationAction"); + fActions.insert("SteppingAction"); + fActions.insert("PreUserTrackingAction"); +} + +void GateKillInteractingParticleActor::ActorInitialize() {} + +void GateKillInteractingParticleActor::StartSimulationAction() { + fNbOfKilledParticles = 0; +} + +void GateKillInteractingParticleActor::PreUserTrackingAction( + const G4Track *track) { + fIsFirstStep = true; +} + +void GateKillInteractingParticleActor::SteppingAction(G4Step *step) { + + G4String logNameMotherVolume = G4LogicalVolumeStore::GetInstance() + ->GetVolume(fMotherVolumeName) + ->GetName(); + G4String physicalVolumeNamePreStep = + step->GetPreStepPoint()->GetPhysicalVolume()->GetName(); + if ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != + logNameMotherVolume) && + (fIsFirstStep)) { + if ((physicalVolumeNamePreStep == fMotherVolumeName) && + (step->GetPreStepPoint()->GetStepStatus() == 1)) { + fKineticEnergyAtTheEntrance = step->GetPreStepPoint()->GetKineticEnergy(); + ftrackIDAtTheEntrance = step->GetTrack()->GetTrackID(); + } + } + + G4String logicalVolumeNamePostStep = step->GetPostStepPoint() + ->GetPhysicalVolume() + ->GetLogicalVolume() + ->GetName(); + + if (step->GetPostStepPoint()->GetStepStatus() == 1){ + if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), + logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { + if ((step->GetTrack()->GetTrackID() != ftrackIDAtTheEntrance) || + (step->GetPostStepPoint()->GetKineticEnergy() != fKineticEnergyAtTheEntrance)) { + auto track = step->GetTrack(); + track->SetTrackStatus(fStopAndKill); + fNbOfKilledParticles++; + } + fKineticEnergyAtTheEntrance = 0; + ftrackIDAtTheEntrance = 0; + } + } + fIsFirstStep = false; +} diff --git a/core/opengate_core/opengate_lib/GateKillInteractingParticleActor.h b/core/opengate_core/opengate_lib/GateKillInteractingParticleActor.h new file mode 100644 index 000000000..9f0b003e4 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateKillInteractingParticleActor.h @@ -0,0 +1,41 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#ifndef GateKillInteractingParticleActor_h +#define GateKillInteractingParticleActor_h + +#include "G4Cache.hh" +#include "GateVActor.h" +#include <pybind11/stl.h> + +namespace py = pybind11; + +class GateKillInteractingParticleActor : public GateVActor { + +public: + // Constructor + GateKillInteractingParticleActor(py::dict &user_info); + + void ActorInitialize() override; + + void StartSimulationAction() override; + + // Main function called every step in attached volume + void SteppingAction(G4Step *) override; + + void PreUserTrackingAction(const G4Track *) override; + + std::vector<G4String> fParticlesTypeToKill; + G4double fKineticEnergyAtTheEntrance = 0; + G4int ftrackIDAtTheEntrance = 0; + G4bool fIsFirstStep = true; + std::vector<std::string> fListOfVolumeAncestor; + + long fNbOfKilledParticles{}; +}; + +#endif diff --git a/core/opengate_core/opengate_lib/pyGateKillInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/pyGateKillInteractingParticleActor.cpp new file mode 100644 index 000000000..fa089c4ef --- /dev/null +++ b/core/opengate_core/opengate_lib/pyGateKillInteractingParticleActor.cpp @@ -0,0 +1,22 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include <pybind11/pybind11.h> + +namespace py = pybind11; + +#include "GateKillInteractingParticleActor.h" + +void init_GateKillInteractingParticleActor(py::module &m) { + py::class_<GateKillInteractingParticleActor, + std::unique_ptr<GateKillInteractingParticleActor, py::nodelete>, + GateVActor>(m, "GateKillInteractingParticleActor") + .def_readwrite( + "fListOfVolumeAncestor", + &GateKillInteractingParticleActor::fListOfVolumeAncestor) + .def(py::init<py::dict &>()); +} diff --git a/opengate/actors/actorbuilders.py b/opengate/actors/actorbuilders.py index 8b6c2f697..ab0e4537e 100644 --- a/opengate/actors/actorbuilders.py +++ b/opengate/actors/actorbuilders.py @@ -17,6 +17,7 @@ SourceInfoActor, TestActor, KillActor, + KillInteractingParticleActor, BremSplittingActor, ComptSplittingActor, ) @@ -44,6 +45,7 @@ ARFTrainingDatasetActor, TestActor, KillActor, + KillInteractingParticleActor, BremSplittingActor, ComptSplittingActor, DynamicGeometryActor, diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 419082e6f..7124b10fc 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -6,6 +6,7 @@ import time import platform import opengate_core as g4 +from anytree import Node, RenderTree from .base import ActorBase from ..exception import fatal from ..geometry.utility import rot_np_as_g4, vec_np_as_g4, vec_g4_as_np @@ -429,6 +430,35 @@ def __init__(self, user_info): g4.GateKillActor.__init__(self, user_info.__dict__) +class KillInteractingParticleActor( + g4.GateKillInteractingParticleActor, ActorBase +): + type_name = "KillInteractingParticleActor" + + def set_default_user_info(user_info): + ActorBase.set_default_user_info(user_info) + user_info.list_of_volume_name = [] + + def __init__(self, user_info): + ActorBase.__init__(self, user_info) + g4.GateKillInteractingParticleActor.__init__(self, user_info.__dict__) + self.list_of_volume_name = user_info.list_of_volume_name + self.user_info.mother = user_info.mother + + def initialize(self, volume_engine=None): + + super().initialize(volume_engine) + volume_tree = self.simulation.volume_manager.get_volume_tree() + dico_of_volume_tree = {} + for pre, _, node in RenderTree(volume_tree): + dico_of_volume_tree[str(node.name)] = node + volume_name = self.user_info.mother + while volume_name != "world": + node = dico_of_volume_tree[volume_name] + volume_name = node.mother + self.list_of_volume_name.append(volume_name) + self.fListOfVolumeAncestor = self.list_of_volume_name + """ class ComptonSplittingActor(g4.GateComptonSplittingActor,ActorBase): type_name = "ComptonSplittingActor" diff --git a/opengate/managers.py b/opengate/managers.py index 738388e42..208c8d895 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -1033,6 +1033,9 @@ def dump_volume_types(self): def dump_material_database_names(self): return list(self.material_database.filenames) + + def get_volume_tree(self): + return (self.volume_tree_root) def setter_hook_verbose_level(self, verbose_level): From c1ebb7220b9e54f583c6568364b417a399ea2bfd Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 14 May 2024 15:12:05 +0200 Subject: [PATCH 19/82] Add a test to verify the killing actor --- .../src/test077_kill_interacting_particles.py | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 opengate/tests/src/test077_kill_interacting_particles.py diff --git a/opengate/tests/src/test077_kill_interacting_particles.py b/opengate/tests/src/test077_kill_interacting_particles.py new file mode 100644 index 000000000..b36776d8f --- /dev/null +++ b/opengate/tests/src/test077_kill_interacting_particles.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +from opengate.tests import utility +from scipy.spatial.transform import Rotation +import numpy as np +from anytree import Node, RenderTree +import uproot + + +def test077_test(entry_data, exit_data_1, exit_data_2): + + + + liste_ekin = [] + liste_evtID = [] + liste_trackID = [] + evt_ID_entry_data = entry_data["EventID"] + j = 0 + i = 0 + while i < len(evt_ID_entry_data): + if ( + j < len(exit_data_1["EventID"]) + and evt_ID_entry_data[i] == exit_data_1["EventID"][j] + ): + TID_entry = entry_data["TrackID"][i] + TID_exit = exit_data_1["TrackID"][j] + Ekin_entry = entry_data["KineticEnergy"][i] + Ekin_exit = exit_data_1["KineticEnergy"][j] + + + if (TID_entry != TID_exit) or (Ekin_exit != Ekin_entry): + liste_ekin.append(exit_data_1["KineticEnergy"][j]) + liste_evtID.append(exit_data_1["EventID"][j]) + liste_trackID.append(exit_data_1["TrackID"][j]) + if (j < len(exit_data_1["EventID"]) - 1) and ( + exit_data_1["EventID"][j] == exit_data_1["EventID"][j + 1] + ): + i = i - 1 + j += 1 + i += 1 + liste_ekin = np.asarray(liste_ekin) + print("Number of tracks to kill =", len(liste_ekin)) + print( + "Number of killed tracks =", + (len(exit_data_1["EventID"]) - len(exit_data_2["EventID"])), + ) + + return len(liste_ekin) == ( + len(exit_data_1["EventID"]) - len(exit_data_2["EventID"]) + ) + + +if __name__ == "__main__": + paths = utility.get_default_test_paths(__file__) + output_path = paths.output + + print(output_path) + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + # ui.visu = True + ui.visu_type = "vrml" + ui.check_volumes_overlap = False + # ui.running_verbose_level = gate.logger.EVENT + ui.number_of_threads = 1 + ui.random_seed = "auto" + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + sec = gate.g4_units.s + gcm3 = gate.g4_units["g/cm3"] + + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + + # adapt world size + world = sim.world + world.size = [1 * m, 1 * m, 1 * m] + world.material = "G4_AIR" + + big_box = sim.add_volume("Box", "big_box") + big_box.mother = world.name + big_box.material = "G4_AIR" + big_box.size = [0.8 * m, 0.8 * m, 0.8 * m] + + actor_box = sim.add_volume("Box", "actor_box") + actor_box.mother = big_box.name + actor_box.material = "G4_AIR" + actor_box.size = [0.6 * m, 0.6 * m, 0.6 * m] + actor_box.translation = [0, 0, -0.1 * m] + + source = sim.add_source("GenericSource", "photon_source") + source.particle = "gamma" + source.position.type = "box" + source.mother = world.name + source.position.size = [6 * cm, 6 * cm, 6 * cm] + source.position.translation = [0, 0, 0.3 * m] + source.direction.type = "momentum" + source.force_rotation = True + # source1.direction.focus_point = [0*cm, 0*cm, -5 *cm] + source.direction.momentum = [0, 0, -1] + source.energy.type = "mono" + source.energy.mono = 6 * MeV + source.n = 1000 + + tungsten_leaves = sim.add_volume("Box", "tungsten_leaves") + tungsten_leaves.mother = actor_box + tungsten_leaves.size = [0.6 * m, 0.6 * m, 0.3 * cm] + tungsten_leaves.material = "Tungsten" + liste_translation_W = [] + for i in range(7): + liste_translation_W.append([0, 0, 0.25 * m - i * 6 * cm]) + tungsten_leaves.translation = liste_translation_W + tungsten_leaves.color = [0.9, 0.0, 0.4, 0.8] + + kill_int_act = sim.add_actor("KillInteractingParticleActor", "killact") + kill_int_act.mother = actor_box.name + + entry_phase_space = sim.add_volume("Box", "entry_phase_space") + entry_phase_space.mother = big_box + entry_phase_space.size = [0.8 * m, 0.8 * m, 1 * nm] + entry_phase_space.material = "G4_AIR" + entry_phase_space.translation = [0, 0, 0.21 * m] + entry_phase_space.color = [0.5, 0.9, 0.3, 1] + + exit_phase_space_1 = sim.add_volume("Box", "exit_phase_space_1") + exit_phase_space_1.mother = actor_box + exit_phase_space_1.size = [0.6 * m, 0.6 * m, 1 * nm] + exit_phase_space_1.material = "G4_AIR" + exit_phase_space_1.translation = [0, 0, -0.3 * m + 1 * nm] + exit_phase_space_1.color = [0.5, 0.9, 0.3, 1] + + exit_phase_space_2 = sim.add_volume("Box", "exit_phase_space_2") + exit_phase_space_2.mother = world.name + exit_phase_space_2.size = [0.6 * m, 0.6 * m, 1 * nm] + exit_phase_space_2.material = "G4_AIR" + exit_phase_space_2.translation = [0, 0, -0.4 * m - 1 * nm] + exit_phase_space_2.color = [0.5, 0.9, 0.3, 1] + + # print(sim.volume_manager.dump_volume_tree()) + liste_phase_space_name = [ + entry_phase_space.name, + exit_phase_space_1.name, + exit_phase_space_2.name, + ] + for name in liste_phase_space_name: + + phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_" + name) + phsp.mother = name + phsp.attributes = ["EventID", "TrackID", "KineticEnergy"] + name_phsp = "test077_" + name + ".root" + phsp.output = output_path / name_phsp + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + sim.physics_manager.enable_decay = False + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1 * mm + sim.physics_manager.global_production_cuts.positron = 1 * mm + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + + # go ! + sim.run() + output = sim.output + stats = sim.output.get_actor("Stats") + print(stats) + + entry_phsp = uproot.open( + str(output_path) + + "/test077_" + + liste_phase_space_name[0] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[0] + ) + exit_phase_space_1 = uproot.open( + str(output_path) + + "/test077_" + + liste_phase_space_name[1] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[1] + ) + exit_phase_space_2 = uproot.open( + str(output_path) + + "/test077_" + + liste_phase_space_name[2] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[2] + ) + + df_entry = entry_phsp.arrays() + df_exit_1 = exit_phase_space_1.arrays() + df_exit_2 = exit_phase_space_2.arrays() + + is_ok = test077_test(df_entry, df_exit_1, df_exit_2) + + utility.test_ok(is_ok) From 77d16f5f65bf1a130282c011eed6297a844f21c5 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Fri, 24 May 2024 13:54:24 +0200 Subject: [PATCH 20/82] Actor creation which split an exiting particle at its last vertex --- core/opengate_core/opengate_core.cpp | 6 +- ...ptrLastVertexInteractionSplittingActor.cpp | 242 ++++++++++++++++++ ...eOptrLastVertexInteractionSplittingActor.h | 122 +++++++++ ...ptrLastVertexInteractionSplittingActor.cpp | 22 ++ opengate/actors/actorbuilders.py | 2 + opengate/actors/miscactors.py | 52 +++- opengate/managers.py | 6 +- 7 files changed, 437 insertions(+), 15 deletions(-) create mode 100644 core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.cpp create mode 100644 core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.h create mode 100644 core/opengate_core/opengate_lib/pyGateOptrLastVertexInteractionSplittingActor.cpp diff --git a/core/opengate_core/opengate_core.cpp b/core/opengate_core/opengate_core.cpp index bff200f36..bb7cf5b17 100644 --- a/core/opengate_core/opengate_core.cpp +++ b/core/opengate_core/opengate_core.cpp @@ -336,10 +336,10 @@ void init_GateSimulationStatisticsActor(py::module &); void init_GatePhaseSpaceActor(py::module &); -// void init_GateComptonSplittingActor(py::module &); - void init_GateOptrComptSplittingActor(py::module &m); +void init_GateOptrLastVertexInteractionSplittingActor(py::module &m); + void init_GateBOptrBremSplittingActor(py::module &m); void init_G4VBiasingOperator(py::module &m); @@ -558,9 +558,9 @@ PYBIND11_MODULE(opengate_core, m) { init_GateLETActor(m); init_GateSimulationStatisticsActor(m); init_GatePhaseSpaceActor(m); - // init_GateComptonSplittingActor(m); init_GateBOptrBremSplittingActor(m); init_GateOptrComptSplittingActor(m); + init_GateOptrLastVertexInteractionSplittingActor(m); init_GateHitsCollectionActor(m); init_GateMotionVolumeActor(m); init_GateHitsAdderActor(m); diff --git a/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.cpp new file mode 100644 index 000000000..6181d6dd8 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.cpp @@ -0,0 +1,242 @@ +// +// ******************************************************************** +// * 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 GateOptrLastVertexInteractionSplittingActor.cc +/// \brief Implementation of the GateOptrLastVertexInteractionSplittingActor class + +#include "GateHelpersDict.h" +#include "GateHelpersImage.h" + +#include "CLHEP/Units/SystemOfUnits.h" +#include "G4BiasingProcessInterface.hh" +#include "G4Gamma.hh" +#include "G4LogicalVolumeStore.hh" +#include "G4ParticleTable.hh" +#include "G4PhysicalVolumeStore.hh" +#include "G4ProcessManager.hh" +#include "G4TrackingManager.hh" +#include "G4ProcessVector.hh" +#include "GateOptnComptSplitting.h" +#include "GateOptrLastVertexInteractionSplittingActor.h" +#include "G4ParticleChangeForGamma.hh" +#include "G4VParticleChange.hh" + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptrLastVertexInteractionSplittingActor::GateOptrLastVertexInteractionSplittingActor(py::dict &user_info) + : G4VBiasingOperator("LastVertexInteractionOperator"), + GateVActor(user_info, false) { + fMotherVolumeName = DictGetStr(user_info, "mother"); + fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); + fWeightThreshold = DictGetDouble(user_info, "weight_threshold"); + fMinWeightOfParticle = DictGetDouble(user_info, "min_weight_of_particle"); + // Since the russian roulette uses as a probablity 1/splitting, we need to + // have a double, but the splitting factor provided by the user is logically + // an int, so we need to change the type. + fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); + fBiasPrimaryOnly = DictGetBool(user_info, "bias_primary_only"); + fBiasOnlyOnce = DictGetBool(user_info, "bias_only_once"); + fRussianRoulette = DictGetBool(user_info, "russian_roulette"); + fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); + fMaxTheta = DictGetDouble(user_info, "max_theta"); + fComptSplittingOperation = + new GateOptnComptSplitting("ComptSplittingOperation"); + fActions.insert("StartSimulationAction"); + fActions.insert("SteppingAction"); + fActions.insert("BeginOfEventAction"); +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +void GateOptrLastVertexInteractionSplittingActor::ComptonSplitting(G4Step* CurrentStep,G4Track track,G4Step step,G4VProcess* process,G4int splittingFactor){ +G4TrackVector *trackVector = CurrentStep->GetfSecondary(); +std::cout<<track.GetKineticEnergy()<<std::endl; +for (int i =0; i <splittingFactor; i++){ + G4VParticleChange* processFinalState = process->PostStepDoIt(track,step); + + //Add of splitted primaries + G4ParticleChangeForGamma *gammaProcessFinalState = (G4ParticleChangeForGamma *)processFinalState; + const G4ThreeVector momentum =gammaProcessFinalState->GetProposedMomentumDirection(); + G4double energy = gammaProcessFinalState->GetProposedKineticEnergy(); + G4double globalTime = track.GetGlobalTime(); + G4double gammaWeight = track.GetWeight(); + const G4ThreeVector position = step.GetPostStepPoint()->GetPosition(); + G4Track *newTrack = new G4Track(track); + newTrack->SetWeight(gammaWeight); + newTrack->SetKineticEnergy(energy); + newTrack->SetMomentumDirection(momentum); + newTrack->SetPosition(position); + trackVector->push_back(newTrack); + + + // Add of splitted secondaries (electrons) + + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + for(int j=0; j <NbOfSecondaries; j++){ + G4Track* newTrack = processFinalState->GetSecondary(j); + trackVector->push_back(newTrack); + } + } +} + + +void GateOptrLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* CurrentStep,G4Track track,G4Step step,G4VProcess* process,G4int splittingFactor){ +G4TrackVector *trackVector = CurrentStep->GetfSecondary(); +for (int i =0; i <splittingFactor; i++){ + G4VParticleChange* processFinalState = process->PostStepDoIt(track,step); + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + std::cout<<NbOfSecondaries<<std::endl; + for(int j=0; j <NbOfSecondaries; j++){ + G4Track* newTrack = processFinalState->GetSecondary(j); + trackVector->push_back(newTrack); + } + } +} + +void GateOptrLastVertexInteractionSplittingActor::AttachToAllLogicalDaughtersVolumes( + G4LogicalVolume *volume) { + AttachTo(volume); + G4int nbOfDaughters = volume->GetNoDaughters(); + if (nbOfDaughters > 0) { + for (int i = 0; i < nbOfDaughters; i++) { + G4LogicalVolume *logicalDaughtersVolume = + volume->GetDaughter(i)->GetLogicalVolume(); + AttachToAllLogicalDaughtersVolumes(logicalDaughtersVolume); + } + } +} + +void GateOptrLastVertexInteractionSplittingActor::StartSimulationAction() { + G4LogicalVolume *biasingVolume = + G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + + // Here we need to attach all the daughters and daughters of daughters (...) + // to the biasing operator. To do that, I use the function + // AttachAllLogicalDaughtersVolumes. + AttachToAllLogicalDaughtersVolumes(biasingVolume); + fComptSplittingOperation->SetSplittingFactor(fSplittingFactor); + fComptSplittingOperation->SetWeightThreshold(fWeightThreshold); + fComptSplittingOperation->SetMaxTheta(fMaxTheta); + fComptSplittingOperation->SetRussianRoulette(fRussianRoulette); + fComptSplittingOperation->SetMinWeightOfParticle(fMinWeightOfParticle); +} + +void GateOptrLastVertexInteractionSplittingActor::StartRun() { + + // The way to behave of the russian roulette is the following : + // we provide a vector director and the theta angle acceptance, where theta = + // 0 is a vector colinear to the vector director Then if the track generated + // is on the acceptance angle, we add it to the primary track, and if it's not + // the case, we launch the russian roulette + if (fRotationVectorDirector) { + G4VPhysicalVolume *physBiasingVolume = + G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + auto rot = physBiasingVolume->GetObjectRotationValue(); + fVectorDirector = rot * fVectorDirector; + } + + fComptSplittingOperation->SetVectorDirector(fVectorDirector); +} + +void GateOptrLastVertexInteractionSplittingActor::StartTracking(const G4Track *track) { + fNInteractions = 0; +} + + +void GateOptrLastVertexInteractionSplittingActor::RememberLastProcessInformation(G4Step* step){ + G4String processName = "None"; + if (step->GetPostStepPoint()->GetProcessDefinedStep() !=0){ + processName = step->GetPostStepPoint()->GetProcessDefinedStep()-> GetProcessName(); + } + if ((std::find(fListOfProcesses.begin(),fListOfProcesses.end(),processName) != fListOfProcesses.end())){ + fTrackInformation = G4Track(*(step->GetTrack())); + fStepInformation = G4Step(*step); + fTrackInformation.SetKineticEnergy(fStepInformation.GetPreStepPoint()->GetKineticEnergy()); + fTrackInformation.SetMomentumDirection(fStepInformation.GetPreStepPoint()->GetMomentumDirection()); + fTrackInformation.SetTrackStatus(fAlive); + fTrackInformation.SetPolarization(fStepInformation.GetPreStepPoint()->GetPolarization()); + fProcessToSplit = processName; + fIDOfSplittedTrack = step->GetTrack()->GetTrackID(); + } + +} + +void GateOptrLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G4Step* CurrentStep,G4Track track,G4Step step,G4String processName){ + G4ParticleDefinition* particleDefinition = track.GetDefinition(); + G4ProcessManager* processManager = particleDefinition->GetProcessManager(); + G4ProcessVector* processList = processManager->GetProcessList(); + auto processToSplit = (*processList)[0]; + for (size_t i = 0; i <processList->size(); ++i) { + auto process = (*processList)[i]; + if (process->GetProcessName() == processName){ + processToSplit = process; + } + } + +if (processName == "compt"){ + ComptonSplitting(CurrentStep,track,step,processToSplit,10); +} +else { + std::cout<<processToSplit->GetProcessName()<<std::endl; + SecondariesSplitting(CurrentStep,track,step,processToSplit,10); +} + +} + + +void GateOptrLastVertexInteractionSplittingActor::BeginOfEventAction(const G4Event* event){ + fIDOfSplittedTrack = 0; + fIsSplitted = false; +} + + + +void GateOptrLastVertexInteractionSplittingActor::SteppingAction(G4Step* step) { + G4int trackID = step->GetTrack()->GetTrackID(); + if ((fIsSplitted == true) && ( trackID == fIDOfSplittedTrack - 1)) + fIsSplitted = false; + + if (fIsSplitted == false){ + RememberLastProcessInformation(step); + G4String logicalVolumeNamePostStep = "None"; + if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) + logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(),logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { + if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(),fProcessToSplit) != fListOfProcesses.end()){ + CreateNewParticleAtTheLastVertex(step,fTrackInformation,fStepInformation,fProcessToSplit); + fIsSplitted = true; + fIDOfSplittedTrack = trackID; + fProcessToSplit = "None"; + } + } + } +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + + + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.h new file mode 100644 index 000000000..f455598d9 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.h @@ -0,0 +1,122 @@ +// +// ******************************************************************** +// * 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 GateOptrLastVertexInteractionSplittingActor.h +/// \brief Definition of the GateOptrLastVertexInteractionSplittingActor class +#ifndef GateOptrLastVertexInteractionSplittingActor_h +#define GateOptrLastVertexInteractionSplittingActor_h 1 + +#include "G4VBiasingOperator.hh" + +#include "GateVActor.h" +#include <iostream> +#include <pybind11/stl.h> +namespace py = pybind11; + +class GateOptnComptSplitting; + +class GateOptrLastVertexInteractionSplittingActor : public G4VBiasingOperator, + public GateVActor { +public: + GateOptrLastVertexInteractionSplittingActor(py::dict &user_info); + virtual ~GateOptrLastVertexInteractionSplittingActor() {} + +public: + // ------------------------- + // Optional from base class: + // ------------------------- + // -- Call at run start: + // virtual void BeginOfRunAction(const G4Run *run); + + // virtual void SteppingAction(G4Step* step); + + // -- Call at each track starting: + // virtual void PreUserTrackingAction( const G4Track* track ); + + G4double fSplittingFactor; + G4double fMinWeightOfParticle; + G4double fWeightThreshold; + G4bool fBiasPrimaryOnly; + G4bool fBiasOnlyOnce; + G4int fNInteractions = 0; + G4bool fRussianRoulette; + G4bool fRotationVectorDirector; + G4ThreeVector fVectorDirector; + G4double fMaxTheta; + G4Track fTrackInformation; + G4Step fStepInformation; + G4String fProcessToSplit = "None"; + G4int fIDOfSplittedTrack = 0; + G4bool fIsSplitted = false; + std::vector<std::string> fListOfVolumeAncestor; + + std::vector<G4String> fListOfProcesses = {"compt","conv","eBrem"}; + // Unused but mandatory + + virtual void StartSimulationAction(); + virtual void SteppingAction(G4Step *) override; + virtual void BeginOfEventAction(const G4Event *) override; + virtual void StartRun(); + virtual void StartTracking(const G4Track *); + virtual void EndTracking() {} + +protected: + // ----------------------------- + // -- Mandatory from base class: + // ----------------------------- + // -- Unused: + void AttachToAllLogicalDaughtersVolumes(G4LogicalVolume *); + void ComptonSplitting(G4Step* CurrentStep,G4Track track,G4Step step,G4VProcess* process,G4int splittingFactor); + void SecondariesSplitting(G4Step* CurrentStep,G4Track track,G4Step step,G4VProcess* process,G4int splittingFactor); + void RememberLastProcessInformation(G4Step*); + void CreateNewParticleAtTheLastVertex(G4Step*,G4Track,G4Step,G4String); + virtual G4VBiasingOperation *ProposeNonPhysicsBiasingOperation( + const G4Track * /* track */, + const G4BiasingProcessInterface * /* callingProcess */) { + return 0; + } + virtual G4VBiasingOperation *ProposeOccurenceBiasingOperation( + const G4Track * /* track */, + const G4BiasingProcessInterface * /* callingProcess */) { + return 0; + } + + // -- Used: + virtual G4VBiasingOperation *ProposeFinalStateBiasingOperation( + const G4Track *track, const G4BiasingProcessInterface *callingProcess){ + return 0; + } + +private: + // -- Avoid compiler complaining for (wrong) method shadowing, + // -- this is because other virtual method with same name exists. + using G4VBiasingOperator::OperationApplied; + +private: + GateOptnComptSplitting *fComptSplittingOperation; +}; + +#endif diff --git a/core/opengate_core/opengate_lib/pyGateOptrLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/pyGateOptrLastVertexInteractionSplittingActor.cpp new file mode 100644 index 000000000..8fc59fa3b --- /dev/null +++ b/core/opengate_core/opengate_lib/pyGateOptrLastVertexInteractionSplittingActor.cpp @@ -0,0 +1,22 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ +#include <pybind11/pybind11.h> + +namespace py = pybind11; +#include "G4VBiasingOperator.hh" +#include "GateOptrLastVertexInteractionSplittingActor.h" + +void init_GateOptrLastVertexInteractionSplittingActor(py::module &m) { + + py::class_<GateOptrLastVertexInteractionSplittingActor, G4VBiasingOperator, GateVActor, + std::unique_ptr<GateOptrLastVertexInteractionSplittingActor, py::nodelete>>( + m, "GateOptrLastVertexInteractionSplittingActor") + .def_readwrite( + "fListOfVolumeAncestor", + &GateOptrLastVertexInteractionSplittingActor::fListOfVolumeAncestor) + .def(py::init<py::dict &>()); +} diff --git a/opengate/actors/actorbuilders.py b/opengate/actors/actorbuilders.py index 8b6c2f697..1f3cd7089 100644 --- a/opengate/actors/actorbuilders.py +++ b/opengate/actors/actorbuilders.py @@ -19,6 +19,7 @@ KillActor, BremSplittingActor, ComptSplittingActor, + LastVertexInteractionSplittingActor, ) from .dynamicactors import DynamicGeometryActor from ..utility import make_builders @@ -46,6 +47,7 @@ KillActor, BremSplittingActor, ComptSplittingActor, + LastVertexInteractionSplittingActor, DynamicGeometryActor, } actor_builders = make_builders(actor_type_names) diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 419082e6f..bad189dd5 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -5,6 +5,7 @@ import numpy as np import time import platform +from anytree import Node, RenderTree import opengate_core as g4 from .base import ActorBase from ..exception import fatal @@ -429,17 +430,6 @@ def __init__(self, user_info): g4.GateKillActor.__init__(self, user_info.__dict__) -""" -class ComptonSplittingActor(g4.GateComptonSplittingActor,ActorBase): - type_name = "ComptonSplittingActor" - def set_default_user_info(user_info): - ActorBase.set_default_user_info(user_info) - user_info.SplittingFactor = 0 - - def __init__(self, user_info): - ActorBase.__init__(self, user_info) - g4.GateComptonSplittingActor.__init__(self, user_info.__dict__) -""" class ComptSplittingActor(g4.GateOptrComptSplittingActor, ActorBase): @@ -464,6 +454,46 @@ def __init__(self, user_info): g4.GateOptrComptSplittingActor.__init__(self, user_info.__dict__) +class LastVertexInteractionSplittingActor(g4.GateOptrLastVertexInteractionSplittingActor, ActorBase): + type_name = "LastVertexInteractionSplittingActor" + + def set_default_user_info(user_info): + ActorBase.set_default_user_info(user_info) + deg = g4_units.deg + user_info.splitting_factor = 1 + user_info.weight_threshold = 0 + user_info.bias_primary_only = True + user_info.min_weight_of_particle = 0 + user_info.bias_only_once = True + user_info.processes = ["phot"] + user_info.russian_roulette = False + user_info.rotation_vector_director = False + user_info.vector_director = [0, 0, 1] + user_info.max_theta = 90 * deg + user_info.list_of_volume_name = [] + + def __init__(self, user_info): + ActorBase.__init__(self, user_info) + g4.GateOptrLastVertexInteractionSplittingActor.__init__(self, user_info.__dict__) + self.list_of_volume_name = user_info.list_of_volume_name + self.user_info.mother = user_info.mother + + def initialize(self, volume_engine=None): + + super().initialize(volume_engine) + volume_tree = self.simulation.volume_manager.get_volume_tree() + dico_of_volume_tree = {} + for pre, _, node in RenderTree(volume_tree): + dico_of_volume_tree[str(node.name)] = node + volume_name = self.user_info.mother + while volume_name != "world": + node = dico_of_volume_tree[volume_name] + volume_name = node.mother + self.list_of_volume_name.append(volume_name) + self.fListOfVolumeAncestor = self.list_of_volume_name + print(self.fListOfVolumeAncestor) + + class BremSplittingActor(g4.GateBOptrBremSplittingActor, ActorBase): type_name = "BremSplittingActor" diff --git a/opengate/managers.py b/opengate/managers.py index d2f8620b4..20cc159ab 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -1033,7 +1033,11 @@ def dump_volume_types(self): for vt in self.volume_types: s += f"{vt} " return s - + + def get_volume_tree(self): + return self.volume_tree_root + + def dump_material_database_names(self): return list(self.material_database.filenames) From f81bbbbb51cf45bed9c082af1b7346c953043413 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Thu, 13 Jun 2024 10:17:27 +0200 Subject: [PATCH 21/82] improvement of the method, adding a unbiased splitting method for the pair production --- ...ptrLastVertexInteractionSplittingActor.cpp | 525 ++++++++++++++---- ...eOptrLastVertexInteractionSplittingActor.h | 79 ++- ...ptrLastVertexInteractionSplittingActor.cpp | 3 +- opengate/actors/miscactors.py | 9 +- .../src/test076_last_vertex_splittting.py | 240 ++++++++ 5 files changed, 686 insertions(+), 170 deletions(-) create mode 100755 opengate/tests/src/test076_last_vertex_splittting.py diff --git a/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.cpp index 6181d6dd8..b38ed6fdf 100644 --- a/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.cpp @@ -33,6 +33,7 @@ #include "CLHEP/Units/SystemOfUnits.h" #include "G4BiasingProcessInterface.hh" #include "G4Gamma.hh" +#include "G4Positron.hh" #include "G4LogicalVolumeStore.hh" #include "G4ParticleTable.hh" #include "G4PhysicalVolumeStore.hh" @@ -41,150 +42,182 @@ #include "G4ProcessVector.hh" #include "GateOptnComptSplitting.h" #include "GateOptrLastVertexInteractionSplittingActor.h" -#include "G4ParticleChangeForGamma.hh" #include "G4VParticleChange.hh" //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptrLastVertexInteractionSplittingActor::GateOptrLastVertexInteractionSplittingActor(py::dict &user_info) - : G4VBiasingOperator("LastVertexInteractionOperator"), - GateVActor(user_info, false) { +GateOptrLastVertexInteractionSplittingActor::GateOptrLastVertexInteractionSplittingActor(py::dict &user_info):GateVActor(user_info, false) { fMotherVolumeName = DictGetStr(user_info, "mother"); fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); - fWeightThreshold = DictGetDouble(user_info, "weight_threshold"); - fMinWeightOfParticle = DictGetDouble(user_info, "min_weight_of_particle"); - // Since the russian roulette uses as a probablity 1/splitting, we need to - // have a double, but the splitting factor provided by the user is logically - // an int, so we need to change the type. fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); - fBiasPrimaryOnly = DictGetBool(user_info, "bias_primary_only"); - fBiasOnlyOnce = DictGetBool(user_info, "bias_only_once"); - fRussianRoulette = DictGetBool(user_info, "russian_roulette"); + fRussianRouletteForAngle = DictGetBool(user_info, "russian_roulette_for_angle"); fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); fMaxTheta = DictGetDouble(user_info, "max_theta"); - fComptSplittingOperation = - new GateOptnComptSplitting("ComptSplittingOperation"); fActions.insert("StartSimulationAction"); fActions.insert("SteppingAction"); fActions.insert("BeginOfEventAction"); + fActions.insert("BeginOfRunAction"); + fActions.insert("PreUserTrackingAction"); + fActions.insert("PostUserTrackingAction"); } //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -void GateOptrLastVertexInteractionSplittingActor::ComptonSplitting(G4Step* CurrentStep,G4Track track,G4Step step,G4VProcess* process,G4int splittingFactor){ -G4TrackVector *trackVector = CurrentStep->GetfSecondary(); -std::cout<<track.GetKineticEnergy()<<std::endl; -for (int i =0; i <splittingFactor; i++){ - G4VParticleChange* processFinalState = process->PostStepDoIt(track,step); - - //Add of splitted primaries - G4ParticleChangeForGamma *gammaProcessFinalState = (G4ParticleChangeForGamma *)processFinalState; - const G4ThreeVector momentum =gammaProcessFinalState->GetProposedMomentumDirection(); - G4double energy = gammaProcessFinalState->GetProposedKineticEnergy(); - G4double globalTime = track.GetGlobalTime(); - G4double gammaWeight = track.GetWeight(); - const G4ThreeVector position = step.GetPostStepPoint()->GetPosition(); - G4Track *newTrack = new G4Track(track); - newTrack->SetWeight(gammaWeight); - newTrack->SetKineticEnergy(energy); - newTrack->SetMomentumDirection(momentum); - newTrack->SetPosition(position); - trackVector->push_back(newTrack); - - // Add of splitted secondaries (electrons) - - G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - for(int j=0; j <NbOfSecondaries; j++){ - G4Track* newTrack = processFinalState->GetSecondary(j); - trackVector->push_back(newTrack); +G4double GateOptrLastVertexInteractionSplittingActor::RussianRouletteForAngleSurvival(G4ThreeVector dir, G4ThreeVector vectorDirector, G4double maxTheta,G4double split) { + G4double cosTheta = vectorDirector * dir; + G4double theta = std::acos(cosTheta); + G4double weightToApply = 1; + if (theta > fMaxTheta) { + G4double probability = G4UniformRand(); + if (probability <= 1 / split) { + weightToApply = split; + } else { + weightToApply = 0; } } + return weightToApply; +} + +G4Track* GateOptrLastVertexInteractionSplittingActor:: CreateComptonTrack(G4ParticleChangeForGamma* gammaProcess,G4Track track, G4double weight){ + G4double energy = gammaProcess->GetProposedKineticEnergy(); + G4double globalTime = track.GetGlobalTime(); + G4double newGammaWeight = weight; + const G4ThreeVector momentum =gammaProcess->GetProposedMomentumDirection(); + const G4ThreeVector position = track.GetPosition(); + G4Track *newTrack = new G4Track(track); + newTrack->SetWeight(newGammaWeight); + newTrack->SetKineticEnergy(energy); + newTrack->SetMomentumDirection(momentum); + newTrack->SetPosition(position); + return newTrack; } +void GateOptrLastVertexInteractionSplittingActor::ComptonSplitting(G4Step* CurrentStep,G4Track track,const G4Step *step,G4VProcess* process){ +//Loop on process and add the secondary tracks to the current step secondary vector + + -void GateOptrLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* CurrentStep,G4Track track,G4Step step,G4VProcess* process,G4int splittingFactor){ G4TrackVector *trackVector = CurrentStep->GetfSecondary(); -for (int i =0; i <splittingFactor; i++){ - G4VParticleChange* processFinalState = process->PostStepDoIt(track,step); - G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - std::cout<<NbOfSecondaries<<std::endl; - for(int j=0; j <NbOfSecondaries; j++){ - G4Track* newTrack = processFinalState->GetSecondary(j); - trackVector->push_back(newTrack); +G4double gammaWeight = CurrentStep->GetTrack()->GetWeight()/fSplittingFactor; +G4int nCall = (G4int) fSplittingFactor; +for (int i =0; i <nCall; i++){ + G4VParticleChange* processFinalState = process->PostStepDoIt(track,*step); + G4ParticleChangeForGamma *gammaProcessFinalState = (G4ParticleChangeForGamma *)processFinalState; + const G4ThreeVector momentum =gammaProcessFinalState->GetProposedMomentumDirection(); + + if (fRussianRouletteForAngle == true) { + G4double weightToApply = RussianRouletteForAngleSurvival(momentum,fVectorDirector, fMaxTheta, fSplittingFactor); + if (weightToApply != 0){ + gammaWeight = gammaWeight * weightToApply; + G4Track* newTrack = CreateComptonTrack(gammaProcessFinalState,track,gammaWeight); + trackVector->push_back(newTrack); + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + for(int j=0; j <NbOfSecondaries; j++){ + G4Track* newTrack = processFinalState->GetSecondary(j); + newTrack->SetWeight(gammaWeight); + trackVector->push_back(newTrack); + } + } } - } -} - -void GateOptrLastVertexInteractionSplittingActor::AttachToAllLogicalDaughtersVolumes( - G4LogicalVolume *volume) { - AttachTo(volume); - G4int nbOfDaughters = volume->GetNoDaughters(); - if (nbOfDaughters > 0) { - for (int i = 0; i < nbOfDaughters; i++) { - G4LogicalVolume *logicalDaughtersVolume = - volume->GetDaughter(i)->GetLogicalVolume(); - AttachToAllLogicalDaughtersVolumes(logicalDaughtersVolume); + + else { + G4Track* newTrack = CreateComptonTrack(gammaProcessFinalState,track,gammaWeight); + trackVector->push_back(newTrack); + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + for(int j=0; j <NbOfSecondaries; j++){ + G4Track* newTrack = processFinalState->GetSecondary(j); + newTrack->SetWeight(gammaWeight); + trackVector->push_back(newTrack); + + } } } } -void GateOptrLastVertexInteractionSplittingActor::StartSimulationAction() { - G4LogicalVolume *biasingVolume = - G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); - // Here we need to attach all the daughters and daughters of daughters (...) - // to the biasing operator. To do that, I use the function - // AttachAllLogicalDaughtersVolumes. - AttachToAllLogicalDaughtersVolumes(biasingVolume); - fComptSplittingOperation->SetSplittingFactor(fSplittingFactor); - fComptSplittingOperation->SetWeightThreshold(fWeightThreshold); - fComptSplittingOperation->SetMaxTheta(fMaxTheta); - fComptSplittingOperation->SetRussianRoulette(fRussianRoulette); - fComptSplittingOperation->SetMinWeightOfParticle(fMinWeightOfParticle); -} - -void GateOptrLastVertexInteractionSplittingActor::StartRun() { +void GateOptrLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* CurrentStep,G4Track track,const G4Step *step,G4VProcess* process){ +//Loop on process and add the secondary tracks to the current step secondary vector. - // The way to behave of the russian roulette is the following : - // we provide a vector director and the theta angle acceptance, where theta = - // 0 is a vector colinear to the vector director Then if the track generated - // is on the acceptance angle, we add it to the primary track, and if it's not - // the case, we launch the russian roulette - if (fRotationVectorDirector) { - G4VPhysicalVolume *physBiasingVolume = - G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); - auto rot = physBiasingVolume->GetObjectRotationValue(); - fVectorDirector = rot * fVectorDirector; +G4TrackVector *trackVector = CurrentStep->GetfSecondary(); +G4int nCall = (G4int) fSplittingFactor; +for (int i =0; i <nCall; i++){ + G4VParticleChange* processFinalState =nullptr; + processFinalState = process->PostStepDoIt(track,*step); + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + for(int j=0; j <NbOfSecondaries; j++){ + G4Track* newTrack = processFinalState->GetSecondary(j); + trackVector->push_back(newTrack); + } } - - fComptSplittingOperation->SetVectorDirector(fVectorDirector); } -void GateOptrLastVertexInteractionSplittingActor::StartTracking(const G4Track *track) { - fNInteractions = 0; -} +void GateOptrLastVertexInteractionSplittingActor::RememberLastProcessInformation(G4Step* step){ + // When an interesting process to split occurs, we remember the status of the track and the process at this current step + // some informations regarding the track info have to be changed because they were update according to the interaction that occured + // These informations are sotcked as a map object, binding the track ID with all the track objects and processes to split. + // Because in some cases, if a secondary was created before an interaction chain, this secondary will be track after the chain and + // without this association, we wll loose the information about the process occuring for this secondary. -void GateOptrLastVertexInteractionSplittingActor::RememberLastProcessInformation(G4Step* step){ + G4String creatorProcessName = "None"; + if (step->GetTrack()->GetCreatorProcess() !=0) + creatorProcessName = step->GetTrack()->GetCreatorProcess()->GetProcessName(); G4String processName = "None"; + G4int trackID = step->GetTrack()->GetTrackID(); if (step->GetPostStepPoint()->GetProcessDefinedStep() !=0){ processName = step->GetPostStepPoint()->GetProcessDefinedStep()-> GetProcessName(); } + if ((step->GetTrack()->GetParticleDefinition()->GetParticleName() == "e+") && ((step->GetTrack()->GetTrackStatus() == 1) || (step->GetTrack()->GetTrackStatus() == 2))){ + processName = "annihil"; + } if ((std::find(fListOfProcesses.begin(),fListOfProcesses.end(),processName) != fListOfProcesses.end())){ - fTrackInformation = G4Track(*(step->GetTrack())); - fStepInformation = G4Step(*step); - fTrackInformation.SetKineticEnergy(fStepInformation.GetPreStepPoint()->GetKineticEnergy()); - fTrackInformation.SetMomentumDirection(fStepInformation.GetPreStepPoint()->GetMomentumDirection()); - fTrackInformation.SetTrackStatus(fAlive); - fTrackInformation.SetPolarization(fStepInformation.GetPreStepPoint()->GetPolarization()); - fProcessToSplit = processName; - fIDOfSplittedTrack = step->GetTrack()->GetTrackID(); + G4Track trackInformation = G4Track(*(step->GetTrack())); + trackInformation.SetKineticEnergy(step->GetPreStepPoint()->GetKineticEnergy()); + trackInformation.SetMomentumDirection(step->GetPreStepPoint()->GetMomentumDirection()); + trackInformation.SetTrackStatus(fAlive); + trackInformation.SetPolarization(step->GetPreStepPoint()->GetPolarization()); + trackInformation.SetTrackID(trackID); + if (auto search = fRememberedTracks.find(trackID); search != fRememberedTracks.end()){ + fRememberedTracks[trackID].push_back(trackInformation); + fRememberedProcesses[trackID].push_back(processName); + ; + } + else { + fRememberedTracks[trackID] = {trackInformation}; + fRememberedProcesses[trackID] = {processName}; + + + } } + else { + + + G4int parentID = step->GetTrack()->GetParentID(); + if (auto search = fRememberedTracks.find(trackID); search == fRememberedTracks.end()){ + if (auto search = fRememberedTracks.find(parentID); search != fRememberedTracks.end()){ + if (auto it = std::find(fRememberedProcesses[parentID].begin(),fRememberedProcesses[parentID].end(),creatorProcessName); it != fRememberedProcesses[parentID].end()){ + auto idx = it- fRememberedProcesses[parentID].begin(); + fRememberedTracks[trackID] = {fRememberedTracks[parentID][idx]}; + fRememberedProcesses[trackID] = {fRememberedProcesses[parentID][idx]}; + } + else { + fRememberedTracks[trackID] = {fRememberedTracks[parentID][0]}; + fRememberedProcesses[trackID] = {fRememberedProcesses[parentID][0]}; + + } + } + } + } } -void GateOptrLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G4Step* CurrentStep,G4Track track,G4Step step,G4String processName){ + +void GateOptrLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G4Step* CurrentStep,G4Track track,const G4Step* step,G4String processName){ + //We retrieve the process associated to the process name to split and we split according the process. + //Since for compton scattering, the gamma is not a secondary particles, this one need to have his own splitting function. + + G4ParticleDefinition* particleDefinition = track.GetDefinition(); G4ProcessManager* processManager = particleDefinition->GetProcessManager(); G4ProcessVector* processList = processManager->GetProcessList(); @@ -195,46 +228,310 @@ void GateOptrLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVert processToSplit = process; } } + if (processName == "compt"){ + ComptonSplitting(CurrentStep,track,step,processToSplit); + } + else { + SecondariesSplitting(CurrentStep,track,step,processToSplit); + } -if (processName == "compt"){ - ComptonSplitting(CurrentStep,track,step,processToSplit,10); } -else { - std::cout<<processToSplit->GetProcessName()<<std::endl; - SecondariesSplitting(CurrentStep,track,step,processToSplit,10); + + +void GateOptrLastVertexInteractionSplittingActor::ResetProcessesForEnteringParticles(G4Step * step){ + +// This function reset the processes and track registered to be split each time a particle enters into a volume. +// Here a new particle incoming is either a particle entering into the volume ( first pre step is a boundary of a mother volume +// or first step trigger the actor and the vertex is not either the mother volume or a the fdaughter volume) or an event generated within +// the volume (the particle did not enter into the volume and its parentID = 0). An additionnal condition is set with the trackID to ensure +// particles crossing twice the volume (after either a compton or pair prod) are not splitted. + + +G4int trackID = step->GetTrack()->GetTrackID(); +if ((fEventIDOfInitialSplittedTrack != fEventID) || ((fEventIDOfInitialSplittedTrack == fEventID ) && (trackID < fTrackIDOfInitialTrack))) + { + G4String logicalVolumeNamePreStep = "None"; + if (step->GetPreStepPoint()->GetPhysicalVolume() !=0){ + logicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + } + if (((step->GetPreStepPoint()->GetStepStatus() == 1) && (logicalVolumeNamePreStep == fMotherVolumeName)) || + ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != logicalVolumeNamePreStep) && (step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() !=fMotherVolumeName))) { + fTrackIDOfInitialTrack = trackID; + fEventIDOfInitialSplittedTrack = fEventID; + fRememberedProcesses.clear(); + fRememberedTracks.clear(); + + } + else if (step->GetTrack()->GetParentID() == 0){ + fTrackIDOfInitialTrack = trackID; + fEventIDOfInitialSplittedTrack = fEventID; + fRememberedProcesses.clear(); + fRememberedTracks.clear(); + } + } + } + + +void GateOptrLastVertexInteractionSplittingActor::PostponeFirstAnnihilationTrackIfInteraction(G4Step* step,G4String processName){ + //In case the first gamma issued fromannihilation undergoes an interaction, in order to not bias the process + //We keep in memory the particle post step state (with its secondaries) and kill the particle and its secondaries. + //If the second photon from annihilation exiting the collimation system with an interaction or is absorbed within + //the collimation, the particle is subsequently resimulated, starting from the interaction point. + + + G4int trackID = step->GetTrack()->GetTrackID(); + if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(),processName) != fListOfProcesses.end()){ + fSuspendForAnnihil = true; + G4Track trackToPostpone = G4Track(*(step->GetTrack())); + trackToPostpone.SetKineticEnergy(step->GetPostStepPoint()->GetKineticEnergy()); + trackToPostpone.SetMomentumDirection(step->GetPostStepPoint()->GetMomentumDirection()); + trackToPostpone.SetTrackStatus(step->GetTrack()->GetTrackStatus()); + trackToPostpone.SetPolarization(step->GetPostStepPoint()->GetPolarization()); + trackToPostpone.SetPosition(step->GetPostStepPoint()->GetPosition()); + trackToPostpone.SetTrackID(trackID); + fTracksToPostpone.push_back(trackToPostpone); + auto theTrack = fTracksToPostpone[0]; + + + auto secVec = step->GetfSecondary(); + for (int i = 0; i< secVec->size();i++){ + G4Track* sec = (*secVec)[i]; + G4Track copySec = G4Track((*sec)); + fTracksToPostpone.push_back(copySec); + } + + fTracksToPostpone[0].SetTrackID(trackID); + step->GetTrack()->SetTrackStatus( fKillTrackAndSecondaries); + + } } +void GateOptrLastVertexInteractionSplittingActor::RegenerationOfPostponedAnnihilationTrack(G4Step* step){ + +//If the second photon from annihilation suceed to exit the collimation system with at least one interaction or was absorbed. +//Resimulation of annihilation photons and its potential secondaries. + +G4TrackVector* currentSecondaries = step->GetfSecondary(); +for (int i =0; i < fTracksToPostpone.size();i ++){ + G4Track* trackToAdd = new G4Track(fTracksToPostpone[fTracksToPostpone.size() - 1 - i]); + trackToAdd->SetParentID(step->GetTrack()->GetTrackID() - 1); + + currentSecondaries->insert(currentSecondaries->begin(),trackToAdd); +} +G4Track* firstPostponedTrack = (*currentSecondaries)[0]; + +// Handle of case were the interaction killed the photon issued from annihilation, it will not be track at the following state +// and the boolean plus the track vector need to be reset + +if (firstPostponedTrack->GetTrackStatus() == 2){ + fSuspendForAnnihil = false; + fTracksToPostpone.clear(); +} + +} + +void GateOptrLastVertexInteractionSplittingActor::HandleTrackIDIfPostponedAnnihilation(G4Step* step){ +// The ID is to modify trackID and the processes and tracks sotcked for a specific trackID associated to +// the postponed annihilation to respect the trackID order in GEANT4. Since in this case the second photon is tracked before the first one +// his trackID +=1. For the postponed one, he is the first secondary particle of the second photon tracks, his trackID is therefore equal +// to the second photon trackID +1 instead of the second photon trackID -1. +// The parentID of secondary particles are also modified because they are used in the rememberlastprocess function +// At last the value associated to a specific trackID in the process and track map are modified according to the new trackID. + + + + if (fSuspendForAnnihil){ + if (step->GetTrack()->GetTrackID() == fTracksToPostpone[0].GetTrackID() -1){ + fRememberedProcesses[step->GetTrack()->GetTrackID()] = fRememberedProcesses[step->GetTrack()->GetTrackID() + 1]; + fRememberedProcesses.erase(step->GetTrack()->GetTrackID() + 1); + fRememberedTracks[step->GetTrack()->GetTrackID()] = fRememberedTracks[step->GetTrack()->GetTrackID() + 1]; + fRememberedTracks.erase(step->GetTrack()->GetTrackID() + 1); + auto vecSec = step->GetSecondary(); + for (int i =0; i <vecSec->size(); i++) + { + (*vecSec)[i]->SetParentID(step->GetTrack()->GetTrackID() +1); + } + step->GetTrack()->SetTrackID(step->GetTrack()->GetTrackID() +1); + } + if (step->GetTrack()->GetTrackID() == fTracksToPostpone[0].GetTrackID() + 1){ + auto vecSec = step->GetSecondary(); + for (int i =0; i <vecSec->size(); i++) + { + (*vecSec)[i]->SetParentID(step->GetTrack()->GetTrackID() -2); + } + step->GetTrack()->SetTrackID(step->GetTrack()->GetTrackID() - 2); + } + } +} + + +void GateOptrLastVertexInteractionSplittingActor::BeginOfRunAction(const G4Run* run){ + + // The way to behave of the russian roulette is the following : + // we provide a vector director and the theta angle acceptance, where theta = + // 0 is a vector colinear to the vector director Then if the track generated + // is on the acceptance angle, we add it to the primary track, and if it's not + // the case, we launch the russian roulette + + + if (fRotationVectorDirector) { + G4VPhysicalVolume *physBiasingVolume = + G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + auto rot = physBiasingVolume->GetObjectRotationValue(); + fVectorDirector = rot * fVectorDirector; + } } + + + void GateOptrLastVertexInteractionSplittingActor::BeginOfEventAction(const G4Event* event){ - fIDOfSplittedTrack = 0; + + + fParentID = -1; fIsSplitted = false; + fSecondaries = false; + fEventID = event ->GetEventID(); + fEventIDOfSplittedTrack = -1; + fTrackIDOfSplittedTrack = -1; + +} + + + +void GateOptrLastVertexInteractionSplittingActor::PreUserTrackingAction (const G4Track *track){ + fIsFirstStep = true; } + + + void GateOptrLastVertexInteractionSplittingActor::SteppingAction(G4Step* step) { - G4int trackID = step->GetTrack()->GetTrackID(); - if ((fIsSplitted == true) && ( trackID == fIDOfSplittedTrack - 1)) - fIsSplitted = false; + if (fIsFirstStep) { + HandleTrackIDIfPostponedAnnihilation(step); + + // To be sure to not split a track which has been already splitted, we put a condition on the trackID of + if ((fIsSplitted == true) && ( step->GetTrack()->GetTrackID() < fTrackIDOfSplittedTrack)){ + fIsSplitted = false; + } + + ResetProcessesForEnteringParticles(step); + } + G4int trackID = step->GetTrack()->GetTrackID(); + + + //std::cout<<trackID<<" "<<step->GetTrack()->GetTrackStatus()<<" "<<step->GetPreStepPoint()->GetKineticEnergy()<<" " <<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetPreStepPoint()->GetPosition()<<" "<<step->GetPostStepPoint()->GetPosition()<<std::endl; + if (fIsSplitted == false){ + RememberLastProcessInformation(step); + G4String process = "None"; + if (auto search = fRememberedProcesses.find(trackID); search != fRememberedProcesses.end()) + process = fRememberedProcesses[trackID].back(); + + + G4String creatorProcessName = "None"; + if (step->GetTrack()->GetCreatorProcess() != 0) + creatorProcessName = step->GetTrack()->GetCreatorProcess()->GetProcessName(); + + + if ((!fSuspendForAnnihil )&& (process != "annihil") && (creatorProcessName == "annihil")){ + PostponeFirstAnnihilationTrackIfInteraction(step,process); + } + + // If the first annihilation photon exit the collimation and the process to split is annihilation + // We kill the second photon, because the annihilation will generate both the photons. + if ((!fSuspendForAnnihil ) && (creatorProcessName == "annihil") && (fTrackIDOfSplittedTrack == step->GetTrack()->GetParentID()) && (fEventID == fEventIDOfSplittedTrack )) + step->GetTrack()->SetTrackStatus(fStopAndKill); + + if (fSuspendForAnnihil){ + if (trackID == fTracksToPostpone[0].GetTrackID() - 1){ + fSuspendForAnnihil = false; + fTracksToPostpone.clear(); + } + } + + G4String logicalVolumeNamePostStep = "None"; if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(),logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { - if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(),fProcessToSplit) != fListOfProcesses.end()){ - CreateNewParticleAtTheLastVertex(step,fTrackInformation,fStepInformation,fProcessToSplit); - fIsSplitted = true; - fIDOfSplittedTrack = trackID; - fProcessToSplit = "None"; + G4String processToSplit = "None"; + if (auto search = fRememberedProcesses.find(trackID); search != fRememberedProcesses.end()) + processToSplit = fRememberedProcesses[trackID].back(); + + if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(),processToSplit) != fListOfProcesses.end()){ + + //Handle of pecularities (1): + + // If the process t split is the gamma issued from compton interaction, the electron primary generated have to be killed + // given that electron will be regenerated + + if ((processToSplit == "compt") && (step->GetTrack()->GetParticleDefinition()->GetParticleName() == "gamma")){ + auto secondaries = step->GetfSecondary(); + if (secondaries->size() > 0){ + G4Track* lastSecTrack = secondaries->back(); + lastSecTrack->SetTrackStatus(fStopAndKill); + } + } + + //Handle of pecularities (2): + + // If the process to split is the annihilation, the second photon, postponed or not, have to be killed + // the reset of the postpone is performed here, whereas the kill of the next annihilation photon, if not postponed + // is realised at the beginning of the step tracking. + + + if (processToSplit == "annihil"){ + if (fSuspendForAnnihil){ + fSuspendForAnnihil = false; + fTracksToPostpone.clear(); + } + } + + + G4Track trackToSplit = fRememberedTracks[trackID].back(); + CreateNewParticleAtTheLastVertex(step,trackToSplit,trackToSplit.GetStep(),processToSplit); + + + fSecondaries = true; + fTrackIDOfSplittedTrack = trackToSplit.GetTrackID(); + fEventIDOfSplittedTrack = fEventID; + fTrackIDOfInitialSplittedTrack = fTrackIDOfInitialTrack; + step->GetTrack()->SetTrackStatus(fStopAndKill); + } + } + } + + + if ((fSuspendForAnnihil) && ((step->GetTrack()->GetTrackStatus() == 1) || (step->GetTrack()->GetTrackStatus() == 2))){ + if (trackID == fTracksToPostpone[0].GetTrackID()) { + RegenerationOfPostponedAnnihilationTrack(step); } } + + fIsFirstStep = false; + } + + + + + +void GateOptrLastVertexInteractionSplittingActor::PostUserTrackingAction (const G4Track *track){ + if (fSecondaries){ + fIsSplitted = true; + fSecondaries = false; } } + + + //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.h index f455598d9..24727beb1 100644 --- a/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.h @@ -29,17 +29,15 @@ #ifndef GateOptrLastVertexInteractionSplittingActor_h #define GateOptrLastVertexInteractionSplittingActor_h 1 -#include "G4VBiasingOperator.hh" - #include "GateVActor.h" +#include "G4ParticleChangeForGamma.hh" #include <iostream> #include <pybind11/stl.h> namespace py = pybind11; class GateOptnComptSplitting; -class GateOptrLastVertexInteractionSplittingActor : public G4VBiasingOperator, - public GateVActor { +class GateOptrLastVertexInteractionSplittingActor : public GateVActor { public: GateOptrLastVertexInteractionSplittingActor(py::dict &user_info); virtual ~GateOptrLastVertexInteractionSplittingActor() {} @@ -57,63 +55,50 @@ class GateOptrLastVertexInteractionSplittingActor : public G4VBiasingOperator, // virtual void PreUserTrackingAction( const G4Track* track ); G4double fSplittingFactor; - G4double fMinWeightOfParticle; - G4double fWeightThreshold; - G4bool fBiasPrimaryOnly; - G4bool fBiasOnlyOnce; - G4int fNInteractions = 0; - G4bool fRussianRoulette; + G4bool fRussianRouletteForAngle = false; G4bool fRotationVectorDirector; G4ThreeVector fVectorDirector; G4double fMaxTheta; - G4Track fTrackInformation; - G4Step fStepInformation; - G4String fProcessToSplit = "None"; - G4int fIDOfSplittedTrack = 0; + G4int fTrackIDOfSplittedTrack = 0; G4bool fIsSplitted = false; + G4bool fSecondaries = false; + G4int fParentID = -1; + G4int fEventID; + G4int fEventIDOfSplittedTrack; + G4int fEventIDOfInitialSplittedTrack; + G4int fTrackIDOfInitialTrack; + G4int fTrackIDOfInitialSplittedTrack = 0; + G4int ftmpTrackID; + G4bool fIsFirstStep = true; + G4bool fSuspendForAnnihil = false; + std::vector<G4Track> fTracksToPostpone; + + std::map<G4int,std::vector<G4Track>> fRememberedTracks; + std::map<G4int,std::vector<G4String>> fRememberedProcesses; + std::vector<std::string> fListOfVolumeAncestor; - std::vector<G4String> fListOfProcesses = {"compt","conv","eBrem"}; + std::vector<G4String> fListOfProcesses = {"compt","eBrem","annihil","conv","phot"}; // Unused but mandatory - virtual void StartSimulationAction(); virtual void SteppingAction(G4Step *) override; virtual void BeginOfEventAction(const G4Event *) override; - virtual void StartRun(); - virtual void StartTracking(const G4Track *); - virtual void EndTracking() {} + virtual void BeginOfRunAction(const G4Run *run) override; + virtual void PreUserTrackingAction(const G4Track* track) override; + virtual void PostUserTrackingAction(const G4Track* track) override; -protected: - // ----------------------------- - // -- Mandatory from base class: - // ----------------------------- - // -- Unused: - void AttachToAllLogicalDaughtersVolumes(G4LogicalVolume *); - void ComptonSplitting(G4Step* CurrentStep,G4Track track,G4Step step,G4VProcess* process,G4int splittingFactor); - void SecondariesSplitting(G4Step* CurrentStep,G4Track track,G4Step step,G4VProcess* process,G4int splittingFactor); + G4Track* CreateComptonTrack(G4ParticleChangeForGamma*,G4Track, G4double); + void ComptonSplitting(G4Step* CurrentStep,G4Track track,const G4Step* step,G4VProcess* process); + void SecondariesSplitting(G4Step* CurrentStep,G4Track track,const G4Step* step,G4VProcess* process); void RememberLastProcessInformation(G4Step*); - void CreateNewParticleAtTheLastVertex(G4Step*,G4Track,G4Step,G4String); - virtual G4VBiasingOperation *ProposeNonPhysicsBiasingOperation( - const G4Track * /* track */, - const G4BiasingProcessInterface * /* callingProcess */) { - return 0; - } - virtual G4VBiasingOperation *ProposeOccurenceBiasingOperation( - const G4Track * /* track */, - const G4BiasingProcessInterface * /* callingProcess */) { - return 0; - } + void CreateNewParticleAtTheLastVertex(G4Step*,G4Track,const G4Step*,G4String); + void ResetProcessesForEnteringParticles(G4Step * step); + void PostponeFirstAnnihilationTrackIfInteraction(G4Step *step,G4String processName); + void RegenerationOfPostponedAnnihilationTrack(G4Step *step); + void HandleTrackIDIfPostponedAnnihilation(G4Step* step); + G4double RussianRouletteForAngleSurvival(G4ThreeVector, G4ThreeVector, G4double, G4double); - // -- Used: - virtual G4VBiasingOperation *ProposeFinalStateBiasingOperation( - const G4Track *track, const G4BiasingProcessInterface *callingProcess){ - return 0; - } -private: - // -- Avoid compiler complaining for (wrong) method shadowing, - // -- this is because other virtual method with same name exists. - using G4VBiasingOperator::OperationApplied; private: GateOptnComptSplitting *fComptSplittingOperation; diff --git a/core/opengate_core/opengate_lib/pyGateOptrLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/pyGateOptrLastVertexInteractionSplittingActor.cpp index 8fc59fa3b..ec54a8077 100644 --- a/core/opengate_core/opengate_lib/pyGateOptrLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateOptrLastVertexInteractionSplittingActor.cpp @@ -7,12 +7,11 @@ #include <pybind11/pybind11.h> namespace py = pybind11; -#include "G4VBiasingOperator.hh" #include "GateOptrLastVertexInteractionSplittingActor.h" void init_GateOptrLastVertexInteractionSplittingActor(py::module &m) { - py::class_<GateOptrLastVertexInteractionSplittingActor, G4VBiasingOperator, GateVActor, + py::class_<GateOptrLastVertexInteractionSplittingActor, GateVActor, std::unique_ptr<GateOptrLastVertexInteractionSplittingActor, py::nodelete>>( m, "GateOptrLastVertexInteractionSplittingActor") .def_readwrite( diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index bad189dd5..5ae64b3d9 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -461,14 +461,9 @@ def set_default_user_info(user_info): ActorBase.set_default_user_info(user_info) deg = g4_units.deg user_info.splitting_factor = 1 - user_info.weight_threshold = 0 - user_info.bias_primary_only = True - user_info.min_weight_of_particle = 0 - user_info.bias_only_once = True - user_info.processes = ["phot"] - user_info.russian_roulette = False + user_info.russian_roulette_for_angle = False user_info.rotation_vector_director = False - user_info.vector_director = [0, 0, 1] + user_info.vector_director = [0, 0, -1] user_info.max_theta = 90 * deg user_info.list_of_volume_name = [] diff --git a/opengate/tests/src/test076_last_vertex_splittting.py b/opengate/tests/src/test076_last_vertex_splittting.py new file mode 100755 index 000000000..fbd10e5db --- /dev/null +++ b/opengate/tests/src/test076_last_vertex_splittting.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +import uproot +import numpy as np +from scipy.spatial.transform import Rotation +from opengate.tests import utility + + +def bool_validation_test(dico_parameters, tol): + keys = dico_parameters.keys() + liste_diff_max = [] + for key in keys: + liste_diff_max.append(np.max(dico_parameters[key])) + liste_diff_max = np.asarray(liste_diff_max) + max_diff = np.max(liste_diff_max) + print( + "Maximal error (mean or std dev) measured between the analog and the biased simulation:", + np.round(max_diff, 2), + "%", + ) + if max_diff <= 100 * tol: + return True + else: + return False + + +def validation_test(arr_ref, arr_data, nb_split, tol=0.2, tol_weights=0.04): + arr_ref = arr_ref[ + (arr_ref["TrackCreatorProcess"] == "compt") + | (arr_ref["TrackCreatorProcess"] == "none") + ] + arr_data = arr_data[ + (arr_data["TrackCreatorProcess"] != "phot") + & (arr_data["TrackCreatorProcess"] != "eBrem") + & (arr_data["TrackCreatorProcess"] != "eIoni") + ] + + EventID = arr_data["EventID"] + weights = arr_data["Weight"][EventID == EventID[0]] + val_weights = np.round(weights[0], 4) + bool_val_weights = 1 / nb_split == val_weights + print( + "Sum of electron and photon weights for the first event simulated:", + np.round(np.sum(weights), 2), + ) + print("Len of the weights vector for the first event:", len(weights)) + condition_weights = np.round(np.sum(weights), 4) > 2 * ( + 1 - tol_weights + ) and np.round(np.sum(weights), 4) < 2 * (1 + tol_weights) + condition_len = len(weights) > 2 * nb_split * (1 - tol_weights) and len( + weights + ) < 2 * nb_split * (1 + tol_weights) + bool_weights = condition_weights and condition_len + keys = ["KineticEnergy", "PreDirection_X", "PreDirection_Y", "PreDirection_Z"] + + arr_ref_phot = arr_ref[arr_ref["ParticleName"] == "gamma"] + arr_ref_elec = arr_ref[arr_ref["ParticleName"] == "e-"] + + arr_data_phot = arr_data[arr_data["ParticleName"] == "gamma"] + arr_data_elec = arr_data[arr_data["ParticleName"] == "e-"] + + keys_dico = ["ref", "data"] + dico_arr_phot = {} + dico_arr_elec = {} + + dico_arr_phot["ref"] = arr_ref_phot + dico_arr_phot["data"] = arr_data_phot + + dico_arr_elec["ref"] = arr_ref_elec + dico_arr_elec["data"] = arr_data_elec + dico_comp_data = {} + + for key in keys: + arr_data = [] + for key_dico in keys_dico: + mean_elec = np.mean(dico_arr_phot[key_dico][key]) + mean_phot = np.mean(dico_arr_elec[key_dico][key]) + std_elec = np.std(dico_arr_phot[key_dico][key]) + std_phot = np.std(dico_arr_elec[key_dico][key]) + arr_data += [mean_elec, mean_phot, std_elec, std_phot] + dico_comp_data[key] = 100 * np.abs( + np.array( + [ + (arr_data[0] - arr_data[4]) / arr_data[0], + (arr_data[1] - arr_data[5]) / arr_data[1], + (arr_data[2] - arr_data[6]) / arr_data[6], + (arr_data[3] - arr_data[7]) / arr_data[3], + ] + ) + ) + bool_test = bool_validation_test(dico_comp_data, tol) + bool_tot = bool_test and bool_weights and bool_val_weights + return bool_tot + + +if __name__ == "__main__": + paths = utility.get_default_test_paths( + __file__, "test071test_operator_compt_splitting", output_folder="test071" + ) + + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + ui.visu = True + ui.visu_type = "vrml" + ui.check_volumes_overlap = False + # ui.running_verbose_level = gate.logger.EVENT + ui.number_of_threads = 1 + #123456 bug free + # 4665 seg fault + #46656 annihil + ui.random_seed = 641 + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + um = gate.g4_units.um + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + deg = gate.g4_units.deg + gcm3 = gate.g4_units.g / gate.g4_units.cm3 + + # adapt world size + world = sim.world + world.size = [0.25 * m, 0.25 * m, 0.25 * m] + world.material = "G4_WATER" + + ####### GEOMETRY TO IRRADIATE ############# + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + + W_tubs = sim.add_volume("Tubs", "W_box") + W_tubs.material = "Tungsten" + W_tubs.mother = world.name + + W_tubs.rmin = 0 + W_tubs.rmax = 0.5 *cm + W_tubs.dz = 0.05 * m + W_tubs.color = [0.8, 0.2, 0.1, 1] + angle_x = 45 + angle_y = 70 + angle_z = 80 + + rotation = Rotation.from_euler( + "xyz", [angle_y, angle_y, angle_z], degrees=True + ).as_matrix() + W_tubs.rotation = rotation + + ####### Compton Splitting ACTOR ######### + nb_split = 4 + vertex_splitting_actor = sim.add_actor("LastVertexInteractionSplittingActor", "vertexSplittingW") + vertex_splitting_actor.mother = W_tubs.name + vertex_splitting_actor.splitting_factor = nb_split + vertex_splitting_actor.russian_roulette_for_angle = False + vertex_splitting_actor.rotation_vector_director = False + vertex_splitting_actor.max_theta = 10 * deg + vertex_splitting_actor.vector_director = [0,0,-1] + + ##### PHASE SPACE plan ######" + plan_tubs = sim.add_volume("Tubs", "phsp_tubs") + plan_tubs.material = "G4_Galactic" + plan_tubs.mother = world.name + plan_tubs.rmin = W_tubs.rmax + 1*nm + plan_tubs.rmax = plan_tubs.rmin + 1 * nm + plan_tubs.dz = 0.5 * m + plan_tubs.color = [0.2, 1, 0.8, 1] + plan_tubs.rotation = rotation + + ####### Electron source ########### + source = sim.add_source("GenericSource", "source1") + source.particle = "gamma" + source.n = 1 + source.position.type = "sphere" + source.position.radius = 1 * nm + source.direction.type = "momentum" + # source.direction.momentum = [0,0,-1] + source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) + source.energy.type = "mono" + source.energy.mono = 4 * MeV + + ####### PHASE SPACE ACTOR ############## + + phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + phsp_actor.mother = plan_tubs.name + phsp_actor.attributes = [ + "EventID", + "TrackID", + "Weight", + "ParticleName", + "KineticEnergy", + "PreDirection", + "TrackCreatorProcess", + ] + + phsp_actor.output = paths.output / "test075_output_data.root" + + ##### MODIFIED PHYSICS LIST ############### + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + #### Extremely important, it seems that GEANT4, for almost all physics lists, encompass all the photon processes in GammaGeneralProc + #### Therefore if we provide the name of the real process (here compt) without deactivating GammaGeneralProcess, it will not find the + #### process to bias and the biasing will fail + s = f"/process/em/UseGeneralProcess false" + sim.add_g4_command_before_init(s) + + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1 * um + sim.physics_manager.global_production_cuts.positron = 1 * um + + output = sim.run() + + # + # print results + stats = sim.output.get_actor("Stats") + h = sim.output.get_actor("PhaseSpace") + print(stats) + # + # f_data = uproot.open(paths.output / "test071_output_data.root") + # f_ref_data = uproot.open(paths.data / "test071_ref_data.root") + # arr_data = f_data["PhaseSpace"].arrays() + # arr_ref_data = f_ref_data["PhaseSpace"].arrays() + # # + # is_ok = validation_test(arr_ref_data, arr_data, nb_split) + # utility.test_ok(is_ok) + From d5eb52c8048b1c7803b07f28b8f29c30b10a5a42 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 18 Jun 2024 13:38:09 +0200 Subject: [PATCH 22/82] Add angular RR for all processes + Bug corrections on postponed pair production + tracking --- core/opengate_core/opengate_core.cpp | 4 +- ...ateLastVertexInteractionSplittingActor.cpp | 643 ++++++++++++++++++ ...GateLastVertexInteractionSplittingActor.h} | 53 +- .../GateLastVertexSplittingPostStepDoIt.h | 75 ++ ...ptrLastVertexInteractionSplittingActor.cpp | 539 --------------- ...teLastVertexInteractionSplittingActor.cpp} | 12 +- opengate/actors/miscactors.py | 4 +- 7 files changed, 752 insertions(+), 578 deletions(-) create mode 100644 core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp rename core/opengate_core/opengate_lib/{GateOptrLastVertexInteractionSplittingActor.h => GateLastVertexInteractionSplittingActor.h} (76%) create mode 100644 core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h delete mode 100644 core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.cpp rename core/opengate_core/opengate_lib/{pyGateOptrLastVertexInteractionSplittingActor.cpp => pyGateLastVertexInteractionSplittingActor.cpp} (51%) diff --git a/core/opengate_core/opengate_core.cpp b/core/opengate_core/opengate_core.cpp index bb7cf5b17..fc2f928b9 100644 --- a/core/opengate_core/opengate_core.cpp +++ b/core/opengate_core/opengate_core.cpp @@ -338,7 +338,7 @@ void init_GatePhaseSpaceActor(py::module &); void init_GateOptrComptSplittingActor(py::module &m); -void init_GateOptrLastVertexInteractionSplittingActor(py::module &m); +void init_GateLastVertexInteractionSplittingActor(py::module &m); void init_GateBOptrBremSplittingActor(py::module &m); @@ -560,7 +560,7 @@ PYBIND11_MODULE(opengate_core, m) { init_GatePhaseSpaceActor(m); init_GateBOptrBremSplittingActor(m); init_GateOptrComptSplittingActor(m); - init_GateOptrLastVertexInteractionSplittingActor(m); + init_GateLastVertexInteractionSplittingActor(m); init_GateHitsCollectionActor(m); init_GateMotionVolumeActor(m); init_GateHitsAdderActor(m); diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp new file mode 100644 index 000000000..3b18610c6 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -0,0 +1,643 @@ +// +// ******************************************************************** +// * 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 GateLastVertexInteractionSplittingActor.cc +/// \brief Implementation of the GateLastVertexInteractionSplittingActor class + +#include "GateHelpersDict.h" +#include "GateHelpersImage.h" + +#include "CLHEP/Units/SystemOfUnits.h" +#include "G4BiasingProcessInterface.hh" +#include "G4Gamma.hh" +#include "G4Positron.hh" +#include "G4LogicalVolumeStore.hh" +#include "G4ParticleTable.hh" +#include "G4PhysicalVolumeStore.hh" +#include "G4ProcessManager.hh" +#include "G4TrackingManager.hh" +#include "G4ProcessVector.hh" +#include "GateOptnComptSplitting.h" +#include "GateLastVertexInteractionSplittingActor.h" +#include "GateLastVertexSplittingPostStepDoIt.h" +#include "G4VParticleChange.hh" + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateLastVertexInteractionSplittingActor::GateLastVertexInteractionSplittingActor(py::dict &user_info) : GateVActor(user_info, false) +{ + fMotherVolumeName = DictGetStr(user_info, "mother"); + fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); + fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); + fRussianRouletteForAngle = DictGetBool(user_info, "russian_roulette_for_angle"); + fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); + fMaxTheta = DictGetDouble(user_info, "max_theta"); + fActions.insert("StartSimulationAction"); + fActions.insert("SteppingAction"); + fActions.insert("BeginOfEventAction"); + fActions.insert("BeginOfRunAction"); + fActions.insert("PreUserTrackingAction"); + fActions.insert("PostUserTrackingAction"); +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +G4double GateLastVertexInteractionSplittingActor::RussianRouletteForAngleSurvival(G4ThreeVector dir, G4ThreeVector vectorDirector, G4double maxTheta, G4double split) +{ + G4double cosTheta = vectorDirector * dir; + G4double theta = std::acos(cosTheta); + G4double weightToApply = 1; + if (theta > fMaxTheta) + { + G4double probability = G4UniformRand(); + if (probability <= 1 / split) + { + weightToApply = split; + } + else + { + weightToApply = 0; + } + } + return weightToApply; +} + +G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack(G4ParticleChangeForGamma *gammaProcess, G4Track track, G4double weight) +{ + G4double energy = gammaProcess->GetProposedKineticEnergy(); + G4double globalTime = track.GetGlobalTime(); + G4double newGammaWeight = weight; + const G4ThreeVector momentum = gammaProcess->GetProposedMomentumDirection(); + const G4ThreeVector position = track.GetPosition(); + G4Track *newTrack = new G4Track(track); + + newTrack->SetWeight(newGammaWeight); + newTrack->SetKineticEnergy(energy); + newTrack->SetMomentumDirection(momentum); + newTrack->SetPosition(position); + return newTrack; +} + +void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step *CurrentStep, G4Track track, const G4Step *step, G4VProcess *process) +{ + // Loop on process and add the secondary tracks to the current step secondary vector + + G4TrackVector *trackVector = CurrentStep->GetfSecondary(); + G4double gammaWeight = 0; + G4int nCall = (G4int)fSplittingFactor; + for (int i = 0; i < nCall; i++) + { + GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; + G4VParticleChange *processFinalState = emProcess->PostStepDoIt(track, *step); + G4ParticleChangeForGamma *gammaProcessFinalState = (G4ParticleChangeForGamma *)processFinalState; + const G4ThreeVector momentum = gammaProcessFinalState->GetProposedMomentumDirection(); + gammaWeight = CurrentStep->GetTrack()->GetWeight() / fSplittingFactor; + if (fRussianRouletteForAngle == true) + { + G4double weightToApply = RussianRouletteForAngleSurvival(momentum, fVectorDirector, fMaxTheta, fSplittingFactor); + if (weightToApply != 0) + { + gammaWeight = gammaWeight * weightToApply; + G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, track, gammaWeight); + trackVector->push_back(newTrack); + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + for (int j = 0; j < NbOfSecondaries; j++) + { + G4Track *newTrack = processFinalState->GetSecondary(j); + newTrack->SetWeight(gammaWeight); + trackVector->push_back(newTrack); + } + } + } + + else + { + G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, track, gammaWeight); + trackVector->push_back(newTrack); + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + for (int j = 0; j < NbOfSecondaries; j++) + { + G4Track *newTrack = processFinalState->GetSecondary(j); + newTrack->SetWeight(gammaWeight); + trackVector->push_back(newTrack); + } + } + processFinalState->Clear(); + gammaProcessFinalState->Clear(); + } +} + +void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step *CurrentStep, G4Track track, const G4Step *step, G4VProcess *process) +{ + // Loop on process and add the secondary tracks to the current step secondary vector. + G4String particleName = track.GetParticleDefinition()->GetParticleName(); + G4TrackVector *trackVector = CurrentStep->GetfSecondary(); + G4double gammaWeight = 0; + G4int nCall = (G4int)fSplittingFactor; + for (int i = 0; i < nCall; i++) + { + G4VParticleChange *processFinalState = nullptr; + + if (process->GetProcessName() == "eBrem") + { + GateBremPostStepDoIt *bremProcess = (GateBremPostStepDoIt *)process; + processFinalState = bremProcess->GateBremPostStepDoIt::PostStepDoIt(track, *step); + } + else + { + GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; + processFinalState = emProcess->PostStepDoIt(track, *step); + } + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + for (int j = 0; j < NbOfSecondaries; j++) + { + gammaWeight = CurrentStep->GetTrack()->GetWeight() / fSplittingFactor; + G4Track *newTrack = processFinalState->GetSecondary(j); + if ((fRussianRouletteForAngle == true) && (particleName == "gamma")) + { + const G4ThreeVector momentum = newTrack->GetMomentumDirection(); + G4double weightToApply = RussianRouletteForAngleSurvival(momentum, fVectorDirector, fMaxTheta, fSplittingFactor); + if (weightToApply != 0) + { + gammaWeight = gammaWeight * weightToApply; + newTrack->SetWeight(gammaWeight); + trackVector->push_back(newTrack); + } + } + else + { + std::cout<<newTrack->GetKineticEnergy()<<std::endl; + newTrack->SetWeight(gammaWeight); + trackVector->push_back(newTrack); + } + } + processFinalState->Clear(); + } +} + +void GateLastVertexInteractionSplittingActor::ClearRememberedTracksAndSteps(std::map<G4int, G4TrackVector> rememberedTracks, std::map<G4int, std::vector<G4Step *>> rememberedSteps) +{ + std::vector<G4Track*> trackToKill = {}; + for (auto it = rememberedTracks.begin(); it != rememberedTracks.end(); ++it) + { + std::vector<G4Track*> vector = it->second; + for (auto it2 = vector.begin(); it2 != vector.end(); it2++){ + if ((std::find(trackToKill.begin(), trackToKill.end(), *it2) == trackToKill.end())){ + trackToKill.push_back(*it2); + } + } + } + + for (auto it = trackToKill.begin(); it != trackToKill.end(); ++it){ + delete *it; + } + +std::vector<G4Step*> stepToKill = {}; + for (auto it = rememberedSteps.begin(); it != rememberedSteps.end(); ++it) + { + std::vector<G4Step*> vector = it->second; + for (auto it2 = vector.begin(); it2 != vector.end(); it2++){ + if ((std::find(stepToKill.begin(), stepToKill.end(), *it2) == stepToKill.end())){ + stepToKill.push_back(*it2); + } + } + } + + for (auto it = stepToKill.begin(); it != stepToKill.end(); ++it){ + delete *it; + } +} + +void GateLastVertexInteractionSplittingActor::RememberLastProcessInformation(G4Step *step) +{ + + // When an interesting process to split occurs, we remember the status of the track and the process at this current step + // some informations regarding the track info have to be changed because they were update according to the interaction that occured + // These informations are stocked as a map object, binding the track ID with all the track objects and processes to split. + // Because in some cases, if a secondary was created before an interaction chain, this secondary will be track after the chain and + // without this association, we wll loose the information about the process occuring for this secondary. + + G4String creatorProcessName = "None"; + if (step->GetTrack()->GetCreatorProcess() != 0) + creatorProcessName = step->GetTrack()->GetCreatorProcess()->GetProcessName(); + G4String processName = "None"; + G4int trackID = step->GetTrack()->GetTrackID(); + G4int parentID = step->GetTrack()->GetParentID(); + if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) + { + processName = step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); + } + if ((step->GetTrack()->GetParticleDefinition()->GetParticleName() == "e+") && ((step->GetTrack()->GetTrackStatus() == 1) || (step->GetTrack()->GetTrackStatus() == 2))) + { + processName = "annihil"; + } + + if ((std::find(fListOfProcesses.begin(), fListOfProcesses.end(), processName) != fListOfProcesses.end())) + { + G4Track *trackInformation = new G4Track(*(step->GetTrack())); + G4Step *stepInformation = new G4Step(*(step)); + + G4StepPoint *stepPoint = nullptr; + if ((processName == "annihil") && (step->GetTrack()->GetKineticEnergy() == 0)) + { + stepPoint = step->GetPostStepPoint(); + } + else + { + stepPoint = step->GetPreStepPoint(); + } + + trackInformation->SetKineticEnergy(stepPoint->GetKineticEnergy()); + trackInformation->SetMomentumDirection(stepPoint->GetMomentumDirection()); + trackInformation->SetTrackStatus(fAlive); + trackInformation->SetPolarization(stepPoint->GetPolarization()); + trackInformation->SetTrackID(trackID); + + if (auto search = fRememberedTracks.find(trackID); search != fRememberedTracks.end()) + { + fRememberedTracks[trackID].push_back(trackInformation); + fRememberedProcesses[trackID].push_back(processName); + fRememberedSteps[trackID].push_back(stepInformation); + } + + else + { + fRememberedTracks[trackID] = {trackInformation}; + fRememberedProcesses[trackID] = {processName}; + fRememberedSteps[trackID] = {stepInformation}; + } + } + + else + { + if (auto search = fRememberedTracks.find(trackID); search == fRememberedTracks.end()) + { + if (auto search = fRememberedTracks.find(parentID); search != fRememberedTracks.end()) + { + if (auto it = std::find(fRememberedProcesses[parentID].begin(), fRememberedProcesses[parentID].end(), creatorProcessName); it != fRememberedProcesses[parentID].end()) + { + auto idx = it - fRememberedProcesses[parentID].begin(); + fRememberedTracks[trackID] = {fRememberedTracks[parentID][idx]}; + fRememberedProcesses[trackID] = {fRememberedProcesses[parentID][idx]}; + fRememberedSteps[trackID] = {fRememberedSteps[parentID][idx]}; + } + else + { + fRememberedTracks[trackID] = {fRememberedTracks[parentID][0]}; + fRememberedProcesses[trackID] = {fRememberedProcesses[parentID][0]}; + fRememberedSteps[trackID] = {fRememberedSteps[parentID][0]}; + } + } + } + } +} + +void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G4Step *CurrentStep, G4Track track, const G4Step *step, G4String processName) +{ + // We retrieve the process associated to the process name to split and we split according the process. + // Since for compton scattering, the gamma is not a secondary particles, this one need to have his own splitting function. + + G4ParticleDefinition *particleDefinition = track.GetDefinition(); + G4ProcessManager *processManager = particleDefinition->GetProcessManager(); + G4ProcessVector *processList = processManager->GetProcessList(); + G4VProcess *processToSplit = nullptr; + for (size_t i = 0; i < processList->size(); ++i) + { + auto process = (*processList)[i]; + if (process->GetProcessName() == processName) + { + processToSplit = process; + } + } + if (processName == "compt") + { + ComptonSplitting(CurrentStep, track, step, processToSplit); + } + else + { + SecondariesSplitting(CurrentStep, track, step, processToSplit); + } +} + +void GateLastVertexInteractionSplittingActor::ResetProcessesForEnteringParticles(G4Step *step) +{ + + // This function reset the processes and track registered to be split each time a particle enters into a volume. + // Here a new particle incoming is either a particle entering into the volume ( first pre step is a boundary of a mother volume + // or first step trigger the actor and the vertex is not either the mother volume or a the fdaughter volume) or an event generated within + // the volume (the particle did not enter into the volume and its parentID = 0). An additionnal condition is set with the trackID to ensure + // particles crossing twice the volume (after either a compton or pair prod) are not splitted. + + G4int trackID = step->GetTrack()->GetTrackID(); + if ((fEventIDOfInitialSplittedTrack != fEventID) || ((fEventIDOfInitialSplittedTrack == fEventID) && (trackID < fTrackIDOfInitialTrack))) + { + G4String logicalVolumeNamePreStep = "None"; + if (step->GetPreStepPoint()->GetPhysicalVolume() != 0) + { + logicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + } + if (((step->GetPreStepPoint()->GetStepStatus() == 1) && (logicalVolumeNamePreStep == fMotherVolumeName)) || + ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != logicalVolumeNamePreStep) && (step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != fMotherVolumeName))) + { + fTrackIDOfInitialTrack = trackID; + fEventIDOfInitialSplittedTrack = fEventID; + fWeightOfEnteringParticle = step->GetTrack()->GetWeight(); + ClearRememberedTracksAndSteps(fRememberedTracks,fRememberedSteps); + fRememberedProcesses.clear(); + fRememberedTracks.clear(); + fRememberedSteps.clear(); + } + else if (step->GetTrack()->GetParentID() == 0) + { + fTrackIDOfInitialTrack = trackID; + fEventIDOfInitialSplittedTrack = fEventID; + fWeightOfEnteringParticle = step->GetTrack()->GetWeight(); + ClearRememberedTracksAndSteps(fRememberedTracks,fRememberedSteps); + fRememberedProcesses.clear(); + fRememberedTracks.clear(); + fRememberedSteps.clear(); + } + } +} + +void GateLastVertexInteractionSplittingActor::PostponeFirstAnnihilationTrackIfInteraction(G4Step *step, G4String processName) +{ + // In case the first gamma issued from annihilation undergoes an interaction, in order to not bias the process + // We keep in memory the particle post step state (with its secondaries) and kill the particle and its secondaries. + // If the second photon from annihilation exiting the collimation system with an interaction or is absorbed within + // the collimation, the particle is subsequently resimulated, starting from the interaction point. + + G4int trackID = step->GetTrack()->GetTrackID(); + if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), processName) != fListOfProcesses.end()) + { + fSuspendForAnnihil = true; + G4Track trackToPostpone = G4Track(*(step->GetTrack())); + trackToPostpone.SetKineticEnergy(step->GetPostStepPoint()->GetKineticEnergy()); + trackToPostpone.SetMomentumDirection(step->GetPostStepPoint()->GetMomentumDirection()); + trackToPostpone.SetTrackStatus(step->GetTrack()->GetTrackStatus()); + trackToPostpone.SetPolarization(step->GetPostStepPoint()->GetPolarization()); + trackToPostpone.SetPosition(step->GetPostStepPoint()->GetPosition()); + trackToPostpone.SetTrackID(trackID); + fTracksToPostpone.push_back(trackToPostpone); + auto theTrack = fTracksToPostpone[0]; + + auto secVec = step->GetfSecondary(); + for (int i = 0; i < secVec->size(); i++) + { + G4Track *sec = (*secVec)[i]; + G4Track copySec = G4Track((*sec)); + fTracksToPostpone.push_back(copySec); + } + + fTracksToPostpone[0].SetTrackID(trackID); + step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); + } +} + +void GateLastVertexInteractionSplittingActor::RegenerationOfPostponedAnnihilationTrack(G4Step *step) +{ + + // If the second photon from annihilation suceed to exit the collimation system with at least one interaction or was absorbed. + // Resimulation of annihilation photons and its potential secondaries. + + G4TrackVector *currentSecondaries = step->GetfSecondary(); + for (int i = 0; i < fTracksToPostpone.size(); i++) + { + G4Track *trackToAdd = new G4Track(fTracksToPostpone[fTracksToPostpone.size() - 1 - i]); + trackToAdd->SetParentID(step->GetTrack()->GetTrackID() - 1); + + currentSecondaries->insert(currentSecondaries->begin(), trackToAdd); + } + G4Track *firstPostponedTrack = (*currentSecondaries)[0]; + + // Handle of case were the interaction killed the photon issued from annihilation, it will not be track at the following state + // and the boolean plus the track vector need to be reset + + if (firstPostponedTrack->GetTrackStatus() == 2) + { + fSuspendForAnnihil = false; + fTracksToPostpone.clear(); + } +} + +void GateLastVertexInteractionSplittingActor::HandleTrackIDIfPostponedAnnihilation(G4Step *step) +{ + // The ID is to modify trackID and the processes and tracks sotcked for a specific trackID associated to + // the postponed annihilation to respect the trackID order in GEANT4. Since in this case the second photon is tracked before the first one + // his trackID +=1. For the postponed one, he is the first secondary particle of the second photon tracks, his trackID is therefore equal + // to the second photon trackID +1 instead of the second photon trackID -1. + // The parentID of secondary particles are also modified because they are used in the rememberlastprocess function + // At last the value associated to a specific trackID in the process and track map are modified according to the new trackID. + + if (fSuspendForAnnihil) + { + if (step->GetTrack()->GetTrackID() == fTracksToPostpone[0].GetTrackID() - 1) + { + fRememberedProcesses[step->GetTrack()->GetTrackID()] = fRememberedProcesses[step->GetTrack()->GetTrackID() + 1]; + fRememberedProcesses.erase(step->GetTrack()->GetTrackID() + 1); + fRememberedSteps[step->GetTrack()->GetTrackID()] = fRememberedSteps[step->GetTrack()->GetTrackID() + 1]; + fRememberedSteps.erase(step->GetTrack()->GetTrackID() + 1); + fRememberedTracks[step->GetTrack()->GetTrackID()] = fRememberedTracks[step->GetTrack()->GetTrackID() + 1]; + fRememberedTracks.erase(step->GetTrack()->GetTrackID() + 1); + auto vecSec = step->GetSecondary(); + for (int i = 0; i < vecSec->size(); i++) + { + (*vecSec)[i]->SetParentID(step->GetTrack()->GetTrackID() + 1); + } + step->GetTrack()->SetTrackID(step->GetTrack()->GetTrackID() + 1); + } + if (step->GetTrack()->GetTrackID() == fTracksToPostpone[0].GetTrackID() + 1) + { + auto vecSec = step->GetSecondary(); + for (int i = 0; i < vecSec->size(); i++) + { + (*vecSec)[i]->SetParentID(step->GetTrack()->GetTrackID() - 2); + } + step->GetTrack()->SetTrackID(step->GetTrack()->GetTrackID() - 2); + } + } +} + +void GateLastVertexInteractionSplittingActor::BeginOfRunAction(const G4Run *run) +{ + + // The way to behave of the russian roulette is the following : + // we provide a vector director and the theta angle acceptance, where theta = + // 0 is a vector colinear to the vector director Then if the track generated + // is on the acceptance angle, we add it to the primary track, and if it's not + // the case, we launch the russian roulette + + if (fRotationVectorDirector) + { + G4VPhysicalVolume *physBiasingVolume = + G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + auto rot = physBiasingVolume->GetObjectRotationValue(); + fVectorDirector = rot * fVectorDirector; + } +} + +void GateLastVertexInteractionSplittingActor::BeginOfEventAction(const G4Event *event) +{ + + fParentID = -1; + fEventID = event->GetEventID(); + if (fEventID%30000 == 0) + std::cout <<fEventID<<std::endl; + fEventIDOfSplittedTrack = -1; + fTrackIDOfSplittedTrack = -1; +} + +void GateLastVertexInteractionSplittingActor::PreUserTrackingAction(const G4Track *track) +{ + fIsFirstStep = true; +} + +void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) +{ + + G4String creatorProcessName = "None"; + if (step->GetTrack()->GetCreatorProcess() != 0) + creatorProcessName = step->GetTrack()->GetCreatorProcess()->GetProcessName(); + + if (fIsFirstStep) + { + + HandleTrackIDIfPostponedAnnihilation(step); + ResetProcessesForEnteringParticles(step); + } + + G4int trackID = step->GetTrack()->GetTrackID(); + + if (step->GetTrack()->GetWeight() >= fWeightOfEnteringParticle) + { + RememberLastProcessInformation(step); + G4String process = "None"; + if (auto search = fRememberedProcesses.find(trackID); search != fRememberedProcesses.end()) + { + process = fRememberedProcesses[trackID].back(); + } + // std::cout<<step->GetTrack()->GetVertexPosition()<<" "<<step->GetTrack()->GetParticleDefinition()->GetParticleName()<<" "<<fSuspendForAnnihil<<" "<<process<<" "<<step->GetTrack()->GetWeight()<<" "<<step->GetTrack()->GetParentID()<<" "<<step->GetTrack()->GetTrackID()<<" "<<creatorProcessName<<" "<<step->GetTrack()->GetTrackStatus()<<" "<<step->GetPreStepPoint()->GetKineticEnergy()<<" " <<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetPreStepPoint()->GetPosition()<<" "<<step->GetPostStepPoint()->GetPosition()<<std::endl; + + if ((!fSuspendForAnnihil) && (creatorProcessName == "annihil") && (fTrackIDOfSplittedTrack == step->GetTrack()->GetParentID()) && (fEventID == fEventIDOfSplittedTrack)) + { + step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); + } + + if ((!fSuspendForAnnihil) && (process != "annihil") && (creatorProcessName == "annihil") && (step->GetTrack()->GetTrackStatus() != 3)) + { + G4int parentID = step->GetTrack()->GetParentID(); + if (auto search = fRememberedProcesses.find(parentID); search != fRememberedProcesses.end()) + { + if (fRememberedProcesses[trackID].back() == "annihil") + PostponeFirstAnnihilationTrackIfInteraction(step, process); + } + } + // If the first annihilation photon exit the collimation and the process to split is annihilation + // We kill the second photon, because the annihilation will generate both the photons. + + if (fSuspendForAnnihil) + { + if (trackID == fTracksToPostpone[0].GetTrackID() - 1) + { + fSuspendForAnnihil = false; + fTracksToPostpone.clear(); + } + } + + G4String logicalVolumeNamePostStep = "None"; + if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) + logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + + if (((step->GetTrack()->GetTrackStatus() != 2) && (step->GetTrack()->GetTrackStatus() != 3)) && (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), logicalVolumeNamePostStep) != fListOfVolumeAncestor.end())) + { + G4String processToSplit = "None"; + if (auto search = fRememberedProcesses.find(trackID); search != fRememberedProcesses.end()) + processToSplit = fRememberedProcesses[trackID].back(); + + if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), processToSplit) != fListOfProcesses.end()) + { + std::cout<<step->GetTrack()->GetKineticEnergy()<<std::endl; + + // Handle of pecularities (1): + + // If the process t split is the gamma issued from compton interaction, the electron primary generated have to be killed + // given that electron will be regenerated + + if ((processToSplit == "compt") && (step->GetTrack()->GetParticleDefinition()->GetParticleName() == "gamma")) + { + auto secondaries = step->GetfSecondary(); + if (secondaries->size() > 0) + { + G4Track *lastSecTrack = secondaries->back(); + lastSecTrack->SetTrackStatus(fStopAndKill); + } + } + + // Handle of pecularities (2): + + // If the process to split is the annihilation, the second photon, postponed or not, have to be killed + // the reset of the postpone is performed here, whereas the kill of the next annihilation photon, if not postponed + // is realised at the beginning of the step tracking. + + if (processToSplit == "annihil") + { + if (fSuspendForAnnihil) + { + fSuspendForAnnihil = false; + fTracksToPostpone.clear(); + } + } + + G4Track *trackToSplit = fRememberedTracks[trackID].back(); + G4Step *stepToSplit = fRememberedSteps[trackID].back(); + CreateNewParticleAtTheLastVertex(step, *trackToSplit, stepToSplit, processToSplit); + + fTrackIDOfSplittedTrack = trackToSplit->GetTrackID(); + fEventIDOfSplittedTrack = fEventID; + fTrackIDOfInitialSplittedTrack = fTrackIDOfInitialTrack; + step->GetTrack()->SetTrackStatus(fStopAndKill); + } + } + } + + if ((fSuspendForAnnihil) && ((step->GetTrack()->GetTrackStatus() == 1) || (step->GetTrack()->GetTrackStatus() == 2))) + { + if (trackID == fTracksToPostpone[0].GetTrackID()) + { + RegenerationOfPostponedAnnihilationTrack(step); + } + } + + fIsFirstStep = false; +} + +void GateLastVertexInteractionSplittingActor::PostUserTrackingAction(const G4Track *track) +{ +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h similarity index 76% rename from core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.h rename to core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h index 24727beb1..b79c5cc7a 100644 --- a/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h @@ -24,35 +24,24 @@ // ******************************************************************** // /// -/// \file GateOptrLastVertexInteractionSplittingActor.h -/// \brief Definition of the GateOptrLastVertexInteractionSplittingActor class -#ifndef GateOptrLastVertexInteractionSplittingActor_h -#define GateOptrLastVertexInteractionSplittingActor_h 1 +/// \file GateLastVertexInteractionSplittingActor.h +/// \brief Definition of the GateLastVertexInteractionSplittingActor class +#ifndef GateLastVertexInteractionSplittingActor_h +#define GateLastVertexInteractionSplittingActor_h 1 #include "GateVActor.h" #include "G4ParticleChangeForGamma.hh" +#include "G4VEnergyLossProcess.hh" #include <iostream> #include <pybind11/stl.h> namespace py = pybind11; -class GateOptnComptSplitting; -class GateOptrLastVertexInteractionSplittingActor : public GateVActor { -public: - GateOptrLastVertexInteractionSplittingActor(py::dict &user_info); - virtual ~GateOptrLastVertexInteractionSplittingActor() {} +class GateLastVertexInteractionSplittingActor : public GateVActor { public: - // ------------------------- - // Optional from base class: - // ------------------------- - // -- Call at run start: - // virtual void BeginOfRunAction(const G4Run *run); - - // virtual void SteppingAction(G4Step* step); - - // -- Call at each track starting: - // virtual void PreUserTrackingAction( const G4Track* track ); + GateLastVertexInteractionSplittingActor(py::dict &user_info); + virtual ~GateLastVertexInteractionSplittingActor() {} G4double fSplittingFactor; G4bool fRussianRouletteForAngle = false; @@ -60,8 +49,6 @@ class GateOptrLastVertexInteractionSplittingActor : public GateVActor { G4ThreeVector fVectorDirector; G4double fMaxTheta; G4int fTrackIDOfSplittedTrack = 0; - G4bool fIsSplitted = false; - G4bool fSecondaries = false; G4int fParentID = -1; G4int fEventID; G4int fEventIDOfSplittedTrack; @@ -71,15 +58,16 @@ class GateOptrLastVertexInteractionSplittingActor : public GateVActor { G4int ftmpTrackID; G4bool fIsFirstStep = true; G4bool fSuspendForAnnihil = false; + G4double fWeightOfEnteringParticle = 0; std::vector<G4Track> fTracksToPostpone; - std::map<G4int,std::vector<G4Track>> fRememberedTracks; + std::map<G4int,G4TrackVector> fRememberedTracks; + std::map<G4int,std::vector<G4Step*>> fRememberedSteps; std::map<G4int,std::vector<G4String>> fRememberedProcesses; std::vector<std::string> fListOfVolumeAncestor; - std::vector<G4String> fListOfProcesses = {"compt","eBrem","annihil","conv","phot"}; - // Unused but mandatory + std::vector<G4String> fListOfProcesses = {"compt","annihil","eBrem","conv","phot"}; virtual void SteppingAction(G4Step *) override; virtual void BeginOfEventAction(const G4Event *) override; @@ -87,21 +75,28 @@ class GateOptrLastVertexInteractionSplittingActor : public GateVActor { virtual void PreUserTrackingAction(const G4Track* track) override; virtual void PostUserTrackingAction(const G4Track* track) override; + //Pure splitting functions + G4double RussianRouletteForAngleSurvival(G4ThreeVector, G4ThreeVector, G4double, G4double); G4Track* CreateComptonTrack(G4ParticleChangeForGamma*,G4Track, G4double); void ComptonSplitting(G4Step* CurrentStep,G4Track track,const G4Step* step,G4VProcess* process); void SecondariesSplitting(G4Step* CurrentStep,G4Track track,const G4Step* step,G4VProcess* process); + + + //Handling the remembered processes to replay void RememberLastProcessInformation(G4Step*); void CreateNewParticleAtTheLastVertex(G4Step*,G4Track,const G4Step*,G4String); void ResetProcessesForEnteringParticles(G4Step * step); + void ClearRememberedTracksAndSteps(std::map<G4int, G4TrackVector>, std::map<G4int, std::vector<G4Step *>>); + + + //Edge case to handle the bias in annihilation + //FIXME : The triple annihilation is not handled for the moment void PostponeFirstAnnihilationTrackIfInteraction(G4Step *step,G4String processName); void RegenerationOfPostponedAnnihilationTrack(G4Step *step); void HandleTrackIDIfPostponedAnnihilation(G4Step* step); - G4double RussianRouletteForAngleSurvival(G4ThreeVector, G4ThreeVector, G4double, G4double); - - + + -private: - GateOptnComptSplitting *fComptSplittingOperation; }; #endif diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h new file mode 100644 index 000000000..d7880138a --- /dev/null +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h @@ -0,0 +1,75 @@ +// +// ******************************************************************** +// * 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. * +// ******************************************************************** +// +/// +#ifndef GateLastVertexSplittingPostStepDoIt_h +#define GateLastVertexSplittingPostStepDoIt_h + + +#include "G4VEnergyLossProcess.hh" +#include "G4VEmProcess.hh" +#include "G4VParticleChange.hh" +#include <iostream> + + + + +class GateBremPostStepDoIt : public G4VEnergyLossProcess { +public : + +GateBremPostStepDoIt(); + +~ GateBremPostStepDoIt(); + +virtual G4VParticleChange * PostStepDoIt (const G4Track & track, const G4Step & step) override +{ + const G4MaterialCutsCouple* couple = step.GetPreStepPoint()->GetMaterialCutsCouple(); + currentCouple = couple; + G4VParticleChange* particleChange = G4VEnergyLossProcess::PostStepDoIt(track,step); + return particleChange; +} + + +}; + + +class GateGammaEmPostStepDoIt : public G4VEmProcess { +public : + +GateGammaEmPostStepDoIt(); + +~ GateGammaEmPostStepDoIt(); + +virtual G4VParticleChange * PostStepDoIt (const G4Track & track, const G4Step & step) override +{ + const G4MaterialCutsCouple* couple = step.GetPreStepPoint()->GetMaterialCutsCouple(); + currentCouple = couple; + G4VParticleChange* particleChange = G4VEmProcess::PostStepDoIt(track,step); + return particleChange; +} + + +}; +#endif diff --git a/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.cpp deleted file mode 100644 index b38ed6fdf..000000000 --- a/core/opengate_core/opengate_lib/GateOptrLastVertexInteractionSplittingActor.cpp +++ /dev/null @@ -1,539 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptrLastVertexInteractionSplittingActor.cc -/// \brief Implementation of the GateOptrLastVertexInteractionSplittingActor class - -#include "GateHelpersDict.h" -#include "GateHelpersImage.h" - -#include "CLHEP/Units/SystemOfUnits.h" -#include "G4BiasingProcessInterface.hh" -#include "G4Gamma.hh" -#include "G4Positron.hh" -#include "G4LogicalVolumeStore.hh" -#include "G4ParticleTable.hh" -#include "G4PhysicalVolumeStore.hh" -#include "G4ProcessManager.hh" -#include "G4TrackingManager.hh" -#include "G4ProcessVector.hh" -#include "GateOptnComptSplitting.h" -#include "GateOptrLastVertexInteractionSplittingActor.h" -#include "G4VParticleChange.hh" - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptrLastVertexInteractionSplittingActor::GateOptrLastVertexInteractionSplittingActor(py::dict &user_info):GateVActor(user_info, false) { - fMotherVolumeName = DictGetStr(user_info, "mother"); - fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); - fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); - fRussianRouletteForAngle = DictGetBool(user_info, "russian_roulette_for_angle"); - fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); - fMaxTheta = DictGetDouble(user_info, "max_theta"); - fActions.insert("StartSimulationAction"); - fActions.insert("SteppingAction"); - fActions.insert("BeginOfEventAction"); - fActions.insert("BeginOfRunAction"); - fActions.insert("PreUserTrackingAction"); - fActions.insert("PostUserTrackingAction"); -} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - - -G4double GateOptrLastVertexInteractionSplittingActor::RussianRouletteForAngleSurvival(G4ThreeVector dir, G4ThreeVector vectorDirector, G4double maxTheta,G4double split) { - G4double cosTheta = vectorDirector * dir; - G4double theta = std::acos(cosTheta); - G4double weightToApply = 1; - if (theta > fMaxTheta) { - G4double probability = G4UniformRand(); - if (probability <= 1 / split) { - weightToApply = split; - } else { - weightToApply = 0; - } - } - return weightToApply; -} - -G4Track* GateOptrLastVertexInteractionSplittingActor:: CreateComptonTrack(G4ParticleChangeForGamma* gammaProcess,G4Track track, G4double weight){ - G4double energy = gammaProcess->GetProposedKineticEnergy(); - G4double globalTime = track.GetGlobalTime(); - G4double newGammaWeight = weight; - const G4ThreeVector momentum =gammaProcess->GetProposedMomentumDirection(); - const G4ThreeVector position = track.GetPosition(); - G4Track *newTrack = new G4Track(track); - newTrack->SetWeight(newGammaWeight); - newTrack->SetKineticEnergy(energy); - newTrack->SetMomentumDirection(momentum); - newTrack->SetPosition(position); - return newTrack; -} - -void GateOptrLastVertexInteractionSplittingActor::ComptonSplitting(G4Step* CurrentStep,G4Track track,const G4Step *step,G4VProcess* process){ -//Loop on process and add the secondary tracks to the current step secondary vector - - - -G4TrackVector *trackVector = CurrentStep->GetfSecondary(); -G4double gammaWeight = CurrentStep->GetTrack()->GetWeight()/fSplittingFactor; -G4int nCall = (G4int) fSplittingFactor; -for (int i =0; i <nCall; i++){ - G4VParticleChange* processFinalState = process->PostStepDoIt(track,*step); - G4ParticleChangeForGamma *gammaProcessFinalState = (G4ParticleChangeForGamma *)processFinalState; - const G4ThreeVector momentum =gammaProcessFinalState->GetProposedMomentumDirection(); - - if (fRussianRouletteForAngle == true) { - G4double weightToApply = RussianRouletteForAngleSurvival(momentum,fVectorDirector, fMaxTheta, fSplittingFactor); - if (weightToApply != 0){ - gammaWeight = gammaWeight * weightToApply; - G4Track* newTrack = CreateComptonTrack(gammaProcessFinalState,track,gammaWeight); - trackVector->push_back(newTrack); - G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - for(int j=0; j <NbOfSecondaries; j++){ - G4Track* newTrack = processFinalState->GetSecondary(j); - newTrack->SetWeight(gammaWeight); - trackVector->push_back(newTrack); - } - } - } - - else { - G4Track* newTrack = CreateComptonTrack(gammaProcessFinalState,track,gammaWeight); - trackVector->push_back(newTrack); - G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - for(int j=0; j <NbOfSecondaries; j++){ - G4Track* newTrack = processFinalState->GetSecondary(j); - newTrack->SetWeight(gammaWeight); - trackVector->push_back(newTrack); - - } - } - } -} - - -void GateOptrLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* CurrentStep,G4Track track,const G4Step *step,G4VProcess* process){ -//Loop on process and add the secondary tracks to the current step secondary vector. - -G4TrackVector *trackVector = CurrentStep->GetfSecondary(); -G4int nCall = (G4int) fSplittingFactor; -for (int i =0; i <nCall; i++){ - G4VParticleChange* processFinalState =nullptr; - processFinalState = process->PostStepDoIt(track,*step); - G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - for(int j=0; j <NbOfSecondaries; j++){ - G4Track* newTrack = processFinalState->GetSecondary(j); - trackVector->push_back(newTrack); - } - } -} - -void GateOptrLastVertexInteractionSplittingActor::RememberLastProcessInformation(G4Step* step){ - - // When an interesting process to split occurs, we remember the status of the track and the process at this current step - // some informations regarding the track info have to be changed because they were update according to the interaction that occured - // These informations are sotcked as a map object, binding the track ID with all the track objects and processes to split. - // Because in some cases, if a secondary was created before an interaction chain, this secondary will be track after the chain and - // without this association, we wll loose the information about the process occuring for this secondary. - - G4String creatorProcessName = "None"; - if (step->GetTrack()->GetCreatorProcess() !=0) - creatorProcessName = step->GetTrack()->GetCreatorProcess()->GetProcessName(); - G4String processName = "None"; - G4int trackID = step->GetTrack()->GetTrackID(); - if (step->GetPostStepPoint()->GetProcessDefinedStep() !=0){ - processName = step->GetPostStepPoint()->GetProcessDefinedStep()-> GetProcessName(); - } - if ((step->GetTrack()->GetParticleDefinition()->GetParticleName() == "e+") && ((step->GetTrack()->GetTrackStatus() == 1) || (step->GetTrack()->GetTrackStatus() == 2))){ - processName = "annihil"; - } - if ((std::find(fListOfProcesses.begin(),fListOfProcesses.end(),processName) != fListOfProcesses.end())){ - G4Track trackInformation = G4Track(*(step->GetTrack())); - trackInformation.SetKineticEnergy(step->GetPreStepPoint()->GetKineticEnergy()); - trackInformation.SetMomentumDirection(step->GetPreStepPoint()->GetMomentumDirection()); - trackInformation.SetTrackStatus(fAlive); - trackInformation.SetPolarization(step->GetPreStepPoint()->GetPolarization()); - trackInformation.SetTrackID(trackID); - if (auto search = fRememberedTracks.find(trackID); search != fRememberedTracks.end()){ - fRememberedTracks[trackID].push_back(trackInformation); - fRememberedProcesses[trackID].push_back(processName); - ; - } - else { - fRememberedTracks[trackID] = {trackInformation}; - fRememberedProcesses[trackID] = {processName}; - - - } - } - else { - - - G4int parentID = step->GetTrack()->GetParentID(); - if (auto search = fRememberedTracks.find(trackID); search == fRememberedTracks.end()){ - if (auto search = fRememberedTracks.find(parentID); search != fRememberedTracks.end()){ - if (auto it = std::find(fRememberedProcesses[parentID].begin(),fRememberedProcesses[parentID].end(),creatorProcessName); it != fRememberedProcesses[parentID].end()){ - auto idx = it- fRememberedProcesses[parentID].begin(); - fRememberedTracks[trackID] = {fRememberedTracks[parentID][idx]}; - fRememberedProcesses[trackID] = {fRememberedProcesses[parentID][idx]}; - } - else { - fRememberedTracks[trackID] = {fRememberedTracks[parentID][0]}; - fRememberedProcesses[trackID] = {fRememberedProcesses[parentID][0]}; - - - } - } - } - } -} - - -void GateOptrLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G4Step* CurrentStep,G4Track track,const G4Step* step,G4String processName){ - //We retrieve the process associated to the process name to split and we split according the process. - //Since for compton scattering, the gamma is not a secondary particles, this one need to have his own splitting function. - - - G4ParticleDefinition* particleDefinition = track.GetDefinition(); - G4ProcessManager* processManager = particleDefinition->GetProcessManager(); - G4ProcessVector* processList = processManager->GetProcessList(); - auto processToSplit = (*processList)[0]; - for (size_t i = 0; i <processList->size(); ++i) { - auto process = (*processList)[i]; - if (process->GetProcessName() == processName){ - processToSplit = process; - } - } - if (processName == "compt"){ - ComptonSplitting(CurrentStep,track,step,processToSplit); - } - else { - SecondariesSplitting(CurrentStep,track,step,processToSplit); - } - -} - - -void GateOptrLastVertexInteractionSplittingActor::ResetProcessesForEnteringParticles(G4Step * step){ - -// This function reset the processes and track registered to be split each time a particle enters into a volume. -// Here a new particle incoming is either a particle entering into the volume ( first pre step is a boundary of a mother volume -// or first step trigger the actor and the vertex is not either the mother volume or a the fdaughter volume) or an event generated within -// the volume (the particle did not enter into the volume and its parentID = 0). An additionnal condition is set with the trackID to ensure -// particles crossing twice the volume (after either a compton or pair prod) are not splitted. - - -G4int trackID = step->GetTrack()->GetTrackID(); -if ((fEventIDOfInitialSplittedTrack != fEventID) || ((fEventIDOfInitialSplittedTrack == fEventID ) && (trackID < fTrackIDOfInitialTrack))) - { - G4String logicalVolumeNamePreStep = "None"; - if (step->GetPreStepPoint()->GetPhysicalVolume() !=0){ - logicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - } - if (((step->GetPreStepPoint()->GetStepStatus() == 1) && (logicalVolumeNamePreStep == fMotherVolumeName)) || - ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != logicalVolumeNamePreStep) && (step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() !=fMotherVolumeName))) { - fTrackIDOfInitialTrack = trackID; - fEventIDOfInitialSplittedTrack = fEventID; - fRememberedProcesses.clear(); - fRememberedTracks.clear(); - - } - else if (step->GetTrack()->GetParentID() == 0){ - fTrackIDOfInitialTrack = trackID; - fEventIDOfInitialSplittedTrack = fEventID; - fRememberedProcesses.clear(); - fRememberedTracks.clear(); - } - } - } - - -void GateOptrLastVertexInteractionSplittingActor::PostponeFirstAnnihilationTrackIfInteraction(G4Step* step,G4String processName){ - //In case the first gamma issued fromannihilation undergoes an interaction, in order to not bias the process - //We keep in memory the particle post step state (with its secondaries) and kill the particle and its secondaries. - //If the second photon from annihilation exiting the collimation system with an interaction or is absorbed within - //the collimation, the particle is subsequently resimulated, starting from the interaction point. - - - G4int trackID = step->GetTrack()->GetTrackID(); - if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(),processName) != fListOfProcesses.end()){ - fSuspendForAnnihil = true; - G4Track trackToPostpone = G4Track(*(step->GetTrack())); - trackToPostpone.SetKineticEnergy(step->GetPostStepPoint()->GetKineticEnergy()); - trackToPostpone.SetMomentumDirection(step->GetPostStepPoint()->GetMomentumDirection()); - trackToPostpone.SetTrackStatus(step->GetTrack()->GetTrackStatus()); - trackToPostpone.SetPolarization(step->GetPostStepPoint()->GetPolarization()); - trackToPostpone.SetPosition(step->GetPostStepPoint()->GetPosition()); - trackToPostpone.SetTrackID(trackID); - fTracksToPostpone.push_back(trackToPostpone); - auto theTrack = fTracksToPostpone[0]; - - - auto secVec = step->GetfSecondary(); - for (int i = 0; i< secVec->size();i++){ - G4Track* sec = (*secVec)[i]; - G4Track copySec = G4Track((*sec)); - fTracksToPostpone.push_back(copySec); - } - - fTracksToPostpone[0].SetTrackID(trackID); - step->GetTrack()->SetTrackStatus( fKillTrackAndSecondaries); - - } -} - -void GateOptrLastVertexInteractionSplittingActor::RegenerationOfPostponedAnnihilationTrack(G4Step* step){ - -//If the second photon from annihilation suceed to exit the collimation system with at least one interaction or was absorbed. -//Resimulation of annihilation photons and its potential secondaries. - -G4TrackVector* currentSecondaries = step->GetfSecondary(); -for (int i =0; i < fTracksToPostpone.size();i ++){ - G4Track* trackToAdd = new G4Track(fTracksToPostpone[fTracksToPostpone.size() - 1 - i]); - trackToAdd->SetParentID(step->GetTrack()->GetTrackID() - 1); - - currentSecondaries->insert(currentSecondaries->begin(),trackToAdd); -} -G4Track* firstPostponedTrack = (*currentSecondaries)[0]; - -// Handle of case were the interaction killed the photon issued from annihilation, it will not be track at the following state -// and the boolean plus the track vector need to be reset - -if (firstPostponedTrack->GetTrackStatus() == 2){ - fSuspendForAnnihil = false; - fTracksToPostpone.clear(); -} - -} - -void GateOptrLastVertexInteractionSplittingActor::HandleTrackIDIfPostponedAnnihilation(G4Step* step){ -// The ID is to modify trackID and the processes and tracks sotcked for a specific trackID associated to -// the postponed annihilation to respect the trackID order in GEANT4. Since in this case the second photon is tracked before the first one -// his trackID +=1. For the postponed one, he is the first secondary particle of the second photon tracks, his trackID is therefore equal -// to the second photon trackID +1 instead of the second photon trackID -1. -// The parentID of secondary particles are also modified because they are used in the rememberlastprocess function -// At last the value associated to a specific trackID in the process and track map are modified according to the new trackID. - - - - if (fSuspendForAnnihil){ - if (step->GetTrack()->GetTrackID() == fTracksToPostpone[0].GetTrackID() -1){ - fRememberedProcesses[step->GetTrack()->GetTrackID()] = fRememberedProcesses[step->GetTrack()->GetTrackID() + 1]; - fRememberedProcesses.erase(step->GetTrack()->GetTrackID() + 1); - fRememberedTracks[step->GetTrack()->GetTrackID()] = fRememberedTracks[step->GetTrack()->GetTrackID() + 1]; - fRememberedTracks.erase(step->GetTrack()->GetTrackID() + 1); - auto vecSec = step->GetSecondary(); - for (int i =0; i <vecSec->size(); i++) - { - (*vecSec)[i]->SetParentID(step->GetTrack()->GetTrackID() +1); - } - step->GetTrack()->SetTrackID(step->GetTrack()->GetTrackID() +1); - } - if (step->GetTrack()->GetTrackID() == fTracksToPostpone[0].GetTrackID() + 1){ - auto vecSec = step->GetSecondary(); - for (int i =0; i <vecSec->size(); i++) - { - (*vecSec)[i]->SetParentID(step->GetTrack()->GetTrackID() -2); - } - step->GetTrack()->SetTrackID(step->GetTrack()->GetTrackID() - 2); - } - } -} - - -void GateOptrLastVertexInteractionSplittingActor::BeginOfRunAction(const G4Run* run){ - - // The way to behave of the russian roulette is the following : - // we provide a vector director and the theta angle acceptance, where theta = - // 0 is a vector colinear to the vector director Then if the track generated - // is on the acceptance angle, we add it to the primary track, and if it's not - // the case, we launch the russian roulette - - - if (fRotationVectorDirector) { - G4VPhysicalVolume *physBiasingVolume = - G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); - auto rot = physBiasingVolume->GetObjectRotationValue(); - fVectorDirector = rot * fVectorDirector; - } -} - - - - - -void GateOptrLastVertexInteractionSplittingActor::BeginOfEventAction(const G4Event* event){ - - - fParentID = -1; - fIsSplitted = false; - fSecondaries = false; - fEventID = event ->GetEventID(); - fEventIDOfSplittedTrack = -1; - fTrackIDOfSplittedTrack = -1; - -} - - - -void GateOptrLastVertexInteractionSplittingActor::PreUserTrackingAction (const G4Track *track){ - fIsFirstStep = true; -} - - - - - - -void GateOptrLastVertexInteractionSplittingActor::SteppingAction(G4Step* step) { - if (fIsFirstStep) { - HandleTrackIDIfPostponedAnnihilation(step); - - // To be sure to not split a track which has been already splitted, we put a condition on the trackID of - if ((fIsSplitted == true) && ( step->GetTrack()->GetTrackID() < fTrackIDOfSplittedTrack)){ - fIsSplitted = false; - } - - ResetProcessesForEnteringParticles(step); - } - G4int trackID = step->GetTrack()->GetTrackID(); - - - //std::cout<<trackID<<" "<<step->GetTrack()->GetTrackStatus()<<" "<<step->GetPreStepPoint()->GetKineticEnergy()<<" " <<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetPreStepPoint()->GetPosition()<<" "<<step->GetPostStepPoint()->GetPosition()<<std::endl; - - - if (fIsSplitted == false){ - - RememberLastProcessInformation(step); - G4String process = "None"; - if (auto search = fRememberedProcesses.find(trackID); search != fRememberedProcesses.end()) - process = fRememberedProcesses[trackID].back(); - - - G4String creatorProcessName = "None"; - if (step->GetTrack()->GetCreatorProcess() != 0) - creatorProcessName = step->GetTrack()->GetCreatorProcess()->GetProcessName(); - - - if ((!fSuspendForAnnihil )&& (process != "annihil") && (creatorProcessName == "annihil")){ - PostponeFirstAnnihilationTrackIfInteraction(step,process); - } - - // If the first annihilation photon exit the collimation and the process to split is annihilation - // We kill the second photon, because the annihilation will generate both the photons. - if ((!fSuspendForAnnihil ) && (creatorProcessName == "annihil") && (fTrackIDOfSplittedTrack == step->GetTrack()->GetParentID()) && (fEventID == fEventIDOfSplittedTrack )) - step->GetTrack()->SetTrackStatus(fStopAndKill); - - if (fSuspendForAnnihil){ - if (trackID == fTracksToPostpone[0].GetTrackID() - 1){ - fSuspendForAnnihil = false; - fTracksToPostpone.clear(); - } - } - - - G4String logicalVolumeNamePostStep = "None"; - if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) - logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - - if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(),logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { - G4String processToSplit = "None"; - if (auto search = fRememberedProcesses.find(trackID); search != fRememberedProcesses.end()) - processToSplit = fRememberedProcesses[trackID].back(); - - if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(),processToSplit) != fListOfProcesses.end()){ - - //Handle of pecularities (1): - - // If the process t split is the gamma issued from compton interaction, the electron primary generated have to be killed - // given that electron will be regenerated - - if ((processToSplit == "compt") && (step->GetTrack()->GetParticleDefinition()->GetParticleName() == "gamma")){ - auto secondaries = step->GetfSecondary(); - if (secondaries->size() > 0){ - G4Track* lastSecTrack = secondaries->back(); - lastSecTrack->SetTrackStatus(fStopAndKill); - } - } - - //Handle of pecularities (2): - - // If the process to split is the annihilation, the second photon, postponed or not, have to be killed - // the reset of the postpone is performed here, whereas the kill of the next annihilation photon, if not postponed - // is realised at the beginning of the step tracking. - - - if (processToSplit == "annihil"){ - if (fSuspendForAnnihil){ - fSuspendForAnnihil = false; - fTracksToPostpone.clear(); - } - } - - - G4Track trackToSplit = fRememberedTracks[trackID].back(); - CreateNewParticleAtTheLastVertex(step,trackToSplit,trackToSplit.GetStep(),processToSplit); - - - fSecondaries = true; - fTrackIDOfSplittedTrack = trackToSplit.GetTrackID(); - fEventIDOfSplittedTrack = fEventID; - fTrackIDOfInitialSplittedTrack = fTrackIDOfInitialTrack; - step->GetTrack()->SetTrackStatus(fStopAndKill); - } - } - } - - - if ((fSuspendForAnnihil) && ((step->GetTrack()->GetTrackStatus() == 1) || (step->GetTrack()->GetTrackStatus() == 2))){ - if (trackID == fTracksToPostpone[0].GetTrackID()) { - RegenerationOfPostponedAnnihilationTrack(step); - } - } - - fIsFirstStep = false; - } - - - - - -void GateOptrLastVertexInteractionSplittingActor::PostUserTrackingAction (const G4Track *track){ - if (fSecondaries){ - fIsSplitted = true; - fSecondaries = false; - } -} - - - - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - - - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/pyGateOptrLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp similarity index 51% rename from core/opengate_core/opengate_lib/pyGateOptrLastVertexInteractionSplittingActor.cpp rename to core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp index ec54a8077..4a6a8c130 100644 --- a/core/opengate_core/opengate_lib/pyGateOptrLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp @@ -7,15 +7,15 @@ #include <pybind11/pybind11.h> namespace py = pybind11; -#include "GateOptrLastVertexInteractionSplittingActor.h" +#include "GateLastVertexInteractionSplittingActor.h" -void init_GateOptrLastVertexInteractionSplittingActor(py::module &m) { +void init_GateLastVertexInteractionSplittingActor(py::module &m) { - py::class_<GateOptrLastVertexInteractionSplittingActor, GateVActor, - std::unique_ptr<GateOptrLastVertexInteractionSplittingActor, py::nodelete>>( - m, "GateOptrLastVertexInteractionSplittingActor") + py::class_<GateLastVertexInteractionSplittingActor, GateVActor, + std::unique_ptr<GateLastVertexInteractionSplittingActor, py::nodelete>>( + m, "GateLastVertexInteractionSplittingActor") .def_readwrite( "fListOfVolumeAncestor", - &GateOptrLastVertexInteractionSplittingActor::fListOfVolumeAncestor) + &GateLastVertexInteractionSplittingActor::fListOfVolumeAncestor) .def(py::init<py::dict &>()); } diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 5ae64b3d9..b82ee5c26 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -454,7 +454,7 @@ def __init__(self, user_info): g4.GateOptrComptSplittingActor.__init__(self, user_info.__dict__) -class LastVertexInteractionSplittingActor(g4.GateOptrLastVertexInteractionSplittingActor, ActorBase): +class LastVertexInteractionSplittingActor(g4.GateLastVertexInteractionSplittingActor, ActorBase): type_name = "LastVertexInteractionSplittingActor" def set_default_user_info(user_info): @@ -469,7 +469,7 @@ def set_default_user_info(user_info): def __init__(self, user_info): ActorBase.__init__(self, user_info) - g4.GateOptrLastVertexInteractionSplittingActor.__init__(self, user_info.__dict__) + g4.GateLastVertexInteractionSplittingActor.__init__(self, user_info.__dict__) self.list_of_volume_name = user_info.list_of_volume_name self.user_info.mother = user_info.mother From 098094f39681357bc8df52d4557986a64037a725 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Wed, 19 Jun 2024 14:59:03 +0200 Subject: [PATCH 23/82] Add of a pecularity to handle exiting positron --- ...ateLastVertexInteractionSplittingActor.cpp | 52 +++++++++++++++---- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index 3b18610c6..b192c62e8 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -90,6 +90,7 @@ G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack(G4ParticleC G4double energy = gammaProcess->GetProposedKineticEnergy(); G4double globalTime = track.GetGlobalTime(); G4double newGammaWeight = weight; + G4ThreeVector polarization = gammaProcess->GetProposedPolarization(); const G4ThreeVector momentum = gammaProcess->GetProposedMomentumDirection(); const G4ThreeVector position = track.GetPosition(); G4Track *newTrack = new G4Track(track); @@ -98,6 +99,7 @@ G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack(G4ParticleC newTrack->SetKineticEnergy(energy); newTrack->SetMomentumDirection(momentum); newTrack->SetPosition(position); + newTrack->SetPolarization(polarization); return newTrack; } @@ -108,6 +110,9 @@ void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step *CurrentSt G4TrackVector *trackVector = CurrentStep->GetfSecondary(); G4double gammaWeight = 0; G4int nCall = (G4int)fSplittingFactor; + if (fSplittingFactor == 1){ + fSplittingFactor = 1.0001; + } for (int i = 0; i < nCall; i++) { GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; @@ -148,6 +153,8 @@ void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step *CurrentSt processFinalState->Clear(); gammaProcessFinalState->Clear(); } + if (fSplittingFactor == 1.0001) + fSplittingFactor = 1; } void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step *CurrentStep, G4Track track, const G4Step *step, G4VProcess *process) @@ -157,6 +164,7 @@ void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step *Curre G4TrackVector *trackVector = CurrentStep->GetfSecondary(); G4double gammaWeight = 0; G4int nCall = (G4int)fSplittingFactor; + for (int i = 0; i < nCall; i++) { G4VParticleChange *processFinalState = nullptr; @@ -189,7 +197,6 @@ void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step *Curre } else { - std::cout<<newTrack->GetKineticEnergy()<<std::endl; newTrack->SetWeight(gammaWeight); trackVector->push_back(newTrack); } @@ -274,7 +281,7 @@ void GateLastVertexInteractionSplittingActor::RememberLastProcessInformation(G4S trackInformation->SetMomentumDirection(stepPoint->GetMomentumDirection()); trackInformation->SetTrackStatus(fAlive); trackInformation->SetPolarization(stepPoint->GetPolarization()); - trackInformation->SetTrackID(trackID); + //trackInformation->SetPosition(stepPoint->GetPosition()); if (auto search = fRememberedTracks.find(trackID); search != fRememberedTracks.end()) { @@ -517,20 +524,22 @@ void GateLastVertexInteractionSplittingActor::PreUserTrackingAction(const G4Trac void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { - + + G4String particleName = step->GetTrack()->GetParticleDefinition()->GetParticleName(); G4String creatorProcessName = "None"; if (step->GetTrack()->GetCreatorProcess() != 0) creatorProcessName = step->GetTrack()->GetCreatorProcess()->GetProcessName(); if (fIsFirstStep) { - HandleTrackIDIfPostponedAnnihilation(step); ResetProcessesForEnteringParticles(step); } G4int trackID = step->GetTrack()->GetTrackID(); - + //std::cout<<step->GetPreStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetWeight()<<std::endl; + + if (step->GetTrack()->GetWeight() >= fWeightOfEnteringParticle) { RememberLastProcessInformation(step); @@ -546,6 +555,10 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); } + if ((particleName == "e-") && (step->GetTrack()->GetWeight() == fWeightOfEnteringParticle) && (creatorProcessName == "conv") && (fTrackIDOfSplittedTrack == step->GetTrack()->GetParentID()) && (fEventID == fEventIDOfSplittedTrack)){ + step->GetTrack()->SetTrackStatus(fStopAndKill); + } + if ((!fSuspendForAnnihil) && (process != "annihil") && (creatorProcessName == "annihil") && (step->GetTrack()->GetTrackStatus() != 3)) { G4int parentID = step->GetTrack()->GetParentID(); @@ -574,19 +587,25 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) if (((step->GetTrack()->GetTrackStatus() != 2) && (step->GetTrack()->GetTrackStatus() != 3)) && (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), logicalVolumeNamePostStep) != fListOfVolumeAncestor.end())) { G4String processToSplit = "None"; - if (auto search = fRememberedProcesses.find(trackID); search != fRememberedProcesses.end()) + G4Track* trackToSplit = nullptr; + G4Step* stepToSplit = nullptr; + + if (auto search = fRememberedProcesses.find(trackID); search != fRememberedProcesses.end()){ processToSplit = fRememberedProcesses[trackID].back(); + trackToSplit = fRememberedTracks[trackID].back(); + stepToSplit = fRememberedSteps[trackID].back(); + } + if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), processToSplit) != fListOfProcesses.end()) { - std::cout<<step->GetTrack()->GetKineticEnergy()<<std::endl; // Handle of pecularities (1): // If the process t split is the gamma issued from compton interaction, the electron primary generated have to be killed // given that electron will be regenerated - if ((processToSplit == "compt") && (step->GetTrack()->GetParticleDefinition()->GetParticleName() == "gamma")) + if ((processToSplit == "compt") && (particleName == "gamma")) { auto secondaries = step->GetfSecondary(); if (secondaries->size() > 0) @@ -610,9 +629,20 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) fTracksToPostpone.clear(); } } + // Handle of pecularities 3 : If the positron which created one or more brem photons exits + // all the brems photons will be killed before their tracking, and the conv processes will then be replayed + + if ((particleName == "e+") &&(processToSplit != "None")) { + G4int parentID = step->GetTrack()->GetParentID(); + processToSplit = fRememberedProcesses[parentID].back(); + trackToSplit = fRememberedTracks[parentID].back(); + stepToSplit = fRememberedSteps[parentID].back(); + step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); + } + + + - G4Track *trackToSplit = fRememberedTracks[trackID].back(); - G4Step *stepToSplit = fRememberedSteps[trackID].back(); CreateNewParticleAtTheLastVertex(step, *trackToSplit, stepToSplit, processToSplit); fTrackIDOfSplittedTrack = trackToSplit->GetTrackID(); @@ -634,6 +664,8 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) fIsFirstStep = false; } + + void GateLastVertexInteractionSplittingActor::PostUserTrackingAction(const G4Track *track) { } From 244e6d9ab6d2dc648a87e1fb0826153ad82a6109 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 25 Jun 2024 11:40:24 +0200 Subject: [PATCH 24/82] change the way to split, the old one being biased, work in progress --- ...ateLastVertexInteractionSplittingActor.cpp | 284 +++++++++++------- .../GateLastVertexInteractionSplittingActor.h | 13 +- .../GateLastVertexSplittingPostStepDoIt.h | 157 +++++++++- .../src/test076_last_vertex_splittting.py | 22 +- .../test076_last_vertex_splittting_distrib.py | 234 +++++++++++++++ 5 files changed, 590 insertions(+), 120 deletions(-) create mode 100755 opengate/tests/src/test076_last_vertex_splittting_distrib.py diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index b192c62e8..2f6d125e9 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -39,11 +39,13 @@ #include "G4PhysicalVolumeStore.hh" #include "G4ProcessManager.hh" #include "G4TrackingManager.hh" +#include "G4TrackStatus.hh" #include "G4ProcessVector.hh" #include "GateOptnComptSplitting.h" #include "GateLastVertexInteractionSplittingActor.h" #include "GateLastVertexSplittingPostStepDoIt.h" #include "G4VParticleChange.hh" +#include "G4eplusAnnihilation.hh" //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... @@ -103,44 +105,25 @@ G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack(G4ParticleC return newTrack; } -void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step *CurrentStep, G4Track track, const G4Step *step, G4VProcess *process) +void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step *CurrentStep, G4Track *track, const G4Step *step, G4VProcess *process) { // Loop on process and add the secondary tracks to the current step secondary vector G4TrackVector *trackVector = CurrentStep->GetfSecondary(); G4double gammaWeight = 0; - G4int nCall = (G4int)fSplittingFactor; - if (fSplittingFactor == 1){ - fSplittingFactor = 1.0001; - } - for (int i = 0; i < nCall; i++) - { - GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; - G4VParticleChange *processFinalState = emProcess->PostStepDoIt(track, *step); - G4ParticleChangeForGamma *gammaProcessFinalState = (G4ParticleChangeForGamma *)processFinalState; - const G4ThreeVector momentum = gammaProcessFinalState->GetProposedMomentumDirection(); - gammaWeight = CurrentStep->GetTrack()->GetWeight() / fSplittingFactor; - if (fRussianRouletteForAngle == true) - { - G4double weightToApply = RussianRouletteForAngleSurvival(momentum, fVectorDirector, fMaxTheta, fSplittingFactor); - if (weightToApply != 0) - { - gammaWeight = gammaWeight * weightToApply; - G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, track, gammaWeight); - trackVector->push_back(newTrack); - G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - for (int j = 0; j < NbOfSecondaries; j++) - { - G4Track *newTrack = processFinalState->GetSecondary(j); - newTrack->SetWeight(gammaWeight); - trackVector->push_back(newTrack); - } - } - } - else + GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; + G4VParticleChange *processFinalState = emProcess->PostStepDoIt(*track, *step); + G4ParticleChangeForGamma *gammaProcessFinalState = (G4ParticleChangeForGamma *)processFinalState; + const G4ThreeVector momentum = gammaProcessFinalState->GetProposedMomentumDirection(); + gammaWeight = fWeightOfEnteringParticle/ fSplittingFactor; + if (fRussianRouletteForAngle == true) + { + G4double weightToApply = RussianRouletteForAngleSurvival(momentum, fVectorDirector, fMaxTheta, fSplittingFactor); + if (weightToApply != 0) { - G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, track, gammaWeight); + gammaWeight = gammaWeight * weightToApply; + G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, *track, gammaWeight); trackVector->push_back(newTrack); G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); for (int j = 0; j < NbOfSecondaries; j++) @@ -150,59 +133,74 @@ void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step *CurrentSt trackVector->push_back(newTrack); } } - processFinalState->Clear(); - gammaProcessFinalState->Clear(); } - if (fSplittingFactor == 1.0001) - fSplittingFactor = 1; + + else + { + G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, *track, gammaWeight); + trackVector->push_back(newTrack); + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + for (int j = 0; j < NbOfSecondaries; j++) + { + G4Track *newTrack = processFinalState->GetSecondary(j); + newTrack->SetWeight(gammaWeight); + trackVector->push_back(newTrack); + } + } + processFinalState->Clear(); + gammaProcessFinalState->Clear(); + + } -void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step *CurrentStep, G4Track track, const G4Step *step, G4VProcess *process) +void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step *CurrentStep, G4Track* track, const G4Step *step, G4VProcess *process) { // Loop on process and add the secondary tracks to the current step secondary vector. - G4String particleName = track.GetParticleDefinition()->GetParticleName(); + + G4String particleName = track->GetParticleDefinition()->GetParticleName(); G4TrackVector *trackVector = CurrentStep->GetfSecondary(); - G4double gammaWeight = 0; - G4int nCall = (G4int)fSplittingFactor; - - for (int i = 0; i < nCall; i++) + G4double gammaWeight = 0; + G4VParticleChange *processFinalState = nullptr; + if (process->GetProcessName() == "eBrem") { - G4VParticleChange *processFinalState = nullptr; - - if (process->GetProcessName() == "eBrem") - { - GateBremPostStepDoIt *bremProcess = (GateBremPostStepDoIt *)process; - processFinalState = bremProcess->GateBremPostStepDoIt::PostStepDoIt(track, *step); - } - else - { - GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; - processFinalState = emProcess->PostStepDoIt(track, *step); + GateBremPostStepDoIt *bremProcess = (GateBremPostStepDoIt *)process; + processFinalState = bremProcess->GateBremPostStepDoIt::PostStepDoIt(*track, *step); + } + else + { + GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *) process ; + if (track->GetTrackStatus() == 0) + processFinalState = emProcess->PostStepDoIt(*track, *step); + if (track->GetTrackStatus() == 1){ + GateplusannihilAtRestDoIt* eplusAnnihilProcess = (GateplusannihilAtRestDoIt*) process; + eplusAnnihilProcess ->GateplusannihilAtRestDoIt::AtRestDoIt(*track, *step); } - G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - for (int j = 0; j < NbOfSecondaries; j++) + } + + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + if (NbOfSecondaries > 0) { + gammaWeight = fWeightOfEnteringParticle/fSplittingFactor; + G4Track *newTrack = processFinalState->GetSecondary(0); + std::cout<<newTrack->GetKineticEnergy()<<std::endl; + if ((fRussianRouletteForAngle == true) && (particleName == "gamma")) { - gammaWeight = CurrentStep->GetTrack()->GetWeight() / fSplittingFactor; - G4Track *newTrack = processFinalState->GetSecondary(j); - if ((fRussianRouletteForAngle == true) && (particleName == "gamma")) - { - const G4ThreeVector momentum = newTrack->GetMomentumDirection(); - G4double weightToApply = RussianRouletteForAngleSurvival(momentum, fVectorDirector, fMaxTheta, fSplittingFactor); - if (weightToApply != 0) - { - gammaWeight = gammaWeight * weightToApply; - newTrack->SetWeight(gammaWeight); - trackVector->push_back(newTrack); - } - } - else + const G4ThreeVector momentum = newTrack->GetMomentumDirection(); + G4double weightToApply = RussianRouletteForAngleSurvival(momentum, fVectorDirector, fMaxTheta, fSplittingFactor); + if (weightToApply != 0) { + gammaWeight = gammaWeight * weightToApply; newTrack->SetWeight(gammaWeight); trackVector->push_back(newTrack); } } - processFinalState->Clear(); + else + { + newTrack->SetWeight(gammaWeight); + trackVector->push_back(newTrack); + } } + processFinalState->Clear(); + } void GateLastVertexInteractionSplittingActor::ClearRememberedTracksAndSteps(std::map<G4int, G4TrackVector> rememberedTracks, std::map<G4int, std::vector<G4Step *>> rememberedSteps) @@ -213,6 +211,7 @@ void GateLastVertexInteractionSplittingActor::ClearRememberedTracksAndSteps(std: std::vector<G4Track*> vector = it->second; for (auto it2 = vector.begin(); it2 != vector.end(); it2++){ if ((std::find(trackToKill.begin(), trackToKill.end(), *it2) == trackToKill.end())){ + //Method set pour clear les doublons au lieu de find. trackToKill.push_back(*it2); } } @@ -279,7 +278,11 @@ void GateLastVertexInteractionSplittingActor::RememberLastProcessInformation(G4S trackInformation->SetKineticEnergy(stepPoint->GetKineticEnergy()); trackInformation->SetMomentumDirection(stepPoint->GetMomentumDirection()); - trackInformation->SetTrackStatus(fAlive); + if ((processName =="annihil") && (step->GetTrack()->GetKineticEnergy() == 0)) + trackInformation->SetTrackStatus(fStopButAlive); + else{ + trackInformation->SetTrackStatus(fAlive); + } trackInformation->SetPolarization(stepPoint->GetPolarization()); //trackInformation->SetPosition(stepPoint->GetPosition()); @@ -322,14 +325,14 @@ void GateLastVertexInteractionSplittingActor::RememberLastProcessInformation(G4S } } -void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G4Step *CurrentStep, G4Track track, const G4Step *step, G4String processName) +void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G4Step *CurrentStep, G4Track* track, const G4Step *step, G4String processName) { // We retrieve the process associated to the process name to split and we split according the process. // Since for compton scattering, the gamma is not a secondary particles, this one need to have his own splitting function. - - G4ParticleDefinition *particleDefinition = track.GetDefinition(); + G4ParticleDefinition *particleDefinition = track->GetDefinition(); G4ProcessManager *processManager = particleDefinition->GetProcessManager(); G4ProcessVector *processList = processManager->GetProcessList(); + G4VProcess *processToSplit = nullptr; for (size_t i = 0; i < processList->size(); ++i) { @@ -341,12 +344,15 @@ void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G } if (processName == "compt") { + ComptonSplitting(CurrentStep, track, step, processToSplit); } - else + + else if (processName != "eBrem") { SecondariesSplitting(CurrentStep, track, step, processToSplit); } + } void GateLastVertexInteractionSplittingActor::ResetProcessesForEnteringParticles(G4Step *step) @@ -511,7 +517,7 @@ void GateLastVertexInteractionSplittingActor::BeginOfEventAction(const G4Event * fParentID = -1; fEventID = event->GetEventID(); - if (fEventID%30000 == 0) + //if (fEventID%30000 == 0) std::cout <<fEventID<<std::endl; fEventIDOfSplittedTrack = -1; fTrackIDOfSplittedTrack = -1; @@ -524,7 +530,6 @@ void GateLastVertexInteractionSplittingActor::PreUserTrackingAction(const G4Trac void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { - G4String particleName = step->GetTrack()->GetParticleDefinition()->GetParticleName(); G4String creatorProcessName = "None"; if (step->GetTrack()->GetCreatorProcess() != 0) @@ -532,15 +537,84 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) if (fIsFirstStep) { - HandleTrackIDIfPostponedAnnihilation(step); + //HandleTrackIDIfPostponedAnnihilation(step); ResetProcessesForEnteringParticles(step); } G4int trackID = step->GetTrack()->GetTrackID(); - //std::cout<<step->GetPreStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetWeight()<<std::endl; + + + if (fIsSplitted == true){ + + + G4String logicalVolumeNamePostStep = "None"; + if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) + logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + + + G4String processName = "None"; + if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) + processName = step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); + + if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), processName) != fListOfProcesses.end()){ + step->GetTrack()->SetTrackStatus(fStopAndKill); + } + + + + if (step->GetTrack()->GetTrackStatus() == 2){ + delete fTrackToSplit; + delete fStepToSplit; + fTrackToSplit = new G4Track(*fRememberedTracks[fTrackIDOfSplittedTrack].back()); + fStepToSplit = new G4Step(*fRememberedSteps[fTrackIDOfSplittedTrack].back()); + std::cout <<"IN"<<" "<<fIsSplitted<<" "<<step->GetPreStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetTrack()->GetTrackStatus()<<" "<<logicalVolumeNamePostStep<<std::endl; + CreateNewParticleAtTheLastVertex(step, fTrackToSplit, fStepToSplit, fProcessToSplit); + + } + + if (fEventID > 27666){ + std::cout << fIsSplitted<<" "<<step->GetPreStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetTrack()->GetTrackStatus()<<" "<<logicalVolumeNamePostStep<<std::endl; + + } + + //if (fEventID == 27135) + //std::cout << step->GetPreStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetTrack()->GetTrackStatus()<<std::endl; + if (((step->GetTrack()->GetTrackStatus() != 2) && (step->GetTrack()->GetTrackStatus() != 3)) && (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), logicalVolumeNamePostStep) != fListOfVolumeAncestor.end())) + { + + if (fSplitCounter < fWeightOfEnteringParticle) { + delete fTrackToSplit; + delete fStepToSplit; + fTrackToSplit = new G4Track(*fRememberedTracks[fTrackIDOfSplittedTrack].back()); + fStepToSplit = new G4Step(*fRememberedSteps[fTrackIDOfSplittedTrack].back()); + CreateNewParticleAtTheLastVertex(step, fTrackToSplit, fStepToSplit, fProcessToSplit); + //std::cout<<fTrackToSplit<<" "<<fStepToSplit<<std::endl; + G4TrackVector *trackVector = step->GetfSecondary(); + G4Track* theTrack= trackVector->back(); + fSplitCounter += theTrack->GetWeight(); + if (fEventID > 27666){ + std::cout << fSplitCounter<<" "<<fIsSplitted<<" "<<step->GetPreStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetTrack()->GetTrackStatus()<<std::endl; + } + //std::cout<<theTrack->GetWeight()<<std::endl; + if (fSplitCounter >= fWeightOfEnteringParticle) { + delete fTrackToSplit; + delete fStepToSplit; + fTrackToSplit = nullptr; + fStepToSplit = nullptr; + //std::cout<<"lol"<<std::endl; + fSplitCounter = 0; + fIsSplitted = false; + fProcessToSplit = "None"; + theTrack->SetTrackStatus(fStopAndKill); + } + } + } + + + } - if (step->GetTrack()->GetWeight() >= fWeightOfEnteringParticle) + if (fIsSplitted == false) { RememberLastProcessInformation(step); G4String process = "None"; @@ -550,15 +624,19 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) } // std::cout<<step->GetTrack()->GetVertexPosition()<<" "<<step->GetTrack()->GetParticleDefinition()->GetParticleName()<<" "<<fSuspendForAnnihil<<" "<<process<<" "<<step->GetTrack()->GetWeight()<<" "<<step->GetTrack()->GetParentID()<<" "<<step->GetTrack()->GetTrackID()<<" "<<creatorProcessName<<" "<<step->GetTrack()->GetTrackStatus()<<" "<<step->GetPreStepPoint()->GetKineticEnergy()<<" " <<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetPreStepPoint()->GetPosition()<<" "<<step->GetPostStepPoint()->GetPosition()<<std::endl; + /* if ((!fSuspendForAnnihil) && (creatorProcessName == "annihil") && (fTrackIDOfSplittedTrack == step->GetTrack()->GetParentID()) && (fEventID == fEventIDOfSplittedTrack)) { step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); } + */ if ((particleName == "e-") && (step->GetTrack()->GetWeight() == fWeightOfEnteringParticle) && (creatorProcessName == "conv") && (fTrackIDOfSplittedTrack == step->GetTrack()->GetParentID()) && (fEventID == fEventIDOfSplittedTrack)){ step->GetTrack()->SetTrackStatus(fStopAndKill); } + + /* if ((!fSuspendForAnnihil) && (process != "annihil") && (creatorProcessName == "annihil") && (step->GetTrack()->GetTrackStatus() != 3)) { G4int parentID = step->GetTrack()->GetParentID(); @@ -568,6 +646,7 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) PostponeFirstAnnihilationTrackIfInteraction(step, process); } } + // If the first annihilation photon exit the collimation and the process to split is annihilation // We kill the second photon, because the annihilation will generate both the photons. @@ -579,33 +658,33 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) fTracksToPostpone.clear(); } } - + */ G4String logicalVolumeNamePostStep = "None"; if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); if (((step->GetTrack()->GetTrackStatus() != 2) && (step->GetTrack()->GetTrackStatus() != 3)) && (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), logicalVolumeNamePostStep) != fListOfVolumeAncestor.end())) { - G4String processToSplit = "None"; - G4Track* trackToSplit = nullptr; - G4Step* stepToSplit = nullptr; if (auto search = fRememberedProcesses.find(trackID); search != fRememberedProcesses.end()){ - processToSplit = fRememberedProcesses[trackID].back(); - trackToSplit = fRememberedTracks[trackID].back(); - stepToSplit = fRememberedSteps[trackID].back(); + fProcessToSplit = fRememberedProcesses[trackID].back(); + fTrackToSplit = new G4Track(*fRememberedTracks[trackID].back()); + fStepToSplit = new G4Step(*fRememberedSteps[trackID].back()); } - if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), processToSplit) != fListOfProcesses.end()) + if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), fProcessToSplit) != fListOfProcesses.end()) { + fTrackIDOfSplittedTrack = trackID; + fEventIDOfSplittedTrack = fEventID; + fTrackIDOfInitialSplittedTrack = fTrackIDOfInitialTrack; // Handle of pecularities (1): // If the process t split is the gamma issued from compton interaction, the electron primary generated have to be killed // given that electron will be regenerated - if ((processToSplit == "compt") && (particleName == "gamma")) + if ((fProcessToSplit == "compt") && (particleName == "gamma")) { auto secondaries = step->GetfSecondary(); if (secondaries->size() > 0) @@ -620,8 +699,8 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) // If the process to split is the annihilation, the second photon, postponed or not, have to be killed // the reset of the postpone is performed here, whereas the kill of the next annihilation photon, if not postponed // is realised at the beginning of the step tracking. - - if (processToSplit == "annihil") + /* + if (fProcessToSplit == "annihil") { if (fSuspendForAnnihil) { @@ -629,30 +708,30 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) fTracksToPostpone.clear(); } } + */ // Handle of pecularities 3 : If the positron which created one or more brem photons exits // all the brems photons will be killed before their tracking, and the conv processes will then be replayed - if ((particleName == "e+") &&(processToSplit != "None")) { + if ((particleName == "e+") &&(fProcessToSplit != "None")) { G4int parentID = step->GetTrack()->GetParentID(); - processToSplit = fRememberedProcesses[parentID].back(); - trackToSplit = fRememberedTracks[parentID].back(); - stepToSplit = fRememberedSteps[parentID].back(); + fProcessToSplit = fRememberedProcesses[parentID].back(); + delete fTrackToSplit; + delete fStepToSplit; + fTrackToSplit = new G4Track(*fRememberedTracks[parentID].back()); + fStepToSplit = new G4Step(*fRememberedSteps[parentID].back()); step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); + fTrackIDOfInitialTrack = parentID; } - - - - CreateNewParticleAtTheLastVertex(step, *trackToSplit, stepToSplit, processToSplit); - - fTrackIDOfSplittedTrack = trackToSplit->GetTrackID(); - fEventIDOfSplittedTrack = fEventID; - fTrackIDOfInitialSplittedTrack = fTrackIDOfInitialTrack; + fIsSplitted = true; + + CreateNewParticleAtTheLastVertex(step, fTrackToSplit, fStepToSplit, fProcessToSplit); step->GetTrack()->SetTrackStatus(fStopAndKill); } } } + /* if ((fSuspendForAnnihil) && ((step->GetTrack()->GetTrackStatus() == 1) || (step->GetTrack()->GetTrackStatus() == 2))) { if (trackID == fTracksToPostpone[0].GetTrackID()) @@ -660,6 +739,7 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) RegenerationOfPostponedAnnihilationTrack(step); } } + */ fIsFirstStep = false; } diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h index b79c5cc7a..e8877ae4b 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h @@ -59,6 +59,13 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { G4bool fIsFirstStep = true; G4bool fSuspendForAnnihil = false; G4double fWeightOfEnteringParticle = 0; + G4double fSplitCounter = 0; + G4bool fIsSplitted = false; + + G4Track* fTrackToSplit = nullptr; + G4Step* fStepToSplit = nullptr; + G4String fProcessToSplit = "None"; + std::vector<G4Track> fTracksToPostpone; std::map<G4int,G4TrackVector> fRememberedTracks; @@ -78,13 +85,13 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { //Pure splitting functions G4double RussianRouletteForAngleSurvival(G4ThreeVector, G4ThreeVector, G4double, G4double); G4Track* CreateComptonTrack(G4ParticleChangeForGamma*,G4Track, G4double); - void ComptonSplitting(G4Step* CurrentStep,G4Track track,const G4Step* step,G4VProcess* process); - void SecondariesSplitting(G4Step* CurrentStep,G4Track track,const G4Step* step,G4VProcess* process); + void ComptonSplitting(G4Step* CurrentStep,G4Track* track,const G4Step* step,G4VProcess* process); + void SecondariesSplitting(G4Step* CurrentStep,G4Track* track,const G4Step* step,G4VProcess* process); //Handling the remembered processes to replay void RememberLastProcessInformation(G4Step*); - void CreateNewParticleAtTheLastVertex(G4Step*,G4Track,const G4Step*,G4String); + void CreateNewParticleAtTheLastVertex(G4Step*,G4Track*,const G4Step*,G4String); void ResetProcessesForEnteringParticles(G4Step * step); void ClearRememberedTracksAndSteps(std::map<G4int, G4TrackVector>, std::map<G4int, std::vector<G4Step *>>); diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h index d7880138a..22b75a0f8 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h @@ -31,6 +31,18 @@ #include "G4VEnergyLossProcess.hh" #include "G4VEmProcess.hh" #include "G4VParticleChange.hh" +#include "G4eplusAnnihilation.hh" +#include "G4PhysicalConstants.hh" +#include "G4MaterialCutsCouple.hh" +#include "G4Gamma.hh" +#include "G4Electron.hh" +#include "G4Positron.hh" +#include "G4eeToTwoGammaModel.hh" +#include "G4EmBiasingManager.hh" +#include "G4EntanglementAuxInfo.hh" +#include "G4eplusAnnihilationEntanglementClipBoard.hh" +#include "G4EmParameters.hh" +#include "G4PhysicsModelCatalog.hh" #include <iostream> @@ -62,14 +74,155 @@ GateGammaEmPostStepDoIt(); ~ GateGammaEmPostStepDoIt(); -virtual G4VParticleChange * PostStepDoIt (const G4Track & track, const G4Step & step) override +virtual G4VParticleChange * PostStepDoIt(const G4Track & track, const G4Step & step) override { const G4MaterialCutsCouple* couple = step.GetPreStepPoint()->GetMaterialCutsCouple(); - currentCouple = couple; + + std::cout<<track.GetDefinition()->GetParticleName()<<std::endl; G4VParticleChange* particleChange = G4VEmProcess::PostStepDoIt(track,step); + std::cout<<track.GetDefinition()->GetParticleName()<<std::endl; return particleChange; } +}; + +class GateplusannihilAtRestDoIt : public G4eplusAnnihilation { +public : + +GateplusannihilAtRestDoIt(); +~ GateplusannihilAtRestDoIt(); + +virtual G4VParticleChange* AtRestDoIt(const G4Track& track, + const G4Step& step) override +// Performs the e+ e- annihilation when both particles are assumed at rest. + { + fParticleChange.InitializeForPostStep(track); + DefineMaterial(step.GetPreStepPoint()->GetMaterialCutsCouple()); + G4int idx = (G4int)CurrentMaterialCutsCoupleIndex(); + G4double ene(0.0); + G4VEmModel* model = SelectModel(ene, idx); + + // define new weight for primary and secondaries + G4double weight = fParticleChange.GetParentWeight(); + + // sample secondaries + secParticles.clear(); + G4double gammaCut = GetGammaEnergyCut(); + model->SampleSecondaries(&secParticles, MaterialCutsCouple(), + track.GetDynamicParticle(), gammaCut); + + G4int num0 = (G4int)secParticles.size(); + // splitting or Russian roulette + if(biasManager) { + if(biasManager->SecondaryBiasingRegion(idx)) { + G4double eloss = 0.0; + weight *= biasManager->ApplySecondaryBiasing( + secParticles, track, model, &fParticleChange, eloss, + idx, gammaCut, step.GetPostStepPoint()->GetSafety()); + if(eloss > 0.0) { + eloss += fParticleChange.GetLocalEnergyDeposit(); + fParticleChange.ProposeLocalEnergyDeposit(eloss); + } + } + } + + // save secondaries + G4int num = (G4int)secParticles.size(); + + // Check that entanglement is switched on... (the following flag is + // set by /process/em/QuantumEntanglement). + G4bool entangled = G4EmParameters::Instance()->QuantumEntanglement(); + // ...and that we have two gammas with both gammas' energies above + // gammaCut (entanglement is only programmed for e+ e- -> gamma gamma). + G4bool entangledgammagamma = false; + if (entangled) { + if (num == 2) { + entangledgammagamma = true; + for (const auto* p: secParticles) { + if (p->GetDefinition() != G4Gamma::Gamma() || + p->GetKineticEnergy() < gammaCut) { + entangledgammagamma = false; + } + } + } + } + + // Prepare a shared pointer for psossible use below. If it is used, the + // shared pointer is copied into the tracks through G4EntanglementAuxInfo. + // This ensures the clip board lasts until both tracks are destroyed. + std::shared_ptr<G4eplusAnnihilationEntanglementClipBoard> clipBoard; + if (entangledgammagamma) { + clipBoard = std::make_shared<G4eplusAnnihilationEntanglementClipBoard>(); + clipBoard->SetParentParticleDefinition(track.GetDefinition()); + } + + if(num > 0) { + fParticleChange.SetNumberOfSecondaries(num); + G4double edep = fParticleChange.GetLocalEnergyDeposit(); + G4double time = track.GetGlobalTime(); + + for (G4int i=0; i<num; ++i) { + if (secParticles[i]) { + G4DynamicParticle* dp = secParticles[i]; + const G4ParticleDefinition* p = dp->GetParticleDefinition(); + G4double e = dp->GetKineticEnergy(); + G4bool good = true; + if(ApplyCuts()) { + if (p == G4Gamma::Gamma()) { + if (e < gammaCut) { good = false; } + } else if (p == G4Electron::Electron()) { + if (e < GetElectronEnergyCut()) { good = false; } + } + // added secondary if it is good + } + if (good) { + G4Track* t = new G4Track(dp, time, track.GetPosition()); + t->SetTouchableHandle(track.GetTouchableHandle()); + if (entangledgammagamma) { + // entangledgammagamma is only true when there are only two gammas + // (See code above where entangledgammagamma is calculated.) + if (i == 0) { // First gamma + clipBoard->SetTrackA(t); + } else if (i == 1) { // Second gamma + clipBoard->SetTrackB(t); + } + t->SetAuxiliaryTrackInformation + (G4PhysicsModelCatalog::GetModelID("model_GammaGammaEntanglement"),new G4EntanglementAuxInfo(clipBoard)); + } + if (biasManager) { + t->SetWeight(weight * biasManager->GetWeight(i)); + } else { + t->SetWeight(weight); + } + pParticleChange->AddSecondary(t); + + // define type of secondary + if(i < mainSecondaries) { t->SetCreatorModelID(secID); } + else if(i < num0) { + if(p == G4Gamma::Gamma()) { + t->SetCreatorModelID(fluoID); + } else { + t->SetCreatorModelID(augerID); + } + } else { + t->SetCreatorModelID(biasID); + } + /* + G4cout << "Secondary(post step) has weight " << t->GetWeight() + << ", Ekin= " << t->GetKineticEnergy()/MeV << " MeV " + << GetProcessName() << " fluoID= " << fluoID + << " augerID= " << augerID <<G4endl; + */ + } else { + delete dp; + edep += e; + } + } + } + fParticleChange.ProposeLocalEnergyDeposit(edep); + } + return &fParticleChange; + } }; #endif diff --git a/opengate/tests/src/test076_last_vertex_splittting.py b/opengate/tests/src/test076_last_vertex_splittting.py index fbd10e5db..dbf9d5fa9 100755 --- a/opengate/tests/src/test076_last_vertex_splittting.py +++ b/opengate/tests/src/test076_last_vertex_splittting.py @@ -111,10 +111,9 @@ def validation_test(arr_ref, arr_data, nb_split, tol=0.2, tol_weights=0.04): ui.check_volumes_overlap = False # ui.running_verbose_level = gate.logger.EVENT ui.number_of_threads = 1 - #123456 bug free - # 4665 seg fault - #46656 annihil - ui.random_seed = 641 + # 1236566 seg fault + #12745 double compt + ui.random_seed = 73 # units m = gate.g4_units.m @@ -147,7 +146,7 @@ def validation_test(arr_ref, arr_data, nb_split, tol=0.2, tol_weights=0.04): W_tubs.mother = world.name W_tubs.rmin = 0 - W_tubs.rmax = 0.5 *cm + W_tubs.rmax = 0.4*cm W_tubs.dz = 0.05 * m W_tubs.color = [0.8, 0.2, 0.1, 1] angle_x = 45 @@ -160,22 +159,19 @@ def validation_test(arr_ref, arr_data, nb_split, tol=0.2, tol_weights=0.04): W_tubs.rotation = rotation ####### Compton Splitting ACTOR ######### - nb_split = 4 + nb_split = 25 vertex_splitting_actor = sim.add_actor("LastVertexInteractionSplittingActor", "vertexSplittingW") vertex_splitting_actor.mother = W_tubs.name vertex_splitting_actor.splitting_factor = nb_split vertex_splitting_actor.russian_roulette_for_angle = False - vertex_splitting_actor.rotation_vector_director = False - vertex_splitting_actor.max_theta = 10 * deg - vertex_splitting_actor.vector_director = [0,0,-1] ##### PHASE SPACE plan ######" plan_tubs = sim.add_volume("Tubs", "phsp_tubs") plan_tubs.material = "G4_Galactic" plan_tubs.mother = world.name - plan_tubs.rmin = W_tubs.rmax + 1*nm + plan_tubs.rmin = W_tubs.rmax + 1*cm plan_tubs.rmax = plan_tubs.rmin + 1 * nm - plan_tubs.dz = 0.5 * m + plan_tubs.dz = 0.05 * m plan_tubs.color = [0.2, 1, 0.8, 1] plan_tubs.rotation = rotation @@ -189,7 +185,7 @@ def validation_test(arr_ref, arr_data, nb_split, tol=0.2, tol_weights=0.04): # source.direction.momentum = [0,0,-1] source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) source.energy.type = "mono" - source.energy.mono = 4 * MeV + source.energy.mono = 4* MeV ####### PHASE SPACE ACTOR ############## @@ -219,7 +215,7 @@ def validation_test(arr_ref, arr_data, nb_split, tol=0.2, tol_weights=0.04): sim.add_g4_command_before_init(s) sim.physics_manager.global_production_cuts.gamma = 1 * mm - sim.physics_manager.global_production_cuts.electron = 1 * um + sim.physics_manager.global_production_cuts.electron = 1 * km sim.physics_manager.global_production_cuts.positron = 1 * um output = sim.run() diff --git a/opengate/tests/src/test076_last_vertex_splittting_distrib.py b/opengate/tests/src/test076_last_vertex_splittting_distrib.py new file mode 100755 index 000000000..087ffeb5b --- /dev/null +++ b/opengate/tests/src/test076_last_vertex_splittting_distrib.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +import uproot +import numpy as np +from scipy.spatial.transform import Rotation +from opengate.tests import utility + + +def bool_validation_test(dico_parameters, tol): + keys = dico_parameters.keys() + liste_diff_max = [] + for key in keys: + liste_diff_max.append(np.max(dico_parameters[key])) + liste_diff_max = np.asarray(liste_diff_max) + max_diff = np.max(liste_diff_max) + print( + "Maximal error (mean or std dev) measured between the analog and the biased simulation:", + np.round(max_diff, 2), + "%", + ) + if max_diff <= 100 * tol: + return True + else: + return False + + +def validation_test(arr_ref, arr_data, nb_split, tol=0.2, tol_weights=0.04): + arr_ref = arr_ref[ + (arr_ref["TrackCreatorProcess"] == "compt") + | (arr_ref["TrackCreatorProcess"] == "none") + ] + arr_data = arr_data[ + (arr_data["TrackCreatorProcess"] != "phot") + & (arr_data["TrackCreatorProcess"] != "eBrem") + & (arr_data["TrackCreatorProcess"] != "eIoni") + ] + + EventID = arr_data["EventID"] + weights = arr_data["Weight"][EventID == EventID[0]] + val_weights = np.round(weights[0], 4) + bool_val_weights = 1 / nb_split == val_weights + print( + "Sum of electron and photon weights for the first event simulated:", + np.round(np.sum(weights), 2), + ) + print("Len of the weights vector for the first event:", len(weights)) + condition_weights = np.round(np.sum(weights), 4) > 2 * ( + 1 - tol_weights + ) and np.round(np.sum(weights), 4) < 2 * (1 + tol_weights) + condition_len = len(weights) > 2 * nb_split * (1 - tol_weights) and len( + weights + ) < 2 * nb_split * (1 + tol_weights) + bool_weights = condition_weights and condition_len + keys = ["KineticEnergy", "PreDirection_X", "PreDirection_Y", "PreDirection_Z"] + + arr_ref_phot = arr_ref[arr_ref["ParticleName"] == "gamma"] + arr_ref_elec = arr_ref[arr_ref["ParticleName"] == "e-"] + + arr_data_phot = arr_data[arr_data["ParticleName"] == "gamma"] + arr_data_elec = arr_data[arr_data["ParticleName"] == "e-"] + + keys_dico = ["ref", "data"] + dico_arr_phot = {} + dico_arr_elec = {} + + dico_arr_phot["ref"] = arr_ref_phot + dico_arr_phot["data"] = arr_data_phot + + dico_arr_elec["ref"] = arr_ref_elec + dico_arr_elec["data"] = arr_data_elec + dico_comp_data = {} + + for key in keys: + arr_data = [] + for key_dico in keys_dico: + mean_elec = np.mean(dico_arr_phot[key_dico][key]) + mean_phot = np.mean(dico_arr_elec[key_dico][key]) + std_elec = np.std(dico_arr_phot[key_dico][key]) + std_phot = np.std(dico_arr_elec[key_dico][key]) + arr_data += [mean_elec, mean_phot, std_elec, std_phot] + dico_comp_data[key] = 100 * np.abs( + np.array( + [ + (arr_data[0] - arr_data[4]) / arr_data[0], + (arr_data[1] - arr_data[5]) / arr_data[1], + (arr_data[2] - arr_data[6]) / arr_data[6], + (arr_data[3] - arr_data[7]) / arr_data[3], + ] + ) + ) + bool_test = bool_validation_test(dico_comp_data, tol) + bool_tot = bool_test and bool_weights and bool_val_weights + return bool_tot + + +if __name__ == "__main__": + paths = utility.get_default_test_paths( + __file__, "test076_last_vertex_splitting", output_folder="test076" + ) + for simu_ID in range(1,2) : + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + # ui.visu = True + ui.visu_type = "vrml" + ui.check_volumes_overlap = False + # ui.running_verbose_level = gate.logger.EVENT + ui.number_of_threads = 1 + # 1236566 seg fault + ui.random_seed = 1234567 + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + um = gate.g4_units.um + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + deg = gate.g4_units.deg + gcm3 = gate.g4_units.g / gate.g4_units.cm3 + + # adapt world size + world = sim.world + world.size = [0.25 * m, 0.25 * m, 1.5 * m] + world.material = "G4_Galactic" + + ####### GEOMETRY TO IRRADIATE ############# + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + + W_tubs = sim.add_volume("Tubs", "W_box") + W_tubs.material = "Tungsten" + W_tubs.mother = world.name + + W_tubs.rmin = 0 + W_tubs.rmax = 1* cm + W_tubs.dz = 0.5 * m + W_tubs.color = [0.8, 0.2, 0.1, 1] + angle_x = 0 + angle_y = 0 + angle_z = 0 + + rotation = Rotation.from_euler( + "xyz", [angle_y, angle_y, angle_z], degrees=True + ).as_matrix() + W_tubs.rotation = rotation + if simu_ID == 1 : + ####### Last vertex splitting ACTOR ######### + nb_split = 1 + vertex_splitting_actor = sim.add_actor("LastVertexInteractionSplittingActor", "vertexSplittingW") + vertex_splitting_actor.mother = W_tubs.name + vertex_splitting_actor.splitting_factor = nb_split + vertex_splitting_actor.russian_roulette_for_angle = False + + ##### PHASE SPACE plan ######" + plan_tubs = sim.add_volume("Tubs", "phsp_tubs") + plan_tubs.material = "G4_Galactic" + plan_tubs.mother = world.name + plan_tubs.rmin = W_tubs.rmax + 1*cm + plan_tubs.rmax = plan_tubs.rmin + 1 * nm + plan_tubs.dz = 0.05 * m + plan_tubs.color = [0.2, 1, 0.8, 1] + plan_tubs.rotation = rotation + + ####### Electron source ########### + source = sim.add_source("GenericSource", "source1") + source.particle = "gamma" + source.n = 1000000 + if simu_ID == 1 : + source.n = source.n/(nb_split) + source.position.type = "sphere" + source.position.radius = 1 * nm + source.direction.type = "momentum" + # source.direction.momentum = [0,0,-1] + source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) + source.energy.type = "mono" + source.position.translation = [0.4*cm,0.4*cm,0] + source.force_rotation=True + source.energy.mono = 4* MeV + + ####### PHASE SPACE ACTOR ############## + + phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + phsp_actor.mother = plan_tubs.name + phsp_actor.attributes = [ + "EventID", + "TrackID", + "Weight", + "ParticleName", + "KineticEnergy", + "PreDirection", + "TrackCreatorProcess", + ] + if simu_ID == 0: + phsp_actor.output = paths.output / "test076_output_data_ref.root" + else : + phsp_actor.output = paths.output / "test076_output_data_split.root" + + ##### MODIFIED PHYSICS LIST ############### + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option2" + #### Extremely important, it seems that GEANT4, for almost all physics lists, encompass all the photon processes in GammaGeneralProc + #### Therefore if we provide the name of the real process (here compt) without deactivating GammaGeneralProcess, it will not find the + #### process to bias and the biasing will fail + s = f"/process/em/UseGeneralProcess false" + sim.add_g4_command_before_init(s) + + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1 * m + sim.physics_manager.global_production_cuts.positron = 1 * um + + output = sim.run() + + # + # print results + stats = sim.output.get_actor("Stats") + h = sim.output.get_actor("PhaseSpace") + print(stats) + + From c71c7d649f244caeedecde9b65cae27212cd1bac Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 25 Jun 2024 13:28:58 +0200 Subject: [PATCH 25/82] Correction of Russian roulette bug --- .../opengate_lib/GateOptnVGenericSplitting.cpp | 2 +- .../GateOptrComptPseudoTransportationActor.cpp | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp index 349a393ff..d69496bd3 100644 --- a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp +++ b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp @@ -94,7 +94,7 @@ if (theta > fMaxTheta){ weightToApply = split; } else{ - G4double weightToApply = 0; + weightToApply = 0; } } return weightToApply; diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp index 141d73b77..96d17099f 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp @@ -52,6 +52,7 @@ #include "GateOptrComptPseudoTransportationActor.h" #include "G4UImanager.hh" #include "G4eplusAnnihilation.hh" +#include "GateOptnVGenericSplitting.h" //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... @@ -163,12 +164,14 @@ void GateOptrComptPseudoTransportationActor::StartRun() { void GateOptrComptPseudoTransportationActor::SteppingAction(G4Step *step) { + + if ((isSplitted == true) && (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary)) { G4String LogicalVolumeName = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); if (!(std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeName ) !=fNameOfBiasedLogicalVolume.end()) && (LogicalVolumeName != fMotherVolumeName)) { - step->GetTrack()->SetTrackStatus(G4TrackStatus::fStopAndKill); + step->GetTrack()->SetTrackStatus(fStopAndKill); isSplitted = false; } } @@ -262,7 +265,7 @@ GateOptrComptPseudoTransportationActor::ProposeFinalStateBiasingOperation( if (!(std::find(fCreationProcessNameList.begin(), fCreationProcessNameList.end(),CreationProcessName) != fCreationProcessNameList.end())){ - if ((callingProcess->GetWrappedProcess()->GetProcessName() == "compt") || (callingProcess->GetWrappedProcess()->GetProcessName() == "Rayl")){ + if (callingProcess->GetWrappedProcess()->GetProcessName() == "compt"){ isSplitted = true; return fScatteredGammaSplittingOperation; } From ca0de09edaf4694b9841069520b9b73b2385a143 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 25 Jun 2024 15:32:34 +0200 Subject: [PATCH 26/82] Add russian roulette for annihilation photons --- .../GateOptnVGenericSplitting.cpp | 2 +- .../opengate_lib/GateOptnVGenericSplitting.h | 2 +- ...GateOptrComptPseudoTransportationActor.cpp | 51 ++++++++++++------- .../GateOptrComptPseudoTransportationActor.h | 3 +- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp index d69496bd3..ba894cf04 100644 --- a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp +++ b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp @@ -88,7 +88,7 @@ G4double GateOptnVGenericSplitting::RussianRouletteForAngleSurvival(G4ThreeVecto G4double cosTheta =vectorDirector * dir; G4double theta = std::acos(cosTheta); G4double weightToApply = 1; -if (theta > fMaxTheta){ +if (theta > maxTheta){ G4double probability = G4UniformRand(); if (probability <= 1 / split) { weightToApply = split; diff --git a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h index ed75c9edd..0ed33e052 100644 --- a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h +++ b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h @@ -74,7 +74,7 @@ class GateOptnVGenericSplitting : public G4VBiasingOperation { void TrackInitializationChargedParticle(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split); void TrackInitializationGamma(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split); -G4double RussianRouletteForAngleSurvival(G4ThreeVector dir,G4ThreeVector vectorDirector,G4double maxTheta,G4double split); +static G4double RussianRouletteForAngleSurvival(G4ThreeVector dir,G4ThreeVector vectorDirector,G4double maxTheta,G4double split); public: diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp index 96d17099f..2de1c0e16 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp @@ -122,7 +122,6 @@ void GateOptrComptPseudoTransportationActor::StartSimulationAction() { // to the biasing operator. To do that, I use the function // AttachAllLogicalDaughtersVolumes. AttachAllLogicalDaughtersVolumes(biasingVolume); - for (int i = 0; i < fNameOfBiasedLogicalVolume.size(); i++) fScatteredGammaSplittingOperation->SetSplittingFactor(fSplittingFactor); fScatteredGammaSplittingOperation->SetMaxTheta(fMaxTheta); fScatteredGammaSplittingOperation->SetRussianRouletteForAngle(fRussianRouletteForAngle); @@ -133,9 +132,6 @@ void GateOptrComptPseudoTransportationActor::StartSimulationAction() { fPairProdSplittingOperation->SetSplittingFactor(fSplittingFactor); - - - fFreeFlightOperation->SetRussianRouletteForWeights(fRussianRouletteForWeights); } @@ -163,19 +159,39 @@ void GateOptrComptPseudoTransportationActor::StartRun() { void GateOptrComptPseudoTransportationActor::SteppingAction(G4Step *step) { - - - + G4String creationProcessName = "None"; + if (step->GetTrack()->GetCreatorProcess() != 0){ + creationProcessName = step->GetTrack()->GetCreatorProcess()->GetProcessName(); + } - if ((isSplitted == true) && (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary)) { - G4String LogicalVolumeName = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - if (!(std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeName ) !=fNameOfBiasedLogicalVolume.end()) - && (LogicalVolumeName != fMotherVolumeName)) { - step->GetTrack()->SetTrackStatus(fStopAndKill); - isSplitted = false; - } +if ((fIsFirstStep) && (fRussianRouletteForAngle)){ + G4String LogicalVolumeNameOfCreation = step->GetTrack()->GetLogicalVolumeAtVertex()->GetName(); + if (std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeNameOfCreation ) !=fNameOfBiasedLogicalVolume.end()){ + if (creationProcessName == "biasWrapper(annihil)"){ + auto dir = step->GetPreStepPoint()->GetMomentumDirection(); + G4double w = GateOptnVGenericSplitting::RussianRouletteForAngleSurvival(dir,fVectorDirector,fMaxTheta,fSplittingFactor); + if (w == 0) + { + step->GetTrack()->SetTrackStatus(fStopAndKill); + } + else { + step->GetTrack()->SetWeight(step->GetTrack()->GetWeight() * w); + } + } } } + +if ((isSplitted == true) && (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary)) { + G4String LogicalVolumeName = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + if (!(std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeName ) !=fNameOfBiasedLogicalVolume.end()) + && (LogicalVolumeName != fMotherVolumeName)) { + step->GetTrack()->SetTrackStatus(fStopAndKill); + isSplitted = false; + } +} + +fIsFirstStep = false; +} void GateOptrComptPseudoTransportationActor::BeginOfEventAction( const G4Event *event) { @@ -189,12 +205,13 @@ void GateOptrComptPseudoTransportationActor::BeginOfEventAction( void GateOptrComptPseudoTransportationActor::StartTracking( const G4Track *track) { - G4String CreationProcessName = "None"; - + G4String creationProcessName = "None"; + fIsFirstStep = true; if (track->GetCreatorProcess() != 0){ - CreationProcessName = track->GetCreatorProcess()->GetProcessName(); + creationProcessName = track->GetCreatorProcess()->GetProcessName(); } + if (track->GetParticleDefinition()->GetParticleName() == "gamma"){ G4String LogicalVolumeNameOfCreation = track->GetLogicalVolumeAtVertex()->GetName(); if (std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeNameOfCreation ) !=fNameOfBiasedLogicalVolume.end()){ diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h index d60e88ad9..77064f94f 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h @@ -86,11 +86,12 @@ class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, G4int fEventID; G4double fEventIDKineticEnergy; G4bool ftestbool= false; + G4bool fIsFirstStep = false; const G4VProcess* fAnnihilation =nullptr; std::vector<G4String> fNameOfBiasedLogicalVolume = {}; std::vector<G4int> v_EventID = {}; - std::vector<G4String> fCreationProcessNameList = {"biasWrapper(compt)","biasWrapper(Rayl)", "biasWrapper(eBrem)","biasWrapper(annihil)"}; + std::vector<G4String> fCreationProcessNameList = {"biasWrapper(compt)", "biasWrapper(eBrem)","biasWrapper(annihil)"}; // Unused but mandatory virtual void StartSimulationAction(); From 101d021978a056031d4d8e4cbecdb38ab0170b49 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 25 Jun 2024 15:33:37 +0200 Subject: [PATCH 27/82] supression of rayleigh biasing --- opengate/actors/miscactors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index fc2a53541..56fb51443 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -463,7 +463,7 @@ def set_default_user_info(user_info): user_info.attach_to_logical_holder = True user_info.splitting_factor = 1 user_info.relative_min_weight_of_particle = np.inf - user_info.gamma_processes = ["compt", "phot", "conv", "Rayl"] + user_info.gamma_processes = ["compt", "phot", "conv"] user_info.electron_processes = ["eBrem"] user_info.positron_processes = ["annihil","eBrem"] user_info.russian_roulette_for_angle = False From e6c0b5b52065d2965d4f9688d604d0bddd25ec10 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Thu, 27 Jun 2024 13:39:33 +0200 Subject: [PATCH 28/82] Modification of Rayleigh behaviour --- .../opengate_lib/GateOptnForceFreeFlight.cpp | 8 +++++++- opengate/actors/miscactors.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp index 53d6ba3a9..990a5629f 100644 --- a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp +++ b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp @@ -118,6 +118,12 @@ void GateOptnForceFreeFlight ::AlongMoveBy( G4double weightChange) { - fWeightChange[callingProcess->GetWrappedProcess()->GetProcessName()] = + G4String processName = callingProcess->GetWrappedProcess()->GetProcessName(); + if (processName != "Rayl"){ + fWeightChange[processName] = weightChange; + } + else { + fWeightChange[processName] = 1; + } } diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index df9d32c9d..280ff0469 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -463,7 +463,7 @@ def set_default_user_info(user_info): user_info.attach_to_logical_holder = True user_info.splitting_factor = 1 user_info.relative_min_weight_of_particle = np.inf - user_info.gamma_processes = ["compt", "phot", "conv"] + user_info.gamma_processes = ["compt", "phot", "conv","Rayl"] user_info.electron_processes = ["eBrem"] user_info.positron_processes = ["annihil", "eBrem"] user_info.russian_roulette_for_angle = False From 5503679b705d983d0ce59331aec3af8fa5667c5c Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 2 Jul 2024 16:07:46 +0200 Subject: [PATCH 29/82] Correction of generation bug implying brem and pair production --- ...ateLastVertexInteractionSplittingActor.cpp | 125 ++++++++++------ .../GateLastVertexInteractionSplittingActor.h | 2 +- .../GateLastVertexSplittingPostStepDoIt.h | 134 +----------------- 3 files changed, 88 insertions(+), 173 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index 2f6d125e9..11571a011 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -157,6 +157,8 @@ void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step *Curre { // Loop on process and add the secondary tracks to the current step secondary vector. + //std::cout<<track->GetKineticEnergy()<<" "<<track->GetDynamicParticle()->GetKineticEnergy()<<" "<<track->GetMomentumDirection()<<" "<<track->GetDynamicParticle()->GetMomentumDirection()<<std::endl; + G4String particleName = track->GetParticleDefinition()->GetParticleName(); G4TrackVector *trackVector = CurrentStep->GetfSecondary(); G4double gammaWeight = 0; @@ -169,19 +171,24 @@ void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step *Curre else { GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *) process ; - if (track->GetTrackStatus() == 0) + if (track->GetTrackStatus() == 0){ + //if (process->GetProcessName() == "annihil") + //std::cout<<"lol"<<std::endl; processFinalState = emProcess->PostStepDoIt(*track, *step); + } if (track->GetTrackStatus() == 1){ GateplusannihilAtRestDoIt* eplusAnnihilProcess = (GateplusannihilAtRestDoIt*) process; - eplusAnnihilProcess ->GateplusannihilAtRestDoIt::AtRestDoIt(*track, *step); + + processFinalState = eplusAnnihilProcess ->GateplusannihilAtRestDoIt::AtRestDoIt(*track, *step); } } + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + if (NbOfSecondaries > 0) { gammaWeight = fWeightOfEnteringParticle/fSplittingFactor; G4Track *newTrack = processFinalState->GetSecondary(0); - std::cout<<newTrack->GetKineticEnergy()<<std::endl; if ((fRussianRouletteForAngle == true) && (particleName == "gamma")) { const G4ThreeVector momentum = newTrack->GetMomentumDirection(); @@ -199,6 +206,10 @@ void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step *Curre trackVector->push_back(newTrack); } } + else { + processFinalState->Clear(); + SecondariesSplitting(CurrentStep,track, step, process); + } processFinalState->Clear(); } @@ -252,6 +263,7 @@ void GateLastVertexInteractionSplittingActor::RememberLastProcessInformation(G4S G4String processName = "None"; G4int trackID = step->GetTrack()->GetTrackID(); G4int parentID = step->GetTrack()->GetParentID(); + //<<processName<<" "<<step->GetTrack()->GetParticleDefinition()->GetParticleName()<<" "<<step->GetTrack()->GetTrackStatus()<<std::endl; if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) { processName = step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); @@ -267,18 +279,15 @@ void GateLastVertexInteractionSplittingActor::RememberLastProcessInformation(G4S G4Step *stepInformation = new G4Step(*(step)); G4StepPoint *stepPoint = nullptr; - if ((processName == "annihil") && (step->GetTrack()->GetKineticEnergy() == 0)) - { - stepPoint = step->GetPostStepPoint(); - } - else - { - stepPoint = step->GetPreStepPoint(); - } + stepPoint = step->GetPreStepPoint(); + trackInformation->SetKineticEnergy(stepPoint->GetKineticEnergy()); + if ((processName == "eBrem") || ((processName == "annihil")&& step->GetTrack()->GetTrackStatus() == 1)){ + trackInformation->SetKineticEnergy(stepPoint->GetKineticEnergy() - step->GetTotalEnergyDeposit()); + } trackInformation->SetMomentumDirection(stepPoint->GetMomentumDirection()); - if ((processName =="annihil") && (step->GetTrack()->GetKineticEnergy() == 0)) + if ((processName =="annihil") && ((step->GetTrack()->GetTrackStatus() == 1))) trackInformation->SetTrackStatus(fStopButAlive); else{ trackInformation->SetTrackStatus(fAlive); @@ -310,15 +319,15 @@ void GateLastVertexInteractionSplittingActor::RememberLastProcessInformation(G4S if (auto it = std::find(fRememberedProcesses[parentID].begin(), fRememberedProcesses[parentID].end(), creatorProcessName); it != fRememberedProcesses[parentID].end()) { auto idx = it - fRememberedProcesses[parentID].begin(); - fRememberedTracks[trackID] = {fRememberedTracks[parentID][idx]}; + fRememberedTracks[trackID] = {new G4Track(*fRememberedTracks[parentID][idx])}; fRememberedProcesses[trackID] = {fRememberedProcesses[parentID][idx]}; - fRememberedSteps[trackID] = {fRememberedSteps[parentID][idx]}; + fRememberedSteps[trackID] = {new G4Step(*fRememberedSteps[parentID][idx])}; } else { - fRememberedTracks[trackID] = {fRememberedTracks[parentID][0]}; + fRememberedTracks[trackID] = {new G4Track(*fRememberedTracks[parentID][0])}; fRememberedProcesses[trackID] = {fRememberedProcesses[parentID][0]}; - fRememberedSteps[trackID] = {fRememberedSteps[parentID][0]}; + fRememberedSteps[trackID] = {new G4Step(*fRememberedSteps[parentID][0])}; } } } @@ -348,7 +357,7 @@ void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G ComptonSplitting(CurrentStep, track, step, processToSplit); } - else if (processName != "eBrem") + else { SecondariesSplitting(CurrentStep, track, step, processToSplit); } @@ -517,10 +526,9 @@ void GateLastVertexInteractionSplittingActor::BeginOfEventAction(const G4Event * fParentID = -1; fEventID = event->GetEventID(); - //if (fEventID%30000 == 0) - std::cout <<fEventID<<std::endl; fEventIDOfSplittedTrack = -1; fTrackIDOfSplittedTrack = -1; + fNotSplitted == true; } void GateLastVertexInteractionSplittingActor::PreUserTrackingAction(const G4Track *track) @@ -530,22 +538,27 @@ void GateLastVertexInteractionSplittingActor::PreUserTrackingAction(const G4Trac void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { + G4String particleName = step->GetTrack()->GetParticleDefinition()->GetParticleName(); G4String creatorProcessName = "None"; + + if (step->GetTrack()->GetCreatorProcess() != 0) creatorProcessName = step->GetTrack()->GetCreatorProcess()->GetProcessName(); + + //std::cout<<particleName<<" "<<fEventID<<" "<<step->GetTotalEnergyDeposit()<<" "<<step->GetPreStepPoint()->GetKineticEnergy()<<" "<<step->GetPreStepPoint()->GetMomentumDirection()<<step->GetTrack()->GetTrackID()<<" "<<step->GetTrack()->GetWeight()<<" "<<step->GetTrack()->GetParentID()<<" "<<fTrackIDOfSplittedTrack<<std::endl;; + + if (fIsFirstStep) { - //HandleTrackIDIfPostponedAnnihilation(step); ResetProcessesForEnteringParticles(step); } G4int trackID = step->GetTrack()->GetTrackID(); - if (fIsSplitted == true){ - + if ((step->GetTrack()->GetWeight() < fWeightOfEnteringParticle) && (fNotSplitted ==false)){ G4String logicalVolumeNamePostStep = "None"; if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) @@ -556,54 +569,59 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) processName = step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); - if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), processName) != fListOfProcesses.end()){ + + if (particleName != "e+"){ + if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), processName) != fListOfProcesses.end()){ + step->GetTrack()->SetTrackStatus(fStopAndKill); + step->GetfSecondary()->clear(); + } + } + + + if ((particleName == "e+") && ((step->GetTrack()->GetTrackStatus() == 1)||(step->GetTrack()->GetTrackStatus() == 2))){ step->GetTrack()->SetTrackStatus(fStopAndKill); + step->GetfSecondary()->clear(); } - if (step->GetTrack()->GetTrackStatus() == 2){ + if ((step->GetTrack()->GetTrackStatus() == 2) || (step->GetTrack()->GetTrackStatus() == 3)){ + + /* delete fTrackToSplit; delete fStepToSplit; fTrackToSplit = new G4Track(*fRememberedTracks[fTrackIDOfSplittedTrack].back()); fStepToSplit = new G4Step(*fRememberedSteps[fTrackIDOfSplittedTrack].back()); - std::cout <<"IN"<<" "<<fIsSplitted<<" "<<step->GetPreStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetTrack()->GetTrackStatus()<<" "<<logicalVolumeNamePostStep<<std::endl; + */ + CreateNewParticleAtTheLastVertex(step, fTrackToSplit, fStepToSplit, fProcessToSplit); } - if (fEventID > 27666){ - std::cout << fIsSplitted<<" "<<step->GetPreStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetTrack()->GetTrackStatus()<<" "<<logicalVolumeNamePostStep<<std::endl; - - } - - //if (fEventID == 27135) - //std::cout << step->GetPreStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetTrack()->GetTrackStatus()<<std::endl; + if (((step->GetTrack()->GetTrackStatus() != 2) && (step->GetTrack()->GetTrackStatus() != 3)) && (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), logicalVolumeNamePostStep) != fListOfVolumeAncestor.end())) { if (fSplitCounter < fWeightOfEnteringParticle) { + /* delete fTrackToSplit; delete fStepToSplit; fTrackToSplit = new G4Track(*fRememberedTracks[fTrackIDOfSplittedTrack].back()); fStepToSplit = new G4Step(*fRememberedSteps[fTrackIDOfSplittedTrack].back()); + */ CreateNewParticleAtTheLastVertex(step, fTrackToSplit, fStepToSplit, fProcessToSplit); //std::cout<<fTrackToSplit<<" "<<fStepToSplit<<std::endl; G4TrackVector *trackVector = step->GetfSecondary(); G4Track* theTrack= trackVector->back(); fSplitCounter += theTrack->GetWeight(); - if (fEventID > 27666){ - std::cout << fSplitCounter<<" "<<fIsSplitted<<" "<<step->GetPreStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetTrack()->GetTrackStatus()<<std::endl; - } //std::cout<<theTrack->GetWeight()<<std::endl; if (fSplitCounter >= fWeightOfEnteringParticle) { delete fTrackToSplit; delete fStepToSplit; fTrackToSplit = nullptr; fStepToSplit = nullptr; - //std::cout<<"lol"<<std::endl; fSplitCounter = 0; - fIsSplitted = false; + fNotSplitted = true; fProcessToSplit = "None"; theTrack->SetTrackStatus(fStopAndKill); } @@ -612,11 +630,13 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) } + - - if (fIsSplitted == false) + if (step->GetTrack()->GetWeight() == fWeightOfEnteringParticle) { + //std::cout<<particleName<<" "<<step->GetTrack()->GetTrackStatus()<<std::endl; RememberLastProcessInformation(step); + //std::cout<<particleName<<" "<<step->GetTrack()->GetTrackStatus()<<std::endl; G4String process = "None"; if (auto search = fRememberedProcesses.find(trackID); search != fRememberedProcesses.end()) { @@ -631,6 +651,8 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) } */ + + if ((particleName == "e-") && (step->GetTrack()->GetWeight() == fWeightOfEnteringParticle) && (creatorProcessName == "conv") && (fTrackIDOfSplittedTrack == step->GetTrack()->GetParentID()) && (fEventID == fEventIDOfSplittedTrack)){ step->GetTrack()->SetTrackStatus(fStopAndKill); } @@ -659,6 +681,7 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) } } */ + G4String logicalVolumeNamePostStep = "None"; if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); @@ -667,14 +690,17 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { if (auto search = fRememberedProcesses.find(trackID); search != fRememberedProcesses.end()){ + fProcessToSplit = fRememberedProcesses[trackID].back(); fTrackToSplit = new G4Track(*fRememberedTracks[trackID].back()); fStepToSplit = new G4Step(*fRememberedSteps[trackID].back()); + } if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), fProcessToSplit) != fListOfProcesses.end()) { + fTrackIDOfSplittedTrack = trackID; fEventIDOfSplittedTrack = fEventID; fTrackIDOfInitialSplittedTrack = fTrackIDOfInitialTrack; @@ -694,6 +720,8 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) } } + + // Handle of pecularities (2): // If the process to split is the annihilation, the second photon, postponed or not, have to be killed @@ -713,20 +741,30 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) // all the brems photons will be killed before their tracking, and the conv processes will then be replayed if ((particleName == "e+") &&(fProcessToSplit != "None")) { + G4int parentID = step->GetTrack()->GetParentID(); fProcessToSplit = fRememberedProcesses[parentID].back(); delete fTrackToSplit; delete fStepToSplit; fTrackToSplit = new G4Track(*fRememberedTracks[parentID].back()); fStepToSplit = new G4Step(*fRememberedSteps[parentID].back()); - step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); fTrackIDOfInitialTrack = parentID; + + auto* vecSecondaries = step->GetfSecondary(); + vecSecondaries->clear(); + } + if (!((fProcessToSplit == "eBrem") && (particleName == "e-"))) + { + CreateNewParticleAtTheLastVertex(step, fTrackToSplit, fStepToSplit, fProcessToSplit); + fNotSplitted = false; + } + step->GetTrack()->SetTrackStatus(fStopAndKill); + - fIsSplitted = true; - CreateNewParticleAtTheLastVertex(step, fTrackToSplit, fStepToSplit, fProcessToSplit); - step->GetTrack()->SetTrackStatus(fStopAndKill); + + } } } @@ -742,6 +780,7 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) */ fIsFirstStep = false; + } diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h index e8877ae4b..3a2e261b5 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h @@ -60,7 +60,7 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { G4bool fSuspendForAnnihil = false; G4double fWeightOfEnteringParticle = 0; G4double fSplitCounter = 0; - G4bool fIsSplitted = false; + G4bool fNotSplitted = true; G4Track* fTrackToSplit = nullptr; G4Step* fStepToSplit = nullptr; diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h index 22b75a0f8..a8a6d9701 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h @@ -77,10 +77,8 @@ GateGammaEmPostStepDoIt(); virtual G4VParticleChange * PostStepDoIt(const G4Track & track, const G4Step & step) override { const G4MaterialCutsCouple* couple = step.GetPreStepPoint()->GetMaterialCutsCouple(); - - std::cout<<track.GetDefinition()->GetParticleName()<<std::endl; + currentCouple = couple; G4VParticleChange* particleChange = G4VEmProcess::PostStepDoIt(track,step); - std::cout<<track.GetDefinition()->GetParticleName()<<std::endl; return particleChange; } @@ -97,132 +95,10 @@ virtual G4VParticleChange* AtRestDoIt(const G4Track& track, const G4Step& step) override // Performs the e+ e- annihilation when both particles are assumed at rest. { - fParticleChange.InitializeForPostStep(track); - DefineMaterial(step.GetPreStepPoint()->GetMaterialCutsCouple()); - G4int idx = (G4int)CurrentMaterialCutsCoupleIndex(); - G4double ene(0.0); - G4VEmModel* model = SelectModel(ene, idx); - - // define new weight for primary and secondaries - G4double weight = fParticleChange.GetParentWeight(); - - // sample secondaries - secParticles.clear(); - G4double gammaCut = GetGammaEnergyCut(); - model->SampleSecondaries(&secParticles, MaterialCutsCouple(), - track.GetDynamicParticle(), gammaCut); - - G4int num0 = (G4int)secParticles.size(); - // splitting or Russian roulette - if(biasManager) { - if(biasManager->SecondaryBiasingRegion(idx)) { - G4double eloss = 0.0; - weight *= biasManager->ApplySecondaryBiasing( - secParticles, track, model, &fParticleChange, eloss, - idx, gammaCut, step.GetPostStepPoint()->GetSafety()); - if(eloss > 0.0) { - eloss += fParticleChange.GetLocalEnergyDeposit(); - fParticleChange.ProposeLocalEnergyDeposit(eloss); - } - } - } - - // save secondaries - G4int num = (G4int)secParticles.size(); - - // Check that entanglement is switched on... (the following flag is - // set by /process/em/QuantumEntanglement). - G4bool entangled = G4EmParameters::Instance()->QuantumEntanglement(); - // ...and that we have two gammas with both gammas' energies above - // gammaCut (entanglement is only programmed for e+ e- -> gamma gamma). - G4bool entangledgammagamma = false; - if (entangled) { - if (num == 2) { - entangledgammagamma = true; - for (const auto* p: secParticles) { - if (p->GetDefinition() != G4Gamma::Gamma() || - p->GetKineticEnergy() < gammaCut) { - entangledgammagamma = false; - } - } - } - } - - // Prepare a shared pointer for psossible use below. If it is used, the - // shared pointer is copied into the tracks through G4EntanglementAuxInfo. - // This ensures the clip board lasts until both tracks are destroyed. - std::shared_ptr<G4eplusAnnihilationEntanglementClipBoard> clipBoard; - if (entangledgammagamma) { - clipBoard = std::make_shared<G4eplusAnnihilationEntanglementClipBoard>(); - clipBoard->SetParentParticleDefinition(track.GetDefinition()); - } - - if(num > 0) { - fParticleChange.SetNumberOfSecondaries(num); - G4double edep = fParticleChange.GetLocalEnergyDeposit(); - G4double time = track.GetGlobalTime(); - - for (G4int i=0; i<num; ++i) { - if (secParticles[i]) { - G4DynamicParticle* dp = secParticles[i]; - const G4ParticleDefinition* p = dp->GetParticleDefinition(); - G4double e = dp->GetKineticEnergy(); - G4bool good = true; - if(ApplyCuts()) { - if (p == G4Gamma::Gamma()) { - if (e < gammaCut) { good = false; } - } else if (p == G4Electron::Electron()) { - if (e < GetElectronEnergyCut()) { good = false; } - } - // added secondary if it is good - } - if (good) { - G4Track* t = new G4Track(dp, time, track.GetPosition()); - t->SetTouchableHandle(track.GetTouchableHandle()); - if (entangledgammagamma) { - // entangledgammagamma is only true when there are only two gammas - // (See code above where entangledgammagamma is calculated.) - if (i == 0) { // First gamma - clipBoard->SetTrackA(t); - } else if (i == 1) { // Second gamma - clipBoard->SetTrackB(t); - } - t->SetAuxiliaryTrackInformation - (G4PhysicsModelCatalog::GetModelID("model_GammaGammaEntanglement"),new G4EntanglementAuxInfo(clipBoard)); - } - if (biasManager) { - t->SetWeight(weight * biasManager->GetWeight(i)); - } else { - t->SetWeight(weight); - } - pParticleChange->AddSecondary(t); - - // define type of secondary - if(i < mainSecondaries) { t->SetCreatorModelID(secID); } - else if(i < num0) { - if(p == G4Gamma::Gamma()) { - t->SetCreatorModelID(fluoID); - } else { - t->SetCreatorModelID(augerID); - } - } else { - t->SetCreatorModelID(biasID); - } - /* - G4cout << "Secondary(post step) has weight " << t->GetWeight() - << ", Ekin= " << t->GetKineticEnergy()/MeV << " MeV " - << GetProcessName() << " fluoID= " << fluoID - << " augerID= " << augerID <<G4endl; - */ - } else { - delete dp; - edep += e; - } - } - } - fParticleChange.ProposeLocalEnergyDeposit(edep); - } - return &fParticleChange; + G4Track copyTrack = G4Track(track); + copyTrack.SetStep(&step); + G4VParticleChange* particleChange = G4eplusAnnihilation::AtRestDoIt(copyTrack,step); + return particleChange; } }; #endif From 1f51419b5b933bfe328035ca1bc6d8d47335f0d5 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Wed, 25 Sep 2024 19:11:21 +0200 Subject: [PATCH 30/82] Refactoring of last vertex splitting actor, combining a data tree structure and a source creation to regenerate event --- core/opengate_core/opengate_core.cpp | 8 +- .../opengate_lib/GateGenericSource.cpp | 7 +- ...ateLastVertexInteractionSplittingActor.cpp | 1031 ++--- .../GateLastVertexInteractionSplittingActor.h | 59 +- ...teLastVertexInteractionSplittingActorOld.h | 110 + ...LastVertexInteractionSplittingOldActor.cpp | 848 ++++ .../opengate_lib/GateLastVertexSource.cpp | 112 + .../opengate_lib/GateLastVertexSource.h | 85 + .../GateLastVertexSplittingDataContainer.h | 343 ++ .../GateLastVertexSplittingPostStepDoIt.h | 1 - .../GateLastVertexSplittingPostStepDoItOld.h | 110 + .../opengate_lib/GateSourceManager.cpp | 4 + .../opengate_lib/GateSourceManager.h | 16 + ...LastVertexInteractionSplittingActorOld.cpp | 21 + .../opengate_lib/pyGateLastVertexSource.cpp | 22 + core/opengate_core/opengate_lib/tree.hh | 3412 +++++++++++++++++ core/opengate_core/opengate_lib/tree_util.hh | 92 + opengate/actors/actorbuilders.py | 1 + opengate/actors/miscactors.py | 35 + opengate/sources/builders.py | 3 +- opengate/sources/generic.py | 18 + 21 files changed, 5692 insertions(+), 646 deletions(-) create mode 100644 core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActorOld.h create mode 100644 core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingOldActor.cpp create mode 100644 core/opengate_core/opengate_lib/GateLastVertexSource.cpp create mode 100644 core/opengate_core/opengate_lib/GateLastVertexSource.h create mode 100644 core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h create mode 100644 core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoItOld.h create mode 100644 core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActorOld.cpp create mode 100644 core/opengate_core/opengate_lib/pyGateLastVertexSource.cpp create mode 100644 core/opengate_core/opengate_lib/tree.hh create mode 100644 core/opengate_core/opengate_lib/tree_util.hh diff --git a/core/opengate_core/opengate_core.cpp b/core/opengate_core/opengate_core.cpp index c32e48188..3f376726d 100644 --- a/core/opengate_core/opengate_core.cpp +++ b/core/opengate_core/opengate_core.cpp @@ -342,7 +342,9 @@ void init_GatePhaseSpaceActor(py::module &); void init_GateOptrComptSplittingActor(py::module &m); -void init_GateLastVertexInteractionSplittingActor(py::module &m); +void init_GateLastVertexInteractionSplittingActor(py::module &m); + +void init_GateLastVertexInteractionSplittingActorOld(py::module &m); void init_GateOptrComptPseudoTransportationActor(py::module &m); @@ -374,6 +376,8 @@ void init_GateVDigiAttribute(py::module &m); void init_GateVSource(py::module &); +void init_GateLastVertexSource(py::module &); + void init_GateExceptionHandler(py::module &); void init_GateNTuple(py::module &); @@ -545,6 +549,7 @@ PYBIND11_MODULE(opengate_core, m) { init_GateImageNestedParameterisation(m); init_GateRepeatParameterisation(m); init_GateVSource(m); + init_GateLastVertexSource(m); init_GateSourceManager(m); init_GateGenericSource(m); init_GateTreatmentPlanPBSource(m); @@ -568,6 +573,7 @@ PYBIND11_MODULE(opengate_core, m) { init_GateOptrComptPseudoTransportationActor(m); init_GateOptrComptSplittingActor(m); init_GateLastVertexInteractionSplittingActor(m); + init_GateLastVertexInteractionSplittingActorOld(m); init_GateHitsCollectionActor(m); init_GateMotionVolumeActor(m); init_GateHitsAdderActor(m); diff --git a/core/opengate_core/opengate_lib/GateGenericSource.cpp b/core/opengate_core/opengate_lib/GateGenericSource.cpp index 88748a25d..97ff9d0ec 100644 --- a/core/opengate_core/opengate_lib/GateGenericSource.cpp +++ b/core/opengate_core/opengate_lib/GateGenericSource.cpp @@ -143,13 +143,14 @@ double GateGenericSource::PrepareNextTime(double current_simulation_time) { if (fMaxN <= 0) { if (fEffectiveEventTime < fStartTime) return fStartTime; - if (fEffectiveEventTime >= fEndTime) + if (fEffectiveEventTime >= fEndTime){ return -1; - + } // get next time according to current fActivity double next_time = CalcNextTime(fEffectiveEventTime); - if (next_time >= fEndTime) + if (next_time >= fEndTime){ return -1; + } return next_time; } diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index 09061f928..5e6d10d9c 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -46,6 +46,10 @@ #include "GateLastVertexInteractionSplittingActor.h" #include "GateLastVertexSplittingPostStepDoIt.h" #include "GateOptnComptSplitting.h" +#include "GateLastVertexSource.h" +#include "G4RunManager.hh" +#include <cmath> + //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... @@ -55,8 +59,7 @@ GateLastVertexInteractionSplittingActor:: fMotherVolumeName = DictGetStr(user_info, "mother"); fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); - fRussianRouletteForAngle = - DictGetBool(user_info, "russian_roulette_for_angle"); + fRussianRouletteForAngle = DictGetBool(user_info, "russian_roulette_for_angle"); fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); fMaxTheta = DictGetDouble(user_info, "max_theta"); fActions.insert("StartSimulationAction"); @@ -65,30 +68,63 @@ GateLastVertexInteractionSplittingActor:: fActions.insert("BeginOfRunAction"); fActions.insert("PreUserTrackingAction"); fActions.insert("PostUserTrackingAction"); + fActions.insert("EndOfEventAction"); + } //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -G4double -GateLastVertexInteractionSplittingActor::RussianRouletteForAngleSurvival( - G4ThreeVector dir, G4ThreeVector vectorDirector, G4double maxTheta, - G4double split) { - G4double cosTheta = vectorDirector * dir; - G4double theta = std::acos(cosTheta); - G4double weightToApply = 1; - if (theta > fMaxTheta) { - G4double probability = G4UniformRand(); - if (probability <= 1 / split) { - weightToApply = split; - } else { - weightToApply = 0; - } +void GateLastVertexInteractionSplittingActor::print_tree(const tree<LastVertexDataContainer>& tr, tree<LastVertexDataContainer>::pre_order_iterator it, tree<LastVertexDataContainer>::pre_order_iterator end) + { + if(!tr.is_valid(it)) return; + int rootdepth=tr.depth(it); + std::cout << "-----" << std::endl; + while(it!=end) { + for(int i=0; i<tr.depth(it)-rootdepth; ++i) + std::cout << " "; + std::cout << (*it) << std::endl << std::flush; + ++it; + } + std::cout << "-----" << std::endl; + } + + + +G4Track* GateLastVertexInteractionSplittingActor::CreateATrackFromContainer(LastVertexDataContainer container, G4Step *step ){ + auto *particle_table = G4ParticleTable::GetParticleTable(); + G4ParticleDefinition *particleDefinition = particle_table->FindParticle(container.GetParticleNameToSplit()); + G4ThreeVector momentum = container.GetMomentum(); + G4double energy = container.GetEnergy(); + if (energy <0){ + energy = 0; + momentum = {0,0,0}; + } + G4int trackStatus = container.GetTrackStatus(); + G4ThreeVector position = container.GetVertexPosition(); + G4ThreeVector polarization = container.GetPolarization(); + + G4DynamicParticle* dynamicParticle = new G4DynamicParticle(particleDefinition,momentum,energy); + G4Track* aTrack = new G4Track(dynamicParticle,step->GetPreStepPoint()->GetGlobalTime(), position); + //std::cout<<aTrack->GetMomentumDirection()<<std::endl; + aTrack->SetPolarization(polarization); + if (trackStatus == 0){ + aTrack->SetTrackStatus(fAlive); } - return weightToApply; + if (trackStatus == 1){ + aTrack->SetTrackStatus(fStopButAlive); + } + if ((trackStatus == 2) || (trackStatus == 3)){ + aTrack->SetTrackStatus(fAlive); + } + aTrack->SetWeight(container.GetWeight()); + + return aTrack; + + } -G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack( - G4ParticleChangeForGamma *gammaProcess, G4Track track, G4double weight) { + +G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack(G4ParticleChangeForGamma *gammaProcess, G4Track track, G4double weight) { G4double energy = gammaProcess->GetProposedKineticEnergy(); G4double globalTime = track.GetGlobalTime(); G4double newGammaWeight = weight; @@ -105,445 +141,325 @@ G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack( return newTrack; } -void GateLastVertexInteractionSplittingActor::ComptonSplitting( - G4Step *CurrentStep, G4Track *track, const G4Step *step, - G4VProcess *process) { - // Loop on process and add the secondary tracks to the current step secondary - // vector +void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process, LastVertexDataContainer container) { G4TrackVector *trackVector = CurrentStep->GetfSecondary(); G4double gammaWeight = 0; + G4Track* aTrack = CreateATrackFromContainer(container,initStep); GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; - G4VParticleChange *processFinalState = emProcess->PostStepDoIt(*track, *step); - G4ParticleChangeForGamma *gammaProcessFinalState = - (G4ParticleChangeForGamma *)processFinalState; - const G4ThreeVector momentum = - gammaProcessFinalState->GetProposedMomentumDirection(); - gammaWeight = fWeightOfEnteringParticle / fSplittingFactor; - if (fRussianRouletteForAngle == true) { - G4double weightToApply = RussianRouletteForAngleSurvival( - momentum, fVectorDirector, fMaxTheta, fSplittingFactor); - if (weightToApply != 0) { - gammaWeight = gammaWeight * weightToApply; - G4Track *newTrack = - CreateComptonTrack(gammaProcessFinalState, *track, gammaWeight); - trackVector->push_back(newTrack); - G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - for (int j = 0; j < NbOfSecondaries; j++) { - G4Track *newTrack = processFinalState->GetSecondary(j); - newTrack->SetWeight(gammaWeight); - trackVector->push_back(newTrack); - } - } - } + G4VParticleChange *processFinalState = emProcess->PostStepDoIt(*aTrack, *initStep); + G4ParticleChangeForGamma *gammaProcessFinalState = (G4ParticleChangeForGamma *)processFinalState; + const G4ThreeVector momentum = gammaProcessFinalState->GetProposedMomentumDirection(); + gammaWeight = aTrack->GetWeight()/ fSplittingFactor; - else { - G4Track *newTrack = - CreateComptonTrack(gammaProcessFinalState, *track, gammaWeight); + + + G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, *aTrack, gammaWeight); + trackVector->push_back(newTrack); + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + for (int j = 0; j < NbOfSecondaries; j++) { + G4Track *newTrack = processFinalState->GetSecondary(j); + newTrack->SetWeight(gammaWeight); trackVector->push_back(newTrack); - G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - for (int j = 0; j < NbOfSecondaries; j++) { - G4Track *newTrack = processFinalState->GetSecondary(j); - newTrack->SetWeight(gammaWeight); - trackVector->push_back(newTrack); - } } + processFinalState->Clear(); gammaProcessFinalState->Clear(); + delete aTrack; +} + + + +G4VProcess* GateLastVertexInteractionSplittingActor::GetProcessFromProcessName(G4String particleName, G4String pName){ + auto *particle_table = G4ParticleTable::GetParticleTable(); + G4ParticleDefinition *particleDefinition = particle_table->FindParticle(particleName); + G4ProcessManager *processManager = particleDefinition->GetProcessManager(); + G4ProcessVector *processList = processManager->GetProcessList(); + + G4VProcess* nullProcess = nullptr; + for (size_t i = 0; i < processList->size(); ++i) { + auto process = (*processList)[i]; + if (process->GetProcessName() == pName) { + return process; + } + } + return nullProcess; + } -void GateLastVertexInteractionSplittingActor::SecondariesSplitting( - G4Step *CurrentStep, G4Track *track, const G4Step *step, - G4VProcess *process) { - // Loop on process and add the secondary tracks to the current step secondary - // vector. - // std::cout<<track->GetKineticEnergy()<<" - // "<<track->GetDynamicParticle()->GetKineticEnergy()<<" - // "<<track->GetMomentumDirection()<<" - // "<<track->GetDynamicParticle()->GetMomentumDirection()<<std::endl; +G4VParticleChange* GateLastVertexInteractionSplittingActor::eBremProcessFinalState(G4Track* track, G4Step* step,G4VProcess *process){ + //It seem's that the the along step method apply only to brem results to no deposited energy but a change in momentum direction according to the process + //Whereas the along step method applied to the ionisation well change the deposited energy but not the momentum. Then I apply both to have a correct + //momentum and deposited energy before the brem effect. + G4String particleName = track->GetDefinition()->GetParticleName(); + G4VProcess* eIoniProcess = GetProcessFromProcessName(particleName, "eIoni"); + G4VProcess* eBremProcess = GetProcessFromProcessName(particleName, "eBrem"); + G4VParticleChange* eIoniProcessAlongState = eIoniProcess->AlongStepDoIt(*track, *step); + G4VParticleChange* eBremProcessAlongState = eBremProcess->AlongStepDoIt(*track, *step); + G4ParticleChangeForLoss* eIoniProcessAlongStateForLoss = (G4ParticleChangeForLoss*) eIoniProcessAlongState; + G4ParticleChangeForLoss* eBremProcessAlongStateForLoss = (G4ParticleChangeForLoss*) eBremProcessAlongState; + G4double LossEnergy = eIoniProcessAlongStateForLoss->GetLocalEnergyDeposit(); + G4ThreeVector momentum = eBremProcessAlongStateForLoss->GetProposedMomentumDirection(); + G4ThreeVector polarization = eBremProcessAlongStateForLoss->GetProposedPolarization(); + + track->SetKineticEnergy(track->GetKineticEnergy() - LossEnergy); + track->SetMomentumDirection(momentum); + track->SetPolarization(polarization); + GateBremPostStepDoIt *bremProcess = (GateBremPostStepDoIt *)process; + eIoniProcessAlongState->Clear(); + eBremProcessAlongState->Clear(); + + return bremProcess->GateBremPostStepDoIt::PostStepDoIt(*track, *step); + +} + +void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container) { + + G4Track* track = CreateATrackFromContainer(container,initStep); G4String particleName = track->GetParticleDefinition()->GetParticleName(); G4TrackVector *trackVector = CurrentStep->GetfSecondary(); G4double gammaWeight = 0; G4VParticleChange *processFinalState = nullptr; if (process->GetProcessName() == "eBrem") { - GateBremPostStepDoIt *bremProcess = (GateBremPostStepDoIt *)process; - processFinalState = - bremProcess->GateBremPostStepDoIt::PostStepDoIt(*track, *step); - } else { + processFinalState = eBremProcessFinalState(track,initStep,process); + } + else { GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; - if (track->GetTrackStatus() == 0) { - // if (process->GetProcessName() == "annihil") - // std::cout<<"lol"<<std::endl; - processFinalState = emProcess->PostStepDoIt(*track, *step); + if ((container.GetAnnihilationFlag() == "PostStep") && (track->GetKineticEnergy() > 0)){ + processFinalState = emProcess->PostStepDoIt(*track, *initStep); } - if (track->GetTrackStatus() == 1) { - GateplusannihilAtRestDoIt *eplusAnnihilProcess = - (GateplusannihilAtRestDoIt *)process; - - processFinalState = - eplusAnnihilProcess->GateplusannihilAtRestDoIt::AtRestDoIt(*track, - *step); + if ((container.GetAnnihilationFlag() == "AtRest") || (track->GetKineticEnergy() == 0)) { + GateplusannihilAtRestDoIt *eplusAnnihilProcess = (GateplusannihilAtRestDoIt *)process; + processFinalState = eplusAnnihilProcess->GateplusannihilAtRestDoIt::AtRestDoIt(*track,*initStep); } } - + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - - if (NbOfSecondaries > 0) { - gammaWeight = fWeightOfEnteringParticle / fSplittingFactor; - G4Track *newTrack = processFinalState->GetSecondary(0); - if ((fRussianRouletteForAngle == true) && (particleName == "gamma")) { - const G4ThreeVector momentum = newTrack->GetMomentumDirection(); - G4double weightToApply = RussianRouletteForAngleSurvival( - momentum, fVectorDirector, fMaxTheta, fSplittingFactor); - if (weightToApply != 0) { - gammaWeight = gammaWeight * weightToApply; - newTrack->SetWeight(gammaWeight); - trackVector->push_back(newTrack); - } - } else { + G4int idx = 0; + if (NbOfSecondaries >0) { + gammaWeight = track->GetWeight()/fSplittingFactor; + + G4double randomIdx = round((NbOfSecondaries -1) * G4UniformRand()); + idx = (G4int) randomIdx; + G4Track *newTrack = processFinalState->GetSecondary(idx); + if (!(isnan((newTrack->GetMomentumDirection())[0]))){ newTrack->SetWeight(gammaWeight); + newTrack->SetCreatorProcess(process); trackVector->push_back(newTrack); } - - for(int i = 1; i < NbOfSecondaries;i++){ - delete processFinalState->GetSecondary(i); + else{ + delete newTrack; + } + + + for(int i = 0; i < NbOfSecondaries;i++){ + if (i != idx) + delete processFinalState->GetSecondary(i); } - } else { - processFinalState->Clear(); - SecondariesSplitting(CurrentStep, track, step, process); } + + delete track; + + + + processFinalState->Clear(); + } -void GateLastVertexInteractionSplittingActor::ClearRememberedTracksAndSteps( - std::map<G4int, G4TrackVector> rememberedTracks, - std::map<G4int, std::vector<G4Step *>> rememberedSteps) { - std::vector<G4Track *> trackToKill = {}; - for (auto it = rememberedTracks.begin(); it != rememberedTracks.end(); ++it) { - std::vector<G4Track *> vector = it->second; - for (auto it2 = vector.begin(); it2 != vector.end(); it2++) { - if ((std::find(trackToKill.begin(), trackToKill.end(), *it2) == - trackToKill.end())) { - // Method set pour clear les doublons au lieu de find. - trackToKill.push_back(*it2); - } - } - } - for (auto it = trackToKill.begin(); it != trackToKill.end(); ++it) { - delete *it; +void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G4Step* initStep,G4Step *step,LastVertexDataContainer container) { + // We retrieve the process associated to the process name to split and we + // split according the process. Since for compton scattering, the gamma is not + // a secondary particles, this one need to have his own splitting function. + G4VProcess* processToSplit = GetProcessFromProcessName(container.GetParticleNameToSplit(),container.GetProcessNameToSplit()); + G4String processName = container.GetProcessNameToSplit(); + if (processName == "compt") { + ComptonSplitting(initStep,step, processToSplit, container); } - std::vector<G4Step *> stepToKill = {}; - for (auto it = rememberedSteps.begin(); it != rememberedSteps.end(); ++it) { - std::vector<G4Step *> vector = it->second; - for (auto it2 = vector.begin(); it2 != vector.end(); it2++) { - if ((std::find(stepToKill.begin(), stepToKill.end(), *it2) == - stepToKill.end())) { - stepToKill.push_back(*it2); - } - } + else if((processName != "msc") && (processName != "conv")){ + SecondariesSplitting(initStep, step, processToSplit, container); } + +} + - for (auto it = stepToKill.begin(); it != stepToKill.end(); ++it) { - delete *it; +void GateLastVertexInteractionSplittingActor::CreateListOfbiasedVolume(G4LogicalVolume *volume) { + G4int nbOfDaughters = volume->GetNoDaughters(); + if (nbOfDaughters > 0) { + for (int i = 0; i < nbOfDaughters; i++) { + G4String LogicalVolumeName = volume->GetDaughter(i)->GetLogicalVolume()->GetName(); + G4LogicalVolume *logicalDaughtersVolume = volume->GetDaughter(i)->GetLogicalVolume(); + if (!(std::find(fListOfBiasedVolume.begin(),fListOfBiasedVolume.end(),LogicalVolumeName) != fListOfBiasedVolume.end())) + fListOfBiasedVolume.push_back(volume->GetDaughter(i)->GetLogicalVolume()->GetName()); + CreateListOfbiasedVolume(logicalDaughtersVolume); + } } } -void GateLastVertexInteractionSplittingActor::RememberLastProcessInformation( - G4Step *step) { - // When an interesting process to split occurs, we remember the status of the - // track and the process at this current step some informations regarding the - // track info have to be changed because they were update according to the - // interaction that occured These informations are stocked as a map object, - // binding the track ID with all the track objects and processes to split. - // Because in some cases, if a secondary was created before an interaction - // chain, this secondary will be track after the chain and without this - // association, we wll loose the information about the process occuring for - // this secondary. +void GateLastVertexInteractionSplittingActor::FillOfDataTree(G4Step*step){ + G4String processName = "None"; + if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) + processName = step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); + G4String creatorProcessName = "None"; if (step->GetTrack()->GetCreatorProcess() != 0) - creatorProcessName = - step->GetTrack()->GetCreatorProcess()->GetProcessName(); - G4String processName = "None"; - G4int trackID = step->GetTrack()->GetTrackID(); - G4int parentID = step->GetTrack()->GetParentID(); - //<<processName<<" - //"<<step->GetTrack()->GetParticleDefinition()->GetParticleName()<<" - //"<<step->GetTrack()->GetTrackStatus()<<std::endl; - if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) { - processName = - step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); - } + creatorProcessName =step->GetTrack()->GetCreatorProcess()->GetProcessName(); + if ((step->GetTrack()->GetParticleDefinition()->GetParticleName() == "e+") && ((step->GetTrack()->GetTrackStatus() == 1) || (step->GetTrack()->GetTrackStatus() == 2))) { processName = "annihil"; } - if ((std::find(fListOfProcesses.begin(), fListOfProcesses.end(), - processName) != fListOfProcesses.end())) { - G4Track *trackInformation = new G4Track(*(step->GetTrack())); - G4Step *stepInformation = new G4Step(*(step)); - - G4StepPoint *stepPoint = nullptr; - stepPoint = step->GetPreStepPoint(); - - trackInformation->SetKineticEnergy(stepPoint->GetKineticEnergy()); - if ((processName == "eBrem") || ((processName == "annihil") && - step->GetTrack()->GetTrackStatus() == 1)) { - trackInformation->SetKineticEnergy(stepPoint->GetKineticEnergy() - - step->GetTotalEnergyDeposit()); + G4String annihilFlag = "None"; + if (processName == "annihil"){ + if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0){ + //std::cout<<processName<<" "<<step->GetPreStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetTotalEnergyDeposit()<<std::endl; + if (processName == step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName()){ + annihilFlag = "PostStep"; + } + else if (processName != step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName()){ + annihilFlag ="AtRest"; + } + //std::cout<<annihilFlag<<std::endl; } - trackInformation->SetMomentumDirection(stepPoint->GetMomentumDirection()); - if ((processName == "annihil") && - ((step->GetTrack()->GetTrackStatus() == 1))) - trackInformation->SetTrackStatus(fStopButAlive); - else { - trackInformation->SetTrackStatus(fAlive); + } + + + if (processName =="eBrem"){ + //std:cout<<step->GetPreStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetTotalEnergyDeposit()<<std::endl; + } + + + if (fIsFirstStep){ + LastVertexDataContainer newContainer = LastVertexDataContainer(); + newContainer.SetTrackID(step->GetTrack()->GetTrackID()); + newContainer.SetParticleName(step->GetTrack()->GetDefinition()->GetParticleName()); + newContainer.SetCreationProcessName(creatorProcessName); + if (fTree.empty()){ + fTree.set_head(newContainer); } - trackInformation->SetPolarization(stepPoint->GetPolarization()); - // trackInformation->SetPosition(stepPoint->GetPosition()); - - if (auto search = fRememberedTracks.find(trackID); - search != fRememberedTracks.end()) { - fRememberedTracks[trackID].push_back(trackInformation); - fRememberedProcesses[trackID].push_back(processName); - fRememberedSteps[trackID].push_back(stepInformation); + + for (auto it = fTree.begin_post(); it != fTree.end_post(); ++it){ + LastVertexDataContainer container = *it; + G4int trackID = container.GetTrackID(); + + if (step->GetTrack()->GetParentID() == trackID){ + newContainer = container.ContainerFromParentInformation(step); + fTree.append_child(it,newContainer); + break; + + } } - else { - fRememberedTracks[trackID] = {trackInformation}; - fRememberedProcesses[trackID] = {processName}; - fRememberedSteps[trackID] = {stepInformation}; + for (auto it = fTree.begin_post(); it != fTree.end_post(); ++it){ + LastVertexDataContainer container = *it; + G4int trackID = container.GetTrackID(); + if (step->GetTrack()->GetTrackID() == trackID){ + fIterator = it; + break; + } } } - else { - if (auto search = fRememberedTracks.find(trackID); - search == fRememberedTracks.end()) { - if (auto search = fRememberedTracks.find(parentID); - search != fRememberedTracks.end()) { - if (auto it = std::find(fRememberedProcesses[parentID].begin(), - fRememberedProcesses[parentID].end(), - creatorProcessName); - it != fRememberedProcesses[parentID].end()) { - auto idx = it - fRememberedProcesses[parentID].begin(); - fRememberedTracks[trackID] = { - new G4Track(*fRememberedTracks[parentID][idx])}; - fRememberedProcesses[trackID] = {fRememberedProcesses[parentID][idx]}; - fRememberedSteps[trackID] = { - new G4Step(*fRememberedSteps[parentID][idx])}; - } else { - fRememberedTracks[trackID] = { - new G4Track(*fRememberedTracks[parentID][0])}; - fRememberedProcesses[trackID] = {fRememberedProcesses[parentID][0]}; - fRememberedSteps[trackID] = { - new G4Step(*fRememberedSteps[parentID][0])}; + + LastVertexDataContainer* container = &(*fIterator); + G4int trackID = container->GetTrackID(); + if ((processName != "Transportation") &&(processName !="None") && (processName !="Rayl")){ + if (step->GetTrack()->GetTrackID() == trackID){ + G4ThreeVector position = step->GetTrack()->GetPosition(); + G4ThreeVector prePosition = step->GetPreStepPoint()->GetPosition(); + G4ThreeVector momentum; + if ((processName == "annihil")) + momentum = step->GetPostStepPoint()->GetMomentumDirection(); + else{ + momentum = step->GetPreStepPoint()->GetMomentumDirection(); + } + G4ThreeVector polarization = step->GetPreStepPoint()->GetPolarization(); + G4String particleName = step->GetTrack()->GetDefinition()->GetParticleName(); + G4double energy = step->GetPreStepPoint()->GetKineticEnergy(); + G4double weight = step->GetTrack()->GetWeight(); + G4int trackStatus = step->GetTrack()->GetTrackStatus(); + G4int nbOfSecondaries = step->GetfSecondary()->size(); + G4double stepLength = step->GetStepLength(); + if (((processName == "annihil"))){ + energy -= (step->GetTotalEnergyDeposit()); } + container->SetSplittingParameters(processName,energy, momentum, position,polarization,particleName,weight,trackStatus,nbOfSecondaries,annihilFlag,stepLength,prePosition); + container->PushListOfSplittingParameters(processName,energy, momentum, position,polarization,particleName,weight,trackStatus,nbOfSecondaries,annihilFlag,stepLength,prePosition); } } - } } -void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex( - G4Step *CurrentStep, G4Track *track, const G4Step *step, - G4String processName) { - // We retrieve the process associated to the process name to split and we - // split according the process. Since for compton scattering, the gamma is not - // a secondary particles, this one need to have his own splitting function. - G4ParticleDefinition *particleDefinition = track->GetDefinition(); - G4ProcessManager *processManager = particleDefinition->GetProcessManager(); - G4ProcessVector *processList = processManager->GetProcessList(); - - G4VProcess *processToSplit = nullptr; - for (size_t i = 0; i < processList->size(); ++i) { - auto process = (*processList)[i]; - if (process->GetProcessName() == processName) { - processToSplit = process; - } - } - if (processName == "compt") { - - ComptonSplitting(CurrentStep, track, step, processToSplit); - } - else { - SecondariesSplitting(CurrentStep, track, step, processToSplit); - } -} -void GateLastVertexInteractionSplittingActor:: - ResetProcessesForEnteringParticles(G4Step *step) { - - // This function reset the processes and track registered to be split each - // time a particle enters into a volume. Here a new particle incoming is - // either a particle entering into the volume ( first pre step is a boundary - // of a mother volume or first step trigger the actor and the vertex is not - // either the mother volume or a the fdaughter volume) or an event generated - // within the volume (the particle did not enter into the volume and its - // parentID = 0). An additionnal condition is set with the trackID to ensure - // particles crossing twice the volume (after either a compton or pair prod) - // are not splitted. - - G4int trackID = step->GetTrack()->GetTrackID(); - if ((fEventIDOfInitialSplittedTrack != fEventID) || - ((fEventIDOfInitialSplittedTrack == fEventID) && - (trackID < fTrackIDOfInitialTrack))) { - G4String logicalVolumeNamePreStep = "None"; - if (step->GetPreStepPoint()->GetPhysicalVolume() != 0) { - logicalVolumeNamePreStep = step->GetPreStepPoint() - ->GetPhysicalVolume() - ->GetLogicalVolume() - ->GetName(); +G4bool GateLastVertexInteractionSplittingActor::IsParticleExitTheBiasedVolume(G4Step*step){ + G4String logicalVolumeNamePreStep = "None"; + G4String logicalVolumeNamePostStep = "None"; + if (step->GetPreStepPoint()->GetPhysicalVolume() != 0) + logicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) + logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + + + if (logicalVolumeNamePreStep != logicalVolumeNamePostStep){ + if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()){ + return true; } - if (((step->GetPreStepPoint()->GetStepStatus() == 1) && - (logicalVolumeNamePreStep == fMotherVolumeName)) || - ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != - logicalVolumeNamePreStep) && - (step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != - fMotherVolumeName))) { - fTrackIDOfInitialTrack = trackID; - fEventIDOfInitialSplittedTrack = fEventID; - fWeightOfEnteringParticle = step->GetTrack()->GetWeight(); - ClearRememberedTracksAndSteps(fRememberedTracks, fRememberedSteps); - fRememberedProcesses.clear(); - fRememberedTracks.clear(); - fRememberedSteps.clear(); - } else if (step->GetTrack()->GetParentID() == 0) { - fTrackIDOfInitialTrack = trackID; - fEventIDOfInitialSplittedTrack = fEventID; - fWeightOfEnteringParticle = step->GetTrack()->GetWeight(); - ClearRememberedTracksAndSteps(fRememberedTracks, fRememberedSteps); - fRememberedProcesses.clear(); - fRememberedTracks.clear(); - fRememberedSteps.clear(); + /* + else if (std::find(fListOfBiasedVolume.begin(), fListOfBiasedVolume.end(), logicalVolumeNamePostStep) != fListOfBiasedVolume.end()) { + return false; + } + */ + else{ + return true; } } + return false; } -void GateLastVertexInteractionSplittingActor:: - PostponeFirstAnnihilationTrackIfInteraction(G4Step *step, - G4String processName) { - // In case the first gamma issued from annihilation undergoes an interaction, - // in order to not bias the process We keep in memory the particle post step - // state (with its secondaries) and kill the particle and its secondaries. If - // the second photon from annihilation exiting the collimation system with an - // interaction or is absorbed within the collimation, the particle is - // subsequently resimulated, starting from the interaction point. - - G4int trackID = step->GetTrack()->GetTrackID(); - if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), - processName) != fListOfProcesses.end()) { - fSuspendForAnnihil = true; - G4Track trackToPostpone = G4Track(*(step->GetTrack())); - trackToPostpone.SetKineticEnergy( - step->GetPostStepPoint()->GetKineticEnergy()); - trackToPostpone.SetMomentumDirection( - step->GetPostStepPoint()->GetMomentumDirection()); - trackToPostpone.SetTrackStatus(step->GetTrack()->GetTrackStatus()); - trackToPostpone.SetPolarization( - step->GetPostStepPoint()->GetPolarization()); - trackToPostpone.SetPosition(step->GetPostStepPoint()->GetPosition()); - trackToPostpone.SetTrackID(trackID); - fTracksToPostpone.push_back(trackToPostpone); - auto theTrack = fTracksToPostpone[0]; - - auto secVec = step->GetfSecondary(); - for (int i = 0; i < secVec->size(); i++) { - G4Track *sec = (*secVec)[i]; - G4Track copySec = G4Track((*sec)); - fTracksToPostpone.push_back(copySec); - } - fTracksToPostpone[0].SetTrackID(trackID); - step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); + +G4bool GateLastVertexInteractionSplittingActor::IsTheParticleUndergoesAProcess(G4Step* step){ + G4String processName = "None"; + G4String particleName = step->GetTrack()->GetParticleDefinition()->GetParticleName(); + if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) + processName = step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); + + if (std::find(fListOfProcessesAccordingParticles[particleName].begin(), fListOfProcessesAccordingParticles[particleName].end(), processName) != fListOfProcessesAccordingParticles[particleName].end()){ + return true; } -} + return false; -void GateLastVertexInteractionSplittingActor:: - RegenerationOfPostponedAnnihilationTrack(G4Step *step) { - // If the second photon from annihilation suceed to exit the collimation - // system with at least one interaction or was absorbed. Resimulation of - // annihilation photons and its potential secondaries. +} - G4TrackVector *currentSecondaries = step->GetfSecondary(); - for (int i = 0; i < fTracksToPostpone.size(); i++) { - G4Track *trackToAdd = - new G4Track(fTracksToPostpone[fTracksToPostpone.size() - 1 - i]); - trackToAdd->SetParentID(step->GetTrack()->GetTrackID() - 1); - currentSecondaries->insert(currentSecondaries->begin(), trackToAdd); - } - G4Track *firstPostponedTrack = (*currentSecondaries)[0]; +void GateLastVertexInteractionSplittingActor::StartSimulationAction (){ + fListOfProcessesAccordingParticles["gamma"] = {"compt","phot","conv"}; + fListOfProcessesAccordingParticles["e-"] = {"eBrem","eIoni","msc"}; + fListOfProcessesAccordingParticles["e+"] = {"eBrem","eIoni","msc","annihil"}; - // Handle of case were the interaction killed the photon issued from - // annihilation, it will not be track at the following state and the boolean - // plus the track vector need to be reset - if (firstPostponedTrack->GetTrackStatus() == 2) { - fSuspendForAnnihil = false; - fTracksToPostpone.clear(); - } -} + G4LogicalVolume *biasingVolume = G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + fListOfBiasedVolume.push_back(biasingVolume->GetName()); + CreateListOfbiasedVolume(biasingVolume); -void GateLastVertexInteractionSplittingActor:: - HandleTrackIDIfPostponedAnnihilation(G4Step *step) { - // The ID is to modify trackID and the processes and tracks sotcked for a - // specific trackID associated to the postponed annihilation to respect the - // trackID order in GEANT4. Since in this case the second photon is tracked - // before the first one his trackID +=1. For the postponed one, he is the - // first secondary particle of the second photon tracks, his trackID is - // therefore equal to the second photon trackID +1 instead of the second - // photon trackID -1. The parentID of secondary particles are also modified - // because they are used in the rememberlastprocess function At last the value - // associated to a specific trackID in the process and track map are modified - // according to the new trackID. - - if (fSuspendForAnnihil) { - if (step->GetTrack()->GetTrackID() == - fTracksToPostpone[0].GetTrackID() - 1) { - fRememberedProcesses[step->GetTrack()->GetTrackID()] = - fRememberedProcesses[step->GetTrack()->GetTrackID() + 1]; - fRememberedProcesses.erase(step->GetTrack()->GetTrackID() + 1); - fRememberedSteps[step->GetTrack()->GetTrackID()] = - fRememberedSteps[step->GetTrack()->GetTrackID() + 1]; - fRememberedSteps.erase(step->GetTrack()->GetTrackID() + 1); - fRememberedTracks[step->GetTrack()->GetTrackID()] = - fRememberedTracks[step->GetTrack()->GetTrackID() + 1]; - fRememberedTracks.erase(step->GetTrack()->GetTrackID() + 1); - auto vecSec = step->GetSecondary(); - for (int i = 0; i < vecSec->size(); i++) { - (*vecSec)[i]->SetParentID(step->GetTrack()->GetTrackID() + 1); - } - step->GetTrack()->SetTrackID(step->GetTrack()->GetTrackID() + 1); - } - if (step->GetTrack()->GetTrackID() == - fTracksToPostpone[0].GetTrackID() + 1) { - auto vecSec = step->GetSecondary(); - for (int i = 0; i < vecSec->size(); i++) { - (*vecSec)[i]->SetParentID(step->GetTrack()->GetTrackID() - 2); - } - step->GetTrack()->SetTrackID(step->GetTrack()->GetTrackID() - 2); - } - } + auto* source = fSourceManager->FindSourceByName("source_vertex"); + fVertexSource = (GateLastVertexSource* ) source; } + void GateLastVertexInteractionSplittingActor::BeginOfRunAction( const G4Run *run) { - // The way to behave of the russian roulette is the following : - // we provide a vector director and the theta angle acceptance, where theta = - // 0 is a vector colinear to the vector director Then if the track generated - // is on the acceptance angle, we add it to the primary track, and if it's not - // the case, we launch the russian roulette - if (fRotationVectorDirector) { G4VPhysicalVolume *physBiasingVolume = G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); @@ -554,293 +470,172 @@ void GateLastVertexInteractionSplittingActor::BeginOfRunAction( void GateLastVertexInteractionSplittingActor::BeginOfEventAction( const G4Event *event) { - fParentID = -1; fEventID = event->GetEventID(); fEventIDOfSplittedTrack = -1; fTrackIDOfSplittedTrack = -1; fNotSplitted == true; + fIsAnnihilAlreadySplit = false; + //std::cout<<fEventID<<std::endl; + fCopyInitStep = nullptr; + + + } void GateLastVertexInteractionSplittingActor::PreUserTrackingAction( const G4Track *track) { - fIsFirstStep = true; + + fIsFirstStep = true; + } void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { - G4String particleName = - step->GetTrack()->GetParticleDefinition()->GetParticleName(); + G4String logicalVolumeNamePreStep = "None"; + G4String logicalVolumeNamePostStep = "None"; + if (step->GetPreStepPoint()->GetPhysicalVolume() != 0) + logicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) + logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + + G4String particleName =step->GetTrack()->GetParticleDefinition()->GetParticleName(); G4String creatorProcessName = "None"; + G4String processName = "None"; if (step->GetTrack()->GetCreatorProcess() != 0) - creatorProcessName = - step->GetTrack()->GetCreatorProcess()->GetProcessName(); - - // std::cout<<particleName<<" "<<fEventID<<" - // "<<step->GetTotalEnergyDeposit()<<" - // "<<step->GetPreStepPoint()->GetKineticEnergy()<<" - // "<<step->GetPreStepPoint()->GetMomentumDirection()<<step->GetTrack()->GetTrackID()<<" - // "<<step->GetTrack()->GetWeight()<<" "<<step->GetTrack()->GetParentID()<<" - // "<<fTrackIDOfSplittedTrack<<std::endl;; - - if (fIsFirstStep) { - ResetProcessesForEnteringParticles(step); + creatorProcessName =step->GetTrack()->GetCreatorProcess()->GetProcessName(); + + if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) + processName = step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); + + /* + if (processName == "annihil"){ + std::cout<<"ANNIHIL"<<" "<<step->GetTrack()->GetTrackStatus()<<std::endl; + std::cout<<step->GetPreStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetTotalEnergyDeposit()<<std::endl; + auto* secondaries = step->GetSecondaryInCurrentStep(); + for (const G4Track* secondary : *secondaries){ + std::cout<<secondary->GetKineticEnergy()<<std::endl; + } + + } + */ + + if (fActiveSource != "source_vertex"){ + FillOfDataTree(step); + if (IsParticleExitTheBiasedVolume(step)){ + fListOfContainer.push_back((*fIterator)); + step->GetTrack()->SetTrackStatus(fStopAndKill); + //std::cout<<processName<<" "<<step->GetPostStepPoint()->GetMomentumDirection()<<std::endl; + //std::cout<<"kill"<<std::endl; + } } - G4int trackID = step->GetTrack()->GetTrackID(); - - if ((step->GetTrack()->GetWeight() < fWeightOfEnteringParticle) && - (fNotSplitted == false)) { - - G4String logicalVolumeNamePostStep = "None"; - if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) - logicalVolumeNamePostStep = step->GetPostStepPoint() - ->GetPhysicalVolume() - ->GetLogicalVolume() - ->GetName(); + + if (fActiveSource == "source_vertex"){ + //std::cout<<particleName<<" "<<step->GetPreStepPoint()->GetMomentumDirection()<<std::endl; + auto* source = fSourceManager->FindSourceByName(fActiveSource); + GateLastVertexSource *vertexSource = (GateLastVertexSource*) source; + LastVertexDataContainer container = vertexSource->GetLastVertexContainer(); + G4ThreeVector momentum = step->GetPreStepPoint()->GetMomentumDirection(); + G4Step* copyInitStep = nullptr; + + G4String processToSplit = vertexSource->GetProcessToSplit(); + if (((step->GetTrack()->GetParentID() == 0)&& (step->GetTrack()->GetTrackID() == 1))|| ((creatorProcessName == "annihil") && (step->GetTrack()->GetParentID() == 1))){ + + if ((processToSplit != "annihil") || ((processToSplit == "annihil")&& (fIsAnnihilAlreadySplit ==false))){ + + step->GetfSecondary()->clear(); + if ((processToSplit != "msc") && (processToSplit != "conv")) { + fCopyInitStep= new G4Step(*step); + if (processToSplit == "eBrem"){ + fCopyInitStep->SetStepLength(container.GetStepLength()); + fCopyInitStep->GetPreStepPoint()->SetKineticEnergy(container.GetEnergy()); - G4String processName = "None"; - if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) - processName = - step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); + } + while (step->GetfSecondary()->size() == 0){ + CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); - if (particleName != "e+"){ - if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), processName) != fListOfProcesses.end()){ + } + } step->GetTrack()->SetTrackStatus(fStopAndKill); - for (int i = 0; i < step->GetfSecondary()->size(); i++){ - G4Track* track =(*(step->GetfSecondary()))[i]; - track->SetTrackStatus(fStopAndKill); + + if (processToSplit == "annihil"){ + fIsAnnihilAlreadySplit = true; + } } + else if ((processToSplit == "annihil")&& (fIsAnnihilAlreadySplit == true)){ + step->GetfSecondary()->clear(); + step->GetTrack()->SetTrackStatus(fStopAndKill); } - } - - - if ((particleName == "e+") && ((step->GetTrack()->GetTrackStatus() == 1)||(step->GetTrack()->GetTrackStatus() == 2))){ - step->GetTrack()->SetTrackStatus(fStopAndKill); - for (int i = 0; i < step->GetfSecondary()->size(); i++){ - G4Track* track = (*(step->GetfSecondary()))[i]; - track->SetTrackStatus(fStopAndKill); + } - } - if ((step->GetTrack()->GetTrackStatus() == 2) || - (step->GetTrack()->GetTrackStatus() == 3)) { - - /* - delete fTrackToSplit; - delete fStepToSplit; - fTrackToSplit = new - G4Track(*fRememberedTracks[fTrackIDOfSplittedTrack].back()); fStepToSplit - = new G4Step(*fRememberedSteps[fTrackIDOfSplittedTrack].back()); - */ - - CreateNewParticleAtTheLastVertex(step, fTrackToSplit, fStepToSplit, - fProcessToSplit); + + else if (IsTheParticleUndergoesAProcess(step)){ + step->GetfSecondary()->clear(); + while (step->GetfSecondary()->size() == 0){ + CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); + } + step->GetTrack()->SetTrackStatus(fStopAndKill); + } + - if (((step->GetTrack()->GetTrackStatus() != 2) && - (step->GetTrack()->GetTrackStatus() != 3)) && - (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), - logicalVolumeNamePostStep) != fListOfVolumeAncestor.end())) { - - if (fSplitCounter < fWeightOfEnteringParticle) { - /* - delete fTrackToSplit; - delete fStepToSplit; - fTrackToSplit = new - G4Track(*fRememberedTracks[fTrackIDOfSplittedTrack].back()); - fStepToSplit = new - G4Step(*fRememberedSteps[fTrackIDOfSplittedTrack].back()); - */ - CreateNewParticleAtTheLastVertex(step, fTrackToSplit, fStepToSplit, - fProcessToSplit); - // std::cout<<fTrackToSplit<<" "<<fStepToSplit<<std::endl; - G4TrackVector *trackVector = step->GetfSecondary(); - G4Track *theTrack = trackVector->back(); - fSplitCounter += theTrack->GetWeight(); - // std::cout<<theTrack->GetWeight()<<std::endl; - if (fSplitCounter >= fWeightOfEnteringParticle) { - delete fTrackToSplit; - delete fStepToSplit; - fTrackToSplit = nullptr; - fStepToSplit = nullptr; - fSplitCounter = 0; - fNotSplitted = true; - fProcessToSplit = "None"; - theTrack->SetTrackStatus(fStopAndKill); - } + else if (IsParticleExitTheBiasedVolume(step)){ + fSplitCounter += 1; + if (fSplitCounter < fSplittingFactor){ + while (step->GetfSecondary()->size() == 0){ + CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); + } } + else{ + delete fCopyInitStep; + fSplitCounter = 0; + fCounter = 0; + } + } + + fCounter ++; + } + + + fIsFirstStep = false; - if (step->GetTrack()->GetWeight() == fWeightOfEnteringParticle) { - // std::cout<<particleName<<" - // "<<step->GetTrack()->GetTrackStatus()<<std::endl; - RememberLastProcessInformation(step); - // std::cout<<particleName<<" - // "<<step->GetTrack()->GetTrackStatus()<<std::endl; - G4String process = "None"; - if (auto search = fRememberedProcesses.find(trackID); - search != fRememberedProcesses.end()) { - process = fRememberedProcesses[trackID].back(); - } - // std::cout<<step->GetTrack()->GetVertexPosition()<<" - // "<<step->GetTrack()->GetParticleDefinition()->GetParticleName()<<" - // "<<fSuspendForAnnihil<<" "<<process<<" - // "<<step->GetTrack()->GetWeight()<<" "<<step->GetTrack()->GetParentID()<<" - // "<<step->GetTrack()->GetTrackID()<<" "<<creatorProcessName<<" - // "<<step->GetTrack()->GetTrackStatus()<<" - // "<<step->GetPreStepPoint()->GetKineticEnergy()<<" " - // <<step->GetPostStepPoint()->GetKineticEnergy()<<" - // "<<step->GetPreStepPoint()->GetPosition()<<" - // "<<step->GetPostStepPoint()->GetPosition()<<std::endl; - /* - if ((!fSuspendForAnnihil) && (creatorProcessName == "annihil") && - (fTrackIDOfSplittedTrack == step->GetTrack()->GetParentID()) && (fEventID == - fEventIDOfSplittedTrack)) - { - step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); - } - */ - if ((particleName == "e-") && - (step->GetTrack()->GetWeight() == fWeightOfEnteringParticle) && - (creatorProcessName == "conv") && - (fTrackIDOfSplittedTrack == step->GetTrack()->GetParentID()) && - (fEventID == fEventIDOfSplittedTrack)) { - step->GetTrack()->SetTrackStatus(fStopAndKill); - } + +} - /* - if ((!fSuspendForAnnihil) && (process != "annihil") && (creatorProcessName - == "annihil") && (step->GetTrack()->GetTrackStatus() != 3)) - { - G4int parentID = step->GetTrack()->GetParentID(); - if (auto search = fRememberedProcesses.find(parentID); search != - fRememberedProcesses.end()) - { - if (fRememberedProcesses[trackID].back() == "annihil") - PostponeFirstAnnihilationTrackIfInteraction(step, process); - } - } +void GateLastVertexInteractionSplittingActor::PostUserTrackingAction( + const G4Track *track) { - // If the first annihilation photon exit the collimation and the process to - split is annihilation - // We kill the second photon, because the annihilation will generate both - the photons. - - if (fSuspendForAnnihil) - { - if (trackID == fTracksToPostpone[0].GetTrackID() - 1) - { - fSuspendForAnnihil = false; - fTracksToPostpone.clear(); - } } - */ - G4String logicalVolumeNamePostStep = "None"; - if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) - logicalVolumeNamePostStep = step->GetPostStepPoint() - ->GetPhysicalVolume() - ->GetLogicalVolume() - ->GetName(); - if (((step->GetTrack()->GetTrackStatus() != 2) && - (step->GetTrack()->GetTrackStatus() != 3)) && - (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), - logicalVolumeNamePostStep) != fListOfVolumeAncestor.end())) { +void GateLastVertexInteractionSplittingActor::EndOfEventAction( + const G4Event* event) { - if (auto search = fRememberedProcesses.find(trackID); - search != fRememberedProcesses.end()) { - fProcessToSplit = fRememberedProcesses[trackID].back(); - fTrackToSplit = new G4Track(*fRememberedTracks[trackID].back()); - fStepToSplit = new G4Step(*fRememberedSteps[trackID].back()); + if (fActiveSource != "source_vertex"){ + //print_tree(fTree,fTree.begin(),fTree.end()); + fVertexSource->SetNumberOfEventToSimulate(fListOfContainer.size()); + fVertexSource->SetNumberOfGeneratedEvent(0); + fVertexSource->SetListOfVertexToSimulate(fListOfContainer); + fTree.clear(); + fListOfContainer.clear(); } - if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), - fProcessToSplit) != fListOfProcesses.end()) { - - fTrackIDOfSplittedTrack = trackID; - fEventIDOfSplittedTrack = fEventID; - fTrackIDOfInitialSplittedTrack = fTrackIDOfInitialTrack; - - // Handle of pecularities (1): - - // If the process t split is the gamma issued from compton interaction, - // the electron primary generated have to be killed given that electron - // will be regenerated - - if ((fProcessToSplit == "compt") && (particleName == "gamma")) { - auto secondaries = step->GetfSecondary(); - if (secondaries->size() > 0) { - G4Track *lastSecTrack = secondaries->back(); - lastSecTrack->SetTrackStatus(fStopAndKill); - } - } - - // Handle of pecularities (2): - - // If the process to split is the annihilation, the second photon, - // postponed or not, have to be killed the reset of the postpone is - // performed here, whereas the kill of the next annihilation photon, if - // not postponed is realised at the beginning of the step tracking. - /* - if (fProcessToSplit == "annihil") - { - if (fSuspendForAnnihil) - { - fSuspendForAnnihil = false; - fTracksToPostpone.clear(); - } - } - */ - // Handle of pecularities 3 : If the positron which created one or more - // brem photons exits all the brems photons will be killed before their - // tracking, and the conv processes will then be replayed - - if ((particleName == "e+") && (fProcessToSplit != "None")) { - - G4int parentID = step->GetTrack()->GetParentID(); - fProcessToSplit = fRememberedProcesses[parentID].back(); - delete fTrackToSplit; - delete fStepToSplit; - fTrackToSplit = new G4Track(*fRememberedTracks[parentID].back()); - fStepToSplit = new G4Step(*fRememberedSteps[parentID].back()); - fTrackIDOfInitialTrack = parentID; - - auto *vecSecondaries = step->GetfSecondary(); - vecSecondaries->clear(); - } - if (!((fProcessToSplit == "eBrem") && (particleName == "e-"))) { - CreateNewParticleAtTheLastVertex(step, fTrackToSplit, fStepToSplit, - fProcessToSplit); - fNotSplitted = false; - } - step->GetTrack()->SetTrackStatus(fStopAndKill); + auto* source = fSourceManager->FindSourceByName("source_vertex"); + GateLastVertexSource* vertexSource = (GateLastVertexSource*) source; + if (vertexSource->GetNumberOfGeneratedEvent() < vertexSource->GetNumberOfEventToSimulate()){ + fSourceManager->SetActiveSourcebyName("source_vertex"); } + fActiveSource = fSourceManager->GetActiveSourceName(); + } - } - - /* - if ((fSuspendForAnnihil) && ((step->GetTrack()->GetTrackStatus() == 1) || - (step->GetTrack()->GetTrackStatus() == 2))) - { - if (trackID == fTracksToPostpone[0].GetTrackID()) - { - RegenerationOfPostponedAnnihilationTrack(step); - } - } - */ - - fIsFirstStep = false; -} - -void GateLastVertexInteractionSplittingActor::PostUserTrackingAction( - const G4Track *track) {} //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h index 62021496c..7e20585c1 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h @@ -34,6 +34,14 @@ #include "GateVActor.h" #include <iostream> #include <pybind11/stl.h> +#include "GateLastVertexSplittingDataContainer.h" +#include "tree.hh" +#include "tree_util.hh" +#include <iostream> +#include "GateLastVertexSource.h" +#include "CLHEP/Vector/ThreeVector.h" +using CLHEP::Hep3Vector; + namespace py = pybind11; class GateLastVertexInteractionSplittingActor : public GateVActor { @@ -59,9 +67,17 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { G4double fWeightOfEnteringParticle = 0; G4double fSplitCounter = 0; G4bool fNotSplitted = true; + G4String fActiveSource = "None"; + G4bool fIsAnnihilAlreadySplit =false; + G4int fCounter; + GateLastVertexSource* fVertexSource = nullptr; + tree<LastVertexDataContainer> fTree; + tree<LastVertexDataContainer>::post_order_iterator fIterator; + std::vector<LastVertexDataContainer> fListOfContainer; + G4Track *fTrackToSplit = nullptr; - G4Step *fStepToSplit = nullptr; + G4Step* fCopyInitStep = nullptr; G4String fProcessToSplit = "None"; std::vector<G4Track> fTracksToPostpone; @@ -69,41 +85,40 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { std::map<G4int, G4TrackVector> fRememberedTracks; std::map<G4int, std::vector<G4Step *>> fRememberedSteps; std::map<G4int, std::vector<G4String>> fRememberedProcesses; + std::map<G4String,std::vector<G4String>> fListOfProcessesAccordingParticles; + + std::vector<std::string> fListOfVolumeAncestor; + std::vector<std::string> fListOfBiasedVolume; std::vector<G4String> fListOfProcesses = {"compt", "annihil", "eBrem", "conv", "phot"}; + virtual void StartSimulationAction() override; virtual void SteppingAction(G4Step *) override; virtual void BeginOfEventAction(const G4Event *) override; + virtual void EndOfEventAction(const G4Event *) override; virtual void BeginOfRunAction(const G4Run *run) override; virtual void PreUserTrackingAction(const G4Track *track) override; virtual void PostUserTrackingAction(const G4Track *track) override; // Pure splitting functions - G4double RussianRouletteForAngleSurvival(G4ThreeVector, G4ThreeVector, - G4double, G4double); G4Track *CreateComptonTrack(G4ParticleChangeForGamma *, G4Track, G4double); - void ComptonSplitting(G4Step *CurrentStep, G4Track *track, const G4Step *step, - G4VProcess *process); - void SecondariesSplitting(G4Step *CurrentStep, G4Track *track, - const G4Step *step, G4VProcess *process); - - // Handling the remembered processes to replay - void RememberLastProcessInformation(G4Step *); - void CreateNewParticleAtTheLastVertex(G4Step *, G4Track *, const G4Step *, - G4String); - void ResetProcessesForEnteringParticles(G4Step *step); - void ClearRememberedTracksAndSteps(std::map<G4int, G4TrackVector>, - std::map<G4int, std::vector<G4Step *>>); - - // Edge case to handle the bias in annihilation - // FIXME : The triple annihilation is not handled for the moment - void PostponeFirstAnnihilationTrackIfInteraction(G4Step *step, - G4String processName); - void RegenerationOfPostponedAnnihilationTrack(G4Step *step); - void HandleTrackIDIfPostponedAnnihilation(G4Step *step); + void ComptonSplitting(G4Step* initStep,G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container); + void SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container); + + void CreateNewParticleAtTheLastVertex(G4Step*init,G4Step *current, LastVertexDataContainer); + G4Track* CreateATrackFromContainer(LastVertexDataContainer container, G4Step *step ); + G4bool IsTheParticleUndergoesAProcess(G4Step* step); + G4VProcess* GetProcessFromProcessName(G4String particleName, G4String pName); + G4VParticleChange* eBremProcessFinalState(G4Track* track, G4Step* step,G4VProcess *process); + + + void FillOfDataTree(G4Step *step); + G4bool IsParticleExitTheBiasedVolume(G4Step*step); + void CreateListOfbiasedVolume(G4LogicalVolume *volume); + void print_tree(const tree<LastVertexDataContainer>& tr, tree<LastVertexDataContainer>::pre_order_iterator it, tree<LastVertexDataContainer>::pre_order_iterator end); }; #endif diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActorOld.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActorOld.h new file mode 100644 index 000000000..3863a8b62 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActorOld.h @@ -0,0 +1,110 @@ +// +// ******************************************************************** +// * 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 GateLastVertexInteractionSplittingActorOld.h +/// \brief Definition of the GateLastVertexInteractionSplittingActorOld class +#ifndef GateLastVertexInteractionSplittingActorOld_h +#define GateLastVertexInteractionSplittingActorOld_h 1 + +#include "G4ParticleChangeForGamma.hh" +#include "G4VEnergyLossProcess.hh" +#include "GateVActor.h" +#include <iostream> +#include <pybind11/stl.h> +namespace py = pybind11; + +class GateLastVertexInteractionSplittingActorOld : public GateVActor { +public: + GateLastVertexInteractionSplittingActorOld(py::dict &user_info); + virtual ~GateLastVertexInteractionSplittingActorOld() {} + + G4double fSplittingFactor; + G4bool fRussianRouletteForAngle = false; + G4bool fRotationVectorDirector; + G4ThreeVector fVectorDirector; + G4double fMaxTheta; + G4int fTrackIDOfSplittedTrack = 0; + G4int fParentID = -1; + G4int fEventID; + G4int fEventIDOfSplittedTrack; + G4int fEventIDOfInitialSplittedTrack; + G4int fTrackIDOfInitialTrack; + G4int fTrackIDOfInitialSplittedTrack = 0; + G4int ftmpTrackID; + G4bool fIsFirstStep = true; + G4bool fSuspendForAnnihil = false; + G4double fWeightOfEnteringParticle = 0; + G4double fSplitCounter = 0; + G4bool fNotSplitted = true; + + G4Track *fTrackToSplit = nullptr; + G4Step *fStepToSplit = nullptr; + G4String fProcessToSplit = "None"; + + std::vector<G4Track> fTracksToPostpone; + + std::map<G4int, G4TrackVector> fRememberedTracks; + std::map<G4int, std::vector<G4Step *>> fRememberedSteps; + std::map<G4int, std::vector<G4String>> fRememberedProcesses; + + std::vector<std::string> fListOfVolumeAncestor; + + std::vector<G4String> fListOfProcesses = {"compt", "annihil", "eBrem", "conv", + "phot"}; + + virtual void SteppingAction(G4Step *) override; + virtual void BeginOfEventAction(const G4Event *) override; + virtual void BeginOfRunAction(const G4Run *run) override; + virtual void PreUserTrackingAction(const G4Track *track) override; + virtual void PostUserTrackingAction(const G4Track *track) override; + + // Pure splitting functions + G4double RussianRouletteForAngleSurvival(G4ThreeVector, G4ThreeVector, + G4double, G4double); + G4Track *CreateComptonTrack(G4ParticleChangeForGamma *, G4Track, G4double); + void ComptonSplitting(G4Step *CurrentStep, G4Track *track, const G4Step *step, + G4VProcess *process); + void SecondariesSplitting(G4Step *CurrentStep, G4Track *track, + const G4Step *step, G4VProcess *process); + + // Handling the remembered processes to replay + void RememberLastProcessInformation(G4Step *); + void CreateNewParticleAtTheLastVertex(G4Step *, G4Track *, const G4Step *, + G4String); + void ResetProcessesForEnteringParticles(G4Step *step); + void ClearRememberedTracksAndSteps(std::map<G4int, G4TrackVector>, + std::map<G4int, std::vector<G4Step *>>); + + // Edge case to handle the bias in annihilation + // FIXME : The triple annihilation is not handled for the moment + void PostponeFirstAnnihilationTrackIfInteraction(G4Step *step, + G4String processName); + void RegenerationOfPostponedAnnihilationTrack(G4Step *step); + void HandleTrackIDIfPostponedAnnihilation(G4Step *step); + G4bool IsParticleExitTheBiasedVolume(G4Step*step); +}; + +#endif diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingOldActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingOldActor.cpp new file mode 100644 index 000000000..fdb7a2f1e --- /dev/null +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingOldActor.cpp @@ -0,0 +1,848 @@ +// +// ******************************************************************** +// * 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 GateLastVertexInteractionSplittingActorOld.cc +/// \brief Implementation of the GateLastVertexInteractionSplittingActorOld class + +#include "GateHelpersDict.h" +#include "GateHelpersImage.h" + +#include "CLHEP/Units/SystemOfUnits.h" +#include "G4BiasingProcessInterface.hh" +#include "G4Gamma.hh" +#include "G4LogicalVolumeStore.hh" +#include "G4ParticleTable.hh" +#include "G4PhysicalVolumeStore.hh" +#include "G4Positron.hh" +#include "G4ProcessManager.hh" +#include "G4ProcessVector.hh" +#include "G4TrackStatus.hh" +#include "G4TrackingManager.hh" +#include "G4VParticleChange.hh" +#include "G4eplusAnnihilation.hh" +#include "GateLastVertexInteractionSplittingActorOld.h" +#include "GateLastVertexSplittingPostStepDoItOld.h" +#include "GateOptnComptSplitting.h" + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateLastVertexInteractionSplittingActorOld:: + GateLastVertexInteractionSplittingActorOld(py::dict &user_info) + : GateVActor(user_info, false) { + fMotherVolumeName = DictGetStr(user_info, "mother"); + fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); + fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); + fRussianRouletteForAngle = + DictGetBool(user_info, "russian_roulette_for_angle"); + fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); + fMaxTheta = DictGetDouble(user_info, "max_theta"); + fActions.insert("StartSimulationAction"); + fActions.insert("SteppingAction"); + fActions.insert("BeginOfEventAction"); + fActions.insert("BeginOfRunAction"); + fActions.insert("PreUserTrackingAction"); + fActions.insert("PostUserTrackingAction"); +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +G4double +GateLastVertexInteractionSplittingActorOld::RussianRouletteForAngleSurvival( + G4ThreeVector dir, G4ThreeVector vectorDirector, G4double maxTheta, + G4double split) { + G4double cosTheta = vectorDirector * dir; + G4double theta = std::acos(cosTheta); + G4double weightToApply = 1; + if (theta > fMaxTheta) { + G4double probability = G4UniformRand(); + if (probability <= 1 / split) { + weightToApply = split; + } else { + weightToApply = 0; + } + } + return weightToApply; +} + +G4Track *GateLastVertexInteractionSplittingActorOld::CreateComptonTrack( + G4ParticleChangeForGamma *gammaProcess, G4Track track, G4double weight) { + G4double energy = gammaProcess->GetProposedKineticEnergy(); + G4double globalTime = track.GetGlobalTime(); + G4double newGammaWeight = weight; + G4ThreeVector polarization = gammaProcess->GetProposedPolarization(); + const G4ThreeVector momentum = gammaProcess->GetProposedMomentumDirection(); + const G4ThreeVector position = track.GetPosition(); + G4Track *newTrack = new G4Track(track); + + newTrack->SetWeight(newGammaWeight); + newTrack->SetKineticEnergy(energy); + newTrack->SetMomentumDirection(momentum); + newTrack->SetPosition(position); + newTrack->SetPolarization(polarization); + return newTrack; +} + +void GateLastVertexInteractionSplittingActorOld::ComptonSplitting( + G4Step *CurrentStep, G4Track *track, const G4Step *step, + G4VProcess *process) { + // Loop on process and add the secondary tracks to the current step secondary + // vector + + G4TrackVector *trackVector = CurrentStep->GetfSecondary(); + G4double gammaWeight = 0; + + GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; + G4VParticleChange *processFinalState = emProcess->PostStepDoIt(*track, *step); + G4ParticleChangeForGamma *gammaProcessFinalState = + (G4ParticleChangeForGamma *)processFinalState; + const G4ThreeVector momentum = + gammaProcessFinalState->GetProposedMomentumDirection(); + gammaWeight = fWeightOfEnteringParticle / fSplittingFactor; + if (fRussianRouletteForAngle == true) { + G4double weightToApply = RussianRouletteForAngleSurvival( + momentum, fVectorDirector, fMaxTheta, fSplittingFactor); + if (weightToApply != 0) { + gammaWeight = gammaWeight * weightToApply; + G4Track *newTrack = + CreateComptonTrack(gammaProcessFinalState, *track, gammaWeight); + trackVector->push_back(newTrack); + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + for (int j = 0; j < NbOfSecondaries; j++) { + G4Track *newTrack = processFinalState->GetSecondary(j); + newTrack->SetWeight(gammaWeight); + trackVector->push_back(newTrack); + } + } + } + + else { + G4Track *newTrack = + CreateComptonTrack(gammaProcessFinalState, *track, gammaWeight); + trackVector->push_back(newTrack); + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + for (int j = 0; j < NbOfSecondaries; j++) { + G4Track *newTrack = processFinalState->GetSecondary(j); + newTrack->SetWeight(gammaWeight); + trackVector->push_back(newTrack); + } + } + processFinalState->Clear(); + gammaProcessFinalState->Clear(); +} + +void GateLastVertexInteractionSplittingActorOld::SecondariesSplitting( + G4Step *CurrentStep, G4Track *track, const G4Step *step, + G4VProcess *process) { + // Loop on process and add the secondary tracks to the current step secondary + // vector. + + // std::cout<<track->GetKineticEnergy()<<" + // "<<track->GetDynamicParticle()->GetKineticEnergy()<<" + // "<<track->GetMomentumDirection()<<" + // "<<track->GetDynamicParticle()->GetMomentumDirection()<<std::endl; + + G4String particleName = track->GetParticleDefinition()->GetParticleName(); + G4TrackVector *trackVector = CurrentStep->GetfSecondary(); + G4double gammaWeight = 0; + G4VParticleChange *processFinalState = nullptr; + if (process->GetProcessName() == "eBrem") { + GateBremPostStepDoIt *bremProcess = (GateBremPostStepDoIt *)process; + processFinalState = + bremProcess->GateBremPostStepDoIt::PostStepDoIt(*track, *step); + } else { + GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; + if (track->GetTrackStatus() == 0) { + // if (process->GetProcessName() == "annihil") + // std::cout<<"lol"<<std::endl; + processFinalState = emProcess->PostStepDoIt(*track, *step); + } + if (track->GetTrackStatus() == 1) { + GateplusannihilAtRestDoIt *eplusAnnihilProcess = + (GateplusannihilAtRestDoIt *)process; + + processFinalState = + eplusAnnihilProcess->GateplusannihilAtRestDoIt::AtRestDoIt(*track, + *step); + } + } + + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + + if (NbOfSecondaries > 0) { + gammaWeight = fWeightOfEnteringParticle / fSplittingFactor; + G4Track *newTrack = processFinalState->GetSecondary(0); + if ((fRussianRouletteForAngle == true) && (particleName == "gamma")) { + const G4ThreeVector momentum = newTrack->GetMomentumDirection(); + G4double weightToApply = RussianRouletteForAngleSurvival( + momentum, fVectorDirector, fMaxTheta, fSplittingFactor); + if (weightToApply != 0) { + gammaWeight = gammaWeight * weightToApply; + newTrack->SetWeight(gammaWeight); + trackVector->push_back(newTrack); + } + } else { + newTrack->SetWeight(gammaWeight); + trackVector->push_back(newTrack); + } + + for(int i = 1; i < NbOfSecondaries;i++){ + delete processFinalState->GetSecondary(i); + } + } else { + processFinalState->Clear(); + SecondariesSplitting(CurrentStep, track, step, process); + } + processFinalState->Clear(); +} + +void GateLastVertexInteractionSplittingActorOld::ClearRememberedTracksAndSteps( + std::map<G4int, G4TrackVector> rememberedTracks, + std::map<G4int, std::vector<G4Step *>> rememberedSteps) { + std::vector<G4Track *> trackToKill = {}; + for (auto it = rememberedTracks.begin(); it != rememberedTracks.end(); ++it) { + std::vector<G4Track *> vector = it->second; + for (auto it2 = vector.begin(); it2 != vector.end(); it2++) { + if ((std::find(trackToKill.begin(), trackToKill.end(), *it2) == + trackToKill.end())) { + // Method set pour clear les doublons au lieu de find. + trackToKill.push_back(*it2); + } + } + } + + for (auto it = trackToKill.begin(); it != trackToKill.end(); ++it) { + delete *it; + } + + std::vector<G4Step *> stepToKill = {}; + for (auto it = rememberedSteps.begin(); it != rememberedSteps.end(); ++it) { + std::vector<G4Step *> vector = it->second; + for (auto it2 = vector.begin(); it2 != vector.end(); it2++) { + if ((std::find(stepToKill.begin(), stepToKill.end(), *it2) == + stepToKill.end())) { + stepToKill.push_back(*it2); + } + } + } + + for (auto it = stepToKill.begin(); it != stepToKill.end(); ++it) { + delete *it; + } +} + +void GateLastVertexInteractionSplittingActorOld::RememberLastProcessInformation( + G4Step *step) { + + // When an interesting process to split occurs, we remember the status of the + // track and the process at this current step some informations regarding the + // track info have to be changed because they were update according to the + // interaction that occured These informations are stocked as a map object, + // binding the track ID with all the track objects and processes to split. + // Because in some cases, if a secondary was created before an interaction + // chain, this secondary will be track after the chain and without this + // association, we wll loose the information about the process occuring for + // this secondary. + + G4String creatorProcessName = "None"; + if (step->GetTrack()->GetCreatorProcess() != 0) + creatorProcessName = + step->GetTrack()->GetCreatorProcess()->GetProcessName(); + G4String processName = "None"; + G4int trackID = step->GetTrack()->GetTrackID(); + G4int parentID = step->GetTrack()->GetParentID(); + //<<processName<<" + //"<<step->GetTrack()->GetParticleDefinition()->GetParticleName()<<" + //"<<step->GetTrack()->GetTrackStatus()<<std::endl; + if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) { + processName = + step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); + } + if ((step->GetTrack()->GetParticleDefinition()->GetParticleName() == "e+") && + ((step->GetTrack()->GetTrackStatus() == 1) || + (step->GetTrack()->GetTrackStatus() == 2))) { + processName = "annihil"; + } + + if ((std::find(fListOfProcesses.begin(), fListOfProcesses.end(), + processName) != fListOfProcesses.end())) { + G4Track *trackInformation = new G4Track(*(step->GetTrack())); + G4Step *stepInformation = new G4Step(*(step)); + + G4StepPoint *stepPoint = nullptr; + stepPoint = step->GetPreStepPoint(); + + trackInformation->SetKineticEnergy(stepPoint->GetKineticEnergy()); + if ((processName == "eBrem") || ((processName == "annihil") && + step->GetTrack()->GetTrackStatus() == 1)) { + trackInformation->SetKineticEnergy(stepPoint->GetKineticEnergy() - + step->GetTotalEnergyDeposit()); + } + trackInformation->SetMomentumDirection(stepPoint->GetMomentumDirection()); + if ((processName == "annihil") && + ((step->GetTrack()->GetTrackStatus() == 1))) + trackInformation->SetTrackStatus(fStopButAlive); + else { + trackInformation->SetTrackStatus(fAlive); + } + trackInformation->SetPolarization(stepPoint->GetPolarization()); + // trackInformation->SetPosition(stepPoint->GetPosition()); + + if (auto search = fRememberedTracks.find(trackID); + search != fRememberedTracks.end()) { + fRememberedTracks[trackID].push_back(trackInformation); + fRememberedProcesses[trackID].push_back(processName); + fRememberedSteps[trackID].push_back(stepInformation); + } + + else { + fRememberedTracks[trackID] = {trackInformation}; + fRememberedProcesses[trackID] = {processName}; + fRememberedSteps[trackID] = {stepInformation}; + } + } + + else { + if (auto search = fRememberedTracks.find(trackID); + search == fRememberedTracks.end()) { + if (auto search = fRememberedTracks.find(parentID); + search != fRememberedTracks.end()) { + if (auto it = std::find(fRememberedProcesses[parentID].begin(), + fRememberedProcesses[parentID].end(), + creatorProcessName); + it != fRememberedProcesses[parentID].end()) { + auto idx = it - fRememberedProcesses[parentID].begin(); + fRememberedTracks[trackID] = { + new G4Track(*fRememberedTracks[parentID][idx])}; + fRememberedProcesses[trackID] = {fRememberedProcesses[parentID][idx]}; + fRememberedSteps[trackID] = { + new G4Step(*fRememberedSteps[parentID][idx])}; + } else { + fRememberedTracks[trackID] = { + new G4Track(*fRememberedTracks[parentID][0])}; + fRememberedProcesses[trackID] = {fRememberedProcesses[parentID][0]}; + fRememberedSteps[trackID] = { + new G4Step(*fRememberedSteps[parentID][0])}; + } + } + } + } +} + +void GateLastVertexInteractionSplittingActorOld::CreateNewParticleAtTheLastVertex( + G4Step *CurrentStep, G4Track *track, const G4Step *step, + G4String processName) { + // We retrieve the process associated to the process name to split and we + // split according the process. Since for compton scattering, the gamma is not + // a secondary particles, this one need to have his own splitting function. + G4ParticleDefinition *particleDefinition = track->GetDefinition(); + G4ProcessManager *processManager = particleDefinition->GetProcessManager(); + G4ProcessVector *processList = processManager->GetProcessList(); + + G4VProcess *processToSplit = nullptr; + for (size_t i = 0; i < processList->size(); ++i) { + auto process = (*processList)[i]; + if (process->GetProcessName() == processName) { + processToSplit = process; + } + } + if (processName == "compt") { + + ComptonSplitting(CurrentStep, track, step, processToSplit); + } + + else { + SecondariesSplitting(CurrentStep, track, step, processToSplit); + } +} + +void GateLastVertexInteractionSplittingActorOld:: + ResetProcessesForEnteringParticles(G4Step *step) { + + // This function reset the processes and track registered to be split each + // time a particle enters into a volume. Here a new particle incoming is + // either a particle entering into the volume ( first pre step is a boundary + // of a mother volume or first step trigger the actor and the vertex is not + // either the mother volume or a the fdaughter volume) or an event generated + // within the volume (the particle did not enter into the volume and its + // parentID = 0). An additionnal condition is set with the trackID to ensure + // particles crossing twice the volume (after either a compton or pair prod) + // are not splitted. + + G4int trackID = step->GetTrack()->GetTrackID(); + if ((fEventIDOfInitialSplittedTrack != fEventID) || + ((fEventIDOfInitialSplittedTrack == fEventID) && + (trackID < fTrackIDOfInitialTrack))) { + G4String logicalVolumeNamePreStep = "None"; + if (step->GetPreStepPoint()->GetPhysicalVolume() != 0) { + logicalVolumeNamePreStep = step->GetPreStepPoint() + ->GetPhysicalVolume() + ->GetLogicalVolume() + ->GetName(); + } + if (((step->GetPreStepPoint()->GetStepStatus() == 1) && + (logicalVolumeNamePreStep == fMotherVolumeName)) || + ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != + logicalVolumeNamePreStep) && + (step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != + fMotherVolumeName))) { + fTrackIDOfInitialTrack = trackID; + fEventIDOfInitialSplittedTrack = fEventID; + fWeightOfEnteringParticle = step->GetTrack()->GetWeight(); + ClearRememberedTracksAndSteps(fRememberedTracks, fRememberedSteps); + fRememberedProcesses.clear(); + fRememberedTracks.clear(); + fRememberedSteps.clear(); + } else if (step->GetTrack()->GetParentID() == 0) { + fTrackIDOfInitialTrack = trackID; + fEventIDOfInitialSplittedTrack = fEventID; + fWeightOfEnteringParticle = step->GetTrack()->GetWeight(); + ClearRememberedTracksAndSteps(fRememberedTracks, fRememberedSteps); + fRememberedProcesses.clear(); + fRememberedTracks.clear(); + fRememberedSteps.clear(); + } + } +} + +void GateLastVertexInteractionSplittingActorOld:: + PostponeFirstAnnihilationTrackIfInteraction(G4Step *step, + G4String processName) { + // In case the first gamma issued from annihilation undergoes an interaction, + // in order to not bias the process We keep in memory the particle post step + // state (with its secondaries) and kill the particle and its secondaries. If + // the second photon from annihilation exiting the collimation system with an + // interaction or is absorbed within the collimation, the particle is + // subsequently resimulated, starting from the interaction point. + + G4int trackID = step->GetTrack()->GetTrackID(); + if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), + processName) != fListOfProcesses.end()) { + fSuspendForAnnihil = true; + G4Track trackToPostpone = G4Track(*(step->GetTrack())); + trackToPostpone.SetKineticEnergy( + step->GetPostStepPoint()->GetKineticEnergy()); + trackToPostpone.SetMomentumDirection( + step->GetPostStepPoint()->GetMomentumDirection()); + trackToPostpone.SetTrackStatus(step->GetTrack()->GetTrackStatus()); + trackToPostpone.SetPolarization( + step->GetPostStepPoint()->GetPolarization()); + trackToPostpone.SetPosition(step->GetPostStepPoint()->GetPosition()); + trackToPostpone.SetTrackID(trackID); + fTracksToPostpone.push_back(trackToPostpone); + auto theTrack = fTracksToPostpone[0]; + + auto secVec = step->GetfSecondary(); + for (int i = 0; i < secVec->size(); i++) { + G4Track *sec = (*secVec)[i]; + G4Track copySec = G4Track((*sec)); + fTracksToPostpone.push_back(copySec); + } + + fTracksToPostpone[0].SetTrackID(trackID); + step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); + } +} + +void GateLastVertexInteractionSplittingActorOld:: + RegenerationOfPostponedAnnihilationTrack(G4Step *step) { + + // If the second photon from annihilation suceed to exit the collimation + // system with at least one interaction or was absorbed. Resimulation of + // annihilation photons and its potential secondaries. + + G4TrackVector *currentSecondaries = step->GetfSecondary(); + for (int i = 0; i < fTracksToPostpone.size(); i++) { + G4Track *trackToAdd = + new G4Track(fTracksToPostpone[fTracksToPostpone.size() - 1 - i]); + trackToAdd->SetParentID(step->GetTrack()->GetTrackID() - 1); + + currentSecondaries->insert(currentSecondaries->begin(), trackToAdd); + } + G4Track *firstPostponedTrack = (*currentSecondaries)[0]; + + // Handle of case were the interaction killed the photon issued from + // annihilation, it will not be track at the following state and the boolean + // plus the track vector need to be reset + + if (firstPostponedTrack->GetTrackStatus() == 2) { + fSuspendForAnnihil = false; + fTracksToPostpone.clear(); + } +} + +void GateLastVertexInteractionSplittingActorOld:: + HandleTrackIDIfPostponedAnnihilation(G4Step *step) { + // The ID is to modify trackID and the processes and tracks sotcked for a + // specific trackID associated to the postponed annihilation to respect the + // trackID order in GEANT4. Since in this case the second photon is tracked + // before the first one his trackID +=1. For the postponed one, he is the + // first secondary particle of the second photon tracks, his trackID is + // therefore equal to the second photon trackID +1 instead of the second + // photon trackID -1. The parentID of secondary particles are also modified + // because they are used in the rememberlastprocess function At last the value + // associated to a specific trackID in the process and track map are modified + // according to the new trackID. + + if (fSuspendForAnnihil) { + if (step->GetTrack()->GetTrackID() == + fTracksToPostpone[0].GetTrackID() - 1) { + fRememberedProcesses[step->GetTrack()->GetTrackID()] = + fRememberedProcesses[step->GetTrack()->GetTrackID() + 1]; + fRememberedProcesses.erase(step->GetTrack()->GetTrackID() + 1); + fRememberedSteps[step->GetTrack()->GetTrackID()] = + fRememberedSteps[step->GetTrack()->GetTrackID() + 1]; + fRememberedSteps.erase(step->GetTrack()->GetTrackID() + 1); + fRememberedTracks[step->GetTrack()->GetTrackID()] = + fRememberedTracks[step->GetTrack()->GetTrackID() + 1]; + fRememberedTracks.erase(step->GetTrack()->GetTrackID() + 1); + auto vecSec = step->GetSecondary(); + for (int i = 0; i < vecSec->size(); i++) { + (*vecSec)[i]->SetParentID(step->GetTrack()->GetTrackID() + 1); + } + step->GetTrack()->SetTrackID(step->GetTrack()->GetTrackID() + 1); + } + if (step->GetTrack()->GetTrackID() == + fTracksToPostpone[0].GetTrackID() + 1) { + auto vecSec = step->GetSecondary(); + for (int i = 0; i < vecSec->size(); i++) { + (*vecSec)[i]->SetParentID(step->GetTrack()->GetTrackID() - 2); + } + step->GetTrack()->SetTrackID(step->GetTrack()->GetTrackID() - 2); + } + } +} + +void GateLastVertexInteractionSplittingActorOld::BeginOfRunAction( + const G4Run *run) { + + // The way to behave of the russian roulette is the following : + // we provide a vector director and the theta angle acceptance, where theta = + // 0 is a vector colinear to the vector director Then if the track generated + // is on the acceptance angle, we add it to the primary track, and if it's not + // the case, we launch the russian roulette + + if (fRotationVectorDirector) { + G4VPhysicalVolume *physBiasingVolume = + G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + auto rot = physBiasingVolume->GetObjectRotationValue(); + fVectorDirector = rot * fVectorDirector; + } +} + +void GateLastVertexInteractionSplittingActorOld::BeginOfEventAction( + const G4Event *event) { + + fParentID = -1; + fEventID = event->GetEventID(); + fEventIDOfSplittedTrack = -1; + fTrackIDOfSplittedTrack = -1; + fNotSplitted == true; +} + +void GateLastVertexInteractionSplittingActorOld::PreUserTrackingAction( + const G4Track *track) { + fIsFirstStep = true; +} + +void GateLastVertexInteractionSplittingActorOld::SteppingAction(G4Step *step) { + + G4String particleName = + step->GetTrack()->GetParticleDefinition()->GetParticleName(); + G4String creatorProcessName = "None"; + + if (step->GetTrack()->GetCreatorProcess() != 0) + creatorProcessName = + step->GetTrack()->GetCreatorProcess()->GetProcessName(); + + // std::cout<<particleName<<" "<<fEventID<<" + // "<<step->GetTotalEnergyDeposit()<<" + // "<<step->GetPreStepPoint()->GetKineticEnergy()<<" + // "<<step->GetPreStepPoint()->GetMomentumDirection()<<step->GetTrack()->GetTrackID()<<" + // "<<step->GetTrack()->GetWeight()<<" "<<step->GetTrack()->GetParentID()<<" + // "<<fTrackIDOfSplittedTrack<<std::endl;; + + if (fIsFirstStep) { + ResetProcessesForEnteringParticles(step); + } + + G4int trackID = step->GetTrack()->GetTrackID(); + + if ((step->GetTrack()->GetWeight() < fWeightOfEnteringParticle) && + (fNotSplitted == false)) { + + G4String logicalVolumeNamePostStep = "None"; + if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) + logicalVolumeNamePostStep = step->GetPostStepPoint() + ->GetPhysicalVolume() + ->GetLogicalVolume() + ->GetName(); + + G4String processName = "None"; + if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) + processName = + step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); + + if (particleName != "e+"){ + if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), processName) != fListOfProcesses.end()){ + step->GetTrack()->SetTrackStatus(fStopAndKill); + for (int i = 0; i < step->GetfSecondary()->size(); i++){ + G4Track* track =(*(step->GetfSecondary()))[i]; + track->SetTrackStatus(fStopAndKill); + } + } + } + + + if ((particleName == "e+") && ((step->GetTrack()->GetTrackStatus() == 1)||(step->GetTrack()->GetTrackStatus() == 2))){ + step->GetTrack()->SetTrackStatus(fStopAndKill); + for (int i = 0; i < step->GetfSecondary()->size(); i++){ + G4Track* track = (*(step->GetfSecondary()))[i]; + track->SetTrackStatus(fStopAndKill); + } + } + + if ((step->GetTrack()->GetTrackStatus() == 2) || + (step->GetTrack()->GetTrackStatus() == 3)) { + + /* + delete fTrackToSplit; + delete fStepToSplit; + fTrackToSplit = new + G4Track(*fRememberedTracks[fTrackIDOfSplittedTrack].back()); fStepToSplit + = new G4Step(*fRememberedSteps[fTrackIDOfSplittedTrack].back()); + */ + + CreateNewParticleAtTheLastVertex(step, fTrackToSplit, fStepToSplit, + fProcessToSplit); + } + + if (((step->GetTrack()->GetTrackStatus() != 2) && + (step->GetTrack()->GetTrackStatus() != 3)) && + (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), + logicalVolumeNamePostStep) != fListOfVolumeAncestor.end())) { + + if (fSplitCounter < fWeightOfEnteringParticle) { + /* + delete fTrackToSplit; + delete fStepToSplit; + fTrackToSplit = new + G4Track(*fRememberedTracks[fTrackIDOfSplittedTrack].back()); + fStepToSplit = new + G4Step(*fRememberedSteps[fTrackIDOfSplittedTrack].back()); + */ + CreateNewParticleAtTheLastVertex(step, fTrackToSplit, fStepToSplit, + fProcessToSplit); + // std::cout<<fTrackToSplit<<" "<<fStepToSplit<<std::endl; + G4TrackVector *trackVector = step->GetfSecondary(); + G4Track *theTrack = trackVector->back(); + fSplitCounter += theTrack->GetWeight(); + // std::cout<<theTrack->GetWeight()<<std::endl; + if (fSplitCounter >= fWeightOfEnteringParticle) { + delete fTrackToSplit; + delete fStepToSplit; + fTrackToSplit = nullptr; + fStepToSplit = nullptr; + fSplitCounter = 0; + fNotSplitted = true; + fProcessToSplit = "None"; + theTrack->SetTrackStatus(fStopAndKill); + } + } + } + } + + if (step->GetTrack()->GetWeight() == fWeightOfEnteringParticle) { + // std::cout<<particleName<<" + // "<<step->GetTrack()->GetTrackStatus()<<std::endl; + RememberLastProcessInformation(step); + // std::cout<<particleName<<" + // "<<step->GetTrack()->GetTrackStatus()<<std::endl; + G4String process = "None"; + if (auto search = fRememberedProcesses.find(trackID); + search != fRememberedProcesses.end()) { + process = fRememberedProcesses[trackID].back(); + } + // std::cout<<step->GetTrack()->GetVertexPosition()<<" + // "<<step->GetTrack()->GetParticleDefinition()->GetParticleName()<<" + // "<<fSuspendForAnnihil<<" "<<process<<" + // "<<step->GetTrack()->GetWeight()<<" "<<step->GetTrack()->GetParentID()<<" + // "<<step->GetTrack()->GetTrackID()<<" "<<creatorProcessName<<" + // "<<step->GetTrack()->GetTrackStatus()<<" + // "<<step->GetPreStepPoint()->GetKineticEnergy()<<" " + // <<step->GetPostStepPoint()->GetKineticEnergy()<<" + // "<<step->GetPreStepPoint()->GetPosition()<<" + // "<<step->GetPostStepPoint()->GetPosition()<<std::endl; + + /* + if ((!fSuspendForAnnihil) && (creatorProcessName == "annihil") && + (fTrackIDOfSplittedTrack == step->GetTrack()->GetParentID()) && (fEventID == + fEventIDOfSplittedTrack)) + { + step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); + } + */ + + if ((particleName == "e-") && + (step->GetTrack()->GetWeight() == fWeightOfEnteringParticle) && + (creatorProcessName == "conv") && + (fTrackIDOfSplittedTrack == step->GetTrack()->GetParentID()) && + (fEventID == fEventIDOfSplittedTrack)) { + step->GetTrack()->SetTrackStatus(fStopAndKill); + } + + /* + if ((!fSuspendForAnnihil) && (process != "annihil") && (creatorProcessName + == "annihil") && (step->GetTrack()->GetTrackStatus() != 3)) + { + G4int parentID = step->GetTrack()->GetParentID(); + if (auto search = fRememberedProcesses.find(parentID); search != + fRememberedProcesses.end()) + { + if (fRememberedProcesses[trackID].back() == "annihil") + PostponeFirstAnnihilationTrackIfInteraction(step, process); + } + } + + // If the first annihilation photon exit the collimation and the process to + split is annihilation + // We kill the second photon, because the annihilation will generate both + the photons. + + if (fSuspendForAnnihil) + { + if (trackID == fTracksToPostpone[0].GetTrackID() - 1) + { + fSuspendForAnnihil = false; + fTracksToPostpone.clear(); + } + } + */ + + G4String logicalVolumeNamePostStep = "None"; + G4ThreeVector particleMomentum = step->GetPostStepPoint()->GetMomentumDirection(); + if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) + logicalVolumeNamePostStep = step->GetPostStepPoint() + ->GetPhysicalVolume() + ->GetLogicalVolume() + ->GetName(); + + if (((step->GetTrack()->GetTrackStatus() != 2) && + (step->GetTrack()->GetTrackStatus() != 3)) && + (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), + logicalVolumeNamePostStep) != fListOfVolumeAncestor.end())&& (particleMomentum[2] < 0)) { + + if (auto search = fRememberedProcesses.find(trackID); + search != fRememberedProcesses.end()) { + + fProcessToSplit = fRememberedProcesses[trackID].back(); + fTrackToSplit = new G4Track(*fRememberedTracks[trackID].back()); + fStepToSplit = new G4Step(*fRememberedSteps[trackID].back()); + } + + if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), + fProcessToSplit) != fListOfProcesses.end()) { + + fTrackIDOfSplittedTrack = trackID; + fEventIDOfSplittedTrack = fEventID; + fTrackIDOfInitialSplittedTrack = fTrackIDOfInitialTrack; + + // Handle of pecularities (1): + + // If the process t split is the gamma issued from compton interaction, + // the electron primary generated have to be killed given that electron + // will be regenerated + + if ((fProcessToSplit == "compt") && (particleName == "gamma")) { + auto secondaries = step->GetfSecondary(); + if (secondaries->size() > 0) { + G4Track *lastSecTrack = secondaries->back(); + lastSecTrack->SetTrackStatus(fStopAndKill); + } + } + + // Handle of pecularities (2): + + // If the process to split is the annihilation, the second photon, + // postponed or not, have to be killed the reset of the postpone is + // performed here, whereas the kill of the next annihilation photon, if + // not postponed is realised at the beginning of the step tracking. + /* + if (fProcessToSplit == "annihil") + { + if (fSuspendForAnnihil) + { + fSuspendForAnnihil = false; + fTracksToPostpone.clear(); + } + } + */ + // Handle of pecularities 3 : If the positron which created one or more + // brem photons exits all the brems photons will be killed before their + // tracking, and the conv processes will then be replayed + + if ((particleName == "e+") && (fProcessToSplit != "None")) { + + G4int parentID = step->GetTrack()->GetParentID(); + fProcessToSplit = fRememberedProcesses[parentID].back(); + delete fTrackToSplit; + delete fStepToSplit; + fTrackToSplit = new G4Track(*fRememberedTracks[parentID].back()); + fStepToSplit = new G4Step(*fRememberedSteps[parentID].back()); + fTrackIDOfInitialTrack = parentID; + + auto *vecSecondaries = step->GetfSecondary(); + vecSecondaries->clear(); + } + if (!((fProcessToSplit == "eBrem") && (particleName == "e-"))) { + CreateNewParticleAtTheLastVertex(step, fTrackToSplit, fStepToSplit, + fProcessToSplit); + fNotSplitted = false; + } + step->GetTrack()->SetTrackStatus(fStopAndKill); + } + } + } + + /* + if ((fSuspendForAnnihil) && ((step->GetTrack()->GetTrackStatus() == 1) || + (step->GetTrack()->GetTrackStatus() == 2))) + { + if (trackID == fTracksToPostpone[0].GetTrackID()) + { + RegenerationOfPostponedAnnihilationTrack(step); + } + } + */ + + fIsFirstStep = false; +} + +void GateLastVertexInteractionSplittingActorOld::PostUserTrackingAction( + const G4Track *track) {} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateLastVertexSource.cpp b/core/opengate_core/opengate_lib/GateLastVertexSource.cpp new file mode 100644 index 000000000..e89c5a728 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateLastVertexSource.cpp @@ -0,0 +1,112 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include "GateLastVertexSource.h" +#include "G4ParticleTable.hh" +#include "GateHelpersDict.h" +#include <G4UnitsTable.hh> + +GateLastVertexSource::GateLastVertexSource() : GateVSource() { +} + +GateLastVertexSource::~GateLastVertexSource() { +} + +void GateLastVertexSource::InitializeUserInfo(py::dict &user_info) { + GateVSource::InitializeUserInfo(user_info); + // get user info about activity or nb of events + fN = DictGetInt(user_info, "n"); + } + +double GateLastVertexSource::PrepareNextTime(double current_simulation_time) { + + /* + // If all N events have been generated, we stop (negative time) + if (fNumberOfGeneratedEvents >= fN){ + std::cout << "LV: "<< -1<<" "<< fNumberOfGeneratedEvents <<" "<< fN<<std::endl; + return -1; + } + + if (fListOfContainer.size()==0){ + std::cout << "LV: "<< 1<<" "<<fNumberOfGeneratedEvents <<" "<< fN<<std::endl; + return 1; + } + // Else we consider all event with a timestamp equal to the simulation + // StartTime + std::cout << "LV: "<< fStartTime<<" "<<fNumberOfGeneratedEvents <<" "<< fN<<std::endl; + return fStartTime; + */ + if (fNumberOfGeneratedEvents >= fN) + return -1; + + return fStartTime + 1; +} + +void GateLastVertexSource::PrepareNextRun() { + // The following compute the global transformation from + // the local volume (mother) to the world + GateVSource::PrepareNextRun(); + + // The global transformation to apply for the current RUN is known in : + // fGlobalTranslation & fGlobalRotation + + // init the number of generated events (here, for each run) + fNumberOfGeneratedEvents = 0; + +} + +void GateLastVertexSource::GenerateOnePrimary(G4Event* event, double current_simulation_time, G4int idx ){ + + if (fNumberOfGeneratedEvents >= fN){ + auto *particle_table = G4ParticleTable::GetParticleTable(); + auto *fParticleDefinition = particle_table->FindParticle("geantino"); + auto *particle = new G4PrimaryParticle(fParticleDefinition); + particle->SetKineticEnergy(0); + particle->SetMomentumDirection({1,0,0}); + particle->SetWeight(1); + auto *vertex = new G4PrimaryVertex({0,0,0}, current_simulation_time); + vertex->SetPrimary(particle); + event->AddPrimaryVertex(vertex); + } + else { + + + G4double energy = fListOfContainer[idx].GetEnergy(); + if (energy < 0){ + energy = 0; + } + fContainer = fListOfContainer[idx]; + G4ThreeVector position = fListOfContainer[idx].GetVertexPosition(); + G4ThreeVector momentum = fListOfContainer[idx].GetMomentum(); + G4String particleName = fListOfContainer[idx].GetParticleNameToSplit(); + G4double weight =fListOfContainer[idx].GetWeight(); + fProcessToSplit = fListOfContainer[idx].GetProcessNameToSplit(); + + auto &l = fThreadLocalData.Get(); + auto *particle_table = G4ParticleTable::GetParticleTable(); + auto *fParticleDefinition = particle_table->FindParticle(particleName); + auto *particle = new G4PrimaryParticle(fParticleDefinition); + particle->SetKineticEnergy(energy); + particle->SetMomentumDirection(momentum); + particle->SetWeight(weight); + auto *vertex = new G4PrimaryVertex(position, current_simulation_time); + vertex->SetPrimary(particle); + event->AddPrimaryVertex(vertex); + } + +} + + +void GateLastVertexSource::GeneratePrimaries(G4Event *event, + double current_simulation_time) { + + GenerateOnePrimary(event,current_simulation_time,fNumberOfGeneratedEvents); + fNumberOfGeneratedEvents++; + if (fNumberOfGeneratedEvents == fListOfContainer.size()){ + fListOfContainer.clear(); + } +} diff --git a/core/opengate_core/opengate_lib/GateLastVertexSource.h b/core/opengate_core/opengate_lib/GateLastVertexSource.h new file mode 100644 index 000000000..dd23c124d --- /dev/null +++ b/core/opengate_core/opengate_lib/GateLastVertexSource.h @@ -0,0 +1,85 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#ifndef GateLastVertexSource_h +#define GateLastVertexSource_h + +#include "GateAcceptanceAngleTesterManager.h" +#include "GateSingleParticleSource.h" +#include "GateVSource.h" +#include "GateLastVertexSplittingDataContainer.h" +#include <pybind11/stl.h> + +namespace py = pybind11; + +/* + This is NOT a real source type but a template to help writing your own source + type. Copy-paste this file with a different name ("MyNewSource.hh") and start + building. You also need to copy : GateLastVertexSource.hh GateLastVertexSource.cpp + pyGateLastVertexSource.cpp + And add the source declaration in opengate_core.cpp + */ + +class GateLastVertexSource : public GateVSource { + +public: + GateLastVertexSource(); + + ~GateLastVertexSource() override; + + void InitializeUserInfo(py::dict &user_info) override; + + double PrepareNextTime(double current_simulation_time) override; + + void PrepareNextRun() override; + + void GeneratePrimaries(G4Event *event, double time) override; + + + void GenerateOnePrimary(G4Event *event, double time,G4int idx); + + + void SetListOfVertexToSimulate(std::vector<LastVertexDataContainer> list){ + fListOfContainer = list; + } + + void SetNumberOfGeneratedEvent(G4int nbEvent){ + fNumberOfGeneratedEvents = nbEvent; + } + + void SetNumberOfEventToSimulate(G4int N){ + fN = N; + } + + G4int GetNumberOfEventToSimulate(){ + return fN; + } + + G4int GetNumberOfGeneratedEvent(){ + return fNumberOfGeneratedEvents; + } + + G4String GetProcessToSplit(){ + return fProcessToSplit; + } + + LastVertexDataContainer GetLastVertexContainer(){ + return fContainer; + } + + +protected: + G4int fNumberOfGeneratedEvents = 0; + G4int fN = 0; + double fFloatValue; + std::vector<double> fVectorValue; + std::vector<LastVertexDataContainer> fListOfContainer; + G4String fProcessToSplit = "None"; + LastVertexDataContainer fContainer; +}; + +#endif // GateLastVertexSource_h diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h new file mode 100644 index 000000000..112cc8062 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h @@ -0,0 +1,343 @@ +// +// ******************************************************************** +// * 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. * +// ******************************************************************** +// +/// +#ifndef LastVertexDataContainer_h +#define LastVertexDataContainer_h + + +#include <iostream> +#include "G4VEnergyLossProcess.hh" +#include "G4Track.hh" +#include "G4VEmProcess.hh" +#include "G4VParticleChange.hh" +#include "G4eplusAnnihilation.hh" +#include "G4PhysicalConstants.hh" +#include "G4MaterialCutsCouple.hh" +#include "G4Gamma.hh" +#include "G4Electron.hh" +#include "G4Positron.hh" +#include "G4eeToTwoGammaModel.hh" +#include "G4EmBiasingManager.hh" +#include "G4EntanglementAuxInfo.hh" +#include "G4eplusAnnihilationEntanglementClipBoard.hh" +#include "G4EmParameters.hh" +#include "G4PhysicsModelCatalog.hh" + + +class LastVertexDataContainer{ + +public : + +LastVertexDataContainer(G4ThreeVector interactionPosition, G4ThreeVector momentum,G4ThreeVector polarization, G4double energy,G4int trackID,G4String creationProcessName){ + + fPositionToSplit= interactionPosition; + fMomentumToSplit = momentum; + fEnergyToSplit = energy; + fIsExit = false; + fToRegenerate = false; + fTrackID = trackID; + fCreationProcessName = creationProcessName; + fPolarizationToSplit = polarization; + +} + + +LastVertexDataContainer(){} + +~LastVertexDataContainer(){} + +void SetProcessNameToSplit(G4String processName){ + fProcessNameToSplit = processName; +} + +G4String GetProcessNameToSplit(){ + return fProcessNameToSplit; +} + +void SetEnergy(G4double energy){ + fEnergyToSplit =energy; +} + +G4double GetEnergy(){ + return fEnergyToSplit; +} + + +void SetWeight(G4double weight){ + fWeightToSplit =weight; +} + +G4double GetWeight(){ + return fWeightToSplit; +} + +void SetPolarization(G4ThreeVector polarization){ + fPolarizationToSplit = polarization; +} + +G4ThreeVector GetPolarization(){ + return fPolarizationToSplit; +} + +void SetMomentum(G4ThreeVector momentum){ + fMomentumToSplit =momentum; +} + +G4ThreeVector GetMomentum(){ + return fMomentumToSplit; +} + +void SetVertexPosition(G4ThreeVector position){ + fPositionToSplit = position; +} + +G4ThreeVector GetVertexPosition(){ + return fPositionToSplit; +} + +void SetExitingStatus(G4bool isExit){ + fIsExit = isExit; +} + +G4bool GetExitingStatus(){ + return fIsExit; +} + + +void SetRegenerationStatus(G4bool toRegenerate){ + fToRegenerate= toRegenerate; +} + +G4bool GetRegenerationStatus(){ + return fToRegenerate; +} + + + +void SetTrackID(G4int trackID ){ + fTrackID = trackID; +} + +G4int GetTrackID(){ + return fTrackID; +} + + +void SetParticleName(G4String name){ + fParticleName = name; +} + +G4String GetParticleName(){ + return fParticleName; +} + +void SetParticleNameToSplit(G4String name){ + fParticleNameToSplit = name; +} + +G4String GetParticleNameToSplit(){ + return fParticleNameToSplit; +} + + +void SetCreationProcessName(G4String creationProcessName){ + fCreationProcessName = creationProcessName; +} + +G4String GetCreationProcessName(){ + return fCreationProcessName; +} + + + +void SetTrackStatus(G4int trackStatus){ + fTrackStatusToSplit = trackStatus; +} + + +G4int GetTrackStatus(){ + return fTrackStatusToSplit; +} + + +void SetNbOfSecondaries(G4int nbSec){ + fNumberOfSecondariesToSplit = nbSec; +} + +G4int GetNbOfSecondaries(){ + return fNumberOfSecondariesToSplit; +} + +void SetAnnihilationFlag(G4String flag){ + fAnnihilProcessFlag = flag; +} + +G4String GetAnnihilationFlag(){ + return fAnnihilProcessFlag; +} + +void SetStepLength(G4double length){ + fStepLength = length; +} + +G4double GetStepLength(){ + return fStepLength; +} + + + +void SetSplittingParameters(G4String processName,G4double energy,G4ThreeVector momentum, G4ThreeVector position,G4ThreeVector polarization,G4String name,G4double weight,G4int trackStatus,G4int nbSec,G4String flag,G4double length, G4ThreeVector prePos){ + fProcessNameToSplit = processName; + fEnergyToSplit = energy; + fMomentumToSplit = momentum; + fPositionToSplit = position; + fPolarizationToSplit = polarization; + fParticleNameToSplit = name; + fWeightToSplit = weight; + fTrackStatusToSplit = trackStatus; + fNumberOfSecondariesToSplit = nbSec; + fAnnihilProcessFlag = flag; + fStepLength = length; + fPrePosition = prePos; +} + + + + + +void PushListOfSplittingParameters(G4String processName,G4double energy,G4ThreeVector momentum, G4ThreeVector position,G4ThreeVector polarization,G4String name,G4double weight, G4int trackStatus,G4int nbSec, G4String flag,G4double length,G4ThreeVector prePos){ + fVectorOfProcessToSplit.push_back(processName); + fVectorOfEnergyToSplit.push_back(energy); + fVectorOfMomentumToSplit.push_back(momentum); + fVectorOfPositionToSplit.push_back(position); + fVectorOfPolarizationToSplit.push_back(polarization); + fVectorOfParticleNameToSplit.push_back(name); + fVectorOfWeightToSplit.push_back(weight); + fVectorOfTrackStatusToSplit.push_back(trackStatus); + fVectorOfNumberOfSecondariesToSplit.push_back(nbSec); + fVectorOfAnnihilProcessFlag.push_back(flag); + fVectorOfStepLength.push_back(length); + fVectorOfPrePosition.push_back(prePos); + +} + + +LastVertexDataContainer ContainerFromParentInformation(G4Step* step){ + LastVertexDataContainer aContainer = LastVertexDataContainer(); + aContainer.fTrackID = step->GetTrack()->GetTrackID(); + aContainer.fParticleName = step->GetTrack()->GetDefinition()->GetParticleName(); + if (this->fProcessNameToSplit != "None"){ + if (this->fVectorOfProcessToSplit.size() !=0){ + G4ThreeVector vertexPosition = step->GetTrack()->GetVertexPosition(); + for (int i =0;i<this->fVectorOfPositionToSplit.size();i++){ + if (vertexPosition == this->fVectorOfPositionToSplit[i]){ + aContainer.SetSplittingParameters(this->fVectorOfProcessToSplit[i],this->fVectorOfEnergyToSplit[i],this->fVectorOfMomentumToSplit[i],this->fVectorOfPositionToSplit[i],this->fVectorOfPolarizationToSplit[i],this->fVectorOfParticleNameToSplit[i],this->fVectorOfWeightToSplit[i], this->fVectorOfTrackStatusToSplit[i], this->fVectorOfNumberOfSecondariesToSplit[i], this->fVectorOfAnnihilProcessFlag[i],this->fVectorOfStepLength[i],this->fVectorOfPrePosition[i]); + return aContainer; + } + } + } + else{ + aContainer.SetSplittingParameters(this->fProcessNameToSplit,this->fEnergyToSplit,this->fMomentumToSplit,this->fPositionToSplit,this->fPolarizationToSplit,this->fParticleNameToSplit,this->fWeightToSplit, this->fTrackStatusToSplit, this->fNumberOfSecondariesToSplit, this->fAnnihilProcessFlag,this->fStepLength,this->fPrePosition); + return aContainer; + } + } + return aContainer; +} + + + + + + + +void DumpInfoToSplit(){ + std::cout<<"Particle name of the particle to split: "<<fParticleNameToSplit<<std::endl; + std::cout<<"Kinetic Energy of the particle to split: "<<fEnergyToSplit<<std::endl; + std::cout<<"Momentum of the particle to split: "<<fMomentumToSplit<<std::endl; + std::cout<<"Initial position of the particle to split: "<<fPositionToSplit<<std::endl; + std::cout<<"ProcessNameToSplit: "<<fProcessNameToSplit<<std::endl; + std::cout<<"trackID of current particle: "<<fTrackID<<std::endl; + std::cout<<"Exiting status of current particle: "<<fIsExit<<std::endl; + std::cout<<"Regeneration status of current particle: "<<fToRegenerate<<std::endl; + std::cout<<"ParticleName of current particle: "<<fParticleName<<std::endl; + std::cout<<" "<<std::endl; + +} + + +friend std::ostream& operator<<(std::ostream& os, const LastVertexDataContainer& container) { + os <<container.fParticleName<<" ID: "<<container.fTrackID<< "process: "<< container.fCreationProcessName; + return os; +} + + + + +private : + +G4String fParticleName="None"; +G4String fParticleNameToSplit="None"; +G4bool fIsExit =false; +G4bool fToRegenerate = false; +G4int fTrackID = 0; +G4String fCreationProcessName ="None"; +G4String fProcessNameToSplit ="None"; +G4double fEnergyToSplit = 0; +G4ThreeVector fMomentumToSplit; +G4ThreeVector fPositionToSplit; +G4ThreeVector fPolarizationToSplit; +G4double fWeightToSplit; +G4int fTrackStatusToSplit; +G4int fNumberOfSecondariesToSplit; +G4String fAnnihilProcessFlag; +G4double fStepLength; +G4ThreeVector fPrePosition; + +std::vector<G4ThreeVector> fVectorOfMomentumToSplit; +std::vector<G4ThreeVector> fVectorOfPositionToSplit; +std::vector<G4ThreeVector> fVectorOfPolarizationToSplit; +std::vector<G4double> fVectorOfEnergyToSplit; +std::vector<G4String> fVectorOfProcessToSplit; +std::vector<G4String> fVectorOfParticleNameToSplit; +std::vector<G4double> fVectorOfWeightToSplit; +std::vector<G4int>fVectorOfTrackStatusToSplit; +std::vector<G4int>fVectorOfNumberOfSecondariesToSplit; +std::vector<G4String>fVectorOfAnnihilProcessFlag; +std::vector<G4double>fVectorOfStepLength; +std::vector<G4ThreeVector>fVectorOfPrePosition; + + + + +}; + +#endif + + + + + + \ No newline at end of file diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h index 30e5b93c2..40b3fdbb4 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h @@ -63,7 +63,6 @@ virtual G4VParticleChange * PostStepDoIt (const G4Track & track, const G4Step & return particleChange; } - }; diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoItOld.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoItOld.h new file mode 100644 index 000000000..df30a39d7 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoItOld.h @@ -0,0 +1,110 @@ +// +// ******************************************************************** +// * 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. * +// ******************************************************************** +// +/// +#ifndef GateLastVertexSplittingPostStepDoItOld_h +#define GateLastVertexSplittingPostStepDoItOld_h + + +#include "G4VEnergyLossProcess.hh" +#include "G4VEmProcess.hh" +#include "G4VParticleChange.hh" +#include "G4eplusAnnihilation.hh" +#include "G4PhysicalConstants.hh" +#include "G4MaterialCutsCouple.hh" +#include "G4Gamma.hh" +#include "G4Electron.hh" +#include "G4Positron.hh" +#include "G4eeToTwoGammaModel.hh" +#include "G4EmBiasingManager.hh" +#include "G4EntanglementAuxInfo.hh" +#include "G4eplusAnnihilationEntanglementClipBoard.hh" +#include "G4EmParameters.hh" +#include "G4PhysicsModelCatalog.hh" +#include <iostream> + + + + +class GateBremPostStepDoIt : public G4VEnergyLossProcess { +public : + +GateBremPostStepDoIt(); + +~ GateBremPostStepDoIt(); + +virtual G4VParticleChange * PostStepDoIt (const G4Track & track, const G4Step & step) override +{ + const G4MaterialCutsCouple* couple = step.GetPreStepPoint()->GetMaterialCutsCouple(); + currentCouple = couple; + G4VParticleChange* particleChange = G4VEnergyLossProcess::PostStepDoIt(track,step); + return particleChange; +} + + +}; + + +class GateGammaEmPostStepDoIt : public G4VEmProcess { +public : + +GateGammaEmPostStepDoIt(); + +~ GateGammaEmPostStepDoIt(); + +virtual G4VParticleChange * PostStepDoIt(const G4Track & track, const G4Step & step) override +{ + const G4MaterialCutsCouple* couple = step.GetPreStepPoint()->GetMaterialCutsCouple(); + currentCouple = couple; + G4VParticleChange* particleChange = G4VEmProcess::PostStepDoIt(track,step); + return particleChange; +} + + +}; + +class GateplusannihilAtRestDoIt : public G4eplusAnnihilation { +public : + +GateplusannihilAtRestDoIt(); +~ GateplusannihilAtRestDoIt(); + +virtual G4VParticleChange* AtRestDoIt(const G4Track& track, + const G4Step& step) override +// Performs the e+ e- annihilation when both particles are assumed at rest. + { + G4Track copyTrack = G4Track(track); + copyTrack.SetStep(&step); + G4VParticleChange* particleChange = G4eplusAnnihilation::AtRestDoIt(copyTrack,step); + return particleChange; + } +}; +#endif + + + + + + \ No newline at end of file diff --git a/core/opengate_core/opengate_lib/GateSourceManager.cpp b/core/opengate_core/opengate_lib/GateSourceManager.cpp index a1d191c89..df119ce11 100644 --- a/core/opengate_core/opengate_lib/GateSourceManager.cpp +++ b/core/opengate_core/opengate_lib/GateSourceManager.cpp @@ -181,6 +181,9 @@ void GateSourceManager::PrepareRunToStart(int run_id) { : std::to_string(G4Threading::G4GetThreadId())); } + + + void GateSourceManager::PrepareNextSource() { auto &l = fThreadLocalData.Get(); l.fNextActiveSource = nullptr; @@ -195,6 +198,7 @@ void GateSourceManager::PrepareNextSource() { l.fNextSimulationTime = t; } } + // If no next time in the current interval, active source is NULL } diff --git a/core/opengate_core/opengate_lib/GateSourceManager.h b/core/opengate_core/opengate_lib/GateSourceManager.h index accb5aef5..9914a3b92 100644 --- a/core/opengate_core/opengate_lib/GateSourceManager.h +++ b/core/opengate_core/opengate_lib/GateSourceManager.h @@ -61,6 +61,22 @@ class GateSourceManager : public G4VUserPrimaryGeneratorAction { // Return a source GateVSource *FindSourceByName(std::string name) const; + + + G4String GetActiveSourceName(){ + auto &l = fThreadLocalData.Get(); + if (l.fNextActiveSource !=0){ + G4String name = l.fNextActiveSource->fName; + return name; + } + return "None"; + } + + void SetActiveSourcebyName(G4String sourceName){ + auto &l = fThreadLocalData.Get(); + auto* source = FindSourceByName(sourceName); + l.fNextActiveSource = source; + } // [available on py side] start the simulation, master thread only void StartMasterThread(); diff --git a/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActorOld.cpp b/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActorOld.cpp new file mode 100644 index 000000000..a15dc55e8 --- /dev/null +++ b/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActorOld.cpp @@ -0,0 +1,21 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ +#include <pybind11/pybind11.h> + +namespace py = pybind11; +#include "GateLastVertexInteractionSplittingActorOld.h" + +void init_GateLastVertexInteractionSplittingActorOld(py::module &m) { + + py::class_<GateLastVertexInteractionSplittingActorOld, GateVActor, + std::unique_ptr<GateLastVertexInteractionSplittingActorOld, py::nodelete>>( + m, "GateLastVertexInteractionSplittingActorOld") + .def_readwrite( + "fListOfVolumeAncestor", + &GateLastVertexInteractionSplittingActorOld::fListOfVolumeAncestor) + .def(py::init<py::dict &>()); +} diff --git a/core/opengate_core/opengate_lib/pyGateLastVertexSource.cpp b/core/opengate_core/opengate_lib/pyGateLastVertexSource.cpp new file mode 100644 index 000000000..0cb59f146 --- /dev/null +++ b/core/opengate_core/opengate_lib/pyGateLastVertexSource.cpp @@ -0,0 +1,22 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include <pybind11/pybind11.h> + +namespace py = pybind11; + +#include "GateLastVertexSource.h" + +void init_GateLastVertexSource(py::module &m) { + + py::class_<GateLastVertexSource, GateVSource>(m, "GateLastVertexSource") + .def(py::init()) + .def("InitializeUserInfo", &GateLastVertexSource::InitializeUserInfo) + // If needed: add your own class functions that will be accessible from + // python side. + ; +} diff --git a/core/opengate_core/opengate_lib/tree.hh b/core/opengate_core/opengate_lib/tree.hh new file mode 100644 index 000000000..4f78d5b8a --- /dev/null +++ b/core/opengate_core/opengate_lib/tree.hh @@ -0,0 +1,3412 @@ + +// STL-like templated tree class. +// +// Copyright (C) 2001-2024 Kasper Peeters <kasper@phi-sci.com> +// Distributed under the GNU General Public License version 3. +// +// Special permission to use tree.hh under the conditions of a +// different license can be requested from the author. + +/** \mainpage tree.hh + \author Kasper Peeters + \version 3.20 + \date 2024-04-12 + \see http://github.com/kpeeters/tree.hh/ + + The tree.hh library for C++ provides an STL-like container class + for n-ary trees, templated over the data stored at the + nodes. Various types of iterators are provided (post-order, + pre-order, and others). Where possible the access methods are + compatible with the STL or alternative algorithms are + available. +*/ + + +#ifndef tree_hh_ +#define tree_hh_ + +#include <cassert> +#include <memory> +#include <stdexcept> +#include <iterator> +#include <set> +#include <queue> +#include <algorithm> +#include <cstddef> +#include <string> + +/// A node in the tree, combining links to other nodes as well as the actual data. +template<class T> +class tree_node_ { // size: 5*4=20 bytes (on 32 bit arch), can be reduced by 8. + public: + tree_node_(); + tree_node_(const T&); + tree_node_(T&&); + + tree_node_<T> *parent; + tree_node_<T> *first_child, *last_child; + tree_node_<T> *prev_sibling, *next_sibling; + T data; +}; + +template<class T> +tree_node_<T>::tree_node_() + : parent(0), first_child(0), last_child(0), prev_sibling(0), next_sibling(0) + { + } + +template<class T> +tree_node_<T>::tree_node_(const T& val) + : parent(0), first_child(0), last_child(0), prev_sibling(0), next_sibling(0), data(val) + { + } + +template<class T> +tree_node_<T>::tree_node_(T&& val) + : parent(0), first_child(0), last_child(0), prev_sibling(0), next_sibling(0), data(val) + { + } + +// Throw an exception with a stacktrace. + +//template <class E> +//void throw_with_trace(const E& e) +// { +// throw boost::enable_error_info(e) +// << traced(boost::stacktrace::stacktrace()); +// } + +class navigation_error : public std::logic_error { + public: + navigation_error(const std::string& s) : std::logic_error(s) + { +// assert(1==0); +// std::ostringstream str; +// std::cerr << boost::stacktrace::stacktrace() << std::endl; +// str << boost::stacktrace::stacktrace(); +// stacktrace=str.str(); + } + +// virtual const char *what() const noexcept override +// { +// return (std::logic_error::what()+std::string("; ")+stacktrace).c_str(); +// } +// +// std::string stacktrace; +}; + +template <class T, class tree_node_allocator = std::allocator<tree_node_<T> > > +class tree { + protected: + typedef tree_node_<T> tree_node; + public: + /// Value of the data stored at a node. + typedef T value_type; + + class iterator_base; + class pre_order_iterator; + class post_order_iterator; + class sibling_iterator; + class leaf_iterator; + + tree(); // empty constructor + tree(const T&); // constructor setting given element as head + tree(const iterator_base&); + tree(const tree<T, tree_node_allocator>&); // copy constructor + tree(tree<T, tree_node_allocator>&&); // move constructor + ~tree(); + tree<T,tree_node_allocator>& operator=(const tree<T, tree_node_allocator>&); // copy assignment + tree<T,tree_node_allocator>& operator=(tree<T, tree_node_allocator>&&); // move assignment + + /// Base class for iterators, only pointers stored, no traversal logic. +#ifdef __SGI_STL_PORT + class iterator_base : public stlport::bidirectional_iterator<T, ptrdiff_t> { +#else + class iterator_base { +#endif + public: + typedef T value_type; + typedef T* pointer; + typedef T& reference; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + typedef std::bidirectional_iterator_tag iterator_category; + + iterator_base(); + iterator_base(tree_node *); + + T& operator*() const; + T* operator->() const; + + /// When called, the next increment/decrement skips children of this node. + void skip_children(); + void skip_children(bool skip); + /// Number of children of the node pointed to by the iterator. + unsigned int number_of_children() const; + + sibling_iterator begin() const; + sibling_iterator end() const; + + tree_node *node; + protected: + bool skip_current_children_; + }; + + /// Depth-first iterator, first accessing the node, then its children. + class pre_order_iterator : public iterator_base { + public: + pre_order_iterator(); + pre_order_iterator(tree_node *); + pre_order_iterator(const iterator_base&); + pre_order_iterator(const sibling_iterator&); + + bool operator==(const pre_order_iterator&) const; + bool operator!=(const pre_order_iterator&) const; + pre_order_iterator& operator++(); + pre_order_iterator& operator--(); + pre_order_iterator operator++(int); + pre_order_iterator operator--(int); + pre_order_iterator& operator+=(unsigned int); + pre_order_iterator& operator-=(unsigned int); + + pre_order_iterator& next_skip_children(); + }; + + /// Depth-first iterator, first accessing the children, then the node itself. + class post_order_iterator : public iterator_base { + public: + post_order_iterator(); + post_order_iterator(tree_node *); + post_order_iterator(const iterator_base&); + post_order_iterator(const sibling_iterator&); + + bool operator==(const post_order_iterator&) const; + bool operator!=(const post_order_iterator&) const; + post_order_iterator& operator++(); + post_order_iterator& operator--(); + post_order_iterator operator++(int); + post_order_iterator operator--(int); + post_order_iterator& operator+=(unsigned int); + post_order_iterator& operator-=(unsigned int); + + /// Set iterator to the first child as deep as possible down the tree. + void descend_all(); + }; + + /// Breadth-first iterator, using a queue + class breadth_first_queued_iterator : public iterator_base { + public: + breadth_first_queued_iterator(); + breadth_first_queued_iterator(tree_node *); + breadth_first_queued_iterator(const iterator_base&); + + bool operator==(const breadth_first_queued_iterator&) const; + bool operator!=(const breadth_first_queued_iterator&) const; + breadth_first_queued_iterator& operator++(); + breadth_first_queued_iterator operator++(int); + breadth_first_queued_iterator& operator+=(unsigned int); + + private: + std::queue<tree_node *> traversal_queue; + }; + + /// The default iterator types throughout the tree class. + typedef pre_order_iterator iterator; + typedef breadth_first_queued_iterator breadth_first_iterator; + + /// Iterator which traverses only the nodes at a given depth from the root. + class fixed_depth_iterator : public iterator_base { + public: + fixed_depth_iterator(); + fixed_depth_iterator(tree_node *); + fixed_depth_iterator(const iterator_base&); + fixed_depth_iterator(const sibling_iterator&); + fixed_depth_iterator(const fixed_depth_iterator&); + + void swap(fixed_depth_iterator&, fixed_depth_iterator&); + fixed_depth_iterator& operator=(fixed_depth_iterator); + + bool operator==(const fixed_depth_iterator&) const; + bool operator!=(const fixed_depth_iterator&) const; + fixed_depth_iterator& operator++(); + fixed_depth_iterator& operator--(); + fixed_depth_iterator operator++(int); + fixed_depth_iterator operator--(int); + fixed_depth_iterator& operator+=(unsigned int); + fixed_depth_iterator& operator-=(unsigned int); + + tree_node *top_node; + }; + + /// Iterator which traverses only the nodes which are siblings of each other. + class sibling_iterator : public iterator_base { + public: + sibling_iterator(); + sibling_iterator(tree_node *); + sibling_iterator(const sibling_iterator&); + sibling_iterator(const iterator_base&); + + void swap(sibling_iterator&, sibling_iterator&); + sibling_iterator& operator=(sibling_iterator); + + bool operator==(const sibling_iterator&) const; + bool operator!=(const sibling_iterator&) const; + sibling_iterator& operator++(); + sibling_iterator& operator--(); + sibling_iterator operator++(int); + sibling_iterator operator--(int); + sibling_iterator& operator+=(unsigned int); + sibling_iterator& operator-=(unsigned int); + + tree_node *range_first() const; + tree_node *range_last() const; + tree_node *parent_; + private: + void set_parent_(); + }; + + /// Iterator which traverses only the leaves. + class leaf_iterator : public iterator_base { + public: + leaf_iterator(); + leaf_iterator(tree_node *, tree_node *top=0); + leaf_iterator(const sibling_iterator&); + leaf_iterator(const iterator_base&); + + bool operator==(const leaf_iterator&) const; + bool operator!=(const leaf_iterator&) const; + leaf_iterator& operator++(); + leaf_iterator& operator--(); + leaf_iterator operator++(int); + leaf_iterator operator--(int); + leaf_iterator& operator+=(unsigned int); + leaf_iterator& operator-=(unsigned int); + private: + tree_node *top_node; + }; + + /// Return iterator to the beginning of the tree. + inline pre_order_iterator begin() const; + /// Return iterator to the end of the tree. + inline pre_order_iterator end() const; + /// Return post-order iterator to the beginning of the tree. + post_order_iterator begin_post() const; + /// Return post-order end iterator of the tree. + post_order_iterator end_post() const; + /// Return fixed-depth iterator to the first node at a given depth from the given iterator. + /// If 'walk_back=true', a depth=0 iterator will be taken from the beginning of the sibling + /// range, not the current node. + fixed_depth_iterator begin_fixed(const iterator_base&, unsigned int, bool walk_back=true) const; + /// Return fixed-depth end iterator. + fixed_depth_iterator end_fixed(const iterator_base&, unsigned int) const; + /// Return breadth-first iterator to the first node at a given depth. + breadth_first_queued_iterator begin_breadth_first() const; + /// Return breadth-first end iterator. + breadth_first_queued_iterator end_breadth_first() const; + /// Return sibling iterator to the first child of given node. + static sibling_iterator begin(const iterator_base&); + /// Return sibling end iterator for children of given node. + static sibling_iterator end(const iterator_base&); + /// Return leaf iterator to the first leaf of the tree. + leaf_iterator begin_leaf() const; + /// Return leaf end iterator for entire tree. + leaf_iterator end_leaf() const; + /// Return leaf iterator to the first leaf of the subtree at the given node. + leaf_iterator begin_leaf(const iterator_base& top) const; + /// Return leaf end iterator for the subtree at the given node. + leaf_iterator end_leaf(const iterator_base& top) const; + + typedef std::vector<int> path_t; + /// Return a path (to be taken from the 'top' node) corresponding to a node in the tree. + /// The first integer in path_t is the number of steps you need to go 'right' in the sibling + /// chain (so 0 if we go straight to the children). + path_t path_from_iterator(const iterator_base& iter, const iterator_base& top) const; + /// Return an iterator given a path from the 'top' node. + iterator iterator_from_path(const path_t&, const iterator_base& top) const; + + /// Return iterator to the parent of a node. Throws a `navigation_error` if the node + /// does not have a parent. + template<typename iter> static iter parent(iter); + /// Return iterator to the previous sibling of a node. + template<typename iter> static iter previous_sibling(iter); + /// Return iterator to the next sibling of a node. + template<typename iter> static iter next_sibling(iter); + /// Return iterator to the next node at a given depth. + template<typename iter> iter next_at_same_depth(iter) const; + + /// Erase all nodes of the tree. + void clear(); + /// Erase element at position pointed to by iterator, return incremented iterator. + template<typename iter> iter erase(iter); + /// Erase all children of the node pointed to by iterator. + void erase_children(const iterator_base&); + /// Erase all siblings to the right of the iterator. + void erase_right_siblings(const iterator_base&); + /// Erase all siblings to the left of the iterator. + void erase_left_siblings(const iterator_base&); + + /// Insert empty node as last/first child of node pointed to by position. + template<typename iter> iter append_child(iter position); + template<typename iter> iter prepend_child(iter position); + /// Insert node as last/first child of node pointed to by position. + template<typename iter> iter append_child(iter position, const T& x); + template<typename iter> iter append_child(iter position, T&& x); + template<typename iter> iter prepend_child(iter position, const T& x); + template<typename iter> iter prepend_child(iter position, T&& x); + /// Append the node (plus its children) at other_position as last/first child of position. + template<typename iter> iter append_child(iter position, iter other_position); + template<typename iter> iter prepend_child(iter position, iter other_position); + /// Append the nodes in the from-to range (plus their children) as last/first children of position. + template<typename iter> iter append_children(iter position, sibling_iterator from, sibling_iterator to); + template<typename iter> iter prepend_children(iter position, sibling_iterator from, sibling_iterator to); + + /// Short-hand to insert topmost node in otherwise empty tree. + pre_order_iterator set_head(const T& x); + pre_order_iterator set_head(T&& x); + /// Insert node as previous sibling of node pointed to by position. + template<typename iter> iter insert(iter position, const T& x); + template<typename iter> iter insert(iter position, T&& x); + /// Specialisation of previous member. + sibling_iterator insert(sibling_iterator position, const T& x); + sibling_iterator insert(sibling_iterator position, T&& x); + /// Insert node (with children) pointed to by subtree as previous sibling of node pointed to by position. + /// Does not change the subtree itself (use move_in or move_in_below for that). + template<typename iter> iter insert_subtree(iter position, const iterator_base& subtree); + /// Insert node as next sibling of node pointed to by position. + template<typename iter> iter insert_after(iter position, const T& x); + template<typename iter> iter insert_after(iter position, T&& x); + /// Insert node (with children) pointed to by subtree as next sibling of node pointed to by position. + template<typename iter> iter insert_subtree_after(iter position, const iterator_base& subtree); + + /// Replace node at 'position' with other node (keeping same children); 'position' becomes invalid. + template<typename iter> iter replace(iter position, const T& x); + /// Replace node at 'position' with subtree starting at 'from' (do not erase subtree at 'from'); see above. + template<typename iter> iter replace(iter position, const iterator_base& from); + /// Replace string of siblings (plus their children) with copy of a new string (with children); see above + sibling_iterator replace(sibling_iterator orig_begin, sibling_iterator orig_end, + sibling_iterator new_begin, sibling_iterator new_end); + + /// Move all children of node at 'position' to be siblings, returns position. + template<typename iter> iter flatten(iter position); + /// Move nodes in range to be children of 'position'. + template<typename iter> iter reparent(iter position, sibling_iterator begin, sibling_iterator end); + /// Move all child nodes of 'from' to be children of 'position'. + template<typename iter> iter reparent(iter position, iter from); + + /// Replace node with a new node, making the old node (plus subtree) a child of the new node. + template<typename iter> iter wrap(iter position, const T& x); + /// Replace the range of sibling nodes (plus subtrees), making these children of the new node. + template<typename iter> iter wrap(iter from, iter to, const T& x); + + /// Move 'source' node (plus its children) to become the next sibling of 'target'. + template<typename iter> iter move_after(iter target, iter source); + /// Move 'source' node (plus its children) to become the previous sibling of 'target'. + template<typename iter> iter move_before(iter target, iter source); + sibling_iterator move_before(sibling_iterator target, sibling_iterator source); + /// Move 'source' node (plus its children) to become the node at 'target' (erasing the node at 'target'). + template<typename iter> iter move_ontop(iter target, iter source); + + /// Extract the subtree starting at the indicated node, removing it from the original tree. + tree move_out(iterator); + /// Inverse of take_out: inserts the given tree as previous sibling of indicated node by a + /// move operation, that is, the given tree becomes empty. Returns iterator to the top node. + template<typename iter> iter move_in(iter, tree&); + /// As above, but now make the tree the last child of the indicated node. + template<typename iter> iter move_in_below(iter, tree&); + /// As above, but now make the tree the nth child of the indicated node (if possible). + template<typename iter> iter move_in_as_nth_child(iter, size_t, tree&); + + /// Merge with other tree, creating new branches and leaves only if they are not already present. + void merge(sibling_iterator, sibling_iterator, sibling_iterator, sibling_iterator, + bool duplicate_leaves=false); + /// As above, but using two trees with a single top node at the 'to' and 'from' positions. + void merge(iterator to, iterator from, bool duplicate_leaves); + /// Sort (std::sort only moves values of nodes, this one moves children as well). + void sort(sibling_iterator from, sibling_iterator to, bool deep=false); + template<class StrictWeakOrdering> + void sort(sibling_iterator from, sibling_iterator to, StrictWeakOrdering comp, bool deep=false); + /// Compare two ranges of nodes (compares nodes as well as tree structure). + template<typename iter> + bool equal(const iter& one, const iter& two, const iter& three) const; + template<typename iter, class BinaryPredicate> + bool equal(const iter& one, const iter& two, const iter& three, BinaryPredicate) const; + template<typename iter> + bool equal_subtree(const iter& one, const iter& two) const; + template<typename iter, class BinaryPredicate> + bool equal_subtree(const iter& one, const iter& two, BinaryPredicate) const; + /// Extract a new tree formed by the range of siblings plus all their children. + tree subtree(sibling_iterator from, sibling_iterator to) const; + void subtree(tree&, sibling_iterator from, sibling_iterator to) const; + /// Exchange the node (plus subtree) with its sibling node (do nothing if no sibling present). + void swap(sibling_iterator it); + /// Exchange two nodes (plus subtrees). The iterators will remain valid and keep + /// pointing to the same nodes, which now sit at different locations in the tree. + void swap(iterator, iterator); + + /// Count the total number of nodes. + size_t size() const; + /// Count the total number of nodes below the indicated node (plus one). + size_t size(const iterator_base&) const; + /// Check if tree is empty. + bool empty() const; + /// Compute the depth to the root or to a fixed other iterator. + static int depth(const iterator_base&); + static int depth(const iterator_base&, const iterator_base&); + /// Compute the depth to the root, counting all levels for which predicate returns true. + template<class Predicate> + static int depth(const iterator_base&, Predicate p); + /// Compute the depth distance between two nodes, counting all levels for which predicate returns true. + template<class Predicate> + static int distance(const iterator_base& top, const iterator_base& bottom, Predicate p); + /// Determine the maximal depth of the tree. An empty tree has max_depth=-1. + int max_depth() const; + /// Determine the maximal depth of the tree with top node at the given position. + int max_depth(const iterator_base&) const; + /// Count the number of children of node at position. + static unsigned int number_of_children(const iterator_base&); + /// Count the number of siblings (left and right) of node at iterator. Total nodes at this level is +1. + unsigned int number_of_siblings(const iterator_base&) const; + /// Determine whether node at position is in the subtrees with indicated top node. + bool is_in_subtree(const iterator_base& position, const iterator_base& top) const; + /// Determine whether node at position is in the subtrees with root in the range. + bool is_in_subtree(const iterator_base& position, const iterator_base& begin, + const iterator_base& end) const; + /// Determine whether the iterator is an 'end' iterator and thus not actually pointing to a node. + bool is_valid(const iterator_base&) const; + /// Determine whether the iterator is one of the 'head' nodes at the top level, i.e. has no parent. + static bool is_head(const iterator_base&); + /// Find the lowest common ancestor of two nodes, that is, the deepest node such that + /// both nodes are descendants of it. + iterator lowest_common_ancestor(const iterator_base&, const iterator_base &) const; + + /// Determine the index of a node in the range of siblings to which it belongs. + unsigned int index(sibling_iterator it) const; + /// Inverse of 'index': return the n-th child of the node at position. + static sibling_iterator child(const iterator_base& position, unsigned int); + /// Return iterator to the sibling indicated by index + sibling_iterator sibling(const iterator_base& position, unsigned int) const; + + /// For debugging only: verify internal consistency by inspecting all pointers in the tree + /// (which will also trigger a valgrind error in case something got corrupted). + void debug_verify_consistency() const; + + /// Comparator class for iterators (compares pointer values; why doesn't this work automatically?) + class iterator_base_less { + public: + bool operator()(const typename tree<T, tree_node_allocator>::iterator_base& one, + const typename tree<T, tree_node_allocator>::iterator_base& two) const + { + return one.node < two.node; + } + }; + tree_node *head, *feet; // head/feet are always dummy; if an iterator points to them it is invalid + private: + tree_node_allocator alloc_; + void head_initialise_(); + void copy_(const tree<T, tree_node_allocator>& other); + + /// Comparator class for two nodes of a tree (used for sorting and searching). + template<class StrictWeakOrdering> + class compare_nodes { + public: + compare_nodes(StrictWeakOrdering comp) : comp_(comp) {} + + bool operator()(const tree_node *a, const tree_node *b) const + { + return comp_(a->data, b->data); + } + private: + StrictWeakOrdering comp_; + }; + }; + +//template <class T, class tree_node_allocator> +//class iterator_base_less { +// public: +// bool operator()(const typename tree<T, tree_node_allocator>::iterator_base& one, +// const typename tree<T, tree_node_allocator>::iterator_base& two) const +// { +// txtout << "operatorclass<" << one.node < two.node << std::endl; +// return one.node < two.node; +// } +//}; + +// template <class T, class tree_node_allocator> +// bool operator<(const typename tree<T, tree_node_allocator>::iterator& one, +// const typename tree<T, tree_node_allocator>::iterator& two) +// { +// txtout << "operator< " << one.node < two.node << std::endl; +// if(one.node < two.node) return true; +// return false; +// } +// +// template <class T, class tree_node_allocator> +// bool operator==(const typename tree<T, tree_node_allocator>::iterator& one, +// const typename tree<T, tree_node_allocator>::iterator& two) +// { +// txtout << "operator== " << one.node == two.node << std::endl; +// if(one.node == two.node) return true; +// return false; +// } +// +// template <class T, class tree_node_allocator> +// bool operator>(const typename tree<T, tree_node_allocator>::iterator_base& one, +// const typename tree<T, tree_node_allocator>::iterator_base& two) +// { +// txtout << "operator> " << one.node < two.node << std::endl; +// if(one.node > two.node) return true; +// return false; +// } + + + +// Tree + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree() + { + head_initialise_(); + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree(const T& x) + { + head_initialise_(); + set_head(x); + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree(tree<T, tree_node_allocator>&& x) + { + head_initialise_(); + if(x.head->next_sibling!=x.feet) { // move tree if non-empty only + head->next_sibling=x.head->next_sibling; + feet->prev_sibling=x.feet->prev_sibling; + x.head->next_sibling->prev_sibling=head; + x.feet->prev_sibling->next_sibling=feet; + x.head->next_sibling=x.feet; + x.feet->prev_sibling=x.head; + } + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree(const iterator_base& other) + { + head_initialise_(); + set_head((*other)); + replace(begin(), other); + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::~tree() + { + clear(); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, head); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, feet); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, head, 1); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, feet, 1); + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::head_initialise_() + { + head = std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + feet = std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, head, tree_node_<T>()); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, feet, tree_node_<T>()); + + head->parent=0; + head->first_child=0; + head->last_child=0; + head->prev_sibling=0; //head; + head->next_sibling=feet; //head; + + feet->parent=0; + feet->first_child=0; + feet->last_child=0; + feet->prev_sibling=head; + feet->next_sibling=0; + } + +template <class T, class tree_node_allocator> +tree<T,tree_node_allocator>& tree<T, tree_node_allocator>::operator=(const tree<T, tree_node_allocator>& other) + { + if(this != &other) + copy_(other); + return *this; + } + +template <class T, class tree_node_allocator> +tree<T,tree_node_allocator>& tree<T, tree_node_allocator>::operator=(tree<T, tree_node_allocator>&& x) + { + if(this != &x) { + clear(); // clear any existing data. + + head->next_sibling=x.head->next_sibling; + feet->prev_sibling=x.feet->prev_sibling; + x.head->next_sibling->prev_sibling=head; + x.feet->prev_sibling->next_sibling=feet; + x.head->next_sibling=x.feet; + x.feet->prev_sibling=x.head; + } + return *this; + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree(const tree<T, tree_node_allocator>& other) + { + head_initialise_(); + copy_(other); + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::copy_(const tree<T, tree_node_allocator>& other) + { + clear(); + pre_order_iterator it=other.begin(), to=begin(); + while(it!=other.end()) { + to=insert(to, (*it)); + it.skip_children(); + ++it; + } + to=begin(); + it=other.begin(); + while(it!=other.end()) { + to=replace(to, it); + to.skip_children(); + it.skip_children(); + ++to; + ++it; + } + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::clear() + { + if(head) + while(head->next_sibling!=feet) + erase(pre_order_iterator(head->next_sibling)); + } + +template<class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::erase_children(const iterator_base& it) + { +// std::cout << "erase_children " << it.node << std::endl; + if(it.node==0) return; + + tree_node *cur=it.node->first_child; + tree_node *prev=0; + + while(cur!=0) { + prev=cur; + cur=cur->next_sibling; + erase_children(pre_order_iterator(prev)); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); + } + it.node->first_child=0; + it.node->last_child=0; +// std::cout << "exit" << std::endl; + } + +template<class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::erase_right_siblings(const iterator_base& it) + { + if(it.node==0) return; + + tree_node *cur=it.node->next_sibling; + tree_node *prev=0; + + while(cur!=0) { + prev=cur; + cur=cur->next_sibling; + erase_children(pre_order_iterator(prev)); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); + } + it.node->next_sibling=0; + if(it.node->parent!=0) + it.node->parent->last_child=it.node; + } + +template<class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::erase_left_siblings(const iterator_base& it) + { + if(it.node==0) return; + + tree_node *cur=it.node->prev_sibling; + tree_node *prev=0; + + while(cur!=0) { + prev=cur; + cur=cur->prev_sibling; + erase_children(pre_order_iterator(prev)); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); + } + it.node->prev_sibling=0; + if(it.node->parent!=0) + it.node->parent->first_child=it.node; + } + +template<class T, class tree_node_allocator> +template<class iter> +iter tree<T, tree_node_allocator>::erase(iter it) + { + tree_node *cur=it.node; + assert(cur!=head); + iter ret=it; + ret.skip_children(); + ++ret; + erase_children(it); + if(cur->prev_sibling==0) { + cur->parent->first_child=cur->next_sibling; + } + else { + cur->prev_sibling->next_sibling=cur->next_sibling; + } + if(cur->next_sibling==0) { + cur->parent->last_child=cur->prev_sibling; + } + else { + cur->next_sibling->prev_sibling=cur->prev_sibling; + } + + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, cur); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, cur, 1); + return ret; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::begin() const + { + return pre_order_iterator(head->next_sibling); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::end() const + { + return pre_order_iterator(feet); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator tree<T, tree_node_allocator>::begin_breadth_first() const + { + return breadth_first_queued_iterator(head->next_sibling); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator tree<T, tree_node_allocator>::end_breadth_first() const + { + return breadth_first_queued_iterator(); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::begin_post() const + { + tree_node *tmp=head->next_sibling; + if(tmp!=feet) { + while(tmp->first_child) + tmp=tmp->first_child; + } + return post_order_iterator(tmp); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::end_post() const + { + return post_order_iterator(feet); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::begin_fixed(const iterator_base& pos, unsigned int dp, bool walk_back) const + { + typename tree<T, tree_node_allocator>::fixed_depth_iterator ret; + ret.top_node=pos.node; + + tree_node *tmp=pos.node; + unsigned int curdepth=0; + while(curdepth<dp) { // go down one level + while(tmp->first_child==0) { + if(tmp->next_sibling==0) { + // try to walk up and then right again + do { + if(tmp==ret.top_node) + throw std::range_error("tree: begin_fixed out of range"); + tmp=tmp->parent; + if(tmp==0) + throw std::range_error("tree: begin_fixed out of range"); + --curdepth; + } while(tmp->next_sibling==0); + } + tmp=tmp->next_sibling; + } + tmp=tmp->first_child; + ++curdepth; + } + + // Now walk back to the first sibling in this range. + if(walk_back) + while(tmp->prev_sibling!=0) + tmp=tmp->prev_sibling; + + ret.node=tmp; + return ret; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::end_fixed(const iterator_base& pos, unsigned int dp) const + { + assert(1==0); // FIXME: not correct yet: use is_valid() as a temporary workaround + tree_node *tmp=pos.node; + unsigned int curdepth=1; + while(curdepth<dp) { // go down one level + while(tmp->first_child==0) { + tmp=tmp->next_sibling; + if(tmp==0) + throw std::range_error("tree: end_fixed out of range"); + } + tmp=tmp->first_child; + ++curdepth; + } + return tmp; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::begin(const iterator_base& pos) + { + assert(pos.node!=0); + if(pos.node->first_child==0) { + return end(pos); + } + return pos.node->first_child; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::end(const iterator_base& pos) + { + sibling_iterator ret(0); + ret.parent_=pos.node; + return ret; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::begin_leaf() const + { + tree_node *tmp=head->next_sibling; + if(tmp!=feet) { + while(tmp->first_child) + tmp=tmp->first_child; + } + return leaf_iterator(tmp); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::end_leaf() const + { + return leaf_iterator(feet); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::path_t tree<T, tree_node_allocator>::path_from_iterator(const iterator_base& iter, const iterator_base& top) const + { + path_t path; + tree_node *walk=iter.node; + + do { + if(path.size()>0) + walk=walk->parent; + int num=0; + while(walk!=top.node && walk->prev_sibling!=0 && walk->prev_sibling!=head) { + ++num; + walk=walk->prev_sibling; + } + path.push_back(num); + } + while(walk->parent!=0 && walk!=top.node); + + std::reverse(path.begin(), path.end()); + return path; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::iterator tree<T, tree_node_allocator>::iterator_from_path(const path_t& path, const iterator_base& top) const + { + iterator it=top; + tree_node *walk=it.node; + + for(size_t step=0; step<path.size(); ++step) { + if(step>0) + walk=walk->first_child; + if(walk==0) + throw std::range_error("tree::iterator_from_path: no more nodes at step "+std::to_string(step)); + + for(int i=0; i<path[step]; ++i) { + walk=walk->next_sibling; + if(walk==0) + throw std::range_error("tree::iterator_from_path: out of siblings at step "+std::to_string(step)); + } + } + it.node=walk; + return it; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::begin_leaf(const iterator_base& top) const + { + tree_node *tmp=top.node; + while(tmp->first_child) + tmp=tmp->first_child; + return leaf_iterator(tmp, top.node); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::end_leaf(const iterator_base& top) const + { + return leaf_iterator(top.node, top.node); + } + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::parent(iter position) + { + if(position.node==0) + throw navigation_error("tree: attempt to navigate from null iterator."); + + if(position.node->parent==0) + throw navigation_error("tree: attempt to navigate up past head node."); + + return iter(position.node->parent); + } + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::previous_sibling(iter position) + { + assert(position.node!=0); + iter ret(position); + ret.node=position.node->prev_sibling; + return ret; + } + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::next_sibling(iter position) + { + assert(position.node!=0); + iter ret(position); + ret.node=position.node->next_sibling; + return ret; + } + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::next_at_same_depth(iter position) const + { + // We make use of a temporary fixed_depth iterator to implement this. + + typename tree<T, tree_node_allocator>::fixed_depth_iterator tmp(position.node); + + ++tmp; + return iter(tmp); + + // assert(position.node!=0); + // iter ret(position); + // + // if(position.node->next_sibling) { + // ret.node=position.node->next_sibling; + // } + // else { + // int relative_depth=0; + // upper: + // do { + // ret.node=ret.node->parent; + // if(ret.node==0) return ret; + // --relative_depth; + // } while(ret.node->next_sibling==0); + // lower: + // ret.node=ret.node->next_sibling; + // while(ret.node->first_child==0) { + // if(ret.node->next_sibling==0) + // goto upper; + // ret.node=ret.node->next_sibling; + // if(ret.node==0) return ret; + // } + // while(relative_depth<0 && ret.node->first_child!=0) { + // ret.node=ret.node->first_child; + // ++relative_depth; + // } + // if(relative_depth<0) { + // if(ret.node->next_sibling==0) goto upper; + // else goto lower; + // } + // } + // return ret; + } + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::append_child(iter position) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, tree_node_<T>()); + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node; + if(position.node->last_child!=0) { + position.node->last_child->next_sibling=tmp; + } + else { + position.node->first_child=tmp; + } + tmp->prev_sibling=position.node->last_child; + position.node->last_child=tmp; + tmp->next_sibling=0; + return tmp; + } + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::prepend_child(iter position) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, tree_node_<T>()); + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node; + if(position.node->first_child!=0) { + position.node->first_child->prev_sibling=tmp; + } + else { + position.node->last_child=tmp; + } + tmp->next_sibling=position.node->first_child; + position.node->prev_child=tmp; + tmp->prev_sibling=0; + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::append_child(iter position, const T& x) + { + // If your program fails here you probably used 'append_child' to add the top + // node to an empty tree. From version 1.45 the top element should be added + // using 'insert'. See the documentation for further information, and sorry about + // the API change. + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node; + if(position.node->last_child!=0) { + position.node->last_child->next_sibling=tmp; + } + else { + position.node->first_child=tmp; + } + tmp->prev_sibling=position.node->last_child; + position.node->last_child=tmp; + tmp->next_sibling=0; + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::append_child(iter position, T&& x) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); // Here is where the move semantics kick in + std::swap(tmp->data, x); + + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node; + if(position.node->last_child!=0) { + position.node->last_child->next_sibling=tmp; + } + else { + position.node->first_child=tmp; + } + tmp->prev_sibling=position.node->last_child; + position.node->last_child=tmp; + tmp->next_sibling=0; + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::prepend_child(iter position, const T& x) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node; + if(position.node->first_child!=0) { + position.node->first_child->prev_sibling=tmp; + } + else { + position.node->last_child=tmp; + } + tmp->next_sibling=position.node->first_child; + position.node->first_child=tmp; + tmp->prev_sibling=0; + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::prepend_child(iter position, T&& x) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); + std::swap(tmp->data, x); + + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node; + if(position.node->first_child!=0) { + position.node->first_child->prev_sibling=tmp; + } + else { + position.node->last_child=tmp; + } + tmp->next_sibling=position.node->first_child; + position.node->first_child=tmp; + tmp->prev_sibling=0; + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::append_child(iter position, iter other) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + sibling_iterator aargh=append_child(position, value_type()); + return replace(aargh, other); + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::prepend_child(iter position, iter other) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + sibling_iterator aargh=prepend_child(position, value_type()); + return replace(aargh, other); + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::append_children(iter position, sibling_iterator from, sibling_iterator to) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + iter ret=from; + + while(from!=to) { + insert_subtree(position.end(), from); + ++from; + } + return ret; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::prepend_children(iter position, sibling_iterator from, sibling_iterator to) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + if(from==to) return from; // should return end of tree? + + iter ret; + do { + --to; + ret=insert_subtree(position.begin(), to); + } + while(to!=from); + + return ret; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::set_head(const T& x) + { + assert(head->next_sibling==feet); + return insert(iterator(feet), x); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::set_head(T&& x) + { + assert(head->next_sibling==feet); + return insert(iterator(feet), x); + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::insert(iter position, const T& x) + { + if(position.node==0) { + position.node=feet; // Backward compatibility: when calling insert on a null node, + // insert before the feet. + } + assert(position.node!=head); // Cannot insert before head. + + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node->parent; + tmp->next_sibling=position.node; + tmp->prev_sibling=position.node->prev_sibling; + position.node->prev_sibling=tmp; + + if(tmp->prev_sibling==0) { + if(tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->first_child=tmp; + } + else + tmp->prev_sibling->next_sibling=tmp; + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::insert(iter position, T&& x) + { + if(position.node==0) { + position.node=feet; // Backward compatibility: when calling insert on a null node, + // insert before the feet. + } + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); + std::swap(tmp->data, x); // Move semantics + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node->parent; + tmp->next_sibling=position.node; + tmp->prev_sibling=position.node->prev_sibling; + position.node->prev_sibling=tmp; + + if(tmp->prev_sibling==0) { + if(tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->first_child=tmp; + } + else + tmp->prev_sibling->next_sibling=tmp; + return tmp; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::insert(sibling_iterator position, const T& x) + { + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child=0; + tmp->last_child=0; + + tmp->next_sibling=position.node; + if(position.node==0) { // iterator points to end of a subtree + tmp->parent=position.parent_; + tmp->prev_sibling=position.range_last(); + tmp->parent->last_child=tmp; + } + else { + tmp->parent=position.node->parent; + tmp->prev_sibling=position.node->prev_sibling; + position.node->prev_sibling=tmp; + } + + if(tmp->prev_sibling==0) { + if(tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->first_child=tmp; + } + else + tmp->prev_sibling->next_sibling=tmp; + return tmp; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::insert(sibling_iterator position, T&& x) + { + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); + std::swap(tmp->data, x); // Move semantics + + tmp->first_child=0; + tmp->last_child=0; + + tmp->next_sibling=position.node; + if(position.node==0) { // iterator points to end of a subtree + tmp->parent=position.parent_; + tmp->prev_sibling=position.range_last(); + tmp->parent->last_child=tmp; + } + else { + tmp->parent=position.node->parent; + tmp->prev_sibling=position.node->prev_sibling; + position.node->prev_sibling=tmp; + } + + if(tmp->prev_sibling==0) { + if(tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->first_child=tmp; + } + else + tmp->prev_sibling->next_sibling=tmp; + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::insert_after(iter position, const T& x) + { + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node->parent; + tmp->prev_sibling=position.node; + tmp->next_sibling=position.node->next_sibling; + position.node->next_sibling=tmp; + + if(tmp->next_sibling==0) { + if(tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->last_child=tmp; + } + else { + tmp->next_sibling->prev_sibling=tmp; + } + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::insert_after(iter position, T&& x) + { + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); + std::swap(tmp->data, x); // move semantics + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node->parent; + tmp->prev_sibling=position.node; + tmp->next_sibling=position.node->next_sibling; + position.node->next_sibling=tmp; + + if(tmp->next_sibling==0) { + if(tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->last_child=tmp; + } + else { + tmp->next_sibling->prev_sibling=tmp; + } + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::insert_subtree(iter position, const iterator_base& subtree) + { + // insert dummy + iter it=insert(position, value_type()); + // replace dummy with subtree + return replace(it, subtree); + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::insert_subtree_after(iter position, const iterator_base& subtree) + { + // insert dummy + iter it=insert_after(position, value_type()); + // replace dummy with subtree + return replace(it, subtree); + } + +// template <class T, class tree_node_allocator> +// template <class iter> +// iter tree<T, tree_node_allocator>::insert_subtree(sibling_iterator position, iter subtree) +// { +// // insert dummy +// iter it(insert(position, value_type())); +// // replace dummy with subtree +// return replace(it, subtree); +// } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::replace(iter position, const T& x) + { +// kp::destructor(&position.node->data); +// kp::constructor(&position.node->data, x); + position.node->data=x; +// alloc_.destroy(position.node); +// alloc_.construct(position.node, x); + return position; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::replace(iter position, const iterator_base& from) + { + assert(position.node!=head); + tree_node *current_from=from.node; + tree_node *start_from=from.node; + tree_node *current_to =position.node; + + // replace the node at position with head of the replacement tree at from +// std::cout << "warning!" << position.node << std::endl; + erase_children(position); +// std::cout << "no warning!" << std::endl; + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, (*from)); + tmp->first_child=0; + tmp->last_child=0; + if(current_to->prev_sibling==0) { + if(current_to->parent!=0) + current_to->parent->first_child=tmp; + } + else { + current_to->prev_sibling->next_sibling=tmp; + } + tmp->prev_sibling=current_to->prev_sibling; + if(current_to->next_sibling==0) { + if(current_to->parent!=0) + current_to->parent->last_child=tmp; + } + else { + current_to->next_sibling->prev_sibling=tmp; + } + tmp->next_sibling=current_to->next_sibling; + tmp->parent=current_to->parent; +// kp::destructor(¤t_to->data); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, current_to); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, current_to, 1); + current_to=tmp; + + // only at this stage can we fix 'last' + tree_node *last=from.node->next_sibling; + + pre_order_iterator toit=tmp; + // copy all children + do { + assert(current_from!=0); + if(current_from->first_child != 0) { + current_from=current_from->first_child; + toit=append_child(toit, current_from->data); + } + else { + while(current_from->next_sibling==0 && current_from!=start_from) { + current_from=current_from->parent; + toit=parent(toit); + assert(current_from!=0); + } + current_from=current_from->next_sibling; + if(current_from!=last) { + toit=append_child(parent(toit), current_from->data); + } + } + } + while(current_from!=last); + + return current_to; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::replace( + sibling_iterator orig_begin, + sibling_iterator orig_end, + sibling_iterator new_begin, + sibling_iterator new_end) + { + tree_node *orig_first=orig_begin.node; + tree_node *new_first=new_begin.node; + tree_node *orig_last=orig_first; + while((++orig_begin)!=orig_end) + orig_last=orig_last->next_sibling; + tree_node *new_last=new_first; + while((++new_begin)!=new_end) + new_last=new_last->next_sibling; + + // insert all siblings in new_first..new_last before orig_first + bool first=true; + pre_order_iterator ret; + while(1==1) { + pre_order_iterator tt=insert_subtree(pre_order_iterator(orig_first), pre_order_iterator(new_first)); + if(first) { + ret=tt; + first=false; + } + if(new_first==new_last) + break; + new_first=new_first->next_sibling; + } + + // erase old range of siblings + bool last=false; + tree_node *next=orig_first; + while(1==1) { + if(next==orig_last) + last=true; + next=next->next_sibling; + erase((pre_order_iterator)orig_first); + if(last) + break; + orig_first=next; + } + return ret; + } + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::flatten(iter position) + { + if(position.node->first_child==0) + return position; + + tree_node *tmp=position.node->first_child; + while(tmp) { + tmp->parent=position.node->parent; + tmp=tmp->next_sibling; + } + if(position.node->next_sibling) { + position.node->last_child->next_sibling=position.node->next_sibling; + position.node->next_sibling->prev_sibling=position.node->last_child; + } + else { + position.node->parent->last_child=position.node->last_child; + } + position.node->next_sibling=position.node->first_child; + position.node->next_sibling->prev_sibling=position.node; + position.node->first_child=0; + position.node->last_child=0; + + return position; + } + + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::reparent(iter position, sibling_iterator begin, sibling_iterator end) + { + tree_node *first=begin.node; + tree_node *last=first; + + assert(first!=position.node); + + if(begin==end) return begin; + // determine last node + while((++begin)!=end) { + last=last->next_sibling; + } + // move subtree + if(first->prev_sibling==0) { + first->parent->first_child=last->next_sibling; + } + else { + first->prev_sibling->next_sibling=last->next_sibling; + } + if(last->next_sibling==0) { + last->parent->last_child=first->prev_sibling; + } + else { + last->next_sibling->prev_sibling=first->prev_sibling; + } + if(position.node->first_child==0) { + position.node->first_child=first; + position.node->last_child=last; + first->prev_sibling=0; + } + else { + position.node->last_child->next_sibling=first; + first->prev_sibling=position.node->last_child; + position.node->last_child=last; + } + last->next_sibling=0; + + tree_node *pos=first; + for(;;) { + pos->parent=position.node; + if(pos==last) break; + pos=pos->next_sibling; + } + + return first; + } + +template <class T, class tree_node_allocator> +template <typename iter> iter tree<T, tree_node_allocator>::reparent(iter position, iter from) + { + if(from.node->first_child==0) return position; + return reparent(position, from.node->first_child, end(from)); + } + +template <class T, class tree_node_allocator> +template <typename iter> iter tree<T, tree_node_allocator>::wrap(iter position, const T& x) + { + assert(position.node!=0); + sibling_iterator fr=position, to=position; + ++to; + iter ret = insert(position, x); + reparent(ret, fr, to); + return ret; + } + +template <class T, class tree_node_allocator> +template <typename iter> iter tree<T, tree_node_allocator>::wrap(iter from, iter to, const T& x) + { + assert(from.node!=0); + iter ret = insert(from, x); + reparent(ret, from, to); + return ret; + } + +template <class T, class tree_node_allocator> +template <typename iter> iter tree<T, tree_node_allocator>::move_after(iter target, iter source) + { + tree_node *dst=target.node; + tree_node *src=source.node; + assert(dst); + assert(src); + + if(dst==src) return source; + if(dst->next_sibling) + if(dst->next_sibling==src) // already in the right spot + return source; + + // take src out of the tree + if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; + else src->parent->first_child=src->next_sibling; + if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; + else src->parent->last_child=src->prev_sibling; + + // connect it to the new point + if(dst->next_sibling!=0) dst->next_sibling->prev_sibling=src; + else dst->parent->last_child=src; + src->next_sibling=dst->next_sibling; + dst->next_sibling=src; + src->prev_sibling=dst; + src->parent=dst->parent; + return src; + } + +template <class T, class tree_node_allocator> +template <typename iter> iter tree<T, tree_node_allocator>::move_before(iter target, iter source) + { + tree_node *dst=target.node; + tree_node *src=source.node; + assert(dst); + assert(src); + + if(dst==src) return source; + if(dst->prev_sibling) + if(dst->prev_sibling==src) // already in the right spot + return source; + + // take src out of the tree + if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; + else src->parent->first_child=src->next_sibling; + if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; + else src->parent->last_child=src->prev_sibling; + + // connect it to the new point + if(dst->prev_sibling!=0) dst->prev_sibling->next_sibling=src; + else dst->parent->first_child=src; + src->prev_sibling=dst->prev_sibling; + dst->prev_sibling=src; + src->next_sibling=dst; + src->parent=dst->parent; + return src; + } + +// specialisation for sibling_iterators +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::move_before(sibling_iterator target, + sibling_iterator source) + { + tree_node *dst=target.node; + tree_node *src=source.node; + tree_node *dst_prev_sibling; + if(dst==0) { // must then be an end iterator + dst_prev_sibling=target.parent_->last_child; + assert(dst_prev_sibling); + } + else dst_prev_sibling=dst->prev_sibling; + assert(src); + + if(dst==src) return source; + if(dst_prev_sibling) + if(dst_prev_sibling==src) // already in the right spot + return source; + + // take src out of the tree + if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; + else src->parent->first_child=src->next_sibling; + if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; + else src->parent->last_child=src->prev_sibling; + + // connect it to the new point + if(dst_prev_sibling!=0) dst_prev_sibling->next_sibling=src; + else target.parent_->first_child=src; + src->prev_sibling=dst_prev_sibling; + if(dst) { + dst->prev_sibling=src; + src->parent=dst->parent; + } + else { + src->parent=dst_prev_sibling->parent; + } + src->next_sibling=dst; + return src; + } + +template <class T, class tree_node_allocator> +template <typename iter> iter tree<T, tree_node_allocator>::move_ontop(iter target, iter source) + { + tree_node *dst=target.node; + tree_node *src=source.node; + assert(dst); + assert(src); + + if(dst==src) return source; + +// if(dst==src->prev_sibling) { +// +// } + + // remember connection points + tree_node *b_prev_sibling=dst->prev_sibling; + tree_node *b_next_sibling=dst->next_sibling; + tree_node *b_parent=dst->parent; + + // remove target + erase(target); + + // take src out of the tree + if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; + else { + assert(src->parent!=0); + src->parent->first_child=src->next_sibling; + } + if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; + else { + assert(src->parent!=0); + src->parent->last_child=src->prev_sibling; + } + + // connect it to the new point + if(b_prev_sibling!=0) b_prev_sibling->next_sibling=src; + else { + assert(b_parent!=0); + b_parent->first_child=src; + } + if(b_next_sibling!=0) b_next_sibling->prev_sibling=src; + else { + assert(b_parent!=0); + b_parent->last_child=src; + } + src->prev_sibling=b_prev_sibling; + src->next_sibling=b_next_sibling; + src->parent=b_parent; + return src; + } + + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator> tree<T, tree_node_allocator>::move_out(iterator source) + { + tree ret; + + // Move source node into the 'ret' tree. + ret.head->next_sibling = source.node; + ret.feet->prev_sibling = source.node; + + // Close the links in the current tree. + if(source.node->prev_sibling!=0) + source.node->prev_sibling->next_sibling = source.node->next_sibling; + + if(source.node->next_sibling!=0) + source.node->next_sibling->prev_sibling = source.node->prev_sibling; + + // If the moved-out node was a first or last child of + // the parent, adjust those links. + if(source.node->parent->first_child==source.node) { + if(source.node->next_sibling!=0) + source.node->parent->first_child=source.node->next_sibling; + else + source.node->parent->first_child=0; + } + if(source.node->parent->last_child==source.node) { + if(source.node->prev_sibling!=0) + source.node->parent->last_child=source.node->prev_sibling; + else + source.node->parent->last_child=0; + } + source.node->parent=0; + + // Fix source prev/next links. + source.node->prev_sibling = ret.head; + source.node->next_sibling = ret.feet; + + return ret; // A good compiler will move this, not copy. + } + +template <class T, class tree_node_allocator> +template<typename iter> iter tree<T, tree_node_allocator>::move_in(iter loc, tree& other) + { + if(other.head->next_sibling==other.feet) return loc; // other tree is empty + + tree_node *other_first_head = other.head->next_sibling; + tree_node *other_last_head = other.feet->prev_sibling; + + sibling_iterator prev(loc); + --prev; + + prev.node->next_sibling = other_first_head; + loc.node->prev_sibling = other_last_head; + other_first_head->prev_sibling = prev.node; + other_last_head->next_sibling = loc.node; + + // Adjust parent pointers. + tree_node *walk=other_first_head; + while(true) { + walk->parent=loc.node->parent; + if(walk==other_last_head) + break; + walk=walk->next_sibling; + } + + // Close other tree. + other.head->next_sibling=other.feet; + other.feet->prev_sibling=other.head; + + return other_first_head; + } + +template <class T, class tree_node_allocator> +template<typename iter> iter tree<T, tree_node_allocator>::move_in_below(iter loc, tree& other) + { + if(other.head->next_sibling==other.feet) return loc; // other tree is empty + + auto n = other.number_of_children(loc); + return move_in_as_nth_child(loc, n, other); + } + +template <class T, class tree_node_allocator> +template<typename iter> iter tree<T, tree_node_allocator>::move_in_as_nth_child(iter loc, size_t n, tree& other) + { + if(other.head->next_sibling==other.feet) return loc; // other tree is empty + + tree_node *other_first_head = other.head->next_sibling; + tree_node *other_last_head = other.feet->prev_sibling; + + if(n==0) { + if(loc.node->first_child==0) { + loc.node->first_child=other_first_head; + loc.node->last_child=other_last_head; + other_last_head->next_sibling=0; + other_first_head->prev_sibling=0; + } + else { + loc.node->first_child->prev_sibling=other_last_head; + other_last_head->next_sibling=loc.node->first_child; + loc.node->first_child=other_first_head; + other_first_head->prev_sibling=0; + } + } + else { + --n; + tree_node *walk = loc.node->first_child; + while(true) { + if(walk==0) + throw std::range_error("tree: move_in_as_nth_child position out of range"); + if(n==0) + break; + --n; + walk = walk->next_sibling; + } + if(walk->next_sibling==0) + loc.node->last_child=other_last_head; + else + walk->next_sibling->prev_sibling=other_last_head; + other_last_head->next_sibling=walk->next_sibling; + walk->next_sibling=other_first_head; + other_first_head->prev_sibling=walk; + } + + // Adjust parent pointers. + tree_node *walk=other_first_head; + while(true) { + walk->parent=loc.node; + if(walk==other_last_head) + break; + walk=walk->next_sibling; + } + + // Close other tree. + other.head->next_sibling=other.feet; + other.feet->prev_sibling=other.head; + + return other_first_head; + } + + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::merge(sibling_iterator to1, sibling_iterator to2, + sibling_iterator from1, sibling_iterator from2, + bool duplicate_leaves) + { + sibling_iterator fnd; + while(from1!=from2) { + if((fnd=std::find(to1, to2, (*from1))) != to2) { // element found + if(from1.begin()==from1.end()) { // full depth reached + if(duplicate_leaves) + append_child(parent(to1), (*from1)); + } + else { // descend further + merge(fnd.begin(), fnd.end(), from1.begin(), from1.end(), duplicate_leaves); + } + } + else { // element missing + insert_subtree(to2, from1); + } + ++from1; + } + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::merge(iterator to, iterator from, bool duplicate_leaves) + { + sibling_iterator to1(to); + sibling_iterator to2=to1; + ++to2; + sibling_iterator from1(from); + sibling_iterator from2=from1; + ++from2; + + merge(to1, to2, from1, from2, duplicate_leaves); + } + + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::sort(sibling_iterator from, sibling_iterator to, bool deep) + { + std::less<T> comp; + sort(from, to, comp, deep); + } + +template <class T, class tree_node_allocator> +template <class StrictWeakOrdering> +void tree<T, tree_node_allocator>::sort(sibling_iterator from, sibling_iterator to, + StrictWeakOrdering comp, bool deep) + { + if(from==to) return; + // make list of sorted nodes + // CHECK: if multiset stores equivalent nodes in the order in which they + // are inserted, then this routine should be called 'stable_sort'. + std::multiset<tree_node *, compare_nodes<StrictWeakOrdering> > nodes(comp); + sibling_iterator it=from, it2=to; + while(it != to) { + nodes.insert(it.node); + ++it; + } + // reassemble + --it2; + + // prev and next are the nodes before and after the sorted range + tree_node *prev=from.node->prev_sibling; + tree_node *next=it2.node->next_sibling; + typename std::multiset<tree_node *, compare_nodes<StrictWeakOrdering> >::iterator nit=nodes.begin(), eit=nodes.end(); + if(prev==0) { + if((*nit)->parent!=0) // to catch "sorting the head" situations, when there is no parent + (*nit)->parent->first_child=(*nit); + } + else prev->next_sibling=(*nit); + + --eit; + while(nit!=eit) { + (*nit)->prev_sibling=prev; + if(prev) + prev->next_sibling=(*nit); + prev=(*nit); + ++nit; + } + // prev now points to the last-but-one node in the sorted range + if(prev) + prev->next_sibling=(*eit); + + // eit points to the last node in the sorted range. + (*eit)->next_sibling=next; + (*eit)->prev_sibling=prev; // missed in the loop above + if(next==0) { + if((*eit)->parent!=0) // to catch "sorting the head" situations, when there is no parent + (*eit)->parent->last_child=(*eit); + } + else next->prev_sibling=(*eit); + + if(deep) { // sort the children of each node too + sibling_iterator bcs(*nodes.begin()); + sibling_iterator ecs(*eit); + ++ecs; + while(bcs!=ecs) { + sort(begin(bcs), end(bcs), comp, deep); + ++bcs; + } + } + } + +template <class T, class tree_node_allocator> +template <typename iter> +bool tree<T, tree_node_allocator>::equal(const iter& one_, const iter& two, const iter& three_) const + { + std::equal_to<T> comp; + return equal(one_, two, three_, comp); + } + +template <class T, class tree_node_allocator> +template <typename iter> +bool tree<T, tree_node_allocator>::equal_subtree(const iter& one_, const iter& two_) const + { + std::equal_to<T> comp; + return equal_subtree(one_, two_, comp); + } + +template <class T, class tree_node_allocator> +template <typename iter, class BinaryPredicate> +bool tree<T, tree_node_allocator>::equal(const iter& one_, const iter& two, const iter& three_, BinaryPredicate fun) const + { + pre_order_iterator one(one_), three(three_); + +// if(one==two && is_valid(three) && three.number_of_children()!=0) +// return false; + while(one!=two && is_valid(three)) { + if(!fun(*one,*three)) + return false; + if(one.number_of_children()!=three.number_of_children()) + return false; + ++one; + ++three; + } + return true; + } + +template <class T, class tree_node_allocator> +template <typename iter, class BinaryPredicate> +bool tree<T, tree_node_allocator>::equal_subtree(const iter& one_, const iter& two_, BinaryPredicate fun) const + { + pre_order_iterator one(one_), two(two_); + + if(!fun(*one,*two)) return false; + if(number_of_children(one)!=number_of_children(two)) return false; + return equal(begin(one),end(one),begin(two),fun); + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator> tree<T, tree_node_allocator>::subtree(sibling_iterator from, sibling_iterator to) const + { + assert(from!=to); // if from==to, the range is empty, hence no tree to return. + + tree tmp; + tmp.set_head(value_type()); + tmp.replace(tmp.begin(), tmp.end(), from, to); + return tmp; + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::subtree(tree& tmp, sibling_iterator from, sibling_iterator to) const + { + assert(from!=to); // if from==to, the range is empty, hence no tree to return. + + tmp.set_head(value_type()); + tmp.replace(tmp.begin(), tmp.end(), from, to); + } + +template <class T, class tree_node_allocator> +size_t tree<T, tree_node_allocator>::size() const + { + size_t i=0; + pre_order_iterator it=begin(), eit=end(); + while(it!=eit) { + ++i; + ++it; + } + return i; + } + +template <class T, class tree_node_allocator> +size_t tree<T, tree_node_allocator>::size(const iterator_base& top) const + { + size_t i=0; + pre_order_iterator it=top, eit=top; + eit.skip_children(); + ++eit; + while(it!=eit) { + ++i; + ++it; + } + return i; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::empty() const + { + pre_order_iterator it=begin(), eit=end(); + return (it==eit); + } + +template <class T, class tree_node_allocator> +int tree<T, tree_node_allocator>::depth(const iterator_base& it) + { + tree_node* pos=it.node; + assert(pos!=0); + int ret=0; + while(pos->parent!=0) { + pos=pos->parent; + ++ret; + } + return ret; + } + +template <class T, class tree_node_allocator> +int tree<T, tree_node_allocator>::depth(const iterator_base& it, const iterator_base& root) + { + tree_node* pos=it.node; + assert(pos!=0); + int ret=0; + while(pos->parent!=0 && pos!=root.node) { + pos=pos->parent; + ++ret; + } + return ret; + } + +template <class T, class tree_node_allocator> +template <class Predicate> +int tree<T, tree_node_allocator>::depth(const iterator_base& it, Predicate p) + { + tree_node* pos=it.node; + assert(pos!=0); + int ret=0; + while(pos->parent!=0) { + pos=pos->parent; + if(p(pos)) + ++ret; + } + return ret; + } + +template <class T, class tree_node_allocator> +template <class Predicate> +int tree<T, tree_node_allocator>::distance(const iterator_base& top, const iterator_base& bottom, Predicate p) + { + tree_node* pos=bottom.node; + assert(pos!=0); + int ret=0; + while(pos->parent!=0 && pos!=top.node) { + pos=pos->parent; + if(p(pos)) + ++ret; + } + return ret; + } + +template <class T, class tree_node_allocator> +int tree<T, tree_node_allocator>::max_depth() const + { + int maxd=-1; + for(tree_node *it = head->next_sibling; it!=feet; it=it->next_sibling) + maxd=std::max(maxd, max_depth(it)); + + return maxd; + } + + +template <class T, class tree_node_allocator> +int tree<T, tree_node_allocator>::max_depth(const iterator_base& pos) const + { + tree_node *tmp=pos.node; + + if(tmp==0 || tmp==head || tmp==feet) return -1; + + int curdepth=0, maxdepth=0; + while(true) { // try to walk the bottom of the tree + while(tmp->first_child==0) { + if(tmp==pos.node) return maxdepth; + if(tmp->next_sibling==0) { + // try to walk up and then right again + do { + tmp=tmp->parent; + if(tmp==0) return maxdepth; + --curdepth; + } + while(tmp->next_sibling==0); + } + if(tmp==pos.node) return maxdepth; + tmp=tmp->next_sibling; + } + tmp=tmp->first_child; + ++curdepth; + maxdepth=std::max(curdepth, maxdepth); + } + } + +template <class T, class tree_node_allocator> +unsigned int tree<T, tree_node_allocator>::number_of_children(const iterator_base& it) + { + tree_node *pos=it.node->first_child; + if(pos==0) return 0; + + unsigned int ret=1; +// while(pos!=it.node->last_child) { +// ++ret; +// pos=pos->next_sibling; +// } + while((pos=pos->next_sibling)) + ++ret; + return ret; + } + +template <class T, class tree_node_allocator> +unsigned int tree<T, tree_node_allocator>::number_of_siblings(const iterator_base& it) const + { + tree_node *pos=it.node; + unsigned int ret=0; + // count forward + while(pos->next_sibling && + pos->next_sibling!=head && + pos->next_sibling!=feet) { + ++ret; + pos=pos->next_sibling; + } + // count backward + pos=it.node; + while(pos->prev_sibling && + pos->prev_sibling!=head && + pos->prev_sibling!=feet) { + ++ret; + pos=pos->prev_sibling; + } + + return ret; + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::swap(sibling_iterator it) + { + tree_node *nxt=it.node->next_sibling; + if(nxt) { + if(it.node->prev_sibling) + it.node->prev_sibling->next_sibling=nxt; + else + it.node->parent->first_child=nxt; + nxt->prev_sibling=it.node->prev_sibling; + tree_node *nxtnxt=nxt->next_sibling; + if(nxtnxt) + nxtnxt->prev_sibling=it.node; + else + it.node->parent->last_child=it.node; + nxt->next_sibling=it.node; + it.node->prev_sibling=nxt; + it.node->next_sibling=nxtnxt; + } + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::swap(iterator one, iterator two) + { + // if one and two are adjacent siblings, use the sibling swap + if(one.node->next_sibling==two.node) swap(one); + else if(two.node->next_sibling==one.node) swap(two); + else { + tree_node *nxt1=one.node->next_sibling; + tree_node *nxt2=two.node->next_sibling; + tree_node *pre1=one.node->prev_sibling; + tree_node *pre2=two.node->prev_sibling; + tree_node *par1=one.node->parent; + tree_node *par2=two.node->parent; + + // reconnect + one.node->parent=par2; + one.node->next_sibling=nxt2; + if(nxt2) nxt2->prev_sibling=one.node; + else par2->last_child=one.node; + one.node->prev_sibling=pre2; + if(pre2) pre2->next_sibling=one.node; + else par2->first_child=one.node; + + two.node->parent=par1; + two.node->next_sibling=nxt1; + if(nxt1) nxt1->prev_sibling=two.node; + else par1->last_child=two.node; + two.node->prev_sibling=pre1; + if(pre1) pre1->next_sibling=two.node; + else par1->first_child=two.node; + } + } + +// template <class BinaryPredicate> +// tree<T, tree_node_allocator>::iterator tree<T, tree_node_allocator>::find_subtree( +// sibling_iterator subfrom, sibling_iterator subto, iterator from, iterator to, +// BinaryPredicate fun) const +// { +// assert(1==0); // this routine is not finished yet. +// while(from!=to) { +// if(fun(*subfrom, *from)) { +// +// } +// } +// return to; +// } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::is_in_subtree(const iterator_base& it, const iterator_base& top) const + { + sibling_iterator first=top; + sibling_iterator last=first; + ++last; + return is_in_subtree(it, first, last); + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::is_in_subtree(const iterator_base& it, const iterator_base& begin, + const iterator_base& end) const + { + // FIXME: this should be optimised. + pre_order_iterator tmp=begin; + while(tmp!=end) { + if(tmp==it) return true; + ++tmp; + } + return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::is_valid(const iterator_base& it) const + { + if(it.node==0 || it.node==feet || it.node==head) return false; + else return true; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::is_head(const iterator_base& it) + { + if(it.node->parent==0) return true; + return false; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::iterator tree<T, tree_node_allocator>::lowest_common_ancestor( + const iterator_base& one, const iterator_base& two) const + { + std::set<iterator, iterator_base_less> parents; + + // Walk up from 'one' storing all parents. + iterator walk=one; + do { + walk=parent(walk); + parents.insert(walk); + } + while( walk.node->parent ); + + // Walk up from 'two' until we encounter a node in parents. + walk=two; + do { + walk=parent(walk); + if(parents.find(walk) != parents.end()) break; + } + while( walk.node->parent ); + + return walk; + } + +template <class T, class tree_node_allocator> +unsigned int tree<T, tree_node_allocator>::index(sibling_iterator it) const + { + unsigned int ind=0; + if(it.node->parent==0) { + while(it.node->prev_sibling!=head) { + it.node=it.node->prev_sibling; + ++ind; + } + } + else { + while(it.node->prev_sibling!=0) { + it.node=it.node->prev_sibling; + ++ind; + } + } + return ind; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::sibling(const iterator_base& it, unsigned int num) const + { + tree_node *tmp; + if(it.node->parent==0) { + tmp=head->next_sibling; + while(num) { + tmp = tmp->next_sibling; + --num; + } + } + else { + tmp=it.node->parent->first_child; + while(num) { + assert(tmp!=0); + tmp = tmp->next_sibling; + --num; + } + } + return tmp; + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::debug_verify_consistency() const + { + iterator it=begin(); + while(it!=end()) { + // std::cerr << *it << " (" << it.node << ")" << std::endl; + if(it.node->parent!=0) { + if(it.node->prev_sibling==0) + assert(it.node->parent->first_child==it.node); + else + assert(it.node->prev_sibling->next_sibling==it.node); + if(it.node->next_sibling==0) + assert(it.node->parent->last_child==it.node); + else + assert(it.node->next_sibling->prev_sibling==it.node); + } + ++it; + } + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::child(const iterator_base& it, unsigned int num) + { + tree_node *tmp=it.node->first_child; + while(num--) { + assert(tmp!=0); + tmp=tmp->next_sibling; + } + return tmp; + } + + + + +// Iterator base + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::iterator_base::iterator_base() + : node(0), skip_current_children_(false) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::iterator_base::iterator_base(tree_node *tn) + : node(tn), skip_current_children_(false) + { + } + +template <class T, class tree_node_allocator> +T& tree<T, tree_node_allocator>::iterator_base::operator*() const + { + return node->data; + } + +template <class T, class tree_node_allocator> +T* tree<T, tree_node_allocator>::iterator_base::operator->() const + { + return &(node->data); + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::post_order_iterator::operator!=(const post_order_iterator& other) const + { + if(other.node!=this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::post_order_iterator::operator==(const post_order_iterator& other) const + { + if(other.node==this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::pre_order_iterator::operator!=(const pre_order_iterator& other) const + { + if(other.node!=this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::pre_order_iterator::operator==(const pre_order_iterator& other) const + { + if(other.node==this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::sibling_iterator::operator!=(const sibling_iterator& other) const + { + if(other.node!=this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::sibling_iterator::operator==(const sibling_iterator& other) const + { + if(other.node==this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::leaf_iterator::operator!=(const leaf_iterator& other) const + { + if(other.node!=this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::leaf_iterator::operator==(const leaf_iterator& other) const + { + if(other.node==this->node && other.top_node==this->top_node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::iterator_base::begin() const + { + if(node->first_child==0) + return end(); + + sibling_iterator ret(node->first_child); + ret.parent_=this->node; + return ret; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::iterator_base::end() const + { + sibling_iterator ret(0); + ret.parent_=node; + return ret; + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::iterator_base::skip_children() + { + skip_current_children_=true; + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::iterator_base::skip_children(bool skip) + { + skip_current_children_=skip; + } + +template <class T, class tree_node_allocator> +unsigned int tree<T, tree_node_allocator>::iterator_base::number_of_children() const + { + tree_node *pos=node->first_child; + if(pos==0) return 0; + + unsigned int ret=1; + while(pos!=node->last_child) { + ++ret; + pos=pos->next_sibling; + } + return ret; + } + + + +// Pre-order iterator + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator() + : iterator_base(0) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator(tree_node *tn) + : iterator_base(tn) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator(const iterator_base &other) + : iterator_base(other.node) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator(const sibling_iterator& other) + : iterator_base(other.node) + { + if(this->node==0) { + if(other.range_last()!=0) + this->node=other.range_last(); + else + this->node=other.parent_; + this->skip_children(); + ++(*this); + } + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator++() + { + assert(this->node!=0); + if(!this->skip_current_children_ && this->node->first_child != 0) { + this->node=this->node->first_child; + } + else { + this->skip_current_children_=false; + while(this->node->next_sibling==0) { + this->node=this->node->parent; + if(this->node==0) + return *this; + } + this->node=this->node->next_sibling; + } + return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator--() + { + assert(this->node!=0); + if(this->node->prev_sibling) { + this->node=this->node->prev_sibling; + while(this->node->last_child) + this->node=this->node->last_child; + } + else { + this->node=this->node->parent; + if(this->node==0) + return *this; + } + return *this; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::pre_order_iterator::operator++(int) + { + pre_order_iterator copy = *this; + ++(*this); + return copy; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::next_skip_children() + { + (*this).skip_children(); + (*this)++; + return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::pre_order_iterator::operator--(int) +{ + pre_order_iterator copy = *this; + --(*this); + return copy; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator+=(unsigned int num) + { + while(num>0) { + ++(*this); + --num; + } + return (*this); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator-=(unsigned int num) + { + while(num>0) { + --(*this); + --num; + } + return (*this); + } + + + +// Post-order iterator + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator() + : iterator_base(0) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator(tree_node *tn) + : iterator_base(tn) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator(const iterator_base &other) + : iterator_base(other.node) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator(const sibling_iterator& other) + : iterator_base(other.node) + { + if(this->node==0) { + if(other.range_last()!=0) + this->node=other.range_last(); + else + this->node=other.parent_; + this->skip_children(); + ++(*this); + } + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator++() + { + assert(this->node!=0); + if(this->node->next_sibling==0) { + this->node=this->node->parent; + this->skip_current_children_=false; + } + else { + this->node=this->node->next_sibling; + if(this->skip_current_children_) { + this->skip_current_children_=false; + } + else { + while(this->node->first_child) + this->node=this->node->first_child; + } + } + return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator--() + { + assert(this->node!=0); + if(this->skip_current_children_ || this->node->last_child==0) { + this->skip_current_children_=false; + while(this->node->prev_sibling==0) + this->node=this->node->parent; + this->node=this->node->prev_sibling; + } + else { + this->node=this->node->last_child; + } + return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::post_order_iterator::operator++(int) + { + post_order_iterator copy = *this; + ++(*this); + return copy; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::post_order_iterator::operator--(int) + { + post_order_iterator copy = *this; + --(*this); + return copy; + } + + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator+=(unsigned int num) + { + while(num>0) { + ++(*this); + --num; + } + return (*this); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator-=(unsigned int num) + { + while(num>0) { + --(*this); + --num; + } + return (*this); + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::post_order_iterator::descend_all() + { + assert(this->node!=0); + while(this->node->first_child) + this->node=this->node->first_child; + } + + +// Breadth-first iterator + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::breadth_first_queued_iterator::breadth_first_queued_iterator() + : iterator_base() + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::breadth_first_queued_iterator::breadth_first_queued_iterator(tree_node *tn) + : iterator_base(tn) + { + traversal_queue.push(tn); + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::breadth_first_queued_iterator::breadth_first_queued_iterator(const iterator_base& other) + : iterator_base(other.node) + { + traversal_queue.push(other.node); + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator!=(const breadth_first_queued_iterator& other) const + { + if(other.node!=this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator==(const breadth_first_queued_iterator& other) const + { + if(other.node==this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator& tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator++() + { + assert(this->node!=0); + + // Add child nodes and pop current node + sibling_iterator sib=this->begin(); + while(sib!=this->end()) { + traversal_queue.push(sib.node); + ++sib; + } + traversal_queue.pop(); + if(traversal_queue.size()>0) + this->node=traversal_queue.front(); + else + this->node=0; + return (*this); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator++(int) + { + breadth_first_queued_iterator copy = *this; + ++(*this); + return copy; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator& tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator+=(unsigned int num) + { + while(num>0) { + ++(*this); + --num; + } + return (*this); + } + + + +// Fixed depth iterator + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator() + : iterator_base() + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(tree_node *tn) + : iterator_base(tn), top_node(0) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(const iterator_base& other) + : iterator_base(other.node), top_node(0) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(const sibling_iterator& other) + : iterator_base(other.node), top_node(0) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(const fixed_depth_iterator& other) + : iterator_base(other.node), top_node(other.top_node) + { + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::fixed_depth_iterator::swap(fixed_depth_iterator& first, fixed_depth_iterator& second) + { + std::swap(first.node, second.node); + std::swap(first.skip_current_children_, second.skip_current_children_); + std::swap(first.top_node, second.top_node); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator=(fixed_depth_iterator other) + { + swap(*this, other); + return *this; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::fixed_depth_iterator::operator==(const fixed_depth_iterator& other) const + { + if(other.node==this->node && other.top_node==top_node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::fixed_depth_iterator::operator!=(const fixed_depth_iterator& other) const + { + if(other.node!=this->node || other.top_node!=top_node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator++() + { + assert(this->node!=0); + + if(this->node->next_sibling) { + this->node=this->node->next_sibling; + } + else { + int relative_depth=0; + upper: + do { + if(this->node==this->top_node) { + this->node=0; // FIXME: return a proper fixed_depth end iterator once implemented + return *this; + } + this->node=this->node->parent; + if(this->node==0) return *this; + --relative_depth; + } while(this->node->next_sibling==0); + lower: + this->node=this->node->next_sibling; + while(this->node->first_child==0) { + if(this->node->next_sibling==0) + goto upper; + this->node=this->node->next_sibling; + if(this->node==0) return *this; + } + while(relative_depth<0 && this->node->first_child!=0) { + this->node=this->node->first_child; + ++relative_depth; + } + if(relative_depth<0) { + if(this->node->next_sibling==0) goto upper; + else goto lower; + } + } + return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator--() + { + assert(this->node!=0); + + if(this->node->prev_sibling) { + this->node=this->node->prev_sibling; + } + else { + int relative_depth=0; + upper: + do { + if(this->node==this->top_node) { + this->node=0; + return *this; + } + this->node=this->node->parent; + if(this->node==0) return *this; + --relative_depth; + } while(this->node->prev_sibling==0); + lower: + this->node=this->node->prev_sibling; + while(this->node->last_child==0) { + if(this->node->prev_sibling==0) + goto upper; + this->node=this->node->prev_sibling; + if(this->node==0) return *this; + } + while(relative_depth<0 && this->node->last_child!=0) { + this->node=this->node->last_child; + ++relative_depth; + } + if(relative_depth<0) { + if(this->node->prev_sibling==0) goto upper; + else goto lower; + } + } + return *this; + +// +// +// assert(this->node!=0); +// if(this->node->prev_sibling!=0) { +// this->node=this->node->prev_sibling; +// assert(this->node!=0); +// if(this->node->parent==0 && this->node->prev_sibling==0) // head element +// this->node=0; +// } +// else { +// tree_node *par=this->node->parent; +// do { +// par=par->prev_sibling; +// if(par==0) { // FIXME: need to keep track of this! +// this->node=0; +// return *this; +// } +// } while(par->last_child==0); +// this->node=par->last_child; +// } +// return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::fixed_depth_iterator::operator++(int) + { + fixed_depth_iterator copy = *this; + ++(*this); + return copy; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::fixed_depth_iterator::operator--(int) + { + fixed_depth_iterator copy = *this; + --(*this); + return copy; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator-=(unsigned int num) + { + while(num>0) { + --(*this); + --(num); + } + return (*this); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator+=(unsigned int num) + { + while(num>0) { + ++(*this); + --(num); + } + return *this; + } + + +// Sibling iterator + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator() + : iterator_base() + { + set_parent_(); + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator(tree_node *tn) + : iterator_base(tn) + { + set_parent_(); + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator(const iterator_base& other) + : iterator_base(other.node) + { + set_parent_(); + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator(const sibling_iterator& other) + : iterator_base(other), parent_(other.parent_) + { + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::sibling_iterator::swap(sibling_iterator& first, sibling_iterator& second) + { + std::swap(first.node, second.node); + std::swap(first.skip_current_children_, second.skip_current_children_); + std::swap(first.parent_, second.parent_); + } + + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator=(sibling_iterator other) + { + swap(*this, other); + return *this; + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::sibling_iterator::set_parent_() + { + parent_=0; + if(this->node==0) return; + if(this->node->parent!=0) + parent_=this->node->parent; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator++() + { + if(this->node) + this->node=this->node->next_sibling; + return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator--() + { + if(this->node) this->node=this->node->prev_sibling; + else { + assert(parent_); + this->node=parent_->last_child; + } + return *this; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::sibling_iterator::operator++(int) + { + sibling_iterator copy = *this; + ++(*this); + return copy; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::sibling_iterator::operator--(int) + { + sibling_iterator copy = *this; + --(*this); + return copy; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator+=(unsigned int num) + { + while(num>0) { + ++(*this); + --num; + } + return (*this); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator-=(unsigned int num) + { + while(num>0) { + --(*this); + --num; + } + return (*this); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::tree_node *tree<T, tree_node_allocator>::sibling_iterator::range_first() const + { + tree_node *tmp=parent_->first_child; + return tmp; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::tree_node *tree<T, tree_node_allocator>::sibling_iterator::range_last() const + { + return parent_->last_child; + } + +// Leaf iterator + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator() + : iterator_base(0), top_node(0) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator(tree_node *tn, tree_node *top) + : iterator_base(tn), top_node(top) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator(const iterator_base &other) + : iterator_base(other.node), top_node(0) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator(const sibling_iterator& other) + : iterator_base(other.node), top_node(0) + { + if(this->node==0) { + if(other.range_last()!=0) + this->node=other.range_last(); + else + this->node=other.parent_; + ++(*this); + } + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator++() + { + assert(this->node!=0); + if(this->node->first_child!=0) { // current node is no longer leaf (children got added) + while(this->node->first_child) + this->node=this->node->first_child; + } + else { + while(this->node->next_sibling==0) { + if (this->node->parent==0) return *this; + this->node=this->node->parent; + if (top_node != 0 && this->node==top_node) return *this; + } + this->node=this->node->next_sibling; + while(this->node->first_child) + this->node=this->node->first_child; + } + return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator--() + { + assert(this->node!=0); + while (this->node->prev_sibling==0) { + if (this->node->parent==0) return *this; + this->node=this->node->parent; + if (top_node !=0 && this->node==top_node) return *this; + } + this->node=this->node->prev_sibling; + while(this->node->last_child) + this->node=this->node->last_child; + return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::leaf_iterator::operator++(int) + { + leaf_iterator copy = *this; + ++(*this); + return copy; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::leaf_iterator::operator--(int) + { + leaf_iterator copy = *this; + --(*this); + return copy; + } + + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator+=(unsigned int num) + { + while(num>0) { + ++(*this); + --num; + } + return (*this); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator-=(unsigned int num) + { + while(num>0) { + --(*this); + --num; + } + return (*this); + } + +#endif + +// Local variables: +// tab-width: 3 +// End: diff --git a/core/opengate_core/opengate_lib/tree_util.hh b/core/opengate_core/opengate_lib/tree_util.hh new file mode 100644 index 000000000..bc69594f0 --- /dev/null +++ b/core/opengate_core/opengate_lib/tree_util.hh @@ -0,0 +1,92 @@ +/* + + A collection of miscellaneous utilities that operate on the templated + tree.hh class. + + + Copyright (C) 2001-2009 Kasper Peeters <kasper.peeters@aei.mpg.de> + + (At the moment this only contains a printing utility, thanks to Linda + Buisman <linda.buisman@studentmail.newcastle.edu.au>) + + This program is free software: you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#ifndef tree_util_hh_ +#define tree_util_hh_ + +#include <iostream> +#include "tree.hh" + +namespace kptree { + +template<class T> +void print_tree_bracketed(const tree<T>& t, std::ostream& str=std::cout); + +template<class T> +void print_subtree_bracketed(const tree<T>& t, typename tree<T>::iterator iRoot, + std::ostream& str=std::cout); + + + +// Iterate over all roots (the head) and print each one on a new line +// by calling printSingleRoot. + +template<class T> +void print_tree_bracketed(const tree<T>& t, std::ostream& str) + { + int headCount = t.number_of_siblings(t.begin()); + int headNum = 0; + for(typename tree<T>::sibling_iterator iRoots = t.begin(); iRoots != t.end(); ++iRoots, ++headNum) { + print_subtree_bracketed(t,iRoots,str); + if (headNum != headCount) { + str << std::endl; + } + } + } + + +// Print everything under this root in a flat, bracketed structure. + +template<class T> +void print_subtree_bracketed(const tree<T>& t, typename tree<T>::iterator iRoot, std::ostream& str) + { + if(t.empty()) return; + if (t.number_of_children(iRoot) == 0) { + str << *iRoot; + } + else { + // parent + str << *iRoot; + str << "("; + // child1, ..., childn + int siblingCount = t.number_of_siblings(t.begin(iRoot)); + int siblingNum; + typename tree<T>::sibling_iterator iChildren; + for (iChildren = t.begin(iRoot), siblingNum = 0; iChildren != t.end(iRoot); ++iChildren, ++siblingNum) { + // recursively print child + print_subtree_bracketed(t,iChildren,str); + // comma after every child except the last one + if (siblingNum != siblingCount ) { + str << ", "; + } + } + str << ")"; + } + } + +} + +#endif diff --git a/opengate/actors/actorbuilders.py b/opengate/actors/actorbuilders.py index 166c5f1d3..237fa1239 100644 --- a/opengate/actors/actorbuilders.py +++ b/opengate/actors/actorbuilders.py @@ -20,6 +20,7 @@ BremSplittingActor, ComptSplittingActor, LastVertexInteractionSplittingActor, + LastVertexInteractionSplittingActorOld, ComptPseudoTransportationActor, KillNonInteractingParticleActor, SurfaceSplittingActor, diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index a763e3ae7..cb3baf9b0 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -486,6 +486,41 @@ def initialize(self, volume_engine=None): self.fListOfVolumeAncestor = self.list_of_volume_name print(self.fListOfVolumeAncestor) + +class LastVertexInteractionSplittingActorOld(g4.GateLastVertexInteractionSplittingActorOld, ActorBase): + type_name = "LastVertexInteractionSplittingActorOld" + + def set_default_user_info(user_info): + ActorBase.set_default_user_info(user_info) + deg = g4_units.deg + user_info.splitting_factor = 1 + user_info.russian_roulette_for_angle = False + user_info.rotation_vector_director = False + user_info.vector_director = [0, 0, -1] + user_info.max_theta = 90 * deg + user_info.list_of_volume_name = [] + + def __init__(self, user_info): + ActorBase.__init__(self, user_info) + g4.GateLastVertexInteractionSplittingActorOld.__init__(self, user_info.__dict__) + self.list_of_volume_name = user_info.list_of_volume_name + self.user_info.mother = user_info.mother + + def initialize(self, volume_engine=None): + + super().initialize(volume_engine) + volume_tree = self.simulation.volume_manager.get_volume_tree() + dico_of_volume_tree = {} + for pre, _, node in RenderTree(volume_tree): + dico_of_volume_tree[str(node.name)] = node + volume_name = self.user_info.mother + while volume_name != "world": + node = dico_of_volume_tree[volume_name] + volume_name = node.mother + self.list_of_volume_name.append(volume_name) + self.fListOfVolumeAncestor = self.list_of_volume_name + print(self.fListOfVolumeAncestor) + class ComptPseudoTransportationActor( g4.GateOptrComptPseudoTransportationActor, ActorBase ): diff --git a/opengate/sources/builders.py b/opengate/sources/builders.py index 14914fd8f..6d14ecfe8 100644 --- a/opengate/sources/builders.py +++ b/opengate/sources/builders.py @@ -1,4 +1,4 @@ -from .generic import GenericSource, TemplateSource +from .generic import GenericSource, TemplateSource, LastVertexSource from .voxelsources import VoxelsSource from .gansources import GANSource, GANPairsSource from .beamsources import IonPencilBeamSource, TreatmentPlanPBSource @@ -20,6 +20,7 @@ GANPairsSource, IonPencilBeamSource, TemplateSource, + LastVertexSource, PhaseSpaceSource, TreatmentPlanPBSource, PhotonFromIonDecaySource, diff --git a/opengate/sources/generic.py b/opengate/sources/generic.py index 701de7b59..110fdfc25 100644 --- a/opengate/sources/generic.py +++ b/opengate/sources/generic.py @@ -581,3 +581,21 @@ def initialize(self, run_timing_intervals): # initialize SourceBase.initialize(self, run_timing_intervals) + + +class LastVertexSource(SourceBase): + type_name = "LastVertexSource" + + @staticmethod + def set_default_user_info(user_info): + SourceBase.set_default_user_info(user_info) + user_info.n = 0 + + def create_g4_source(self): + return opengate_core.GateLastVertexSource() + + def __init__(self, user_info): + super().__init__(user_info) + + def initialize(self, run_timing_intervals): + SourceBase.initialize(self, run_timing_intervals) From 957103f7c40cabe6a4e889e82125f6c7fec80480 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Fri, 27 Sep 2024 16:39:06 +0200 Subject: [PATCH 31/82] Add a particle kill according to its direction relative to a specified angle --- ...ateLastVertexInteractionSplittingActor.cpp | 79 +++++++++++-------- .../GateLastVertexInteractionSplittingActor.h | 3 +- opengate/actors/miscactors.py | 2 +- 3 files changed, 51 insertions(+), 33 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index 5e6d10d9c..dba4b8f39 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -59,7 +59,7 @@ GateLastVertexInteractionSplittingActor:: fMotherVolumeName = DictGetStr(user_info, "mother"); fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); - fRussianRouletteForAngle = DictGetBool(user_info, "russian_roulette_for_angle"); + fAngularKill = DictGetBool(user_info, "angular_kill"); fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); fMaxTheta = DictGetDouble(user_info, "max_theta"); fActions.insert("StartSimulationAction"); @@ -89,6 +89,14 @@ void GateLastVertexInteractionSplittingActor::print_tree(const tree<LastVertexDa } +G4bool GateLastVertexInteractionSplittingActor::DoesParticleEmittedInSolidAngle(G4ThreeVector dir, G4ThreeVector vectorDirector, G4double maxTheta) { + G4double cosTheta = vectorDirector * dir; + G4double theta = std::acos(cosTheta); + if (theta > fMaxTheta) + return false; + return true; +} + G4Track* GateLastVertexInteractionSplittingActor::CreateATrackFromContainer(LastVertexDataContainer container, G4Step *step ){ auto *particle_table = G4ParticleTable::GetParticleTable(); @@ -156,14 +164,17 @@ void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step* initStep, G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, *aTrack, gammaWeight); - trackVector->push_back(newTrack); - G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - for (int j = 0; j < NbOfSecondaries; j++) { - G4Track *newTrack = processFinalState->GetSecondary(j); - newTrack->SetWeight(gammaWeight); + if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(newTrack->GetMomentumDirection(),fVectorDirector,fMaxTheta) == false)) + delete newTrack; + else { trackVector->push_back(newTrack); + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + for (int j = 0; j < NbOfSecondaries; j++) { + G4Track *newTrack = processFinalState->GetSecondary(j); + newTrack->SetWeight(gammaWeight); + trackVector->push_back(newTrack); + } } - processFinalState->Clear(); gammaProcessFinalState->Clear(); delete aTrack; @@ -217,6 +228,7 @@ G4VParticleChange* GateLastVertexInteractionSplittingActor::eBremProcessFinalSta void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container) { + G4Track* track = CreateATrackFromContainer(container,initStep); G4String particleName = track->GetParticleDefinition()->GetParticleName(); @@ -236,33 +248,38 @@ void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initS processFinalState = eplusAnnihilProcess->GateplusannihilAtRestDoIt::AtRestDoIt(*track,*initStep); } } + + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); G4int idx = 0; if (NbOfSecondaries >0) { gammaWeight = track->GetWeight()/fSplittingFactor; - - G4double randomIdx = round((NbOfSecondaries -1) * G4UniformRand()); - idx = (G4int) randomIdx; - G4Track *newTrack = processFinalState->GetSecondary(idx); - if (!(isnan((newTrack->GetMomentumDirection())[0]))){ - newTrack->SetWeight(gammaWeight); - newTrack->SetCreatorProcess(process); - trackVector->push_back(newTrack); - } - else{ - delete newTrack; - } - - - for(int i = 0; i < NbOfSecondaries;i++){ - if (i != idx) - delete processFinalState->GetSecondary(i); + G4bool alreadySplitted = false; + for (int i; i < NbOfSecondaries; i++){ + G4Track *newTrack = processFinalState->GetSecondary(i); + G4ThreeVector momentum = newTrack->GetMomentumDirection(); + if (!(isnan(momentum[0]))){ + if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(momentum,fVectorDirector,fMaxTheta) == false)){ + delete newTrack; + } + else if (alreadySplitted == false){ + newTrack->SetWeight(gammaWeight); + newTrack->SetCreatorProcess(process); + trackVector->push_back(newTrack); + alreadySplitted = true; + } + else{ + delete newTrack; + } + } + else { + delete newTrack; } + } } - + delete track; - @@ -476,7 +493,7 @@ void GateLastVertexInteractionSplittingActor::BeginOfEventAction( fTrackIDOfSplittedTrack = -1; fNotSplitted == true; fIsAnnihilAlreadySplit = false; - //std::cout<<fEventID<<std::endl; + std::cout<<fEventID<<std::endl; fCopyInitStep = nullptr; @@ -524,7 +541,9 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { if (fActiveSource != "source_vertex"){ FillOfDataTree(step); if (IsParticleExitTheBiasedVolume(step)){ - fListOfContainer.push_back((*fIterator)); + if ((fAngularKill == false) || ((fAngularKill == true) && (DoesParticleEmittedInSolidAngle(step->GetTrack()->GetMomentumDirection(),fVectorDirector,fMaxTheta) == true))){ + fListOfContainer.push_back((*fIterator)); + } step->GetTrack()->SetTrackStatus(fStopAndKill); //std::cout<<processName<<" "<<step->GetPostStepPoint()->GetMomentumDirection()<<std::endl; //std::cout<<"kill"<<std::endl; @@ -533,7 +552,6 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { if (fActiveSource == "source_vertex"){ - //std::cout<<particleName<<" "<<step->GetPreStepPoint()->GetMomentumDirection()<<std::endl; auto* source = fSourceManager->FindSourceByName(fActiveSource); GateLastVertexSource *vertexSource = (GateLastVertexSource*) source; LastVertexDataContainer container = vertexSource->GetLastVertexContainer(); @@ -600,8 +618,7 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { fCounter ++; } - - + fIsFirstStep = false; diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h index 7e20585c1..f67ae3ccd 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h @@ -50,7 +50,7 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { virtual ~GateLastVertexInteractionSplittingActor() {} G4double fSplittingFactor; - G4bool fRussianRouletteForAngle = false; + G4bool fAngularKill; G4bool fRotationVectorDirector; G4ThreeVector fVectorDirector; G4double fMaxTheta; @@ -104,6 +104,7 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { virtual void PostUserTrackingAction(const G4Track *track) override; // Pure splitting functions + G4bool DoesParticleEmittedInSolidAngle(G4ThreeVector dir, G4ThreeVector vectorDirector, G4double maxTheta); G4Track *CreateComptonTrack(G4ParticleChangeForGamma *, G4Track, G4double); void ComptonSplitting(G4Step* initStep,G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container); void SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container); diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index cb3baf9b0..593047024 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -459,7 +459,7 @@ def set_default_user_info(user_info): ActorBase.set_default_user_info(user_info) deg = g4_units.deg user_info.splitting_factor = 1 - user_info.russian_roulette_for_angle = False + user_info.angular_kill = False user_info.rotation_vector_director = False user_info.vector_director = [0, 0, -1] user_info.max_theta = 90 * deg From 06eae5bb672f1ab9cf791a11a62fd4b27e867a23 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Thu, 3 Oct 2024 18:43:35 +0200 Subject: [PATCH 32/82] bug correction according exiting particle coming back to the biased volume --- ...ateLastVertexInteractionSplittingActor.cpp | 170 +++++++++--------- .../src/test076_last_vertex_splittting.py | 76 +++++--- 2 files changed, 139 insertions(+), 107 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index dba4b8f39..921f57698 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -112,8 +112,10 @@ G4Track* GateLastVertexInteractionSplittingActor::CreateATrackFromContainer(Last G4ThreeVector polarization = container.GetPolarization(); G4DynamicParticle* dynamicParticle = new G4DynamicParticle(particleDefinition,momentum,energy); - G4Track* aTrack = new G4Track(dynamicParticle,step->GetPreStepPoint()->GetGlobalTime(), position); - //std::cout<<aTrack->GetMomentumDirection()<<std::endl; + G4double time = 0; + if (step->GetPreStepPoint() != 0) + time = step->GetPreStepPoint()->GetGlobalTime(); + G4Track* aTrack = new G4Track(dynamicParticle,time, position); aTrack->SetPolarization(polarization); if (trackStatus == 0){ aTrack->SetTrackStatus(fAlive); @@ -133,15 +135,16 @@ G4Track* GateLastVertexInteractionSplittingActor::CreateATrackFromContainer(Last G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack(G4ParticleChangeForGamma *gammaProcess, G4Track track, G4double weight) { + G4double energy = gammaProcess->GetProposedKineticEnergy(); G4double globalTime = track.GetGlobalTime(); - G4double newGammaWeight = weight; + //G4double newGammaWeight = weight; G4ThreeVector polarization = gammaProcess->GetProposedPolarization(); const G4ThreeVector momentum = gammaProcess->GetProposedMomentumDirection(); const G4ThreeVector position = track.GetPosition(); G4Track *newTrack = new G4Track(track); - newTrack->SetWeight(newGammaWeight); + //newTrack->SetWeight(newGammaWeight); newTrack->SetKineticEnergy(energy); newTrack->SetMomentumDirection(momentum); newTrack->SetPosition(position); @@ -153,17 +156,19 @@ void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step* initStep, G4TrackVector *trackVector = CurrentStep->GetfSecondary(); G4double gammaWeight = 0; + G4Track* aTrack = CreateATrackFromContainer(container,initStep); GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; G4VParticleChange *processFinalState = emProcess->PostStepDoIt(*aTrack, *initStep); G4ParticleChangeForGamma *gammaProcessFinalState = (G4ParticleChangeForGamma *)processFinalState; - const G4ThreeVector momentum = gammaProcessFinalState->GetProposedMomentumDirection(); - gammaWeight = aTrack->GetWeight()/ fSplittingFactor; + const G4ThreeVector momentum = gammaProcessFinalState->GetProposedMomentumDirection(); + //gammaWeight = aTrack->GetWeight()/ fSplittingFactor; G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, *aTrack, gammaWeight); + if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(newTrack->GetMomentumDirection(),fVectorDirector,fMaxTheta) == false)) delete newTrack; else { @@ -171,13 +176,18 @@ void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step* initStep, G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); for (int j = 0; j < NbOfSecondaries; j++) { G4Track *newTrack = processFinalState->GetSecondary(j); - newTrack->SetWeight(gammaWeight); + //newTrack->SetWeight(gammaWeight); trackVector->push_back(newTrack); } } + processFinalState->Clear(); gammaProcessFinalState->Clear(); delete aTrack; + + + + } @@ -187,7 +197,6 @@ G4VProcess* GateLastVertexInteractionSplittingActor::GetProcessFromProcessName(G G4ParticleDefinition *particleDefinition = particle_table->FindParticle(particleName); G4ProcessManager *processManager = particleDefinition->GetProcessManager(); G4ProcessVector *processList = processManager->GetProcessList(); - G4VProcess* nullProcess = nullptr; for (size_t i = 0; i < processList->size(); ++i) { auto process = (*processList)[i]; @@ -229,7 +238,7 @@ G4VParticleChange* GateLastVertexInteractionSplittingActor::eBremProcessFinalSta void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container) { - + G4Track* track = CreateATrackFromContainer(container,initStep); G4String particleName = track->GetParticleDefinition()->GetParticleName(); G4TrackVector *trackVector = CurrentStep->GetfSecondary(); @@ -254,7 +263,7 @@ void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initS G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); G4int idx = 0; if (NbOfSecondaries >0) { - gammaWeight = track->GetWeight()/fSplittingFactor; + //gammaWeight = track->GetWeight()/fSplittingFactor; G4bool alreadySplitted = false; for (int i; i < NbOfSecondaries; i++){ G4Track *newTrack = processFinalState->GetSecondary(i); @@ -264,7 +273,7 @@ void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initS delete newTrack; } else if (alreadySplitted == false){ - newTrack->SetWeight(gammaWeight); + //newTrack->SetWeight(gammaWeight); newTrack->SetCreatorProcess(process); trackVector->push_back(newTrack); alreadySplitted = true; @@ -281,10 +290,12 @@ void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initS delete track; + processFinalState->Clear(); + } @@ -425,7 +436,7 @@ G4bool GateLastVertexInteractionSplittingActor::IsParticleExitTheBiasedVolume(G4 if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - + if (logicalVolumeNamePreStep != logicalVolumeNamePostStep){ if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()){ return true; @@ -435,9 +446,6 @@ G4bool GateLastVertexInteractionSplittingActor::IsParticleExitTheBiasedVolume(G4 return false; } */ - else{ - return true; - } } return false; } @@ -493,7 +501,8 @@ void GateLastVertexInteractionSplittingActor::BeginOfEventAction( fTrackIDOfSplittedTrack = -1; fNotSplitted == true; fIsAnnihilAlreadySplit = false; - std::cout<<fEventID<<std::endl; + if (fEventID%50000 == 0) + std::cout<<fEventID<<std::endl; fCopyInitStep = nullptr; @@ -508,9 +517,11 @@ void GateLastVertexInteractionSplittingActor::PreUserTrackingAction( } void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { + G4String logicalVolumeNamePreStep = "None"; G4String logicalVolumeNamePostStep = "None"; + if (step->GetPreStepPoint()->GetPhysicalVolume() != 0) logicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) @@ -525,18 +536,7 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) processName = step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); - - /* - if (processName == "annihil"){ - std::cout<<"ANNIHIL"<<" "<<step->GetTrack()->GetTrackStatus()<<std::endl; - std::cout<<step->GetPreStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetTotalEnergyDeposit()<<std::endl; - auto* secondaries = step->GetSecondaryInCurrentStep(); - for (const G4Track* secondary : *secondaries){ - std::cout<<secondary->GetKineticEnergy()<<std::endl; - } - - } - */ + if (fActiveSource != "source_vertex"){ FillOfDataTree(step); @@ -545,78 +545,88 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { fListOfContainer.push_back((*fIterator)); } step->GetTrack()->SetTrackStatus(fStopAndKill); - //std::cout<<processName<<" "<<step->GetPostStepPoint()->GetMomentumDirection()<<std::endl; - //std::cout<<"kill"<<std::endl; } } if (fActiveSource == "source_vertex"){ + auto* source = fSourceManager->FindSourceByName(fActiveSource); GateLastVertexSource *vertexSource = (GateLastVertexSource*) source; LastVertexDataContainer container = vertexSource->GetLastVertexContainer(); - G4ThreeVector momentum = step->GetPreStepPoint()->GetMomentumDirection(); - G4Step* copyInitStep = nullptr; - - G4String processToSplit = vertexSource->GetProcessToSplit(); - if (((step->GetTrack()->GetParentID() == 0)&& (step->GetTrack()->GetTrackID() == 1))|| ((creatorProcessName == "annihil") && (step->GetTrack()->GetParentID() == 1))){ - - if ((processToSplit != "annihil") || ((processToSplit == "annihil")&& (fIsAnnihilAlreadySplit ==false))){ - - step->GetfSecondary()->clear(); - if ((processToSplit != "msc") && (processToSplit != "conv")) { - fCopyInitStep= new G4Step(*step); - if (processToSplit == "eBrem"){ - fCopyInitStep->SetStepLength(container.GetStepLength()); - fCopyInitStep->GetPreStepPoint()->SetKineticEnergy(container.GetEnergy()); - + if ((step->GetTrack()->GetWeight() == container.GetWeight())){ + G4ThreeVector momentum = step->GetPreStepPoint()->GetMomentumDirection(); + G4String processToSplit = vertexSource->GetProcessToSplit(); + if (((step->GetTrack()->GetParentID() == 0)&& (step->GetTrack()->GetTrackID() == 1))|| ((creatorProcessName == "annihil") && (step->GetTrack()->GetParentID() == 1))){ + + if ((processToSplit != "annihil") || ((processToSplit == "annihil")&& (fIsAnnihilAlreadySplit ==false))){ + + step->GetfSecondary()->clear(); + //FIXME : list of process which are not splitable yet + if ((processToSplit != "msc") && (processToSplit != "conv") && (processToSplit != "eIoni")) { + fCopyInitStep= new G4Step(*step); + if (processToSplit == "eBrem"){ + fCopyInitStep->SetStepLength(container.GetStepLength()); + fCopyInitStep->GetPreStepPoint()->SetKineticEnergy(container.GetEnergy()); + + } + while (step->GetfSecondary()->size() != 1){ + step->GetfSecondary()->clear(); + CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); + + } } - while (step->GetfSecondary()->size() == 0){ - CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); - + step->GetTrack()->SetTrackStatus(fStopAndKill); + + if (processToSplit == "annihil"){ + fIsAnnihilAlreadySplit = true; + } } + + + else if ((processToSplit == "annihil")&& (fIsAnnihilAlreadySplit == true)){ + step->GetfSecondary()->clear(); + step->GetTrack()->SetTrackStatus(fStopAndKill); } - step->GetTrack()->SetTrackStatus(fStopAndKill); - if (processToSplit == "annihil"){ - fIsAnnihilAlreadySplit = true; - } } - else if ((processToSplit == "annihil")&& (fIsAnnihilAlreadySplit == true)){ + + + else if (IsTheParticleUndergoesAProcess(step)){ step->GetfSecondary()->clear(); + while (step->GetfSecondary()->size() != 1){ + step->GetfSecondary()->clear(); + CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); + } step->GetTrack()->SetTrackStatus(fStopAndKill); + } - } - - else if (IsTheParticleUndergoesAProcess(step)){ - step->GetfSecondary()->clear(); - while (step->GetfSecondary()->size() == 0){ - CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); - } - step->GetTrack()->SetTrackStatus(fStopAndKill); - - } - + else if (IsParticleExitTheBiasedVolume(step)){ + fSplitCounter += 1; + if (fSplitCounter < fSplittingFactor){ + while (step->GetfSecondary()->size() != 1){ + step->GetfSecondary()->clear(); + CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); - else if (IsParticleExitTheBiasedVolume(step)){ - fSplitCounter += 1; - if (fSplitCounter < fSplittingFactor){ - while (step->GetfSecondary()->size() == 0){ - CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); - } - } - else{ - delete fCopyInitStep; - fSplitCounter = 0; - fCounter = 0; - } - + } + } + else{ + delete fCopyInitStep; + fCopyInitStep = nullptr; + fSplitCounter = 0; + } + + + //FIXME Debug case if splitting factor equal to 1, as It is used as a condition to enable the split + // I just set the weight to a very close value of the real one + if (fSplittingFactor != 1) + step->GetPostStepPoint()->SetWeight(container.GetWeight()/fSplittingFactor); + else + step->GetPostStepPoint()->SetWeight(container.GetWeight()*0.99999999); + } } - - fCounter ++; - } fIsFirstStep = false; diff --git a/opengate/tests/src/test076_last_vertex_splittting.py b/opengate/tests/src/test076_last_vertex_splittting.py index dbf9d5fa9..078452349 100755 --- a/opengate/tests/src/test076_last_vertex_splittting.py +++ b/opengate/tests/src/test076_last_vertex_splittting.py @@ -97,7 +97,7 @@ def validation_test(arr_ref, arr_data, nb_split, tol=0.2, tol_weights=0.04): if __name__ == "__main__": paths = utility.get_default_test_paths( - __file__, "test071test_operator_compt_splitting", output_folder="test071" + __file__, "test076_last_vertex_splitting", output_folder="test076" ) # create the simulation @@ -113,7 +113,10 @@ def validation_test(arr_ref, arr_data, nb_split, tol=0.2, tol_weights=0.04): ui.number_of_threads = 1 # 1236566 seg fault #12745 double compt - ui.random_seed = 73 + # ui.random_seed = 73 + # ui.random_seed = 2632 + # ui.random_seed = 45 + ui.random_seed = 444 # units m = gate.g4_units.m @@ -131,7 +134,7 @@ def validation_test(arr_ref, arr_data, nb_split, tol=0.2, tol_weights=0.04): # adapt world size world = sim.world world.size = [0.25 * m, 0.25 * m, 0.25 * m] - world.material = "G4_WATER" + world.material = "G4_Galactic" ####### GEOMETRY TO IRRADIATE ############# sim.volume_manager.material_database.add_material_weights( @@ -157,40 +160,57 @@ def validation_test(arr_ref, arr_data, nb_split, tol=0.2, tol_weights=0.04): "xyz", [angle_y, angle_y, angle_z], degrees=True ).as_matrix() W_tubs.rotation = rotation + bias = True - ####### Compton Splitting ACTOR ######### - nb_split = 25 - vertex_splitting_actor = sim.add_actor("LastVertexInteractionSplittingActor", "vertexSplittingW") - vertex_splitting_actor.mother = W_tubs.name - vertex_splitting_actor.splitting_factor = nb_split - vertex_splitting_actor.russian_roulette_for_angle = False + if bias : + ###### Last vertex Splitting ACTOR ######### + nb_split = 25 + vertex_splitting_actor = sim.add_actor("LastVertexInteractionSplittingActor", "vertexSplittingW") + vertex_splitting_actor.mother = W_tubs.name + vertex_splitting_actor.splitting_factor = nb_split + vertex_splitting_actor.angular_kill = True + vertex_splitting_actor.max_theta = 15*deg ##### PHASE SPACE plan ######" - plan_tubs = sim.add_volume("Tubs", "phsp_tubs") - plan_tubs.material = "G4_Galactic" - plan_tubs.mother = world.name - plan_tubs.rmin = W_tubs.rmax + 1*cm - plan_tubs.rmax = plan_tubs.rmin + 1 * nm - plan_tubs.dz = 0.05 * m - plan_tubs.color = [0.2, 1, 0.8, 1] - plan_tubs.rotation = rotation - - ####### Electron source ########### + # plan_tubs = sim.add_volume("Tubs", "phsp_tubs") + # plan_tubs.material = "G4_Galactic" + # plan_tubs.mother = world.name + # plan_tubs.rmin = W_tubs.rmax + 1*cm + # plan_tubs.rmax = plan_tubs.rmin + 1 * nm + # plan_tubs.dz = 0.05 * m + # plan_tubs.color = [0.2, 1, 0.8, 1] + # plan_tubs.rotation = rotation + + plan = sim.add_volume("Box", "plan_phsp") + plan.material = "G4_Galactic" + plan.size = [5*cm,5*cm,1*nm] + plan.translation = [0,0,-1*cm] + + + ####### gamma source ########### source = sim.add_source("GenericSource", "source1") source.particle = "gamma" - source.n = 1 + if bias : + source.n = 100 + else : + source.n = 10000000 source.position.type = "sphere" source.position.radius = 1 * nm source.direction.type = "momentum" # source.direction.momentum = [0,0,-1] source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) source.energy.type = "mono" - source.energy.mono = 4* MeV + source.energy.mono = 4 * MeV + # + ###### LastVertexSource ############# + if bias : + source_0 = sim.add_source("LastVertexSource", "source_vertex") + source_0.n = 1 ####### PHASE SPACE ACTOR ############## phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") - phsp_actor.mother = plan_tubs.name + phsp_actor.mother = plan.name phsp_actor.attributes = [ "EventID", "TrackID", @@ -198,12 +218,14 @@ def validation_test(arr_ref, arr_data, nb_split, tol=0.2, tol_weights=0.04): "ParticleName", "KineticEnergy", "PreDirection", + "PrePosition", "TrackCreatorProcess", ] + if bias : + phsp_actor.output = paths.output / ("test076_output_data_last_vertex_osef.root") + else : + phsp_actor.output = paths.output / ("test076_output_data_last_vertex_ref.root") - phsp_actor.output = paths.output / "test075_output_data.root" - - ##### MODIFIED PHYSICS LIST ############### s = sim.add_actor("SimulationStatisticsActor", "Stats") s.track_types_flag = True @@ -215,8 +237,8 @@ def validation_test(arr_ref, arr_data, nb_split, tol=0.2, tol_weights=0.04): sim.add_g4_command_before_init(s) sim.physics_manager.global_production_cuts.gamma = 1 * mm - sim.physics_manager.global_production_cuts.electron = 1 * km - sim.physics_manager.global_production_cuts.positron = 1 * um + sim.physics_manager.global_production_cuts.electron = 1000 * km + sim.physics_manager.global_production_cuts.positron = 1000 * km output = sim.run() From 61745551341a775ef53d1e5a982d169a8cf6c78e Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 15 Oct 2024 15:38:42 +0200 Subject: [PATCH 33/82] Modif of container structure to optimize time simulation --- ...ateLastVertexInteractionSplittingActor.cpp | 157 +++++++------ .../GateLastVertexInteractionSplittingActor.h | 1 + .../opengate_lib/GateLastVertexSource.cpp | 14 +- .../GateLastVertexSplittingDataContainer.h | 217 ++---------------- .../GateLastVertexSplittingPostStepDoIt.h | 8 + .../GateLastVertexSplittingSimpleContainer.h | 217 ++++++++++++++++++ 6 files changed, 334 insertions(+), 280 deletions(-) create mode 100644 core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index 921f57698..22cd2800b 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -98,8 +98,9 @@ G4bool GateLastVertexInteractionSplittingActor::DoesParticleEmittedInSolidAngle( } -G4Track* GateLastVertexInteractionSplittingActor::CreateATrackFromContainer(LastVertexDataContainer container, G4Step *step ){ - auto *particle_table = G4ParticleTable::GetParticleTable(); +G4Track* GateLastVertexInteractionSplittingActor::CreateATrackFromContainer(LastVertexDataContainer theContainer, G4Step *step ){ + auto *particle_table = G4ParticleTable::GetParticleTable(); + SimpleContainer container = theContainer.GetContainerToSplit(); G4ParticleDefinition *particleDefinition = particle_table->FindParticle(container.GetParticleNameToSplit()); G4ThreeVector momentum = container.GetMomentum(); G4double energy = container.GetEnergy(); @@ -236,10 +237,11 @@ G4VParticleChange* GateLastVertexInteractionSplittingActor::eBremProcessFinalSta } -void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container) { +void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer theContainer) { - G4Track* track = CreateATrackFromContainer(container,initStep); + G4Track* track = CreateATrackFromContainer(theContainer,initStep); + SimpleContainer container = theContainer.GetContainerToSplit(); G4String particleName = track->GetParticleDefinition()->GetParticleName(); G4TrackVector *trackVector = CurrentStep->GetfSecondary(); G4double gammaWeight = 0; @@ -299,18 +301,19 @@ void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initS } -void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G4Step* initStep,G4Step *step,LastVertexDataContainer container) { +void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G4Step* initStep,G4Step *step,LastVertexDataContainer theContainer) { // We retrieve the process associated to the process name to split and we // split according the process. Since for compton scattering, the gamma is not // a secondary particles, this one need to have his own splitting function. + SimpleContainer container = theContainer.GetContainerToSplit(); G4VProcess* processToSplit = GetProcessFromProcessName(container.GetParticleNameToSplit(),container.GetProcessNameToSplit()); G4String processName = container.GetProcessNameToSplit(); if (processName == "compt") { - ComptonSplitting(initStep,step, processToSplit, container); + ComptonSplitting(initStep,step, processToSplit, theContainer); } else if((processName != "msc") && (processName != "conv")){ - SecondariesSplitting(initStep, step, processToSplit, container); + SecondariesSplitting(initStep, step, processToSplit, theContainer); } } @@ -397,7 +400,6 @@ void GateLastVertexInteractionSplittingActor::FillOfDataTree(G4Step*step){ } } - LastVertexDataContainer* container = &(*fIterator); G4int trackID = container->GetTrackID(); if ((processName != "Transportation") &&(processName !="None") && (processName !="Rayl")){ @@ -420,8 +422,10 @@ void GateLastVertexInteractionSplittingActor::FillOfDataTree(G4Step*step){ if (((processName == "annihil"))){ energy -= (step->GetTotalEnergyDeposit()); } - container->SetSplittingParameters(processName,energy, momentum, position,polarization,particleName,weight,trackStatus,nbOfSecondaries,annihilFlag,stepLength,prePosition); - container->PushListOfSplittingParameters(processName,energy, momentum, position,polarization,particleName,weight,trackStatus,nbOfSecondaries,annihilFlag,stepLength,prePosition); + SimpleContainer containerToSplit = SimpleContainer(processName,energy, momentum, position,polarization,particleName,weight,trackStatus,nbOfSecondaries,annihilFlag,stepLength,prePosition); + container->SetContainerToSplit(containerToSplit); + container->PushListOfSplittingParameters(); + } } } @@ -548,84 +552,86 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { } } - - if (fActiveSource == "source_vertex"){ - - auto* source = fSourceManager->FindSourceByName(fActiveSource); - GateLastVertexSource *vertexSource = (GateLastVertexSource*) source; - LastVertexDataContainer container = vertexSource->GetLastVertexContainer(); - if ((step->GetTrack()->GetWeight() == container.GetWeight())){ - G4ThreeVector momentum = step->GetPreStepPoint()->GetMomentumDirection(); - G4String processToSplit = vertexSource->GetProcessToSplit(); - if (((step->GetTrack()->GetParentID() == 0)&& (step->GetTrack()->GetTrackID() == 1))|| ((creatorProcessName == "annihil") && (step->GetTrack()->GetParentID() == 1))){ - - if ((processToSplit != "annihil") || ((processToSplit == "annihil")&& (fIsAnnihilAlreadySplit ==false))){ - - step->GetfSecondary()->clear(); - //FIXME : list of process which are not splitable yet - if ((processToSplit != "msc") && (processToSplit != "conv") && (processToSplit != "eIoni")) { - fCopyInitStep= new G4Step(*step); - if (processToSplit == "eBrem"){ - fCopyInitStep->SetStepLength(container.GetStepLength()); - fCopyInitStep->GetPreStepPoint()->SetKineticEnergy(container.GetEnergy()); + if (fOnlyTree == false){ + if (fActiveSource == "source_vertex"){ + + auto* source = fSourceManager->FindSourceByName(fActiveSource); + GateLastVertexSource *vertexSource = (GateLastVertexSource*) source; + LastVertexDataContainer container = vertexSource->GetLastVertexContainer(); + if ((step->GetTrack()->GetWeight() == container.GetContainerToSplit().GetWeight())){ + G4ThreeVector momentum = step->GetPreStepPoint()->GetMomentumDirection(); + G4String processToSplit = vertexSource->GetProcessToSplit(); + if (((step->GetTrack()->GetParentID() == 0)&& (step->GetTrack()->GetTrackID() == 1))|| ((creatorProcessName == "annihil") && (step->GetTrack()->GetParentID() == 1))){ + + if ((processToSplit != "annihil") || ((processToSplit == "annihil")&& (fIsAnnihilAlreadySplit ==false))){ + + step->GetfSecondary()->clear(); + //FIXME : list of process which are not splitable yet + if ((processToSplit != "msc") && (processToSplit != "conv") && (processToSplit != "eIoni")) { + fCopyInitStep= new G4Step(*step); + if (processToSplit == "eBrem"){ + fCopyInitStep->SetStepLength(container.GetContainerToSplit().GetStepLength()); + fCopyInitStep->GetPreStepPoint()->SetKineticEnergy(container.GetContainerToSplit().GetEnergy()); + + } + while (step->GetfSecondary()->size() != 1){ + step->GetfSecondary()->clear(); + CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); + + } } - while (step->GetfSecondary()->size() != 1){ - step->GetfSecondary()->clear(); - CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); - + step->GetTrack()->SetTrackStatus(fStopAndKill); + + if (processToSplit == "annihil"){ + fIsAnnihilAlreadySplit = true; + } } + + + else if ((processToSplit == "annihil")&& (fIsAnnihilAlreadySplit == true)){ + step->GetfSecondary()->clear(); + step->GetTrack()->SetTrackStatus(fStopAndKill); } - step->GetTrack()->SetTrackStatus(fStopAndKill); - if (processToSplit == "annihil"){ - fIsAnnihilAlreadySplit = true; - } } - - else if ((processToSplit == "annihil")&& (fIsAnnihilAlreadySplit == true)){ + + else if (IsTheParticleUndergoesAProcess(step)){ step->GetfSecondary()->clear(); + while (step->GetfSecondary()->size() != 1){ + step->GetfSecondary()->clear(); + CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); + } step->GetTrack()->SetTrackStatus(fStopAndKill); + } - } - - - else if (IsTheParticleUndergoesAProcess(step)){ - step->GetfSecondary()->clear(); - while (step->GetfSecondary()->size() != 1){ - step->GetfSecondary()->clear(); - CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); - } - step->GetTrack()->SetTrackStatus(fStopAndKill); - - } - - else if (IsParticleExitTheBiasedVolume(step)){ - fSplitCounter += 1; - if (fSplitCounter < fSplittingFactor){ - while (step->GetfSecondary()->size() != 1){ - step->GetfSecondary()->clear(); - CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); + else if (IsParticleExitTheBiasedVolume(step)){ + fSplitCounter += 1; + if (fSplitCounter < fSplittingFactor){ + while (step->GetfSecondary()->size() != 1){ + step->GetfSecondary()->clear(); + CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); - } - } - else{ - delete fCopyInitStep; - fCopyInitStep = nullptr; - fSplitCounter = 0; - } - + } + } + else{ + delete fCopyInitStep; + fCopyInitStep = nullptr; + fSplitCounter = 0; + } + - //FIXME Debug case if splitting factor equal to 1, as It is used as a condition to enable the split - // I just set the weight to a very close value of the real one - if (fSplittingFactor != 1) - step->GetPostStepPoint()->SetWeight(container.GetWeight()/fSplittingFactor); - else - step->GetPostStepPoint()->SetWeight(container.GetWeight()*0.99999999); - } + //FIXME Debug case if splitting factor equal to 1, as It is used as a condition to enable the split + // I just set the weight to a very close value of the real one + if (fSplittingFactor != 1) + step->GetPostStepPoint()->SetWeight(container.GetContainerToSplit().GetWeight()/fSplittingFactor); + else + step->GetPostStepPoint()->SetWeight(container.GetContainerToSplit().GetWeight()*0.99999999); + } + } } } @@ -655,12 +661,15 @@ void GateLastVertexInteractionSplittingActor::EndOfEventAction( fListOfContainer.clear(); } + if (fOnlyTree == false){ + auto* source = fSourceManager->FindSourceByName("source_vertex"); GateLastVertexSource* vertexSource = (GateLastVertexSource*) source; if (vertexSource->GetNumberOfGeneratedEvent() < vertexSource->GetNumberOfEventToSimulate()){ fSourceManager->SetActiveSourcebyName("source_vertex"); } fActiveSource = fSourceManager->GetActiveSourceName(); + } } diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h index f67ae3ccd..4957e4f90 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h @@ -70,6 +70,7 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { G4String fActiveSource = "None"; G4bool fIsAnnihilAlreadySplit =false; G4int fCounter; + G4bool fOnlyTree = true; GateLastVertexSource* fVertexSource = nullptr; tree<LastVertexDataContainer> fTree; tree<LastVertexDataContainer>::post_order_iterator fIterator; diff --git a/core/opengate_core/opengate_lib/GateLastVertexSource.cpp b/core/opengate_core/opengate_lib/GateLastVertexSource.cpp index e89c5a728..b903d90c8 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSource.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexSource.cpp @@ -74,17 +74,17 @@ void GateLastVertexSource::GenerateOnePrimary(G4Event* event, double current_sim } else { - - G4double energy = fListOfContainer[idx].GetEnergy(); + SimpleContainer containerToSplit = fListOfContainer[idx].GetContainerToSplit(); + G4double energy = containerToSplit.GetEnergy(); if (energy < 0){ energy = 0; } fContainer = fListOfContainer[idx]; - G4ThreeVector position = fListOfContainer[idx].GetVertexPosition(); - G4ThreeVector momentum = fListOfContainer[idx].GetMomentum(); - G4String particleName = fListOfContainer[idx].GetParticleNameToSplit(); - G4double weight =fListOfContainer[idx].GetWeight(); - fProcessToSplit = fListOfContainer[idx].GetProcessNameToSplit(); + G4ThreeVector position = containerToSplit.GetVertexPosition(); + G4ThreeVector momentum = containerToSplit.GetMomentum(); + G4String particleName = containerToSplit.GetParticleNameToSplit(); + G4double weight =containerToSplit.GetWeight(); + fProcessToSplit = containerToSplit.GetProcessNameToSplit(); auto &l = fThreadLocalData.Get(); auto *particle_table = G4ParticleTable::GetParticleTable(); diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h index 112cc8062..0d63bd80a 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h @@ -45,96 +45,18 @@ #include "G4eplusAnnihilationEntanglementClipBoard.hh" #include "G4EmParameters.hh" #include "G4PhysicsModelCatalog.hh" +#include "GateLastVertexSplittingSimpleContainer.h" class LastVertexDataContainer{ public : -LastVertexDataContainer(G4ThreeVector interactionPosition, G4ThreeVector momentum,G4ThreeVector polarization, G4double energy,G4int trackID,G4String creationProcessName){ - - fPositionToSplit= interactionPosition; - fMomentumToSplit = momentum; - fEnergyToSplit = energy; - fIsExit = false; - fToRegenerate = false; - fTrackID = trackID; - fCreationProcessName = creationProcessName; - fPolarizationToSplit = polarization; - -} - LastVertexDataContainer(){} ~LastVertexDataContainer(){} -void SetProcessNameToSplit(G4String processName){ - fProcessNameToSplit = processName; -} - -G4String GetProcessNameToSplit(){ - return fProcessNameToSplit; -} - -void SetEnergy(G4double energy){ - fEnergyToSplit =energy; -} - -G4double GetEnergy(){ - return fEnergyToSplit; -} - - -void SetWeight(G4double weight){ - fWeightToSplit =weight; -} - -G4double GetWeight(){ - return fWeightToSplit; -} - -void SetPolarization(G4ThreeVector polarization){ - fPolarizationToSplit = polarization; -} - -G4ThreeVector GetPolarization(){ - return fPolarizationToSplit; -} - -void SetMomentum(G4ThreeVector momentum){ - fMomentumToSplit =momentum; -} - -G4ThreeVector GetMomentum(){ - return fMomentumToSplit; -} - -void SetVertexPosition(G4ThreeVector position){ - fPositionToSplit = position; -} - -G4ThreeVector GetVertexPosition(){ - return fPositionToSplit; -} - -void SetExitingStatus(G4bool isExit){ - fIsExit = isExit; -} - -G4bool GetExitingStatus(){ - return fIsExit; -} - - -void SetRegenerationStatus(G4bool toRegenerate){ - fToRegenerate= toRegenerate; -} - -G4bool GetRegenerationStatus(){ - return fToRegenerate; -} - void SetTrackID(G4int trackID ){ @@ -154,14 +76,6 @@ G4String GetParticleName(){ return fParticleName; } -void SetParticleNameToSplit(G4String name){ - fParticleNameToSplit = name; -} - -G4String GetParticleNameToSplit(){ - return fParticleNameToSplit; -} - void SetCreationProcessName(G4String creationProcessName){ fCreationProcessName = creationProcessName; @@ -172,95 +86,43 @@ G4String GetCreationProcessName(){ } - -void SetTrackStatus(G4int trackStatus){ - fTrackStatusToSplit = trackStatus; -} - - -G4int GetTrackStatus(){ - return fTrackStatusToSplit; -} - - -void SetNbOfSecondaries(G4int nbSec){ - fNumberOfSecondariesToSplit = nbSec; -} - -G4int GetNbOfSecondaries(){ - return fNumberOfSecondariesToSplit; -} - -void SetAnnihilationFlag(G4String flag){ - fAnnihilProcessFlag = flag; +void SetContainerToSplit(SimpleContainer container){ + fContainerToSplit = container; } -G4String GetAnnihilationFlag(){ - return fAnnihilProcessFlag; -} - -void SetStepLength(G4double length){ - fStepLength = length; -} -G4double GetStepLength(){ - return fStepLength; +SimpleContainer GetContainerToSplit(){ + return fContainerToSplit; } -void SetSplittingParameters(G4String processName,G4double energy,G4ThreeVector momentum, G4ThreeVector position,G4ThreeVector polarization,G4String name,G4double weight,G4int trackStatus,G4int nbSec,G4String flag,G4double length, G4ThreeVector prePos){ - fProcessNameToSplit = processName; - fEnergyToSplit = energy; - fMomentumToSplit = momentum; - fPositionToSplit = position; - fPolarizationToSplit = polarization; - fParticleNameToSplit = name; - fWeightToSplit = weight; - fTrackStatusToSplit = trackStatus; - fNumberOfSecondariesToSplit = nbSec; - fAnnihilProcessFlag = flag; - fStepLength = length; - fPrePosition = prePos; +void PushListOfSplittingParameters(){ + fVectorOfContainerToSplit.emplace_back(fContainerToSplit); } - -void PushListOfSplittingParameters(G4String processName,G4double energy,G4ThreeVector momentum, G4ThreeVector position,G4ThreeVector polarization,G4String name,G4double weight, G4int trackStatus,G4int nbSec, G4String flag,G4double length,G4ThreeVector prePos){ - fVectorOfProcessToSplit.push_back(processName); - fVectorOfEnergyToSplit.push_back(energy); - fVectorOfMomentumToSplit.push_back(momentum); - fVectorOfPositionToSplit.push_back(position); - fVectorOfPolarizationToSplit.push_back(polarization); - fVectorOfParticleNameToSplit.push_back(name); - fVectorOfWeightToSplit.push_back(weight); - fVectorOfTrackStatusToSplit.push_back(trackStatus); - fVectorOfNumberOfSecondariesToSplit.push_back(nbSec); - fVectorOfAnnihilProcessFlag.push_back(flag); - fVectorOfStepLength.push_back(length); - fVectorOfPrePosition.push_back(prePos); - -} - - LastVertexDataContainer ContainerFromParentInformation(G4Step* step){ LastVertexDataContainer aContainer = LastVertexDataContainer(); + aContainer.fTrackID = step->GetTrack()->GetTrackID(); aContainer.fParticleName = step->GetTrack()->GetDefinition()->GetParticleName(); - if (this->fProcessNameToSplit != "None"){ - if (this->fVectorOfProcessToSplit.size() !=0){ + if (this->fContainerToSplit.GetProcessNameToSplit() != "None"){ + if (this->fVectorOfContainerToSplit.size() !=0){ G4ThreeVector vertexPosition = step->GetTrack()->GetVertexPosition(); - for (int i =0;i<this->fVectorOfPositionToSplit.size();i++){ - if (vertexPosition == this->fVectorOfPositionToSplit[i]){ - aContainer.SetSplittingParameters(this->fVectorOfProcessToSplit[i],this->fVectorOfEnergyToSplit[i],this->fVectorOfMomentumToSplit[i],this->fVectorOfPositionToSplit[i],this->fVectorOfPolarizationToSplit[i],this->fVectorOfParticleNameToSplit[i],this->fVectorOfWeightToSplit[i], this->fVectorOfTrackStatusToSplit[i], this->fVectorOfNumberOfSecondariesToSplit[i], this->fVectorOfAnnihilProcessFlag[i],this->fVectorOfStepLength[i],this->fVectorOfPrePosition[i]); + for (int i =0;i<this->fVectorOfContainerToSplit.size();i++){ + if (vertexPosition == this->fVectorOfContainerToSplit[i].GetVertexPosition()){ + SimpleContainer tmpContainer = this->fVectorOfContainerToSplit[i]; + fContainerToSplit = SimpleContainer(tmpContainer.GetProcessNameToSplit(),tmpContainer.GetEnergy(),tmpContainer.GetMomentum(),tmpContainer.GetVertexPosition(),tmpContainer.GetPolarization(),tmpContainer.GetParticleNameToSplit(),tmpContainer.GetWeight(),tmpContainer.GetTrackStatus(),tmpContainer.GetNbOfSecondaries(),tmpContainer.GetAnnihilationFlag(),tmpContainer.GetStepLength(), tmpContainer.GetPrePositionToSplit()); return aContainer; } } } else{ - aContainer.SetSplittingParameters(this->fProcessNameToSplit,this->fEnergyToSplit,this->fMomentumToSplit,this->fPositionToSplit,this->fPolarizationToSplit,this->fParticleNameToSplit,this->fWeightToSplit, this->fTrackStatusToSplit, this->fNumberOfSecondariesToSplit, this->fAnnihilProcessFlag,this->fStepLength,this->fPrePosition); + SimpleContainer tmpContainer = this->fContainerToSplit; + fContainerToSplit = SimpleContainer(tmpContainer.GetProcessNameToSplit(),tmpContainer.GetEnergy(),tmpContainer.GetMomentum(),tmpContainer.GetVertexPosition(),tmpContainer.GetPolarization(),tmpContainer.GetParticleNameToSplit(),tmpContainer.GetWeight(),tmpContainer.GetTrackStatus(),tmpContainer.GetNbOfSecondaries(),tmpContainer.GetAnnihilationFlag(),tmpContainer.GetStepLength(),tmpContainer.GetPrePositionToSplit()); return aContainer; } } @@ -272,22 +134,6 @@ LastVertexDataContainer ContainerFromParentInformation(G4Step* step){ - -void DumpInfoToSplit(){ - std::cout<<"Particle name of the particle to split: "<<fParticleNameToSplit<<std::endl; - std::cout<<"Kinetic Energy of the particle to split: "<<fEnergyToSplit<<std::endl; - std::cout<<"Momentum of the particle to split: "<<fMomentumToSplit<<std::endl; - std::cout<<"Initial position of the particle to split: "<<fPositionToSplit<<std::endl; - std::cout<<"ProcessNameToSplit: "<<fProcessNameToSplit<<std::endl; - std::cout<<"trackID of current particle: "<<fTrackID<<std::endl; - std::cout<<"Exiting status of current particle: "<<fIsExit<<std::endl; - std::cout<<"Regeneration status of current particle: "<<fToRegenerate<<std::endl; - std::cout<<"ParticleName of current particle: "<<fParticleName<<std::endl; - std::cout<<" "<<std::endl; - -} - - friend std::ostream& operator<<(std::ostream& os, const LastVertexDataContainer& container) { os <<container.fParticleName<<" ID: "<<container.fTrackID<< "process: "<< container.fCreationProcessName; return os; @@ -299,38 +145,11 @@ friend std::ostream& operator<<(std::ostream& os, const LastVertexDataContainer& private : G4String fParticleName="None"; -G4String fParticleNameToSplit="None"; -G4bool fIsExit =false; -G4bool fToRegenerate = false; G4int fTrackID = 0; G4String fCreationProcessName ="None"; -G4String fProcessNameToSplit ="None"; -G4double fEnergyToSplit = 0; -G4ThreeVector fMomentumToSplit; -G4ThreeVector fPositionToSplit; -G4ThreeVector fPolarizationToSplit; -G4double fWeightToSplit; -G4int fTrackStatusToSplit; -G4int fNumberOfSecondariesToSplit; -G4String fAnnihilProcessFlag; -G4double fStepLength; -G4ThreeVector fPrePosition; - -std::vector<G4ThreeVector> fVectorOfMomentumToSplit; -std::vector<G4ThreeVector> fVectorOfPositionToSplit; -std::vector<G4ThreeVector> fVectorOfPolarizationToSplit; -std::vector<G4double> fVectorOfEnergyToSplit; -std::vector<G4String> fVectorOfProcessToSplit; -std::vector<G4String> fVectorOfParticleNameToSplit; -std::vector<G4double> fVectorOfWeightToSplit; -std::vector<G4int>fVectorOfTrackStatusToSplit; -std::vector<G4int>fVectorOfNumberOfSecondariesToSplit; -std::vector<G4String>fVectorOfAnnihilProcessFlag; -std::vector<G4double>fVectorOfStepLength; -std::vector<G4ThreeVector>fVectorOfPrePosition; - - +SimpleContainer fContainerToSplit; +std::vector<SimpleContainer> fVectorOfContainerToSplit; }; diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h index 40b3fdbb4..241eeab57 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h @@ -63,6 +63,14 @@ virtual G4VParticleChange * PostStepDoIt (const G4Track & track, const G4Step & return particleChange; } +virtual G4VParticleChange * AlongStepDoIt (const G4Track & track, const G4Step & step) override +{ + isIonisation = true; + G4VParticleChange* particleChange = G4VEnergyLossProcess::AlongStepDoIt(track,step); + return particleChange; +} + + }; diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h new file mode 100644 index 000000000..e3069bf2c --- /dev/null +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h @@ -0,0 +1,217 @@ +// +// ******************************************************************** +// * 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. * +// ******************************************************************** +// +/// +#ifndef SimpleContainer_h +#define SimpleContainer_h + + +#include <iostream> +#include "G4VEnergyLossProcess.hh" +#include "G4Track.hh" +#include "G4VEmProcess.hh" +#include "G4VParticleChange.hh" +#include "G4eplusAnnihilation.hh" +#include "G4PhysicalConstants.hh" +#include "G4MaterialCutsCouple.hh" +#include "G4Gamma.hh" +#include "G4Electron.hh" +#include "G4Positron.hh" +#include "G4eeToTwoGammaModel.hh" +#include "G4EmBiasingManager.hh" +#include "G4EntanglementAuxInfo.hh" +#include "G4eplusAnnihilationEntanglementClipBoard.hh" +#include "G4EmParameters.hh" +#include "G4PhysicsModelCatalog.hh" + + +class SimpleContainer{ + +public : +SimpleContainer(G4String processName,G4double energy,G4ThreeVector momentum, G4ThreeVector position,G4ThreeVector polarization,G4String name,G4double weight,G4int trackStatus,G4int nbSec,G4String flag,G4double length, G4ThreeVector prePos){ + + fProcessNameToSplit = processName; + fEnergyToSplit = energy; + fMomentumToSplit = momentum; + fPositionToSplit = position; + fPolarizationToSplit = polarization; + fParticleNameToSplit = name; + fWeightToSplit = weight; + fTrackStatusToSplit = trackStatus; + fNumberOfSecondariesToSplit = nbSec; + fAnnihilProcessFlag = flag; + fStepLength = length; + fPrePosition = prePos; + +} + + +SimpleContainer(){} + +~SimpleContainer(){} + +void SetProcessNameToSplit(G4String processName){ + fProcessNameToSplit = processName; +} + +G4String GetProcessNameToSplit(){ + return fProcessNameToSplit; +} + +void SetEnergy(G4double energy){ + fEnergyToSplit =energy; +} + +G4double GetEnergy(){ + return fEnergyToSplit; +} + + +void SetWeight(G4double weight){ + fWeightToSplit =weight; +} + +G4double GetWeight(){ + return fWeightToSplit; +} + +void SetPolarization(G4ThreeVector polarization){ + fPolarizationToSplit = polarization; +} + +G4ThreeVector GetPolarization(){ + return fPolarizationToSplit; +} + +void SetMomentum(G4ThreeVector momentum){ + fMomentumToSplit =momentum; +} + +G4ThreeVector GetMomentum(){ + return fMomentumToSplit; +} + +void SetVertexPosition(G4ThreeVector position){ + fPositionToSplit = position; +} + +G4ThreeVector GetVertexPosition(){ + return fPositionToSplit; +} + +void SetParticleNameToSplit(G4String name){ + fParticleNameToSplit = name; +} + +G4String GetParticleNameToSplit(){ + return fParticleNameToSplit; +} + + +void SetTrackStatus(G4int trackStatus){ + fTrackStatusToSplit = trackStatus; +} + + +G4int GetTrackStatus(){ + return fTrackStatusToSplit; +} + + +void SetNbOfSecondaries(G4int nbSec){ + fNumberOfSecondariesToSplit = nbSec; +} + +G4int GetNbOfSecondaries(){ + return fNumberOfSecondariesToSplit; +} + +void SetAnnihilationFlag(G4String flag){ + fAnnihilProcessFlag = flag; +} + +G4String GetAnnihilationFlag(){ + return fAnnihilProcessFlag; +} + +void SetStepLength(G4double length){ + fStepLength = length; +} + +G4double GetStepLength(){ + return fStepLength; +} + + + +void SetPrePositionToSplit(G4ThreeVector prePos){ + fPrePosition = prePos; +} + +G4ThreeVector GetPrePositionToSplit(){ + return fPrePosition; +} + + + + +void DumpInfoToSplit(){ + std::cout<<"Particle name of the particle to split: "<<fParticleNameToSplit<<std::endl; + std::cout<<"Kinetic Energy of the particle to split: "<<fEnergyToSplit<<std::endl; + std::cout<<"Momentum of the particle to split: "<<fMomentumToSplit<<std::endl; + std::cout<<"Initial position of the particle to split: "<<fPositionToSplit<<std::endl; + std::cout<<"ProcessNameToSplit: "<<fProcessNameToSplit<<std::endl; + std::cout<<" "<<std::endl; + +} + + + + +private : + +G4String fParticleNameToSplit="None"; +G4String fProcessNameToSplit ="None"; +G4double fEnergyToSplit = 0; +G4ThreeVector fMomentumToSplit; +G4ThreeVector fPositionToSplit; +G4ThreeVector fPolarizationToSplit; +G4double fWeightToSplit; +G4int fTrackStatusToSplit; +G4int fNumberOfSecondariesToSplit; +G4String fAnnihilProcessFlag; +G4double fStepLength; +G4ThreeVector fPrePosition; + + +}; + +#endif + + + + + + \ No newline at end of file From ce16241257eccaa54860015a3aa85d74b7a71d84 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Thu, 17 Oct 2024 12:13:36 +0200 Subject: [PATCH 34/82] Bug correction and time optimization of splitting process --- ...ateLastVertexInteractionSplittingActor.cpp | 484 ++++++++++-------- .../GateLastVertexInteractionSplittingActor.h | 24 +- .../GateLastVertexSplittingDataContainer.h | 15 +- 3 files changed, 291 insertions(+), 232 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index 22cd2800b..774394c86 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -88,7 +88,6 @@ void GateLastVertexInteractionSplittingActor::print_tree(const tree<LastVertexDa std::cout << "-----" << std::endl; } - G4bool GateLastVertexInteractionSplittingActor::DoesParticleEmittedInSolidAngle(G4ThreeVector dir, G4ThreeVector vectorDirector, G4double maxTheta) { G4double cosTheta = vectorDirector * dir; G4double theta = std::acos(cosTheta); @@ -98,38 +97,39 @@ G4bool GateLastVertexInteractionSplittingActor::DoesParticleEmittedInSolidAngle( } -G4Track* GateLastVertexInteractionSplittingActor::CreateATrackFromContainer(LastVertexDataContainer theContainer, G4Step *step ){ +G4Track* GateLastVertexInteractionSplittingActor::CreateATrackFromContainer(LastVertexDataContainer theContainer){ + auto *particle_table = G4ParticleTable::GetParticleTable(); SimpleContainer container = theContainer.GetContainerToSplit(); - G4ParticleDefinition *particleDefinition = particle_table->FindParticle(container.GetParticleNameToSplit()); - G4ThreeVector momentum = container.GetMomentum(); - G4double energy = container.GetEnergy(); - if (energy <0){ - energy = 0; - momentum = {0,0,0}; - } - G4int trackStatus = container.GetTrackStatus(); - G4ThreeVector position = container.GetVertexPosition(); - G4ThreeVector polarization = container.GetPolarization(); - - G4DynamicParticle* dynamicParticle = new G4DynamicParticle(particleDefinition,momentum,energy); - G4double time = 0; - if (step->GetPreStepPoint() != 0) - time = step->GetPreStepPoint()->GetGlobalTime(); - G4Track* aTrack = new G4Track(dynamicParticle,time, position); - aTrack->SetPolarization(polarization); - if (trackStatus == 0){ - aTrack->SetTrackStatus(fAlive); - } - if (trackStatus == 1){ - aTrack->SetTrackStatus(fStopButAlive); - } - if ((trackStatus == 2) || (trackStatus == 3)){ - aTrack->SetTrackStatus(fAlive); + if (container.GetParticleNameToSplit() != "None"){ + G4ParticleDefinition *particleDefinition = particle_table->FindParticle(container.GetParticleNameToSplit()); + G4ThreeVector momentum = container.GetMomentum(); + G4double energy = container.GetEnergy(); + if (energy <0){ + energy = 0; + momentum = {0,0,0}; + } + G4int trackStatus = container.GetTrackStatus(); + G4ThreeVector position = container.GetVertexPosition(); + G4ThreeVector polarization = container.GetPolarization(); + G4DynamicParticle* dynamicParticle = new G4DynamicParticle(particleDefinition,momentum,energy); + G4double time = 0; + G4Track* aTrack = new G4Track(dynamicParticle,time, position); + aTrack->SetPolarization(polarization); + if (trackStatus == 0){ + aTrack->SetTrackStatus(fAlive); + } + if (trackStatus == 1){ + aTrack->SetTrackStatus(fStopButAlive); + } + if ((trackStatus == 2) || (trackStatus == 3)){ + aTrack->SetTrackStatus(fAlive); + } + aTrack->SetWeight(container.GetWeight()); + return aTrack; } - aTrack->SetWeight(container.GetWeight()); - return aTrack; + return nullptr; } @@ -150,6 +150,7 @@ G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack(G4ParticleC newTrack->SetMomentumDirection(momentum); newTrack->SetPosition(position); newTrack->SetPolarization(polarization); + newTrack->SetWeight(weight); return newTrack; } @@ -158,41 +159,38 @@ void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step* initStep, G4TrackVector *trackVector = CurrentStep->GetfSecondary(); G4double gammaWeight = 0; - G4Track* aTrack = CreateATrackFromContainer(container,initStep); - GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; - G4VParticleChange *processFinalState = emProcess->PostStepDoIt(*aTrack, *initStep); + G4VParticleChange *processFinalState = emProcess->PostStepDoIt(*fTrackToSplit, *initStep); G4ParticleChangeForGamma *gammaProcessFinalState = (G4ParticleChangeForGamma *)processFinalState; - const G4ThreeVector momentum = gammaProcessFinalState->GetProposedMomentumDirection(); - //gammaWeight = aTrack->GetWeight()/ fSplittingFactor; + G4ThreeVector momentum = gammaProcessFinalState->GetProposedMomentumDirection(); + gammaWeight = fTrackToSplit->GetWeight()/ fSplittingFactor; - - G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, *aTrack, gammaWeight); + G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, *fTrackToSplit, gammaWeight); - if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(newTrack->GetMomentumDirection(),fVectorDirector,fMaxTheta) == false)) - delete newTrack; - else { + if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(newTrack->GetMomentumDirection(),fVectorDirector,fMaxTheta) == false)){ + delete newTrack; + ComptonSplitting(initStep,CurrentStep,process,container); + } + else{ trackVector->push_back(newTrack); - G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - for (int j = 0; j < NbOfSecondaries; j++) { - G4Track *newTrack = processFinalState->GetSecondary(j); - //newTrack->SetWeight(gammaWeight); - trackVector->push_back(newTrack); - } } - processFinalState->Clear(); - gammaProcessFinalState->Clear(); - delete aTrack; - - - + // Special case here, since we generate independently each particle, we will not attach an electron to exiting compton photon. + /* + G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + for (int j = 0; j < NbOfSecondaries; j++) { + G4Track *newTrack = processFinalState->GetSecondary(j); + newTrack->SetWeight(gammaWeight); + trackVector->push_back(newTrack); + } + */ + processFinalState->Clear(); + gammaProcessFinalState->Clear(); } - G4VProcess* GateLastVertexInteractionSplittingActor::GetProcessFromProcessName(G4String particleName, G4String pName){ auto *particle_table = G4ParticleTable::GetParticleTable(); G4ParticleDefinition *particleDefinition = particle_table->FindParticle(particleName); @@ -210,7 +208,7 @@ G4VProcess* GateLastVertexInteractionSplittingActor::GetProcessFromProcessName(G } -G4VParticleChange* GateLastVertexInteractionSplittingActor::eBremProcessFinalState(G4Track* track, G4Step* step,G4VProcess *process){ +G4Track GateLastVertexInteractionSplittingActor::eBremProcessFinalState(G4Track* track, G4Step* step,G4VProcess *process){ //It seem's that the the along step method apply only to brem results to no deposited energy but a change in momentum direction according to the process //Whereas the along step method applied to the ionisation well change the deposited energy but not the momentum. Then I apply both to have a correct //momentum and deposited energy before the brem effect. @@ -224,77 +222,85 @@ G4VParticleChange* GateLastVertexInteractionSplittingActor::eBremProcessFinalSta G4double LossEnergy = eIoniProcessAlongStateForLoss->GetLocalEnergyDeposit(); G4ThreeVector momentum = eBremProcessAlongStateForLoss->GetProposedMomentumDirection(); G4ThreeVector polarization = eBremProcessAlongStateForLoss->GetProposedPolarization(); + G4Track aTrack = G4Track(*track); - track->SetKineticEnergy(track->GetKineticEnergy() - LossEnergy); - track->SetMomentumDirection(momentum); - track->SetPolarization(polarization); - GateBremPostStepDoIt *bremProcess = (GateBremPostStepDoIt *)process; + aTrack.SetKineticEnergy(track->GetKineticEnergy() - LossEnergy); + aTrack.SetMomentumDirection(momentum); + aTrack.SetPolarization(polarization); eIoniProcessAlongState->Clear(); eBremProcessAlongState->Clear(); - - return bremProcess->GateBremPostStepDoIt::PostStepDoIt(*track, *step); + return aTrack; } void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer theContainer) { - - - G4Track* track = CreateATrackFromContainer(theContainer,initStep); SimpleContainer container = theContainer.GetContainerToSplit(); - G4String particleName = track->GetParticleDefinition()->GetParticleName(); + G4String particleName = fTrackToSplit->GetParticleDefinition()->GetParticleName(); G4TrackVector *trackVector = CurrentStep->GetfSecondary(); G4double gammaWeight = 0; G4VParticleChange *processFinalState = nullptr; - if (process->GetProcessName() == "eBrem") { - processFinalState = eBremProcessFinalState(track,initStep,process); - } - else { - GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; - if ((container.GetAnnihilationFlag() == "PostStep") && (track->GetKineticEnergy() > 0)){ - processFinalState = emProcess->PostStepDoIt(*track, *initStep); + GateBremPostStepDoIt* bremProcess = nullptr; + GateGammaEmPostStepDoIt *emProcess = nullptr; + GateplusannihilAtRestDoIt *eplusAnnihilProcess = nullptr; + + if ((container.GetAnnihilationFlag() == "PostStep") && (fTrackToSplit->GetKineticEnergy() > 0)){ + emProcess = (GateGammaEmPostStepDoIt *)process; + } + if ((container.GetAnnihilationFlag() == "AtRest") || (fTrackToSplit->GetKineticEnergy() == 0)) { + eplusAnnihilProcess = (GateplusannihilAtRestDoIt *)process; + } + + + + + G4int NbOfSecondaries = 0; + while (NbOfSecondaries == 0){ + if (process->GetProcessName() == "eBrem") { + G4Track aTrack = eBremProcessFinalState(fTrackToSplit,initStep,process); + bremProcess = (GateBremPostStepDoIt*) process; + processFinalState = bremProcess->GateBremPostStepDoIt::PostStepDoIt(aTrack, *initStep); } - if ((container.GetAnnihilationFlag() == "AtRest") || (track->GetKineticEnergy() == 0)) { - GateplusannihilAtRestDoIt *eplusAnnihilProcess = (GateplusannihilAtRestDoIt *)process; - processFinalState = eplusAnnihilProcess->GateplusannihilAtRestDoIt::AtRestDoIt(*track,*initStep); + else { + if ((container.GetAnnihilationFlag() == "PostStep") && (fTrackToSplit->GetKineticEnergy() > 0)){ + processFinalState = emProcess->PostStepDoIt(*fTrackToSplit, *initStep); + } + if ((container.GetAnnihilationFlag() == "AtRest") || (fTrackToSplit->GetKineticEnergy() == 0)) { + processFinalState = eplusAnnihilProcess->GateplusannihilAtRestDoIt::AtRestDoIt(*fTrackToSplit,*initStep); + } + } + NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + if (NbOfSecondaries == 0){ + processFinalState->Clear(); } } - - - G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); G4int idx = 0; - if (NbOfSecondaries >0) { - //gammaWeight = track->GetWeight()/fSplittingFactor; - G4bool alreadySplitted = false; - for (int i; i < NbOfSecondaries; i++){ - G4Track *newTrack = processFinalState->GetSecondary(i); - G4ThreeVector momentum = newTrack->GetMomentumDirection(); - if (!(isnan(momentum[0]))){ - if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(momentum,fVectorDirector,fMaxTheta) == false)){ - delete newTrack; - } - else if (alreadySplitted == false){ - //newTrack->SetWeight(gammaWeight); - newTrack->SetCreatorProcess(process); - trackVector->push_back(newTrack); - alreadySplitted = true; - } - else{ - delete newTrack; - } + gammaWeight = fTrackToSplit->GetWeight()/fSplittingFactor; + G4bool IsPushBack =false; + for (int i; i < NbOfSecondaries; i++){ + G4Track *newTrack = processFinalState->GetSecondary(i); + G4ThreeVector momentum = newTrack->GetMomentumDirection(); + if (!(isnan(momentum[0]))){ + if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(momentum,fVectorDirector,fMaxTheta) == false)){ + delete newTrack; } else { - delete newTrack; + newTrack->SetWeight(gammaWeight); + newTrack->SetCreatorProcess(process); + trackVector->push_back(newTrack); + IsPushBack=true; + break; + } } + else { + delete newTrack; + } } - delete track; - - - - + if (IsPushBack == false) + SecondariesSplitting(initStep,CurrentStep,process,theContainer); processFinalState->Clear(); @@ -305,17 +311,21 @@ void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G // We retrieve the process associated to the process name to split and we // split according the process. Since for compton scattering, the gamma is not // a secondary particles, this one need to have his own splitting function. - SimpleContainer container = theContainer.GetContainerToSplit(); - G4VProcess* processToSplit = GetProcessFromProcessName(container.GetParticleNameToSplit(),container.GetProcessNameToSplit()); - G4String processName = container.GetProcessNameToSplit(); - if (processName == "compt") { - ComptonSplitting(initStep,step, processToSplit, theContainer); - } - else if((processName != "msc") && (processName != "conv")){ - SecondariesSplitting(initStep, step, processToSplit, theContainer); - } - + G4String processName = fProcessNameToSplit; + if ((fProcessToSplit == 0) || (fProcessToSplit == nullptr)){ + SimpleContainer container = theContainer.GetContainerToSplit(); + fProcessToSplit = GetProcessFromProcessName(container.GetParticleNameToSplit(),processName); + } + + if (processName == "compt") { + ComptonSplitting(initStep,step, fProcessToSplit, theContainer); + } + + else if((processName != "msc") && (processName != "conv")){ + SecondariesSplitting(initStep, step, fProcessToSplit, theContainer); + } + } @@ -352,28 +362,25 @@ void GateLastVertexInteractionSplittingActor::FillOfDataTree(G4Step*step){ G4String annihilFlag = "None"; if (processName == "annihil"){ if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0){ - //std::cout<<processName<<" "<<step->GetPreStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetTotalEnergyDeposit()<<std::endl; - if (processName == step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName()){ + if (processName == step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName()){ annihilFlag = "PostStep"; } else if (processName != step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName()){ annihilFlag ="AtRest"; } - //std::cout<<annihilFlag<<std::endl; } } - - if (processName =="eBrem"){ - //std:cout<<step->GetPreStepPoint()->GetKineticEnergy()<<" "<<step->GetPostStepPoint()->GetKineticEnergy()<<" "<<step->GetTotalEnergyDeposit()<<std::endl; - } - + if (fIsFirstStep){ LastVertexDataContainer newContainer = LastVertexDataContainer(); newContainer.SetTrackID(step->GetTrack()->GetTrackID()); newContainer.SetParticleName(step->GetTrack()->GetDefinition()->GetParticleName()); newContainer.SetCreationProcessName(creatorProcessName); + + + if (fTree.empty()){ fTree.set_head(newContainer); } @@ -399,58 +406,65 @@ void GateLastVertexInteractionSplittingActor::FillOfDataTree(G4Step*step){ } } } + - LastVertexDataContainer* container = &(*fIterator); - G4int trackID = container->GetTrackID(); - if ((processName != "Transportation") &&(processName !="None") && (processName !="Rayl")){ - if (step->GetTrack()->GetTrackID() == trackID){ - G4ThreeVector position = step->GetTrack()->GetPosition(); - G4ThreeVector prePosition = step->GetPreStepPoint()->GetPosition(); - G4ThreeVector momentum; - if ((processName == "annihil")) - momentum = step->GetPostStepPoint()->GetMomentumDirection(); - else{ - momentum = step->GetPreStepPoint()->GetMomentumDirection(); - } - G4ThreeVector polarization = step->GetPreStepPoint()->GetPolarization(); - G4String particleName = step->GetTrack()->GetDefinition()->GetParticleName(); - G4double energy = step->GetPreStepPoint()->GetKineticEnergy(); - G4double weight = step->GetTrack()->GetWeight(); - G4int trackStatus = step->GetTrack()->GetTrackStatus(); - G4int nbOfSecondaries = step->GetfSecondary()->size(); - G4double stepLength = step->GetStepLength(); - if (((processName == "annihil"))){ - energy -= (step->GetTotalEnergyDeposit()); - } - SimpleContainer containerToSplit = SimpleContainer(processName,energy, momentum, position,polarization,particleName,weight,trackStatus,nbOfSecondaries,annihilFlag,stepLength,prePosition); - container->SetContainerToSplit(containerToSplit); - container->PushListOfSplittingParameters(); - + + LastVertexDataContainer* container = &(*fIterator); + G4int trackID = container->GetTrackID(); + if ((processName != "Transportation") &&(processName !="None") && (processName !="Rayl")){ + if (step->GetTrack()->GetTrackID() == trackID){ + G4ThreeVector position = step->GetTrack()->GetPosition(); + G4ThreeVector prePosition = step->GetPreStepPoint()->GetPosition(); + G4ThreeVector momentum; + if ((processName == "annihil")) + momentum = step->GetPostStepPoint()->GetMomentumDirection(); + else{ + momentum = step->GetPreStepPoint()->GetMomentumDirection(); } + G4ThreeVector polarization = step->GetPreStepPoint()->GetPolarization(); + G4String particleName = step->GetTrack()->GetDefinition()->GetParticleName(); + G4double energy = step->GetPreStepPoint()->GetKineticEnergy(); + G4double weight = step->GetTrack()->GetWeight(); + G4int trackStatus = step->GetTrack()->GetTrackStatus(); + G4int nbOfSecondaries = step->GetfSecondary()->size(); + G4double stepLength = step->GetStepLength(); + if (((processName == "annihil"))){ + energy -= (step->GetTotalEnergyDeposit()); + } + SimpleContainer containerToSplit = SimpleContainer(processName,energy, momentum, position,polarization,particleName,weight,trackStatus,nbOfSecondaries,annihilFlag,stepLength,prePosition); + //std::cout<<"during saving " <<containerToSplit.GetProcessNameToSplit()<<std::endl; + container->SetContainerToSplit(containerToSplit); + container->PushListOfSplittingParameters(containerToSplit); + + //std::cout<<fEventID<<" recordProcess "<<container->GetContainerToSplit().GetProcessNameToSplit()<<std::endl; + } + } + + } G4bool GateLastVertexInteractionSplittingActor::IsParticleExitTheBiasedVolume(G4Step*step){ - G4String logicalVolumeNamePreStep = "None"; - G4String logicalVolumeNamePostStep = "None"; - if (step->GetPreStepPoint()->GetPhysicalVolume() != 0) - logicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) - logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - - if (logicalVolumeNamePreStep != logicalVolumeNamePostStep){ + + if ((step->GetPostStepPoint()->GetStepStatus() == 1)) { + G4String logicalVolumeNamePostStep = "None"; + if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) + logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()){ return true; } - /* - else if (std::find(fListOfBiasedVolume.begin(), fListOfBiasedVolume.end(), logicalVolumeNamePostStep) != fListOfBiasedVolume.end()) { - return false; + /* + else if (std::find(fListOfBiasedVolume.begin(), fListOfBiasedVolume.end(), logicalVolumeNamePostStep) != fListOfBiasedVolume.end()) { + return false; + } + */ } - */ - } + if (step->GetPostStepPoint()->GetStepStatus() == 0) + return true; return false; } @@ -468,6 +482,14 @@ G4bool GateLastVertexInteractionSplittingActor::IsTheParticleUndergoesAProcess(G return false; +} + +G4bool GateLastVertexInteractionSplittingActor::IsTheParticleUndergoesALossEnergyProcess(G4Step* step){ + if (step->GetPostStepPoint()->GetKineticEnergy() - step->GetPreStepPoint()->GetKineticEnergy() != 0) + return true; + return false; + + } @@ -503,19 +525,34 @@ void GateLastVertexInteractionSplittingActor::BeginOfEventAction( fEventID = event->GetEventID(); fEventIDOfSplittedTrack = -1; fTrackIDOfSplittedTrack = -1; - fNotSplitted == true; fIsAnnihilAlreadySplit = false; if (fEventID%50000 == 0) - std::cout<<fEventID<<std::endl; + std::cout<<"event ID : "<<fEventID<<std::endl; fCopyInitStep = nullptr; + if (fActiveSource == "source_vertex"){ + auto* source = fSourceManager->FindSourceByName(fActiveSource); + GateLastVertexSource *vertexSource = (GateLastVertexSource*) source; + fContainer = vertexSource->GetLastVertexContainer(); + fProcessNameToSplit = vertexSource->GetProcessToSplit(); + if (fProcessToSplit !=0){ + fProcessToSplit = nullptr; + } + if (fTrackToSplit !=0){ + delete fTrackToSplit; + fTrackToSplit = nullptr; + } + fTrackToSplit = CreateATrackFromContainer(fContainer); + } + + } void GateLastVertexInteractionSplittingActor::PreUserTrackingAction( const G4Track *track) { - + fToSplit =true; fIsFirstStep = true; } @@ -523,99 +560,104 @@ void GateLastVertexInteractionSplittingActor::PreUserTrackingAction( void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { - G4String logicalVolumeNamePreStep = "None"; - G4String logicalVolumeNamePostStep = "None"; - - if (step->GetPreStepPoint()->GetPhysicalVolume() != 0) - logicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) - logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - - G4String particleName =step->GetTrack()->GetParticleDefinition()->GetParticleName(); - G4String creatorProcessName = "None"; - G4String processName = "None"; - - if (step->GetTrack()->GetCreatorProcess() != 0) - creatorProcessName =step->GetTrack()->GetCreatorProcess()->GetProcessName(); - - if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) - processName = step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); - - if (fActiveSource != "source_vertex"){ FillOfDataTree(step); + if (IsParticleExitTheBiasedVolume(step)){ if ((fAngularKill == false) || ((fAngularKill == true) && (DoesParticleEmittedInSolidAngle(step->GetTrack()->GetMomentumDirection(),fVectorDirector,fMaxTheta) == true))){ fListOfContainer.push_back((*fIterator)); } + step->GetTrack()->SetTrackStatus(fStopAndKill); } + + } + if (fOnlyTree == false){ if (fActiveSource == "source_vertex"){ - - auto* source = fSourceManager->FindSourceByName(fActiveSource); - GateLastVertexSource *vertexSource = (GateLastVertexSource*) source; - LastVertexDataContainer container = vertexSource->GetLastVertexContainer(); - if ((step->GetTrack()->GetWeight() == container.GetContainerToSplit().GetWeight())){ - G4ThreeVector momentum = step->GetPreStepPoint()->GetMomentumDirection(); - G4String processToSplit = vertexSource->GetProcessToSplit(); + + if (fIsFirstStep){ + fTrackID = step->GetTrack()->GetTrackID(); + fEkin = step->GetPostStepPoint()->GetKineticEnergy(); + } + else{ + if ((fTrackID == step->GetTrack()->GetTrackID()) && (fEkin != step->GetPreStepPoint()->GetKineticEnergy())){ + fToSplit =false; + } + else{ + fEkin = step->GetPostStepPoint()->GetKineticEnergy(); + } + + } + if (fToSplit) { + G4String creatorProcessName = "None"; + if (step->GetTrack()->GetCreatorProcess() != 0) + creatorProcessName =step->GetTrack()->GetCreatorProcess()->GetProcessName(); if (((step->GetTrack()->GetParentID() == 0)&& (step->GetTrack()->GetTrackID() == 1))|| ((creatorProcessName == "annihil") && (step->GetTrack()->GetParentID() == 1))){ - if ((processToSplit != "annihil") || ((processToSplit == "annihil")&& (fIsAnnihilAlreadySplit ==false))){ + if ((fProcessNameToSplit != "annihil") || ((fProcessNameToSplit == "annihil")&& (fIsAnnihilAlreadySplit ==false))){ step->GetfSecondary()->clear(); //FIXME : list of process which are not splitable yet - if ((processToSplit != "msc") && (processToSplit != "conv") && (processToSplit != "eIoni")) { + if ((fProcessNameToSplit != "msc") && (fProcessNameToSplit != "conv") && (fProcessNameToSplit != "eIoni")) { fCopyInitStep= new G4Step(*step); - if (processToSplit == "eBrem"){ - fCopyInitStep->SetStepLength(container.GetContainerToSplit().GetStepLength()); - fCopyInitStep->GetPreStepPoint()->SetKineticEnergy(container.GetContainerToSplit().GetEnergy()); - - } - while (step->GetfSecondary()->size() != 1){ - step->GetfSecondary()->clear(); - CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); + if (fProcessNameToSplit == "eBrem"){ + fCopyInitStep->SetStepLength(fContainer.GetContainerToSplit().GetStepLength()); + fCopyInitStep->GetPreStepPoint()->SetKineticEnergy(fContainer.GetContainerToSplit().GetEnergy()); } + CreateNewParticleAtTheLastVertex(fCopyInitStep,step,fContainer); } step->GetTrack()->SetTrackStatus(fStopAndKill); - if (processToSplit == "annihil"){ + if (fProcessNameToSplit == "annihil"){ fIsAnnihilAlreadySplit = true; } } - else if ((processToSplit == "annihil")&& (fIsAnnihilAlreadySplit == true)){ + else if ((fProcessNameToSplit == "annihil")&& (fIsAnnihilAlreadySplit == true)){ step->GetfSecondary()->clear(); step->GetTrack()->SetTrackStatus(fStopAndKill); } } - - else if (IsTheParticleUndergoesAProcess(step)){ - step->GetfSecondary()->clear(); - while (step->GetfSecondary()->size() != 1){ - step->GetfSecondary()->clear(); - CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); - } - step->GetTrack()->SetTrackStatus(fStopAndKill); - + else { + + if (fIsFirstStep){ + if (fKilledBecauseOfProcess == false){ + fSplitCounter += 1; + } + else { + fKilledBecauseOfProcess = false; + } + + if (fSplitCounter < fSplittingFactor){ + CreateNewParticleAtTheLastVertex(fCopyInitStep,step,fContainer); + } + } + + if (IsTheParticleUndergoesALossEnergyProcess(step)){ + //std::cout<<"kill because process"<<std::endl; + step->GetfSecondary()->clear(); + CreateNewParticleAtTheLastVertex(fCopyInitStep,step,fContainer); + step->GetTrack()->SetTrackStatus(fStopAndKill); + fKilledBecauseOfProcess = true; + } + + + } - + /* else if (IsParticleExitTheBiasedVolume(step)){ fSplitCounter += 1; if (fSplitCounter < fSplittingFactor){ - while (step->GetfSecondary()->size() != 1){ - step->GetfSecondary()->clear(); - CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); - - } + CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); } else{ delete fCopyInitStep; @@ -630,8 +672,10 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { step->GetPostStepPoint()->SetWeight(container.GetContainerToSplit().GetWeight()/fSplittingFactor); else step->GetPostStepPoint()->SetWeight(container.GetContainerToSplit().GetWeight()*0.99999999); + } - } + */ + } } } @@ -644,6 +688,11 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { void GateLastVertexInteractionSplittingActor::PostUserTrackingAction( const G4Track *track) { + if ((fSplitCounter == fSplittingFactor) && (fKilledBecauseOfProcess == false)){ + delete fCopyInitStep; + fCopyInitStep = nullptr; + fSplitCounter = 0; + } } @@ -653,10 +702,13 @@ void GateLastVertexInteractionSplittingActor::EndOfEventAction( if (fActiveSource != "source_vertex"){ - //print_tree(fTree,fTree.begin(),fTree.end()); + + //print_tree(fTree,fTree.begin(),fTree.end()); fVertexSource->SetNumberOfEventToSimulate(fListOfContainer.size()); fVertexSource->SetNumberOfGeneratedEvent(0); fVertexSource->SetListOfVertexToSimulate(fListOfContainer); + + //fDataMap.clear(); fTree.clear(); fListOfContainer.clear(); } diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h index 4957e4f90..bdb5092b1 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h @@ -59,34 +59,37 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { G4int fEventID; G4int fEventIDOfSplittedTrack; G4int fEventIDOfInitialSplittedTrack; - G4int fTrackIDOfInitialTrack; + G4int fTrackID; + G4double fEkin; G4int fTrackIDOfInitialSplittedTrack = 0; G4int ftmpTrackID; G4bool fIsFirstStep = true; G4bool fSuspendForAnnihil = false; G4double fWeightOfEnteringParticle = 0; G4double fSplitCounter = 0; - G4bool fNotSplitted = true; + G4bool fToSplit = true; G4String fActiveSource = "None"; G4bool fIsAnnihilAlreadySplit =false; G4int fCounter; - G4bool fOnlyTree = true; + G4bool fKilledBecauseOfProcess = false; + G4bool fFirstSplittedPart = true; + G4bool fOnlyTree = false; GateLastVertexSource* fVertexSource = nullptr; tree<LastVertexDataContainer> fTree; tree<LastVertexDataContainer>::post_order_iterator fIterator; std::vector<LastVertexDataContainer> fListOfContainer; + G4Track *fTrackToSplit = nullptr; G4Step* fCopyInitStep = nullptr; - G4String fProcessToSplit = "None"; + G4String fProcessNameToSplit; + G4VProcess* fProcessToSplit; + LastVertexDataContainer fContainer; std::vector<G4Track> fTracksToPostpone; - - std::map<G4int, G4TrackVector> fRememberedTracks; - std::map<G4int, std::vector<G4Step *>> fRememberedSteps; - std::map<G4int, std::vector<G4String>> fRememberedProcesses; std::map<G4String,std::vector<G4String>> fListOfProcessesAccordingParticles; + std::map<G4int,LastVertexDataContainer> fDataMap; @@ -111,10 +114,11 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { void SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container); void CreateNewParticleAtTheLastVertex(G4Step*init,G4Step *current, LastVertexDataContainer); - G4Track* CreateATrackFromContainer(LastVertexDataContainer container, G4Step *step ); + G4Track* CreateATrackFromContainer(LastVertexDataContainer container); G4bool IsTheParticleUndergoesAProcess(G4Step* step); + G4bool IsTheParticleUndergoesALossEnergyProcess(G4Step* step); G4VProcess* GetProcessFromProcessName(G4String particleName, G4String pName); - G4VParticleChange* eBremProcessFinalState(G4Track* track, G4Step* step,G4VProcess *process); + G4Track eBremProcessFinalState(G4Track* track, G4Step* step,G4VProcess *process); void FillOfDataTree(G4Step *step); diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h index 0d63bd80a..474d6cc84 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h @@ -97,8 +97,8 @@ SimpleContainer GetContainerToSplit(){ -void PushListOfSplittingParameters(){ - fVectorOfContainerToSplit.emplace_back(fContainerToSplit); +void PushListOfSplittingParameters(SimpleContainer container){ + fVectorOfContainerToSplit.emplace_back(container); } @@ -115,17 +115,20 @@ LastVertexDataContainer ContainerFromParentInformation(G4Step* step){ for (int i =0;i<this->fVectorOfContainerToSplit.size();i++){ if (vertexPosition == this->fVectorOfContainerToSplit[i].GetVertexPosition()){ SimpleContainer tmpContainer = this->fVectorOfContainerToSplit[i]; - fContainerToSplit = SimpleContainer(tmpContainer.GetProcessNameToSplit(),tmpContainer.GetEnergy(),tmpContainer.GetMomentum(),tmpContainer.GetVertexPosition(),tmpContainer.GetPolarization(),tmpContainer.GetParticleNameToSplit(),tmpContainer.GetWeight(),tmpContainer.GetTrackStatus(),tmpContainer.GetNbOfSecondaries(),tmpContainer.GetAnnihilationFlag(),tmpContainer.GetStepLength(), tmpContainer.GetPrePositionToSplit()); + //std::cout<<"1 "<<tmpContainer.GetProcessNameToSplit()<<std::endl; + aContainer.fContainerToSplit = SimpleContainer(tmpContainer.GetProcessNameToSplit(),tmpContainer.GetEnergy(),tmpContainer.GetMomentum(),tmpContainer.GetVertexPosition(),tmpContainer.GetPolarization(),tmpContainer.GetParticleNameToSplit(),tmpContainer.GetWeight(),tmpContainer.GetTrackStatus(),tmpContainer.GetNbOfSecondaries(),tmpContainer.GetAnnihilationFlag(),tmpContainer.GetStepLength(), tmpContainer.GetPrePositionToSplit()); return aContainer; } } } else{ SimpleContainer tmpContainer = this->fContainerToSplit; - fContainerToSplit = SimpleContainer(tmpContainer.GetProcessNameToSplit(),tmpContainer.GetEnergy(),tmpContainer.GetMomentum(),tmpContainer.GetVertexPosition(),tmpContainer.GetPolarization(),tmpContainer.GetParticleNameToSplit(),tmpContainer.GetWeight(),tmpContainer.GetTrackStatus(),tmpContainer.GetNbOfSecondaries(),tmpContainer.GetAnnihilationFlag(),tmpContainer.GetStepLength(),tmpContainer.GetPrePositionToSplit()); + //std::cout<<"2 "<<tmpContainer.GetProcessNameToSplit()<<std::endl; + aContainer.fContainerToSplit = SimpleContainer(tmpContainer.GetProcessNameToSplit(),tmpContainer.GetEnergy(),tmpContainer.GetMomentum(),tmpContainer.GetVertexPosition(),tmpContainer.GetPolarization(),tmpContainer.GetParticleNameToSplit(),tmpContainer.GetWeight(),tmpContainer.GetTrackStatus(),tmpContainer.GetNbOfSecondaries(),tmpContainer.GetAnnihilationFlag(),tmpContainer.GetStepLength(),tmpContainer.GetPrePositionToSplit()); return aContainer; } } + //std::cout<<"3"<<std::endl; return aContainer; } @@ -134,8 +137,8 @@ LastVertexDataContainer ContainerFromParentInformation(G4Step* step){ -friend std::ostream& operator<<(std::ostream& os, const LastVertexDataContainer& container) { - os <<container.fParticleName<<" ID: "<<container.fTrackID<< "process: "<< container.fCreationProcessName; +friend std::ostream& operator<<(std::ostream& os, LastVertexDataContainer& container) { + os <<container.fParticleName<<" ID: "<<container.fTrackID<< " process to split : "<<container.fContainerToSplit.GetProcessNameToSplit()<<" name to split"<<container.fContainerToSplit.GetParticleNameToSplit() ; return os; } From fb57f719d0db575acbcc2595845e30d73481dbd3 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Thu, 17 Oct 2024 12:44:13 +0200 Subject: [PATCH 35/82] speed up of Russian roulette calculation --- ...ateLastVertexInteractionSplittingActor.cpp | 63 +++++++++---------- .../GateLastVertexInteractionSplittingActor.h | 4 +- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index 774394c86..f31cf11e6 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -88,14 +88,28 @@ void GateLastVertexInteractionSplittingActor::print_tree(const tree<LastVertexDa std::cout << "-----" << std::endl; } -G4bool GateLastVertexInteractionSplittingActor::DoesParticleEmittedInSolidAngle(G4ThreeVector dir, G4ThreeVector vectorDirector, G4double maxTheta) { +G4bool GateLastVertexInteractionSplittingActor::DoesParticleEmittedInSolidAngle(G4ThreeVector dir, G4ThreeVector vectorDirector) { G4double cosTheta = vectorDirector * dir; - G4double theta = std::acos(cosTheta); - if (theta > fMaxTheta) + if (cosTheta < fCosMaxTheta) return false; return true; } +G4VProcess* GateLastVertexInteractionSplittingActor::GetProcessFromProcessName(G4String particleName, G4String pName){ + auto *particle_table = G4ParticleTable::GetParticleTable(); + G4ParticleDefinition *particleDefinition = particle_table->FindParticle(particleName); + G4ProcessManager *processManager = particleDefinition->GetProcessManager(); + G4ProcessVector *processList = processManager->GetProcessList(); + G4VProcess* nullProcess = nullptr; + for (size_t i = 0; i < processList->size(); ++i) { + auto process = (*processList)[i]; + if (process->GetProcessName() == pName) { + return process; + } + } + return nullProcess; + +} G4Track* GateLastVertexInteractionSplittingActor::CreateATrackFromContainer(LastVertexDataContainer theContainer){ @@ -139,13 +153,11 @@ G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack(G4ParticleC G4double energy = gammaProcess->GetProposedKineticEnergy(); G4double globalTime = track.GetGlobalTime(); - //G4double newGammaWeight = weight; G4ThreeVector polarization = gammaProcess->GetProposedPolarization(); const G4ThreeVector momentum = gammaProcess->GetProposedMomentumDirection(); const G4ThreeVector position = track.GetPosition(); G4Track *newTrack = new G4Track(track); - //newTrack->SetWeight(newGammaWeight); newTrack->SetKineticEnergy(energy); newTrack->SetMomentumDirection(momentum); newTrack->SetPosition(position); @@ -157,23 +169,20 @@ G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack(G4ParticleC void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process, LastVertexDataContainer container) { G4TrackVector *trackVector = CurrentStep->GetfSecondary(); - G4double gammaWeight = 0; - GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; G4VParticleChange *processFinalState = emProcess->PostStepDoIt(*fTrackToSplit, *initStep); G4ParticleChangeForGamma *gammaProcessFinalState = (G4ParticleChangeForGamma *)processFinalState; G4ThreeVector momentum = gammaProcessFinalState->GetProposedMomentumDirection(); - gammaWeight = fTrackToSplit->GetWeight()/ fSplittingFactor; - G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, *fTrackToSplit, gammaWeight); + G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, *fTrackToSplit, fWeight); - if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(newTrack->GetMomentumDirection(),fVectorDirector,fMaxTheta) == false)){ + if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(newTrack->GetMomentumDirection(),fVectorDirector) == false)){ delete newTrack; ComptonSplitting(initStep,CurrentStep,process,container); } else{ - trackVector->push_back(newTrack); + trackVector->emplace_back(newTrack); } @@ -191,21 +200,6 @@ void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step* initStep, } -G4VProcess* GateLastVertexInteractionSplittingActor::GetProcessFromProcessName(G4String particleName, G4String pName){ - auto *particle_table = G4ParticleTable::GetParticleTable(); - G4ParticleDefinition *particleDefinition = particle_table->FindParticle(particleName); - G4ProcessManager *processManager = particleDefinition->GetProcessManager(); - G4ProcessVector *processList = processManager->GetProcessList(); - G4VProcess* nullProcess = nullptr; - for (size_t i = 0; i < processList->size(); ++i) { - auto process = (*processList)[i]; - if (process->GetProcessName() == pName) { - return process; - } - } - return nullProcess; - -} G4Track GateLastVertexInteractionSplittingActor::eBremProcessFinalState(G4Track* track, G4Step* step,G4VProcess *process){ @@ -238,7 +232,6 @@ void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initS SimpleContainer container = theContainer.GetContainerToSplit(); G4String particleName = fTrackToSplit->GetParticleDefinition()->GetParticleName(); G4TrackVector *trackVector = CurrentStep->GetfSecondary(); - G4double gammaWeight = 0; G4VParticleChange *processFinalState = nullptr; GateBremPostStepDoIt* bremProcess = nullptr; GateGammaEmPostStepDoIt *emProcess = nullptr; @@ -276,19 +269,18 @@ void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initS } G4int idx = 0; - gammaWeight = fTrackToSplit->GetWeight()/fSplittingFactor; G4bool IsPushBack =false; for (int i; i < NbOfSecondaries; i++){ G4Track *newTrack = processFinalState->GetSecondary(i); G4ThreeVector momentum = newTrack->GetMomentumDirection(); if (!(isnan(momentum[0]))){ - if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(momentum,fVectorDirector,fMaxTheta) == false)){ + if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(momentum,fVectorDirector) == false)){ delete newTrack; } else { - newTrack->SetWeight(gammaWeight); + newTrack->SetWeight(fWeight); newTrack->SetCreatorProcess(process); - trackVector->push_back(newTrack); + trackVector->emplace_back(newTrack); IsPushBack=true; break; @@ -505,6 +497,9 @@ void GateLastVertexInteractionSplittingActor::StartSimulationAction (){ auto* source = fSourceManager->FindSourceByName("source_vertex"); fVertexSource = (GateLastVertexSource* ) source; + + fCosMaxTheta = std::cos(fMaxTheta); + } @@ -543,6 +538,8 @@ void GateLastVertexInteractionSplittingActor::BeginOfEventAction( fTrackToSplit = nullptr; } fTrackToSplit = CreateATrackFromContainer(fContainer); + if (fTrackToSplit != 0) + fWeight = fTrackToSplit->GetWeight()/fSplittingFactor; } @@ -564,7 +561,7 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { FillOfDataTree(step); if (IsParticleExitTheBiasedVolume(step)){ - if ((fAngularKill == false) || ((fAngularKill == true) && (DoesParticleEmittedInSolidAngle(step->GetTrack()->GetMomentumDirection(),fVectorDirector,fMaxTheta) == true))){ + if ((fAngularKill == false) || ((fAngularKill == true) && (DoesParticleEmittedInSolidAngle(step->GetTrack()->GetMomentumDirection(),fVectorDirector) == true))){ fListOfContainer.push_back((*fIterator)); } @@ -603,7 +600,7 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { step->GetfSecondary()->clear(); //FIXME : list of process which are not splitable yet if ((fProcessNameToSplit != "msc") && (fProcessNameToSplit != "conv") && (fProcessNameToSplit != "eIoni")) { - fCopyInitStep= new G4Step(*step); + fCopyInitStep = new G4Step(*step); if (fProcessNameToSplit == "eBrem"){ fCopyInitStep->SetStepLength(fContainer.GetContainerToSplit().GetStepLength()); fCopyInitStep->GetPreStepPoint()->SetKineticEnergy(fContainer.GetContainerToSplit().GetEnergy()); diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h index bdb5092b1..7ad080cfd 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h @@ -54,6 +54,7 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { G4bool fRotationVectorDirector; G4ThreeVector fVectorDirector; G4double fMaxTheta; + G4double fCosMaxTheta; G4int fTrackIDOfSplittedTrack = 0; G4int fParentID = -1; G4int fEventID; @@ -74,6 +75,7 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { G4bool fKilledBecauseOfProcess = false; G4bool fFirstSplittedPart = true; G4bool fOnlyTree = false; + G4double fWeight; GateLastVertexSource* fVertexSource = nullptr; tree<LastVertexDataContainer> fTree; tree<LastVertexDataContainer>::post_order_iterator fIterator; @@ -108,7 +110,7 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { virtual void PostUserTrackingAction(const G4Track *track) override; // Pure splitting functions - G4bool DoesParticleEmittedInSolidAngle(G4ThreeVector dir, G4ThreeVector vectorDirector, G4double maxTheta); + G4bool DoesParticleEmittedInSolidAngle(G4ThreeVector dir, G4ThreeVector vectorDirector); G4Track *CreateComptonTrack(G4ParticleChangeForGamma *, G4Track, G4double); void ComptonSplitting(G4Step* initStep,G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container); void SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container); From 102b6242d8eda2b73404b2a0733635c513f5a5ff Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Wed, 23 Oct 2024 17:49:24 +0200 Subject: [PATCH 36/82] Dev of a batching method to create splitted tracks --- ...ateLastVertexInteractionSplittingActor.cpp | 238 ++++++++---------- .../GateLastVertexInteractionSplittingActor.h | 13 +- opengate/actors/miscactors.py | 2 +- 3 files changed, 116 insertions(+), 137 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index f31cf11e6..eb53adad8 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -62,6 +62,7 @@ GateLastVertexInteractionSplittingActor:: fAngularKill = DictGetBool(user_info, "angular_kill"); fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); fMaxTheta = DictGetDouble(user_info, "max_theta"); + fBatchSize = DictGetDouble(user_info, "batch_size"); fActions.insert("StartSimulationAction"); fActions.insert("SteppingAction"); fActions.insert("BeginOfEventAction"); @@ -166,37 +167,37 @@ G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack(G4ParticleC return newTrack; } -void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process, LastVertexDataContainer container) { +void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process, LastVertexDataContainer container, G4double batchSize) { - G4TrackVector *trackVector = CurrentStep->GetfSecondary(); + //G4TrackVector *trackVector = CurrentStep->GetfSecondary(); GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; - G4VParticleChange *processFinalState = emProcess->PostStepDoIt(*fTrackToSplit, *initStep); - G4ParticleChangeForGamma *gammaProcessFinalState = (G4ParticleChangeForGamma *)processFinalState; + for (int i = 0; i < batchSize; i++){ + G4VParticleChange *processFinalState = emProcess->PostStepDoIt(*fTrackToSplit, *initStep); + G4ParticleChangeForGamma *gammaProcessFinalState = (G4ParticleChangeForGamma *)processFinalState; - G4ThreeVector momentum = gammaProcessFinalState->GetProposedMomentumDirection(); + G4ThreeVector momentum = gammaProcessFinalState->GetProposedMomentumDirection(); - G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, *fTrackToSplit, fWeight); + G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, *fTrackToSplit, fWeight); - if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(newTrack->GetMomentumDirection(),fVectorDirector) == false)){ - delete newTrack; - ComptonSplitting(initStep,CurrentStep,process,container); - } - else{ - trackVector->emplace_back(newTrack); - } + if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(newTrack->GetMomentumDirection(),fVectorDirector) == false)){ + delete newTrack; + } + else{ + fStackManager->PushOneTrack(newTrack); + } + + + // Special case here, since we generate independently each particle, we will not attach an electron to exiting compton photon, but we will the secondaries. + + if (processFinalState->GetNumberOfSecondaries()> 0){ + delete processFinalState->GetSecondary(0); + } - // Special case here, since we generate independently each particle, we will not attach an electron to exiting compton photon. - /* - G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - for (int j = 0; j < NbOfSecondaries; j++) { - G4Track *newTrack = processFinalState->GetSecondary(j); - newTrack->SetWeight(gammaWeight); - trackVector->push_back(newTrack); + + processFinalState->Clear(); + gammaProcessFinalState->Clear(); } - */ - processFinalState->Clear(); - gammaProcessFinalState->Clear(); } @@ -228,10 +229,11 @@ G4Track GateLastVertexInteractionSplittingActor::eBremProcessFinalState(G4Track* } -void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer theContainer) { +void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer theContainer, G4double batchSize) { SimpleContainer container = theContainer.GetContainerToSplit(); G4String particleName = fTrackToSplit->GetParticleDefinition()->GetParticleName(); - G4TrackVector *trackVector = CurrentStep->GetfSecondary(); + //G4TrackVector *trackVector = CurrentStep->GetfSecondary(); + G4VParticleChange *processFinalState = nullptr; GateBremPostStepDoIt* bremProcess = nullptr; GateGammaEmPostStepDoIt *emProcess = nullptr; @@ -243,80 +245,86 @@ void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initS if ((container.GetAnnihilationFlag() == "AtRest") || (fTrackToSplit->GetKineticEnergy() == 0)) { eplusAnnihilProcess = (GateplusannihilAtRestDoIt *)process; } - - - - - G4int NbOfSecondaries = 0; - while (NbOfSecondaries == 0){ - if (process->GetProcessName() == "eBrem") { - G4Track aTrack = eBremProcessFinalState(fTrackToSplit,initStep,process); - bremProcess = (GateBremPostStepDoIt*) process; - processFinalState = bremProcess->GateBremPostStepDoIt::PostStepDoIt(aTrack, *initStep); - } - else { - if ((container.GetAnnihilationFlag() == "PostStep") && (fTrackToSplit->GetKineticEnergy() > 0)){ - processFinalState = emProcess->PostStepDoIt(*fTrackToSplit, *initStep); + for (int j = 0; j < batchSize;j++){ + G4int NbOfSecondaries = 0; + + while (NbOfSecondaries == 0){ + if (process->GetProcessName() == "eBrem") { + G4Track aTrack = eBremProcessFinalState(fTrackToSplit,initStep,process); + bremProcess = (GateBremPostStepDoIt*) process; + processFinalState = bremProcess->GateBremPostStepDoIt::PostStepDoIt(aTrack, *initStep); + } + else { + if ((container.GetAnnihilationFlag() == "PostStep") && (fTrackToSplit->GetKineticEnergy() > 0)){ + processFinalState = emProcess->PostStepDoIt(*fTrackToSplit, *initStep); + } + if ((container.GetAnnihilationFlag() == "AtRest") || (fTrackToSplit->GetKineticEnergy() == 0)) { + processFinalState = eplusAnnihilProcess->GateplusannihilAtRestDoIt::AtRestDoIt(*fTrackToSplit,*initStep); + } } - if ((container.GetAnnihilationFlag() == "AtRest") || (fTrackToSplit->GetKineticEnergy() == 0)) { - processFinalState = eplusAnnihilProcess->GateplusannihilAtRestDoIt::AtRestDoIt(*fTrackToSplit,*initStep); + NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + if (NbOfSecondaries == 0){ + processFinalState->Clear(); } } - NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - if (NbOfSecondaries == 0){ - processFinalState->Clear(); - } - } - G4int idx = 0; - G4bool IsPushBack =false; - for (int i; i < NbOfSecondaries; i++){ - G4Track *newTrack = processFinalState->GetSecondary(i); - G4ThreeVector momentum = newTrack->GetMomentumDirection(); - if (!(isnan(momentum[0]))){ - if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(momentum,fVectorDirector) == false)){ - delete newTrack; + G4int idx = 0; + G4bool IsPushBack =false; + for (int i=0; i < NbOfSecondaries; i++){ + G4Track *newTrack = processFinalState->GetSecondary(i); + G4ThreeVector momentum = newTrack->GetMomentumDirection(); + + if (!(isnan(momentum[0]))){ + if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(momentum,fVectorDirector) == false)){ + delete newTrack; + } + else if (IsPushBack == true){ + delete newTrack; + } + else { + newTrack->SetWeight(fWeight); + newTrack->SetCreatorProcess(process); + //trackVector->emplace_back(newTrack); + fStackManager->PushOneTrack(newTrack); + //delete newTrack; + IsPushBack=true; + + } } else { - newTrack->SetWeight(fWeight); - newTrack->SetCreatorProcess(process); - trackVector->emplace_back(newTrack); - IsPushBack=true; - break; - + delete newTrack; } } - else { - delete newTrack; - } + processFinalState->Clear(); } - - if (IsPushBack == false) - SecondariesSplitting(initStep,CurrentStep,process,theContainer); - processFinalState->Clear(); - - } -void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G4Step* initStep,G4Step *step,LastVertexDataContainer theContainer) { +void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G4Step* initStep,G4Step *step,LastVertexDataContainer theContainer, G4double batchSize) { // We retrieve the process associated to the process name to split and we // split according the process. Since for compton scattering, the gamma is not // a secondary particles, this one need to have his own splitting function. G4String processName = fProcessNameToSplit; + G4int nbOfTrackAlreadyInStack = fStackManager->GetNTotalTrack(); if ((fProcessToSplit == 0) || (fProcessToSplit == nullptr)){ SimpleContainer container = theContainer.GetContainerToSplit(); fProcessToSplit = GetProcessFromProcessName(container.GetParticleNameToSplit(),processName); } if (processName == "compt") { - ComptonSplitting(initStep,step, fProcessToSplit, theContainer); + ComptonSplitting(initStep,step, fProcessToSplit, theContainer, batchSize); } else if((processName != "msc") && (processName != "conv")){ - SecondariesSplitting(initStep, step, fProcessToSplit, theContainer); + SecondariesSplitting(initStep, step, fProcessToSplit, theContainer,batchSize); } + fNumberOfTrackToSimulate = fStackManager->GetNTotalTrack() - nbOfTrackAlreadyInStack; + fNbOfBatchForExitingParticle ++; + if (fNbOfBatchForExitingParticle >500){ + fStackManager->clear(); + } + //stackManager->clear(); } @@ -424,11 +432,8 @@ void GateLastVertexInteractionSplittingActor::FillOfDataTree(G4Step*step){ energy -= (step->GetTotalEnergyDeposit()); } SimpleContainer containerToSplit = SimpleContainer(processName,energy, momentum, position,polarization,particleName,weight,trackStatus,nbOfSecondaries,annihilFlag,stepLength,prePosition); - //std::cout<<"during saving " <<containerToSplit.GetProcessNameToSplit()<<std::endl; container->SetContainerToSplit(containerToSplit); container->PushListOfSplittingParameters(containerToSplit); - - //std::cout<<fEventID<<" recordProcess "<<container->GetContainerToSplit().GetProcessNameToSplit()<<std::endl; } } @@ -499,6 +504,8 @@ void GateLastVertexInteractionSplittingActor::StartSimulationAction (){ fVertexSource = (GateLastVertexSource* ) source; fCosMaxTheta = std::cos(fMaxTheta); + std::cout<<"batch size "<<fBatchSize<<std::endl; + fStackManager = G4EventManager::GetEventManager()->GetStackManager(); } @@ -516,14 +523,18 @@ void GateLastVertexInteractionSplittingActor::BeginOfRunAction( void GateLastVertexInteractionSplittingActor::BeginOfEventAction( const G4Event *event) { - fParentID = -1; fEventID = event->GetEventID(); - fEventIDOfSplittedTrack = -1; - fTrackIDOfSplittedTrack = -1; fIsAnnihilAlreadySplit = false; + fNbOfBatchForExitingParticle = 0; if (fEventID%50000 == 0) std::cout<<"event ID : "<<fEventID<<std::endl; - fCopyInitStep = nullptr; + if (fCopyInitStep != 0){ + delete fCopyInitStep; + fCopyInitStep = nullptr; + } + fSplitCounter =0; + fNumberOfTrackToSimulate =0; + fKilledBecauseOfProcess = false; if (fActiveSource == "source_vertex"){ auto* source = fSourceManager->FindSourceByName(fActiveSource); @@ -543,8 +554,6 @@ void GateLastVertexInteractionSplittingActor::BeginOfEventAction( } - - } void GateLastVertexInteractionSplittingActor::PreUserTrackingAction( @@ -596,8 +605,7 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { if (((step->GetTrack()->GetParentID() == 0)&& (step->GetTrack()->GetTrackID() == 1))|| ((creatorProcessName == "annihil") && (step->GetTrack()->GetParentID() == 1))){ if ((fProcessNameToSplit != "annihil") || ((fProcessNameToSplit == "annihil")&& (fIsAnnihilAlreadySplit ==false))){ - - step->GetfSecondary()->clear(); + //FIXME : list of process which are not splitable yet if ((fProcessNameToSplit != "msc") && (fProcessNameToSplit != "conv") && (fProcessNameToSplit != "eIoni")) { fCopyInitStep = new G4Step(*step); @@ -606,9 +614,9 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { fCopyInitStep->GetPreStepPoint()->SetKineticEnergy(fContainer.GetContainerToSplit().GetEnergy()); } - CreateNewParticleAtTheLastVertex(fCopyInitStep,step,fContainer); + CreateNewParticleAtTheLastVertex(fCopyInitStep,step,fContainer, fBatchSize); } - step->GetTrack()->SetTrackStatus(fStopAndKill); + step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); if (fProcessNameToSplit == "annihil"){ fIsAnnihilAlreadySplit = true; @@ -617,15 +625,14 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { else if ((fProcessNameToSplit == "annihil")&& (fIsAnnihilAlreadySplit == true)){ - step->GetfSecondary()->clear(); - step->GetTrack()->SetTrackStatus(fStopAndKill); + step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); } } else { - if (fIsFirstStep){ + fNumberOfTrackToSimulate --; if (fKilledBecauseOfProcess == false){ fSplitCounter += 1; } @@ -633,45 +640,25 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { fKilledBecauseOfProcess = false; } - if (fSplitCounter < fSplittingFactor){ - CreateNewParticleAtTheLastVertex(fCopyInitStep,step,fContainer); + if (fSplitCounter > fSplittingFactor){ + step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); + fStackManager->clear(); } } if (IsTheParticleUndergoesALossEnergyProcess(step)){ - //std::cout<<"kill because process"<<std::endl; - step->GetfSecondary()->clear(); - CreateNewParticleAtTheLastVertex(fCopyInitStep,step,fContainer); - step->GetTrack()->SetTrackStatus(fStopAndKill); + step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); fKilledBecauseOfProcess = true; } - - - } - - /* - else if (IsParticleExitTheBiasedVolume(step)){ - fSplitCounter += 1; - if (fSplitCounter < fSplittingFactor){ - CreateNewParticleAtTheLastVertex(fCopyInitStep,step,container); - } - else{ - delete fCopyInitStep; - fCopyInitStep = nullptr; - fSplitCounter = 0; + if (fIsFirstStep){ + if (fSplitCounter <= fSplittingFactor){ + if (fNumberOfTrackToSimulate == 0){ + CreateNewParticleAtTheLastVertex(fCopyInitStep,step,fContainer,(fSplittingFactor - fSplitCounter +1)/fSplittingFactor * fBatchSize); + } + } } - - - //FIXME Debug case if splitting factor equal to 1, as It is used as a condition to enable the split - // I just set the weight to a very close value of the real one - if (fSplittingFactor != 1) - step->GetPostStepPoint()->SetWeight(container.GetContainerToSplit().GetWeight()/fSplittingFactor); - else - step->GetPostStepPoint()->SetWeight(container.GetContainerToSplit().GetWeight()*0.99999999); - - } - */ + } } } } @@ -683,29 +670,16 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { } -void GateLastVertexInteractionSplittingActor::PostUserTrackingAction( - const G4Track *track) { - if ((fSplitCounter == fSplittingFactor) && (fKilledBecauseOfProcess == false)){ - delete fCopyInitStep; - fCopyInitStep = nullptr; - fSplitCounter = 0; - } - - } - void GateLastVertexInteractionSplittingActor::EndOfEventAction( const G4Event* event) { - if (fActiveSource != "source_vertex"){ //print_tree(fTree,fTree.begin(),fTree.end()); fVertexSource->SetNumberOfEventToSimulate(fListOfContainer.size()); fVertexSource->SetNumberOfGeneratedEvent(0); fVertexSource->SetListOfVertexToSimulate(fListOfContainer); - - //fDataMap.clear(); fTree.clear(); fListOfContainer.clear(); } diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h index 7ad080cfd..f290c08b8 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h @@ -40,6 +40,7 @@ #include <iostream> #include "GateLastVertexSource.h" #include "CLHEP/Vector/ThreeVector.h" +#include "G4StackManager.hh" using CLHEP::Hep3Vector; namespace py = pybind11; @@ -76,10 +77,15 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { G4bool fFirstSplittedPart = true; G4bool fOnlyTree = false; G4double fWeight; + G4double fBatchSize; + G4int fNumberOfTrackToSimulate = 0; + G4int fNbOfBatchForExitingParticle=0; + G4int fTracksCounts=0; GateLastVertexSource* fVertexSource = nullptr; tree<LastVertexDataContainer> fTree; tree<LastVertexDataContainer>::post_order_iterator fIterator; std::vector<LastVertexDataContainer> fListOfContainer; + G4StackManager* fStackManager = nullptr; @@ -107,15 +113,14 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { virtual void EndOfEventAction(const G4Event *) override; virtual void BeginOfRunAction(const G4Run *run) override; virtual void PreUserTrackingAction(const G4Track *track) override; - virtual void PostUserTrackingAction(const G4Track *track) override; // Pure splitting functions G4bool DoesParticleEmittedInSolidAngle(G4ThreeVector dir, G4ThreeVector vectorDirector); G4Track *CreateComptonTrack(G4ParticleChangeForGamma *, G4Track, G4double); - void ComptonSplitting(G4Step* initStep,G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container); - void SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container); + void ComptonSplitting(G4Step* initStep,G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container, G4double batchSize); + void SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container, G4double batchSize); - void CreateNewParticleAtTheLastVertex(G4Step*init,G4Step *current, LastVertexDataContainer); + void CreateNewParticleAtTheLastVertex(G4Step*init,G4Step *current, LastVertexDataContainer, G4double batchSize); G4Track* CreateATrackFromContainer(LastVertexDataContainer container); G4bool IsTheParticleUndergoesAProcess(G4Step* step); G4bool IsTheParticleUndergoesALossEnergyProcess(G4Step* step); diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 853b5e450..404d964f0 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -495,7 +495,7 @@ def set_default_user_info(user_info): user_info.vector_director = [0, 0, -1] user_info.max_theta = 90 * deg user_info.list_of_volume_name = [] - + user_info.batch_size = 1 def __init__(self, user_info): ActorBase.__init__(self, user_info) g4.GateLastVertexInteractionSplittingActor.__init__(self, user_info.__dict__) From 2dfaea550bca993691cb620acbf823faa6641c92 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 29 Oct 2024 15:39:51 +0100 Subject: [PATCH 37/82] bug correction --- .../opengate_lib/GateLastVertexSplittingPostStepDoIt.h | 1 - 1 file changed, 1 deletion(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h index 241eeab57..38a9dc2ea 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h @@ -65,7 +65,6 @@ virtual G4VParticleChange * PostStepDoIt (const G4Track & track, const G4Step & virtual G4VParticleChange * AlongStepDoIt (const G4Track & track, const G4Step & step) override { - isIonisation = true; G4VParticleChange* particleChange = G4VEnergyLossProcess::AlongStepDoIt(track,step); return particleChange; } From 21f148ac6321a06f36eea3315f6ba54b1a7979b1 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Mon, 4 Nov 2024 12:33:01 +0100 Subject: [PATCH 38/82] Add of a security break in case of infinite loop --- .../GateLastVertexInteractionSplittingActor.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index eb53adad8..0bf43bac9 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -248,6 +248,7 @@ void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initS for (int j = 0; j < batchSize;j++){ G4int NbOfSecondaries = 0; + G4int count =0; while (NbOfSecondaries == 0){ if (process->GetProcessName() == "eBrem") { G4Track aTrack = eBremProcessFinalState(fTrackToSplit,initStep,process); @@ -266,6 +267,15 @@ void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initS if (NbOfSecondaries == 0){ processFinalState->Clear(); } + count ++; + //Security break, in case of infinite loop + if (count > 10000){ + G4ExceptionDescription ed; + ed << " infinite loop detected during the track creation for the " <<process->GetProcessName() <<" process"<<G4endl; + G4Exception("GateLastVertexInteractionSplittingActor::SecondariesSplitting","BIAS.LV1",JustWarning,ed); + G4RunManager::GetRunManager()->AbortEvent(); + break; + } } G4int idx = 0; From 1ae349369eefd61fbe23b5090059aa4283eeb34f Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Fri, 8 Nov 2024 14:23:31 +0100 Subject: [PATCH 39/82] Add C++ code of the kill actor --- core/opengate_core/opengate_core.cpp | 3 + .../GateKillNonInteractingParticleActor.cpp | 74 +++++++++++++++++++ .../GateKillNonInteractingParticleActor.h | 40 ++++++++++ .../pyGateKillNonInteractingParticleActor.cpp | 22 ++++++ 4 files changed, 139 insertions(+) create mode 100644 core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp create mode 100644 core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.h create mode 100644 core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp diff --git a/core/opengate_core/opengate_core.cpp b/core/opengate_core/opengate_core.cpp index 760a93135..952ddbeb8 100644 --- a/core/opengate_core/opengate_core.cpp +++ b/core/opengate_core/opengate_core.cpp @@ -309,6 +309,8 @@ void init_GateARFTrainingDatasetActor(py::module &m); void init_GateKillActor(py::module &); +void init_GateKillNonInteractingParticleActor(py::module &); + void init_itk_image(py::module &); void init_GateImageNestedParameterisation(py::module &); @@ -586,6 +588,7 @@ PYBIND11_MODULE(opengate_core, m) { init_GateARFActor(m); init_GateARFTrainingDatasetActor(m); init_GateKillActor(m); + init_GateKillNonInteractingParticleActor(m); init_GateDigiAttributeManager(m); init_GateVDigiAttribute(m); init_GateExceptionHandler(m); diff --git a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp new file mode 100644 index 000000000..0dc62d5a0 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp @@ -0,0 +1,74 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + ------------------------------------ -------------- */ + +#include "GateKillNonInteractingParticleActor.h" +#include "G4LogicalVolumeStore.hh" +#include "G4PhysicalVolumeStore.hh" +#include "G4ios.hh" +#include "GateHelpers.h" +#include "GateHelpersDict.h" + +GateKillNonInteractingParticleActor::GateKillNonInteractingParticleActor( + py::dict &user_info) + : GateVActor(user_info, false) { + fActions.insert("StartSimulationAction"); + fActions.insert("SteppingAction"); + fActions.insert("PreUserTrackingAction"); +} + + +void GateKillNonInteractingParticleActor::StartSimulationAction() { + fNbOfKilledParticles = 0; +} + +void GateKillNonInteractingParticleActor::PreUserTrackingAction( + const G4Track *track) { + fIsFirstStep = true; + fKineticEnergyAtTheEntrance = 0; + ftrackIDAtTheEntrance = 0; + fPassedByTheMotherVolume = false; +} + +void GateKillNonInteractingParticleActor::SteppingAction(G4Step *step) { + + G4String logNameMotherVolume = G4LogicalVolumeStore::GetInstance() + ->GetVolume(fMotherVolumeName) + ->GetName(); + G4String physicalVolumeNamePreStep = + step->GetPreStepPoint()->GetPhysicalVolume()->GetName(); + if ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != + logNameMotherVolume) && + (fIsFirstStep)) { + if ((fPassedByTheMotherVolume == false) && + (physicalVolumeNamePreStep == fMotherVolumeName) && + (step->GetPreStepPoint()->GetStepStatus() == 1)) { + fPassedByTheMotherVolume = true; + fKineticEnergyAtTheEntrance = step->GetPreStepPoint()->GetKineticEnergy(); + ftrackIDAtTheEntrance = step->GetTrack()->GetTrackID(); + } + } + + G4String logicalVolumeNamePostStep = step->GetPostStepPoint() + ->GetPhysicalVolume() + ->GetLogicalVolume() + ->GetName(); + if ((fPassedByTheMotherVolume) && + (step->GetPostStepPoint()->GetStepStatus() == 1)) { + if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), + logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { + if ((step->GetTrack()->GetTrackID() == ftrackIDAtTheEntrance) && + (step->GetPostStepPoint()->GetKineticEnergy() == + fKineticEnergyAtTheEntrance)) { + auto track = step->GetTrack(); + track->SetTrackStatus(fStopAndKill); + fNbOfKilledParticles++; + } + } + } + + fIsFirstStep = false; +} diff --git a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.h b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.h new file mode 100644 index 000000000..b0e0066d1 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.h @@ -0,0 +1,40 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#ifndef GateKillNonInteractingParticleActor_h +#define GateKillNonInteractingParticleActor_h + +#include "G4Cache.hh" +#include "GateVActor.h" +#include <pybind11/stl.h> + +namespace py = pybind11; + +class GateKillNonInteractingParticleActor : public GateVActor { + +public: + // Constructor + GateKillNonInteractingParticleActor(py::dict &user_info); + + void StartSimulationAction() override; + + // Main function called every step in attached volume + void SteppingAction(G4Step *) override; + + void PreUserTrackingAction(const G4Track *) override; + + std::vector<G4String> fParticlesTypeToKill; + G4bool fPassedByTheMotherVolume = false; + G4double fKineticEnergyAtTheEntrance = 0; + G4int ftrackIDAtTheEntrance = 0; + G4bool fIsFirstStep = true; + std::vector<std::string> fListOfVolumeAncestor; + + long fNbOfKilledParticles{}; +}; + +#endif diff --git a/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp new file mode 100644 index 000000000..5de9fe12b --- /dev/null +++ b/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp @@ -0,0 +1,22 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include <pybind11/pybind11.h> + +namespace py = pybind11; + +#include "GateKillNonInteractingParticleActor.h" + +void init_GateKillNonInteractingParticleActor(py::module &m) { + py::class_<GateKillNonInteractingParticleActor, + std::unique_ptr<GateKillNonInteractingParticleActor, py::nodelete>, + GateVActor>(m, "GateKillNonInteractingParticleActor") + .def_readwrite( + "fListOfVolumeAncestor", + &GateKillNonInteractingParticleActor::fListOfVolumeAncestor) + .def(py::init<py::dict &>()); +} From 051d05afe829596c10fd4419fa0b24ba9ef87f3e Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Fri, 8 Nov 2024 18:28:54 +0100 Subject: [PATCH 40/82] modify the python code to retrieve volumes_tree --- .../GateKillNonInteractingParticleActor.cpp | 23 ++++--------- opengate/actors/miscactors.py | 33 +++++++++++++++++++ opengate/managers.py | 5 +++ 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp index 0dc62d5a0..93eb2fa25 100644 --- a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp +++ b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp @@ -15,9 +15,6 @@ GateKillNonInteractingParticleActor::GateKillNonInteractingParticleActor( py::dict &user_info) : GateVActor(user_info, false) { - fActions.insert("StartSimulationAction"); - fActions.insert("SteppingAction"); - fActions.insert("PreUserTrackingAction"); } @@ -38,13 +35,11 @@ void GateKillNonInteractingParticleActor::SteppingAction(G4Step *step) { G4String logNameMotherVolume = G4LogicalVolumeStore::GetInstance() ->GetVolume(fMotherVolumeName) ->GetName(); - G4String physicalVolumeNamePreStep = - step->GetPreStepPoint()->GetPhysicalVolume()->GetName(); - if ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != - logNameMotherVolume) && - (fIsFirstStep)) { - if ((fPassedByTheMotherVolume == false) && - (physicalVolumeNamePreStep == fMotherVolumeName) && + G4String physicalVolumeNamePreStep = "None"; + if (step->GetPreStepPoint()->GetPhysicalVolume() !=0) + physicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetName(); + if ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != logNameMotherVolume) && (fIsFirstStep)) { + if ((fPassedByTheMotherVolume == false) && (physicalVolumeNamePreStep == fMotherVolumeName) && (step->GetPreStepPoint()->GetStepStatus() == 1)) { fPassedByTheMotherVolume = true; fKineticEnergyAtTheEntrance = step->GetPreStepPoint()->GetKineticEnergy(); @@ -52,12 +47,8 @@ void GateKillNonInteractingParticleActor::SteppingAction(G4Step *step) { } } - G4String logicalVolumeNamePostStep = step->GetPostStepPoint() - ->GetPhysicalVolume() - ->GetLogicalVolume() - ->GetName(); - if ((fPassedByTheMotherVolume) && - (step->GetPostStepPoint()->GetStepStatus() == 1)) { + G4String logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + if ((fPassedByTheMotherVolume) && (step->GetPostStepPoint()->GetStepStatus() == 1)) { if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { if ((step->GetTrack()->GetTrackID() == ftrackIDAtTheEntrance) && diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index f580f6abc..ffb1a5747 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -7,6 +7,8 @@ from ..serialization import dump_json from ..exception import warning from ..base import process_cls +from anytree import Node, RenderTree + def _setter_hook_stats_actor_output_filename(self, output_filename): @@ -298,6 +300,36 @@ def EndSimulationAction(self): self.number_of_killed_particles = self.GetNumberOfKilledParticles() +class KillNonInteractingParticleActor(ActorBase, g4.GateKillNonInteractingParticleActor): + + def __init__(self, *args, **kwargs): + ActorBase.__init__(self, *args, **kwargs) + self.__initcpp__() + self.list_of_volume_name = [] + + def __initcpp__(self): + g4.GateKillNonInteractingParticleActor.__init__(self, self.user_info) + self.AddActions( + {"StartSimulationAction","PreUserTrackingAction", "SteppingAction"} + ) + + def initialize(self): + ActorBase.initialize(self) + self.InitializeUserInput(self.user_info) + self.InitializeCpp() + volume_tree = self.simulation.volume_manager.get_volume_tree() + print(type(volume_tree)) + dico_of_volume_tree = {} + for pre, _, node in RenderTree(volume_tree): + dico_of_volume_tree[str(node.name)] = node + volume_name = self.user_info.attached_to + while volume_name != "world": + node = dico_of_volume_tree[volume_name] + volume_name = node.mother + self.list_of_volume_name.append(volume_name) + self.fListOfVolumeAncestor = self.list_of_volume_name + + def _setter_hook_particles(self, value): if isinstance(value, str): return [value] @@ -305,6 +337,7 @@ def _setter_hook_particles(self, value): return list(value) + class SplittingActorBase(ActorBase): # hints for IDE splitting_factor: int diff --git a/opengate/managers.py b/opengate/managers.py index a0de748be..2ab7ef5c3 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -73,6 +73,7 @@ from .actors.miscactors import ( SimulationStatisticsActor, KillActor, + KillNonInteractingParticleActor, SplittingActorBase, ComptSplittingActor, BremSplittingActor, @@ -107,6 +108,7 @@ "ARFTrainingDatasetActor": ARFTrainingDatasetActor, "SimulationStatisticsActor": SimulationStatisticsActor, "KillActor": KillActor, + "KillNonInteractingParticleActor":KillNonInteractingParticleActor, "BremSplittingActor": BremSplittingActor, "ComptSplittingActor": ComptSplittingActor, "DigitizerAdderActor": DigitizerAdderActor, @@ -1183,6 +1185,9 @@ def dump_volume_tree(self): # FIXME: pre should be used directly but cannot be encoded correctly in Windows s += len(pre) * " " + f"{node.name}\n" return s + + def get_volume_tree(self): + return self.volume_tree_root def print_volume_tree(self): print(self.dump_volume_tree()) From 03e7c401bc8c8355770963fca4b123ad6f4c2541 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Fri, 8 Nov 2024 18:29:32 +0100 Subject: [PATCH 41/82] Add of a test to verify if killed or not --- .../test074_kill_non_interacting_particles.py | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 opengate/tests/src/test074_kill_non_interacting_particles.py diff --git a/opengate/tests/src/test074_kill_non_interacting_particles.py b/opengate/tests/src/test074_kill_non_interacting_particles.py new file mode 100644 index 000000000..b12a6f66d --- /dev/null +++ b/opengate/tests/src/test074_kill_non_interacting_particles.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +from opengate.tests import utility +from scipy.spatial.transform import Rotation +import numpy as np +from anytree import Node, RenderTree +import uproot + + +def test074_test(entry_data, exit_data_1, exit_data_2): + liste_ekin = [] + liste_evtID = [] + liste_trackID = [] + evt_ID_entry_data = entry_data["EventID"] + j = 0 + i = 0 + while i < len(evt_ID_entry_data): + if ( + j < len(exit_data_1["EventID"]) + and evt_ID_entry_data[i] == exit_data_1["EventID"][j] + ): + TID_entry = entry_data["TrackID"][i] + TID_exit = exit_data_1["TrackID"][j] + Ekin_entry = entry_data["KineticEnergy"][i] + Ekin_exit = exit_data_1["KineticEnergy"][j] + + if (TID_entry == TID_exit) and (Ekin_exit == Ekin_entry): + liste_ekin.append(exit_data_1["KineticEnergy"][j]) + liste_evtID.append(exit_data_1["EventID"][j]) + liste_trackID.append(exit_data_1["TrackID"][j]) + if (j < len(exit_data_1["EventID"]) - 1) and ( + exit_data_1["EventID"][j] == exit_data_1["EventID"][j + 1] + ): + i = i - 1 + j += 1 + i += 1 + liste_ekin = np.asarray(liste_ekin) + print("Number of tracks to kill =", len(liste_ekin)) + print( + "Number of killed tracks =", + (len(exit_data_1["EventID"]) - len(exit_data_2["EventID"])), + ) + + return len(liste_ekin) == ( + len(exit_data_1["EventID"]) - len(exit_data_2["EventID"]) + ) + + +if __name__ == "__main__": + paths = utility.get_default_test_paths(__file__) + output_path = paths.output + + + print(output_path) + # create the simulation + sim = gate.Simulation() + sim.output_dir = output_path + + # main options + ui = sim.user_info + ui.g4_verbose = False + # ui.visu = True + ui.visu_type = "vrml" + ui.check_volumes_overlap = False + # ui.running_verbose_level = gate.logger.EVENT + ui.number_of_threads = 1 + ui.random_seed = "auto" + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + sec = gate.g4_units.s + gcm3 = gate.g4_units["g/cm3"] + + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + + # adapt world size + world = sim.world + world.size = [1 * m, 1 * m, 1 * m] + world.material = "G4_AIR" + + big_box = sim.add_volume("Box", "big_box") + big_box.mother = world.name + big_box.material = "G4_AIR" + big_box.size = [0.8 * m, 0.8 * m, 0.8 * m] + + actor_box = sim.add_volume("Box", "actor_box") + actor_box.mother = big_box.name + actor_box.material = "G4_AIR" + actor_box.size = [0.6 * m, 0.6 * m, 0.6 * m] + actor_box.translation = [0, 0, -0.1 * m] + + source = sim.add_source("GenericSource", "photon_source") + source.particle = "gamma" + source.position.type = "box" + source.mother = world.name + source.position.size = [6 * cm, 6 * cm, 6 * cm] + source.position.translation = [0, 0, 0.3 * m] + source.direction.type = "momentum" + source.direction_relative_to_attached_volume = True + # source1.direction.focus_point = [0*cm, 0*cm, -5 *cm] + source.direction.momentum = [0, 0, -1] + source.energy.type = "mono" + source.energy.mono = 6 * MeV + source.n = 5000 + + tungsten_leaves = sim.add_volume("Box", "tungsten_leaves") + tungsten_leaves.mother = actor_box + tungsten_leaves.size = [0.6 * m, 0.6 * m, 0.3 * cm] + tungsten_leaves.material = "Tungsten" + liste_translation_W = [] + for i in range(7): + liste_translation_W.append([0, 0, 0.25 * m - i * 6 * cm]) + tungsten_leaves.translation = liste_translation_W + tungsten_leaves.color = [0.9, 0.0, 0.4, 0.8] + + kill_No_int_act = sim.add_actor("KillNonInteractingParticleActor", "killact") + kill_No_int_act.attached_to = actor_box.name + + entry_phase_space = sim.add_volume("Box", "entry_phase_space") + entry_phase_space.mother = big_box + entry_phase_space.size = [0.8 * m, 0.8 * m, 1 * nm] + entry_phase_space.material = "G4_AIR" + entry_phase_space.translation = [0, 0, 0.21 * m] + entry_phase_space.color = [0.5, 0.9, 0.3, 1] + + exit_phase_space_1 = sim.add_volume("Box", "exit_phase_space_1") + exit_phase_space_1.mother = actor_box + exit_phase_space_1.size = [0.6 * m, 0.6 * m, 1 * nm] + exit_phase_space_1.material = "G4_AIR" + exit_phase_space_1.translation = [0, 0, -0.3 * m + 1 * nm] + exit_phase_space_1.color = [0.5, 0.9, 0.3, 1] + + exit_phase_space_2 = sim.add_volume("Box", "exit_phase_space_2") + exit_phase_space_2.mother = world.name + exit_phase_space_2.size = [0.6 * m, 0.6 * m, 1 * nm] + exit_phase_space_2.material = "G4_AIR" + exit_phase_space_2.translation = [0, 0, -0.4 * m - 1 * nm] + exit_phase_space_2.color = [0.5, 0.9, 0.3, 1] + + # print(sim.volume_manager.dump_volume_tree()) + liste_phase_space_name = [ + entry_phase_space.name, + exit_phase_space_1.name, + exit_phase_space_2.name, + ] + for name in liste_phase_space_name: + print(name) + phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_" + name) + phsp.attached_to = name + phsp.attributes = ["EventID", "TrackID", "KineticEnergy"] + name_phsp = "test074_" + name + ".root" + phsp.output_filename = name_phsp + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + sim.physics_manager.enable_decay = False + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1 * mm + sim.physics_manager.global_production_cuts.positron = 1 * mm + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + + # go ! + sim.run() + print(s) + print(output_path) + entry_phsp = uproot.open( + str(output_path) + + "/test074_" + + liste_phase_space_name[0] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[0] + ) + exit_phase_space_1 = uproot.open( + str(output_path) + + "/test074_" + + liste_phase_space_name[1] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[1] + ) + exit_phase_space_2 = uproot.open( + str(output_path) + + "/test074_" + + liste_phase_space_name[2] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[2] + ) + + df_entry = entry_phsp.arrays() + df_exit_1 = exit_phase_space_1.arrays() + df_exit_2 = exit_phase_space_2.arrays() + + is_ok = test074_test(df_entry, df_exit_1, df_exit_2) + + utility.test_ok(is_ok) From 00abcd58c94ff7818168a5ba0cf3009fb959d401 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Fri, 8 Nov 2024 18:31:59 +0100 Subject: [PATCH 42/82] test name modification --- opengate/actors/miscactors.py | 1 - .../test082_kill_non_interacting_particles.py | 212 ++++++++++++++++++ 2 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 opengate/tests/src/test082_kill_non_interacting_particles.py diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index ffb1a5747..db70a9f58 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -318,7 +318,6 @@ def initialize(self): self.InitializeUserInput(self.user_info) self.InitializeCpp() volume_tree = self.simulation.volume_manager.get_volume_tree() - print(type(volume_tree)) dico_of_volume_tree = {} for pre, _, node in RenderTree(volume_tree): dico_of_volume_tree[str(node.name)] = node diff --git a/opengate/tests/src/test082_kill_non_interacting_particles.py b/opengate/tests/src/test082_kill_non_interacting_particles.py new file mode 100644 index 000000000..c6fe7aba9 --- /dev/null +++ b/opengate/tests/src/test082_kill_non_interacting_particles.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +from opengate.tests import utility +from scipy.spatial.transform import Rotation +import numpy as np +from anytree import Node, RenderTree +import uproot + + +def test082_test(entry_data, exit_data_1, exit_data_2): + liste_ekin = [] + liste_evtID = [] + liste_trackID = [] + evt_ID_entry_data = entry_data["EventID"] + j = 0 + i = 0 + while i < len(evt_ID_entry_data): + if ( + j < len(exit_data_1["EventID"]) + and evt_ID_entry_data[i] == exit_data_1["EventID"][j] + ): + TID_entry = entry_data["TrackID"][i] + TID_exit = exit_data_1["TrackID"][j] + Ekin_entry = entry_data["KineticEnergy"][i] + Ekin_exit = exit_data_1["KineticEnergy"][j] + + if (TID_entry == TID_exit) and (Ekin_exit == Ekin_entry): + liste_ekin.append(exit_data_1["KineticEnergy"][j]) + liste_evtID.append(exit_data_1["EventID"][j]) + liste_trackID.append(exit_data_1["TrackID"][j]) + if (j < len(exit_data_1["EventID"]) - 1) and ( + exit_data_1["EventID"][j] == exit_data_1["EventID"][j + 1] + ): + i = i - 1 + j += 1 + i += 1 + liste_ekin = np.asarray(liste_ekin) + print("Number of tracks to kill =", len(liste_ekin)) + print( + "Number of killed tracks =", + (len(exit_data_1["EventID"]) - len(exit_data_2["EventID"])), + ) + + return len(liste_ekin) == ( + len(exit_data_1["EventID"]) - len(exit_data_2["EventID"]) + ) + + +if __name__ == "__main__": + paths = utility.get_default_test_paths(__file__) + output_path = paths.output + + + print(output_path) + # create the simulation + sim = gate.Simulation() + sim.output_dir = output_path + + # main options + ui = sim.user_info + ui.g4_verbose = False + # ui.visu = True + ui.visu_type = "vrml" + ui.check_volumes_overlap = False + # ui.running_verbose_level = gate.logger.EVENT + ui.number_of_threads = 1 + ui.random_seed = "auto" + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + sec = gate.g4_units.s + gcm3 = gate.g4_units["g/cm3"] + + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + + # adapt world size + world = sim.world + world.size = [1 * m, 1 * m, 1 * m] + world.material = "G4_AIR" + + big_box = sim.add_volume("Box", "big_box") + big_box.mother = world.name + big_box.material = "G4_AIR" + big_box.size = [0.8 * m, 0.8 * m, 0.8 * m] + + actor_box = sim.add_volume("Box", "actor_box") + actor_box.mother = big_box.name + actor_box.material = "G4_AIR" + actor_box.size = [0.6 * m, 0.6 * m, 0.6 * m] + actor_box.translation = [0, 0, -0.1 * m] + + source = sim.add_source("GenericSource", "photon_source") + source.particle = "gamma" + source.position.type = "box" + source.mother = world.name + source.position.size = [6 * cm, 6 * cm, 6 * cm] + source.position.translation = [0, 0, 0.3 * m] + source.direction.type = "momentum" + source.direction_relative_to_attached_volume = True + # source1.direction.focus_point = [0*cm, 0*cm, -5 *cm] + source.direction.momentum = [0, 0, -1] + source.energy.type = "mono" + source.energy.mono = 6 * MeV + source.n = 5000 + + tungsten_leaves = sim.add_volume("Box", "tungsten_leaves") + tungsten_leaves.mother = actor_box + tungsten_leaves.size = [0.6 * m, 0.6 * m, 0.3 * cm] + tungsten_leaves.material = "Tungsten" + liste_translation_W = [] + for i in range(7): + liste_translation_W.append([0, 0, 0.25 * m - i * 6 * cm]) + tungsten_leaves.translation = liste_translation_W + tungsten_leaves.color = [0.9, 0.0, 0.4, 0.8] + + kill_No_int_act = sim.add_actor("KillNonInteractingParticleActor", "killact") + kill_No_int_act.attached_to = actor_box.name + + entry_phase_space = sim.add_volume("Box", "entry_phase_space") + entry_phase_space.mother = big_box + entry_phase_space.size = [0.8 * m, 0.8 * m, 1 * nm] + entry_phase_space.material = "G4_AIR" + entry_phase_space.translation = [0, 0, 0.21 * m] + entry_phase_space.color = [0.5, 0.9, 0.3, 1] + + exit_phase_space_1 = sim.add_volume("Box", "exit_phase_space_1") + exit_phase_space_1.mother = actor_box + exit_phase_space_1.size = [0.6 * m, 0.6 * m, 1 * nm] + exit_phase_space_1.material = "G4_AIR" + exit_phase_space_1.translation = [0, 0, -0.3 * m + 1 * nm] + exit_phase_space_1.color = [0.5, 0.9, 0.3, 1] + + exit_phase_space_2 = sim.add_volume("Box", "exit_phase_space_2") + exit_phase_space_2.mother = world.name + exit_phase_space_2.size = [0.6 * m, 0.6 * m, 1 * nm] + exit_phase_space_2.material = "G4_AIR" + exit_phase_space_2.translation = [0, 0, -0.4 * m - 1 * nm] + exit_phase_space_2.color = [0.5, 0.9, 0.3, 1] + + # print(sim.volume_manager.dump_volume_tree()) + liste_phase_space_name = [ + entry_phase_space.name, + exit_phase_space_1.name, + exit_phase_space_2.name, + ] + for name in liste_phase_space_name: + print(name) + phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_" + name) + phsp.attached_to = name + phsp.attributes = ["EventID", "TrackID", "KineticEnergy"] + name_phsp = "test082_" + name + ".root" + phsp.output_filename = name_phsp + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + sim.physics_manager.enable_decay = False + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1 * mm + sim.physics_manager.global_production_cuts.positron = 1 * mm + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + + # go ! + sim.run() + print(s) + print(output_path) + entry_phsp = uproot.open( + str(output_path) + + "/test082_" + + liste_phase_space_name[0] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[0] + ) + exit_phase_space_1 = uproot.open( + str(output_path) + + "/test082_" + + liste_phase_space_name[1] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[1] + ) + exit_phase_space_2 = uproot.open( + str(output_path) + + "/test082_" + + liste_phase_space_name[2] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[2] + ) + + df_entry = entry_phsp.arrays() + df_exit_1 = exit_phase_space_1.arrays() + df_exit_2 = exit_phase_space_2.arrays() + + is_ok = test082_test(df_entry, df_exit_1, df_exit_2) + + utility.test_ok(is_ok) From 33b7a4850848810e77b764ab99203853e2f84ec2 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Fri, 8 Nov 2024 18:35:03 +0100 Subject: [PATCH 43/82] Add a short header at the beginning of the actor --- opengate/actors/miscactors.py | 5 + .../test074_kill_non_interacting_particles.py | 212 ------------------ 2 files changed, 5 insertions(+), 212 deletions(-) delete mode 100644 opengate/tests/src/test074_kill_non_interacting_particles.py diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index db70a9f58..c3d0b4663 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -302,6 +302,11 @@ def EndSimulationAction(self): class KillNonInteractingParticleActor(ActorBase, g4.GateKillNonInteractingParticleActor): + """ + If a particle, not generated within the volume at which our actor is attached, crosses the volume + without interaction, the particle is killed + """ + def __init__(self, *args, **kwargs): ActorBase.__init__(self, *args, **kwargs) self.__initcpp__() diff --git a/opengate/tests/src/test074_kill_non_interacting_particles.py b/opengate/tests/src/test074_kill_non_interacting_particles.py deleted file mode 100644 index b12a6f66d..000000000 --- a/opengate/tests/src/test074_kill_non_interacting_particles.py +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import opengate as gate -from opengate.tests import utility -from scipy.spatial.transform import Rotation -import numpy as np -from anytree import Node, RenderTree -import uproot - - -def test074_test(entry_data, exit_data_1, exit_data_2): - liste_ekin = [] - liste_evtID = [] - liste_trackID = [] - evt_ID_entry_data = entry_data["EventID"] - j = 0 - i = 0 - while i < len(evt_ID_entry_data): - if ( - j < len(exit_data_1["EventID"]) - and evt_ID_entry_data[i] == exit_data_1["EventID"][j] - ): - TID_entry = entry_data["TrackID"][i] - TID_exit = exit_data_1["TrackID"][j] - Ekin_entry = entry_data["KineticEnergy"][i] - Ekin_exit = exit_data_1["KineticEnergy"][j] - - if (TID_entry == TID_exit) and (Ekin_exit == Ekin_entry): - liste_ekin.append(exit_data_1["KineticEnergy"][j]) - liste_evtID.append(exit_data_1["EventID"][j]) - liste_trackID.append(exit_data_1["TrackID"][j]) - if (j < len(exit_data_1["EventID"]) - 1) and ( - exit_data_1["EventID"][j] == exit_data_1["EventID"][j + 1] - ): - i = i - 1 - j += 1 - i += 1 - liste_ekin = np.asarray(liste_ekin) - print("Number of tracks to kill =", len(liste_ekin)) - print( - "Number of killed tracks =", - (len(exit_data_1["EventID"]) - len(exit_data_2["EventID"])), - ) - - return len(liste_ekin) == ( - len(exit_data_1["EventID"]) - len(exit_data_2["EventID"]) - ) - - -if __name__ == "__main__": - paths = utility.get_default_test_paths(__file__) - output_path = paths.output - - - print(output_path) - # create the simulation - sim = gate.Simulation() - sim.output_dir = output_path - - # main options - ui = sim.user_info - ui.g4_verbose = False - # ui.visu = True - ui.visu_type = "vrml" - ui.check_volumes_overlap = False - # ui.running_verbose_level = gate.logger.EVENT - ui.number_of_threads = 1 - ui.random_seed = "auto" - - # units - m = gate.g4_units.m - km = gate.g4_units.km - mm = gate.g4_units.mm - cm = gate.g4_units.cm - nm = gate.g4_units.nm - Bq = gate.g4_units.Bq - MeV = gate.g4_units.MeV - keV = gate.g4_units.keV - sec = gate.g4_units.s - gcm3 = gate.g4_units["g/cm3"] - - sim.volume_manager.material_database.add_material_weights( - "Tungsten", - ["W"], - [1], - 19.3 * gcm3, - ) - - # adapt world size - world = sim.world - world.size = [1 * m, 1 * m, 1 * m] - world.material = "G4_AIR" - - big_box = sim.add_volume("Box", "big_box") - big_box.mother = world.name - big_box.material = "G4_AIR" - big_box.size = [0.8 * m, 0.8 * m, 0.8 * m] - - actor_box = sim.add_volume("Box", "actor_box") - actor_box.mother = big_box.name - actor_box.material = "G4_AIR" - actor_box.size = [0.6 * m, 0.6 * m, 0.6 * m] - actor_box.translation = [0, 0, -0.1 * m] - - source = sim.add_source("GenericSource", "photon_source") - source.particle = "gamma" - source.position.type = "box" - source.mother = world.name - source.position.size = [6 * cm, 6 * cm, 6 * cm] - source.position.translation = [0, 0, 0.3 * m] - source.direction.type = "momentum" - source.direction_relative_to_attached_volume = True - # source1.direction.focus_point = [0*cm, 0*cm, -5 *cm] - source.direction.momentum = [0, 0, -1] - source.energy.type = "mono" - source.energy.mono = 6 * MeV - source.n = 5000 - - tungsten_leaves = sim.add_volume("Box", "tungsten_leaves") - tungsten_leaves.mother = actor_box - tungsten_leaves.size = [0.6 * m, 0.6 * m, 0.3 * cm] - tungsten_leaves.material = "Tungsten" - liste_translation_W = [] - for i in range(7): - liste_translation_W.append([0, 0, 0.25 * m - i * 6 * cm]) - tungsten_leaves.translation = liste_translation_W - tungsten_leaves.color = [0.9, 0.0, 0.4, 0.8] - - kill_No_int_act = sim.add_actor("KillNonInteractingParticleActor", "killact") - kill_No_int_act.attached_to = actor_box.name - - entry_phase_space = sim.add_volume("Box", "entry_phase_space") - entry_phase_space.mother = big_box - entry_phase_space.size = [0.8 * m, 0.8 * m, 1 * nm] - entry_phase_space.material = "G4_AIR" - entry_phase_space.translation = [0, 0, 0.21 * m] - entry_phase_space.color = [0.5, 0.9, 0.3, 1] - - exit_phase_space_1 = sim.add_volume("Box", "exit_phase_space_1") - exit_phase_space_1.mother = actor_box - exit_phase_space_1.size = [0.6 * m, 0.6 * m, 1 * nm] - exit_phase_space_1.material = "G4_AIR" - exit_phase_space_1.translation = [0, 0, -0.3 * m + 1 * nm] - exit_phase_space_1.color = [0.5, 0.9, 0.3, 1] - - exit_phase_space_2 = sim.add_volume("Box", "exit_phase_space_2") - exit_phase_space_2.mother = world.name - exit_phase_space_2.size = [0.6 * m, 0.6 * m, 1 * nm] - exit_phase_space_2.material = "G4_AIR" - exit_phase_space_2.translation = [0, 0, -0.4 * m - 1 * nm] - exit_phase_space_2.color = [0.5, 0.9, 0.3, 1] - - # print(sim.volume_manager.dump_volume_tree()) - liste_phase_space_name = [ - entry_phase_space.name, - exit_phase_space_1.name, - exit_phase_space_2.name, - ] - for name in liste_phase_space_name: - print(name) - phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_" + name) - phsp.attached_to = name - phsp.attributes = ["EventID", "TrackID", "KineticEnergy"] - name_phsp = "test074_" + name + ".root" - phsp.output_filename = name_phsp - - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" - sim.physics_manager.enable_decay = False - sim.physics_manager.global_production_cuts.gamma = 1 * mm - sim.physics_manager.global_production_cuts.electron = 1 * mm - sim.physics_manager.global_production_cuts.positron = 1 * mm - - s = sim.add_actor("SimulationStatisticsActor", "Stats") - s.track_types_flag = True - - # go ! - sim.run() - print(s) - print(output_path) - entry_phsp = uproot.open( - str(output_path) - + "/test074_" - + liste_phase_space_name[0] - + ".root" - + ":PhaseSpace_" - + liste_phase_space_name[0] - ) - exit_phase_space_1 = uproot.open( - str(output_path) - + "/test074_" - + liste_phase_space_name[1] - + ".root" - + ":PhaseSpace_" - + liste_phase_space_name[1] - ) - exit_phase_space_2 = uproot.open( - str(output_path) - + "/test074_" - + liste_phase_space_name[2] - + ".root" - + ":PhaseSpace_" - + liste_phase_space_name[2] - ) - - df_entry = entry_phsp.arrays() - df_exit_1 = exit_phase_space_1.arrays() - df_exit_2 = exit_phase_space_2.arrays() - - is_ok = test074_test(df_entry, df_exit_1, df_exit_2) - - utility.test_ok(is_ok) From 2115b1f9908108fc66412d9e3aaa4147a378107b Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Fri, 8 Nov 2024 19:26:17 +0100 Subject: [PATCH 44/82] actor adaptation for particle generated within the actorified volume --- .../GateKillNonInteractingParticleActor.cpp | 9 ++++--- opengate/actors/miscactors.py | 5 ++-- .../test082_kill_non_interacting_particles.py | 25 ++++++++----------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp index 93eb2fa25..f101683f4 100644 --- a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp +++ b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp @@ -11,6 +11,7 @@ #include "G4ios.hh" #include "GateHelpers.h" #include "GateHelpersDict.h" +#include "G4TransportationManager.hh" GateKillNonInteractingParticleActor::GateKillNonInteractingParticleActor( py::dict &user_info) @@ -31,6 +32,7 @@ void GateKillNonInteractingParticleActor::PreUserTrackingAction( } void GateKillNonInteractingParticleActor::SteppingAction(G4Step *step) { + G4Navigator* navigator = G4TransportationManager::GetTransportationManager()->GetNavigatorForTracking(); G4String logNameMotherVolume = G4LogicalVolumeStore::GetInstance() ->GetVolume(fMotherVolumeName) @@ -38,15 +40,16 @@ void GateKillNonInteractingParticleActor::SteppingAction(G4Step *step) { G4String physicalVolumeNamePreStep = "None"; if (step->GetPreStepPoint()->GetPhysicalVolume() !=0) physicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetName(); - if ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != logNameMotherVolume) && (fIsFirstStep)) { - if ((fPassedByTheMotherVolume == false) && (physicalVolumeNamePreStep == fMotherVolumeName) && - (step->GetPreStepPoint()->GetStepStatus() == 1)) { + if (((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != logNameMotherVolume) && (fIsFirstStep)) || ((fIsFirstStep) && (step->GetTrack()->GetParentID() == 0))) { + if ((fPassedByTheMotherVolume == false) && (((step->GetPreStepPoint()->GetStepStatus() == 1) && (physicalVolumeNamePreStep == fMotherVolumeName)) || ((fIsFirstStep) && (step->GetTrack()->GetParentID() == 0)))) { fPassedByTheMotherVolume = true; fKineticEnergyAtTheEntrance = step->GetPreStepPoint()->GetKineticEnergy(); ftrackIDAtTheEntrance = step->GetTrack()->GetTrackID(); } } + + G4String logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); if ((fPassedByTheMotherVolume) && (step->GetPostStepPoint()->GetStepStatus() == 1)) { if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index c3d0b4663..04fc5e151 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -303,8 +303,9 @@ def EndSimulationAction(self): class KillNonInteractingParticleActor(ActorBase, g4.GateKillNonInteractingParticleActor): """ - If a particle, not generated within the volume at which our actor is attached, crosses the volume - without interaction, the particle is killed + If a particle, not generated or generated within the volume at which our actor is attached, crosses the volume + without interaction, the particle is killed. Warning : this actor being based on energy measurement, Rayleigh photon + may not be killed. """ def __init__(self, *args, **kwargs): diff --git a/opengate/tests/src/test082_kill_non_interacting_particles.py b/opengate/tests/src/test082_kill_non_interacting_particles.py index c6fe7aba9..cc626163b 100644 --- a/opengate/tests/src/test082_kill_non_interacting_particles.py +++ b/opengate/tests/src/test082_kill_non_interacting_particles.py @@ -51,9 +51,6 @@ def test082_test(entry_data, exit_data_1, exit_data_2): if __name__ == "__main__": paths = utility.get_default_test_paths(__file__) output_path = paths.output - - - print(output_path) # create the simulation sim = gate.Simulation() sim.output_dir = output_path @@ -90,16 +87,16 @@ def test082_test(entry_data, exit_data_1, exit_data_2): # adapt world size world = sim.world world.size = [1 * m, 1 * m, 1 * m] - world.material = "G4_AIR" + world.material = "G4_Galactic" big_box = sim.add_volume("Box", "big_box") big_box.mother = world.name - big_box.material = "G4_AIR" + big_box.material = "G4_Galactic" big_box.size = [0.8 * m, 0.8 * m, 0.8 * m] actor_box = sim.add_volume("Box", "actor_box") actor_box.mother = big_box.name - actor_box.material = "G4_AIR" + actor_box.material = "G4_Galactic" actor_box.size = [0.6 * m, 0.6 * m, 0.6 * m] actor_box.translation = [0, 0, -0.1 * m] @@ -107,8 +104,8 @@ def test082_test(entry_data, exit_data_1, exit_data_2): source.particle = "gamma" source.position.type = "box" source.mother = world.name - source.position.size = [6 * cm, 6 * cm, 6 * cm] - source.position.translation = [0, 0, 0.3 * m] + source.position.size = [6 * cm, 6 * cm, 4 * cm] + source.position.translation = [0, 0, 0.205 * m] source.direction.type = "momentum" source.direction_relative_to_attached_volume = True # source1.direction.focus_point = [0*cm, 0*cm, -5 *cm] @@ -131,23 +128,23 @@ def test082_test(entry_data, exit_data_1, exit_data_2): kill_No_int_act.attached_to = actor_box.name entry_phase_space = sim.add_volume("Box", "entry_phase_space") - entry_phase_space.mother = big_box - entry_phase_space.size = [0.8 * m, 0.8 * m, 1 * nm] - entry_phase_space.material = "G4_AIR" - entry_phase_space.translation = [0, 0, 0.21 * m] + entry_phase_space.mother = actor_box.name + entry_phase_space.size = [0.6 * m, 0.6 * m, 1 * nm] + entry_phase_space.material = "G4_Galactic" + entry_phase_space.translation = [0, 0, 0.255* m] entry_phase_space.color = [0.5, 0.9, 0.3, 1] exit_phase_space_1 = sim.add_volume("Box", "exit_phase_space_1") exit_phase_space_1.mother = actor_box exit_phase_space_1.size = [0.6 * m, 0.6 * m, 1 * nm] - exit_phase_space_1.material = "G4_AIR" + exit_phase_space_1.material = "G4_Galactic" exit_phase_space_1.translation = [0, 0, -0.3 * m + 1 * nm] exit_phase_space_1.color = [0.5, 0.9, 0.3, 1] exit_phase_space_2 = sim.add_volume("Box", "exit_phase_space_2") exit_phase_space_2.mother = world.name exit_phase_space_2.size = [0.6 * m, 0.6 * m, 1 * nm] - exit_phase_space_2.material = "G4_AIR" + exit_phase_space_2.material = "G4_Galactic" exit_phase_space_2.translation = [0, 0, -0.4 * m - 1 * nm] exit_phase_space_2.color = [0.5, 0.9, 0.3, 1] From 220932b453d764521822f5f65a6950562549c9ce Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 12 Nov 2024 11:17:02 +0100 Subject: [PATCH 45/82] Add of an output actor retrieving the number of particles killed --- .../pyGateKillNonInteractingParticleActor.cpp | 1 + opengate/actors/miscactors.py | 36 +++++++++++++++++-- .../test082_kill_non_interacting_particles.py | 2 ++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp index 5de9fe12b..d841af3d0 100644 --- a/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp @@ -18,5 +18,6 @@ void init_GateKillNonInteractingParticleActor(py::module &m) { .def_readwrite( "fListOfVolumeAncestor", &GateKillNonInteractingParticleActor::fListOfVolumeAncestor) + .def_readwrite("number_of_killed_particles", &GateKillNonInteractingParticleActor::fNbOfKilledParticles) .def(py::init<py::dict &>()); } diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 04fc5e151..372bf73ca 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -277,7 +277,6 @@ def SteppingAction(self, step, touchable): do_something() """ - class KillActor(ActorBase, g4.GateKillActor): def __init__(self, *args, **kwargs): @@ -300,6 +299,27 @@ def EndSimulationAction(self): self.number_of_killed_particles = self.GetNumberOfKilledParticles() +class ActorOutputKillNonInteractingParticleActor(ActorOutputBase): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.number_of_killed_particles = 0 + + + def get_processed_output(self): + d = {} + d["particles killed"] = self.number_of_killed_particles + return d + + def __str__(self): + s = "" + for k, v in self.get_processed_output().items(): + s = k + ": " + str(v) + s += "\n" + return(s) + + + class KillNonInteractingParticleActor(ActorBase, g4.GateKillNonInteractingParticleActor): """ @@ -310,13 +330,15 @@ class KillNonInteractingParticleActor(ActorBase, g4.GateKillNonInteractingPartic def __init__(self, *args, **kwargs): ActorBase.__init__(self, *args, **kwargs) + self._add_user_output(ActorOutputKillNonInteractingParticleActor, "kill_non_interacting_particles") self.__initcpp__() self.list_of_volume_name = [] + self.number_of_killed_particles = 0 def __initcpp__(self): g4.GateKillNonInteractingParticleActor.__init__(self, self.user_info) self.AddActions( - {"StartSimulationAction","PreUserTrackingAction", "SteppingAction"} + {"StartSimulationAction","PreUserTrackingAction", "SteppingAction","EndOfSimulationAction"} ) def initialize(self): @@ -335,6 +357,15 @@ def initialize(self): self.fListOfVolumeAncestor = self.list_of_volume_name + def EndSimulationAction(self): + self.user_output.kill_non_interacting_particles.number_of_killed_particles = self.number_of_killed_particles + + + def __str__(self): + s = self.user_output["kill_non_interacting_particles"].__str__() + return s + + def _setter_hook_particles(self, value): if isinstance(value, str): return [value] @@ -475,6 +506,7 @@ def initialize(self): process_cls(ActorOutputStatisticsActor) process_cls(SimulationStatisticsActor) process_cls(KillActor) +process_cls(KillNonInteractingParticleActor) process_cls(SplittingActorBase) process_cls(ComptSplittingActor) process_cls(BremSplittingActor) diff --git a/opengate/tests/src/test082_kill_non_interacting_particles.py b/opengate/tests/src/test082_kill_non_interacting_particles.py index cc626163b..cf433209e 100644 --- a/opengate/tests/src/test082_kill_non_interacting_particles.py +++ b/opengate/tests/src/test082_kill_non_interacting_particles.py @@ -206,4 +206,6 @@ def test082_test(entry_data, exit_data_1, exit_data_2): is_ok = test082_test(df_entry, df_exit_1, df_exit_2) + print(kill_No_int_act.user_output.kill_non_interacting_particles) + utility.test_ok(is_ok) From 53fc3938c83296946d9f9543ef7697a78b1aaf3d Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 12 Nov 2024 19:02:19 +0100 Subject: [PATCH 46/82] Removal of conditionnal kill actors --- core/opengate_core/opengate_core.cpp | 9 -- .../GateKillInteractingParticleActor.cpp | 70 ----------- .../GateKillInteractingParticleActor.h | 41 ------- .../GateKillNonInteractingParticleActor.cpp | 75 ------------ .../GateKillNonInteractingParticleActor.h | 42 ------- ...teLastVertexInteractionSplittingActorOld.h | 110 ------------------ .../GateLastVertexSplittingPostStepDoItOld.h | 110 ------------------ .../pyGateKillInteractingParticleActor.cpp | 22 ---- .../pyGateKillNonInteractingParticleActor.cpp | 22 ---- ...LastVertexInteractionSplittingActorOld.cpp | 21 ---- opengate/actors/actorbuilders.py | 5 - opengate/actors/miscactors.py | 92 --------------- 12 files changed, 619 deletions(-) delete mode 100644 core/opengate_core/opengate_lib/GateKillInteractingParticleActor.cpp delete mode 100644 core/opengate_core/opengate_lib/GateKillInteractingParticleActor.h delete mode 100644 core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp delete mode 100644 core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.h delete mode 100644 core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActorOld.h delete mode 100644 core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoItOld.h delete mode 100644 core/opengate_core/opengate_lib/pyGateKillInteractingParticleActor.cpp delete mode 100644 core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp delete mode 100644 core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActorOld.cpp diff --git a/core/opengate_core/opengate_core.cpp b/core/opengate_core/opengate_core.cpp index 7f57e1d1b..76a4c7089 100644 --- a/core/opengate_core/opengate_core.cpp +++ b/core/opengate_core/opengate_core.cpp @@ -302,12 +302,8 @@ void init_GateARFTrainingDatasetActor(py::module &m); void init_GateKillActor(py::module &); -void init_GateKillNonInteractingParticleActor(py::module &); - void init_GateSurfaceSplittingActor(py::module &); -void init_GateKillInteractingParticleActor(py::module &); - void init_itk_image(py::module &); void init_GateImageNestedParameterisation(py::module &); @@ -346,8 +342,6 @@ void init_GateOptrComptSplittingActor(py::module &m); void init_GateLastVertexInteractionSplittingActor(py::module &m); -void init_GateLastVertexInteractionSplittingActorOld(py::module &m); - void init_GateOptrComptPseudoTransportationActor(py::module &m); void init_GateBOptrBremSplittingActor(py::module &m); @@ -575,7 +569,6 @@ PYBIND11_MODULE(opengate_core, m) { init_GateOptrComptPseudoTransportationActor(m); init_GateOptrComptSplittingActor(m); init_GateLastVertexInteractionSplittingActor(m); - init_GateLastVertexInteractionSplittingActorOld(m); init_GateHitsCollectionActor(m); init_GateMotionVolumeActor(m); init_GateHitsAdderActor(m); @@ -588,9 +581,7 @@ PYBIND11_MODULE(opengate_core, m) { init_GateARFActor(m); init_GateARFTrainingDatasetActor(m); init_GateKillActor(m); - init_GateKillNonInteractingParticleActor(m); init_GateSurfaceSplittingActor(m); - init_GateKillInteractingParticleActor(m); init_GateDigiAttributeManager(m); init_GateVDigiAttribute(m); init_GateExceptionHandler(m); diff --git a/core/opengate_core/opengate_lib/GateKillInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/GateKillInteractingParticleActor.cpp deleted file mode 100644 index b6b4b4ba9..000000000 --- a/core/opengate_core/opengate_lib/GateKillInteractingParticleActor.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* -------------------------------------------------- - Copyright (C): OpenGATE Collaboration - This software is distributed under the terms - of the GNU Lesser General Public Licence (LGPL) - See LICENSE.md for further details - ------------------------------------ -------------- */ - -#include "GateKillInteractingParticleActor.h" -#include "G4LogicalVolumeStore.hh" -#include "G4PhysicalVolumeStore.hh" -#include "G4ios.hh" -#include "GateHelpers.h" -#include "GateHelpersDict.h" - -GateKillInteractingParticleActor::GateKillInteractingParticleActor( - py::dict &user_info) - : GateVActor(user_info, false) { - fActions.insert("StartSimulationAction"); - fActions.insert("SteppingAction"); - fActions.insert("PreUserTrackingAction"); -} - -void GateKillInteractingParticleActor::ActorInitialize() {} - -void GateKillInteractingParticleActor::StartSimulationAction() { - fNbOfKilledParticles = 0; -} - -void GateKillInteractingParticleActor::PreUserTrackingAction( - const G4Track *track) { - fIsFirstStep = true; -} - -void GateKillInteractingParticleActor::SteppingAction(G4Step *step) { - - G4String logNameMotherVolume = G4LogicalVolumeStore::GetInstance() - ->GetVolume(fMotherVolumeName) - ->GetName(); - G4String physicalVolumeNamePreStep = - step->GetPreStepPoint()->GetPhysicalVolume()->GetName(); - if ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != - logNameMotherVolume) && - (fIsFirstStep)) { - if ((physicalVolumeNamePreStep == fMotherVolumeName) && - (step->GetPreStepPoint()->GetStepStatus() == 1)) { - fKineticEnergyAtTheEntrance = step->GetPreStepPoint()->GetKineticEnergy(); - ftrackIDAtTheEntrance = step->GetTrack()->GetTrackID(); - } - } - - G4String logicalVolumeNamePostStep = step->GetPostStepPoint() - ->GetPhysicalVolume() - ->GetLogicalVolume() - ->GetName(); - - if (step->GetPostStepPoint()->GetStepStatus() == 1){ - if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), - logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { - if ((step->GetTrack()->GetTrackID() != ftrackIDAtTheEntrance) || - (step->GetPostStepPoint()->GetKineticEnergy() != fKineticEnergyAtTheEntrance)) { - auto track = step->GetTrack(); - track->SetTrackStatus(fStopAndKill); - fNbOfKilledParticles++; - } - fKineticEnergyAtTheEntrance = 0; - ftrackIDAtTheEntrance = 0; - } - } - fIsFirstStep = false; -} diff --git a/core/opengate_core/opengate_lib/GateKillInteractingParticleActor.h b/core/opengate_core/opengate_lib/GateKillInteractingParticleActor.h deleted file mode 100644 index 9f0b003e4..000000000 --- a/core/opengate_core/opengate_lib/GateKillInteractingParticleActor.h +++ /dev/null @@ -1,41 +0,0 @@ -/* -------------------------------------------------- - Copyright (C): OpenGATE Collaboration - This software is distributed under the terms - of the GNU Lesser General Public Licence (LGPL) - See LICENSE.md for further details - -------------------------------------------------- */ - -#ifndef GateKillInteractingParticleActor_h -#define GateKillInteractingParticleActor_h - -#include "G4Cache.hh" -#include "GateVActor.h" -#include <pybind11/stl.h> - -namespace py = pybind11; - -class GateKillInteractingParticleActor : public GateVActor { - -public: - // Constructor - GateKillInteractingParticleActor(py::dict &user_info); - - void ActorInitialize() override; - - void StartSimulationAction() override; - - // Main function called every step in attached volume - void SteppingAction(G4Step *) override; - - void PreUserTrackingAction(const G4Track *) override; - - std::vector<G4String> fParticlesTypeToKill; - G4double fKineticEnergyAtTheEntrance = 0; - G4int ftrackIDAtTheEntrance = 0; - G4bool fIsFirstStep = true; - std::vector<std::string> fListOfVolumeAncestor; - - long fNbOfKilledParticles{}; -}; - -#endif diff --git a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp deleted file mode 100644 index 5a6b544c7..000000000 --- a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* -------------------------------------------------- - Copyright (C): OpenGATE Collaboration - This software is distributed under the terms - of the GNU Lesser General Public Licence (LGPL) - See LICENSE.md for further details - ------------------------------------ -------------- */ - -#include "GateKillNonInteractingParticleActor.h" -#include "G4LogicalVolumeStore.hh" -#include "G4PhysicalVolumeStore.hh" -#include "G4ios.hh" -#include "GateHelpers.h" -#include "GateHelpersDict.h" - -GateKillNonInteractingParticleActor::GateKillNonInteractingParticleActor( - py::dict &user_info) - : GateVActor(user_info, false) { - fActions.insert("StartSimulationAction"); - fActions.insert("SteppingAction"); - fActions.insert("PreUserTrackingAction"); -} - -void GateKillNonInteractingParticleActor::ActorInitialize() {} - -void GateKillNonInteractingParticleActor::StartSimulationAction() { - fNbOfKilledParticles = 0; -} - -void GateKillNonInteractingParticleActor::PreUserTrackingAction( - const G4Track *track) { - fIsFirstStep = true; - fKineticEnergyAtTheEntrance = 0; - ftrackIDAtTheEntrance = 0; - fPassedByTheMotherVolume = false; -} - -void GateKillNonInteractingParticleActor::SteppingAction(G4Step *step) { - - G4String logNameMotherVolume = G4LogicalVolumeStore::GetInstance() - ->GetVolume(fMotherVolumeName) - ->GetName(); - G4String physicalVolumeNamePreStep = - step->GetPreStepPoint()->GetPhysicalVolume()->GetName(); - if ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != - logNameMotherVolume) && - (fIsFirstStep)) { - if ((fPassedByTheMotherVolume == false) && - (physicalVolumeNamePreStep == fMotherVolumeName) && - (step->GetPreStepPoint()->GetStepStatus() == 1)) { - fPassedByTheMotherVolume = true; - fKineticEnergyAtTheEntrance = step->GetPreStepPoint()->GetKineticEnergy(); - ftrackIDAtTheEntrance = step->GetTrack()->GetTrackID(); - } - } - - G4String logicalVolumeNamePostStep = step->GetPostStepPoint() - ->GetPhysicalVolume() - ->GetLogicalVolume() - ->GetName(); - if ((fPassedByTheMotherVolume) && - (step->GetPostStepPoint()->GetStepStatus() == 1)) { - if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), - logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { - if ((step->GetTrack()->GetTrackID() == ftrackIDAtTheEntrance) && - (step->GetPostStepPoint()->GetKineticEnergy() == - fKineticEnergyAtTheEntrance)) { - auto track = step->GetTrack(); - track->SetTrackStatus(fStopAndKill); - fNbOfKilledParticles++; - } - } - } - - fIsFirstStep = false; -} diff --git a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.h b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.h deleted file mode 100644 index bb7aabe7a..000000000 --- a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.h +++ /dev/null @@ -1,42 +0,0 @@ -/* -------------------------------------------------- - Copyright (C): OpenGATE Collaboration - This software is distributed under the terms - of the GNU Lesser General Public Licence (LGPL) - See LICENSE.md for further details - -------------------------------------------------- */ - -#ifndef GateKillNonInteractingParticleActor_h -#define GateKillNonInteractingParticleActor_h - -#include "G4Cache.hh" -#include "GateVActor.h" -#include <pybind11/stl.h> - -namespace py = pybind11; - -class GateKillNonInteractingParticleActor : public GateVActor { - -public: - // Constructor - GateKillNonInteractingParticleActor(py::dict &user_info); - - void ActorInitialize() override; - - void StartSimulationAction() override; - - // Main function called every step in attached volume - void SteppingAction(G4Step *) override; - - void PreUserTrackingAction(const G4Track *) override; - - std::vector<G4String> fParticlesTypeToKill; - G4bool fPassedByTheMotherVolume = false; - G4double fKineticEnergyAtTheEntrance = 0; - G4int ftrackIDAtTheEntrance = 0; - G4bool fIsFirstStep = true; - std::vector<std::string> fListOfVolumeAncestor; - - long fNbOfKilledParticles{}; -}; - -#endif diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActorOld.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActorOld.h deleted file mode 100644 index 3863a8b62..000000000 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActorOld.h +++ /dev/null @@ -1,110 +0,0 @@ -// -// ******************************************************************** -// * 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 GateLastVertexInteractionSplittingActorOld.h -/// \brief Definition of the GateLastVertexInteractionSplittingActorOld class -#ifndef GateLastVertexInteractionSplittingActorOld_h -#define GateLastVertexInteractionSplittingActorOld_h 1 - -#include "G4ParticleChangeForGamma.hh" -#include "G4VEnergyLossProcess.hh" -#include "GateVActor.h" -#include <iostream> -#include <pybind11/stl.h> -namespace py = pybind11; - -class GateLastVertexInteractionSplittingActorOld : public GateVActor { -public: - GateLastVertexInteractionSplittingActorOld(py::dict &user_info); - virtual ~GateLastVertexInteractionSplittingActorOld() {} - - G4double fSplittingFactor; - G4bool fRussianRouletteForAngle = false; - G4bool fRotationVectorDirector; - G4ThreeVector fVectorDirector; - G4double fMaxTheta; - G4int fTrackIDOfSplittedTrack = 0; - G4int fParentID = -1; - G4int fEventID; - G4int fEventIDOfSplittedTrack; - G4int fEventIDOfInitialSplittedTrack; - G4int fTrackIDOfInitialTrack; - G4int fTrackIDOfInitialSplittedTrack = 0; - G4int ftmpTrackID; - G4bool fIsFirstStep = true; - G4bool fSuspendForAnnihil = false; - G4double fWeightOfEnteringParticle = 0; - G4double fSplitCounter = 0; - G4bool fNotSplitted = true; - - G4Track *fTrackToSplit = nullptr; - G4Step *fStepToSplit = nullptr; - G4String fProcessToSplit = "None"; - - std::vector<G4Track> fTracksToPostpone; - - std::map<G4int, G4TrackVector> fRememberedTracks; - std::map<G4int, std::vector<G4Step *>> fRememberedSteps; - std::map<G4int, std::vector<G4String>> fRememberedProcesses; - - std::vector<std::string> fListOfVolumeAncestor; - - std::vector<G4String> fListOfProcesses = {"compt", "annihil", "eBrem", "conv", - "phot"}; - - virtual void SteppingAction(G4Step *) override; - virtual void BeginOfEventAction(const G4Event *) override; - virtual void BeginOfRunAction(const G4Run *run) override; - virtual void PreUserTrackingAction(const G4Track *track) override; - virtual void PostUserTrackingAction(const G4Track *track) override; - - // Pure splitting functions - G4double RussianRouletteForAngleSurvival(G4ThreeVector, G4ThreeVector, - G4double, G4double); - G4Track *CreateComptonTrack(G4ParticleChangeForGamma *, G4Track, G4double); - void ComptonSplitting(G4Step *CurrentStep, G4Track *track, const G4Step *step, - G4VProcess *process); - void SecondariesSplitting(G4Step *CurrentStep, G4Track *track, - const G4Step *step, G4VProcess *process); - - // Handling the remembered processes to replay - void RememberLastProcessInformation(G4Step *); - void CreateNewParticleAtTheLastVertex(G4Step *, G4Track *, const G4Step *, - G4String); - void ResetProcessesForEnteringParticles(G4Step *step); - void ClearRememberedTracksAndSteps(std::map<G4int, G4TrackVector>, - std::map<G4int, std::vector<G4Step *>>); - - // Edge case to handle the bias in annihilation - // FIXME : The triple annihilation is not handled for the moment - void PostponeFirstAnnihilationTrackIfInteraction(G4Step *step, - G4String processName); - void RegenerationOfPostponedAnnihilationTrack(G4Step *step); - void HandleTrackIDIfPostponedAnnihilation(G4Step *step); - G4bool IsParticleExitTheBiasedVolume(G4Step*step); -}; - -#endif diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoItOld.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoItOld.h deleted file mode 100644 index df30a39d7..000000000 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoItOld.h +++ /dev/null @@ -1,110 +0,0 @@ -// -// ******************************************************************** -// * 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. * -// ******************************************************************** -// -/// -#ifndef GateLastVertexSplittingPostStepDoItOld_h -#define GateLastVertexSplittingPostStepDoItOld_h - - -#include "G4VEnergyLossProcess.hh" -#include "G4VEmProcess.hh" -#include "G4VParticleChange.hh" -#include "G4eplusAnnihilation.hh" -#include "G4PhysicalConstants.hh" -#include "G4MaterialCutsCouple.hh" -#include "G4Gamma.hh" -#include "G4Electron.hh" -#include "G4Positron.hh" -#include "G4eeToTwoGammaModel.hh" -#include "G4EmBiasingManager.hh" -#include "G4EntanglementAuxInfo.hh" -#include "G4eplusAnnihilationEntanglementClipBoard.hh" -#include "G4EmParameters.hh" -#include "G4PhysicsModelCatalog.hh" -#include <iostream> - - - - -class GateBremPostStepDoIt : public G4VEnergyLossProcess { -public : - -GateBremPostStepDoIt(); - -~ GateBremPostStepDoIt(); - -virtual G4VParticleChange * PostStepDoIt (const G4Track & track, const G4Step & step) override -{ - const G4MaterialCutsCouple* couple = step.GetPreStepPoint()->GetMaterialCutsCouple(); - currentCouple = couple; - G4VParticleChange* particleChange = G4VEnergyLossProcess::PostStepDoIt(track,step); - return particleChange; -} - - -}; - - -class GateGammaEmPostStepDoIt : public G4VEmProcess { -public : - -GateGammaEmPostStepDoIt(); - -~ GateGammaEmPostStepDoIt(); - -virtual G4VParticleChange * PostStepDoIt(const G4Track & track, const G4Step & step) override -{ - const G4MaterialCutsCouple* couple = step.GetPreStepPoint()->GetMaterialCutsCouple(); - currentCouple = couple; - G4VParticleChange* particleChange = G4VEmProcess::PostStepDoIt(track,step); - return particleChange; -} - - -}; - -class GateplusannihilAtRestDoIt : public G4eplusAnnihilation { -public : - -GateplusannihilAtRestDoIt(); -~ GateplusannihilAtRestDoIt(); - -virtual G4VParticleChange* AtRestDoIt(const G4Track& track, - const G4Step& step) override -// Performs the e+ e- annihilation when both particles are assumed at rest. - { - G4Track copyTrack = G4Track(track); - copyTrack.SetStep(&step); - G4VParticleChange* particleChange = G4eplusAnnihilation::AtRestDoIt(copyTrack,step); - return particleChange; - } -}; -#endif - - - - - - \ No newline at end of file diff --git a/core/opengate_core/opengate_lib/pyGateKillInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/pyGateKillInteractingParticleActor.cpp deleted file mode 100644 index fa089c4ef..000000000 --- a/core/opengate_core/opengate_lib/pyGateKillInteractingParticleActor.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* -------------------------------------------------- - Copyright (C): OpenGATE Collaboration - This software is distributed under the terms - of the GNU Lesser General Public Licence (LGPL) - See LICENSE.md for further details - -------------------------------------------------- */ - -#include <pybind11/pybind11.h> - -namespace py = pybind11; - -#include "GateKillInteractingParticleActor.h" - -void init_GateKillInteractingParticleActor(py::module &m) { - py::class_<GateKillInteractingParticleActor, - std::unique_ptr<GateKillInteractingParticleActor, py::nodelete>, - GateVActor>(m, "GateKillInteractingParticleActor") - .def_readwrite( - "fListOfVolumeAncestor", - &GateKillInteractingParticleActor::fListOfVolumeAncestor) - .def(py::init<py::dict &>()); -} diff --git a/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp deleted file mode 100644 index 5de9fe12b..000000000 --- a/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* -------------------------------------------------- - Copyright (C): OpenGATE Collaboration - This software is distributed under the terms - of the GNU Lesser General Public Licence (LGPL) - See LICENSE.md for further details - -------------------------------------------------- */ - -#include <pybind11/pybind11.h> - -namespace py = pybind11; - -#include "GateKillNonInteractingParticleActor.h" - -void init_GateKillNonInteractingParticleActor(py::module &m) { - py::class_<GateKillNonInteractingParticleActor, - std::unique_ptr<GateKillNonInteractingParticleActor, py::nodelete>, - GateVActor>(m, "GateKillNonInteractingParticleActor") - .def_readwrite( - "fListOfVolumeAncestor", - &GateKillNonInteractingParticleActor::fListOfVolumeAncestor) - .def(py::init<py::dict &>()); -} diff --git a/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActorOld.cpp b/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActorOld.cpp deleted file mode 100644 index a15dc55e8..000000000 --- a/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActorOld.cpp +++ /dev/null @@ -1,21 +0,0 @@ -/* -------------------------------------------------- - Copyright (C): OpenGATE Collaboration - This software is distributed under the terms - of the GNU Lesser General Public Licence (LGPL) - See LICENSE.md for further details - -------------------------------------------------- */ -#include <pybind11/pybind11.h> - -namespace py = pybind11; -#include "GateLastVertexInteractionSplittingActorOld.h" - -void init_GateLastVertexInteractionSplittingActorOld(py::module &m) { - - py::class_<GateLastVertexInteractionSplittingActorOld, GateVActor, - std::unique_ptr<GateLastVertexInteractionSplittingActorOld, py::nodelete>>( - m, "GateLastVertexInteractionSplittingActorOld") - .def_readwrite( - "fListOfVolumeAncestor", - &GateLastVertexInteractionSplittingActorOld::fListOfVolumeAncestor) - .def(py::init<py::dict &>()); -} diff --git a/opengate/actors/actorbuilders.py b/opengate/actors/actorbuilders.py index b8644c5bd..eeae701de 100644 --- a/opengate/actors/actorbuilders.py +++ b/opengate/actors/actorbuilders.py @@ -17,13 +17,10 @@ SourceInfoActor, TestActor, KillActor, - KillInteractingParticleActor, BremSplittingActor, ComptSplittingActor, LastVertexInteractionSplittingActor, - LastVertexInteractionSplittingActorOld, ComptPseudoTransportationActor, - KillNonInteractingParticleActor, SurfaceSplittingActor, ) from .dynamicactors import DynamicGeometryActor @@ -50,12 +47,10 @@ ARFTrainingDatasetActor, TestActor, KillActor, - KillInteractingParticleActor, BremSplittingActor, ComptSplittingActor, LastVertexInteractionSplittingActor, ComptPseudoTransportationActor, - KillNonInteractingParticleActor, SurfaceSplittingActor, DynamicGeometryActor, } diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 404d964f0..ad2606d49 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -431,36 +431,6 @@ def __init__(self, user_info): g4.GateKillActor.__init__(self, user_info.__dict__) -class KillInteractingParticleActor( - g4.GateKillInteractingParticleActor, ActorBase -): - type_name = "KillInteractingParticleActor" - - def set_default_user_info(user_info): - ActorBase.set_default_user_info(user_info) - user_info.list_of_volume_name = [] - - def __init__(self, user_info): - ActorBase.__init__(self, user_info) - g4.GateKillInteractingParticleActor.__init__(self, user_info.__dict__) - self.list_of_volume_name = user_info.list_of_volume_name - self.user_info.mother = user_info.mother - - def initialize(self, volume_engine=None): - - super().initialize(volume_engine) - volume_tree = self.simulation.volume_manager.get_volume_tree() - dico_of_volume_tree = {} - for pre, _, node in RenderTree(volume_tree): - dico_of_volume_tree[str(node.name)] = node - volume_name = self.user_info.mother - while volume_name != "world": - node = dico_of_volume_tree[volume_name] - volume_name = node.mother - self.list_of_volume_name.append(volume_name) - self.fListOfVolumeAncestor = self.list_of_volume_name - - class ComptSplittingActor(g4.GateOptrComptSplittingActor, ActorBase): type_name = "ComptSplittingActor" @@ -518,40 +488,6 @@ def initialize(self, volume_engine=None): print(self.fListOfVolumeAncestor) -class LastVertexInteractionSplittingActorOld(g4.GateLastVertexInteractionSplittingActorOld, ActorBase): - type_name = "LastVertexInteractionSplittingActorOld" - - def set_default_user_info(user_info): - ActorBase.set_default_user_info(user_info) - deg = g4_units.deg - user_info.splitting_factor = 1 - user_info.russian_roulette_for_angle = False - user_info.rotation_vector_director = False - user_info.vector_director = [0, 0, -1] - user_info.max_theta = 90 * deg - user_info.list_of_volume_name = [] - - def __init__(self, user_info): - ActorBase.__init__(self, user_info) - g4.GateLastVertexInteractionSplittingActorOld.__init__(self, user_info.__dict__) - self.list_of_volume_name = user_info.list_of_volume_name - self.user_info.mother = user_info.mother - - def initialize(self, volume_engine=None): - - super().initialize(volume_engine) - volume_tree = self.simulation.volume_manager.get_volume_tree() - dico_of_volume_tree = {} - for pre, _, node in RenderTree(volume_tree): - dico_of_volume_tree[str(node.name)] = node - volume_name = self.user_info.mother - while volume_name != "world": - node = dico_of_volume_tree[volume_name] - volume_name = node.mother - self.list_of_volume_name.append(volume_name) - self.fListOfVolumeAncestor = self.list_of_volume_name - print(self.fListOfVolumeAncestor) - class ComptPseudoTransportationActor( g4.GateOptrComptPseudoTransportationActor, ActorBase ): @@ -593,34 +529,6 @@ def __init__(self, user_info): g4.GateBOptrBremSplittingActor.__init__(self, user_info.__dict__) -class KillNonInteractingParticleActor( - g4.GateKillNonInteractingParticleActor, ActorBase -): - type_name = "KillNonInteractingParticleActor" - - def set_default_user_info(user_info): - ActorBase.set_default_user_info(user_info) - user_info.list_of_volume_name = [] - - def __init__(self, user_info): - ActorBase.__init__(self, user_info) - g4.GateKillNonInteractingParticleActor.__init__(self, user_info.__dict__) - self.list_of_volume_name = user_info.list_of_volume_name - self.user_info.mother = user_info.mother - - def initialize(self, volume_engine=None): - - super().initialize(volume_engine) - volume_tree = self.simulation.volume_manager.get_volume_tree() - dico_of_volume_tree = {} - for pre, _, node in RenderTree(volume_tree): - dico_of_volume_tree[str(node.name)] = node - volume_name = self.user_info.mother - while volume_name != "world": - node = dico_of_volume_tree[volume_name] - volume_name = node.mother - self.list_of_volume_name.append(volume_name) - self.fListOfVolumeAncestor = self.list_of_volume_name class SurfaceSplittingActor(g4.GateSurfaceSplittingActor, ActorBase): From b9600a591dac14e0cdf8ef8221e6350d93bae11c Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Wed, 13 Nov 2024 10:48:36 +0100 Subject: [PATCH 47/82] files deletion --- ...LastVertexInteractionSplittingOldActor.cpp | 848 ------------------ .../GateSurfaceSplittingActor.cpp | 96 -- .../opengate_lib/GateSurfaceSplittingActor.h | 43 - .../pyGateSurfaceSplittingActor.cpp | 22 - 4 files changed, 1009 deletions(-) delete mode 100644 core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingOldActor.cpp delete mode 100644 core/opengate_core/opengate_lib/GateSurfaceSplittingActor.cpp delete mode 100644 core/opengate_core/opengate_lib/GateSurfaceSplittingActor.h delete mode 100644 core/opengate_core/opengate_lib/pyGateSurfaceSplittingActor.cpp diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingOldActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingOldActor.cpp deleted file mode 100644 index fdb7a2f1e..000000000 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingOldActor.cpp +++ /dev/null @@ -1,848 +0,0 @@ -// -// ******************************************************************** -// * 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 GateLastVertexInteractionSplittingActorOld.cc -/// \brief Implementation of the GateLastVertexInteractionSplittingActorOld class - -#include "GateHelpersDict.h" -#include "GateHelpersImage.h" - -#include "CLHEP/Units/SystemOfUnits.h" -#include "G4BiasingProcessInterface.hh" -#include "G4Gamma.hh" -#include "G4LogicalVolumeStore.hh" -#include "G4ParticleTable.hh" -#include "G4PhysicalVolumeStore.hh" -#include "G4Positron.hh" -#include "G4ProcessManager.hh" -#include "G4ProcessVector.hh" -#include "G4TrackStatus.hh" -#include "G4TrackingManager.hh" -#include "G4VParticleChange.hh" -#include "G4eplusAnnihilation.hh" -#include "GateLastVertexInteractionSplittingActorOld.h" -#include "GateLastVertexSplittingPostStepDoItOld.h" -#include "GateOptnComptSplitting.h" - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateLastVertexInteractionSplittingActorOld:: - GateLastVertexInteractionSplittingActorOld(py::dict &user_info) - : GateVActor(user_info, false) { - fMotherVolumeName = DictGetStr(user_info, "mother"); - fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); - fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); - fRussianRouletteForAngle = - DictGetBool(user_info, "russian_roulette_for_angle"); - fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); - fMaxTheta = DictGetDouble(user_info, "max_theta"); - fActions.insert("StartSimulationAction"); - fActions.insert("SteppingAction"); - fActions.insert("BeginOfEventAction"); - fActions.insert("BeginOfRunAction"); - fActions.insert("PreUserTrackingAction"); - fActions.insert("PostUserTrackingAction"); -} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -G4double -GateLastVertexInteractionSplittingActorOld::RussianRouletteForAngleSurvival( - G4ThreeVector dir, G4ThreeVector vectorDirector, G4double maxTheta, - G4double split) { - G4double cosTheta = vectorDirector * dir; - G4double theta = std::acos(cosTheta); - G4double weightToApply = 1; - if (theta > fMaxTheta) { - G4double probability = G4UniformRand(); - if (probability <= 1 / split) { - weightToApply = split; - } else { - weightToApply = 0; - } - } - return weightToApply; -} - -G4Track *GateLastVertexInteractionSplittingActorOld::CreateComptonTrack( - G4ParticleChangeForGamma *gammaProcess, G4Track track, G4double weight) { - G4double energy = gammaProcess->GetProposedKineticEnergy(); - G4double globalTime = track.GetGlobalTime(); - G4double newGammaWeight = weight; - G4ThreeVector polarization = gammaProcess->GetProposedPolarization(); - const G4ThreeVector momentum = gammaProcess->GetProposedMomentumDirection(); - const G4ThreeVector position = track.GetPosition(); - G4Track *newTrack = new G4Track(track); - - newTrack->SetWeight(newGammaWeight); - newTrack->SetKineticEnergy(energy); - newTrack->SetMomentumDirection(momentum); - newTrack->SetPosition(position); - newTrack->SetPolarization(polarization); - return newTrack; -} - -void GateLastVertexInteractionSplittingActorOld::ComptonSplitting( - G4Step *CurrentStep, G4Track *track, const G4Step *step, - G4VProcess *process) { - // Loop on process and add the secondary tracks to the current step secondary - // vector - - G4TrackVector *trackVector = CurrentStep->GetfSecondary(); - G4double gammaWeight = 0; - - GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; - G4VParticleChange *processFinalState = emProcess->PostStepDoIt(*track, *step); - G4ParticleChangeForGamma *gammaProcessFinalState = - (G4ParticleChangeForGamma *)processFinalState; - const G4ThreeVector momentum = - gammaProcessFinalState->GetProposedMomentumDirection(); - gammaWeight = fWeightOfEnteringParticle / fSplittingFactor; - if (fRussianRouletteForAngle == true) { - G4double weightToApply = RussianRouletteForAngleSurvival( - momentum, fVectorDirector, fMaxTheta, fSplittingFactor); - if (weightToApply != 0) { - gammaWeight = gammaWeight * weightToApply; - G4Track *newTrack = - CreateComptonTrack(gammaProcessFinalState, *track, gammaWeight); - trackVector->push_back(newTrack); - G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - for (int j = 0; j < NbOfSecondaries; j++) { - G4Track *newTrack = processFinalState->GetSecondary(j); - newTrack->SetWeight(gammaWeight); - trackVector->push_back(newTrack); - } - } - } - - else { - G4Track *newTrack = - CreateComptonTrack(gammaProcessFinalState, *track, gammaWeight); - trackVector->push_back(newTrack); - G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - for (int j = 0; j < NbOfSecondaries; j++) { - G4Track *newTrack = processFinalState->GetSecondary(j); - newTrack->SetWeight(gammaWeight); - trackVector->push_back(newTrack); - } - } - processFinalState->Clear(); - gammaProcessFinalState->Clear(); -} - -void GateLastVertexInteractionSplittingActorOld::SecondariesSplitting( - G4Step *CurrentStep, G4Track *track, const G4Step *step, - G4VProcess *process) { - // Loop on process and add the secondary tracks to the current step secondary - // vector. - - // std::cout<<track->GetKineticEnergy()<<" - // "<<track->GetDynamicParticle()->GetKineticEnergy()<<" - // "<<track->GetMomentumDirection()<<" - // "<<track->GetDynamicParticle()->GetMomentumDirection()<<std::endl; - - G4String particleName = track->GetParticleDefinition()->GetParticleName(); - G4TrackVector *trackVector = CurrentStep->GetfSecondary(); - G4double gammaWeight = 0; - G4VParticleChange *processFinalState = nullptr; - if (process->GetProcessName() == "eBrem") { - GateBremPostStepDoIt *bremProcess = (GateBremPostStepDoIt *)process; - processFinalState = - bremProcess->GateBremPostStepDoIt::PostStepDoIt(*track, *step); - } else { - GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; - if (track->GetTrackStatus() == 0) { - // if (process->GetProcessName() == "annihil") - // std::cout<<"lol"<<std::endl; - processFinalState = emProcess->PostStepDoIt(*track, *step); - } - if (track->GetTrackStatus() == 1) { - GateplusannihilAtRestDoIt *eplusAnnihilProcess = - (GateplusannihilAtRestDoIt *)process; - - processFinalState = - eplusAnnihilProcess->GateplusannihilAtRestDoIt::AtRestDoIt(*track, - *step); - } - } - - G4int NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - - if (NbOfSecondaries > 0) { - gammaWeight = fWeightOfEnteringParticle / fSplittingFactor; - G4Track *newTrack = processFinalState->GetSecondary(0); - if ((fRussianRouletteForAngle == true) && (particleName == "gamma")) { - const G4ThreeVector momentum = newTrack->GetMomentumDirection(); - G4double weightToApply = RussianRouletteForAngleSurvival( - momentum, fVectorDirector, fMaxTheta, fSplittingFactor); - if (weightToApply != 0) { - gammaWeight = gammaWeight * weightToApply; - newTrack->SetWeight(gammaWeight); - trackVector->push_back(newTrack); - } - } else { - newTrack->SetWeight(gammaWeight); - trackVector->push_back(newTrack); - } - - for(int i = 1; i < NbOfSecondaries;i++){ - delete processFinalState->GetSecondary(i); - } - } else { - processFinalState->Clear(); - SecondariesSplitting(CurrentStep, track, step, process); - } - processFinalState->Clear(); -} - -void GateLastVertexInteractionSplittingActorOld::ClearRememberedTracksAndSteps( - std::map<G4int, G4TrackVector> rememberedTracks, - std::map<G4int, std::vector<G4Step *>> rememberedSteps) { - std::vector<G4Track *> trackToKill = {}; - for (auto it = rememberedTracks.begin(); it != rememberedTracks.end(); ++it) { - std::vector<G4Track *> vector = it->second; - for (auto it2 = vector.begin(); it2 != vector.end(); it2++) { - if ((std::find(trackToKill.begin(), trackToKill.end(), *it2) == - trackToKill.end())) { - // Method set pour clear les doublons au lieu de find. - trackToKill.push_back(*it2); - } - } - } - - for (auto it = trackToKill.begin(); it != trackToKill.end(); ++it) { - delete *it; - } - - std::vector<G4Step *> stepToKill = {}; - for (auto it = rememberedSteps.begin(); it != rememberedSteps.end(); ++it) { - std::vector<G4Step *> vector = it->second; - for (auto it2 = vector.begin(); it2 != vector.end(); it2++) { - if ((std::find(stepToKill.begin(), stepToKill.end(), *it2) == - stepToKill.end())) { - stepToKill.push_back(*it2); - } - } - } - - for (auto it = stepToKill.begin(); it != stepToKill.end(); ++it) { - delete *it; - } -} - -void GateLastVertexInteractionSplittingActorOld::RememberLastProcessInformation( - G4Step *step) { - - // When an interesting process to split occurs, we remember the status of the - // track and the process at this current step some informations regarding the - // track info have to be changed because they were update according to the - // interaction that occured These informations are stocked as a map object, - // binding the track ID with all the track objects and processes to split. - // Because in some cases, if a secondary was created before an interaction - // chain, this secondary will be track after the chain and without this - // association, we wll loose the information about the process occuring for - // this secondary. - - G4String creatorProcessName = "None"; - if (step->GetTrack()->GetCreatorProcess() != 0) - creatorProcessName = - step->GetTrack()->GetCreatorProcess()->GetProcessName(); - G4String processName = "None"; - G4int trackID = step->GetTrack()->GetTrackID(); - G4int parentID = step->GetTrack()->GetParentID(); - //<<processName<<" - //"<<step->GetTrack()->GetParticleDefinition()->GetParticleName()<<" - //"<<step->GetTrack()->GetTrackStatus()<<std::endl; - if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) { - processName = - step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); - } - if ((step->GetTrack()->GetParticleDefinition()->GetParticleName() == "e+") && - ((step->GetTrack()->GetTrackStatus() == 1) || - (step->GetTrack()->GetTrackStatus() == 2))) { - processName = "annihil"; - } - - if ((std::find(fListOfProcesses.begin(), fListOfProcesses.end(), - processName) != fListOfProcesses.end())) { - G4Track *trackInformation = new G4Track(*(step->GetTrack())); - G4Step *stepInformation = new G4Step(*(step)); - - G4StepPoint *stepPoint = nullptr; - stepPoint = step->GetPreStepPoint(); - - trackInformation->SetKineticEnergy(stepPoint->GetKineticEnergy()); - if ((processName == "eBrem") || ((processName == "annihil") && - step->GetTrack()->GetTrackStatus() == 1)) { - trackInformation->SetKineticEnergy(stepPoint->GetKineticEnergy() - - step->GetTotalEnergyDeposit()); - } - trackInformation->SetMomentumDirection(stepPoint->GetMomentumDirection()); - if ((processName == "annihil") && - ((step->GetTrack()->GetTrackStatus() == 1))) - trackInformation->SetTrackStatus(fStopButAlive); - else { - trackInformation->SetTrackStatus(fAlive); - } - trackInformation->SetPolarization(stepPoint->GetPolarization()); - // trackInformation->SetPosition(stepPoint->GetPosition()); - - if (auto search = fRememberedTracks.find(trackID); - search != fRememberedTracks.end()) { - fRememberedTracks[trackID].push_back(trackInformation); - fRememberedProcesses[trackID].push_back(processName); - fRememberedSteps[trackID].push_back(stepInformation); - } - - else { - fRememberedTracks[trackID] = {trackInformation}; - fRememberedProcesses[trackID] = {processName}; - fRememberedSteps[trackID] = {stepInformation}; - } - } - - else { - if (auto search = fRememberedTracks.find(trackID); - search == fRememberedTracks.end()) { - if (auto search = fRememberedTracks.find(parentID); - search != fRememberedTracks.end()) { - if (auto it = std::find(fRememberedProcesses[parentID].begin(), - fRememberedProcesses[parentID].end(), - creatorProcessName); - it != fRememberedProcesses[parentID].end()) { - auto idx = it - fRememberedProcesses[parentID].begin(); - fRememberedTracks[trackID] = { - new G4Track(*fRememberedTracks[parentID][idx])}; - fRememberedProcesses[trackID] = {fRememberedProcesses[parentID][idx]}; - fRememberedSteps[trackID] = { - new G4Step(*fRememberedSteps[parentID][idx])}; - } else { - fRememberedTracks[trackID] = { - new G4Track(*fRememberedTracks[parentID][0])}; - fRememberedProcesses[trackID] = {fRememberedProcesses[parentID][0]}; - fRememberedSteps[trackID] = { - new G4Step(*fRememberedSteps[parentID][0])}; - } - } - } - } -} - -void GateLastVertexInteractionSplittingActorOld::CreateNewParticleAtTheLastVertex( - G4Step *CurrentStep, G4Track *track, const G4Step *step, - G4String processName) { - // We retrieve the process associated to the process name to split and we - // split according the process. Since for compton scattering, the gamma is not - // a secondary particles, this one need to have his own splitting function. - G4ParticleDefinition *particleDefinition = track->GetDefinition(); - G4ProcessManager *processManager = particleDefinition->GetProcessManager(); - G4ProcessVector *processList = processManager->GetProcessList(); - - G4VProcess *processToSplit = nullptr; - for (size_t i = 0; i < processList->size(); ++i) { - auto process = (*processList)[i]; - if (process->GetProcessName() == processName) { - processToSplit = process; - } - } - if (processName == "compt") { - - ComptonSplitting(CurrentStep, track, step, processToSplit); - } - - else { - SecondariesSplitting(CurrentStep, track, step, processToSplit); - } -} - -void GateLastVertexInteractionSplittingActorOld:: - ResetProcessesForEnteringParticles(G4Step *step) { - - // This function reset the processes and track registered to be split each - // time a particle enters into a volume. Here a new particle incoming is - // either a particle entering into the volume ( first pre step is a boundary - // of a mother volume or first step trigger the actor and the vertex is not - // either the mother volume or a the fdaughter volume) or an event generated - // within the volume (the particle did not enter into the volume and its - // parentID = 0). An additionnal condition is set with the trackID to ensure - // particles crossing twice the volume (after either a compton or pair prod) - // are not splitted. - - G4int trackID = step->GetTrack()->GetTrackID(); - if ((fEventIDOfInitialSplittedTrack != fEventID) || - ((fEventIDOfInitialSplittedTrack == fEventID) && - (trackID < fTrackIDOfInitialTrack))) { - G4String logicalVolumeNamePreStep = "None"; - if (step->GetPreStepPoint()->GetPhysicalVolume() != 0) { - logicalVolumeNamePreStep = step->GetPreStepPoint() - ->GetPhysicalVolume() - ->GetLogicalVolume() - ->GetName(); - } - if (((step->GetPreStepPoint()->GetStepStatus() == 1) && - (logicalVolumeNamePreStep == fMotherVolumeName)) || - ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != - logicalVolumeNamePreStep) && - (step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != - fMotherVolumeName))) { - fTrackIDOfInitialTrack = trackID; - fEventIDOfInitialSplittedTrack = fEventID; - fWeightOfEnteringParticle = step->GetTrack()->GetWeight(); - ClearRememberedTracksAndSteps(fRememberedTracks, fRememberedSteps); - fRememberedProcesses.clear(); - fRememberedTracks.clear(); - fRememberedSteps.clear(); - } else if (step->GetTrack()->GetParentID() == 0) { - fTrackIDOfInitialTrack = trackID; - fEventIDOfInitialSplittedTrack = fEventID; - fWeightOfEnteringParticle = step->GetTrack()->GetWeight(); - ClearRememberedTracksAndSteps(fRememberedTracks, fRememberedSteps); - fRememberedProcesses.clear(); - fRememberedTracks.clear(); - fRememberedSteps.clear(); - } - } -} - -void GateLastVertexInteractionSplittingActorOld:: - PostponeFirstAnnihilationTrackIfInteraction(G4Step *step, - G4String processName) { - // In case the first gamma issued from annihilation undergoes an interaction, - // in order to not bias the process We keep in memory the particle post step - // state (with its secondaries) and kill the particle and its secondaries. If - // the second photon from annihilation exiting the collimation system with an - // interaction or is absorbed within the collimation, the particle is - // subsequently resimulated, starting from the interaction point. - - G4int trackID = step->GetTrack()->GetTrackID(); - if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), - processName) != fListOfProcesses.end()) { - fSuspendForAnnihil = true; - G4Track trackToPostpone = G4Track(*(step->GetTrack())); - trackToPostpone.SetKineticEnergy( - step->GetPostStepPoint()->GetKineticEnergy()); - trackToPostpone.SetMomentumDirection( - step->GetPostStepPoint()->GetMomentumDirection()); - trackToPostpone.SetTrackStatus(step->GetTrack()->GetTrackStatus()); - trackToPostpone.SetPolarization( - step->GetPostStepPoint()->GetPolarization()); - trackToPostpone.SetPosition(step->GetPostStepPoint()->GetPosition()); - trackToPostpone.SetTrackID(trackID); - fTracksToPostpone.push_back(trackToPostpone); - auto theTrack = fTracksToPostpone[0]; - - auto secVec = step->GetfSecondary(); - for (int i = 0; i < secVec->size(); i++) { - G4Track *sec = (*secVec)[i]; - G4Track copySec = G4Track((*sec)); - fTracksToPostpone.push_back(copySec); - } - - fTracksToPostpone[0].SetTrackID(trackID); - step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); - } -} - -void GateLastVertexInteractionSplittingActorOld:: - RegenerationOfPostponedAnnihilationTrack(G4Step *step) { - - // If the second photon from annihilation suceed to exit the collimation - // system with at least one interaction or was absorbed. Resimulation of - // annihilation photons and its potential secondaries. - - G4TrackVector *currentSecondaries = step->GetfSecondary(); - for (int i = 0; i < fTracksToPostpone.size(); i++) { - G4Track *trackToAdd = - new G4Track(fTracksToPostpone[fTracksToPostpone.size() - 1 - i]); - trackToAdd->SetParentID(step->GetTrack()->GetTrackID() - 1); - - currentSecondaries->insert(currentSecondaries->begin(), trackToAdd); - } - G4Track *firstPostponedTrack = (*currentSecondaries)[0]; - - // Handle of case were the interaction killed the photon issued from - // annihilation, it will not be track at the following state and the boolean - // plus the track vector need to be reset - - if (firstPostponedTrack->GetTrackStatus() == 2) { - fSuspendForAnnihil = false; - fTracksToPostpone.clear(); - } -} - -void GateLastVertexInteractionSplittingActorOld:: - HandleTrackIDIfPostponedAnnihilation(G4Step *step) { - // The ID is to modify trackID and the processes and tracks sotcked for a - // specific trackID associated to the postponed annihilation to respect the - // trackID order in GEANT4. Since in this case the second photon is tracked - // before the first one his trackID +=1. For the postponed one, he is the - // first secondary particle of the second photon tracks, his trackID is - // therefore equal to the second photon trackID +1 instead of the second - // photon trackID -1. The parentID of secondary particles are also modified - // because they are used in the rememberlastprocess function At last the value - // associated to a specific trackID in the process and track map are modified - // according to the new trackID. - - if (fSuspendForAnnihil) { - if (step->GetTrack()->GetTrackID() == - fTracksToPostpone[0].GetTrackID() - 1) { - fRememberedProcesses[step->GetTrack()->GetTrackID()] = - fRememberedProcesses[step->GetTrack()->GetTrackID() + 1]; - fRememberedProcesses.erase(step->GetTrack()->GetTrackID() + 1); - fRememberedSteps[step->GetTrack()->GetTrackID()] = - fRememberedSteps[step->GetTrack()->GetTrackID() + 1]; - fRememberedSteps.erase(step->GetTrack()->GetTrackID() + 1); - fRememberedTracks[step->GetTrack()->GetTrackID()] = - fRememberedTracks[step->GetTrack()->GetTrackID() + 1]; - fRememberedTracks.erase(step->GetTrack()->GetTrackID() + 1); - auto vecSec = step->GetSecondary(); - for (int i = 0; i < vecSec->size(); i++) { - (*vecSec)[i]->SetParentID(step->GetTrack()->GetTrackID() + 1); - } - step->GetTrack()->SetTrackID(step->GetTrack()->GetTrackID() + 1); - } - if (step->GetTrack()->GetTrackID() == - fTracksToPostpone[0].GetTrackID() + 1) { - auto vecSec = step->GetSecondary(); - for (int i = 0; i < vecSec->size(); i++) { - (*vecSec)[i]->SetParentID(step->GetTrack()->GetTrackID() - 2); - } - step->GetTrack()->SetTrackID(step->GetTrack()->GetTrackID() - 2); - } - } -} - -void GateLastVertexInteractionSplittingActorOld::BeginOfRunAction( - const G4Run *run) { - - // The way to behave of the russian roulette is the following : - // we provide a vector director and the theta angle acceptance, where theta = - // 0 is a vector colinear to the vector director Then if the track generated - // is on the acceptance angle, we add it to the primary track, and if it's not - // the case, we launch the russian roulette - - if (fRotationVectorDirector) { - G4VPhysicalVolume *physBiasingVolume = - G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); - auto rot = physBiasingVolume->GetObjectRotationValue(); - fVectorDirector = rot * fVectorDirector; - } -} - -void GateLastVertexInteractionSplittingActorOld::BeginOfEventAction( - const G4Event *event) { - - fParentID = -1; - fEventID = event->GetEventID(); - fEventIDOfSplittedTrack = -1; - fTrackIDOfSplittedTrack = -1; - fNotSplitted == true; -} - -void GateLastVertexInteractionSplittingActorOld::PreUserTrackingAction( - const G4Track *track) { - fIsFirstStep = true; -} - -void GateLastVertexInteractionSplittingActorOld::SteppingAction(G4Step *step) { - - G4String particleName = - step->GetTrack()->GetParticleDefinition()->GetParticleName(); - G4String creatorProcessName = "None"; - - if (step->GetTrack()->GetCreatorProcess() != 0) - creatorProcessName = - step->GetTrack()->GetCreatorProcess()->GetProcessName(); - - // std::cout<<particleName<<" "<<fEventID<<" - // "<<step->GetTotalEnergyDeposit()<<" - // "<<step->GetPreStepPoint()->GetKineticEnergy()<<" - // "<<step->GetPreStepPoint()->GetMomentumDirection()<<step->GetTrack()->GetTrackID()<<" - // "<<step->GetTrack()->GetWeight()<<" "<<step->GetTrack()->GetParentID()<<" - // "<<fTrackIDOfSplittedTrack<<std::endl;; - - if (fIsFirstStep) { - ResetProcessesForEnteringParticles(step); - } - - G4int trackID = step->GetTrack()->GetTrackID(); - - if ((step->GetTrack()->GetWeight() < fWeightOfEnteringParticle) && - (fNotSplitted == false)) { - - G4String logicalVolumeNamePostStep = "None"; - if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) - logicalVolumeNamePostStep = step->GetPostStepPoint() - ->GetPhysicalVolume() - ->GetLogicalVolume() - ->GetName(); - - G4String processName = "None"; - if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) - processName = - step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); - - if (particleName != "e+"){ - if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), processName) != fListOfProcesses.end()){ - step->GetTrack()->SetTrackStatus(fStopAndKill); - for (int i = 0; i < step->GetfSecondary()->size(); i++){ - G4Track* track =(*(step->GetfSecondary()))[i]; - track->SetTrackStatus(fStopAndKill); - } - } - } - - - if ((particleName == "e+") && ((step->GetTrack()->GetTrackStatus() == 1)||(step->GetTrack()->GetTrackStatus() == 2))){ - step->GetTrack()->SetTrackStatus(fStopAndKill); - for (int i = 0; i < step->GetfSecondary()->size(); i++){ - G4Track* track = (*(step->GetfSecondary()))[i]; - track->SetTrackStatus(fStopAndKill); - } - } - - if ((step->GetTrack()->GetTrackStatus() == 2) || - (step->GetTrack()->GetTrackStatus() == 3)) { - - /* - delete fTrackToSplit; - delete fStepToSplit; - fTrackToSplit = new - G4Track(*fRememberedTracks[fTrackIDOfSplittedTrack].back()); fStepToSplit - = new G4Step(*fRememberedSteps[fTrackIDOfSplittedTrack].back()); - */ - - CreateNewParticleAtTheLastVertex(step, fTrackToSplit, fStepToSplit, - fProcessToSplit); - } - - if (((step->GetTrack()->GetTrackStatus() != 2) && - (step->GetTrack()->GetTrackStatus() != 3)) && - (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), - logicalVolumeNamePostStep) != fListOfVolumeAncestor.end())) { - - if (fSplitCounter < fWeightOfEnteringParticle) { - /* - delete fTrackToSplit; - delete fStepToSplit; - fTrackToSplit = new - G4Track(*fRememberedTracks[fTrackIDOfSplittedTrack].back()); - fStepToSplit = new - G4Step(*fRememberedSteps[fTrackIDOfSplittedTrack].back()); - */ - CreateNewParticleAtTheLastVertex(step, fTrackToSplit, fStepToSplit, - fProcessToSplit); - // std::cout<<fTrackToSplit<<" "<<fStepToSplit<<std::endl; - G4TrackVector *trackVector = step->GetfSecondary(); - G4Track *theTrack = trackVector->back(); - fSplitCounter += theTrack->GetWeight(); - // std::cout<<theTrack->GetWeight()<<std::endl; - if (fSplitCounter >= fWeightOfEnteringParticle) { - delete fTrackToSplit; - delete fStepToSplit; - fTrackToSplit = nullptr; - fStepToSplit = nullptr; - fSplitCounter = 0; - fNotSplitted = true; - fProcessToSplit = "None"; - theTrack->SetTrackStatus(fStopAndKill); - } - } - } - } - - if (step->GetTrack()->GetWeight() == fWeightOfEnteringParticle) { - // std::cout<<particleName<<" - // "<<step->GetTrack()->GetTrackStatus()<<std::endl; - RememberLastProcessInformation(step); - // std::cout<<particleName<<" - // "<<step->GetTrack()->GetTrackStatus()<<std::endl; - G4String process = "None"; - if (auto search = fRememberedProcesses.find(trackID); - search != fRememberedProcesses.end()) { - process = fRememberedProcesses[trackID].back(); - } - // std::cout<<step->GetTrack()->GetVertexPosition()<<" - // "<<step->GetTrack()->GetParticleDefinition()->GetParticleName()<<" - // "<<fSuspendForAnnihil<<" "<<process<<" - // "<<step->GetTrack()->GetWeight()<<" "<<step->GetTrack()->GetParentID()<<" - // "<<step->GetTrack()->GetTrackID()<<" "<<creatorProcessName<<" - // "<<step->GetTrack()->GetTrackStatus()<<" - // "<<step->GetPreStepPoint()->GetKineticEnergy()<<" " - // <<step->GetPostStepPoint()->GetKineticEnergy()<<" - // "<<step->GetPreStepPoint()->GetPosition()<<" - // "<<step->GetPostStepPoint()->GetPosition()<<std::endl; - - /* - if ((!fSuspendForAnnihil) && (creatorProcessName == "annihil") && - (fTrackIDOfSplittedTrack == step->GetTrack()->GetParentID()) && (fEventID == - fEventIDOfSplittedTrack)) - { - step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); - } - */ - - if ((particleName == "e-") && - (step->GetTrack()->GetWeight() == fWeightOfEnteringParticle) && - (creatorProcessName == "conv") && - (fTrackIDOfSplittedTrack == step->GetTrack()->GetParentID()) && - (fEventID == fEventIDOfSplittedTrack)) { - step->GetTrack()->SetTrackStatus(fStopAndKill); - } - - /* - if ((!fSuspendForAnnihil) && (process != "annihil") && (creatorProcessName - == "annihil") && (step->GetTrack()->GetTrackStatus() != 3)) - { - G4int parentID = step->GetTrack()->GetParentID(); - if (auto search = fRememberedProcesses.find(parentID); search != - fRememberedProcesses.end()) - { - if (fRememberedProcesses[trackID].back() == "annihil") - PostponeFirstAnnihilationTrackIfInteraction(step, process); - } - } - - // If the first annihilation photon exit the collimation and the process to - split is annihilation - // We kill the second photon, because the annihilation will generate both - the photons. - - if (fSuspendForAnnihil) - { - if (trackID == fTracksToPostpone[0].GetTrackID() - 1) - { - fSuspendForAnnihil = false; - fTracksToPostpone.clear(); - } - } - */ - - G4String logicalVolumeNamePostStep = "None"; - G4ThreeVector particleMomentum = step->GetPostStepPoint()->GetMomentumDirection(); - if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) - logicalVolumeNamePostStep = step->GetPostStepPoint() - ->GetPhysicalVolume() - ->GetLogicalVolume() - ->GetName(); - - if (((step->GetTrack()->GetTrackStatus() != 2) && - (step->GetTrack()->GetTrackStatus() != 3)) && - (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), - logicalVolumeNamePostStep) != fListOfVolumeAncestor.end())&& (particleMomentum[2] < 0)) { - - if (auto search = fRememberedProcesses.find(trackID); - search != fRememberedProcesses.end()) { - - fProcessToSplit = fRememberedProcesses[trackID].back(); - fTrackToSplit = new G4Track(*fRememberedTracks[trackID].back()); - fStepToSplit = new G4Step(*fRememberedSteps[trackID].back()); - } - - if (std::find(fListOfProcesses.begin(), fListOfProcesses.end(), - fProcessToSplit) != fListOfProcesses.end()) { - - fTrackIDOfSplittedTrack = trackID; - fEventIDOfSplittedTrack = fEventID; - fTrackIDOfInitialSplittedTrack = fTrackIDOfInitialTrack; - - // Handle of pecularities (1): - - // If the process t split is the gamma issued from compton interaction, - // the electron primary generated have to be killed given that electron - // will be regenerated - - if ((fProcessToSplit == "compt") && (particleName == "gamma")) { - auto secondaries = step->GetfSecondary(); - if (secondaries->size() > 0) { - G4Track *lastSecTrack = secondaries->back(); - lastSecTrack->SetTrackStatus(fStopAndKill); - } - } - - // Handle of pecularities (2): - - // If the process to split is the annihilation, the second photon, - // postponed or not, have to be killed the reset of the postpone is - // performed here, whereas the kill of the next annihilation photon, if - // not postponed is realised at the beginning of the step tracking. - /* - if (fProcessToSplit == "annihil") - { - if (fSuspendForAnnihil) - { - fSuspendForAnnihil = false; - fTracksToPostpone.clear(); - } - } - */ - // Handle of pecularities 3 : If the positron which created one or more - // brem photons exits all the brems photons will be killed before their - // tracking, and the conv processes will then be replayed - - if ((particleName == "e+") && (fProcessToSplit != "None")) { - - G4int parentID = step->GetTrack()->GetParentID(); - fProcessToSplit = fRememberedProcesses[parentID].back(); - delete fTrackToSplit; - delete fStepToSplit; - fTrackToSplit = new G4Track(*fRememberedTracks[parentID].back()); - fStepToSplit = new G4Step(*fRememberedSteps[parentID].back()); - fTrackIDOfInitialTrack = parentID; - - auto *vecSecondaries = step->GetfSecondary(); - vecSecondaries->clear(); - } - if (!((fProcessToSplit == "eBrem") && (particleName == "e-"))) { - CreateNewParticleAtTheLastVertex(step, fTrackToSplit, fStepToSplit, - fProcessToSplit); - fNotSplitted = false; - } - step->GetTrack()->SetTrackStatus(fStopAndKill); - } - } - } - - /* - if ((fSuspendForAnnihil) && ((step->GetTrack()->GetTrackStatus() == 1) || - (step->GetTrack()->GetTrackStatus() == 2))) - { - if (trackID == fTracksToPostpone[0].GetTrackID()) - { - RegenerationOfPostponedAnnihilationTrack(step); - } - } - */ - - fIsFirstStep = false; -} - -void GateLastVertexInteractionSplittingActorOld::PostUserTrackingAction( - const G4Track *track) {} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.cpp b/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.cpp deleted file mode 100644 index 1afbbf25d..000000000 --- a/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* -------------------------------------------------- - Copyright (C): OpenGATE Collaboration - This software is distributed under the terms - of the GNU Lesser General Public Licence (LGPL) - See LICENSE.md for further details - ------------------------------------ -------------- */ - -#include "GateSurfaceSplittingActor.h" -#include "G4LogicalVolumeStore.hh" -#include "G4ios.hh" -#include "GateHelpers.h" -#include "GateHelpersDict.h" - -GateSurfaceSplittingActor::GateSurfaceSplittingActor(py::dict &user_info) - : GateVActor(user_info, false) { - fActions.insert("StartSimulationAction"); - fActions.insert("SteppingAction"); - fActions.insert("PreUserTrackingAction"); - fMotherVolumeName = DictGetStr(user_info, "mother"); - fWeightThreshold = DictGetBool(user_info, "weight_threshold"); - fSplittingFactor = DictGetInt(user_info, "splitting_factor"); - fSplitEnteringParticles = DictGetBool(user_info, "split_entering_particles"); - fSplitExitingParticles = DictGetBool(user_info, "split_exiting_particles"); -} - -void GateSurfaceSplittingActor::ActorInitialize() {} - -void GateSurfaceSplittingActor::StartSimulationAction() { - fNbOfKilledParticles = 0; -} - -void GateSurfaceSplittingActor::PreUserTrackingAction(const G4Track *track) { - - fIsFirstStep = true; -} - -void GateSurfaceSplittingActor::SteppingAction(G4Step *step) { - auto track = step->GetTrack(); - auto weight = track->GetWeight(); - - if (weight >= fWeightThreshold) { - if (fSplitEnteringParticles) { - G4String logicalVolumeNamePreStep = step->GetPreStepPoint() - ->GetPhysicalVolume() - ->GetLogicalVolume() - ->GetName(); - if (((fIsFirstStep) && (step->GetPreStepPoint()->GetStepStatus() == 1) && - (logicalVolumeNamePreStep == fMotherVolumeName)) || - ((fIsFirstStep) && - (track->GetLogicalVolumeAtVertex()->GetName() != - logicalVolumeNamePreStep) && - (track->GetLogicalVolumeAtVertex()->GetName() != - fMotherVolumeName))) { - G4ThreeVector position = step->GetPreStepPoint()->GetPosition(); - G4ThreeVector momentum = step->GetPreStepPoint()->GetMomentum(); - G4double ekin = step->GetPreStepPoint()->GetKineticEnergy(); - - const G4DynamicParticle *particleType = track->GetDynamicParticle(); - G4double time = step->GetPreStepPoint()->GetGlobalTime(); - G4TrackVector *trackVector = step->GetfSecondary(); - - for (int i = 0; i < fSplittingFactor - 1; i++) { - G4DynamicParticle *particleTypeToAdd = - new G4DynamicParticle(*particleType); - G4Track *clone = new G4Track(particleTypeToAdd, time, position); - clone->SetKineticEnergy(ekin); - clone->SetMomentumDirection(momentum); - clone->SetWeight(weight / fSplittingFactor); - trackVector->push_back(clone); - } - step->GetPreStepPoint()->SetWeight(weight / fSplittingFactor); - step->GetPostStepPoint()->SetWeight(weight / fSplittingFactor); - track->SetWeight(weight / fSplittingFactor); - } - } - - if (fSplitExitingParticles) { - G4String logicalVolumeNamePostStep = step->GetPostStepPoint() - ->GetPhysicalVolume() - ->GetLogicalVolume() - ->GetName(); - if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), - logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { - G4TrackVector *trackVector = step->GetfSecondary(); - for (int i = 0; i < fSplittingFactor - 1; i++) { - G4Track *clone = new G4Track(*track); - clone->SetWeight(weight / fSplittingFactor); - trackVector->push_back(clone); - } - step->GetPostStepPoint()->SetWeight(weight / fSplittingFactor); - track->SetWeight(weight / fSplittingFactor); - } - } - } - fIsFirstStep = false; -} diff --git a/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.h b/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.h deleted file mode 100644 index 6adce44d1..000000000 --- a/core/opengate_core/opengate_lib/GateSurfaceSplittingActor.h +++ /dev/null @@ -1,43 +0,0 @@ -/* -------------------------------------------------- - Copyright (C): OpenGATE Collaboration - This software is distributed under the terms - of the GNU Lesser General Public Licence (LGPL) - See LICENSE.md for further details - -------------------------------------------------- */ - -#ifndef GateSurfaceSplittingActor_h -#define GateSurfaceSplittingActor_h - -#include "G4Cache.hh" -#include "GateVActor.h" -#include <pybind11/stl.h> - -namespace py = pybind11; - -class GateSurfaceSplittingActor : public GateVActor { - -public: - // Constructor - GateSurfaceSplittingActor(py::dict &user_info); - - void ActorInitialize() override; - - void StartSimulationAction() override; - - // Main function called every step in attached volume - void SteppingAction(G4Step *) override; - - void PreUserTrackingAction(const G4Track *) override; - - G4bool fSplitEnteringParticles = false; - G4bool fSplitExitingParticles = false; - G4int fSplittingFactor; - G4bool fIsFirstStep; - G4bool fWeightThreshold; - G4String fMotherVolumeName; - std::vector<std::string> fListOfVolumeAncestor; - - long fNbOfKilledParticles{}; -}; - -#endif diff --git a/core/opengate_core/opengate_lib/pyGateSurfaceSplittingActor.cpp b/core/opengate_core/opengate_lib/pyGateSurfaceSplittingActor.cpp deleted file mode 100644 index 0d6fc94e9..000000000 --- a/core/opengate_core/opengate_lib/pyGateSurfaceSplittingActor.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* -------------------------------------------------- - Copyright (C): OpenGATE Collaboration - This software is distributed under the terms - of the GNU Lesser General Public Licence (LGPL) - See LICENSE.md for further details - -------------------------------------------------- */ - -#include <pybind11/pybind11.h> - -namespace py = pybind11; - -#include "GateSurfaceSplittingActor.h" - -void init_GateSurfaceSplittingActor(py::module &m) { - py::class_<GateSurfaceSplittingActor, - std::unique_ptr<GateSurfaceSplittingActor, py::nodelete>, - GateVActor>(m, "GateSurfaceSplittingActor") - .def(py::init<py::dict &>()) - .def_readwrite("fListOfVolumeAncestor", - &GateSurfaceSplittingActor::fListOfVolumeAncestor) - .def(py::init<py::dict &>()); -} From 4e1d204480beeecababf79d9694c8ed343a26d4f Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Wed, 13 Nov 2024 14:31:26 +0100 Subject: [PATCH 48/82] Code adaptation for gate10.09 --- core/opengate_core/opengate_core.cpp | 3 - ...ateLastVertexInteractionSplittingActor.cpp | 42 ++++----- .../GateLastVertexInteractionSplittingActor.h | 2 +- opengate/actors/miscactors.py | 93 ++++++++++++++----- opengate/managers.py | 2 + 5 files changed, 93 insertions(+), 49 deletions(-) diff --git a/core/opengate_core/opengate_core.cpp b/core/opengate_core/opengate_core.cpp index 7bcdb6cf7..222ba8c26 100644 --- a/core/opengate_core/opengate_core.cpp +++ b/core/opengate_core/opengate_core.cpp @@ -309,8 +309,6 @@ void init_GateARFTrainingDatasetActor(py::module &m); void init_GateKillActor(py::module &); -void init_GateSurfaceSplittingActor(py::module &); - void init_itk_image(py::module &); void init_GateImageNestedParameterisation(py::module &); @@ -594,7 +592,6 @@ PYBIND11_MODULE(opengate_core, m) { init_GateARFActor(m); init_GateARFTrainingDatasetActor(m); init_GateKillActor(m); - init_GateSurfaceSplittingActor(m); init_GateDigiAttributeManager(m); init_GateVDigiAttribute(m); init_GateExceptionHandler(m); diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index 0bf43bac9..3d4e345e0 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -56,21 +56,21 @@ GateLastVertexInteractionSplittingActor:: GateLastVertexInteractionSplittingActor(py::dict &user_info) : GateVActor(user_info, false) { - fMotherVolumeName = DictGetStr(user_info, "mother"); - fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); - fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); - fAngularKill = DictGetBool(user_info, "angular_kill"); - fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); - fMaxTheta = DictGetDouble(user_info, "max_theta"); - fBatchSize = DictGetDouble(user_info, "batch_size"); - fActions.insert("StartSimulationAction"); - fActions.insert("SteppingAction"); - fActions.insert("BeginOfEventAction"); - fActions.insert("BeginOfRunAction"); - fActions.insert("PreUserTrackingAction"); - fActions.insert("PostUserTrackingAction"); - fActions.insert("EndOfEventAction"); + + +} + + +void GateLastVertexInteractionSplittingActor::InitializeUserInput(py::dict &user_info) { +GateVActor::InitializeUserInput(user_info); +fMotherVolumeName = DictGetStr(user_info, "attached_to"); +fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); +fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); +fAngularKill = DictGetBool(user_info, "angular_kill"); +fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); +fMaxTheta = DictGetDouble(user_info, "max_theta"); +fBatchSize = DictGetDouble(user_info, "batch_size"); } //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... @@ -500,12 +500,15 @@ G4bool GateLastVertexInteractionSplittingActor::IsTheParticleUndergoesALossEnerg } -void GateLastVertexInteractionSplittingActor::StartSimulationAction (){ + +void GateLastVertexInteractionSplittingActor::BeginOfRunAction( + const G4Run *run) { + fListOfProcessesAccordingParticles["gamma"] = {"compt","phot","conv"}; fListOfProcessesAccordingParticles["e-"] = {"eBrem","eIoni","msc"}; fListOfProcessesAccordingParticles["e+"] = {"eBrem","eIoni","msc","annihil"}; - + std::cout<<fMotherVolumeName<<std::endl; G4LogicalVolume *biasingVolume = G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); fListOfBiasedVolume.push_back(biasingVolume->GetName()); CreateListOfbiasedVolume(biasingVolume); @@ -514,14 +517,7 @@ void GateLastVertexInteractionSplittingActor::StartSimulationAction (){ fVertexSource = (GateLastVertexSource* ) source; fCosMaxTheta = std::cos(fMaxTheta); - std::cout<<"batch size "<<fBatchSize<<std::endl; fStackManager = G4EventManager::GetEventManager()->GetStackManager(); - -} - - -void GateLastVertexInteractionSplittingActor::BeginOfRunAction( - const G4Run *run) { if (fRotationVectorDirector) { G4VPhysicalVolume *physBiasingVolume = diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h index f290c08b8..dfe0ee850 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h @@ -107,7 +107,7 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { std::vector<G4String> fListOfProcesses = {"compt", "annihil", "eBrem", "conv", "phot"}; - virtual void StartSimulationAction() override; + void InitializeUserInput(py::dict &user_info) override; virtual void SteppingAction(G4Step *) override; virtual void BeginOfEventAction(const G4Event *) override; virtual void EndOfEventAction(const G4Event *) override; diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 588a9442d..4e588a9e5 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -409,38 +409,86 @@ def initialize(self): class LastVertexInteractionSplittingActor(ActorBase,g4.GateLastVertexInteractionSplittingActor): - type_name = "LastVertexInteractionSplittingActor" - - def set_default_user_info(user_info): - ActorBase.set_default_user_info(user_info) - deg = g4_units.deg - user_info.splitting_factor = 1 - user_info.angular_kill = False - user_info.rotation_vector_director = False - user_info.vector_director = [0, 0, -1] - user_info.max_theta = 90 * deg - user_info.list_of_volume_name = [] - user_info.batch_size = 1 - def __init__(self, user_info): - ActorBase.__init__(self, user_info) - g4.GateLastVertexInteractionSplittingActor.__init__(self, user_info.__dict__) - self.list_of_volume_name = user_info.list_of_volume_name - self.user_info.mother = user_info.mother - - def initialize(self, volume_engine=None): + """This splitting actor proposes an interaction splitting at the last particle vertex before the exit + of the biased volume. This actor can be usefull for application where collimation are important, + such as in medical LINAC (Linear Accelerator) simulations or radiation shielding. + """ + + user_info_defaults = { + "splitting_factor": ( + 1, + { + "doc": "Defines the number of particles exiting at each split process. Unlike other split actors, this splitting factor counts particles that actually exit, not just those generated.", + }, + ), + + "angular_kill": ( + False, + { + "doc": "If enabled, particles with momentum outside a specified angular range are killed.", + }, + ), + + "max_theta": ( + 90 * g4_units.deg, + { + "doc": "Defines the maximum angle (in degrees) from a central axis within which particles are retained. Particles with momentum beyond this angle are removed. The angular range spans from 0 to max_theta, measured from the vector_director", + }, + ), - super().initialize(volume_engine) + + "vector_director": ( + [0, 0, 1], + { + "doc": "Specifies the reference direction vector from which max_theta is measured. Particlesโ angular range is calculated based on this direction.", + }, + ), + "rotation_vector_director": ( + False, + { + "doc": "If enabled, the vector_director rotates in alignment with any rotation applied to the biased volume attached to this actor.", + }, + ), + "batch_size": ( + 1, + { + "doc": "Defines a batch of number of processes to regenerate, calculated as batch_size * splitting_factor. The optimal value depends on the collimation setup; for example, a batch_size of 10 works well for LINAC head configurations.", + }, + ), + + + } + + + def __init__(self, *args, **kwargs): + ActorBase.__init__(self, *args, **kwargs) + self.__initcpp__() + self.list_of_volume_name = [] + + def __initcpp__(self): + g4.GateLastVertexInteractionSplittingActor.__init__(self, {"name": self.name}) + self.AddActions({"BeginOfRunAction", + "BeginOfEventAction", + "PreUserTrackingAction", + "SteppingAction", + "PostUserTrackingAction", + "EndOfEventAction"}) + + + def initialize(self): + ActorBase.initialize(self) + self.InitializeUserInput(self.user_info) + self.InitializeCpp() volume_tree = self.simulation.volume_manager.get_volume_tree() dico_of_volume_tree = {} for pre, _, node in RenderTree(volume_tree): dico_of_volume_tree[str(node.name)] = node - volume_name = self.user_info.mother + volume_name = self.user_info.attached_to while volume_name != "world": node = dico_of_volume_tree[volume_name] volume_name = node.mother self.list_of_volume_name.append(volume_name) self.fListOfVolumeAncestor = self.list_of_volume_name - print(self.fListOfVolumeAncestor) class BremSplittingActor(SplittingActorBase, g4.GateBOptrBremSplittingActor): # hints for IDE @@ -473,6 +521,7 @@ def initialize(self): process_cls(ActorOutputStatisticsActor) process_cls(SimulationStatisticsActor) process_cls(KillActor) +process_cls(LastVertexInteractionSplittingActor) process_cls(SplittingActorBase) process_cls(ComptSplittingActor) process_cls(BremSplittingActor) diff --git a/opengate/managers.py b/opengate/managers.py index 0627bcc02..9f85c4a7c 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -73,6 +73,7 @@ from .actors.miscactors import ( SimulationStatisticsActor, KillActor, + LastVertexInteractionSplittingActor, SplittingActorBase, ComptSplittingActor, BremSplittingActor, @@ -118,6 +119,7 @@ "DigitizerEnergyWindowsActor": DigitizerEnergyWindowsActor, "DigitizerHitsCollectionActor": DigitizerHitsCollectionActor, "PhaseSpaceActor": PhaseSpaceActor, + "LastVertexInteractionSplittingActor":LastVertexInteractionSplittingActor, } From 326d94b58e7e662a1e35a8bc29d491b5ed5f2c7a Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Wed, 13 Nov 2024 14:32:40 +0100 Subject: [PATCH 49/82] Test creation for last vertex splitting method --- .../src/test076_last_vertex_splittting.py | 258 ------------------ .../test076_last_vertex_splittting_distrib.py | 234 ---------------- .../src/test084_last_vertex_splittting.py | 210 ++++++++++++++ ...084_last_vertex_splittting_angular_kill.py | 172 ++++++++++++ 4 files changed, 382 insertions(+), 492 deletions(-) delete mode 100755 opengate/tests/src/test076_last_vertex_splittting.py delete mode 100755 opengate/tests/src/test076_last_vertex_splittting_distrib.py create mode 100755 opengate/tests/src/test084_last_vertex_splittting.py create mode 100755 opengate/tests/src/test084_last_vertex_splittting_angular_kill.py diff --git a/opengate/tests/src/test076_last_vertex_splittting.py b/opengate/tests/src/test076_last_vertex_splittting.py deleted file mode 100755 index 078452349..000000000 --- a/opengate/tests/src/test076_last_vertex_splittting.py +++ /dev/null @@ -1,258 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import opengate as gate -import uproot -import numpy as np -from scipy.spatial.transform import Rotation -from opengate.tests import utility - - -def bool_validation_test(dico_parameters, tol): - keys = dico_parameters.keys() - liste_diff_max = [] - for key in keys: - liste_diff_max.append(np.max(dico_parameters[key])) - liste_diff_max = np.asarray(liste_diff_max) - max_diff = np.max(liste_diff_max) - print( - "Maximal error (mean or std dev) measured between the analog and the biased simulation:", - np.round(max_diff, 2), - "%", - ) - if max_diff <= 100 * tol: - return True - else: - return False - - -def validation_test(arr_ref, arr_data, nb_split, tol=0.2, tol_weights=0.04): - arr_ref = arr_ref[ - (arr_ref["TrackCreatorProcess"] == "compt") - | (arr_ref["TrackCreatorProcess"] == "none") - ] - arr_data = arr_data[ - (arr_data["TrackCreatorProcess"] != "phot") - & (arr_data["TrackCreatorProcess"] != "eBrem") - & (arr_data["TrackCreatorProcess"] != "eIoni") - ] - - EventID = arr_data["EventID"] - weights = arr_data["Weight"][EventID == EventID[0]] - val_weights = np.round(weights[0], 4) - bool_val_weights = 1 / nb_split == val_weights - print( - "Sum of electron and photon weights for the first event simulated:", - np.round(np.sum(weights), 2), - ) - print("Len of the weights vector for the first event:", len(weights)) - condition_weights = np.round(np.sum(weights), 4) > 2 * ( - 1 - tol_weights - ) and np.round(np.sum(weights), 4) < 2 * (1 + tol_weights) - condition_len = len(weights) > 2 * nb_split * (1 - tol_weights) and len( - weights - ) < 2 * nb_split * (1 + tol_weights) - bool_weights = condition_weights and condition_len - keys = ["KineticEnergy", "PreDirection_X", "PreDirection_Y", "PreDirection_Z"] - - arr_ref_phot = arr_ref[arr_ref["ParticleName"] == "gamma"] - arr_ref_elec = arr_ref[arr_ref["ParticleName"] == "e-"] - - arr_data_phot = arr_data[arr_data["ParticleName"] == "gamma"] - arr_data_elec = arr_data[arr_data["ParticleName"] == "e-"] - - keys_dico = ["ref", "data"] - dico_arr_phot = {} - dico_arr_elec = {} - - dico_arr_phot["ref"] = arr_ref_phot - dico_arr_phot["data"] = arr_data_phot - - dico_arr_elec["ref"] = arr_ref_elec - dico_arr_elec["data"] = arr_data_elec - dico_comp_data = {} - - for key in keys: - arr_data = [] - for key_dico in keys_dico: - mean_elec = np.mean(dico_arr_phot[key_dico][key]) - mean_phot = np.mean(dico_arr_elec[key_dico][key]) - std_elec = np.std(dico_arr_phot[key_dico][key]) - std_phot = np.std(dico_arr_elec[key_dico][key]) - arr_data += [mean_elec, mean_phot, std_elec, std_phot] - dico_comp_data[key] = 100 * np.abs( - np.array( - [ - (arr_data[0] - arr_data[4]) / arr_data[0], - (arr_data[1] - arr_data[5]) / arr_data[1], - (arr_data[2] - arr_data[6]) / arr_data[6], - (arr_data[3] - arr_data[7]) / arr_data[3], - ] - ) - ) - bool_test = bool_validation_test(dico_comp_data, tol) - bool_tot = bool_test and bool_weights and bool_val_weights - return bool_tot - - -if __name__ == "__main__": - paths = utility.get_default_test_paths( - __file__, "test076_last_vertex_splitting", output_folder="test076" - ) - - # create the simulation - sim = gate.Simulation() - - # main options - ui = sim.user_info - ui.g4_verbose = False - ui.visu = True - ui.visu_type = "vrml" - ui.check_volumes_overlap = False - # ui.running_verbose_level = gate.logger.EVENT - ui.number_of_threads = 1 - # 1236566 seg fault - #12745 double compt - # ui.random_seed = 73 - # ui.random_seed = 2632 - # ui.random_seed = 45 - ui.random_seed = 444 - - # units - m = gate.g4_units.m - km = gate.g4_units.km - mm = gate.g4_units.mm - um = gate.g4_units.um - cm = gate.g4_units.cm - nm = gate.g4_units.nm - Bq = gate.g4_units.Bq - MeV = gate.g4_units.MeV - keV = gate.g4_units.keV - deg = gate.g4_units.deg - gcm3 = gate.g4_units.g / gate.g4_units.cm3 - - # adapt world size - world = sim.world - world.size = [0.25 * m, 0.25 * m, 0.25 * m] - world.material = "G4_Galactic" - - ####### GEOMETRY TO IRRADIATE ############# - sim.volume_manager.material_database.add_material_weights( - "Tungsten", - ["W"], - [1], - 19.3 * gcm3, - ) - - W_tubs = sim.add_volume("Tubs", "W_box") - W_tubs.material = "Tungsten" - W_tubs.mother = world.name - - W_tubs.rmin = 0 - W_tubs.rmax = 0.4*cm - W_tubs.dz = 0.05 * m - W_tubs.color = [0.8, 0.2, 0.1, 1] - angle_x = 45 - angle_y = 70 - angle_z = 80 - - rotation = Rotation.from_euler( - "xyz", [angle_y, angle_y, angle_z], degrees=True - ).as_matrix() - W_tubs.rotation = rotation - bias = True - - if bias : - ###### Last vertex Splitting ACTOR ######### - nb_split = 25 - vertex_splitting_actor = sim.add_actor("LastVertexInteractionSplittingActor", "vertexSplittingW") - vertex_splitting_actor.mother = W_tubs.name - vertex_splitting_actor.splitting_factor = nb_split - vertex_splitting_actor.angular_kill = True - vertex_splitting_actor.max_theta = 15*deg - - ##### PHASE SPACE plan ######" - # plan_tubs = sim.add_volume("Tubs", "phsp_tubs") - # plan_tubs.material = "G4_Galactic" - # plan_tubs.mother = world.name - # plan_tubs.rmin = W_tubs.rmax + 1*cm - # plan_tubs.rmax = plan_tubs.rmin + 1 * nm - # plan_tubs.dz = 0.05 * m - # plan_tubs.color = [0.2, 1, 0.8, 1] - # plan_tubs.rotation = rotation - - plan = sim.add_volume("Box", "plan_phsp") - plan.material = "G4_Galactic" - plan.size = [5*cm,5*cm,1*nm] - plan.translation = [0,0,-1*cm] - - - ####### gamma source ########### - source = sim.add_source("GenericSource", "source1") - source.particle = "gamma" - if bias : - source.n = 100 - else : - source.n = 10000000 - source.position.type = "sphere" - source.position.radius = 1 * nm - source.direction.type = "momentum" - # source.direction.momentum = [0,0,-1] - source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) - source.energy.type = "mono" - source.energy.mono = 4 * MeV - # - ###### LastVertexSource ############# - if bias : - source_0 = sim.add_source("LastVertexSource", "source_vertex") - source_0.n = 1 - - ####### PHASE SPACE ACTOR ############## - - phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") - phsp_actor.mother = plan.name - phsp_actor.attributes = [ - "EventID", - "TrackID", - "Weight", - "ParticleName", - "KineticEnergy", - "PreDirection", - "PrePosition", - "TrackCreatorProcess", - ] - if bias : - phsp_actor.output = paths.output / ("test076_output_data_last_vertex_osef.root") - else : - phsp_actor.output = paths.output / ("test076_output_data_last_vertex_ref.root") - - - s = sim.add_actor("SimulationStatisticsActor", "Stats") - s.track_types_flag = True - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" - #### Extremely important, it seems that GEANT4, for almost all physics lists, encompass all the photon processes in GammaGeneralProc - #### Therefore if we provide the name of the real process (here compt) without deactivating GammaGeneralProcess, it will not find the - #### process to bias and the biasing will fail - s = f"/process/em/UseGeneralProcess false" - sim.add_g4_command_before_init(s) - - sim.physics_manager.global_production_cuts.gamma = 1 * mm - sim.physics_manager.global_production_cuts.electron = 1000 * km - sim.physics_manager.global_production_cuts.positron = 1000 * km - - output = sim.run() - - # - # print results - stats = sim.output.get_actor("Stats") - h = sim.output.get_actor("PhaseSpace") - print(stats) - # - # f_data = uproot.open(paths.output / "test071_output_data.root") - # f_ref_data = uproot.open(paths.data / "test071_ref_data.root") - # arr_data = f_data["PhaseSpace"].arrays() - # arr_ref_data = f_ref_data["PhaseSpace"].arrays() - # # - # is_ok = validation_test(arr_ref_data, arr_data, nb_split) - # utility.test_ok(is_ok) - diff --git a/opengate/tests/src/test076_last_vertex_splittting_distrib.py b/opengate/tests/src/test076_last_vertex_splittting_distrib.py deleted file mode 100755 index 087ffeb5b..000000000 --- a/opengate/tests/src/test076_last_vertex_splittting_distrib.py +++ /dev/null @@ -1,234 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import opengate as gate -import uproot -import numpy as np -from scipy.spatial.transform import Rotation -from opengate.tests import utility - - -def bool_validation_test(dico_parameters, tol): - keys = dico_parameters.keys() - liste_diff_max = [] - for key in keys: - liste_diff_max.append(np.max(dico_parameters[key])) - liste_diff_max = np.asarray(liste_diff_max) - max_diff = np.max(liste_diff_max) - print( - "Maximal error (mean or std dev) measured between the analog and the biased simulation:", - np.round(max_diff, 2), - "%", - ) - if max_diff <= 100 * tol: - return True - else: - return False - - -def validation_test(arr_ref, arr_data, nb_split, tol=0.2, tol_weights=0.04): - arr_ref = arr_ref[ - (arr_ref["TrackCreatorProcess"] == "compt") - | (arr_ref["TrackCreatorProcess"] == "none") - ] - arr_data = arr_data[ - (arr_data["TrackCreatorProcess"] != "phot") - & (arr_data["TrackCreatorProcess"] != "eBrem") - & (arr_data["TrackCreatorProcess"] != "eIoni") - ] - - EventID = arr_data["EventID"] - weights = arr_data["Weight"][EventID == EventID[0]] - val_weights = np.round(weights[0], 4) - bool_val_weights = 1 / nb_split == val_weights - print( - "Sum of electron and photon weights for the first event simulated:", - np.round(np.sum(weights), 2), - ) - print("Len of the weights vector for the first event:", len(weights)) - condition_weights = np.round(np.sum(weights), 4) > 2 * ( - 1 - tol_weights - ) and np.round(np.sum(weights), 4) < 2 * (1 + tol_weights) - condition_len = len(weights) > 2 * nb_split * (1 - tol_weights) and len( - weights - ) < 2 * nb_split * (1 + tol_weights) - bool_weights = condition_weights and condition_len - keys = ["KineticEnergy", "PreDirection_X", "PreDirection_Y", "PreDirection_Z"] - - arr_ref_phot = arr_ref[arr_ref["ParticleName"] == "gamma"] - arr_ref_elec = arr_ref[arr_ref["ParticleName"] == "e-"] - - arr_data_phot = arr_data[arr_data["ParticleName"] == "gamma"] - arr_data_elec = arr_data[arr_data["ParticleName"] == "e-"] - - keys_dico = ["ref", "data"] - dico_arr_phot = {} - dico_arr_elec = {} - - dico_arr_phot["ref"] = arr_ref_phot - dico_arr_phot["data"] = arr_data_phot - - dico_arr_elec["ref"] = arr_ref_elec - dico_arr_elec["data"] = arr_data_elec - dico_comp_data = {} - - for key in keys: - arr_data = [] - for key_dico in keys_dico: - mean_elec = np.mean(dico_arr_phot[key_dico][key]) - mean_phot = np.mean(dico_arr_elec[key_dico][key]) - std_elec = np.std(dico_arr_phot[key_dico][key]) - std_phot = np.std(dico_arr_elec[key_dico][key]) - arr_data += [mean_elec, mean_phot, std_elec, std_phot] - dico_comp_data[key] = 100 * np.abs( - np.array( - [ - (arr_data[0] - arr_data[4]) / arr_data[0], - (arr_data[1] - arr_data[5]) / arr_data[1], - (arr_data[2] - arr_data[6]) / arr_data[6], - (arr_data[3] - arr_data[7]) / arr_data[3], - ] - ) - ) - bool_test = bool_validation_test(dico_comp_data, tol) - bool_tot = bool_test and bool_weights and bool_val_weights - return bool_tot - - -if __name__ == "__main__": - paths = utility.get_default_test_paths( - __file__, "test076_last_vertex_splitting", output_folder="test076" - ) - for simu_ID in range(1,2) : - # create the simulation - sim = gate.Simulation() - - # main options - ui = sim.user_info - ui.g4_verbose = False - # ui.visu = True - ui.visu_type = "vrml" - ui.check_volumes_overlap = False - # ui.running_verbose_level = gate.logger.EVENT - ui.number_of_threads = 1 - # 1236566 seg fault - ui.random_seed = 1234567 - - # units - m = gate.g4_units.m - km = gate.g4_units.km - mm = gate.g4_units.mm - um = gate.g4_units.um - cm = gate.g4_units.cm - nm = gate.g4_units.nm - Bq = gate.g4_units.Bq - MeV = gate.g4_units.MeV - keV = gate.g4_units.keV - deg = gate.g4_units.deg - gcm3 = gate.g4_units.g / gate.g4_units.cm3 - - # adapt world size - world = sim.world - world.size = [0.25 * m, 0.25 * m, 1.5 * m] - world.material = "G4_Galactic" - - ####### GEOMETRY TO IRRADIATE ############# - sim.volume_manager.material_database.add_material_weights( - "Tungsten", - ["W"], - [1], - 19.3 * gcm3, - ) - - W_tubs = sim.add_volume("Tubs", "W_box") - W_tubs.material = "Tungsten" - W_tubs.mother = world.name - - W_tubs.rmin = 0 - W_tubs.rmax = 1* cm - W_tubs.dz = 0.5 * m - W_tubs.color = [0.8, 0.2, 0.1, 1] - angle_x = 0 - angle_y = 0 - angle_z = 0 - - rotation = Rotation.from_euler( - "xyz", [angle_y, angle_y, angle_z], degrees=True - ).as_matrix() - W_tubs.rotation = rotation - if simu_ID == 1 : - ####### Last vertex splitting ACTOR ######### - nb_split = 1 - vertex_splitting_actor = sim.add_actor("LastVertexInteractionSplittingActor", "vertexSplittingW") - vertex_splitting_actor.mother = W_tubs.name - vertex_splitting_actor.splitting_factor = nb_split - vertex_splitting_actor.russian_roulette_for_angle = False - - ##### PHASE SPACE plan ######" - plan_tubs = sim.add_volume("Tubs", "phsp_tubs") - plan_tubs.material = "G4_Galactic" - plan_tubs.mother = world.name - plan_tubs.rmin = W_tubs.rmax + 1*cm - plan_tubs.rmax = plan_tubs.rmin + 1 * nm - plan_tubs.dz = 0.05 * m - plan_tubs.color = [0.2, 1, 0.8, 1] - plan_tubs.rotation = rotation - - ####### Electron source ########### - source = sim.add_source("GenericSource", "source1") - source.particle = "gamma" - source.n = 1000000 - if simu_ID == 1 : - source.n = source.n/(nb_split) - source.position.type = "sphere" - source.position.radius = 1 * nm - source.direction.type = "momentum" - # source.direction.momentum = [0,0,-1] - source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) - source.energy.type = "mono" - source.position.translation = [0.4*cm,0.4*cm,0] - source.force_rotation=True - source.energy.mono = 4* MeV - - ####### PHASE SPACE ACTOR ############## - - phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") - phsp_actor.mother = plan_tubs.name - phsp_actor.attributes = [ - "EventID", - "TrackID", - "Weight", - "ParticleName", - "KineticEnergy", - "PreDirection", - "TrackCreatorProcess", - ] - if simu_ID == 0: - phsp_actor.output = paths.output / "test076_output_data_ref.root" - else : - phsp_actor.output = paths.output / "test076_output_data_split.root" - - ##### MODIFIED PHYSICS LIST ############### - - s = sim.add_actor("SimulationStatisticsActor", "Stats") - s.track_types_flag = True - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option2" - #### Extremely important, it seems that GEANT4, for almost all physics lists, encompass all the photon processes in GammaGeneralProc - #### Therefore if we provide the name of the real process (here compt) without deactivating GammaGeneralProcess, it will not find the - #### process to bias and the biasing will fail - s = f"/process/em/UseGeneralProcess false" - sim.add_g4_command_before_init(s) - - sim.physics_manager.global_production_cuts.gamma = 1 * mm - sim.physics_manager.global_production_cuts.electron = 1 * m - sim.physics_manager.global_production_cuts.positron = 1 * um - - output = sim.run() - - # - # print results - stats = sim.output.get_actor("Stats") - h = sim.output.get_actor("PhaseSpace") - print(stats) - - diff --git a/opengate/tests/src/test084_last_vertex_splittting.py b/opengate/tests/src/test084_last_vertex_splittting.py new file mode 100755 index 000000000..956520b59 --- /dev/null +++ b/opengate/tests/src/test084_last_vertex_splittting.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +import uproot +import numpy as np +from scipy.spatial.transform import Rotation +from opengate.tests import utility + + + +def validation_test(arr_ref, arr_data, nb_split): + arr_ref = arr_ref[arr_ref["ParticleName"] == "gamma"] + arr_data = arr_data[arr_data["ParticleName"] == "gamma"] + + weight_data = np.round(np.mean(arr_data["Weight"]),4) + bool_weight = False + print("Weight mean is equal to",weight_data, "and need to be equal to",1/nb_split) + if weight_data == 1/nb_split: + bool_weight = True + + + bool_events = False + sigma = np.sqrt((len(arr_data["KineticEnergy"])/nb_split))*nb_split + nb_events_ref = len(arr_ref["KineticEnergy"]) + nb_events_data = len(arr_data["KineticEnergy"]) + print("Reference counts number:",nb_events_ref) + print("Biased counts number:", nb_events_data) + if nb_events_data - 4*sigma <= nb_events_ref <= nb_events_data + 4*sigma: + bool_events =True + + keys = ["KineticEnergy", "PreDirection_X","PreDirection_Y","PreDirection_Z","PrePosition_X","PrePosition_Y"] + + bool_distrib = True + for key in keys : + ref = arr_ref[key] + data = arr_data[key] + + mean_ref = np.mean(ref) + mean_data = np.mean(data) + + std_dev_ref = np.std(ref,ddof =1) + std_dev_data = np.std(data,ddof =1) + + std_err_ref = std_dev_ref/np.sqrt(len(ref)) + std_err_data= std_dev_data/(nb_split * np.sqrt(len(data)/nb_split)) + + print(key,"mean ref value:", np.round(mean_ref,3),"+-",np.round(std_err_ref,3)) + print(key,"mean data value:", np.round(mean_data,3),"+-",np.round(std_err_data,3)) + + + if (mean_data - 4 * np.sqrt(std_err_data**2 + std_err_ref**2) > mean_ref) or (mean_data + 4 * np.sqrt(std_err_data**2 + std_err_ref**2) <mean_ref): + bool_distrib = False + + if bool_distrib and bool_events and bool_weight: + return True + else : + return False + + + + +if __name__ == "__main__": + for i in range(2): + if i == 0 : + bias = False + else : + bias = True + paths = utility.get_default_test_paths( + __file__, "test084_last_vertex_splitting", output_folder="test084" + ) + + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + ui.visu = False + ui.visu_type = "vrml" + ui.check_volumes_overlap = False + ui.number_of_threads = 1 + ui.random_seed = 123456789 + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + um = gate.g4_units.um + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + deg = gate.g4_units.deg + gcm3 = gate.g4_units.g / gate.g4_units.cm3 + + # adapt world size + world = sim.world + world.size = [0.25 * m, 0.25 * m, 0.25 * m] + world.material = "G4_Galactic" + + ####### GEOMETRY TO IRRADIATE ############# + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + + W_tubs = sim.add_volume("Tubs", "W_box") + W_tubs.material = "Tungsten" + W_tubs.mother = world.name + + W_tubs.rmin = 0 + W_tubs.rmax = 0.4*cm + W_tubs.dz = 0.05 * m + W_tubs.color = [0.8, 0.2, 0.1, 1] + angle_x = 45 + angle_y = 70 + angle_z = 80 + + rotation = Rotation.from_euler( + "xyz", [angle_y, angle_y, angle_z], degrees=True + ).as_matrix() + W_tubs.rotation = rotation + + if bias : + ###### Last vertex Splitting ACTOR ######### + nb_split = 10 + vertex_splitting_actor = sim.add_actor("LastVertexInteractionSplittingActor", "vertexSplittingW") + vertex_splitting_actor.attached_to = W_tubs.name + vertex_splitting_actor.splitting_factor = nb_split + vertex_splitting_actor.angular_kill = True + vertex_splitting_actor.vector_director = [0,0,-1] + vertex_splitting_actor.max_theta = 90*deg + vertex_splitting_actor.batch_size = 10 + + + plan = sim.add_volume("Box", "plan_phsp") + plan.material = "G4_Galactic" + plan.size = [5*cm,5*cm,1*nm] + plan.translation = [0,0,-1*cm] + + + ####### gamma source ########### + source = sim.add_source("GenericSource", "source1") + source.particle = "gamma" + source.n = 100000 + if bias : + source.n = source.n/nb_split + + + source.position.type = "sphere" + source.position.radius = 1 * nm + source.direction.type = "momentum" + # source.direction.momentum = [0,0,-1] + source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) + source.energy.type = "mono" + source.energy.mono = 4 * MeV + # + ###### LastVertexSource ############# + if bias : + source_0 = sim.add_source("LastVertexSource", "source_vertex") + source_0.n = 1 + + ####### PHASE SPACE ACTOR ############## + sim.output_dir =paths.output + phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + phsp_actor.attached_to = plan.name + phsp_actor.attributes = [ + "EventID", + "TrackID", + "Weight", + "ParticleName", + "KineticEnergy", + "PreDirection", + "PrePosition", + "TrackCreatorProcess", + ] + if bias : + phsp_actor.output_filename ="test084_output_data_last_vertex_biased.root" + else : + phsp_actor.output_filename = "test084_output_data_last_vertex_ref.root" + + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + s = f"/process/em/UseGeneralProcess false" + sim.g4_commands_before_init.append(s) + + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1000 * km + sim.physics_manager.global_production_cuts.positron = 1000 * km + + output = sim.run(True) + print(s) + + + + + f_data = uproot.open(paths.output / "test084_output_data_last_vertex_biased.root") + f_ref_data = uproot.open(paths.output / "test084_output_data_last_vertex_ref.root") + arr_data = f_data["PhaseSpace"].arrays() + arr_ref_data = f_ref_data["PhaseSpace"].arrays() + # # + is_ok = validation_test(arr_ref_data, arr_data, nb_split) + utility.test_ok(is_ok) + diff --git a/opengate/tests/src/test084_last_vertex_splittting_angular_kill.py b/opengate/tests/src/test084_last_vertex_splittting_angular_kill.py new file mode 100755 index 000000000..c9f058618 --- /dev/null +++ b/opengate/tests/src/test084_last_vertex_splittting_angular_kill.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +import uproot +import numpy as np +from scipy.spatial.transform import Rotation +from opengate.tests import utility + + + +def validation_test(arr_data,vector_director,max_theta): + arr_data = arr_data[arr_data["ParticleName"] == "gamma"] + qt_mvt_data = arr_data[["PreDirection_X","PreDirection_Y","PreDirection_Z"]] + mom_data = np.zeros((len(qt_mvt_data["PreDirection_X"]),3)) + mom_data[:,0] += np.array(qt_mvt_data["PreDirection_X"]) + mom_data[:, 1] += np.array(qt_mvt_data["PreDirection_Y"]) + mom_data[:, 2] += np.array(qt_mvt_data["PreDirection_Z"]) + + l_theta = np.zeros(len(mom_data[:,0])) + + for i in range(len(l_theta)): + theta = np.arccos(mom_data[i].dot(vector_director)) + l_theta[i] = theta + print('Number of particles with an angle higher than max_theta:',len(l_theta[l_theta > max_theta])) + if len(l_theta[l_theta > max_theta]) == 0: + return True + else: + return False + + + +if __name__ == "__main__": + bias = True + paths = utility.get_default_test_paths( + __file__, "test084_last_vertex_splitting", output_folder="test084" + ) + + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + ui.visu = False + ui.visu_type = "vrml" + ui.check_volumes_overlap = False + ui.number_of_threads = 1 + ui.random_seed = 123456789 + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + um = gate.g4_units.um + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + deg = gate.g4_units.deg + gcm3 = gate.g4_units.g / gate.g4_units.cm3 + + # adapt world size + world = sim.world + world.size = [0.25 * m, 0.25 * m, 0.25 * m] + world.material = "G4_Galactic" + + ####### GEOMETRY TO IRRADIATE ############# + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + + W_tubs = sim.add_volume("Tubs", "W_box") + W_tubs.material = "Tungsten" + W_tubs.mother = world.name + + W_tubs.rmin = 0 + W_tubs.rmax = 0.4*cm + W_tubs.dz = 0.05 * m + W_tubs.color = [0.8, 0.2, 0.1, 1] + angle_x = 45 + angle_y = 70 + angle_z = 80 + + rotation = Rotation.from_euler( + "xyz", [angle_y, angle_y, angle_z], degrees=True + ).as_matrix() + W_tubs.rotation = rotation + + if bias : + ###### Last vertex Splitting ACTOR ######### + nb_split = 10 + vertex_splitting_actor = sim.add_actor("LastVertexInteractionSplittingActor", "vertexSplittingW") + vertex_splitting_actor.attached_to = W_tubs.name + vertex_splitting_actor.splitting_factor = nb_split + vertex_splitting_actor.angular_kill = True + vertex_splitting_actor.vector_director = [0,0,-1] + vertex_splitting_actor.max_theta = 15*deg + vertex_splitting_actor.batch_size = 10 + + + plan = sim.add_volume("Box", "plan_phsp") + plan.material = "G4_Galactic" + plan.size = [5*cm,5*cm,1*nm] + plan.translation = [0,0,-1*cm] + + if bias : + vector_director = np.array(vertex_splitting_actor.vector_director) + + + + + ####### gamma source ########### + source = sim.add_source("GenericSource", "source1") + source.particle = "gamma" + source.n = 100000 + if bias : + source.n = source.n/nb_split + + + source.position.type = "sphere" + source.position.radius = 1 * nm + source.direction.type = "momentum" + # source.direction.momentum = [0,0,-1] + source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) + source.energy.type = "mono" + source.energy.mono = 4 * MeV + + ###### LastVertexSource ############# + if bias : + source_0 = sim.add_source("LastVertexSource", "source_vertex") + source_0.n = 1 + + ####### PHASE SPACE ACTOR ############## + sim.output_dir =paths.output + phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + phsp_actor.attached_to = plan.name + phsp_actor.attributes = [ + "EventID", + "TrackID", + "Weight", + "ParticleName", + "KineticEnergy", + "PreDirection", + "PrePosition", + "TrackCreatorProcess", + ] + if bias : + phsp_actor.output_filename ="test084_output_data_last_vertex_angular_kill.root" + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + s = f"/process/em/UseGeneralProcess false" + sim.g4_commands_before_init.append(s) + + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1000 * km + sim.physics_manager.global_production_cuts.positron = 1000 * km + + output = sim.run() + print(s) + + f_data = uproot.open(paths.output / "test084_output_data_last_vertex_angular_kill.root") + arr_data = f_data["PhaseSpace"].arrays() + is_ok = validation_test(arr_data, vector_director,vertex_splitting_actor.max_theta) + utility.test_ok(is_ok) + From 7be2414712c92bb4b21fbfa68383c1921f8c919a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 13 Nov 2024 13:33:53 +0000 Subject: [PATCH 50/82] [pre-commit.ci] Automatic python and c++ formatting --- .../opengate_lib/GateGenericSource.cpp | 4 +- ...ateLastVertexInteractionSplittingActor.cpp | 676 +- .../GateLastVertexInteractionSplittingActor.h | 73 +- .../opengate_lib/GateLastVertexSource.cpp | 59 +- .../opengate_lib/GateLastVertexSource.h | 37 +- .../GateLastVertexSplittingDataContainer.h | 191 +- .../GateLastVertexSplittingPostStepDoIt.h | 123 +- .../GateLastVertexSplittingSimpleContainer.h | 238 +- .../opengate_lib/GateOptnForceFreeFlight.cpp | 8 +- .../GateOptnVGenericSplitting.cpp | 70 +- .../opengate_lib/GateOptnVGenericSplitting.h | 21 +- ...GateOptrComptPseudoTransportationActor.cpp | 80 +- .../GateOptrComptPseudoTransportationActor.h | 7 +- .../opengate_lib/GateSourceManager.cpp | 3 - .../opengate_lib/GateSourceManager.h | 13 +- ...ateLastVertexInteractionSplittingActor.cpp | 5 +- core/opengate_core/opengate_lib/tree.hh | 6222 +++++++++-------- core/opengate_core/opengate_lib/tree_util.hh | 108 +- opengate/actors/miscactors.py | 35 +- opengate/managers.py | 11 +- .../src/test077_kill_interacting_particles.py | 3 - .../src/test084_last_vertex_splittting.py | 110 +- ...084_last_vertex_splittting_angular_kill.py | 63 +- 23 files changed, 4093 insertions(+), 4067 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateGenericSource.cpp b/core/opengate_core/opengate_lib/GateGenericSource.cpp index 74d7e743a..a00707865 100644 --- a/core/opengate_core/opengate_lib/GateGenericSource.cpp +++ b/core/opengate_core/opengate_lib/GateGenericSource.cpp @@ -151,12 +151,12 @@ double GateGenericSource::PrepareNextTime(double current_simulation_time) { if (fMaxN <= 0) { if (fEffectiveEventTime < fStartTime) return fStartTime; - if (fEffectiveEventTime >= fEndTime){ + if (fEffectiveEventTime >= fEndTime) { return -1; } // get next time according to current fActivity double next_time = CalcNextTime(fEffectiveEventTime); - if (next_time >= fEndTime){ + if (next_time >= fEndTime) { return -1; } return next_time; diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index 3d4e345e0..23f24c1d1 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -39,69 +39,70 @@ #include "G4Positron.hh" #include "G4ProcessManager.hh" #include "G4ProcessVector.hh" +#include "G4RunManager.hh" #include "G4TrackStatus.hh" #include "G4TrackingManager.hh" #include "G4VParticleChange.hh" #include "G4eplusAnnihilation.hh" #include "GateLastVertexInteractionSplittingActor.h" +#include "GateLastVertexSource.h" #include "GateLastVertexSplittingPostStepDoIt.h" #include "GateOptnComptSplitting.h" -#include "GateLastVertexSource.h" -#include "G4RunManager.hh" #include <cmath> - //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... GateLastVertexInteractionSplittingActor:: GateLastVertexInteractionSplittingActor(py::dict &user_info) - : GateVActor(user_info, false) { - - - + : GateVActor(user_info, false) {} + +void GateLastVertexInteractionSplittingActor::InitializeUserInput( + py::dict &user_info) { + GateVActor::InitializeUserInput(user_info); + fMotherVolumeName = DictGetStr(user_info, "attached_to"); + fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); + fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); + fAngularKill = DictGetBool(user_info, "angular_kill"); + fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); + fMaxTheta = DictGetDouble(user_info, "max_theta"); + fBatchSize = DictGetDouble(user_info, "batch_size"); } +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -void GateLastVertexInteractionSplittingActor::InitializeUserInput(py::dict &user_info) { -GateVActor::InitializeUserInput(user_info); -fMotherVolumeName = DictGetStr(user_info, "attached_to"); -fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); -fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); -fAngularKill = DictGetBool(user_info, "angular_kill"); -fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); -fMaxTheta = DictGetDouble(user_info, "max_theta"); -fBatchSize = DictGetDouble(user_info, "batch_size"); +void GateLastVertexInteractionSplittingActor::print_tree( + const tree<LastVertexDataContainer> &tr, + tree<LastVertexDataContainer>::pre_order_iterator it, + tree<LastVertexDataContainer>::pre_order_iterator end) { + if (!tr.is_valid(it)) + return; + int rootdepth = tr.depth(it); + std::cout << "-----" << std::endl; + while (it != end) { + for (int i = 0; i < tr.depth(it) - rootdepth; ++i) + std::cout << " "; + std::cout << (*it) << std::endl << std::flush; + ++it; + } + std::cout << "-----" << std::endl; } -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -void GateLastVertexInteractionSplittingActor::print_tree(const tree<LastVertexDataContainer>& tr, tree<LastVertexDataContainer>::pre_order_iterator it, tree<LastVertexDataContainer>::pre_order_iterator end) - { - if(!tr.is_valid(it)) return; - int rootdepth=tr.depth(it); - std::cout << "-----" << std::endl; - while(it!=end) { - for(int i=0; i<tr.depth(it)-rootdepth; ++i) - std::cout << " "; - std::cout << (*it) << std::endl << std::flush; - ++it; - } - std::cout << "-----" << std::endl; - } - -G4bool GateLastVertexInteractionSplittingActor::DoesParticleEmittedInSolidAngle(G4ThreeVector dir, G4ThreeVector vectorDirector) { +G4bool GateLastVertexInteractionSplittingActor::DoesParticleEmittedInSolidAngle( + G4ThreeVector dir, G4ThreeVector vectorDirector) { G4double cosTheta = vectorDirector * dir; if (cosTheta < fCosMaxTheta) return false; return true; } -G4VProcess* GateLastVertexInteractionSplittingActor::GetProcessFromProcessName(G4String particleName, G4String pName){ - auto *particle_table = G4ParticleTable::GetParticleTable(); - G4ParticleDefinition *particleDefinition = particle_table->FindParticle(particleName); +G4VProcess *GateLastVertexInteractionSplittingActor::GetProcessFromProcessName( + G4String particleName, G4String pName) { + auto *particle_table = G4ParticleTable::GetParticleTable(); + G4ParticleDefinition *particleDefinition = + particle_table->FindParticle(particleName); G4ProcessManager *processManager = particleDefinition->GetProcessManager(); G4ProcessVector *processList = processManager->GetProcessList(); - G4VProcess* nullProcess = nullptr; + G4VProcess *nullProcess = nullptr; for (size_t i = 0; i < processList->size(); ++i) { auto process = (*processList)[i]; if (process->GetProcessName() == pName) { @@ -109,49 +110,49 @@ G4VProcess* GateLastVertexInteractionSplittingActor::GetProcessFromProcessName(G } } return nullProcess; - } -G4Track* GateLastVertexInteractionSplittingActor::CreateATrackFromContainer(LastVertexDataContainer theContainer){ +G4Track *GateLastVertexInteractionSplittingActor::CreateATrackFromContainer( + LastVertexDataContainer theContainer) { auto *particle_table = G4ParticleTable::GetParticleTable(); SimpleContainer container = theContainer.GetContainerToSplit(); - if (container.GetParticleNameToSplit() != "None"){ - G4ParticleDefinition *particleDefinition = particle_table->FindParticle(container.GetParticleNameToSplit()); + if (container.GetParticleNameToSplit() != "None") { + G4ParticleDefinition *particleDefinition = + particle_table->FindParticle(container.GetParticleNameToSplit()); G4ThreeVector momentum = container.GetMomentum(); G4double energy = container.GetEnergy(); - if (energy <0){ + if (energy < 0) { energy = 0; - momentum = {0,0,0}; + momentum = {0, 0, 0}; } G4int trackStatus = container.GetTrackStatus(); G4ThreeVector position = container.GetVertexPosition(); G4ThreeVector polarization = container.GetPolarization(); - G4DynamicParticle* dynamicParticle = new G4DynamicParticle(particleDefinition,momentum,energy); + G4DynamicParticle *dynamicParticle = + new G4DynamicParticle(particleDefinition, momentum, energy); G4double time = 0; - G4Track* aTrack = new G4Track(dynamicParticle,time, position); + G4Track *aTrack = new G4Track(dynamicParticle, time, position); aTrack->SetPolarization(polarization); - if (trackStatus == 0){ + if (trackStatus == 0) { aTrack->SetTrackStatus(fAlive); } - if (trackStatus == 1){ + if (trackStatus == 1) { aTrack->SetTrackStatus(fStopButAlive); } - if ((trackStatus == 2) || (trackStatus == 3)){ + if ((trackStatus == 2) || (trackStatus == 3)) { aTrack->SetTrackStatus(fAlive); } aTrack->SetWeight(container.GetWeight()); return aTrack; } - - return nullptr; - + return nullptr; } +G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack( + G4ParticleChangeForGamma *gammaProcess, G4Track track, G4double weight) { -G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack(G4ParticleChangeForGamma *gammaProcess, G4Track track, G4double weight) { - G4double energy = gammaProcess->GetProposedKineticEnergy(); G4double globalTime = track.GetGlobalTime(); G4ThreeVector polarization = gammaProcess->GetProposedPolarization(); @@ -167,56 +168,68 @@ G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack(G4ParticleC return newTrack; } -void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process, LastVertexDataContainer container, G4double batchSize) { +void GateLastVertexInteractionSplittingActor::ComptonSplitting( + G4Step *initStep, G4Step *CurrentStep, G4VProcess *process, + LastVertexDataContainer container, G4double batchSize) { - //G4TrackVector *trackVector = CurrentStep->GetfSecondary(); + // G4TrackVector *trackVector = CurrentStep->GetfSecondary(); GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; - for (int i = 0; i < batchSize; i++){ - G4VParticleChange *processFinalState = emProcess->PostStepDoIt(*fTrackToSplit, *initStep); - G4ParticleChangeForGamma *gammaProcessFinalState = (G4ParticleChangeForGamma *)processFinalState; + for (int i = 0; i < batchSize; i++) { + G4VParticleChange *processFinalState = + emProcess->PostStepDoIt(*fTrackToSplit, *initStep); + G4ParticleChangeForGamma *gammaProcessFinalState = + (G4ParticleChangeForGamma *)processFinalState; - G4ThreeVector momentum = gammaProcessFinalState->GetProposedMomentumDirection(); + G4ThreeVector momentum = + gammaProcessFinalState->GetProposedMomentumDirection(); - G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, *fTrackToSplit, fWeight); + G4Track *newTrack = + CreateComptonTrack(gammaProcessFinalState, *fTrackToSplit, fWeight); - if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(newTrack->GetMomentumDirection(),fVectorDirector) == false)){ + if ((fAngularKill) && + (DoesParticleEmittedInSolidAngle(newTrack->GetMomentumDirection(), + fVectorDirector) == false)) { delete newTrack; - } - else{ + } else { fStackManager->PushOneTrack(newTrack); } + // Special case here, since we generate independently each particle, we will + // not attach an electron to exiting compton photon, but we will the + // secondaries. - // Special case here, since we generate independently each particle, we will not attach an electron to exiting compton photon, but we will the secondaries. - - - if (processFinalState->GetNumberOfSecondaries()> 0){ + if (processFinalState->GetNumberOfSecondaries() > 0) { delete processFinalState->GetSecondary(0); } - processFinalState->Clear(); gammaProcessFinalState->Clear(); } } - - - -G4Track GateLastVertexInteractionSplittingActor::eBremProcessFinalState(G4Track* track, G4Step* step,G4VProcess *process){ - //It seem's that the the along step method apply only to brem results to no deposited energy but a change in momentum direction according to the process - //Whereas the along step method applied to the ionisation well change the deposited energy but not the momentum. Then I apply both to have a correct - //momentum and deposited energy before the brem effect. +G4Track GateLastVertexInteractionSplittingActor::eBremProcessFinalState( + G4Track *track, G4Step *step, G4VProcess *process) { + // It seem's that the the along step method apply only to brem results to no + // deposited energy but a change in momentum direction according to the + // process Whereas the along step method applied to the ionisation well change + // the deposited energy but not the momentum. Then I apply both to have a + // correct momentum and deposited energy before the brem effect. G4String particleName = track->GetDefinition()->GetParticleName(); - G4VProcess* eIoniProcess = GetProcessFromProcessName(particleName, "eIoni"); - G4VProcess* eBremProcess = GetProcessFromProcessName(particleName, "eBrem"); - G4VParticleChange* eIoniProcessAlongState = eIoniProcess->AlongStepDoIt(*track, *step); - G4VParticleChange* eBremProcessAlongState = eBremProcess->AlongStepDoIt(*track, *step); - G4ParticleChangeForLoss* eIoniProcessAlongStateForLoss = (G4ParticleChangeForLoss*) eIoniProcessAlongState; - G4ParticleChangeForLoss* eBremProcessAlongStateForLoss = (G4ParticleChangeForLoss*) eBremProcessAlongState; + G4VProcess *eIoniProcess = GetProcessFromProcessName(particleName, "eIoni"); + G4VProcess *eBremProcess = GetProcessFromProcessName(particleName, "eBrem"); + G4VParticleChange *eIoniProcessAlongState = + eIoniProcess->AlongStepDoIt(*track, *step); + G4VParticleChange *eBremProcessAlongState = + eBremProcess->AlongStepDoIt(*track, *step); + G4ParticleChangeForLoss *eIoniProcessAlongStateForLoss = + (G4ParticleChangeForLoss *)eIoniProcessAlongState; + G4ParticleChangeForLoss *eBremProcessAlongStateForLoss = + (G4ParticleChangeForLoss *)eBremProcessAlongState; G4double LossEnergy = eIoniProcessAlongStateForLoss->GetLocalEnergyDeposit(); - G4ThreeVector momentum = eBremProcessAlongStateForLoss->GetProposedMomentumDirection(); - G4ThreeVector polarization = eBremProcessAlongStateForLoss->GetProposedPolarization(); + G4ThreeVector momentum = + eBremProcessAlongStateForLoss->GetProposedMomentumDirection(); + G4ThreeVector polarization = + eBremProcessAlongStateForLoss->GetProposedPolarization(); G4Track aTrack = G4Track(*track); aTrack.SetKineticEnergy(track->GetKineticEnergy() - LossEnergy); @@ -225,83 +238,92 @@ G4Track GateLastVertexInteractionSplittingActor::eBremProcessFinalState(G4Track* eIoniProcessAlongState->Clear(); eBremProcessAlongState->Clear(); return aTrack; - } - -void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer theContainer, G4double batchSize) { +void GateLastVertexInteractionSplittingActor::SecondariesSplitting( + G4Step *initStep, G4Step *CurrentStep, G4VProcess *process, + LastVertexDataContainer theContainer, G4double batchSize) { SimpleContainer container = theContainer.GetContainerToSplit(); - G4String particleName = fTrackToSplit->GetParticleDefinition()->GetParticleName(); - //G4TrackVector *trackVector = CurrentStep->GetfSecondary(); + G4String particleName = + fTrackToSplit->GetParticleDefinition()->GetParticleName(); + // G4TrackVector *trackVector = CurrentStep->GetfSecondary(); G4VParticleChange *processFinalState = nullptr; - GateBremPostStepDoIt* bremProcess = nullptr; + GateBremPostStepDoIt *bremProcess = nullptr; GateGammaEmPostStepDoIt *emProcess = nullptr; GateplusannihilAtRestDoIt *eplusAnnihilProcess = nullptr; - if ((container.GetAnnihilationFlag() == "PostStep") && (fTrackToSplit->GetKineticEnergy() > 0)){ + if ((container.GetAnnihilationFlag() == "PostStep") && + (fTrackToSplit->GetKineticEnergy() > 0)) { emProcess = (GateGammaEmPostStepDoIt *)process; } - if ((container.GetAnnihilationFlag() == "AtRest") || (fTrackToSplit->GetKineticEnergy() == 0)) { + if ((container.GetAnnihilationFlag() == "AtRest") || + (fTrackToSplit->GetKineticEnergy() == 0)) { eplusAnnihilProcess = (GateplusannihilAtRestDoIt *)process; } - for (int j = 0; j < batchSize;j++){ + for (int j = 0; j < batchSize; j++) { G4int NbOfSecondaries = 0; - - G4int count =0; - while (NbOfSecondaries == 0){ + + G4int count = 0; + while (NbOfSecondaries == 0) { if (process->GetProcessName() == "eBrem") { - G4Track aTrack = eBremProcessFinalState(fTrackToSplit,initStep,process); - bremProcess = (GateBremPostStepDoIt*) process; - processFinalState = bremProcess->GateBremPostStepDoIt::PostStepDoIt(aTrack, *initStep); - } - else { - if ((container.GetAnnihilationFlag() == "PostStep") && (fTrackToSplit->GetKineticEnergy() > 0)){ - processFinalState = emProcess->PostStepDoIt(*fTrackToSplit, *initStep); + G4Track aTrack = + eBremProcessFinalState(fTrackToSplit, initStep, process); + bremProcess = (GateBremPostStepDoIt *)process; + processFinalState = + bremProcess->GateBremPostStepDoIt::PostStepDoIt(aTrack, *initStep); + } else { + if ((container.GetAnnihilationFlag() == "PostStep") && + (fTrackToSplit->GetKineticEnergy() > 0)) { + processFinalState = + emProcess->PostStepDoIt(*fTrackToSplit, *initStep); } - if ((container.GetAnnihilationFlag() == "AtRest") || (fTrackToSplit->GetKineticEnergy() == 0)) { - processFinalState = eplusAnnihilProcess->GateplusannihilAtRestDoIt::AtRestDoIt(*fTrackToSplit,*initStep); + if ((container.GetAnnihilationFlag() == "AtRest") || + (fTrackToSplit->GetKineticEnergy() == 0)) { + processFinalState = + eplusAnnihilProcess->GateplusannihilAtRestDoIt::AtRestDoIt( + *fTrackToSplit, *initStep); } } NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - if (NbOfSecondaries == 0){ + if (NbOfSecondaries == 0) { processFinalState->Clear(); } - count ++; - //Security break, in case of infinite loop - if (count > 10000){ + count++; + // Security break, in case of infinite loop + if (count > 10000) { G4ExceptionDescription ed; - ed << " infinite loop detected during the track creation for the " <<process->GetProcessName() <<" process"<<G4endl; - G4Exception("GateLastVertexInteractionSplittingActor::SecondariesSplitting","BIAS.LV1",JustWarning,ed); + ed << " infinite loop detected during the track creation for the " + << process->GetProcessName() << " process" << G4endl; + G4Exception( + "GateLastVertexInteractionSplittingActor::SecondariesSplitting", + "BIAS.LV1", JustWarning, ed); G4RunManager::GetRunManager()->AbortEvent(); break; } } G4int idx = 0; - G4bool IsPushBack =false; - for (int i=0; i < NbOfSecondaries; i++){ + G4bool IsPushBack = false; + for (int i = 0; i < NbOfSecondaries; i++) { G4Track *newTrack = processFinalState->GetSecondary(i); G4ThreeVector momentum = newTrack->GetMomentumDirection(); - - if (!(isnan(momentum[0]))){ - if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(momentum,fVectorDirector) == false)){ + + if (!(isnan(momentum[0]))) { + if ((fAngularKill) && (DoesParticleEmittedInSolidAngle( + momentum, fVectorDirector) == false)) { delete newTrack; - } - else if (IsPushBack == true){ + } else if (IsPushBack == true) { delete newTrack; - } - else { + } else { newTrack->SetWeight(fWeight); newTrack->SetCreatorProcess(process); - //trackVector->emplace_back(newTrack); + // trackVector->emplace_back(newTrack); fStackManager->PushOneTrack(newTrack); - //delete newTrack; - IsPushBack=true; - + // delete newTrack; + IsPushBack = true; } - } - else { + } else { delete newTrack; } } @@ -309,60 +331,68 @@ void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initS } } - -void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G4Step* initStep,G4Step *step,LastVertexDataContainer theContainer, G4double batchSize) { +void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex( + G4Step *initStep, G4Step *step, LastVertexDataContainer theContainer, + G4double batchSize) { // We retrieve the process associated to the process name to split and we // split according the process. Since for compton scattering, the gamma is not // a secondary particles, this one need to have his own splitting function. - G4String processName = fProcessNameToSplit; - G4int nbOfTrackAlreadyInStack = fStackManager->GetNTotalTrack(); - if ((fProcessToSplit == 0) || (fProcessToSplit == nullptr)){ - SimpleContainer container = theContainer.GetContainerToSplit(); - fProcessToSplit = GetProcessFromProcessName(container.GetParticleNameToSplit(),processName); - } - - if (processName == "compt") { - ComptonSplitting(initStep,step, fProcessToSplit, theContainer, batchSize); - } + G4String processName = fProcessNameToSplit; + G4int nbOfTrackAlreadyInStack = fStackManager->GetNTotalTrack(); + if ((fProcessToSplit == 0) || (fProcessToSplit == nullptr)) { + SimpleContainer container = theContainer.GetContainerToSplit(); + fProcessToSplit = GetProcessFromProcessName( + container.GetParticleNameToSplit(), processName); + } - else if((processName != "msc") && (processName != "conv")){ - SecondariesSplitting(initStep, step, fProcessToSplit, theContainer,batchSize); - } - fNumberOfTrackToSimulate = fStackManager->GetNTotalTrack() - nbOfTrackAlreadyInStack; - fNbOfBatchForExitingParticle ++; - if (fNbOfBatchForExitingParticle >500){ - fStackManager->clear(); - } - //stackManager->clear(); + if (processName == "compt") { + ComptonSplitting(initStep, step, fProcessToSplit, theContainer, batchSize); + } + else if ((processName != "msc") && (processName != "conv")) { + SecondariesSplitting(initStep, step, fProcessToSplit, theContainer, + batchSize); + } + fNumberOfTrackToSimulate = + fStackManager->GetNTotalTrack() - nbOfTrackAlreadyInStack; + fNbOfBatchForExitingParticle++; + if (fNbOfBatchForExitingParticle > 500) { + fStackManager->clear(); + } + // stackManager->clear(); } - -void GateLastVertexInteractionSplittingActor::CreateListOfbiasedVolume(G4LogicalVolume *volume) { +void GateLastVertexInteractionSplittingActor::CreateListOfbiasedVolume( + G4LogicalVolume *volume) { G4int nbOfDaughters = volume->GetNoDaughters(); if (nbOfDaughters > 0) { for (int i = 0; i < nbOfDaughters; i++) { - G4String LogicalVolumeName = volume->GetDaughter(i)->GetLogicalVolume()->GetName(); - G4LogicalVolume *logicalDaughtersVolume = volume->GetDaughter(i)->GetLogicalVolume(); - if (!(std::find(fListOfBiasedVolume.begin(),fListOfBiasedVolume.end(),LogicalVolumeName) != fListOfBiasedVolume.end())) - fListOfBiasedVolume.push_back(volume->GetDaughter(i)->GetLogicalVolume()->GetName()); + G4String LogicalVolumeName = + volume->GetDaughter(i)->GetLogicalVolume()->GetName(); + G4LogicalVolume *logicalDaughtersVolume = + volume->GetDaughter(i)->GetLogicalVolume(); + if (!(std::find(fListOfBiasedVolume.begin(), fListOfBiasedVolume.end(), + LogicalVolumeName) != fListOfBiasedVolume.end())) + fListOfBiasedVolume.push_back( + volume->GetDaughter(i)->GetLogicalVolume()->GetName()); CreateListOfbiasedVolume(logicalDaughtersVolume); } } } - -void GateLastVertexInteractionSplittingActor::FillOfDataTree(G4Step*step){ +void GateLastVertexInteractionSplittingActor::FillOfDataTree(G4Step *step) { G4String processName = "None"; if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) - processName = step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); - + processName = + step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); + G4String creatorProcessName = "None"; if (step->GetTrack()->GetCreatorProcess() != 0) - creatorProcessName =step->GetTrack()->GetCreatorProcess()->GetProcessName(); - + creatorProcessName = + step->GetTrack()->GetCreatorProcess()->GetProcessName(); + if ((step->GetTrack()->GetParticleDefinition()->GetParticleName() == "e+") && ((step->GetTrack()->GetTrackStatus() == 1) || (step->GetTrack()->GetTrackStatus() == 2))) { @@ -370,151 +400,154 @@ void GateLastVertexInteractionSplittingActor::FillOfDataTree(G4Step*step){ } G4String annihilFlag = "None"; - if (processName == "annihil"){ - if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0){ - if (processName == step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName()){ + if (processName == "annihil") { + if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) { + if (processName == + step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName()) { annihilFlag = "PostStep"; - } - else if (processName != step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName()){ - annihilFlag ="AtRest"; + } else if (processName != step->GetPostStepPoint() + ->GetProcessDefinedStep() + ->GetProcessName()) { + annihilFlag = "AtRest"; } } } - - - if (fIsFirstStep){ + if (fIsFirstStep) { LastVertexDataContainer newContainer = LastVertexDataContainer(); newContainer.SetTrackID(step->GetTrack()->GetTrackID()); - newContainer.SetParticleName(step->GetTrack()->GetDefinition()->GetParticleName()); + newContainer.SetParticleName( + step->GetTrack()->GetDefinition()->GetParticleName()); newContainer.SetCreationProcessName(creatorProcessName); - - - if (fTree.empty()){ + if (fTree.empty()) { fTree.set_head(newContainer); } - - for (auto it = fTree.begin_post(); it != fTree.end_post(); ++it){ + + for (auto it = fTree.begin_post(); it != fTree.end_post(); ++it) { LastVertexDataContainer container = *it; G4int trackID = container.GetTrackID(); - - if (step->GetTrack()->GetParentID() == trackID){ - newContainer = container.ContainerFromParentInformation(step); - fTree.append_child(it,newContainer); - break; + if (step->GetTrack()->GetParentID() == trackID) { + newContainer = container.ContainerFromParentInformation(step); + fTree.append_child(it, newContainer); + break; } } - for (auto it = fTree.begin_post(); it != fTree.end_post(); ++it){ + for (auto it = fTree.begin_post(); it != fTree.end_post(); ++it) { LastVertexDataContainer container = *it; G4int trackID = container.GetTrackID(); - if (step->GetTrack()->GetTrackID() == trackID){ + if (step->GetTrack()->GetTrackID() == trackID) { fIterator = it; break; } } } - - - LastVertexDataContainer* container = &(*fIterator); + LastVertexDataContainer *container = &(*fIterator); G4int trackID = container->GetTrackID(); - if ((processName != "Transportation") &&(processName !="None") && (processName !="Rayl")){ - if (step->GetTrack()->GetTrackID() == trackID){ + if ((processName != "Transportation") && (processName != "None") && + (processName != "Rayl")) { + if (step->GetTrack()->GetTrackID() == trackID) { G4ThreeVector position = step->GetTrack()->GetPosition(); G4ThreeVector prePosition = step->GetPreStepPoint()->GetPosition(); G4ThreeVector momentum; - if ((processName == "annihil")) + if ((processName == "annihil")) momentum = step->GetPostStepPoint()->GetMomentumDirection(); - else{ + else { momentum = step->GetPreStepPoint()->GetMomentumDirection(); } G4ThreeVector polarization = step->GetPreStepPoint()->GetPolarization(); - G4String particleName = step->GetTrack()->GetDefinition()->GetParticleName(); + G4String particleName = + step->GetTrack()->GetDefinition()->GetParticleName(); G4double energy = step->GetPreStepPoint()->GetKineticEnergy(); G4double weight = step->GetTrack()->GetWeight(); G4int trackStatus = step->GetTrack()->GetTrackStatus(); G4int nbOfSecondaries = step->GetfSecondary()->size(); G4double stepLength = step->GetStepLength(); - if (((processName == "annihil"))){ + if (((processName == "annihil"))) { energy -= (step->GetTotalEnergyDeposit()); } - SimpleContainer containerToSplit = SimpleContainer(processName,energy, momentum, position,polarization,particleName,weight,trackStatus,nbOfSecondaries,annihilFlag,stepLength,prePosition); + SimpleContainer containerToSplit = + SimpleContainer(processName, energy, momentum, position, polarization, + particleName, weight, trackStatus, nbOfSecondaries, + annihilFlag, stepLength, prePosition); container->SetContainerToSplit(containerToSplit); container->PushListOfSplittingParameters(containerToSplit); - } } - - } - - -G4bool GateLastVertexInteractionSplittingActor::IsParticleExitTheBiasedVolume(G4Step*step){ - +G4bool GateLastVertexInteractionSplittingActor::IsParticleExitTheBiasedVolume( + G4Step *step) { if ((step->GetPostStepPoint()->GetStepStatus() == 1)) { G4String logicalVolumeNamePostStep = "None"; if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) - logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - - if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()){ + logicalVolumeNamePostStep = step->GetPostStepPoint() + ->GetPhysicalVolume() + ->GetLogicalVolume() + ->GetName(); + + if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), + logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { return true; } - /* - else if (std::find(fListOfBiasedVolume.begin(), fListOfBiasedVolume.end(), logicalVolumeNamePostStep) != fListOfBiasedVolume.end()) { - return false; - } - */ + /* + else if (std::find(fListOfBiasedVolume.begin(), fListOfBiasedVolume.end(), + logicalVolumeNamePostStep) != fListOfBiasedVolume.end()) { return false; } + */ + } if (step->GetPostStepPoint()->GetStepStatus() == 0) return true; return false; } - - -G4bool GateLastVertexInteractionSplittingActor::IsTheParticleUndergoesAProcess(G4Step* step){ +G4bool GateLastVertexInteractionSplittingActor::IsTheParticleUndergoesAProcess( + G4Step *step) { G4String processName = "None"; - G4String particleName = step->GetTrack()->GetParticleDefinition()->GetParticleName(); + G4String particleName = + step->GetTrack()->GetParticleDefinition()->GetParticleName(); if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) - processName = step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); - - if (std::find(fListOfProcessesAccordingParticles[particleName].begin(), fListOfProcessesAccordingParticles[particleName].end(), processName) != fListOfProcessesAccordingParticles[particleName].end()){ + processName = + step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); + + if (std::find(fListOfProcessesAccordingParticles[particleName].begin(), + fListOfProcessesAccordingParticles[particleName].end(), + processName) != + fListOfProcessesAccordingParticles[particleName].end()) { return true; } return false; - - } -G4bool GateLastVertexInteractionSplittingActor::IsTheParticleUndergoesALossEnergyProcess(G4Step* step){ - if (step->GetPostStepPoint()->GetKineticEnergy() - step->GetPreStepPoint()->GetKineticEnergy() != 0) +G4bool GateLastVertexInteractionSplittingActor:: + IsTheParticleUndergoesALossEnergyProcess(G4Step *step) { + if (step->GetPostStepPoint()->GetKineticEnergy() - + step->GetPreStepPoint()->GetKineticEnergy() != + 0) return true; return false; - - } - - void GateLastVertexInteractionSplittingActor::BeginOfRunAction( const G4Run *run) { - fListOfProcessesAccordingParticles["gamma"] = {"compt","phot","conv"}; - fListOfProcessesAccordingParticles["e-"] = {"eBrem","eIoni","msc"}; - fListOfProcessesAccordingParticles["e+"] = {"eBrem","eIoni","msc","annihil"}; + fListOfProcessesAccordingParticles["gamma"] = {"compt", "phot", "conv"}; + fListOfProcessesAccordingParticles["e-"] = {"eBrem", "eIoni", "msc"}; + fListOfProcessesAccordingParticles["e+"] = {"eBrem", "eIoni", "msc", + "annihil"}; - std::cout<<fMotherVolumeName<<std::endl; - G4LogicalVolume *biasingVolume = G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + std::cout << fMotherVolumeName << std::endl; + G4LogicalVolume *biasingVolume = + G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); fListOfBiasedVolume.push_back(biasingVolume->GetName()); CreateListOfbiasedVolume(biasingVolume); - auto* source = fSourceManager->FindSourceByName("source_vertex"); - fVertexSource = (GateLastVertexSource* ) source; + auto *source = fSourceManager->FindSourceByName("source_vertex"); + fVertexSource = (GateLastVertexSource *)source; fCosMaxTheta = std::cos(fMaxTheta); fStackManager = G4EventManager::GetEventManager()->GetStackManager(); @@ -532,175 +565,176 @@ void GateLastVertexInteractionSplittingActor::BeginOfEventAction( fEventID = event->GetEventID(); fIsAnnihilAlreadySplit = false; fNbOfBatchForExitingParticle = 0; - if (fEventID%50000 == 0) - std::cout<<"event ID : "<<fEventID<<std::endl; - if (fCopyInitStep != 0){ + if (fEventID % 50000 == 0) + std::cout << "event ID : " << fEventID << std::endl; + if (fCopyInitStep != 0) { delete fCopyInitStep; fCopyInitStep = nullptr; } - fSplitCounter =0; - fNumberOfTrackToSimulate =0; + fSplitCounter = 0; + fNumberOfTrackToSimulate = 0; fKilledBecauseOfProcess = false; - if (fActiveSource == "source_vertex"){ - auto* source = fSourceManager->FindSourceByName(fActiveSource); - GateLastVertexSource *vertexSource = (GateLastVertexSource*) source; + if (fActiveSource == "source_vertex") { + auto *source = fSourceManager->FindSourceByName(fActiveSource); + GateLastVertexSource *vertexSource = (GateLastVertexSource *)source; fContainer = vertexSource->GetLastVertexContainer(); fProcessNameToSplit = vertexSource->GetProcessToSplit(); - if (fProcessToSplit !=0){ + if (fProcessToSplit != 0) { fProcessToSplit = nullptr; } - if (fTrackToSplit !=0){ + if (fTrackToSplit != 0) { delete fTrackToSplit; fTrackToSplit = nullptr; } fTrackToSplit = CreateATrackFromContainer(fContainer); if (fTrackToSplit != 0) - fWeight = fTrackToSplit->GetWeight()/fSplittingFactor; + fWeight = fTrackToSplit->GetWeight() / fSplittingFactor; } - - } void GateLastVertexInteractionSplittingActor::PreUserTrackingAction( const G4Track *track) { - fToSplit =true; - fIsFirstStep = true; - + fToSplit = true; + fIsFirstStep = true; } void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { - - if (fActiveSource != "source_vertex"){ + if (fActiveSource != "source_vertex") { FillOfDataTree(step); - - if (IsParticleExitTheBiasedVolume(step)){ - if ((fAngularKill == false) || ((fAngularKill == true) && (DoesParticleEmittedInSolidAngle(step->GetTrack()->GetMomentumDirection(),fVectorDirector) == true))){ + + if (IsParticleExitTheBiasedVolume(step)) { + if ((fAngularKill == false) || + ((fAngularKill == true) && + (DoesParticleEmittedInSolidAngle( + step->GetTrack()->GetMomentumDirection(), fVectorDirector) == + true))) { fListOfContainer.push_back((*fIterator)); } - + step->GetTrack()->SetTrackStatus(fStopAndKill); } - - } - - - if (fOnlyTree == false){ - if (fActiveSource == "source_vertex"){ + if (fOnlyTree == false) { + if (fActiveSource == "source_vertex") { - if (fIsFirstStep){ + if (fIsFirstStep) { fTrackID = step->GetTrack()->GetTrackID(); fEkin = step->GetPostStepPoint()->GetKineticEnergy(); - } - else{ - if ((fTrackID == step->GetTrack()->GetTrackID()) && (fEkin != step->GetPreStepPoint()->GetKineticEnergy())){ - fToSplit =false; - } - else{ + } else { + if ((fTrackID == step->GetTrack()->GetTrackID()) && + (fEkin != step->GetPreStepPoint()->GetKineticEnergy())) { + fToSplit = false; + } else { fEkin = step->GetPostStepPoint()->GetKineticEnergy(); } - } if (fToSplit) { G4String creatorProcessName = "None"; if (step->GetTrack()->GetCreatorProcess() != 0) - creatorProcessName =step->GetTrack()->GetCreatorProcess()->GetProcessName(); - if (((step->GetTrack()->GetParentID() == 0)&& (step->GetTrack()->GetTrackID() == 1))|| ((creatorProcessName == "annihil") && (step->GetTrack()->GetParentID() == 1))){ - - if ((fProcessNameToSplit != "annihil") || ((fProcessNameToSplit == "annihil")&& (fIsAnnihilAlreadySplit ==false))){ - - //FIXME : list of process which are not splitable yet - if ((fProcessNameToSplit != "msc") && (fProcessNameToSplit != "conv") && (fProcessNameToSplit != "eIoni")) { + creatorProcessName = + step->GetTrack()->GetCreatorProcess()->GetProcessName(); + if (((step->GetTrack()->GetParentID() == 0) && + (step->GetTrack()->GetTrackID() == 1)) || + ((creatorProcessName == "annihil") && + (step->GetTrack()->GetParentID() == 1))) { + + if ((fProcessNameToSplit != "annihil") || + ((fProcessNameToSplit == "annihil") && + (fIsAnnihilAlreadySplit == false))) { + + // FIXME : list of process which are not splitable yet + if ((fProcessNameToSplit != "msc") && + (fProcessNameToSplit != "conv") && + (fProcessNameToSplit != "eIoni")) { fCopyInitStep = new G4Step(*step); - if (fProcessNameToSplit == "eBrem"){ - fCopyInitStep->SetStepLength(fContainer.GetContainerToSplit().GetStepLength()); - fCopyInitStep->GetPreStepPoint()->SetKineticEnergy(fContainer.GetContainerToSplit().GetEnergy()); - + if (fProcessNameToSplit == "eBrem") { + fCopyInitStep->SetStepLength( + fContainer.GetContainerToSplit().GetStepLength()); + fCopyInitStep->GetPreStepPoint()->SetKineticEnergy( + fContainer.GetContainerToSplit().GetEnergy()); } - CreateNewParticleAtTheLastVertex(fCopyInitStep,step,fContainer, fBatchSize); + CreateNewParticleAtTheLastVertex(fCopyInitStep, step, fContainer, + fBatchSize); } step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); - - if (fProcessNameToSplit == "annihil"){ + + if (fProcessNameToSplit == "annihil") { fIsAnnihilAlreadySplit = true; - } } + } - - else if ((fProcessNameToSplit == "annihil")&& (fIsAnnihilAlreadySplit == true)){ + else if ((fProcessNameToSplit == "annihil") && + (fIsAnnihilAlreadySplit == true)) { step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); } - - } - + + } + else { - if (fIsFirstStep){ - fNumberOfTrackToSimulate --; - if (fKilledBecauseOfProcess == false){ + if (fIsFirstStep) { + fNumberOfTrackToSimulate--; + if (fKilledBecauseOfProcess == false) { fSplitCounter += 1; - } - else { + } else { fKilledBecauseOfProcess = false; } - if (fSplitCounter > fSplittingFactor){ + if (fSplitCounter > fSplittingFactor) { step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); fStackManager->clear(); } } - if (IsTheParticleUndergoesALossEnergyProcess(step)){ + if (IsTheParticleUndergoesALossEnergyProcess(step)) { step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); fKilledBecauseOfProcess = true; } - if (fIsFirstStep){ - if (fSplitCounter <= fSplittingFactor){ - if (fNumberOfTrackToSimulate == 0){ - CreateNewParticleAtTheLastVertex(fCopyInitStep,step,fContainer,(fSplittingFactor - fSplitCounter +1)/fSplittingFactor * fBatchSize); - } + if (fIsFirstStep) { + if (fSplitCounter <= fSplittingFactor) { + if (fNumberOfTrackToSimulate == 0) { + CreateNewParticleAtTheLastVertex( + fCopyInitStep, step, fContainer, + (fSplittingFactor - fSplitCounter + 1) / fSplittingFactor * + fBatchSize); + } } } } - } + } } } fIsFirstStep = false; - - - - } - void GateLastVertexInteractionSplittingActor::EndOfEventAction( - const G4Event* event) { - - if (fActiveSource != "source_vertex"){ - - //print_tree(fTree,fTree.begin(),fTree.end()); - fVertexSource->SetNumberOfEventToSimulate(fListOfContainer.size()); - fVertexSource->SetNumberOfGeneratedEvent(0); - fVertexSource->SetListOfVertexToSimulate(fListOfContainer); - fTree.clear(); - fListOfContainer.clear(); - } + const G4Event *event) { - if (fOnlyTree == false){ + if (fActiveSource != "source_vertex") { - auto* source = fSourceManager->FindSourceByName("source_vertex"); - GateLastVertexSource* vertexSource = (GateLastVertexSource*) source; - if (vertexSource->GetNumberOfGeneratedEvent() < vertexSource->GetNumberOfEventToSimulate()){ - fSourceManager->SetActiveSourcebyName("source_vertex"); - } - fActiveSource = fSourceManager->GetActiveSourceName(); - } - + // print_tree(fTree,fTree.begin(),fTree.end()); + fVertexSource->SetNumberOfEventToSimulate(fListOfContainer.size()); + fVertexSource->SetNumberOfGeneratedEvent(0); + fVertexSource->SetListOfVertexToSimulate(fListOfContainer); + fTree.clear(); + fListOfContainer.clear(); + } + + if (fOnlyTree == false) { + + auto *source = fSourceManager->FindSourceByName("source_vertex"); + GateLastVertexSource *vertexSource = (GateLastVertexSource *)source; + if (vertexSource->GetNumberOfGeneratedEvent() < + vertexSource->GetNumberOfEventToSimulate()) { + fSourceManager->SetActiveSourcebyName("source_vertex"); } + fActiveSource = fSourceManager->GetActiveSourceName(); + } +} //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h index dfe0ee850..e5d0b333f 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h @@ -29,18 +29,17 @@ #ifndef GateLastVertexInteractionSplittingActor_h #define GateLastVertexInteractionSplittingActor_h 1 +#include "CLHEP/Vector/ThreeVector.h" #include "G4ParticleChangeForGamma.hh" +#include "G4StackManager.hh" #include "G4VEnergyLossProcess.hh" -#include "GateVActor.h" -#include <iostream> -#include <pybind11/stl.h> +#include "GateLastVertexSource.h" #include "GateLastVertexSplittingDataContainer.h" +#include "GateVActor.h" #include "tree.hh" #include "tree_util.hh" #include <iostream> -#include "GateLastVertexSource.h" -#include "CLHEP/Vector/ThreeVector.h" -#include "G4StackManager.hh" +#include <pybind11/stl.h> using CLHEP::Hep3Vector; namespace py = pybind11; @@ -71,35 +70,31 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { G4double fSplitCounter = 0; G4bool fToSplit = true; G4String fActiveSource = "None"; - G4bool fIsAnnihilAlreadySplit =false; + G4bool fIsAnnihilAlreadySplit = false; G4int fCounter; - G4bool fKilledBecauseOfProcess = false; + G4bool fKilledBecauseOfProcess = false; G4bool fFirstSplittedPart = true; G4bool fOnlyTree = false; G4double fWeight; G4double fBatchSize; G4int fNumberOfTrackToSimulate = 0; - G4int fNbOfBatchForExitingParticle=0; - G4int fTracksCounts=0; - GateLastVertexSource* fVertexSource = nullptr; + G4int fNbOfBatchForExitingParticle = 0; + G4int fTracksCounts = 0; + GateLastVertexSource *fVertexSource = nullptr; tree<LastVertexDataContainer> fTree; tree<LastVertexDataContainer>::post_order_iterator fIterator; std::vector<LastVertexDataContainer> fListOfContainer; - G4StackManager* fStackManager = nullptr; - - + G4StackManager *fStackManager = nullptr; G4Track *fTrackToSplit = nullptr; - G4Step* fCopyInitStep = nullptr; + G4Step *fCopyInitStep = nullptr; G4String fProcessNameToSplit; - G4VProcess* fProcessToSplit; + G4VProcess *fProcessToSplit; LastVertexDataContainer fContainer; std::vector<G4Track> fTracksToPostpone; - std::map<G4String,std::vector<G4String>> fListOfProcessesAccordingParticles; - std::map<G4int,LastVertexDataContainer> fDataMap; - - + std::map<G4String, std::vector<G4String>> fListOfProcessesAccordingParticles; + std::map<G4int, LastVertexDataContainer> fDataMap; std::vector<std::string> fListOfVolumeAncestor; std::vector<std::string> fListOfBiasedVolume; @@ -107,7 +102,7 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { std::vector<G4String> fListOfProcesses = {"compt", "annihil", "eBrem", "conv", "phot"}; - void InitializeUserInput(py::dict &user_info) override; + void InitializeUserInput(py::dict &user_info) override; virtual void SteppingAction(G4Step *) override; virtual void BeginOfEventAction(const G4Event *) override; virtual void EndOfEventAction(const G4Event *) override; @@ -115,23 +110,33 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { virtual void PreUserTrackingAction(const G4Track *track) override; // Pure splitting functions - G4bool DoesParticleEmittedInSolidAngle(G4ThreeVector dir, G4ThreeVector vectorDirector); + G4bool DoesParticleEmittedInSolidAngle(G4ThreeVector dir, + G4ThreeVector vectorDirector); G4Track *CreateComptonTrack(G4ParticleChangeForGamma *, G4Track, G4double); - void ComptonSplitting(G4Step* initStep,G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container, G4double batchSize); - void SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container, G4double batchSize); - - void CreateNewParticleAtTheLastVertex(G4Step*init,G4Step *current, LastVertexDataContainer, G4double batchSize); - G4Track* CreateATrackFromContainer(LastVertexDataContainer container); - G4bool IsTheParticleUndergoesAProcess(G4Step* step); - G4bool IsTheParticleUndergoesALossEnergyProcess(G4Step* step); - G4VProcess* GetProcessFromProcessName(G4String particleName, G4String pName); - G4Track eBremProcessFinalState(G4Track* track, G4Step* step,G4VProcess *process); - + void ComptonSplitting(G4Step *initStep, G4Step *CurrentStep, + G4VProcess *process, LastVertexDataContainer container, + G4double batchSize); + void SecondariesSplitting(G4Step *initStep, G4Step *CurrentStep, + G4VProcess *process, + LastVertexDataContainer container, + G4double batchSize); + + void CreateNewParticleAtTheLastVertex(G4Step *init, G4Step *current, + LastVertexDataContainer, + G4double batchSize); + G4Track *CreateATrackFromContainer(LastVertexDataContainer container); + G4bool IsTheParticleUndergoesAProcess(G4Step *step); + G4bool IsTheParticleUndergoesALossEnergyProcess(G4Step *step); + G4VProcess *GetProcessFromProcessName(G4String particleName, G4String pName); + G4Track eBremProcessFinalState(G4Track *track, G4Step *step, + G4VProcess *process); void FillOfDataTree(G4Step *step); - G4bool IsParticleExitTheBiasedVolume(G4Step*step); + G4bool IsParticleExitTheBiasedVolume(G4Step *step); void CreateListOfbiasedVolume(G4LogicalVolume *volume); - void print_tree(const tree<LastVertexDataContainer>& tr, tree<LastVertexDataContainer>::pre_order_iterator it, tree<LastVertexDataContainer>::pre_order_iterator end); + void print_tree(const tree<LastVertexDataContainer> &tr, + tree<LastVertexDataContainer>::pre_order_iterator it, + tree<LastVertexDataContainer>::pre_order_iterator end); }; #endif diff --git a/core/opengate_core/opengate_lib/GateLastVertexSource.cpp b/core/opengate_core/opengate_lib/GateLastVertexSource.cpp index b903d90c8..c870637a4 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSource.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexSource.cpp @@ -10,40 +10,38 @@ #include "GateHelpersDict.h" #include <G4UnitsTable.hh> -GateLastVertexSource::GateLastVertexSource() : GateVSource() { -} +GateLastVertexSource::GateLastVertexSource() : GateVSource() {} -GateLastVertexSource::~GateLastVertexSource() { -} +GateLastVertexSource::~GateLastVertexSource() {} void GateLastVertexSource::InitializeUserInfo(py::dict &user_info) { GateVSource::InitializeUserInfo(user_info); // get user info about activity or nb of events fN = DictGetInt(user_info, "n"); - } +} double GateLastVertexSource::PrepareNextTime(double current_simulation_time) { /* // If all N events have been generated, we stop (negative time) if (fNumberOfGeneratedEvents >= fN){ - std::cout << "LV: "<< -1<<" "<< fNumberOfGeneratedEvents <<" "<< fN<<std::endl; - return -1; + std::cout << "LV: "<< -1<<" "<< fNumberOfGeneratedEvents <<" "<< + fN<<std::endl; return -1; } if (fListOfContainer.size()==0){ - std::cout << "LV: "<< 1<<" "<<fNumberOfGeneratedEvents <<" "<< fN<<std::endl; - return 1; + std::cout << "LV: "<< 1<<" "<<fNumberOfGeneratedEvents <<" "<< + fN<<std::endl; return 1; } // Else we consider all event with a timestamp equal to the simulation // StartTime - std::cout << "LV: "<< fStartTime<<" "<<fNumberOfGeneratedEvents <<" "<< fN<<std::endl; - return fStartTime; + std::cout << "LV: "<< fStartTime<<" "<<fNumberOfGeneratedEvents <<" "<< + fN<<std::endl; return fStartTime; */ - if (fNumberOfGeneratedEvents >= fN) - return -1; + if (fNumberOfGeneratedEvents >= fN) + return -1; - return fStartTime + 1; + return fStartTime + 1; } void GateLastVertexSource::PrepareNextRun() { @@ -56,34 +54,35 @@ void GateLastVertexSource::PrepareNextRun() { // init the number of generated events (here, for each run) fNumberOfGeneratedEvents = 0; - } -void GateLastVertexSource::GenerateOnePrimary(G4Event* event, double current_simulation_time, G4int idx ){ +void GateLastVertexSource::GenerateOnePrimary(G4Event *event, + double current_simulation_time, + G4int idx) { - if (fNumberOfGeneratedEvents >= fN){ + if (fNumberOfGeneratedEvents >= fN) { auto *particle_table = G4ParticleTable::GetParticleTable(); auto *fParticleDefinition = particle_table->FindParticle("geantino"); auto *particle = new G4PrimaryParticle(fParticleDefinition); particle->SetKineticEnergy(0); - particle->SetMomentumDirection({1,0,0}); + particle->SetMomentumDirection({1, 0, 0}); particle->SetWeight(1); - auto *vertex = new G4PrimaryVertex({0,0,0}, current_simulation_time); + auto *vertex = new G4PrimaryVertex({0, 0, 0}, current_simulation_time); vertex->SetPrimary(particle); event->AddPrimaryVertex(vertex); - } - else { + } else { - SimpleContainer containerToSplit = fListOfContainer[idx].GetContainerToSplit(); + SimpleContainer containerToSplit = + fListOfContainer[idx].GetContainerToSplit(); G4double energy = containerToSplit.GetEnergy(); - if (energy < 0){ + if (energy < 0) { energy = 0; } fContainer = fListOfContainer[idx]; - G4ThreeVector position = containerToSplit.GetVertexPosition(); + G4ThreeVector position = containerToSplit.GetVertexPosition(); G4ThreeVector momentum = containerToSplit.GetMomentum(); G4String particleName = containerToSplit.GetParticleNameToSplit(); - G4double weight =containerToSplit.GetWeight(); + G4double weight = containerToSplit.GetWeight(); fProcessToSplit = containerToSplit.GetProcessNameToSplit(); auto &l = fThreadLocalData.Get(); @@ -97,16 +96,14 @@ void GateLastVertexSource::GenerateOnePrimary(G4Event* event, double current_sim vertex->SetPrimary(particle); event->AddPrimaryVertex(vertex); } - } - void GateLastVertexSource::GeneratePrimaries(G4Event *event, - double current_simulation_time) { - - GenerateOnePrimary(event,current_simulation_time,fNumberOfGeneratedEvents); + double current_simulation_time) { + + GenerateOnePrimary(event, current_simulation_time, fNumberOfGeneratedEvents); fNumberOfGeneratedEvents++; - if (fNumberOfGeneratedEvents == fListOfContainer.size()){ + if (fNumberOfGeneratedEvents == fListOfContainer.size()) { fListOfContainer.clear(); } } diff --git a/core/opengate_core/opengate_lib/GateLastVertexSource.h b/core/opengate_core/opengate_lib/GateLastVertexSource.h index dd23c124d..eebf43e90 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSource.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSource.h @@ -9,9 +9,9 @@ #define GateLastVertexSource_h #include "GateAcceptanceAngleTesterManager.h" +#include "GateLastVertexSplittingDataContainer.h" #include "GateSingleParticleSource.h" #include "GateVSource.h" -#include "GateLastVertexSplittingDataContainer.h" #include <pybind11/stl.h> namespace py = pybind11; @@ -19,9 +19,9 @@ namespace py = pybind11; /* This is NOT a real source type but a template to help writing your own source type. Copy-paste this file with a different name ("MyNewSource.hh") and start - building. You also need to copy : GateLastVertexSource.hh GateLastVertexSource.cpp - pyGateLastVertexSource.cpp - And add the source declaration in opengate_core.cpp + building. You also need to copy : GateLastVertexSource.hh + GateLastVertexSource.cpp pyGateLastVertexSource.cpp And add the source + declaration in opengate_core.cpp */ class GateLastVertexSource : public GateVSource { @@ -39,38 +39,25 @@ class GateLastVertexSource : public GateVSource { void GeneratePrimaries(G4Event *event, double time) override; + void GenerateOnePrimary(G4Event *event, double time, G4int idx); - void GenerateOnePrimary(G4Event *event, double time,G4int idx); - - - void SetListOfVertexToSimulate(std::vector<LastVertexDataContainer> list){ + void SetListOfVertexToSimulate(std::vector<LastVertexDataContainer> list) { fListOfContainer = list; } - void SetNumberOfGeneratedEvent(G4int nbEvent){ + void SetNumberOfGeneratedEvent(G4int nbEvent) { fNumberOfGeneratedEvents = nbEvent; } - void SetNumberOfEventToSimulate(G4int N){ - fN = N; - } - - G4int GetNumberOfEventToSimulate(){ - return fN; - } + void SetNumberOfEventToSimulate(G4int N) { fN = N; } - G4int GetNumberOfGeneratedEvent(){ - return fNumberOfGeneratedEvents; - } + G4int GetNumberOfEventToSimulate() { return fN; } - G4String GetProcessToSplit(){ - return fProcessToSplit; - } + G4int GetNumberOfGeneratedEvent() { return fNumberOfGeneratedEvents; } - LastVertexDataContainer GetLastVertexContainer(){ - return fContainer; - } + G4String GetProcessToSplit() { return fProcessToSplit; } + LastVertexDataContainer GetLastVertexContainer() { return fContainer; } protected: G4int fNumberOfGeneratedEvents = 0; diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h index 474d6cc84..92edcb5f0 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h @@ -27,139 +27,118 @@ #ifndef LastVertexDataContainer_h #define LastVertexDataContainer_h - -#include <iostream> -#include "G4VEnergyLossProcess.hh" +#include "G4Electron.hh" +#include "G4EmBiasingManager.hh" +#include "G4EmParameters.hh" +#include "G4EntanglementAuxInfo.hh" +#include "G4Gamma.hh" +#include "G4MaterialCutsCouple.hh" +#include "G4PhysicalConstants.hh" +#include "G4PhysicsModelCatalog.hh" +#include "G4Positron.hh" #include "G4Track.hh" #include "G4VEmProcess.hh" +#include "G4VEnergyLossProcess.hh" #include "G4VParticleChange.hh" -#include "G4eplusAnnihilation.hh" -#include "G4PhysicalConstants.hh" -#include "G4MaterialCutsCouple.hh" -#include "G4Gamma.hh" -#include "G4Electron.hh" -#include "G4Positron.hh" #include "G4eeToTwoGammaModel.hh" -#include "G4EmBiasingManager.hh" -#include "G4EntanglementAuxInfo.hh" +#include "G4eplusAnnihilation.hh" #include "G4eplusAnnihilationEntanglementClipBoard.hh" -#include "G4EmParameters.hh" -#include "G4PhysicsModelCatalog.hh" #include "GateLastVertexSplittingSimpleContainer.h" +#include <iostream> +class LastVertexDataContainer { -class LastVertexDataContainer{ - -public : - - -LastVertexDataContainer(){} - -~LastVertexDataContainer(){} - - - -void SetTrackID(G4int trackID ){ - fTrackID = trackID; -} - -G4int GetTrackID(){ - return fTrackID; -} - - -void SetParticleName(G4String name){ - fParticleName = name; -} - -G4String GetParticleName(){ - return fParticleName; -} - - -void SetCreationProcessName(G4String creationProcessName){ - fCreationProcessName = creationProcessName; -} - -G4String GetCreationProcessName(){ - return fCreationProcessName; -} - +public: + LastVertexDataContainer() {} -void SetContainerToSplit(SimpleContainer container){ - fContainerToSplit = container; -} + ~LastVertexDataContainer() {} + void SetTrackID(G4int trackID) { fTrackID = trackID; } -SimpleContainer GetContainerToSplit(){ - return fContainerToSplit; -} + G4int GetTrackID() { return fTrackID; } + void SetParticleName(G4String name) { fParticleName = name; } + G4String GetParticleName() { return fParticleName; } -void PushListOfSplittingParameters(SimpleContainer container){ - fVectorOfContainerToSplit.emplace_back(container); -} + void SetCreationProcessName(G4String creationProcessName) { + fCreationProcessName = creationProcessName; + } + G4String GetCreationProcessName() { return fCreationProcessName; } + void SetContainerToSplit(SimpleContainer container) { + fContainerToSplit = container; + } + SimpleContainer GetContainerToSplit() { return fContainerToSplit; } -LastVertexDataContainer ContainerFromParentInformation(G4Step* step){ - LastVertexDataContainer aContainer = LastVertexDataContainer(); + void PushListOfSplittingParameters(SimpleContainer container) { + fVectorOfContainerToSplit.emplace_back(container); + } - aContainer.fTrackID = step->GetTrack()->GetTrackID(); - aContainer.fParticleName = step->GetTrack()->GetDefinition()->GetParticleName(); - if (this->fContainerToSplit.GetProcessNameToSplit() != "None"){ - if (this->fVectorOfContainerToSplit.size() !=0){ - G4ThreeVector vertexPosition = step->GetTrack()->GetVertexPosition(); - for (int i =0;i<this->fVectorOfContainerToSplit.size();i++){ - if (vertexPosition == this->fVectorOfContainerToSplit[i].GetVertexPosition()){ - SimpleContainer tmpContainer = this->fVectorOfContainerToSplit[i]; - //std::cout<<"1 "<<tmpContainer.GetProcessNameToSplit()<<std::endl; - aContainer.fContainerToSplit = SimpleContainer(tmpContainer.GetProcessNameToSplit(),tmpContainer.GetEnergy(),tmpContainer.GetMomentum(),tmpContainer.GetVertexPosition(),tmpContainer.GetPolarization(),tmpContainer.GetParticleNameToSplit(),tmpContainer.GetWeight(),tmpContainer.GetTrackStatus(),tmpContainer.GetNbOfSecondaries(),tmpContainer.GetAnnihilationFlag(),tmpContainer.GetStepLength(), tmpContainer.GetPrePositionToSplit()); - return aContainer; + LastVertexDataContainer ContainerFromParentInformation(G4Step *step) { + LastVertexDataContainer aContainer = LastVertexDataContainer(); + + aContainer.fTrackID = step->GetTrack()->GetTrackID(); + aContainer.fParticleName = + step->GetTrack()->GetDefinition()->GetParticleName(); + if (this->fContainerToSplit.GetProcessNameToSplit() != "None") { + if (this->fVectorOfContainerToSplit.size() != 0) { + G4ThreeVector vertexPosition = step->GetTrack()->GetVertexPosition(); + for (int i = 0; i < this->fVectorOfContainerToSplit.size(); i++) { + if (vertexPosition == + this->fVectorOfContainerToSplit[i].GetVertexPosition()) { + SimpleContainer tmpContainer = this->fVectorOfContainerToSplit[i]; + // std::cout<<"1 "<<tmpContainer.GetProcessNameToSplit()<<std::endl; + aContainer.fContainerToSplit = SimpleContainer( + tmpContainer.GetProcessNameToSplit(), tmpContainer.GetEnergy(), + tmpContainer.GetMomentum(), tmpContainer.GetVertexPosition(), + tmpContainer.GetPolarization(), + tmpContainer.GetParticleNameToSplit(), tmpContainer.GetWeight(), + tmpContainer.GetTrackStatus(), + tmpContainer.GetNbOfSecondaries(), + tmpContainer.GetAnnihilationFlag(), + tmpContainer.GetStepLength(), + tmpContainer.GetPrePositionToSplit()); + return aContainer; + } } + } else { + SimpleContainer tmpContainer = this->fContainerToSplit; + // std::cout<<"2 "<<tmpContainer.GetProcessNameToSplit()<<std::endl; + aContainer.fContainerToSplit = SimpleContainer( + tmpContainer.GetProcessNameToSplit(), tmpContainer.GetEnergy(), + tmpContainer.GetMomentum(), tmpContainer.GetVertexPosition(), + tmpContainer.GetPolarization(), + tmpContainer.GetParticleNameToSplit(), tmpContainer.GetWeight(), + tmpContainer.GetTrackStatus(), tmpContainer.GetNbOfSecondaries(), + tmpContainer.GetAnnihilationFlag(), tmpContainer.GetStepLength(), + tmpContainer.GetPrePositionToSplit()); + return aContainer; } } - else{ - SimpleContainer tmpContainer = this->fContainerToSplit; - //std::cout<<"2 "<<tmpContainer.GetProcessNameToSplit()<<std::endl; - aContainer.fContainerToSplit = SimpleContainer(tmpContainer.GetProcessNameToSplit(),tmpContainer.GetEnergy(),tmpContainer.GetMomentum(),tmpContainer.GetVertexPosition(),tmpContainer.GetPolarization(),tmpContainer.GetParticleNameToSplit(),tmpContainer.GetWeight(),tmpContainer.GetTrackStatus(),tmpContainer.GetNbOfSecondaries(),tmpContainer.GetAnnihilationFlag(),tmpContainer.GetStepLength(),tmpContainer.GetPrePositionToSplit()); - return aContainer; - } + // std::cout<<"3"<<std::endl; + return aContainer; } - //std::cout<<"3"<<std::endl; - return aContainer; -} - - - - - -friend std::ostream& operator<<(std::ostream& os, LastVertexDataContainer& container) { - os <<container.fParticleName<<" ID: "<<container.fTrackID<< " process to split : "<<container.fContainerToSplit.GetProcessNameToSplit()<<" name to split"<<container.fContainerToSplit.GetParticleNameToSplit() ; + friend std::ostream &operator<<(std::ostream &os, + LastVertexDataContainer &container) { + os << container.fParticleName << " ID: " << container.fTrackID + << " process to split : " + << container.fContainerToSplit.GetProcessNameToSplit() + << " name to split" + << container.fContainerToSplit.GetParticleNameToSplit(); return os; -} - - - - -private : - -G4String fParticleName="None"; -G4int fTrackID = 0; -G4String fCreationProcessName ="None"; -SimpleContainer fContainerToSplit; + } -std::vector<SimpleContainer> fVectorOfContainerToSplit; +private: + G4String fParticleName = "None"; + G4int fTrackID = 0; + G4String fCreationProcessName = "None"; + SimpleContainer fContainerToSplit; + std::vector<SimpleContainer> fVectorOfContainerToSplit; }; #endif - - - - - - \ No newline at end of file diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h index 38a9dc2ea..ba7b9b366 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h @@ -27,90 +27,77 @@ #ifndef GateLastVertexSplittingPostStepDoIt_h #define GateLastVertexSplittingPostStepDoIt_h - -#include "G4VEnergyLossProcess.hh" -#include "G4VEmProcess.hh" -#include "G4VParticleChange.hh" -#include "G4eplusAnnihilation.hh" -#include "G4PhysicalConstants.hh" -#include "G4MaterialCutsCouple.hh" -#include "G4Gamma.hh" #include "G4Electron.hh" -#include "G4Positron.hh" -#include "G4eeToTwoGammaModel.hh" #include "G4EmBiasingManager.hh" -#include "G4EntanglementAuxInfo.hh" -#include "G4eplusAnnihilationEntanglementClipBoard.hh" #include "G4EmParameters.hh" +#include "G4EntanglementAuxInfo.hh" +#include "G4Gamma.hh" +#include "G4MaterialCutsCouple.hh" +#include "G4PhysicalConstants.hh" #include "G4PhysicsModelCatalog.hh" +#include "G4Positron.hh" +#include "G4VEmProcess.hh" +#include "G4VEnergyLossProcess.hh" +#include "G4VParticleChange.hh" +#include "G4eeToTwoGammaModel.hh" +#include "G4eplusAnnihilation.hh" +#include "G4eplusAnnihilationEntanglementClipBoard.hh" #include <iostream> - - - class GateBremPostStepDoIt : public G4VEnergyLossProcess { -public : - -GateBremPostStepDoIt(); - -~ GateBremPostStepDoIt(); - -virtual G4VParticleChange * PostStepDoIt (const G4Track & track, const G4Step & step) override -{ - const G4MaterialCutsCouple* couple = step.GetPreStepPoint()->GetMaterialCutsCouple(); - currentCouple = couple; - G4VParticleChange* particleChange = G4VEnergyLossProcess::PostStepDoIt(track,step); - return particleChange; -} - -virtual G4VParticleChange * AlongStepDoIt (const G4Track & track, const G4Step & step) override -{ - G4VParticleChange* particleChange = G4VEnergyLossProcess::AlongStepDoIt(track,step); - return particleChange; -} - +public: + GateBremPostStepDoIt(); + + ~GateBremPostStepDoIt(); + + virtual G4VParticleChange *PostStepDoIt(const G4Track &track, + const G4Step &step) override { + const G4MaterialCutsCouple *couple = + step.GetPreStepPoint()->GetMaterialCutsCouple(); + currentCouple = couple; + G4VParticleChange *particleChange = + G4VEnergyLossProcess::PostStepDoIt(track, step); + return particleChange; + } + virtual G4VParticleChange *AlongStepDoIt(const G4Track &track, + const G4Step &step) override { + G4VParticleChange *particleChange = + G4VEnergyLossProcess::AlongStepDoIt(track, step); + return particleChange; + } }; - class GateGammaEmPostStepDoIt : public G4VEmProcess { -public : - -GateGammaEmPostStepDoIt(); - -~ GateGammaEmPostStepDoIt(); - -virtual G4VParticleChange * PostStepDoIt(const G4Track & track, const G4Step & step) override -{ - const G4MaterialCutsCouple* couple = step.GetPreStepPoint()->GetMaterialCutsCouple(); - currentCouple = couple; - G4VParticleChange* particleChange = G4VEmProcess::PostStepDoIt(track,step); - return particleChange; -} - - +public: + GateGammaEmPostStepDoIt(); + + ~GateGammaEmPostStepDoIt(); + + virtual G4VParticleChange *PostStepDoIt(const G4Track &track, + const G4Step &step) override { + const G4MaterialCutsCouple *couple = + step.GetPreStepPoint()->GetMaterialCutsCouple(); + currentCouple = couple; + G4VParticleChange *particleChange = G4VEmProcess::PostStepDoIt(track, step); + return particleChange; + } }; class GateplusannihilAtRestDoIt : public G4eplusAnnihilation { -public : - -GateplusannihilAtRestDoIt(); -~ GateplusannihilAtRestDoIt(); +public: + GateplusannihilAtRestDoIt(); + ~GateplusannihilAtRestDoIt(); -virtual G4VParticleChange* AtRestDoIt(const G4Track& track, - const G4Step& step) override -// Performs the e+ e- annihilation when both particles are assumed at rest. + virtual G4VParticleChange *AtRestDoIt(const G4Track &track, + const G4Step &step) override + // Performs the e+ e- annihilation when both particles are assumed at rest. { - G4Track copyTrack = G4Track(track); - copyTrack.SetStep(&step); - G4VParticleChange* particleChange = G4eplusAnnihilation::AtRestDoIt(copyTrack,step); - return particleChange; + G4Track copyTrack = G4Track(track); + copyTrack.SetStep(&step); + G4VParticleChange *particleChange = + G4eplusAnnihilation::AtRestDoIt(copyTrack, step); + return particleChange; } }; #endif - - - - - - \ No newline at end of file diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h index e3069bf2c..f39087b70 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h @@ -27,191 +27,131 @@ #ifndef SimpleContainer_h #define SimpleContainer_h - -#include <iostream> -#include "G4VEnergyLossProcess.hh" +#include "G4Electron.hh" +#include "G4EmBiasingManager.hh" +#include "G4EmParameters.hh" +#include "G4EntanglementAuxInfo.hh" +#include "G4Gamma.hh" +#include "G4MaterialCutsCouple.hh" +#include "G4PhysicalConstants.hh" +#include "G4PhysicsModelCatalog.hh" +#include "G4Positron.hh" #include "G4Track.hh" #include "G4VEmProcess.hh" +#include "G4VEnergyLossProcess.hh" #include "G4VParticleChange.hh" -#include "G4eplusAnnihilation.hh" -#include "G4PhysicalConstants.hh" -#include "G4MaterialCutsCouple.hh" -#include "G4Gamma.hh" -#include "G4Electron.hh" -#include "G4Positron.hh" #include "G4eeToTwoGammaModel.hh" -#include "G4EmBiasingManager.hh" -#include "G4EntanglementAuxInfo.hh" +#include "G4eplusAnnihilation.hh" #include "G4eplusAnnihilationEntanglementClipBoard.hh" -#include "G4EmParameters.hh" -#include "G4PhysicsModelCatalog.hh" - - -class SimpleContainer{ - -public : -SimpleContainer(G4String processName,G4double energy,G4ThreeVector momentum, G4ThreeVector position,G4ThreeVector polarization,G4String name,G4double weight,G4int trackStatus,G4int nbSec,G4String flag,G4double length, G4ThreeVector prePos){ - - fProcessNameToSplit = processName; - fEnergyToSplit = energy; - fMomentumToSplit = momentum; - fPositionToSplit = position; - fPolarizationToSplit = polarization; - fParticleNameToSplit = name; - fWeightToSplit = weight; - fTrackStatusToSplit = trackStatus; - fNumberOfSecondariesToSplit = nbSec; - fAnnihilProcessFlag = flag; - fStepLength = length; - fPrePosition = prePos; - -} - - -SimpleContainer(){} - -~SimpleContainer(){} - -void SetProcessNameToSplit(G4String processName){ - fProcessNameToSplit = processName; -} - -G4String GetProcessNameToSplit(){ - return fProcessNameToSplit; -} - -void SetEnergy(G4double energy){ - fEnergyToSplit =energy; -} - -G4double GetEnergy(){ - return fEnergyToSplit; -} - - -void SetWeight(G4double weight){ - fWeightToSplit =weight; -} - -G4double GetWeight(){ - return fWeightToSplit; -} - -void SetPolarization(G4ThreeVector polarization){ - fPolarizationToSplit = polarization; -} - -G4ThreeVector GetPolarization(){ - return fPolarizationToSplit; -} - -void SetMomentum(G4ThreeVector momentum){ - fMomentumToSplit =momentum; -} - -G4ThreeVector GetMomentum(){ - return fMomentumToSplit; -} +#include <iostream> -void SetVertexPosition(G4ThreeVector position){ - fPositionToSplit = position; -} +class SimpleContainer { -G4ThreeVector GetVertexPosition(){ - return fPositionToSplit; -} +public: + SimpleContainer(G4String processName, G4double energy, G4ThreeVector momentum, + G4ThreeVector position, G4ThreeVector polarization, + G4String name, G4double weight, G4int trackStatus, + G4int nbSec, G4String flag, G4double length, + G4ThreeVector prePos) { -void SetParticleNameToSplit(G4String name){ - fParticleNameToSplit = name; -} + fProcessNameToSplit = processName; + fEnergyToSplit = energy; + fMomentumToSplit = momentum; + fPositionToSplit = position; + fPolarizationToSplit = polarization; + fParticleNameToSplit = name; + fWeightToSplit = weight; + fTrackStatusToSplit = trackStatus; + fNumberOfSecondariesToSplit = nbSec; + fAnnihilProcessFlag = flag; + fStepLength = length; + fPrePosition = prePos; + } -G4String GetParticleNameToSplit(){ - return fParticleNameToSplit; -} + SimpleContainer() {} + ~SimpleContainer() {} -void SetTrackStatus(G4int trackStatus){ - fTrackStatusToSplit = trackStatus; -} + void SetProcessNameToSplit(G4String processName) { + fProcessNameToSplit = processName; + } + G4String GetProcessNameToSplit() { return fProcessNameToSplit; } -G4int GetTrackStatus(){ - return fTrackStatusToSplit; -} + void SetEnergy(G4double energy) { fEnergyToSplit = energy; } + G4double GetEnergy() { return fEnergyToSplit; } -void SetNbOfSecondaries(G4int nbSec){ - fNumberOfSecondariesToSplit = nbSec; -} + void SetWeight(G4double weight) { fWeightToSplit = weight; } -G4int GetNbOfSecondaries(){ - return fNumberOfSecondariesToSplit; -} + G4double GetWeight() { return fWeightToSplit; } -void SetAnnihilationFlag(G4String flag){ - fAnnihilProcessFlag = flag; -} + void SetPolarization(G4ThreeVector polarization) { + fPolarizationToSplit = polarization; + } -G4String GetAnnihilationFlag(){ - return fAnnihilProcessFlag; -} + G4ThreeVector GetPolarization() { return fPolarizationToSplit; } -void SetStepLength(G4double length){ - fStepLength = length; -} + void SetMomentum(G4ThreeVector momentum) { fMomentumToSplit = momentum; } -G4double GetStepLength(){ - return fStepLength; -} + G4ThreeVector GetMomentum() { return fMomentumToSplit; } + void SetVertexPosition(G4ThreeVector position) { + fPositionToSplit = position; + } + G4ThreeVector GetVertexPosition() { return fPositionToSplit; } -void SetPrePositionToSplit(G4ThreeVector prePos){ - fPrePosition = prePos; -} + void SetParticleNameToSplit(G4String name) { fParticleNameToSplit = name; } -G4ThreeVector GetPrePositionToSplit(){ - return fPrePosition; -} + G4String GetParticleNameToSplit() { return fParticleNameToSplit; } + void SetTrackStatus(G4int trackStatus) { fTrackStatusToSplit = trackStatus; } + G4int GetTrackStatus() { return fTrackStatusToSplit; } + void SetNbOfSecondaries(G4int nbSec) { fNumberOfSecondariesToSplit = nbSec; } -void DumpInfoToSplit(){ - std::cout<<"Particle name of the particle to split: "<<fParticleNameToSplit<<std::endl; - std::cout<<"Kinetic Energy of the particle to split: "<<fEnergyToSplit<<std::endl; - std::cout<<"Momentum of the particle to split: "<<fMomentumToSplit<<std::endl; - std::cout<<"Initial position of the particle to split: "<<fPositionToSplit<<std::endl; - std::cout<<"ProcessNameToSplit: "<<fProcessNameToSplit<<std::endl; - std::cout<<" "<<std::endl; + G4int GetNbOfSecondaries() { return fNumberOfSecondariesToSplit; } -} + void SetAnnihilationFlag(G4String flag) { fAnnihilProcessFlag = flag; } + G4String GetAnnihilationFlag() { return fAnnihilProcessFlag; } + void SetStepLength(G4double length) { fStepLength = length; } + G4double GetStepLength() { return fStepLength; } -private : + void SetPrePositionToSplit(G4ThreeVector prePos) { fPrePosition = prePos; } -G4String fParticleNameToSplit="None"; -G4String fProcessNameToSplit ="None"; -G4double fEnergyToSplit = 0; -G4ThreeVector fMomentumToSplit; -G4ThreeVector fPositionToSplit; -G4ThreeVector fPolarizationToSplit; -G4double fWeightToSplit; -G4int fTrackStatusToSplit; -G4int fNumberOfSecondariesToSplit; -G4String fAnnihilProcessFlag; -G4double fStepLength; -G4ThreeVector fPrePosition; + G4ThreeVector GetPrePositionToSplit() { return fPrePosition; } + void DumpInfoToSplit() { + std::cout << "Particle name of the particle to split: " + << fParticleNameToSplit << std::endl; + std::cout << "Kinetic Energy of the particle to split: " << fEnergyToSplit + << std::endl; + std::cout << "Momentum of the particle to split: " << fMomentumToSplit + << std::endl; + std::cout << "Initial position of the particle to split: " + << fPositionToSplit << std::endl; + std::cout << "ProcessNameToSplit: " << fProcessNameToSplit << std::endl; + std::cout << " " << std::endl; + } +private: + G4String fParticleNameToSplit = "None"; + G4String fProcessNameToSplit = "None"; + G4double fEnergyToSplit = 0; + G4ThreeVector fMomentumToSplit; + G4ThreeVector fPositionToSplit; + G4ThreeVector fPolarizationToSplit; + G4double fWeightToSplit; + G4int fTrackStatusToSplit; + G4int fNumberOfSecondariesToSplit; + G4String fAnnihilProcessFlag; + G4double fStepLength; + G4ThreeVector fPrePosition; }; #endif - - - - - - \ No newline at end of file diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp index 990a5629f..a1d281d47 100644 --- a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp +++ b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp @@ -119,11 +119,9 @@ void GateOptnForceFreeFlight ::AlongMoveBy( { G4String processName = callingProcess->GetWrappedProcess()->GetProcessName(); - if (processName != "Rayl"){ - fWeightChange[processName] = - weightChange; - } - else { + if (processName != "Rayl") { + fWeightChange[processName] = weightChange; + } else { fWeightChange[processName] = 1; } } diff --git a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp index ba894cf04..8c22b6399 100644 --- a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp +++ b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp @@ -49,57 +49,63 @@ //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptnVGenericSplitting:: - GateOptnVGenericSplitting(G4String name) +GateOptnVGenericSplitting::GateOptnVGenericSplitting(G4String name) : G4VBiasingOperation(name), fParticleChange() {} //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptnVGenericSplitting:: - ~GateOptnVGenericSplitting() {} +GateOptnVGenericSplitting::~GateOptnVGenericSplitting() {} +void GateOptnVGenericSplitting::TrackInitializationChargedParticle( + G4ParticleChange *particleChange, G4VParticleChange *processFinalState, + const G4Track *track, G4double split) { -void GateOptnVGenericSplitting::TrackInitializationChargedParticle(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split) { - - G4ParticleChangeForLoss* processFinalStateForLoss =( G4ParticleChangeForLoss* ) processFinalState ; + G4ParticleChangeForLoss *processFinalStateForLoss = + (G4ParticleChangeForLoss *)processFinalState; particleChange->Initialize(*track); - particleChange->ProposeTrackStatus(processFinalStateForLoss->GetTrackStatus() ); - particleChange->ProposeEnergy(processFinalStateForLoss->GetProposedKineticEnergy() ); - particleChange->ProposeMomentumDirection(processFinalStateForLoss->GetProposedMomentumDirection()); + particleChange->ProposeTrackStatus( + processFinalStateForLoss->GetTrackStatus()); + particleChange->ProposeEnergy( + processFinalStateForLoss->GetProposedKineticEnergy()); + particleChange->ProposeMomentumDirection( + processFinalStateForLoss->GetProposedMomentumDirection()); particleChange->SetNumberOfSecondaries(fSplittingFactor); particleChange->SetSecondaryWeightByProcess(true); processFinalStateForLoss->Clear(); } -void GateOptnVGenericSplitting::TrackInitializationGamma(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split) { - G4ParticleChangeForGamma* processFinalStateForGamma = (G4ParticleChangeForGamma *)processFinalState; +void GateOptnVGenericSplitting::TrackInitializationGamma( + G4ParticleChange *particleChange, G4VParticleChange *processFinalState, + const G4Track *track, G4double split) { + G4ParticleChangeForGamma *processFinalStateForGamma = + (G4ParticleChangeForGamma *)processFinalState; particleChange->Initialize(*track); - particleChange->ProposeTrackStatus(processFinalStateForGamma->GetTrackStatus() ); - particleChange->ProposeEnergy(processFinalStateForGamma->GetProposedKineticEnergy() ); - particleChange->ProposeMomentumDirection(processFinalStateForGamma->GetProposedMomentumDirection() ); + particleChange->ProposeTrackStatus( + processFinalStateForGamma->GetTrackStatus()); + particleChange->ProposeEnergy( + processFinalStateForGamma->GetProposedKineticEnergy()); + particleChange->ProposeMomentumDirection( + processFinalStateForGamma->GetProposedMomentumDirection()); particleChange->SetNumberOfSecondaries(fSplittingFactor); particleChange->SetSecondaryWeightByProcess(true); processFinalStateForGamma->Clear(); - - } -G4double GateOptnVGenericSplitting::RussianRouletteForAngleSurvival(G4ThreeVector dir,G4ThreeVector vectorDirector,G4double maxTheta,G4double split){ -G4double cosTheta =vectorDirector * dir; -G4double theta = std::acos(cosTheta); -G4double weightToApply = 1; -if (theta > maxTheta){ - G4double probability = G4UniformRand(); - if (probability <= 1 / split) { - weightToApply = split; +G4double GateOptnVGenericSplitting::RussianRouletteForAngleSurvival( + G4ThreeVector dir, G4ThreeVector vectorDirector, G4double maxTheta, + G4double split) { + G4double cosTheta = vectorDirector * dir; + G4double theta = std::acos(cosTheta); + G4double weightToApply = 1; + if (theta > maxTheta) { + G4double probability = G4UniformRand(); + if (probability <= 1 / split) { + weightToApply = split; + } else { + weightToApply = 0; + } } - else{ - weightToApply = 0; - } -} -return weightToApply; - + return weightToApply; } - //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h index d7cebd78a..b89f0baee 100644 --- a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h +++ b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h @@ -70,15 +70,20 @@ class GateOptnVGenericSplitting : public G4VBiasingOperation { return 0; } + // ---------------------------------------------- + // -- Methods for the generic splitting + // ---------------------------------------------- -// ---------------------------------------------- -// -- Methods for the generic splitting -// ---------------------------------------------- - -void TrackInitializationChargedParticle(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split); -void TrackInitializationGamma(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split); -static G4double RussianRouletteForAngleSurvival(G4ThreeVector dir,G4ThreeVector vectorDirector,G4double maxTheta,G4double split); - + void TrackInitializationChargedParticle(G4ParticleChange *particleChange, + G4VParticleChange *processFinalState, + const G4Track *track, G4double split); + void TrackInitializationGamma(G4ParticleChange *particleChange, + G4VParticleChange *processFinalState, + const G4Track *track, G4double split); + static G4double RussianRouletteForAngleSurvival(G4ThreeVector dir, + G4ThreeVector vectorDirector, + G4double maxTheta, + G4double split); public: // ---------------------------------------------- diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp index 13c5b3cfb..f973cc4aa 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp @@ -50,12 +50,9 @@ #include "G4eplusAnnihilation.hh" #include "GateOptnPairProdSplitting.h" #include "GateOptnScatteredGammaSplitting.h" +#include "GateOptnVGenericSplitting.h" #include "GateOptneBremSplitting.h" #include "GateOptrComptPseudoTransportationActor.h" -#include "G4UImanager.hh" -#include "G4eplusAnnihilation.hh" -#include "GateOptnVGenericSplitting.h" - //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... @@ -144,9 +141,8 @@ void GateOptrComptPseudoTransportationActor::StartSimulationAction() { fPairProdSplittingOperation->SetSplittingFactor(fSplittingFactor); - fFreeFlightOperation->SetRussianRouletteForWeights(fRussianRouletteForWeights); - - + fFreeFlightOperation->SetRussianRouletteForWeights( + fRussianRouletteForWeights); } void GateOptrComptPseudoTransportationActor::StartRun() { @@ -171,38 +167,47 @@ void GateOptrComptPseudoTransportationActor::StartRun() { void GateOptrComptPseudoTransportationActor::SteppingAction(G4Step *step) { G4String creationProcessName = "None"; - if (step->GetTrack()->GetCreatorProcess() != 0){ - creationProcessName = step->GetTrack()->GetCreatorProcess()->GetProcessName(); - + if (step->GetTrack()->GetCreatorProcess() != 0) { + creationProcessName = + step->GetTrack()->GetCreatorProcess()->GetProcessName(); } - -if ((fIsFirstStep) && (fRussianRouletteForAngle)){ - G4String LogicalVolumeNameOfCreation = step->GetTrack()->GetLogicalVolumeAtVertex()->GetName(); - if (std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeNameOfCreation ) !=fNameOfBiasedLogicalVolume.end()){ - if (creationProcessName == "biasWrapper(annihil)"){ + + if ((fIsFirstStep) && (fRussianRouletteForAngle)) { + G4String LogicalVolumeNameOfCreation = + step->GetTrack()->GetLogicalVolumeAtVertex()->GetName(); + if (std::find(fNameOfBiasedLogicalVolume.begin(), + fNameOfBiasedLogicalVolume.end(), + LogicalVolumeNameOfCreation) != + fNameOfBiasedLogicalVolume.end()) { + if (creationProcessName == "biasWrapper(annihil)") { auto dir = step->GetPreStepPoint()->GetMomentumDirection(); - G4double w = GateOptnVGenericSplitting::RussianRouletteForAngleSurvival(dir,fVectorDirector,fMaxTheta,fSplittingFactor); - if (w == 0) - { + G4double w = GateOptnVGenericSplitting::RussianRouletteForAngleSurvival( + dir, fVectorDirector, fMaxTheta, fSplittingFactor); + if (w == 0) { step->GetTrack()->SetTrackStatus(fStopAndKill); - } - else { + } else { step->GetTrack()->SetWeight(step->GetTrack()->GetWeight() * w); } } + } } -} - -if ((isSplitted == true) && (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary)) { - G4String LogicalVolumeName = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - if (!(std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeName ) !=fNameOfBiasedLogicalVolume.end()) - && (LogicalVolumeName != fMotherVolumeName)) { + + if ((isSplitted == true) && + (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary)) { + G4String LogicalVolumeName = step->GetPostStepPoint() + ->GetPhysicalVolume() + ->GetLogicalVolume() + ->GetName(); + if (!(std::find(fNameOfBiasedLogicalVolume.begin(), + fNameOfBiasedLogicalVolume.end(), + LogicalVolumeName) != fNameOfBiasedLogicalVolume.end()) && + (LogicalVolumeName != fMotherVolumeName)) { step->GetTrack()->SetTrackStatus(fStopAndKill); isSplitted = false; + } } -} -fIsFirstStep = false; + fIsFirstStep = false; } void GateOptrComptPseudoTransportationActor::BeginOfEventAction( @@ -218,14 +223,17 @@ void GateOptrComptPseudoTransportationActor::StartTracking( const G4Track *track) { G4String creationProcessName = "None"; fIsFirstStep = true; - if (track->GetCreatorProcess() != 0){ + if (track->GetCreatorProcess() != 0) { creationProcessName = track->GetCreatorProcess()->GetProcessName(); } - - if (track->GetParticleDefinition()->GetParticleName() == "gamma"){ - G4String LogicalVolumeNameOfCreation = track->GetLogicalVolumeAtVertex()->GetName(); - if (std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeNameOfCreation ) !=fNameOfBiasedLogicalVolume.end()){ + if (track->GetParticleDefinition()->GetParticleName() == "gamma") { + G4String LogicalVolumeNameOfCreation = + track->GetLogicalVolumeAtVertex()->GetName(); + if (std::find(fNameOfBiasedLogicalVolume.begin(), + fNameOfBiasedLogicalVolume.end(), + LogicalVolumeNameOfCreation) != + fNameOfBiasedLogicalVolume.end()) { fInitialWeight = track->GetWeight(); fFreeFlightOperation->SetInitialWeight(fInitialWeight); @@ -299,8 +307,10 @@ GateOptrComptPseudoTransportationActor::ProposeFinalStateBiasingOperation( return feBremSplittingOperation; } - if (!(std::find(fCreationProcessNameList.begin(), fCreationProcessNameList.end(),CreationProcessName) != fCreationProcessNameList.end())){ - if (callingProcess->GetWrappedProcess()->GetProcessName() == "compt"){ + if (!(std::find(fCreationProcessNameList.begin(), + fCreationProcessNameList.end(), + CreationProcessName) != fCreationProcessNameList.end())) { + if (callingProcess->GetWrappedProcess()->GetProcessName() == "compt") { isSplitted = true; return fScatteredGammaSplittingOperation; diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h index 629d31b69..c463a4e2c 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h @@ -85,13 +85,14 @@ class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, G4int ftrackIDAtTheEntrance; G4int fEventID; G4double fEventIDKineticEnergy; - G4bool ftestbool= false; + G4bool ftestbool = false; G4bool fIsFirstStep = false; - const G4VProcess* fAnnihilation =nullptr; + const G4VProcess *fAnnihilation = nullptr; std::vector<G4String> fNameOfBiasedLogicalVolume = {}; std::vector<G4int> v_EventID = {}; - std::vector<G4String> fCreationProcessNameList = {"biasWrapper(compt)", "biasWrapper(eBrem)","biasWrapper(annihil)"}; + std::vector<G4String> fCreationProcessNameList = { + "biasWrapper(compt)", "biasWrapper(eBrem)", "biasWrapper(annihil)"}; // Unused but mandatory diff --git a/core/opengate_core/opengate_lib/GateSourceManager.cpp b/core/opengate_core/opengate_lib/GateSourceManager.cpp index d0772fe15..61dfb8361 100644 --- a/core/opengate_core/opengate_lib/GateSourceManager.cpp +++ b/core/opengate_core/opengate_lib/GateSourceManager.cpp @@ -241,9 +241,6 @@ void GateSourceManager::PrepareRunToStart(int run_id) { : std::to_string(G4Threading::G4GetThreadId())); } - - - void GateSourceManager::PrepareNextSource() { auto &l = fThreadLocalData.Get(); l.fNextActiveSource = nullptr; diff --git a/core/opengate_core/opengate_lib/GateSourceManager.h b/core/opengate_core/opengate_lib/GateSourceManager.h index 1b559a07b..342cbaeab 100644 --- a/core/opengate_core/opengate_lib/GateSourceManager.h +++ b/core/opengate_core/opengate_lib/GateSourceManager.h @@ -64,20 +64,19 @@ class GateSourceManager : public G4VUserPrimaryGeneratorAction { // Return a source GateVSource *FindSourceByName(std::string name) const; - - - G4String GetActiveSourceName(){ - auto &l = fThreadLocalData.Get(); - if (l.fNextActiveSource !=0){ + + G4String GetActiveSourceName() { + auto &l = fThreadLocalData.Get(); + if (l.fNextActiveSource != 0) { G4String name = l.fNextActiveSource->fName; return name; } return "None"; } - void SetActiveSourcebyName(G4String sourceName){ + void SetActiveSourcebyName(G4String sourceName) { auto &l = fThreadLocalData.Get(); - auto* source = FindSourceByName(sourceName); + auto *source = FindSourceByName(sourceName); l.fNextActiveSource = source; } diff --git a/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp index 4a6a8c130..85f22a9ef 100644 --- a/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp @@ -11,8 +11,9 @@ namespace py = pybind11; void init_GateLastVertexInteractionSplittingActor(py::module &m) { - py::class_<GateLastVertexInteractionSplittingActor, GateVActor, - std::unique_ptr<GateLastVertexInteractionSplittingActor, py::nodelete>>( + py::class_< + GateLastVertexInteractionSplittingActor, GateVActor, + std::unique_ptr<GateLastVertexInteractionSplittingActor, py::nodelete>>( m, "GateLastVertexInteractionSplittingActor") .def_readwrite( "fListOfVolumeAncestor", diff --git a/core/opengate_core/opengate_lib/tree.hh b/core/opengate_core/opengate_lib/tree.hh index 4f78d5b8a..ef78833de 100644 --- a/core/opengate_core/opengate_lib/tree.hh +++ b/core/opengate_core/opengate_lib/tree.hh @@ -4,7 +4,7 @@ // Copyright (C) 2001-2024 Kasper Peeters <kasper@phi-sci.com> // Distributed under the GNU General Public License version 3. // -// Special permission to use tree.hh under the conditions of a +// Special permission to use tree.hh under the conditions of a // different license can be requested from the author. /** \mainpage tree.hh @@ -18,1460 +18,1508 @@ nodes. Various types of iterators are provided (post-order, pre-order, and others). Where possible the access methods are compatible with the STL or alternative algorithms are - available. + available. */ - #ifndef tree_hh_ #define tree_hh_ +#include <algorithm> #include <cassert> -#include <memory> -#include <stdexcept> +#include <cstddef> #include <iterator> -#include <set> +#include <memory> #include <queue> -#include <algorithm> -#include <cstddef> +#include <set> +#include <stdexcept> #include <string> -/// A node in the tree, combining links to other nodes as well as the actual data. -template<class T> +/// A node in the tree, combining links to other nodes as well as the actual +/// data. +template <class T> class tree_node_ { // size: 5*4=20 bytes (on 32 bit arch), can be reduced by 8. - public: - tree_node_(); - tree_node_(const T&); - tree_node_(T&&); - - tree_node_<T> *parent; - tree_node_<T> *first_child, *last_child; - tree_node_<T> *prev_sibling, *next_sibling; - T data; -}; - -template<class T> +public: + tree_node_(); + tree_node_(const T &); + tree_node_(T &&); + + tree_node_<T> *parent; + tree_node_<T> *first_child, *last_child; + tree_node_<T> *prev_sibling, *next_sibling; + T data; +}; + +template <class T> tree_node_<T>::tree_node_() - : parent(0), first_child(0), last_child(0), prev_sibling(0), next_sibling(0) - { - } - -template<class T> -tree_node_<T>::tree_node_(const T& val) - : parent(0), first_child(0), last_child(0), prev_sibling(0), next_sibling(0), data(val) - { - } - -template<class T> -tree_node_<T>::tree_node_(T&& val) - : parent(0), first_child(0), last_child(0), prev_sibling(0), next_sibling(0), data(val) - { - } + : parent(0), first_child(0), last_child(0), prev_sibling(0), + next_sibling(0) {} + +template <class T> +tree_node_<T>::tree_node_(const T &val) + : parent(0), first_child(0), last_child(0), prev_sibling(0), + next_sibling(0), data(val) {} + +template <class T> +tree_node_<T>::tree_node_(T &&val) + : parent(0), first_child(0), last_child(0), prev_sibling(0), + next_sibling(0), data(val) {} // Throw an exception with a stacktrace. -//template <class E> -//void throw_with_trace(const E& e) +// template <class E> +// void throw_with_trace(const E& e) // { // throw boost::enable_error_info(e) // << traced(boost::stacktrace::stacktrace()); // } class navigation_error : public std::logic_error { - public: - navigation_error(const std::string& s) : std::logic_error(s) - { -// assert(1==0); -// std::ostringstream str; -// std::cerr << boost::stacktrace::stacktrace() << std::endl; -// str << boost::stacktrace::stacktrace(); -// stacktrace=str.str(); - } - -// virtual const char *what() const noexcept override -// { -// return (std::logic_error::what()+std::string("; ")+stacktrace).c_str(); -// } -// -// std::string stacktrace; +public: + navigation_error(const std::string &s) : std::logic_error(s) { + // assert(1==0); + // std::ostringstream str; + // std::cerr << boost::stacktrace::stacktrace() << + //std::endl; str << boost::stacktrace::stacktrace(); stacktrace=str.str(); + } + + // virtual const char *what() const noexcept override + // { + // return (std::logic_error::what()+std::string("; + //")+stacktrace).c_str(); + // } + // + // std::string stacktrace; }; - -template <class T, class tree_node_allocator = std::allocator<tree_node_<T> > > + +template <class T, class tree_node_allocator = std::allocator<tree_node_<T>>> class tree { - protected: - typedef tree_node_<T> tree_node; - public: - /// Value of the data stored at a node. - typedef T value_type; - - class iterator_base; - class pre_order_iterator; - class post_order_iterator; - class sibling_iterator; - class leaf_iterator; - - tree(); // empty constructor - tree(const T&); // constructor setting given element as head - tree(const iterator_base&); - tree(const tree<T, tree_node_allocator>&); // copy constructor - tree(tree<T, tree_node_allocator>&&); // move constructor - ~tree(); - tree<T,tree_node_allocator>& operator=(const tree<T, tree_node_allocator>&); // copy assignment - tree<T,tree_node_allocator>& operator=(tree<T, tree_node_allocator>&&); // move assignment - - /// Base class for iterators, only pointers stored, no traversal logic. +protected: + typedef tree_node_<T> tree_node; + +public: + /// Value of the data stored at a node. + typedef T value_type; + + class iterator_base; + class pre_order_iterator; + class post_order_iterator; + class sibling_iterator; + class leaf_iterator; + + tree(); // empty constructor + tree(const T &); // constructor setting given element as head + tree(const iterator_base &); + tree(const tree<T, tree_node_allocator> &); // copy constructor + tree(tree<T, tree_node_allocator> &&); // move constructor + ~tree(); + tree<T, tree_node_allocator> & + operator=(const tree<T, tree_node_allocator> &); // copy assignment + tree<T, tree_node_allocator> & + operator=(tree<T, tree_node_allocator> &&); // move assignment + + /// Base class for iterators, only pointers stored, no traversal logic. #ifdef __SGI_STL_PORT - class iterator_base : public stlport::bidirectional_iterator<T, ptrdiff_t> { + class iterator_base : public stlport::bidirectional_iterator<T, ptrdiff_t> { #else - class iterator_base { + class iterator_base { #endif - public: - typedef T value_type; - typedef T* pointer; - typedef T& reference; - typedef size_t size_type; - typedef ptrdiff_t difference_type; - typedef std::bidirectional_iterator_tag iterator_category; - - iterator_base(); - iterator_base(tree_node *); - - T& operator*() const; - T* operator->() const; - - /// When called, the next increment/decrement skips children of this node. - void skip_children(); - void skip_children(bool skip); - /// Number of children of the node pointed to by the iterator. - unsigned int number_of_children() const; - - sibling_iterator begin() const; - sibling_iterator end() const; - - tree_node *node; - protected: - bool skip_current_children_; - }; - - /// Depth-first iterator, first accessing the node, then its children. - class pre_order_iterator : public iterator_base { - public: - pre_order_iterator(); - pre_order_iterator(tree_node *); - pre_order_iterator(const iterator_base&); - pre_order_iterator(const sibling_iterator&); - - bool operator==(const pre_order_iterator&) const; - bool operator!=(const pre_order_iterator&) const; - pre_order_iterator& operator++(); - pre_order_iterator& operator--(); - pre_order_iterator operator++(int); - pre_order_iterator operator--(int); - pre_order_iterator& operator+=(unsigned int); - pre_order_iterator& operator-=(unsigned int); - - pre_order_iterator& next_skip_children(); - }; - - /// Depth-first iterator, first accessing the children, then the node itself. - class post_order_iterator : public iterator_base { - public: - post_order_iterator(); - post_order_iterator(tree_node *); - post_order_iterator(const iterator_base&); - post_order_iterator(const sibling_iterator&); - - bool operator==(const post_order_iterator&) const; - bool operator!=(const post_order_iterator&) const; - post_order_iterator& operator++(); - post_order_iterator& operator--(); - post_order_iterator operator++(int); - post_order_iterator operator--(int); - post_order_iterator& operator+=(unsigned int); - post_order_iterator& operator-=(unsigned int); - - /// Set iterator to the first child as deep as possible down the tree. - void descend_all(); - }; - - /// Breadth-first iterator, using a queue - class breadth_first_queued_iterator : public iterator_base { - public: - breadth_first_queued_iterator(); - breadth_first_queued_iterator(tree_node *); - breadth_first_queued_iterator(const iterator_base&); - - bool operator==(const breadth_first_queued_iterator&) const; - bool operator!=(const breadth_first_queued_iterator&) const; - breadth_first_queued_iterator& operator++(); - breadth_first_queued_iterator operator++(int); - breadth_first_queued_iterator& operator+=(unsigned int); - - private: - std::queue<tree_node *> traversal_queue; - }; - - /// The default iterator types throughout the tree class. - typedef pre_order_iterator iterator; - typedef breadth_first_queued_iterator breadth_first_iterator; - - /// Iterator which traverses only the nodes at a given depth from the root. - class fixed_depth_iterator : public iterator_base { - public: - fixed_depth_iterator(); - fixed_depth_iterator(tree_node *); - fixed_depth_iterator(const iterator_base&); - fixed_depth_iterator(const sibling_iterator&); - fixed_depth_iterator(const fixed_depth_iterator&); - - void swap(fixed_depth_iterator&, fixed_depth_iterator&); - fixed_depth_iterator& operator=(fixed_depth_iterator); - - bool operator==(const fixed_depth_iterator&) const; - bool operator!=(const fixed_depth_iterator&) const; - fixed_depth_iterator& operator++(); - fixed_depth_iterator& operator--(); - fixed_depth_iterator operator++(int); - fixed_depth_iterator operator--(int); - fixed_depth_iterator& operator+=(unsigned int); - fixed_depth_iterator& operator-=(unsigned int); - - tree_node *top_node; - }; - - /// Iterator which traverses only the nodes which are siblings of each other. - class sibling_iterator : public iterator_base { - public: - sibling_iterator(); - sibling_iterator(tree_node *); - sibling_iterator(const sibling_iterator&); - sibling_iterator(const iterator_base&); - - void swap(sibling_iterator&, sibling_iterator&); - sibling_iterator& operator=(sibling_iterator); - - bool operator==(const sibling_iterator&) const; - bool operator!=(const sibling_iterator&) const; - sibling_iterator& operator++(); - sibling_iterator& operator--(); - sibling_iterator operator++(int); - sibling_iterator operator--(int); - sibling_iterator& operator+=(unsigned int); - sibling_iterator& operator-=(unsigned int); - - tree_node *range_first() const; - tree_node *range_last() const; - tree_node *parent_; - private: - void set_parent_(); - }; - - /// Iterator which traverses only the leaves. - class leaf_iterator : public iterator_base { - public: - leaf_iterator(); - leaf_iterator(tree_node *, tree_node *top=0); - leaf_iterator(const sibling_iterator&); - leaf_iterator(const iterator_base&); - - bool operator==(const leaf_iterator&) const; - bool operator!=(const leaf_iterator&) const; - leaf_iterator& operator++(); - leaf_iterator& operator--(); - leaf_iterator operator++(int); - leaf_iterator operator--(int); - leaf_iterator& operator+=(unsigned int); - leaf_iterator& operator-=(unsigned int); - private: - tree_node *top_node; - }; - - /// Return iterator to the beginning of the tree. - inline pre_order_iterator begin() const; - /// Return iterator to the end of the tree. - inline pre_order_iterator end() const; - /// Return post-order iterator to the beginning of the tree. - post_order_iterator begin_post() const; - /// Return post-order end iterator of the tree. - post_order_iterator end_post() const; - /// Return fixed-depth iterator to the first node at a given depth from the given iterator. - /// If 'walk_back=true', a depth=0 iterator will be taken from the beginning of the sibling - /// range, not the current node. - fixed_depth_iterator begin_fixed(const iterator_base&, unsigned int, bool walk_back=true) const; - /// Return fixed-depth end iterator. - fixed_depth_iterator end_fixed(const iterator_base&, unsigned int) const; - /// Return breadth-first iterator to the first node at a given depth. - breadth_first_queued_iterator begin_breadth_first() const; - /// Return breadth-first end iterator. - breadth_first_queued_iterator end_breadth_first() const; - /// Return sibling iterator to the first child of given node. - static sibling_iterator begin(const iterator_base&); - /// Return sibling end iterator for children of given node. - static sibling_iterator end(const iterator_base&); - /// Return leaf iterator to the first leaf of the tree. - leaf_iterator begin_leaf() const; - /// Return leaf end iterator for entire tree. - leaf_iterator end_leaf() const; - /// Return leaf iterator to the first leaf of the subtree at the given node. - leaf_iterator begin_leaf(const iterator_base& top) const; - /// Return leaf end iterator for the subtree at the given node. - leaf_iterator end_leaf(const iterator_base& top) const; - - typedef std::vector<int> path_t; - /// Return a path (to be taken from the 'top' node) corresponding to a node in the tree. - /// The first integer in path_t is the number of steps you need to go 'right' in the sibling - /// chain (so 0 if we go straight to the children). - path_t path_from_iterator(const iterator_base& iter, const iterator_base& top) const; - /// Return an iterator given a path from the 'top' node. - iterator iterator_from_path(const path_t&, const iterator_base& top) const; - - /// Return iterator to the parent of a node. Throws a `navigation_error` if the node - /// does not have a parent. - template<typename iter> static iter parent(iter); - /// Return iterator to the previous sibling of a node. - template<typename iter> static iter previous_sibling(iter); - /// Return iterator to the next sibling of a node. - template<typename iter> static iter next_sibling(iter); - /// Return iterator to the next node at a given depth. - template<typename iter> iter next_at_same_depth(iter) const; - - /// Erase all nodes of the tree. - void clear(); - /// Erase element at position pointed to by iterator, return incremented iterator. - template<typename iter> iter erase(iter); - /// Erase all children of the node pointed to by iterator. - void erase_children(const iterator_base&); - /// Erase all siblings to the right of the iterator. - void erase_right_siblings(const iterator_base&); - /// Erase all siblings to the left of the iterator. - void erase_left_siblings(const iterator_base&); - - /// Insert empty node as last/first child of node pointed to by position. - template<typename iter> iter append_child(iter position); - template<typename iter> iter prepend_child(iter position); - /// Insert node as last/first child of node pointed to by position. - template<typename iter> iter append_child(iter position, const T& x); - template<typename iter> iter append_child(iter position, T&& x); - template<typename iter> iter prepend_child(iter position, const T& x); - template<typename iter> iter prepend_child(iter position, T&& x); - /// Append the node (plus its children) at other_position as last/first child of position. - template<typename iter> iter append_child(iter position, iter other_position); - template<typename iter> iter prepend_child(iter position, iter other_position); - /// Append the nodes in the from-to range (plus their children) as last/first children of position. - template<typename iter> iter append_children(iter position, sibling_iterator from, sibling_iterator to); - template<typename iter> iter prepend_children(iter position, sibling_iterator from, sibling_iterator to); - - /// Short-hand to insert topmost node in otherwise empty tree. - pre_order_iterator set_head(const T& x); - pre_order_iterator set_head(T&& x); - /// Insert node as previous sibling of node pointed to by position. - template<typename iter> iter insert(iter position, const T& x); - template<typename iter> iter insert(iter position, T&& x); - /// Specialisation of previous member. - sibling_iterator insert(sibling_iterator position, const T& x); - sibling_iterator insert(sibling_iterator position, T&& x); - /// Insert node (with children) pointed to by subtree as previous sibling of node pointed to by position. - /// Does not change the subtree itself (use move_in or move_in_below for that). - template<typename iter> iter insert_subtree(iter position, const iterator_base& subtree); - /// Insert node as next sibling of node pointed to by position. - template<typename iter> iter insert_after(iter position, const T& x); - template<typename iter> iter insert_after(iter position, T&& x); - /// Insert node (with children) pointed to by subtree as next sibling of node pointed to by position. - template<typename iter> iter insert_subtree_after(iter position, const iterator_base& subtree); - - /// Replace node at 'position' with other node (keeping same children); 'position' becomes invalid. - template<typename iter> iter replace(iter position, const T& x); - /// Replace node at 'position' with subtree starting at 'from' (do not erase subtree at 'from'); see above. - template<typename iter> iter replace(iter position, const iterator_base& from); - /// Replace string of siblings (plus their children) with copy of a new string (with children); see above - sibling_iterator replace(sibling_iterator orig_begin, sibling_iterator orig_end, - sibling_iterator new_begin, sibling_iterator new_end); - - /// Move all children of node at 'position' to be siblings, returns position. - template<typename iter> iter flatten(iter position); - /// Move nodes in range to be children of 'position'. - template<typename iter> iter reparent(iter position, sibling_iterator begin, sibling_iterator end); - /// Move all child nodes of 'from' to be children of 'position'. - template<typename iter> iter reparent(iter position, iter from); - - /// Replace node with a new node, making the old node (plus subtree) a child of the new node. - template<typename iter> iter wrap(iter position, const T& x); - /// Replace the range of sibling nodes (plus subtrees), making these children of the new node. - template<typename iter> iter wrap(iter from, iter to, const T& x); - - /// Move 'source' node (plus its children) to become the next sibling of 'target'. - template<typename iter> iter move_after(iter target, iter source); - /// Move 'source' node (plus its children) to become the previous sibling of 'target'. - template<typename iter> iter move_before(iter target, iter source); - sibling_iterator move_before(sibling_iterator target, sibling_iterator source); - /// Move 'source' node (plus its children) to become the node at 'target' (erasing the node at 'target'). - template<typename iter> iter move_ontop(iter target, iter source); - - /// Extract the subtree starting at the indicated node, removing it from the original tree. - tree move_out(iterator); - /// Inverse of take_out: inserts the given tree as previous sibling of indicated node by a - /// move operation, that is, the given tree becomes empty. Returns iterator to the top node. - template<typename iter> iter move_in(iter, tree&); - /// As above, but now make the tree the last child of the indicated node. - template<typename iter> iter move_in_below(iter, tree&); - /// As above, but now make the tree the nth child of the indicated node (if possible). - template<typename iter> iter move_in_as_nth_child(iter, size_t, tree&); - - /// Merge with other tree, creating new branches and leaves only if they are not already present. - void merge(sibling_iterator, sibling_iterator, sibling_iterator, sibling_iterator, - bool duplicate_leaves=false); - /// As above, but using two trees with a single top node at the 'to' and 'from' positions. - void merge(iterator to, iterator from, bool duplicate_leaves); - /// Sort (std::sort only moves values of nodes, this one moves children as well). - void sort(sibling_iterator from, sibling_iterator to, bool deep=false); - template<class StrictWeakOrdering> - void sort(sibling_iterator from, sibling_iterator to, StrictWeakOrdering comp, bool deep=false); - /// Compare two ranges of nodes (compares nodes as well as tree structure). - template<typename iter> - bool equal(const iter& one, const iter& two, const iter& three) const; - template<typename iter, class BinaryPredicate> - bool equal(const iter& one, const iter& two, const iter& three, BinaryPredicate) const; - template<typename iter> - bool equal_subtree(const iter& one, const iter& two) const; - template<typename iter, class BinaryPredicate> - bool equal_subtree(const iter& one, const iter& two, BinaryPredicate) const; - /// Extract a new tree formed by the range of siblings plus all their children. - tree subtree(sibling_iterator from, sibling_iterator to) const; - void subtree(tree&, sibling_iterator from, sibling_iterator to) const; - /// Exchange the node (plus subtree) with its sibling node (do nothing if no sibling present). - void swap(sibling_iterator it); - /// Exchange two nodes (plus subtrees). The iterators will remain valid and keep - /// pointing to the same nodes, which now sit at different locations in the tree. - void swap(iterator, iterator); - - /// Count the total number of nodes. - size_t size() const; - /// Count the total number of nodes below the indicated node (plus one). - size_t size(const iterator_base&) const; - /// Check if tree is empty. - bool empty() const; - /// Compute the depth to the root or to a fixed other iterator. - static int depth(const iterator_base&); - static int depth(const iterator_base&, const iterator_base&); - /// Compute the depth to the root, counting all levels for which predicate returns true. - template<class Predicate> - static int depth(const iterator_base&, Predicate p); - /// Compute the depth distance between two nodes, counting all levels for which predicate returns true. - template<class Predicate> - static int distance(const iterator_base& top, const iterator_base& bottom, Predicate p); - /// Determine the maximal depth of the tree. An empty tree has max_depth=-1. - int max_depth() const; - /// Determine the maximal depth of the tree with top node at the given position. - int max_depth(const iterator_base&) const; - /// Count the number of children of node at position. - static unsigned int number_of_children(const iterator_base&); - /// Count the number of siblings (left and right) of node at iterator. Total nodes at this level is +1. - unsigned int number_of_siblings(const iterator_base&) const; - /// Determine whether node at position is in the subtrees with indicated top node. - bool is_in_subtree(const iterator_base& position, const iterator_base& top) const; - /// Determine whether node at position is in the subtrees with root in the range. - bool is_in_subtree(const iterator_base& position, const iterator_base& begin, - const iterator_base& end) const; - /// Determine whether the iterator is an 'end' iterator and thus not actually pointing to a node. - bool is_valid(const iterator_base&) const; - /// Determine whether the iterator is one of the 'head' nodes at the top level, i.e. has no parent. - static bool is_head(const iterator_base&); - /// Find the lowest common ancestor of two nodes, that is, the deepest node such that - /// both nodes are descendants of it. - iterator lowest_common_ancestor(const iterator_base&, const iterator_base &) const; - - /// Determine the index of a node in the range of siblings to which it belongs. - unsigned int index(sibling_iterator it) const; - /// Inverse of 'index': return the n-th child of the node at position. - static sibling_iterator child(const iterator_base& position, unsigned int); - /// Return iterator to the sibling indicated by index - sibling_iterator sibling(const iterator_base& position, unsigned int) const; - - /// For debugging only: verify internal consistency by inspecting all pointers in the tree - /// (which will also trigger a valgrind error in case something got corrupted). - void debug_verify_consistency() const; - - /// Comparator class for iterators (compares pointer values; why doesn't this work automatically?) - class iterator_base_less { - public: - bool operator()(const typename tree<T, tree_node_allocator>::iterator_base& one, - const typename tree<T, tree_node_allocator>::iterator_base& two) const - { - return one.node < two.node; - } - }; - tree_node *head, *feet; // head/feet are always dummy; if an iterator points to them it is invalid - private: - tree_node_allocator alloc_; - void head_initialise_(); - void copy_(const tree<T, tree_node_allocator>& other); - - /// Comparator class for two nodes of a tree (used for sorting and searching). - template<class StrictWeakOrdering> - class compare_nodes { - public: - compare_nodes(StrictWeakOrdering comp) : comp_(comp) {} - - bool operator()(const tree_node *a, const tree_node *b) const - { - return comp_(a->data, b->data); - } - private: - StrictWeakOrdering comp_; - }; - }; - -//template <class T, class tree_node_allocator> -//class iterator_base_less { + public: + typedef T value_type; + typedef T *pointer; + typedef T &reference; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + typedef std::bidirectional_iterator_tag iterator_category; + + iterator_base(); + iterator_base(tree_node *); + + T &operator*() const; + T *operator->() const; + + /// When called, the next increment/decrement skips children of this node. + void skip_children(); + void skip_children(bool skip); + /// Number of children of the node pointed to by the iterator. + unsigned int number_of_children() const; + + sibling_iterator begin() const; + sibling_iterator end() const; + + tree_node *node; + + protected: + bool skip_current_children_; + }; + + /// Depth-first iterator, first accessing the node, then its children. + class pre_order_iterator : public iterator_base { + public: + pre_order_iterator(); + pre_order_iterator(tree_node *); + pre_order_iterator(const iterator_base &); + pre_order_iterator(const sibling_iterator &); + + bool operator==(const pre_order_iterator &) const; + bool operator!=(const pre_order_iterator &) const; + pre_order_iterator &operator++(); + pre_order_iterator &operator--(); + pre_order_iterator operator++(int); + pre_order_iterator operator--(int); + pre_order_iterator &operator+=(unsigned int); + pre_order_iterator &operator-=(unsigned int); + + pre_order_iterator &next_skip_children(); + }; + + /// Depth-first iterator, first accessing the children, then the node itself. + class post_order_iterator : public iterator_base { + public: + post_order_iterator(); + post_order_iterator(tree_node *); + post_order_iterator(const iterator_base &); + post_order_iterator(const sibling_iterator &); + + bool operator==(const post_order_iterator &) const; + bool operator!=(const post_order_iterator &) const; + post_order_iterator &operator++(); + post_order_iterator &operator--(); + post_order_iterator operator++(int); + post_order_iterator operator--(int); + post_order_iterator &operator+=(unsigned int); + post_order_iterator &operator-=(unsigned int); + + /// Set iterator to the first child as deep as possible down the tree. + void descend_all(); + }; + + /// Breadth-first iterator, using a queue + class breadth_first_queued_iterator : public iterator_base { + public: + breadth_first_queued_iterator(); + breadth_first_queued_iterator(tree_node *); + breadth_first_queued_iterator(const iterator_base &); + + bool operator==(const breadth_first_queued_iterator &) const; + bool operator!=(const breadth_first_queued_iterator &) const; + breadth_first_queued_iterator &operator++(); + breadth_first_queued_iterator operator++(int); + breadth_first_queued_iterator &operator+=(unsigned int); + + private: + std::queue<tree_node *> traversal_queue; + }; + + /// The default iterator types throughout the tree class. + typedef pre_order_iterator iterator; + typedef breadth_first_queued_iterator breadth_first_iterator; + + /// Iterator which traverses only the nodes at a given depth from the root. + class fixed_depth_iterator : public iterator_base { + public: + fixed_depth_iterator(); + fixed_depth_iterator(tree_node *); + fixed_depth_iterator(const iterator_base &); + fixed_depth_iterator(const sibling_iterator &); + fixed_depth_iterator(const fixed_depth_iterator &); + + void swap(fixed_depth_iterator &, fixed_depth_iterator &); + fixed_depth_iterator &operator=(fixed_depth_iterator); + + bool operator==(const fixed_depth_iterator &) const; + bool operator!=(const fixed_depth_iterator &) const; + fixed_depth_iterator &operator++(); + fixed_depth_iterator &operator--(); + fixed_depth_iterator operator++(int); + fixed_depth_iterator operator--(int); + fixed_depth_iterator &operator+=(unsigned int); + fixed_depth_iterator &operator-=(unsigned int); + + tree_node *top_node; + }; + + /// Iterator which traverses only the nodes which are siblings of each other. + class sibling_iterator : public iterator_base { + public: + sibling_iterator(); + sibling_iterator(tree_node *); + sibling_iterator(const sibling_iterator &); + sibling_iterator(const iterator_base &); + + void swap(sibling_iterator &, sibling_iterator &); + sibling_iterator &operator=(sibling_iterator); + + bool operator==(const sibling_iterator &) const; + bool operator!=(const sibling_iterator &) const; + sibling_iterator &operator++(); + sibling_iterator &operator--(); + sibling_iterator operator++(int); + sibling_iterator operator--(int); + sibling_iterator &operator+=(unsigned int); + sibling_iterator &operator-=(unsigned int); + + tree_node *range_first() const; + tree_node *range_last() const; + tree_node *parent_; + + private: + void set_parent_(); + }; + + /// Iterator which traverses only the leaves. + class leaf_iterator : public iterator_base { + public: + leaf_iterator(); + leaf_iterator(tree_node *, tree_node *top = 0); + leaf_iterator(const sibling_iterator &); + leaf_iterator(const iterator_base &); + + bool operator==(const leaf_iterator &) const; + bool operator!=(const leaf_iterator &) const; + leaf_iterator &operator++(); + leaf_iterator &operator--(); + leaf_iterator operator++(int); + leaf_iterator operator--(int); + leaf_iterator &operator+=(unsigned int); + leaf_iterator &operator-=(unsigned int); + + private: + tree_node *top_node; + }; + + /// Return iterator to the beginning of the tree. + inline pre_order_iterator begin() const; + /// Return iterator to the end of the tree. + inline pre_order_iterator end() const; + /// Return post-order iterator to the beginning of the tree. + post_order_iterator begin_post() const; + /// Return post-order end iterator of the tree. + post_order_iterator end_post() const; + /// Return fixed-depth iterator to the first node at a given depth from the + /// given iterator. If 'walk_back=true', a depth=0 iterator will be taken from + /// the beginning of the sibling range, not the current node. + fixed_depth_iterator begin_fixed(const iterator_base &, unsigned int, + bool walk_back = true) const; + /// Return fixed-depth end iterator. + fixed_depth_iterator end_fixed(const iterator_base &, unsigned int) const; + /// Return breadth-first iterator to the first node at a given depth. + breadth_first_queued_iterator begin_breadth_first() const; + /// Return breadth-first end iterator. + breadth_first_queued_iterator end_breadth_first() const; + /// Return sibling iterator to the first child of given node. + static sibling_iterator begin(const iterator_base &); + /// Return sibling end iterator for children of given node. + static sibling_iterator end(const iterator_base &); + /// Return leaf iterator to the first leaf of the tree. + leaf_iterator begin_leaf() const; + /// Return leaf end iterator for entire tree. + leaf_iterator end_leaf() const; + /// Return leaf iterator to the first leaf of the subtree at the given node. + leaf_iterator begin_leaf(const iterator_base &top) const; + /// Return leaf end iterator for the subtree at the given node. + leaf_iterator end_leaf(const iterator_base &top) const; + + typedef std::vector<int> path_t; + /// Return a path (to be taken from the 'top' node) corresponding to a node in + /// the tree. The first integer in path_t is the number of steps you need to + /// go 'right' in the sibling chain (so 0 if we go straight to the children). + path_t path_from_iterator(const iterator_base &iter, + const iterator_base &top) const; + /// Return an iterator given a path from the 'top' node. + iterator iterator_from_path(const path_t &, const iterator_base &top) const; + + /// Return iterator to the parent of a node. Throws a `navigation_error` if + /// the node does not have a parent. + template <typename iter> static iter parent(iter); + /// Return iterator to the previous sibling of a node. + template <typename iter> static iter previous_sibling(iter); + /// Return iterator to the next sibling of a node. + template <typename iter> static iter next_sibling(iter); + /// Return iterator to the next node at a given depth. + template <typename iter> iter next_at_same_depth(iter) const; + + /// Erase all nodes of the tree. + void clear(); + /// Erase element at position pointed to by iterator, return incremented + /// iterator. + template <typename iter> iter erase(iter); + /// Erase all children of the node pointed to by iterator. + void erase_children(const iterator_base &); + /// Erase all siblings to the right of the iterator. + void erase_right_siblings(const iterator_base &); + /// Erase all siblings to the left of the iterator. + void erase_left_siblings(const iterator_base &); + + /// Insert empty node as last/first child of node pointed to by position. + template <typename iter> iter append_child(iter position); + template <typename iter> iter prepend_child(iter position); + /// Insert node as last/first child of node pointed to by position. + template <typename iter> iter append_child(iter position, const T &x); + template <typename iter> iter append_child(iter position, T &&x); + template <typename iter> iter prepend_child(iter position, const T &x); + template <typename iter> iter prepend_child(iter position, T &&x); + /// Append the node (plus its children) at other_position as last/first child + /// of position. + template <typename iter> + iter append_child(iter position, iter other_position); + template <typename iter> + iter prepend_child(iter position, iter other_position); + /// Append the nodes in the from-to range (plus their children) as last/first + /// children of position. + template <typename iter> + iter append_children(iter position, sibling_iterator from, + sibling_iterator to); + template <typename iter> + iter prepend_children(iter position, sibling_iterator from, + sibling_iterator to); + + /// Short-hand to insert topmost node in otherwise empty tree. + pre_order_iterator set_head(const T &x); + pre_order_iterator set_head(T &&x); + /// Insert node as previous sibling of node pointed to by position. + template <typename iter> iter insert(iter position, const T &x); + template <typename iter> iter insert(iter position, T &&x); + /// Specialisation of previous member. + sibling_iterator insert(sibling_iterator position, const T &x); + sibling_iterator insert(sibling_iterator position, T &&x); + /// Insert node (with children) pointed to by subtree as previous sibling of + /// node pointed to by position. Does not change the subtree itself (use + /// move_in or move_in_below for that). + template <typename iter> + iter insert_subtree(iter position, const iterator_base &subtree); + /// Insert node as next sibling of node pointed to by position. + template <typename iter> iter insert_after(iter position, const T &x); + template <typename iter> iter insert_after(iter position, T &&x); + /// Insert node (with children) pointed to by subtree as next sibling of node + /// pointed to by position. + template <typename iter> + iter insert_subtree_after(iter position, const iterator_base &subtree); + + /// Replace node at 'position' with other node (keeping same children); + /// 'position' becomes invalid. + template <typename iter> iter replace(iter position, const T &x); + /// Replace node at 'position' with subtree starting at 'from' (do not erase + /// subtree at 'from'); see above. + template <typename iter> + iter replace(iter position, const iterator_base &from); + /// Replace string of siblings (plus their children) with copy of a new string + /// (with children); see above + sibling_iterator replace(sibling_iterator orig_begin, + sibling_iterator orig_end, + sibling_iterator new_begin, + sibling_iterator new_end); + + /// Move all children of node at 'position' to be siblings, returns position. + template <typename iter> iter flatten(iter position); + /// Move nodes in range to be children of 'position'. + template <typename iter> + iter reparent(iter position, sibling_iterator begin, sibling_iterator end); + /// Move all child nodes of 'from' to be children of 'position'. + template <typename iter> iter reparent(iter position, iter from); + + /// Replace node with a new node, making the old node (plus subtree) a child + /// of the new node. + template <typename iter> iter wrap(iter position, const T &x); + /// Replace the range of sibling nodes (plus subtrees), making these children + /// of the new node. + template <typename iter> iter wrap(iter from, iter to, const T &x); + + /// Move 'source' node (plus its children) to become the next sibling of + /// 'target'. + template <typename iter> iter move_after(iter target, iter source); + /// Move 'source' node (plus its children) to become the previous sibling of + /// 'target'. + template <typename iter> iter move_before(iter target, iter source); + sibling_iterator move_before(sibling_iterator target, + sibling_iterator source); + /// Move 'source' node (plus its children) to become the node at 'target' + /// (erasing the node at 'target'). + template <typename iter> iter move_ontop(iter target, iter source); + + /// Extract the subtree starting at the indicated node, removing it from the + /// original tree. + tree move_out(iterator); + /// Inverse of take_out: inserts the given tree as previous sibling of + /// indicated node by a move operation, that is, the given tree becomes empty. + /// Returns iterator to the top node. + template <typename iter> iter move_in(iter, tree &); + /// As above, but now make the tree the last child of the indicated node. + template <typename iter> iter move_in_below(iter, tree &); + /// As above, but now make the tree the nth child of the indicated node (if + /// possible). + template <typename iter> iter move_in_as_nth_child(iter, size_t, tree &); + + /// Merge with other tree, creating new branches and leaves only if they are + /// not already present. + void merge(sibling_iterator, sibling_iterator, sibling_iterator, + sibling_iterator, bool duplicate_leaves = false); + /// As above, but using two trees with a single top node at the 'to' and + /// 'from' positions. + void merge(iterator to, iterator from, bool duplicate_leaves); + /// Sort (std::sort only moves values of nodes, this one moves children as + /// well). + void sort(sibling_iterator from, sibling_iterator to, bool deep = false); + template <class StrictWeakOrdering> + void sort(sibling_iterator from, sibling_iterator to, StrictWeakOrdering comp, + bool deep = false); + /// Compare two ranges of nodes (compares nodes as well as tree structure). + template <typename iter> + bool equal(const iter &one, const iter &two, const iter &three) const; + template <typename iter, class BinaryPredicate> + bool equal(const iter &one, const iter &two, const iter &three, + BinaryPredicate) const; + template <typename iter> + bool equal_subtree(const iter &one, const iter &two) const; + template <typename iter, class BinaryPredicate> + bool equal_subtree(const iter &one, const iter &two, BinaryPredicate) const; + /// Extract a new tree formed by the range of siblings plus all their + /// children. + tree subtree(sibling_iterator from, sibling_iterator to) const; + void subtree(tree &, sibling_iterator from, sibling_iterator to) const; + /// Exchange the node (plus subtree) with its sibling node (do nothing if no + /// sibling present). + void swap(sibling_iterator it); + /// Exchange two nodes (plus subtrees). The iterators will remain valid and + /// keep pointing to the same nodes, which now sit at different locations in + /// the tree. + void swap(iterator, iterator); + + /// Count the total number of nodes. + size_t size() const; + /// Count the total number of nodes below the indicated node (plus one). + size_t size(const iterator_base &) const; + /// Check if tree is empty. + bool empty() const; + /// Compute the depth to the root or to a fixed other iterator. + static int depth(const iterator_base &); + static int depth(const iterator_base &, const iterator_base &); + /// Compute the depth to the root, counting all levels for which predicate + /// returns true. + template <class Predicate> + static int depth(const iterator_base &, Predicate p); + /// Compute the depth distance between two nodes, counting all levels for + /// which predicate returns true. + template <class Predicate> + static int distance(const iterator_base &top, const iterator_base &bottom, + Predicate p); + /// Determine the maximal depth of the tree. An empty tree has max_depth=-1. + int max_depth() const; + /// Determine the maximal depth of the tree with top node at the given + /// position. + int max_depth(const iterator_base &) const; + /// Count the number of children of node at position. + static unsigned int number_of_children(const iterator_base &); + /// Count the number of siblings (left and right) of node at iterator. Total + /// nodes at this level is +1. + unsigned int number_of_siblings(const iterator_base &) const; + /// Determine whether node at position is in the subtrees with indicated top + /// node. + bool is_in_subtree(const iterator_base &position, + const iterator_base &top) const; + /// Determine whether node at position is in the subtrees with root in the + /// range. + bool is_in_subtree(const iterator_base &position, const iterator_base &begin, + const iterator_base &end) const; + /// Determine whether the iterator is an 'end' iterator and thus not actually + /// pointing to a node. + bool is_valid(const iterator_base &) const; + /// Determine whether the iterator is one of the 'head' nodes at the top + /// level, i.e. has no parent. + static bool is_head(const iterator_base &); + /// Find the lowest common ancestor of two nodes, that is, the deepest node + /// such that both nodes are descendants of it. + iterator lowest_common_ancestor(const iterator_base &, + const iterator_base &) const; + + /// Determine the index of a node in the range of siblings to which it + /// belongs. + unsigned int index(sibling_iterator it) const; + /// Inverse of 'index': return the n-th child of the node at position. + static sibling_iterator child(const iterator_base &position, unsigned int); + /// Return iterator to the sibling indicated by index + sibling_iterator sibling(const iterator_base &position, unsigned int) const; + + /// For debugging only: verify internal consistency by inspecting all pointers + /// in the tree (which will also trigger a valgrind error in case something + /// got corrupted). + void debug_verify_consistency() const; + + /// Comparator class for iterators (compares pointer values; why doesn't this + /// work automatically?) + class iterator_base_less { + public: + bool operator()( + const typename tree<T, tree_node_allocator>::iterator_base &one, + const typename tree<T, tree_node_allocator>::iterator_base &two) const { + return one.node < two.node; + } + }; + tree_node *head, *feet; // head/feet are always dummy; if an iterator points + // to them it is invalid +private: + tree_node_allocator alloc_; + void head_initialise_(); + void copy_(const tree<T, tree_node_allocator> &other); + + /// Comparator class for two nodes of a tree (used for sorting and searching). + template <class StrictWeakOrdering> class compare_nodes { + public: + compare_nodes(StrictWeakOrdering comp) : comp_(comp) {} + + bool operator()(const tree_node *a, const tree_node *b) const { + return comp_(a->data, b->data); + } + + private: + StrictWeakOrdering comp_; + }; +}; + +// template <class T, class tree_node_allocator> +// class iterator_base_less { // public: -// bool operator()(const typename tree<T, tree_node_allocator>::iterator_base& one, -// const typename tree<T, tree_node_allocator>::iterator_base& two) const +// bool operator()(const typename tree<T, +//tree_node_allocator>::iterator_base& one, const typename tree<T, +//tree_node_allocator>::iterator_base& two) const // { -// txtout << "operatorclass<" << one.node < two.node << std::endl; -// return one.node < two.node; +// txtout << "operatorclass<" << one.node < two.node << +//std::endl; return one.node < two.node; // } -//}; +// }; // template <class T, class tree_node_allocator> // bool operator<(const typename tree<T, tree_node_allocator>::iterator& one, -// const typename tree<T, tree_node_allocator>::iterator& two) +// const typename tree<T, +// tree_node_allocator>::iterator& two) // { // txtout << "operator< " << one.node < two.node << std::endl; // if(one.node < two.node) return true; // return false; // } -// +// // template <class T, class tree_node_allocator> // bool operator==(const typename tree<T, tree_node_allocator>::iterator& one, -// const typename tree<T, tree_node_allocator>::iterator& two) +// const typename tree<T, +// tree_node_allocator>::iterator& two) // { // txtout << "operator== " << one.node == two.node << std::endl; // if(one.node == two.node) return true; // return false; // } -// +// // template <class T, class tree_node_allocator> -// bool operator>(const typename tree<T, tree_node_allocator>::iterator_base& one, -// const typename tree<T, tree_node_allocator>::iterator_base& two) +// bool operator>(const typename tree<T, tree_node_allocator>::iterator_base& +// one, const typename tree<T, tree_node_allocator>::iterator_base& two) // { // txtout << "operator> " << one.node < two.node << std::endl; // if(one.node > two.node) return true; // return false; // } +// Tree +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree() { + head_initialise_(); +} -// Tree +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree(const T &x) { + head_initialise_(); + set_head(x); +} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree(tree<T, tree_node_allocator> &&x) { + head_initialise_(); + if (x.head->next_sibling != x.feet) { // move tree if non-empty only + head->next_sibling = x.head->next_sibling; + feet->prev_sibling = x.feet->prev_sibling; + x.head->next_sibling->prev_sibling = head; + x.feet->prev_sibling->next_sibling = feet; + x.head->next_sibling = x.feet; + x.feet->prev_sibling = x.head; + } +} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree(const iterator_base &other) { + head_initialise_(); + set_head((*other)); + replace(begin(), other); +} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::~tree() { + clear(); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, head); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, feet); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, head, 1); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, feet, 1); +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::head_initialise_() { + head = std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + feet = std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, head, + tree_node_<T>()); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, feet, + tree_node_<T>()); + + head->parent = 0; + head->first_child = 0; + head->last_child = 0; + head->prev_sibling = 0; // head; + head->next_sibling = feet; // head; + + feet->parent = 0; + feet->first_child = 0; + feet->last_child = 0; + feet->prev_sibling = head; + feet->next_sibling = 0; +} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator> &tree<T, tree_node_allocator>::operator=( + const tree<T, tree_node_allocator> &other) { + if (this != &other) + copy_(other); + return *this; +} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator> & +tree<T, tree_node_allocator>::operator=(tree<T, tree_node_allocator> &&x) { + if (this != &x) { + clear(); // clear any existing data. + + head->next_sibling = x.head->next_sibling; + feet->prev_sibling = x.feet->prev_sibling; + x.head->next_sibling->prev_sibling = head; + x.feet->prev_sibling->next_sibling = feet; + x.head->next_sibling = x.feet; + x.feet->prev_sibling = x.head; + } + return *this; +} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree(const tree<T, tree_node_allocator> &other) { + head_initialise_(); + copy_(other); +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::copy_( + const tree<T, tree_node_allocator> &other) { + clear(); + pre_order_iterator it = other.begin(), to = begin(); + while (it != other.end()) { + to = insert(to, (*it)); + it.skip_children(); + ++it; + } + to = begin(); + it = other.begin(); + while (it != other.end()) { + to = replace(to, it); + to.skip_children(); + it.skip_children(); + ++to; + ++it; + } +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::clear() { + if (head) + while (head->next_sibling != feet) + erase(pre_order_iterator(head->next_sibling)); +} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::tree() - { - head_initialise_(); - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::tree(const T& x) - { - head_initialise_(); - set_head(x); - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::tree(tree<T, tree_node_allocator>&& x) - { - head_initialise_(); - if(x.head->next_sibling!=x.feet) { // move tree if non-empty only - head->next_sibling=x.head->next_sibling; - feet->prev_sibling=x.feet->prev_sibling; - x.head->next_sibling->prev_sibling=head; - x.feet->prev_sibling->next_sibling=feet; - x.head->next_sibling=x.feet; - x.feet->prev_sibling=x.head; - } - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::tree(const iterator_base& other) - { - head_initialise_(); - set_head((*other)); - replace(begin(), other); - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::~tree() - { - clear(); - std::allocator_traits<decltype(alloc_)>::destroy(alloc_, head); - std::allocator_traits<decltype(alloc_)>::destroy(alloc_, feet); - std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, head, 1); - std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, feet, 1); - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::head_initialise_() - { - head = std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - feet = std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, head, tree_node_<T>()); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, feet, tree_node_<T>()); - - head->parent=0; - head->first_child=0; - head->last_child=0; - head->prev_sibling=0; //head; - head->next_sibling=feet; //head; - - feet->parent=0; - feet->first_child=0; - feet->last_child=0; - feet->prev_sibling=head; - feet->next_sibling=0; - } - -template <class T, class tree_node_allocator> -tree<T,tree_node_allocator>& tree<T, tree_node_allocator>::operator=(const tree<T, tree_node_allocator>& other) - { - if(this != &other) - copy_(other); - return *this; - } - -template <class T, class tree_node_allocator> -tree<T,tree_node_allocator>& tree<T, tree_node_allocator>::operator=(tree<T, tree_node_allocator>&& x) - { - if(this != &x) { - clear(); // clear any existing data. - - head->next_sibling=x.head->next_sibling; - feet->prev_sibling=x.feet->prev_sibling; - x.head->next_sibling->prev_sibling=head; - x.feet->prev_sibling->next_sibling=feet; - x.head->next_sibling=x.feet; - x.feet->prev_sibling=x.head; - } - return *this; - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::tree(const tree<T, tree_node_allocator>& other) - { - head_initialise_(); - copy_(other); - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::copy_(const tree<T, tree_node_allocator>& other) - { - clear(); - pre_order_iterator it=other.begin(), to=begin(); - while(it!=other.end()) { - to=insert(to, (*it)); - it.skip_children(); - ++it; - } - to=begin(); - it=other.begin(); - while(it!=other.end()) { - to=replace(to, it); - to.skip_children(); - it.skip_children(); - ++to; - ++it; - } - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::clear() - { - if(head) - while(head->next_sibling!=feet) - erase(pre_order_iterator(head->next_sibling)); - } - -template<class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::erase_children(const iterator_base& it) - { -// std::cout << "erase_children " << it.node << std::endl; - if(it.node==0) return; - - tree_node *cur=it.node->first_child; - tree_node *prev=0; - - while(cur!=0) { - prev=cur; - cur=cur->next_sibling; - erase_children(pre_order_iterator(prev)); - std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); - std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); - } - it.node->first_child=0; - it.node->last_child=0; -// std::cout << "exit" << std::endl; - } - -template<class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::erase_right_siblings(const iterator_base& it) - { - if(it.node==0) return; - - tree_node *cur=it.node->next_sibling; - tree_node *prev=0; - - while(cur!=0) { - prev=cur; - cur=cur->next_sibling; - erase_children(pre_order_iterator(prev)); - std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); - std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); - } - it.node->next_sibling=0; - if(it.node->parent!=0) - it.node->parent->last_child=it.node; - } - -template<class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::erase_left_siblings(const iterator_base& it) - { - if(it.node==0) return; - - tree_node *cur=it.node->prev_sibling; - tree_node *prev=0; - - while(cur!=0) { - prev=cur; - cur=cur->prev_sibling; - erase_children(pre_order_iterator(prev)); - std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); - std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); - } - it.node->prev_sibling=0; - if(it.node->parent!=0) - it.node->parent->first_child=it.node; - } - -template<class T, class tree_node_allocator> -template<class iter> -iter tree<T, tree_node_allocator>::erase(iter it) - { - tree_node *cur=it.node; - assert(cur!=head); - iter ret=it; - ret.skip_children(); - ++ret; - erase_children(it); - if(cur->prev_sibling==0) { - cur->parent->first_child=cur->next_sibling; - } - else { - cur->prev_sibling->next_sibling=cur->next_sibling; - } - if(cur->next_sibling==0) { - cur->parent->last_child=cur->prev_sibling; - } - else { - cur->next_sibling->prev_sibling=cur->prev_sibling; - } - - std::allocator_traits<decltype(alloc_)>::destroy(alloc_, cur); - std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, cur, 1); - return ret; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::begin() const - { - return pre_order_iterator(head->next_sibling); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::end() const - { - return pre_order_iterator(feet); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::breadth_first_queued_iterator tree<T, tree_node_allocator>::begin_breadth_first() const - { - return breadth_first_queued_iterator(head->next_sibling); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::breadth_first_queued_iterator tree<T, tree_node_allocator>::end_breadth_first() const - { - return breadth_first_queued_iterator(); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::begin_post() const - { - tree_node *tmp=head->next_sibling; - if(tmp!=feet) { - while(tmp->first_child) - tmp=tmp->first_child; - } - return post_order_iterator(tmp); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::end_post() const - { - return post_order_iterator(feet); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::begin_fixed(const iterator_base& pos, unsigned int dp, bool walk_back) const - { - typename tree<T, tree_node_allocator>::fixed_depth_iterator ret; - ret.top_node=pos.node; - - tree_node *tmp=pos.node; - unsigned int curdepth=0; - while(curdepth<dp) { // go down one level - while(tmp->first_child==0) { - if(tmp->next_sibling==0) { - // try to walk up and then right again - do { - if(tmp==ret.top_node) - throw std::range_error("tree: begin_fixed out of range"); - tmp=tmp->parent; - if(tmp==0) - throw std::range_error("tree: begin_fixed out of range"); - --curdepth; - } while(tmp->next_sibling==0); - } - tmp=tmp->next_sibling; - } - tmp=tmp->first_child; - ++curdepth; - } - - // Now walk back to the first sibling in this range. - if(walk_back) - while(tmp->prev_sibling!=0) - tmp=tmp->prev_sibling; - - ret.node=tmp; - return ret; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::end_fixed(const iterator_base& pos, unsigned int dp) const - { - assert(1==0); // FIXME: not correct yet: use is_valid() as a temporary workaround - tree_node *tmp=pos.node; - unsigned int curdepth=1; - while(curdepth<dp) { // go down one level - while(tmp->first_child==0) { - tmp=tmp->next_sibling; - if(tmp==0) - throw std::range_error("tree: end_fixed out of range"); - } - tmp=tmp->first_child; - ++curdepth; - } - return tmp; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::begin(const iterator_base& pos) - { - assert(pos.node!=0); - if(pos.node->first_child==0) { - return end(pos); - } - return pos.node->first_child; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::end(const iterator_base& pos) - { - sibling_iterator ret(0); - ret.parent_=pos.node; - return ret; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::begin_leaf() const - { - tree_node *tmp=head->next_sibling; - if(tmp!=feet) { - while(tmp->first_child) - tmp=tmp->first_child; +void tree<T, tree_node_allocator>::erase_children(const iterator_base &it) { + // std::cout << "erase_children " << it.node << std::endl; + if (it.node == 0) + return; + + tree_node *cur = it.node->first_child; + tree_node *prev = 0; + + while (cur != 0) { + prev = cur; + cur = cur->next_sibling; + erase_children(pre_order_iterator(prev)); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); + } + it.node->first_child = 0; + it.node->last_child = 0; + // std::cout << "exit" << std::endl; +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::erase_right_siblings( + const iterator_base &it) { + if (it.node == 0) + return; + + tree_node *cur = it.node->next_sibling; + tree_node *prev = 0; + + while (cur != 0) { + prev = cur; + cur = cur->next_sibling; + erase_children(pre_order_iterator(prev)); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); + } + it.node->next_sibling = 0; + if (it.node->parent != 0) + it.node->parent->last_child = it.node; +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::erase_left_siblings( + const iterator_base &it) { + if (it.node == 0) + return; + + tree_node *cur = it.node->prev_sibling; + tree_node *prev = 0; + + while (cur != 0) { + prev = cur; + cur = cur->prev_sibling; + erase_children(pre_order_iterator(prev)); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); + } + it.node->prev_sibling = 0; + if (it.node->parent != 0) + it.node->parent->first_child = it.node; +} + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::erase(iter it) { + tree_node *cur = it.node; + assert(cur != head); + iter ret = it; + ret.skip_children(); + ++ret; + erase_children(it); + if (cur->prev_sibling == 0) { + cur->parent->first_child = cur->next_sibling; + } else { + cur->prev_sibling->next_sibling = cur->next_sibling; + } + if (cur->next_sibling == 0) { + cur->parent->last_child = cur->prev_sibling; + } else { + cur->next_sibling->prev_sibling = cur->prev_sibling; + } + + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, cur); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, cur, 1); + return ret; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator +tree<T, tree_node_allocator>::begin() const { + return pre_order_iterator(head->next_sibling); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator +tree<T, tree_node_allocator>::end() const { + return pre_order_iterator(feet); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator +tree<T, tree_node_allocator>::begin_breadth_first() const { + return breadth_first_queued_iterator(head->next_sibling); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator +tree<T, tree_node_allocator>::end_breadth_first() const { + return breadth_first_queued_iterator(); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator +tree<T, tree_node_allocator>::begin_post() const { + tree_node *tmp = head->next_sibling; + if (tmp != feet) { + while (tmp->first_child) + tmp = tmp->first_child; + } + return post_order_iterator(tmp); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator +tree<T, tree_node_allocator>::end_post() const { + return post_order_iterator(feet); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator +tree<T, tree_node_allocator>::begin_fixed(const iterator_base &pos, + unsigned int dp, + bool walk_back) const { + typename tree<T, tree_node_allocator>::fixed_depth_iterator ret; + ret.top_node = pos.node; + + tree_node *tmp = pos.node; + unsigned int curdepth = 0; + while (curdepth < dp) { // go down one level + while (tmp->first_child == 0) { + if (tmp->next_sibling == 0) { + // try to walk up and then right again + do { + if (tmp == ret.top_node) + throw std::range_error("tree: begin_fixed out of range"); + tmp = tmp->parent; + if (tmp == 0) + throw std::range_error("tree: begin_fixed out of range"); + --curdepth; + } while (tmp->next_sibling == 0); } - return leaf_iterator(tmp); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::end_leaf() const - { - return leaf_iterator(feet); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::path_t tree<T, tree_node_allocator>::path_from_iterator(const iterator_base& iter, const iterator_base& top) const - { - path_t path; - tree_node *walk=iter.node; - - do { - if(path.size()>0) - walk=walk->parent; - int num=0; - while(walk!=top.node && walk->prev_sibling!=0 && walk->prev_sibling!=head) { - ++num; - walk=walk->prev_sibling; - } - path.push_back(num); - } - while(walk->parent!=0 && walk!=top.node); - - std::reverse(path.begin(), path.end()); - return path; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::iterator tree<T, tree_node_allocator>::iterator_from_path(const path_t& path, const iterator_base& top) const - { - iterator it=top; - tree_node *walk=it.node; - - for(size_t step=0; step<path.size(); ++step) { - if(step>0) - walk=walk->first_child; - if(walk==0) - throw std::range_error("tree::iterator_from_path: no more nodes at step "+std::to_string(step)); - - for(int i=0; i<path[step]; ++i) { - walk=walk->next_sibling; - if(walk==0) - throw std::range_error("tree::iterator_from_path: out of siblings at step "+std::to_string(step)); - } - } - it.node=walk; - return it; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::begin_leaf(const iterator_base& top) const - { - tree_node *tmp=top.node; - while(tmp->first_child) - tmp=tmp->first_child; - return leaf_iterator(tmp, top.node); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::end_leaf(const iterator_base& top) const - { - return leaf_iterator(top.node, top.node); - } + tmp = tmp->next_sibling; + } + tmp = tmp->first_child; + ++curdepth; + } + + // Now walk back to the first sibling in this range. + if (walk_back) + while (tmp->prev_sibling != 0) + tmp = tmp->prev_sibling; + + ret.node = tmp; + return ret; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator +tree<T, tree_node_allocator>::end_fixed(const iterator_base &pos, + unsigned int dp) const { + assert(1 == + 0); // FIXME: not correct yet: use is_valid() as a temporary workaround + tree_node *tmp = pos.node; + unsigned int curdepth = 1; + while (curdepth < dp) { // go down one level + while (tmp->first_child == 0) { + tmp = tmp->next_sibling; + if (tmp == 0) + throw std::range_error("tree: end_fixed out of range"); + } + tmp = tmp->first_child; + ++curdepth; + } + return tmp; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::begin(const iterator_base &pos) { + assert(pos.node != 0); + if (pos.node->first_child == 0) { + return end(pos); + } + return pos.node->first_child; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::end(const iterator_base &pos) { + sibling_iterator ret(0); + ret.parent_ = pos.node; + return ret; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator +tree<T, tree_node_allocator>::begin_leaf() const { + tree_node *tmp = head->next_sibling; + if (tmp != feet) { + while (tmp->first_child) + tmp = tmp->first_child; + } + return leaf_iterator(tmp); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator +tree<T, tree_node_allocator>::end_leaf() const { + return leaf_iterator(feet); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::path_t +tree<T, tree_node_allocator>::path_from_iterator( + const iterator_base &iter, const iterator_base &top) const { + path_t path; + tree_node *walk = iter.node; + + do { + if (path.size() > 0) + walk = walk->parent; + int num = 0; + while (walk != top.node && walk->prev_sibling != 0 && + walk->prev_sibling != head) { + ++num; + walk = walk->prev_sibling; + } + path.push_back(num); + } while (walk->parent != 0 && walk != top.node); + + std::reverse(path.begin(), path.end()); + return path; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::iterator +tree<T, tree_node_allocator>::iterator_from_path( + const path_t &path, const iterator_base &top) const { + iterator it = top; + tree_node *walk = it.node; + + for (size_t step = 0; step < path.size(); ++step) { + if (step > 0) + walk = walk->first_child; + if (walk == 0) + throw std::range_error( + "tree::iterator_from_path: no more nodes at step " + + std::to_string(step)); + + for (int i = 0; i < path[step]; ++i) { + walk = walk->next_sibling; + if (walk == 0) + throw std::range_error( + "tree::iterator_from_path: out of siblings at step " + + std::to_string(step)); + } + } + it.node = walk; + return it; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator +tree<T, tree_node_allocator>::begin_leaf(const iterator_base &top) const { + tree_node *tmp = top.node; + while (tmp->first_child) + tmp = tmp->first_child; + return leaf_iterator(tmp, top.node); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator +tree<T, tree_node_allocator>::end_leaf(const iterator_base &top) const { + return leaf_iterator(top.node, top.node); +} template <class T, class tree_node_allocator> template <typename iter> -iter tree<T, tree_node_allocator>::parent(iter position) - { - if(position.node==0) - throw navigation_error("tree: attempt to navigate from null iterator."); - - if(position.node->parent==0) - throw navigation_error("tree: attempt to navigate up past head node."); +iter tree<T, tree_node_allocator>::parent(iter position) { + if (position.node == 0) + throw navigation_error("tree: attempt to navigate from null iterator."); - return iter(position.node->parent); - } + if (position.node->parent == 0) + throw navigation_error("tree: attempt to navigate up past head node."); + + return iter(position.node->parent); +} template <class T, class tree_node_allocator> template <typename iter> -iter tree<T, tree_node_allocator>::previous_sibling(iter position) - { - assert(position.node!=0); - iter ret(position); - ret.node=position.node->prev_sibling; - return ret; - } +iter tree<T, tree_node_allocator>::previous_sibling(iter position) { + assert(position.node != 0); + iter ret(position); + ret.node = position.node->prev_sibling; + return ret; +} template <class T, class tree_node_allocator> template <typename iter> -iter tree<T, tree_node_allocator>::next_sibling(iter position) - { - assert(position.node!=0); - iter ret(position); - ret.node=position.node->next_sibling; - return ret; - } +iter tree<T, tree_node_allocator>::next_sibling(iter position) { + assert(position.node != 0); + iter ret(position); + ret.node = position.node->next_sibling; + return ret; +} template <class T, class tree_node_allocator> template <typename iter> -iter tree<T, tree_node_allocator>::next_at_same_depth(iter position) const - { - // We make use of a temporary fixed_depth iterator to implement this. - - typename tree<T, tree_node_allocator>::fixed_depth_iterator tmp(position.node); - - ++tmp; - return iter(tmp); - - // assert(position.node!=0); - // iter ret(position); - // - // if(position.node->next_sibling) { - // ret.node=position.node->next_sibling; - // } - // else { - // int relative_depth=0; - // upper: - // do { - // ret.node=ret.node->parent; - // if(ret.node==0) return ret; - // --relative_depth; - // } while(ret.node->next_sibling==0); - // lower: - // ret.node=ret.node->next_sibling; - // while(ret.node->first_child==0) { - // if(ret.node->next_sibling==0) - // goto upper; - // ret.node=ret.node->next_sibling; - // if(ret.node==0) return ret; - // } - // while(relative_depth<0 && ret.node->first_child!=0) { - // ret.node=ret.node->first_child; - // ++relative_depth; - // } - // if(relative_depth<0) { - // if(ret.node->next_sibling==0) goto upper; - // else goto lower; - // } - // } - // return ret; - } +iter tree<T, tree_node_allocator>::next_at_same_depth(iter position) const { + // We make use of a temporary fixed_depth iterator to implement this. + + typename tree<T, tree_node_allocator>::fixed_depth_iterator tmp( + position.node); + + ++tmp; + return iter(tmp); + + // assert(position.node!=0); + // iter ret(position); + // + // if(position.node->next_sibling) { + // ret.node=position.node->next_sibling; + // } + // else { + // int relative_depth=0; + // upper: + // do { + // ret.node=ret.node->parent; + // if(ret.node==0) return ret; + // --relative_depth; + // } while(ret.node->next_sibling==0); + // lower: + // ret.node=ret.node->next_sibling; + // while(ret.node->first_child==0) { + // if(ret.node->next_sibling==0) + // goto upper; + // ret.node=ret.node->next_sibling; + // if(ret.node==0) return ret; + // } + // while(relative_depth<0 && ret.node->first_child!=0) { + // ret.node=ret.node->first_child; + // ++relative_depth; + // } + // if(relative_depth<0) { + // if(ret.node->next_sibling==0) goto upper; + // else goto lower; + // } + // } + // return ret; +} template <class T, class tree_node_allocator> template <typename iter> -iter tree<T, tree_node_allocator>::append_child(iter position) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); - - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, tree_node_<T>()); - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node; - if(position.node->last_child!=0) { - position.node->last_child->next_sibling=tmp; - } - else { - position.node->first_child=tmp; - } - tmp->prev_sibling=position.node->last_child; - position.node->last_child=tmp; - tmp->next_sibling=0; - return tmp; - } +iter tree<T, tree_node_allocator>::append_child(iter position) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); + + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, + tree_node_<T>()); + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node; + if (position.node->last_child != 0) { + position.node->last_child->next_sibling = tmp; + } else { + position.node->first_child = tmp; + } + tmp->prev_sibling = position.node->last_child; + position.node->last_child = tmp; + tmp->next_sibling = 0; + return tmp; +} template <class T, class tree_node_allocator> template <typename iter> -iter tree<T, tree_node_allocator>::prepend_child(iter position) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); - - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, tree_node_<T>()); - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node; - if(position.node->first_child!=0) { - position.node->first_child->prev_sibling=tmp; - } - else { - position.node->last_child=tmp; - } - tmp->next_sibling=position.node->first_child; - position.node->prev_child=tmp; - tmp->prev_sibling=0; - return tmp; - } +iter tree<T, tree_node_allocator>::prepend_child(iter position) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); + + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, + tree_node_<T>()); + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node; + if (position.node->first_child != 0) { + position.node->first_child->prev_sibling = tmp; + } else { + position.node->last_child = tmp; + } + tmp->next_sibling = position.node->first_child; + position.node->prev_child = tmp; + tmp->prev_sibling = 0; + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::append_child(iter position, const T& x) - { - // If your program fails here you probably used 'append_child' to add the top - // node to an empty tree. From version 1.45 the top element should be added - // using 'insert'. See the documentation for further information, and sorry about - // the API change. - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); - - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node; - if(position.node->last_child!=0) { - position.node->last_child->next_sibling=tmp; - } - else { - position.node->first_child=tmp; - } - tmp->prev_sibling=position.node->last_child; - position.node->last_child=tmp; - tmp->next_sibling=0; - return tmp; - } +iter tree<T, tree_node_allocator>::append_child(iter position, const T &x) { + // If your program fails here you probably used 'append_child' to add the top + // node to an empty tree. From version 1.45 the top element should be added + // using 'insert'. See the documentation for further information, and sorry + // about the API change. + assert(position.node != head); + assert(position.node != feet); + assert(position.node); + + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node; + if (position.node->last_child != 0) { + position.node->last_child->next_sibling = tmp; + } else { + position.node->first_child = tmp; + } + tmp->prev_sibling = position.node->last_child; + position.node->last_child = tmp; + tmp->next_sibling = 0; + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::append_child(iter position, T&& x) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); - - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); // Here is where the move semantics kick in - std::swap(tmp->data, x); - - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node; - if(position.node->last_child!=0) { - position.node->last_child->next_sibling=tmp; - } - else { - position.node->first_child=tmp; - } - tmp->prev_sibling=position.node->last_child; - position.node->last_child=tmp; - tmp->next_sibling=0; - return tmp; - } +iter tree<T, tree_node_allocator>::append_child(iter position, T &&x) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); + + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct( + alloc_, tmp); // Here is where the move semantics kick in + std::swap(tmp->data, x); + + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node; + if (position.node->last_child != 0) { + position.node->last_child->next_sibling = tmp; + } else { + position.node->first_child = tmp; + } + tmp->prev_sibling = position.node->last_child; + position.node->last_child = tmp; + tmp->next_sibling = 0; + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::prepend_child(iter position, const T& x) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); - - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node; - if(position.node->first_child!=0) { - position.node->first_child->prev_sibling=tmp; - } - else { - position.node->last_child=tmp; - } - tmp->next_sibling=position.node->first_child; - position.node->first_child=tmp; - tmp->prev_sibling=0; - return tmp; - } +iter tree<T, tree_node_allocator>::prepend_child(iter position, const T &x) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); + + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node; + if (position.node->first_child != 0) { + position.node->first_child->prev_sibling = tmp; + } else { + position.node->last_child = tmp; + } + tmp->next_sibling = position.node->first_child; + position.node->first_child = tmp; + tmp->prev_sibling = 0; + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::prepend_child(iter position, T&& x) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); - - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); - std::swap(tmp->data, x); - - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node; - if(position.node->first_child!=0) { - position.node->first_child->prev_sibling=tmp; - } - else { - position.node->last_child=tmp; - } - tmp->next_sibling=position.node->first_child; - position.node->first_child=tmp; - tmp->prev_sibling=0; - return tmp; - } +iter tree<T, tree_node_allocator>::prepend_child(iter position, T &&x) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); + + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); + std::swap(tmp->data, x); + + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node; + if (position.node->first_child != 0) { + position.node->first_child->prev_sibling = tmp; + } else { + position.node->last_child = tmp; + } + tmp->next_sibling = position.node->first_child; + position.node->first_child = tmp; + tmp->prev_sibling = 0; + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::append_child(iter position, iter other) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); +iter tree<T, tree_node_allocator>::append_child(iter position, iter other) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); - sibling_iterator aargh=append_child(position, value_type()); - return replace(aargh, other); - } + sibling_iterator aargh = append_child(position, value_type()); + return replace(aargh, other); +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::prepend_child(iter position, iter other) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); +iter tree<T, tree_node_allocator>::prepend_child(iter position, iter other) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); - sibling_iterator aargh=prepend_child(position, value_type()); - return replace(aargh, other); - } + sibling_iterator aargh = prepend_child(position, value_type()); + return replace(aargh, other); +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::append_children(iter position, sibling_iterator from, sibling_iterator to) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); - - iter ret=from; - - while(from!=to) { - insert_subtree(position.end(), from); - ++from; - } - return ret; - } +iter tree<T, tree_node_allocator>::append_children(iter position, + sibling_iterator from, + sibling_iterator to) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); + + iter ret = from; + + while (from != to) { + insert_subtree(position.end(), from); + ++from; + } + return ret; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::prepend_children(iter position, sibling_iterator from, sibling_iterator to) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); - - if(from==to) return from; // should return end of tree? - - iter ret; - do { - --to; - ret=insert_subtree(position.begin(), to); - } - while(to!=from); - - return ret; - } +iter tree<T, tree_node_allocator>::prepend_children(iter position, + sibling_iterator from, + sibling_iterator to) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); + + if (from == to) + return from; // should return end of tree? + + iter ret; + do { + --to; + ret = insert_subtree(position.begin(), to); + } while (to != from); + + return ret; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::set_head(const T& x) - { - assert(head->next_sibling==feet); - return insert(iterator(feet), x); - } +typename tree<T, tree_node_allocator>::pre_order_iterator +tree<T, tree_node_allocator>::set_head(const T &x) { + assert(head->next_sibling == feet); + return insert(iterator(feet), x); +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::set_head(T&& x) - { - assert(head->next_sibling==feet); - return insert(iterator(feet), x); - } +typename tree<T, tree_node_allocator>::pre_order_iterator +tree<T, tree_node_allocator>::set_head(T &&x) { + assert(head->next_sibling == feet); + return insert(iterator(feet), x); +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::insert(iter position, const T& x) - { - if(position.node==0) { - position.node=feet; // Backward compatibility: when calling insert on a null node, - // insert before the feet. - } - assert(position.node!=head); // Cannot insert before head. - - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node->parent; - tmp->next_sibling=position.node; - tmp->prev_sibling=position.node->prev_sibling; - position.node->prev_sibling=tmp; - - if(tmp->prev_sibling==0) { - if(tmp->parent) // when inserting nodes at the head, there is no parent - tmp->parent->first_child=tmp; - } - else - tmp->prev_sibling->next_sibling=tmp; - return tmp; - } +iter tree<T, tree_node_allocator>::insert(iter position, const T &x) { + if (position.node == 0) { + position.node = feet; // Backward compatibility: when calling insert on a + // null node, insert before the feet. + } + assert(position.node != head); // Cannot insert before head. + + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node->parent; + tmp->next_sibling = position.node; + tmp->prev_sibling = position.node->prev_sibling; + position.node->prev_sibling = tmp; + + if (tmp->prev_sibling == 0) { + if (tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->first_child = tmp; + } else + tmp->prev_sibling->next_sibling = tmp; + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::insert(iter position, T&& x) - { - if(position.node==0) { - position.node=feet; // Backward compatibility: when calling insert on a null node, - // insert before the feet. - } - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); - std::swap(tmp->data, x); // Move semantics - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node->parent; - tmp->next_sibling=position.node; - tmp->prev_sibling=position.node->prev_sibling; - position.node->prev_sibling=tmp; - - if(tmp->prev_sibling==0) { - if(tmp->parent) // when inserting nodes at the head, there is no parent - tmp->parent->first_child=tmp; - } - else - tmp->prev_sibling->next_sibling=tmp; - return tmp; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::insert(sibling_iterator position, const T& x) - { - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); - tmp->first_child=0; - tmp->last_child=0; - - tmp->next_sibling=position.node; - if(position.node==0) { // iterator points to end of a subtree - tmp->parent=position.parent_; - tmp->prev_sibling=position.range_last(); - tmp->parent->last_child=tmp; - } - else { - tmp->parent=position.node->parent; - tmp->prev_sibling=position.node->prev_sibling; - position.node->prev_sibling=tmp; - } - - if(tmp->prev_sibling==0) { - if(tmp->parent) // when inserting nodes at the head, there is no parent - tmp->parent->first_child=tmp; - } - else - tmp->prev_sibling->next_sibling=tmp; - return tmp; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::insert(sibling_iterator position, T&& x) - { - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); - std::swap(tmp->data, x); // Move semantics - - tmp->first_child=0; - tmp->last_child=0; - - tmp->next_sibling=position.node; - if(position.node==0) { // iterator points to end of a subtree - tmp->parent=position.parent_; - tmp->prev_sibling=position.range_last(); - tmp->parent->last_child=tmp; - } - else { - tmp->parent=position.node->parent; - tmp->prev_sibling=position.node->prev_sibling; - position.node->prev_sibling=tmp; - } - - if(tmp->prev_sibling==0) { - if(tmp->parent) // when inserting nodes at the head, there is no parent - tmp->parent->first_child=tmp; - } - else - tmp->prev_sibling->next_sibling=tmp; - return tmp; - } +iter tree<T, tree_node_allocator>::insert(iter position, T &&x) { + if (position.node == 0) { + position.node = feet; // Backward compatibility: when calling insert on a + // null node, insert before the feet. + } + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); + std::swap(tmp->data, x); // Move semantics + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node->parent; + tmp->next_sibling = position.node; + tmp->prev_sibling = position.node->prev_sibling; + position.node->prev_sibling = tmp; + + if (tmp->prev_sibling == 0) { + if (tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->first_child = tmp; + } else + tmp->prev_sibling->next_sibling = tmp; + return tmp; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::insert(sibling_iterator position, const T &x) { + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->next_sibling = position.node; + if (position.node == 0) { // iterator points to end of a subtree + tmp->parent = position.parent_; + tmp->prev_sibling = position.range_last(); + tmp->parent->last_child = tmp; + } else { + tmp->parent = position.node->parent; + tmp->prev_sibling = position.node->prev_sibling; + position.node->prev_sibling = tmp; + } + + if (tmp->prev_sibling == 0) { + if (tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->first_child = tmp; + } else + tmp->prev_sibling->next_sibling = tmp; + return tmp; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::insert(sibling_iterator position, T &&x) { + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); + std::swap(tmp->data, x); // Move semantics + + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->next_sibling = position.node; + if (position.node == 0) { // iterator points to end of a subtree + tmp->parent = position.parent_; + tmp->prev_sibling = position.range_last(); + tmp->parent->last_child = tmp; + } else { + tmp->parent = position.node->parent; + tmp->prev_sibling = position.node->prev_sibling; + position.node->prev_sibling = tmp; + } + + if (tmp->prev_sibling == 0) { + if (tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->first_child = tmp; + } else + tmp->prev_sibling->next_sibling = tmp; + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::insert_after(iter position, const T& x) - { - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node->parent; - tmp->prev_sibling=position.node; - tmp->next_sibling=position.node->next_sibling; - position.node->next_sibling=tmp; - - if(tmp->next_sibling==0) { - if(tmp->parent) // when inserting nodes at the head, there is no parent - tmp->parent->last_child=tmp; - } - else { - tmp->next_sibling->prev_sibling=tmp; - } - return tmp; - } +iter tree<T, tree_node_allocator>::insert_after(iter position, const T &x) { + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node->parent; + tmp->prev_sibling = position.node; + tmp->next_sibling = position.node->next_sibling; + position.node->next_sibling = tmp; + + if (tmp->next_sibling == 0) { + if (tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->last_child = tmp; + } else { + tmp->next_sibling->prev_sibling = tmp; + } + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::insert_after(iter position, T&& x) - { - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); - std::swap(tmp->data, x); // move semantics - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node->parent; - tmp->prev_sibling=position.node; - tmp->next_sibling=position.node->next_sibling; - position.node->next_sibling=tmp; - - if(tmp->next_sibling==0) { - if(tmp->parent) // when inserting nodes at the head, there is no parent - tmp->parent->last_child=tmp; - } - else { - tmp->next_sibling->prev_sibling=tmp; - } - return tmp; - } +iter tree<T, tree_node_allocator>::insert_after(iter position, T &&x) { + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); + std::swap(tmp->data, x); // move semantics + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node->parent; + tmp->prev_sibling = position.node; + tmp->next_sibling = position.node->next_sibling; + position.node->next_sibling = tmp; + + if (tmp->next_sibling == 0) { + if (tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->last_child = tmp; + } else { + tmp->next_sibling->prev_sibling = tmp; + } + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::insert_subtree(iter position, const iterator_base& subtree) - { - // insert dummy - iter it=insert(position, value_type()); - // replace dummy with subtree - return replace(it, subtree); - } +iter tree<T, tree_node_allocator>::insert_subtree( + iter position, const iterator_base &subtree) { + // insert dummy + iter it = insert(position, value_type()); + // replace dummy with subtree + return replace(it, subtree); +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::insert_subtree_after(iter position, const iterator_base& subtree) - { - // insert dummy - iter it=insert_after(position, value_type()); - // replace dummy with subtree - return replace(it, subtree); - } +iter tree<T, tree_node_allocator>::insert_subtree_after( + iter position, const iterator_base &subtree) { + // insert dummy + iter it = insert_after(position, value_type()); + // replace dummy with subtree + return replace(it, subtree); +} // template <class T, class tree_node_allocator> // template <class iter> -// iter tree<T, tree_node_allocator>::insert_subtree(sibling_iterator position, iter subtree) +// iter tree<T, tree_node_allocator>::insert_subtree(sibling_iterator position, +// iter subtree) // { // // insert dummy // iter it(insert(position, value_type())); @@ -1481,1929 +1529,1957 @@ iter tree<T, tree_node_allocator>::insert_subtree_after(iter position, const ite template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::replace(iter position, const T& x) - { -// kp::destructor(&position.node->data); -// kp::constructor(&position.node->data, x); - position.node->data=x; -// alloc_.destroy(position.node); -// alloc_.construct(position.node, x); - return position; - } +iter tree<T, tree_node_allocator>::replace(iter position, const T &x) { + // kp::destructor(&position.node->data); + // kp::constructor(&position.node->data, x); + position.node->data = x; + // alloc_.destroy(position.node); + // alloc_.construct(position.node, x); + return position; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::replace(iter position, const iterator_base& from) - { - assert(position.node!=head); - tree_node *current_from=from.node; - tree_node *start_from=from.node; - tree_node *current_to =position.node; - - // replace the node at position with head of the replacement tree at from -// std::cout << "warning!" << position.node << std::endl; - erase_children(position); -// std::cout << "no warning!" << std::endl; - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, (*from)); - tmp->first_child=0; - tmp->last_child=0; - if(current_to->prev_sibling==0) { - if(current_to->parent!=0) - current_to->parent->first_child=tmp; - } - else { - current_to->prev_sibling->next_sibling=tmp; - } - tmp->prev_sibling=current_to->prev_sibling; - if(current_to->next_sibling==0) { - if(current_to->parent!=0) - current_to->parent->last_child=tmp; - } - else { - current_to->next_sibling->prev_sibling=tmp; - } - tmp->next_sibling=current_to->next_sibling; - tmp->parent=current_to->parent; -// kp::destructor(¤t_to->data); - std::allocator_traits<decltype(alloc_)>::destroy(alloc_, current_to); - std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, current_to, 1); - current_to=tmp; - - // only at this stage can we fix 'last' - tree_node *last=from.node->next_sibling; - - pre_order_iterator toit=tmp; - // copy all children - do { - assert(current_from!=0); - if(current_from->first_child != 0) { - current_from=current_from->first_child; - toit=append_child(toit, current_from->data); - } - else { - while(current_from->next_sibling==0 && current_from!=start_from) { - current_from=current_from->parent; - toit=parent(toit); - assert(current_from!=0); - } - current_from=current_from->next_sibling; - if(current_from!=last) { - toit=append_child(parent(toit), current_from->data); - } - } - } - while(current_from!=last); - - return current_to; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::replace( - sibling_iterator orig_begin, - sibling_iterator orig_end, - sibling_iterator new_begin, - sibling_iterator new_end) - { - tree_node *orig_first=orig_begin.node; - tree_node *new_first=new_begin.node; - tree_node *orig_last=orig_first; - while((++orig_begin)!=orig_end) - orig_last=orig_last->next_sibling; - tree_node *new_last=new_first; - while((++new_begin)!=new_end) - new_last=new_last->next_sibling; - - // insert all siblings in new_first..new_last before orig_first - bool first=true; - pre_order_iterator ret; - while(1==1) { - pre_order_iterator tt=insert_subtree(pre_order_iterator(orig_first), pre_order_iterator(new_first)); - if(first) { - ret=tt; - first=false; - } - if(new_first==new_last) - break; - new_first=new_first->next_sibling; - } - - // erase old range of siblings - bool last=false; - tree_node *next=orig_first; - while(1==1) { - if(next==orig_last) - last=true; - next=next->next_sibling; - erase((pre_order_iterator)orig_first); - if(last) - break; - orig_first=next; - } - return ret; - } +iter tree<T, tree_node_allocator>::replace(iter position, + const iterator_base &from) { + assert(position.node != head); + tree_node *current_from = from.node; + tree_node *start_from = from.node; + tree_node *current_to = position.node; + + // replace the node at position with head of the replacement tree at from + // std::cout << "warning!" << position.node << std::endl; + erase_children(position); + // std::cout << "no warning!" << std::endl; + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, (*from)); + tmp->first_child = 0; + tmp->last_child = 0; + if (current_to->prev_sibling == 0) { + if (current_to->parent != 0) + current_to->parent->first_child = tmp; + } else { + current_to->prev_sibling->next_sibling = tmp; + } + tmp->prev_sibling = current_to->prev_sibling; + if (current_to->next_sibling == 0) { + if (current_to->parent != 0) + current_to->parent->last_child = tmp; + } else { + current_to->next_sibling->prev_sibling = tmp; + } + tmp->next_sibling = current_to->next_sibling; + tmp->parent = current_to->parent; + // kp::destructor(¤t_to->data); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, current_to); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, current_to, 1); + current_to = tmp; + + // only at this stage can we fix 'last' + tree_node *last = from.node->next_sibling; + + pre_order_iterator toit = tmp; + // copy all children + do { + assert(current_from != 0); + if (current_from->first_child != 0) { + current_from = current_from->first_child; + toit = append_child(toit, current_from->data); + } else { + while (current_from->next_sibling == 0 && current_from != start_from) { + current_from = current_from->parent; + toit = parent(toit); + assert(current_from != 0); + } + current_from = current_from->next_sibling; + if (current_from != last) { + toit = append_child(parent(toit), current_from->data); + } + } + } while (current_from != last); + + return current_to; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::replace(sibling_iterator orig_begin, + sibling_iterator orig_end, + sibling_iterator new_begin, + sibling_iterator new_end) { + tree_node *orig_first = orig_begin.node; + tree_node *new_first = new_begin.node; + tree_node *orig_last = orig_first; + while ((++orig_begin) != orig_end) + orig_last = orig_last->next_sibling; + tree_node *new_last = new_first; + while ((++new_begin) != new_end) + new_last = new_last->next_sibling; + + // insert all siblings in new_first..new_last before orig_first + bool first = true; + pre_order_iterator ret; + while (1 == 1) { + pre_order_iterator tt = insert_subtree(pre_order_iterator(orig_first), + pre_order_iterator(new_first)); + if (first) { + ret = tt; + first = false; + } + if (new_first == new_last) + break; + new_first = new_first->next_sibling; + } + + // erase old range of siblings + bool last = false; + tree_node *next = orig_first; + while (1 == 1) { + if (next == orig_last) + last = true; + next = next->next_sibling; + erase((pre_order_iterator)orig_first); + if (last) + break; + orig_first = next; + } + return ret; +} + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::flatten(iter position) { + if (position.node->first_child == 0) + return position; + + tree_node *tmp = position.node->first_child; + while (tmp) { + tmp->parent = position.node->parent; + tmp = tmp->next_sibling; + } + if (position.node->next_sibling) { + position.node->last_child->next_sibling = position.node->next_sibling; + position.node->next_sibling->prev_sibling = position.node->last_child; + } else { + position.node->parent->last_child = position.node->last_child; + } + position.node->next_sibling = position.node->first_child; + position.node->next_sibling->prev_sibling = position.node; + position.node->first_child = 0; + position.node->last_child = 0; + + return position; +} + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::reparent(iter position, + sibling_iterator begin, + sibling_iterator end) { + tree_node *first = begin.node; + tree_node *last = first; + + assert(first != position.node); + + if (begin == end) + return begin; + // determine last node + while ((++begin) != end) { + last = last->next_sibling; + } + // move subtree + if (first->prev_sibling == 0) { + first->parent->first_child = last->next_sibling; + } else { + first->prev_sibling->next_sibling = last->next_sibling; + } + if (last->next_sibling == 0) { + last->parent->last_child = first->prev_sibling; + } else { + last->next_sibling->prev_sibling = first->prev_sibling; + } + if (position.node->first_child == 0) { + position.node->first_child = first; + position.node->last_child = last; + first->prev_sibling = 0; + } else { + position.node->last_child->next_sibling = first; + first->prev_sibling = position.node->last_child; + position.node->last_child = last; + } + last->next_sibling = 0; + + tree_node *pos = first; + for (;;) { + pos->parent = position.node; + if (pos == last) + break; + pos = pos->next_sibling; + } + + return first; +} + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::reparent(iter position, iter from) { + if (from.node->first_child == 0) + return position; + return reparent(position, from.node->first_child, end(from)); +} + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::wrap(iter position, const T &x) { + assert(position.node != 0); + sibling_iterator fr = position, to = position; + ++to; + iter ret = insert(position, x); + reparent(ret, fr, to); + return ret; +} template <class T, class tree_node_allocator> template <typename iter> -iter tree<T, tree_node_allocator>::flatten(iter position) - { - if(position.node->first_child==0) - return position; - - tree_node *tmp=position.node->first_child; - while(tmp) { - tmp->parent=position.node->parent; - tmp=tmp->next_sibling; - } - if(position.node->next_sibling) { - position.node->last_child->next_sibling=position.node->next_sibling; - position.node->next_sibling->prev_sibling=position.node->last_child; - } - else { - position.node->parent->last_child=position.node->last_child; - } - position.node->next_sibling=position.node->first_child; - position.node->next_sibling->prev_sibling=position.node; - position.node->first_child=0; - position.node->last_child=0; - - return position; - } +iter tree<T, tree_node_allocator>::wrap(iter from, iter to, const T &x) { + assert(from.node != 0); + iter ret = insert(from, x); + reparent(ret, from, to); + return ret; +} +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::move_after(iter target, iter source) { + tree_node *dst = target.node; + tree_node *src = source.node; + assert(dst); + assert(src); + + if (dst == src) + return source; + if (dst->next_sibling) + if (dst->next_sibling == src) // already in the right spot + return source; + + // take src out of the tree + if (src->prev_sibling != 0) + src->prev_sibling->next_sibling = src->next_sibling; + else + src->parent->first_child = src->next_sibling; + if (src->next_sibling != 0) + src->next_sibling->prev_sibling = src->prev_sibling; + else + src->parent->last_child = src->prev_sibling; + + // connect it to the new point + if (dst->next_sibling != 0) + dst->next_sibling->prev_sibling = src; + else + dst->parent->last_child = src; + src->next_sibling = dst->next_sibling; + dst->next_sibling = src; + src->prev_sibling = dst; + src->parent = dst->parent; + return src; +} template <class T, class tree_node_allocator> template <typename iter> -iter tree<T, tree_node_allocator>::reparent(iter position, sibling_iterator begin, sibling_iterator end) - { - tree_node *first=begin.node; - tree_node *last=first; - - assert(first!=position.node); - - if(begin==end) return begin; - // determine last node - while((++begin)!=end) { - last=last->next_sibling; - } - // move subtree - if(first->prev_sibling==0) { - first->parent->first_child=last->next_sibling; - } - else { - first->prev_sibling->next_sibling=last->next_sibling; - } - if(last->next_sibling==0) { - last->parent->last_child=first->prev_sibling; - } - else { - last->next_sibling->prev_sibling=first->prev_sibling; - } - if(position.node->first_child==0) { - position.node->first_child=first; - position.node->last_child=last; - first->prev_sibling=0; - } - else { - position.node->last_child->next_sibling=first; - first->prev_sibling=position.node->last_child; - position.node->last_child=last; - } - last->next_sibling=0; - - tree_node *pos=first; - for(;;) { - pos->parent=position.node; - if(pos==last) break; - pos=pos->next_sibling; - } - - return first; - } - -template <class T, class tree_node_allocator> -template <typename iter> iter tree<T, tree_node_allocator>::reparent(iter position, iter from) - { - if(from.node->first_child==0) return position; - return reparent(position, from.node->first_child, end(from)); - } - -template <class T, class tree_node_allocator> -template <typename iter> iter tree<T, tree_node_allocator>::wrap(iter position, const T& x) - { - assert(position.node!=0); - sibling_iterator fr=position, to=position; - ++to; - iter ret = insert(position, x); - reparent(ret, fr, to); - return ret; - } - -template <class T, class tree_node_allocator> -template <typename iter> iter tree<T, tree_node_allocator>::wrap(iter from, iter to, const T& x) - { - assert(from.node!=0); - iter ret = insert(from, x); - reparent(ret, from, to); - return ret; - } - -template <class T, class tree_node_allocator> -template <typename iter> iter tree<T, tree_node_allocator>::move_after(iter target, iter source) - { - tree_node *dst=target.node; - tree_node *src=source.node; - assert(dst); - assert(src); - - if(dst==src) return source; - if(dst->next_sibling) - if(dst->next_sibling==src) // already in the right spot - return source; - - // take src out of the tree - if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; - else src->parent->first_child=src->next_sibling; - if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; - else src->parent->last_child=src->prev_sibling; - - // connect it to the new point - if(dst->next_sibling!=0) dst->next_sibling->prev_sibling=src; - else dst->parent->last_child=src; - src->next_sibling=dst->next_sibling; - dst->next_sibling=src; - src->prev_sibling=dst; - src->parent=dst->parent; - return src; - } - -template <class T, class tree_node_allocator> -template <typename iter> iter tree<T, tree_node_allocator>::move_before(iter target, iter source) - { - tree_node *dst=target.node; - tree_node *src=source.node; - assert(dst); - assert(src); - - if(dst==src) return source; - if(dst->prev_sibling) - if(dst->prev_sibling==src) // already in the right spot - return source; - - // take src out of the tree - if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; - else src->parent->first_child=src->next_sibling; - if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; - else src->parent->last_child=src->prev_sibling; - - // connect it to the new point - if(dst->prev_sibling!=0) dst->prev_sibling->next_sibling=src; - else dst->parent->first_child=src; - src->prev_sibling=dst->prev_sibling; - dst->prev_sibling=src; - src->next_sibling=dst; - src->parent=dst->parent; - return src; - } +iter tree<T, tree_node_allocator>::move_before(iter target, iter source) { + tree_node *dst = target.node; + tree_node *src = source.node; + assert(dst); + assert(src); + + if (dst == src) + return source; + if (dst->prev_sibling) + if (dst->prev_sibling == src) // already in the right spot + return source; + + // take src out of the tree + if (src->prev_sibling != 0) + src->prev_sibling->next_sibling = src->next_sibling; + else + src->parent->first_child = src->next_sibling; + if (src->next_sibling != 0) + src->next_sibling->prev_sibling = src->prev_sibling; + else + src->parent->last_child = src->prev_sibling; + + // connect it to the new point + if (dst->prev_sibling != 0) + dst->prev_sibling->next_sibling = src; + else + dst->parent->first_child = src; + src->prev_sibling = dst->prev_sibling; + dst->prev_sibling = src; + src->next_sibling = dst; + src->parent = dst->parent; + return src; +} // specialisation for sibling_iterators template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::move_before(sibling_iterator target, - sibling_iterator source) - { - tree_node *dst=target.node; - tree_node *src=source.node; - tree_node *dst_prev_sibling; - if(dst==0) { // must then be an end iterator - dst_prev_sibling=target.parent_->last_child; - assert(dst_prev_sibling); - } - else dst_prev_sibling=dst->prev_sibling; - assert(src); - - if(dst==src) return source; - if(dst_prev_sibling) - if(dst_prev_sibling==src) // already in the right spot - return source; - - // take src out of the tree - if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; - else src->parent->first_child=src->next_sibling; - if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; - else src->parent->last_child=src->prev_sibling; - - // connect it to the new point - if(dst_prev_sibling!=0) dst_prev_sibling->next_sibling=src; - else target.parent_->first_child=src; - src->prev_sibling=dst_prev_sibling; - if(dst) { - dst->prev_sibling=src; - src->parent=dst->parent; - } - else { - src->parent=dst_prev_sibling->parent; - } - src->next_sibling=dst; - return src; - } - -template <class T, class tree_node_allocator> -template <typename iter> iter tree<T, tree_node_allocator>::move_ontop(iter target, iter source) - { - tree_node *dst=target.node; - tree_node *src=source.node; - assert(dst); - assert(src); - - if(dst==src) return source; - -// if(dst==src->prev_sibling) { -// -// } - - // remember connection points - tree_node *b_prev_sibling=dst->prev_sibling; - tree_node *b_next_sibling=dst->next_sibling; - tree_node *b_parent=dst->parent; - - // remove target - erase(target); - - // take src out of the tree - if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; - else { - assert(src->parent!=0); - src->parent->first_child=src->next_sibling; - } - if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; - else { - assert(src->parent!=0); - src->parent->last_child=src->prev_sibling; - } - - // connect it to the new point - if(b_prev_sibling!=0) b_prev_sibling->next_sibling=src; - else { - assert(b_parent!=0); - b_parent->first_child=src; - } - if(b_next_sibling!=0) b_next_sibling->prev_sibling=src; - else { - assert(b_parent!=0); - b_parent->last_child=src; - } - src->prev_sibling=b_prev_sibling; - src->next_sibling=b_next_sibling; - src->parent=b_parent; - return src; - } - - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator> tree<T, tree_node_allocator>::move_out(iterator source) - { - tree ret; - - // Move source node into the 'ret' tree. - ret.head->next_sibling = source.node; - ret.feet->prev_sibling = source.node; - - // Close the links in the current tree. - if(source.node->prev_sibling!=0) - source.node->prev_sibling->next_sibling = source.node->next_sibling; - - if(source.node->next_sibling!=0) - source.node->next_sibling->prev_sibling = source.node->prev_sibling; - - // If the moved-out node was a first or last child of - // the parent, adjust those links. - if(source.node->parent->first_child==source.node) { - if(source.node->next_sibling!=0) - source.node->parent->first_child=source.node->next_sibling; - else - source.node->parent->first_child=0; - } - if(source.node->parent->last_child==source.node) { - if(source.node->prev_sibling!=0) - source.node->parent->last_child=source.node->prev_sibling; - else - source.node->parent->last_child=0; - } - source.node->parent=0; - - // Fix source prev/next links. - source.node->prev_sibling = ret.head; - source.node->next_sibling = ret.feet; - - return ret; // A good compiler will move this, not copy. - } - -template <class T, class tree_node_allocator> -template<typename iter> iter tree<T, tree_node_allocator>::move_in(iter loc, tree& other) - { - if(other.head->next_sibling==other.feet) return loc; // other tree is empty - - tree_node *other_first_head = other.head->next_sibling; - tree_node *other_last_head = other.feet->prev_sibling; - - sibling_iterator prev(loc); - --prev; - - prev.node->next_sibling = other_first_head; - loc.node->prev_sibling = other_last_head; - other_first_head->prev_sibling = prev.node; - other_last_head->next_sibling = loc.node; - - // Adjust parent pointers. - tree_node *walk=other_first_head; - while(true) { - walk->parent=loc.node->parent; - if(walk==other_last_head) - break; - walk=walk->next_sibling; - } - - // Close other tree. - other.head->next_sibling=other.feet; - other.feet->prev_sibling=other.head; - - return other_first_head; - } - -template <class T, class tree_node_allocator> -template<typename iter> iter tree<T, tree_node_allocator>::move_in_below(iter loc, tree& other) - { - if(other.head->next_sibling==other.feet) return loc; // other tree is empty - - auto n = other.number_of_children(loc); - return move_in_as_nth_child(loc, n, other); - } - -template <class T, class tree_node_allocator> -template<typename iter> iter tree<T, tree_node_allocator>::move_in_as_nth_child(iter loc, size_t n, tree& other) - { - if(other.head->next_sibling==other.feet) return loc; // other tree is empty - - tree_node *other_first_head = other.head->next_sibling; - tree_node *other_last_head = other.feet->prev_sibling; - - if(n==0) { - if(loc.node->first_child==0) { - loc.node->first_child=other_first_head; - loc.node->last_child=other_last_head; - other_last_head->next_sibling=0; - other_first_head->prev_sibling=0; - } - else { - loc.node->first_child->prev_sibling=other_last_head; - other_last_head->next_sibling=loc.node->first_child; - loc.node->first_child=other_first_head; - other_first_head->prev_sibling=0; - } - } - else { - --n; - tree_node *walk = loc.node->first_child; - while(true) { - if(walk==0) - throw std::range_error("tree: move_in_as_nth_child position out of range"); - if(n==0) - break; - --n; - walk = walk->next_sibling; - } - if(walk->next_sibling==0) - loc.node->last_child=other_last_head; - else - walk->next_sibling->prev_sibling=other_last_head; - other_last_head->next_sibling=walk->next_sibling; - walk->next_sibling=other_first_head; - other_first_head->prev_sibling=walk; - } - - // Adjust parent pointers. - tree_node *walk=other_first_head; - while(true) { - walk->parent=loc.node; - if(walk==other_last_head) - break; - walk=walk->next_sibling; - } - - // Close other tree. - other.head->next_sibling=other.feet; - other.feet->prev_sibling=other.head; - - return other_first_head; - } - - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::merge(sibling_iterator to1, sibling_iterator to2, - sibling_iterator from1, sibling_iterator from2, - bool duplicate_leaves) - { - sibling_iterator fnd; - while(from1!=from2) { - if((fnd=std::find(to1, to2, (*from1))) != to2) { // element found - if(from1.begin()==from1.end()) { // full depth reached - if(duplicate_leaves) - append_child(parent(to1), (*from1)); - } - else { // descend further - merge(fnd.begin(), fnd.end(), from1.begin(), from1.end(), duplicate_leaves); - } - } - else { // element missing - insert_subtree(to2, from1); - } - ++from1; - } - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::merge(iterator to, iterator from, bool duplicate_leaves) - { - sibling_iterator to1(to); - sibling_iterator to2=to1; - ++to2; - sibling_iterator from1(from); - sibling_iterator from2=from1; - ++from2; - - merge(to1, to2, from1, from2, duplicate_leaves); - } - - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::sort(sibling_iterator from, sibling_iterator to, bool deep) - { - std::less<T> comp; - sort(from, to, comp, deep); - } +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::move_before(sibling_iterator target, + sibling_iterator source) { + tree_node *dst = target.node; + tree_node *src = source.node; + tree_node *dst_prev_sibling; + if (dst == 0) { // must then be an end iterator + dst_prev_sibling = target.parent_->last_child; + assert(dst_prev_sibling); + } else + dst_prev_sibling = dst->prev_sibling; + assert(src); + + if (dst == src) + return source; + if (dst_prev_sibling) + if (dst_prev_sibling == src) // already in the right spot + return source; + + // take src out of the tree + if (src->prev_sibling != 0) + src->prev_sibling->next_sibling = src->next_sibling; + else + src->parent->first_child = src->next_sibling; + if (src->next_sibling != 0) + src->next_sibling->prev_sibling = src->prev_sibling; + else + src->parent->last_child = src->prev_sibling; + + // connect it to the new point + if (dst_prev_sibling != 0) + dst_prev_sibling->next_sibling = src; + else + target.parent_->first_child = src; + src->prev_sibling = dst_prev_sibling; + if (dst) { + dst->prev_sibling = src; + src->parent = dst->parent; + } else { + src->parent = dst_prev_sibling->parent; + } + src->next_sibling = dst; + return src; +} + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::move_ontop(iter target, iter source) { + tree_node *dst = target.node; + tree_node *src = source.node; + assert(dst); + assert(src); + + if (dst == src) + return source; + + // if(dst==src->prev_sibling) { + // + // } + + // remember connection points + tree_node *b_prev_sibling = dst->prev_sibling; + tree_node *b_next_sibling = dst->next_sibling; + tree_node *b_parent = dst->parent; + + // remove target + erase(target); + + // take src out of the tree + if (src->prev_sibling != 0) + src->prev_sibling->next_sibling = src->next_sibling; + else { + assert(src->parent != 0); + src->parent->first_child = src->next_sibling; + } + if (src->next_sibling != 0) + src->next_sibling->prev_sibling = src->prev_sibling; + else { + assert(src->parent != 0); + src->parent->last_child = src->prev_sibling; + } + + // connect it to the new point + if (b_prev_sibling != 0) + b_prev_sibling->next_sibling = src; + else { + assert(b_parent != 0); + b_parent->first_child = src; + } + if (b_next_sibling != 0) + b_next_sibling->prev_sibling = src; + else { + assert(b_parent != 0); + b_parent->last_child = src; + } + src->prev_sibling = b_prev_sibling; + src->next_sibling = b_next_sibling; + src->parent = b_parent; + return src; +} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator> +tree<T, tree_node_allocator>::move_out(iterator source) { + tree ret; + + // Move source node into the 'ret' tree. + ret.head->next_sibling = source.node; + ret.feet->prev_sibling = source.node; + + // Close the links in the current tree. + if (source.node->prev_sibling != 0) + source.node->prev_sibling->next_sibling = source.node->next_sibling; + + if (source.node->next_sibling != 0) + source.node->next_sibling->prev_sibling = source.node->prev_sibling; + + // If the moved-out node was a first or last child of + // the parent, adjust those links. + if (source.node->parent->first_child == source.node) { + if (source.node->next_sibling != 0) + source.node->parent->first_child = source.node->next_sibling; + else + source.node->parent->first_child = 0; + } + if (source.node->parent->last_child == source.node) { + if (source.node->prev_sibling != 0) + source.node->parent->last_child = source.node->prev_sibling; + else + source.node->parent->last_child = 0; + } + source.node->parent = 0; + + // Fix source prev/next links. + source.node->prev_sibling = ret.head; + source.node->next_sibling = ret.feet; + + return ret; // A good compiler will move this, not copy. +} + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::move_in(iter loc, tree &other) { + if (other.head->next_sibling == other.feet) + return loc; // other tree is empty + + tree_node *other_first_head = other.head->next_sibling; + tree_node *other_last_head = other.feet->prev_sibling; + + sibling_iterator prev(loc); + --prev; + + prev.node->next_sibling = other_first_head; + loc.node->prev_sibling = other_last_head; + other_first_head->prev_sibling = prev.node; + other_last_head->next_sibling = loc.node; + + // Adjust parent pointers. + tree_node *walk = other_first_head; + while (true) { + walk->parent = loc.node->parent; + if (walk == other_last_head) + break; + walk = walk->next_sibling; + } + + // Close other tree. + other.head->next_sibling = other.feet; + other.feet->prev_sibling = other.head; + + return other_first_head; +} + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::move_in_below(iter loc, tree &other) { + if (other.head->next_sibling == other.feet) + return loc; // other tree is empty + + auto n = other.number_of_children(loc); + return move_in_as_nth_child(loc, n, other); +} + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::move_in_as_nth_child(iter loc, size_t n, + tree &other) { + if (other.head->next_sibling == other.feet) + return loc; // other tree is empty + + tree_node *other_first_head = other.head->next_sibling; + tree_node *other_last_head = other.feet->prev_sibling; + + if (n == 0) { + if (loc.node->first_child == 0) { + loc.node->first_child = other_first_head; + loc.node->last_child = other_last_head; + other_last_head->next_sibling = 0; + other_first_head->prev_sibling = 0; + } else { + loc.node->first_child->prev_sibling = other_last_head; + other_last_head->next_sibling = loc.node->first_child; + loc.node->first_child = other_first_head; + other_first_head->prev_sibling = 0; + } + } else { + --n; + tree_node *walk = loc.node->first_child; + while (true) { + if (walk == 0) + throw std::range_error( + "tree: move_in_as_nth_child position out of range"); + if (n == 0) + break; + --n; + walk = walk->next_sibling; + } + if (walk->next_sibling == 0) + loc.node->last_child = other_last_head; + else + walk->next_sibling->prev_sibling = other_last_head; + other_last_head->next_sibling = walk->next_sibling; + walk->next_sibling = other_first_head; + other_first_head->prev_sibling = walk; + } + + // Adjust parent pointers. + tree_node *walk = other_first_head; + while (true) { + walk->parent = loc.node; + if (walk == other_last_head) + break; + walk = walk->next_sibling; + } + + // Close other tree. + other.head->next_sibling = other.feet; + other.feet->prev_sibling = other.head; + + return other_first_head; +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::merge(sibling_iterator to1, + sibling_iterator to2, + sibling_iterator from1, + sibling_iterator from2, + bool duplicate_leaves) { + sibling_iterator fnd; + while (from1 != from2) { + if ((fnd = std::find(to1, to2, (*from1))) != to2) { // element found + if (from1.begin() == from1.end()) { // full depth reached + if (duplicate_leaves) + append_child(parent(to1), (*from1)); + } else { // descend further + merge(fnd.begin(), fnd.end(), from1.begin(), from1.end(), + duplicate_leaves); + } + } else { // element missing + insert_subtree(to2, from1); + } + ++from1; + } +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::merge(iterator to, iterator from, + bool duplicate_leaves) { + sibling_iterator to1(to); + sibling_iterator to2 = to1; + ++to2; + sibling_iterator from1(from); + sibling_iterator from2 = from1; + ++from2; + + merge(to1, to2, from1, from2, duplicate_leaves); +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::sort(sibling_iterator from, + sibling_iterator to, bool deep) { + std::less<T> comp; + sort(from, to, comp, deep); +} template <class T, class tree_node_allocator> template <class StrictWeakOrdering> -void tree<T, tree_node_allocator>::sort(sibling_iterator from, sibling_iterator to, - StrictWeakOrdering comp, bool deep) - { - if(from==to) return; - // make list of sorted nodes - // CHECK: if multiset stores equivalent nodes in the order in which they - // are inserted, then this routine should be called 'stable_sort'. - std::multiset<tree_node *, compare_nodes<StrictWeakOrdering> > nodes(comp); - sibling_iterator it=from, it2=to; - while(it != to) { - nodes.insert(it.node); - ++it; - } - // reassemble - --it2; - - // prev and next are the nodes before and after the sorted range - tree_node *prev=from.node->prev_sibling; - tree_node *next=it2.node->next_sibling; - typename std::multiset<tree_node *, compare_nodes<StrictWeakOrdering> >::iterator nit=nodes.begin(), eit=nodes.end(); - if(prev==0) { - if((*nit)->parent!=0) // to catch "sorting the head" situations, when there is no parent - (*nit)->parent->first_child=(*nit); - } - else prev->next_sibling=(*nit); - - --eit; - while(nit!=eit) { - (*nit)->prev_sibling=prev; - if(prev) - prev->next_sibling=(*nit); - prev=(*nit); - ++nit; - } - // prev now points to the last-but-one node in the sorted range - if(prev) - prev->next_sibling=(*eit); - - // eit points to the last node in the sorted range. - (*eit)->next_sibling=next; - (*eit)->prev_sibling=prev; // missed in the loop above - if(next==0) { - if((*eit)->parent!=0) // to catch "sorting the head" situations, when there is no parent - (*eit)->parent->last_child=(*eit); - } - else next->prev_sibling=(*eit); - - if(deep) { // sort the children of each node too - sibling_iterator bcs(*nodes.begin()); - sibling_iterator ecs(*eit); - ++ecs; - while(bcs!=ecs) { - sort(begin(bcs), end(bcs), comp, deep); - ++bcs; - } - } - } +void tree<T, tree_node_allocator>::sort(sibling_iterator from, + sibling_iterator to, + StrictWeakOrdering comp, bool deep) { + if (from == to) + return; + // make list of sorted nodes + // CHECK: if multiset stores equivalent nodes in the order in which they + // are inserted, then this routine should be called 'stable_sort'. + std::multiset<tree_node *, compare_nodes<StrictWeakOrdering>> nodes(comp); + sibling_iterator it = from, it2 = to; + while (it != to) { + nodes.insert(it.node); + ++it; + } + // reassemble + --it2; + + // prev and next are the nodes before and after the sorted range + tree_node *prev = from.node->prev_sibling; + tree_node *next = it2.node->next_sibling; + typename std::multiset<tree_node *, + compare_nodes<StrictWeakOrdering>>::iterator + nit = nodes.begin(), + eit = nodes.end(); + if (prev == 0) { + if ((*nit)->parent != + 0) // to catch "sorting the head" situations, when there is no parent + (*nit)->parent->first_child = (*nit); + } else + prev->next_sibling = (*nit); + + --eit; + while (nit != eit) { + (*nit)->prev_sibling = prev; + if (prev) + prev->next_sibling = (*nit); + prev = (*nit); + ++nit; + } + // prev now points to the last-but-one node in the sorted range + if (prev) + prev->next_sibling = (*eit); + + // eit points to the last node in the sorted range. + (*eit)->next_sibling = next; + (*eit)->prev_sibling = prev; // missed in the loop above + if (next == 0) { + if ((*eit)->parent != + 0) // to catch "sorting the head" situations, when there is no parent + (*eit)->parent->last_child = (*eit); + } else + next->prev_sibling = (*eit); + + if (deep) { // sort the children of each node too + sibling_iterator bcs(*nodes.begin()); + sibling_iterator ecs(*eit); + ++ecs; + while (bcs != ecs) { + sort(begin(bcs), end(bcs), comp, deep); + ++bcs; + } + } +} template <class T, class tree_node_allocator> template <typename iter> -bool tree<T, tree_node_allocator>::equal(const iter& one_, const iter& two, const iter& three_) const - { - std::equal_to<T> comp; - return equal(one_, two, three_, comp); - } +bool tree<T, tree_node_allocator>::equal(const iter &one_, const iter &two, + const iter &three_) const { + std::equal_to<T> comp; + return equal(one_, two, three_, comp); +} template <class T, class tree_node_allocator> template <typename iter> -bool tree<T, tree_node_allocator>::equal_subtree(const iter& one_, const iter& two_) const - { - std::equal_to<T> comp; - return equal_subtree(one_, two_, comp); - } +bool tree<T, tree_node_allocator>::equal_subtree(const iter &one_, + const iter &two_) const { + std::equal_to<T> comp; + return equal_subtree(one_, two_, comp); +} template <class T, class tree_node_allocator> template <typename iter, class BinaryPredicate> -bool tree<T, tree_node_allocator>::equal(const iter& one_, const iter& two, const iter& three_, BinaryPredicate fun) const - { - pre_order_iterator one(one_), three(three_); - -// if(one==two && is_valid(three) && three.number_of_children()!=0) -// return false; - while(one!=two && is_valid(three)) { - if(!fun(*one,*three)) - return false; - if(one.number_of_children()!=three.number_of_children()) - return false; - ++one; - ++three; - } - return true; - } +bool tree<T, tree_node_allocator>::equal(const iter &one_, const iter &two, + const iter &three_, + BinaryPredicate fun) const { + pre_order_iterator one(one_), three(three_); + + // if(one==two && is_valid(three) && three.number_of_children()!=0) + // return false; + while (one != two && is_valid(three)) { + if (!fun(*one, *three)) + return false; + if (one.number_of_children() != three.number_of_children()) + return false; + ++one; + ++three; + } + return true; +} template <class T, class tree_node_allocator> template <typename iter, class BinaryPredicate> -bool tree<T, tree_node_allocator>::equal_subtree(const iter& one_, const iter& two_, BinaryPredicate fun) const - { - pre_order_iterator one(one_), two(two_); - - if(!fun(*one,*two)) return false; - if(number_of_children(one)!=number_of_children(two)) return false; - return equal(begin(one),end(one),begin(two),fun); - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator> tree<T, tree_node_allocator>::subtree(sibling_iterator from, sibling_iterator to) const - { - assert(from!=to); // if from==to, the range is empty, hence no tree to return. - - tree tmp; - tmp.set_head(value_type()); - tmp.replace(tmp.begin(), tmp.end(), from, to); - return tmp; - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::subtree(tree& tmp, sibling_iterator from, sibling_iterator to) const - { - assert(from!=to); // if from==to, the range is empty, hence no tree to return. - - tmp.set_head(value_type()); - tmp.replace(tmp.begin(), tmp.end(), from, to); - } - -template <class T, class tree_node_allocator> -size_t tree<T, tree_node_allocator>::size() const - { - size_t i=0; - pre_order_iterator it=begin(), eit=end(); - while(it!=eit) { - ++i; - ++it; - } - return i; - } - -template <class T, class tree_node_allocator> -size_t tree<T, tree_node_allocator>::size(const iterator_base& top) const - { - size_t i=0; - pre_order_iterator it=top, eit=top; - eit.skip_children(); - ++eit; - while(it!=eit) { - ++i; - ++it; - } - return i; - } - -template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::empty() const - { - pre_order_iterator it=begin(), eit=end(); - return (it==eit); - } - -template <class T, class tree_node_allocator> -int tree<T, tree_node_allocator>::depth(const iterator_base& it) - { - tree_node* pos=it.node; - assert(pos!=0); - int ret=0; - while(pos->parent!=0) { - pos=pos->parent; - ++ret; - } - return ret; - } - -template <class T, class tree_node_allocator> -int tree<T, tree_node_allocator>::depth(const iterator_base& it, const iterator_base& root) - { - tree_node* pos=it.node; - assert(pos!=0); - int ret=0; - while(pos->parent!=0 && pos!=root.node) { - pos=pos->parent; - ++ret; - } - return ret; - } +bool tree<T, tree_node_allocator>::equal_subtree(const iter &one_, + const iter &two_, + BinaryPredicate fun) const { + pre_order_iterator one(one_), two(two_); + + if (!fun(*one, *two)) + return false; + if (number_of_children(one) != number_of_children(two)) + return false; + return equal(begin(one), end(one), begin(two), fun); +} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator> +tree<T, tree_node_allocator>::subtree(sibling_iterator from, + sibling_iterator to) const { + assert(from != + to); // if from==to, the range is empty, hence no tree to return. + + tree tmp; + tmp.set_head(value_type()); + tmp.replace(tmp.begin(), tmp.end(), from, to); + return tmp; +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::subtree(tree &tmp, sibling_iterator from, + sibling_iterator to) const { + assert(from != + to); // if from==to, the range is empty, hence no tree to return. + + tmp.set_head(value_type()); + tmp.replace(tmp.begin(), tmp.end(), from, to); +} + +template <class T, class tree_node_allocator> +size_t tree<T, tree_node_allocator>::size() const { + size_t i = 0; + pre_order_iterator it = begin(), eit = end(); + while (it != eit) { + ++i; + ++it; + } + return i; +} + +template <class T, class tree_node_allocator> +size_t tree<T, tree_node_allocator>::size(const iterator_base &top) const { + size_t i = 0; + pre_order_iterator it = top, eit = top; + eit.skip_children(); + ++eit; + while (it != eit) { + ++i; + ++it; + } + return i; +} + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::empty() const { + pre_order_iterator it = begin(), eit = end(); + return (it == eit); +} + +template <class T, class tree_node_allocator> +int tree<T, tree_node_allocator>::depth(const iterator_base &it) { + tree_node *pos = it.node; + assert(pos != 0); + int ret = 0; + while (pos->parent != 0) { + pos = pos->parent; + ++ret; + } + return ret; +} + +template <class T, class tree_node_allocator> +int tree<T, tree_node_allocator>::depth(const iterator_base &it, + const iterator_base &root) { + tree_node *pos = it.node; + assert(pos != 0); + int ret = 0; + while (pos->parent != 0 && pos != root.node) { + pos = pos->parent; + ++ret; + } + return ret; +} template <class T, class tree_node_allocator> template <class Predicate> -int tree<T, tree_node_allocator>::depth(const iterator_base& it, Predicate p) - { - tree_node* pos=it.node; - assert(pos!=0); - int ret=0; - while(pos->parent!=0) { - pos=pos->parent; - if(p(pos)) - ++ret; - } - return ret; - } +int tree<T, tree_node_allocator>::depth(const iterator_base &it, Predicate p) { + tree_node *pos = it.node; + assert(pos != 0); + int ret = 0; + while (pos->parent != 0) { + pos = pos->parent; + if (p(pos)) + ++ret; + } + return ret; +} template <class T, class tree_node_allocator> template <class Predicate> -int tree<T, tree_node_allocator>::distance(const iterator_base& top, const iterator_base& bottom, Predicate p) - { - tree_node* pos=bottom.node; - assert(pos!=0); - int ret=0; - while(pos->parent!=0 && pos!=top.node) { - pos=pos->parent; - if(p(pos)) - ++ret; - } - return ret; - } - -template <class T, class tree_node_allocator> -int tree<T, tree_node_allocator>::max_depth() const - { - int maxd=-1; - for(tree_node *it = head->next_sibling; it!=feet; it=it->next_sibling) - maxd=std::max(maxd, max_depth(it)); - - return maxd; - } - - -template <class T, class tree_node_allocator> -int tree<T, tree_node_allocator>::max_depth(const iterator_base& pos) const - { - tree_node *tmp=pos.node; - - if(tmp==0 || tmp==head || tmp==feet) return -1; - - int curdepth=0, maxdepth=0; - while(true) { // try to walk the bottom of the tree - while(tmp->first_child==0) { - if(tmp==pos.node) return maxdepth; - if(tmp->next_sibling==0) { - // try to walk up and then right again - do { - tmp=tmp->parent; - if(tmp==0) return maxdepth; - --curdepth; - } - while(tmp->next_sibling==0); - } - if(tmp==pos.node) return maxdepth; - tmp=tmp->next_sibling; - } - tmp=tmp->first_child; - ++curdepth; - maxdepth=std::max(curdepth, maxdepth); - } - } - -template <class T, class tree_node_allocator> -unsigned int tree<T, tree_node_allocator>::number_of_children(const iterator_base& it) - { - tree_node *pos=it.node->first_child; - if(pos==0) return 0; - - unsigned int ret=1; -// while(pos!=it.node->last_child) { -// ++ret; -// pos=pos->next_sibling; -// } - while((pos=pos->next_sibling)) - ++ret; - return ret; - } - -template <class T, class tree_node_allocator> -unsigned int tree<T, tree_node_allocator>::number_of_siblings(const iterator_base& it) const - { - tree_node *pos=it.node; - unsigned int ret=0; - // count forward - while(pos->next_sibling && - pos->next_sibling!=head && - pos->next_sibling!=feet) { - ++ret; - pos=pos->next_sibling; - } - // count backward - pos=it.node; - while(pos->prev_sibling && - pos->prev_sibling!=head && - pos->prev_sibling!=feet) { - ++ret; - pos=pos->prev_sibling; - } - - return ret; - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::swap(sibling_iterator it) - { - tree_node *nxt=it.node->next_sibling; - if(nxt) { - if(it.node->prev_sibling) - it.node->prev_sibling->next_sibling=nxt; - else - it.node->parent->first_child=nxt; - nxt->prev_sibling=it.node->prev_sibling; - tree_node *nxtnxt=nxt->next_sibling; - if(nxtnxt) - nxtnxt->prev_sibling=it.node; - else - it.node->parent->last_child=it.node; - nxt->next_sibling=it.node; - it.node->prev_sibling=nxt; - it.node->next_sibling=nxtnxt; - } - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::swap(iterator one, iterator two) - { - // if one and two are adjacent siblings, use the sibling swap - if(one.node->next_sibling==two.node) swap(one); - else if(two.node->next_sibling==one.node) swap(two); - else { - tree_node *nxt1=one.node->next_sibling; - tree_node *nxt2=two.node->next_sibling; - tree_node *pre1=one.node->prev_sibling; - tree_node *pre2=two.node->prev_sibling; - tree_node *par1=one.node->parent; - tree_node *par2=two.node->parent; - - // reconnect - one.node->parent=par2; - one.node->next_sibling=nxt2; - if(nxt2) nxt2->prev_sibling=one.node; - else par2->last_child=one.node; - one.node->prev_sibling=pre2; - if(pre2) pre2->next_sibling=one.node; - else par2->first_child=one.node; - - two.node->parent=par1; - two.node->next_sibling=nxt1; - if(nxt1) nxt1->prev_sibling=two.node; - else par1->last_child=two.node; - two.node->prev_sibling=pre1; - if(pre1) pre1->next_sibling=two.node; - else par1->first_child=two.node; - } - } +int tree<T, tree_node_allocator>::distance(const iterator_base &top, + const iterator_base &bottom, + Predicate p) { + tree_node *pos = bottom.node; + assert(pos != 0); + int ret = 0; + while (pos->parent != 0 && pos != top.node) { + pos = pos->parent; + if (p(pos)) + ++ret; + } + return ret; +} + +template <class T, class tree_node_allocator> +int tree<T, tree_node_allocator>::max_depth() const { + int maxd = -1; + for (tree_node *it = head->next_sibling; it != feet; it = it->next_sibling) + maxd = std::max(maxd, max_depth(it)); + + return maxd; +} + +template <class T, class tree_node_allocator> +int tree<T, tree_node_allocator>::max_depth(const iterator_base &pos) const { + tree_node *tmp = pos.node; + + if (tmp == 0 || tmp == head || tmp == feet) + return -1; + + int curdepth = 0, maxdepth = 0; + while (true) { // try to walk the bottom of the tree + while (tmp->first_child == 0) { + if (tmp == pos.node) + return maxdepth; + if (tmp->next_sibling == 0) { + // try to walk up and then right again + do { + tmp = tmp->parent; + if (tmp == 0) + return maxdepth; + --curdepth; + } while (tmp->next_sibling == 0); + } + if (tmp == pos.node) + return maxdepth; + tmp = tmp->next_sibling; + } + tmp = tmp->first_child; + ++curdepth; + maxdepth = std::max(curdepth, maxdepth); + } +} + +template <class T, class tree_node_allocator> +unsigned int +tree<T, tree_node_allocator>::number_of_children(const iterator_base &it) { + tree_node *pos = it.node->first_child; + if (pos == 0) + return 0; + + unsigned int ret = 1; + // while(pos!=it.node->last_child) { + // ++ret; + // pos=pos->next_sibling; + // } + while ((pos = pos->next_sibling)) + ++ret; + return ret; +} + +template <class T, class tree_node_allocator> +unsigned int tree<T, tree_node_allocator>::number_of_siblings( + const iterator_base &it) const { + tree_node *pos = it.node; + unsigned int ret = 0; + // count forward + while (pos->next_sibling && pos->next_sibling != head && + pos->next_sibling != feet) { + ++ret; + pos = pos->next_sibling; + } + // count backward + pos = it.node; + while (pos->prev_sibling && pos->prev_sibling != head && + pos->prev_sibling != feet) { + ++ret; + pos = pos->prev_sibling; + } + + return ret; +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::swap(sibling_iterator it) { + tree_node *nxt = it.node->next_sibling; + if (nxt) { + if (it.node->prev_sibling) + it.node->prev_sibling->next_sibling = nxt; + else + it.node->parent->first_child = nxt; + nxt->prev_sibling = it.node->prev_sibling; + tree_node *nxtnxt = nxt->next_sibling; + if (nxtnxt) + nxtnxt->prev_sibling = it.node; + else + it.node->parent->last_child = it.node; + nxt->next_sibling = it.node; + it.node->prev_sibling = nxt; + it.node->next_sibling = nxtnxt; + } +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::swap(iterator one, iterator two) { + // if one and two are adjacent siblings, use the sibling swap + if (one.node->next_sibling == two.node) + swap(one); + else if (two.node->next_sibling == one.node) + swap(two); + else { + tree_node *nxt1 = one.node->next_sibling; + tree_node *nxt2 = two.node->next_sibling; + tree_node *pre1 = one.node->prev_sibling; + tree_node *pre2 = two.node->prev_sibling; + tree_node *par1 = one.node->parent; + tree_node *par2 = two.node->parent; + + // reconnect + one.node->parent = par2; + one.node->next_sibling = nxt2; + if (nxt2) + nxt2->prev_sibling = one.node; + else + par2->last_child = one.node; + one.node->prev_sibling = pre2; + if (pre2) + pre2->next_sibling = one.node; + else + par2->first_child = one.node; + + two.node->parent = par1; + two.node->next_sibling = nxt1; + if (nxt1) + nxt1->prev_sibling = two.node; + else + par1->last_child = two.node; + two.node->prev_sibling = pre1; + if (pre1) + pre1->next_sibling = two.node; + else + par1->first_child = two.node; + } +} // template <class BinaryPredicate> -// tree<T, tree_node_allocator>::iterator tree<T, tree_node_allocator>::find_subtree( -// sibling_iterator subfrom, sibling_iterator subto, iterator from, iterator to, -// BinaryPredicate fun) const +// tree<T, tree_node_allocator>::iterator tree<T, +// tree_node_allocator>::find_subtree( sibling_iterator subfrom, +// sibling_iterator subto, iterator from, iterator to, BinaryPredicate fun) +// const // { // assert(1==0); // this routine is not finished yet. // while(from!=to) { // if(fun(*subfrom, *from)) { -// +// // } // } // return to; // } template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::is_in_subtree(const iterator_base& it, const iterator_base& top) const - { - sibling_iterator first=top; - sibling_iterator last=first; - ++last; - return is_in_subtree(it, first, last); - } - -template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::is_in_subtree(const iterator_base& it, const iterator_base& begin, - const iterator_base& end) const - { - // FIXME: this should be optimised. - pre_order_iterator tmp=begin; - while(tmp!=end) { - if(tmp==it) return true; - ++tmp; - } - return false; - } - -template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::is_valid(const iterator_base& it) const - { - if(it.node==0 || it.node==feet || it.node==head) return false; - else return true; - } - -template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::is_head(const iterator_base& it) - { - if(it.node->parent==0) return true; - return false; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::iterator tree<T, tree_node_allocator>::lowest_common_ancestor( - const iterator_base& one, const iterator_base& two) const - { - std::set<iterator, iterator_base_less> parents; - - // Walk up from 'one' storing all parents. - iterator walk=one; - do { - walk=parent(walk); - parents.insert(walk); - } - while( walk.node->parent ); - - // Walk up from 'two' until we encounter a node in parents. - walk=two; - do { - walk=parent(walk); - if(parents.find(walk) != parents.end()) break; - } - while( walk.node->parent ); - - return walk; - } - -template <class T, class tree_node_allocator> -unsigned int tree<T, tree_node_allocator>::index(sibling_iterator it) const - { - unsigned int ind=0; - if(it.node->parent==0) { - while(it.node->prev_sibling!=head) { - it.node=it.node->prev_sibling; - ++ind; - } - } - else { - while(it.node->prev_sibling!=0) { - it.node=it.node->prev_sibling; - ++ind; - } - } - return ind; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::sibling(const iterator_base& it, unsigned int num) const - { - tree_node *tmp; - if(it.node->parent==0) { - tmp=head->next_sibling; - while(num) { - tmp = tmp->next_sibling; - --num; - } - } - else { - tmp=it.node->parent->first_child; - while(num) { - assert(tmp!=0); - tmp = tmp->next_sibling; - --num; - } - } - return tmp; - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::debug_verify_consistency() const - { - iterator it=begin(); - while(it!=end()) { - // std::cerr << *it << " (" << it.node << ")" << std::endl; - if(it.node->parent!=0) { - if(it.node->prev_sibling==0) - assert(it.node->parent->first_child==it.node); - else - assert(it.node->prev_sibling->next_sibling==it.node); - if(it.node->next_sibling==0) - assert(it.node->parent->last_child==it.node); - else - assert(it.node->next_sibling->prev_sibling==it.node); - } - ++it; - } - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::child(const iterator_base& it, unsigned int num) - { - tree_node *tmp=it.node->first_child; - while(num--) { - assert(tmp!=0); - tmp=tmp->next_sibling; - } - return tmp; - } +bool tree<T, tree_node_allocator>::is_in_subtree( + const iterator_base &it, const iterator_base &top) const { + sibling_iterator first = top; + sibling_iterator last = first; + ++last; + return is_in_subtree(it, first, last); +} +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::is_in_subtree( + const iterator_base &it, const iterator_base &begin, + const iterator_base &end) const { + // FIXME: this should be optimised. + pre_order_iterator tmp = begin; + while (tmp != end) { + if (tmp == it) + return true; + ++tmp; + } + return false; +} +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::is_valid(const iterator_base &it) const { + if (it.node == 0 || it.node == feet || it.node == head) + return false; + else + return true; +} +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::is_head(const iterator_base &it) { + if (it.node->parent == 0) + return true; + return false; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::iterator +tree<T, tree_node_allocator>::lowest_common_ancestor( + const iterator_base &one, const iterator_base &two) const { + std::set<iterator, iterator_base_less> parents; + + // Walk up from 'one' storing all parents. + iterator walk = one; + do { + walk = parent(walk); + parents.insert(walk); + } while (walk.node->parent); + + // Walk up from 'two' until we encounter a node in parents. + walk = two; + do { + walk = parent(walk); + if (parents.find(walk) != parents.end()) + break; + } while (walk.node->parent); + + return walk; +} + +template <class T, class tree_node_allocator> +unsigned int tree<T, tree_node_allocator>::index(sibling_iterator it) const { + unsigned int ind = 0; + if (it.node->parent == 0) { + while (it.node->prev_sibling != head) { + it.node = it.node->prev_sibling; + ++ind; + } + } else { + while (it.node->prev_sibling != 0) { + it.node = it.node->prev_sibling; + ++ind; + } + } + return ind; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::sibling(const iterator_base &it, + unsigned int num) const { + tree_node *tmp; + if (it.node->parent == 0) { + tmp = head->next_sibling; + while (num) { + tmp = tmp->next_sibling; + --num; + } + } else { + tmp = it.node->parent->first_child; + while (num) { + assert(tmp != 0); + tmp = tmp->next_sibling; + --num; + } + } + return tmp; +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::debug_verify_consistency() const { + iterator it = begin(); + while (it != end()) { + // std::cerr << *it << " (" << it.node << ")" << std::endl; + if (it.node->parent != 0) { + if (it.node->prev_sibling == 0) + assert(it.node->parent->first_child == it.node); + else + assert(it.node->prev_sibling->next_sibling == it.node); + if (it.node->next_sibling == 0) + assert(it.node->parent->last_child == it.node); + else + assert(it.node->next_sibling->prev_sibling == it.node); + } + ++it; + } +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::child(const iterator_base &it, unsigned int num) { + tree_node *tmp = it.node->first_child; + while (num--) { + assert(tmp != 0); + tmp = tmp->next_sibling; + } + return tmp; +} // Iterator base template <class T, class tree_node_allocator> tree<T, tree_node_allocator>::iterator_base::iterator_base() - : node(0), skip_current_children_(false) - { - } + : node(0), skip_current_children_(false) {} template <class T, class tree_node_allocator> tree<T, tree_node_allocator>::iterator_base::iterator_base(tree_node *tn) - : node(tn), skip_current_children_(false) - { - } + : node(tn), skip_current_children_(false) {} template <class T, class tree_node_allocator> -T& tree<T, tree_node_allocator>::iterator_base::operator*() const - { - return node->data; - } +T &tree<T, tree_node_allocator>::iterator_base::operator*() const { + return node->data; +} template <class T, class tree_node_allocator> -T* tree<T, tree_node_allocator>::iterator_base::operator->() const - { - return &(node->data); - } +T *tree<T, tree_node_allocator>::iterator_base::operator->() const { + return &(node->data); +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::post_order_iterator::operator!=(const post_order_iterator& other) const - { - if(other.node!=this->node) return true; - else return false; - } +bool tree<T, tree_node_allocator>::post_order_iterator::operator!=( + const post_order_iterator &other) const { + if (other.node != this->node) + return true; + else + return false; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::post_order_iterator::operator==(const post_order_iterator& other) const - { - if(other.node==this->node) return true; - else return false; - } +bool tree<T, tree_node_allocator>::post_order_iterator::operator==( + const post_order_iterator &other) const { + if (other.node == this->node) + return true; + else + return false; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::pre_order_iterator::operator!=(const pre_order_iterator& other) const - { - if(other.node!=this->node) return true; - else return false; - } +bool tree<T, tree_node_allocator>::pre_order_iterator::operator!=( + const pre_order_iterator &other) const { + if (other.node != this->node) + return true; + else + return false; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::pre_order_iterator::operator==(const pre_order_iterator& other) const - { - if(other.node==this->node) return true; - else return false; - } +bool tree<T, tree_node_allocator>::pre_order_iterator::operator==( + const pre_order_iterator &other) const { + if (other.node == this->node) + return true; + else + return false; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::sibling_iterator::operator!=(const sibling_iterator& other) const - { - if(other.node!=this->node) return true; - else return false; - } +bool tree<T, tree_node_allocator>::sibling_iterator::operator!=( + const sibling_iterator &other) const { + if (other.node != this->node) + return true; + else + return false; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::sibling_iterator::operator==(const sibling_iterator& other) const - { - if(other.node==this->node) return true; - else return false; - } +bool tree<T, tree_node_allocator>::sibling_iterator::operator==( + const sibling_iterator &other) const { + if (other.node == this->node) + return true; + else + return false; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::leaf_iterator::operator!=(const leaf_iterator& other) const - { - if(other.node!=this->node) return true; - else return false; - } +bool tree<T, tree_node_allocator>::leaf_iterator::operator!=( + const leaf_iterator &other) const { + if (other.node != this->node) + return true; + else + return false; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::leaf_iterator::operator==(const leaf_iterator& other) const - { - if(other.node==this->node && other.top_node==this->top_node) return true; - else return false; - } +bool tree<T, tree_node_allocator>::leaf_iterator::operator==( + const leaf_iterator &other) const { + if (other.node == this->node && other.top_node == this->top_node) + return true; + else + return false; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::iterator_base::begin() const - { - if(node->first_child==0) - return end(); +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::iterator_base::begin() const { + if (node->first_child == 0) + return end(); - sibling_iterator ret(node->first_child); - ret.parent_=this->node; - return ret; - } + sibling_iterator ret(node->first_child); + ret.parent_ = this->node; + return ret; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::iterator_base::end() const - { - sibling_iterator ret(0); - ret.parent_=node; - return ret; - } +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::iterator_base::end() const { + sibling_iterator ret(0); + ret.parent_ = node; + return ret; +} template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::iterator_base::skip_children() - { - skip_current_children_=true; - } +void tree<T, tree_node_allocator>::iterator_base::skip_children() { + skip_current_children_ = true; +} template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::iterator_base::skip_children(bool skip) - { - skip_current_children_=skip; - } +void tree<T, tree_node_allocator>::iterator_base::skip_children(bool skip) { + skip_current_children_ = skip; +} template <class T, class tree_node_allocator> -unsigned int tree<T, tree_node_allocator>::iterator_base::number_of_children() const - { - tree_node *pos=node->first_child; - if(pos==0) return 0; - - unsigned int ret=1; - while(pos!=node->last_child) { - ++ret; - pos=pos->next_sibling; - } - return ret; - } - +unsigned int +tree<T, tree_node_allocator>::iterator_base::number_of_children() const { + tree_node *pos = node->first_child; + if (pos == 0) + return 0; + unsigned int ret = 1; + while (pos != node->last_child) { + ++ret; + pos = pos->next_sibling; + } + return ret; +} // Pre-order iterator template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator() - : iterator_base(0) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator(tree_node *tn) - : iterator_base(tn) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator(const iterator_base &other) - : iterator_base(other.node) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator(const sibling_iterator& other) - : iterator_base(other.node) - { - if(this->node==0) { - if(other.range_last()!=0) - this->node=other.range_last(); - else - this->node=other.parent_; - this->skip_children(); - ++(*this); - } - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator++() - { - assert(this->node!=0); - if(!this->skip_current_children_ && this->node->first_child != 0) { - this->node=this->node->first_child; - } - else { - this->skip_current_children_=false; - while(this->node->next_sibling==0) { - this->node=this->node->parent; - if(this->node==0) - return *this; - } - this->node=this->node->next_sibling; - } - return *this; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator--() - { - assert(this->node!=0); - if(this->node->prev_sibling) { - this->node=this->node->prev_sibling; - while(this->node->last_child) - this->node=this->node->last_child; - } - else { - this->node=this->node->parent; - if(this->node==0) - return *this; - } - return *this; -} - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::pre_order_iterator::operator++(int) - { - pre_order_iterator copy = *this; - ++(*this); - return copy; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::next_skip_children() - { - (*this).skip_children(); - (*this)++; - return *this; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::pre_order_iterator::operator--(int) -{ +tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator() + : iterator_base(0) {} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator( + tree_node *tn) + : iterator_base(tn) {} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator( + const iterator_base &other) + : iterator_base(other.node) {} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator( + const sibling_iterator &other) + : iterator_base(other.node) { + if (this->node == 0) { + if (other.range_last() != 0) + this->node = other.range_last(); + else + this->node = other.parent_; + this->skip_children(); + ++(*this); + } +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator & +tree<T, tree_node_allocator>::pre_order_iterator::operator++() { + assert(this->node != 0); + if (!this->skip_current_children_ && this->node->first_child != 0) { + this->node = this->node->first_child; + } else { + this->skip_current_children_ = false; + while (this->node->next_sibling == 0) { + this->node = this->node->parent; + if (this->node == 0) + return *this; + } + this->node = this->node->next_sibling; + } + return *this; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator & +tree<T, tree_node_allocator>::pre_order_iterator::operator--() { + assert(this->node != 0); + if (this->node->prev_sibling) { + this->node = this->node->prev_sibling; + while (this->node->last_child) + this->node = this->node->last_child; + } else { + this->node = this->node->parent; + if (this->node == 0) + return *this; + } + return *this; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator +tree<T, tree_node_allocator>::pre_order_iterator::operator++(int) { pre_order_iterator copy = *this; - --(*this); + ++(*this); return copy; } template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator+=(unsigned int num) - { - while(num>0) { - ++(*this); - --num; - } - return (*this); - } +typename tree<T, tree_node_allocator>::pre_order_iterator & +tree<T, tree_node_allocator>::pre_order_iterator::next_skip_children() { + (*this).skip_children(); + (*this)++; + return *this; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator-=(unsigned int num) - { - while(num>0) { - --(*this); - --num; - } - return (*this); - } +typename tree<T, tree_node_allocator>::pre_order_iterator +tree<T, tree_node_allocator>::pre_order_iterator::operator--(int) { + pre_order_iterator copy = *this; + --(*this); + return copy; +} +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator & +tree<T, tree_node_allocator>::pre_order_iterator::operator+=(unsigned int num) { + while (num > 0) { + ++(*this); + --num; + } + return (*this); +} +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator & +tree<T, tree_node_allocator>::pre_order_iterator::operator-=(unsigned int num) { + while (num > 0) { + --(*this); + --num; + } + return (*this); +} // Post-order iterator template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator() - : iterator_base(0) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator(tree_node *tn) - : iterator_base(tn) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator(const iterator_base &other) - : iterator_base(other.node) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator(const sibling_iterator& other) - : iterator_base(other.node) - { - if(this->node==0) { - if(other.range_last()!=0) - this->node=other.range_last(); - else - this->node=other.parent_; - this->skip_children(); - ++(*this); - } - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator++() - { - assert(this->node!=0); - if(this->node->next_sibling==0) { - this->node=this->node->parent; - this->skip_current_children_=false; - } - else { - this->node=this->node->next_sibling; - if(this->skip_current_children_) { - this->skip_current_children_=false; - } - else { - while(this->node->first_child) - this->node=this->node->first_child; - } - } - return *this; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator--() - { - assert(this->node!=0); - if(this->skip_current_children_ || this->node->last_child==0) { - this->skip_current_children_=false; - while(this->node->prev_sibling==0) - this->node=this->node->parent; - this->node=this->node->prev_sibling; - } - else { - this->node=this->node->last_child; - } - return *this; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::post_order_iterator::operator++(int) - { - post_order_iterator copy = *this; - ++(*this); - return copy; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::post_order_iterator::operator--(int) - { - post_order_iterator copy = *this; - --(*this); - return copy; - } - - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator+=(unsigned int num) - { - while(num>0) { - ++(*this); - --num; - } - return (*this); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator-=(unsigned int num) - { - while(num>0) { - --(*this); - --num; - } - return (*this); - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::post_order_iterator::descend_all() - { - assert(this->node!=0); - while(this->node->first_child) - this->node=this->node->first_child; - } +tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator() + : iterator_base(0) {} +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator( + tree_node *tn) + : iterator_base(tn) {} -// Breadth-first iterator +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator( + const iterator_base &other) + : iterator_base(other.node) {} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::breadth_first_queued_iterator::breadth_first_queued_iterator() - : iterator_base() - { - } +tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator( + const sibling_iterator &other) + : iterator_base(other.node) { + if (this->node == 0) { + if (other.range_last() != 0) + this->node = other.range_last(); + else + this->node = other.parent_; + this->skip_children(); + ++(*this); + } +} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::breadth_first_queued_iterator::breadth_first_queued_iterator(tree_node *tn) - : iterator_base(tn) - { - traversal_queue.push(tn); - } +typename tree<T, tree_node_allocator>::post_order_iterator & +tree<T, tree_node_allocator>::post_order_iterator::operator++() { + assert(this->node != 0); + if (this->node->next_sibling == 0) { + this->node = this->node->parent; + this->skip_current_children_ = false; + } else { + this->node = this->node->next_sibling; + if (this->skip_current_children_) { + this->skip_current_children_ = false; + } else { + while (this->node->first_child) + this->node = this->node->first_child; + } + } + return *this; +} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::breadth_first_queued_iterator::breadth_first_queued_iterator(const iterator_base& other) - : iterator_base(other.node) - { - traversal_queue.push(other.node); - } +typename tree<T, tree_node_allocator>::post_order_iterator & +tree<T, tree_node_allocator>::post_order_iterator::operator--() { + assert(this->node != 0); + if (this->skip_current_children_ || this->node->last_child == 0) { + this->skip_current_children_ = false; + while (this->node->prev_sibling == 0) + this->node = this->node->parent; + this->node = this->node->prev_sibling; + } else { + this->node = this->node->last_child; + } + return *this; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator!=(const breadth_first_queued_iterator& other) const - { - if(other.node!=this->node) return true; - else return false; - } +typename tree<T, tree_node_allocator>::post_order_iterator +tree<T, tree_node_allocator>::post_order_iterator::operator++(int) { + post_order_iterator copy = *this; + ++(*this); + return copy; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator==(const breadth_first_queued_iterator& other) const - { - if(other.node==this->node) return true; - else return false; - } +typename tree<T, tree_node_allocator>::post_order_iterator +tree<T, tree_node_allocator>::post_order_iterator::operator--(int) { + post_order_iterator copy = *this; + --(*this); + return copy; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::breadth_first_queued_iterator& tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator++() - { - assert(this->node!=0); +typename tree<T, tree_node_allocator>::post_order_iterator & +tree<T, tree_node_allocator>::post_order_iterator::operator+=( + unsigned int num) { + while (num > 0) { + ++(*this); + --num; + } + return (*this); +} - // Add child nodes and pop current node - sibling_iterator sib=this->begin(); - while(sib!=this->end()) { - traversal_queue.push(sib.node); - ++sib; - } - traversal_queue.pop(); - if(traversal_queue.size()>0) - this->node=traversal_queue.front(); - else - this->node=0; - return (*this); - } +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator & +tree<T, tree_node_allocator>::post_order_iterator::operator-=( + unsigned int num) { + while (num > 0) { + --(*this); + --num; + } + return (*this); +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::breadth_first_queued_iterator tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator++(int) - { - breadth_first_queued_iterator copy = *this; - ++(*this); - return copy; - } +void tree<T, tree_node_allocator>::post_order_iterator::descend_all() { + assert(this->node != 0); + while (this->node->first_child) + this->node = this->node->first_child; +} + +// Breadth-first iterator template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::breadth_first_queued_iterator& tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator+=(unsigned int num) - { - while(num>0) { - ++(*this); - --num; - } - return (*this); - } +tree<T, tree_node_allocator>::breadth_first_queued_iterator:: + breadth_first_queued_iterator() + : iterator_base() {} +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::breadth_first_queued_iterator:: + breadth_first_queued_iterator(tree_node *tn) + : iterator_base(tn) { + traversal_queue.push(tn); +} +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::breadth_first_queued_iterator:: + breadth_first_queued_iterator(const iterator_base &other) + : iterator_base(other.node) { + traversal_queue.push(other.node); +} + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator!=( + const breadth_first_queued_iterator &other) const { + if (other.node != this->node) + return true; + else + return false; +} + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator==( + const breadth_first_queued_iterator &other) const { + if (other.node == this->node) + return true; + else + return false; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator & +tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator++() { + assert(this->node != 0); + + // Add child nodes and pop current node + sibling_iterator sib = this->begin(); + while (sib != this->end()) { + traversal_queue.push(sib.node); + ++sib; + } + traversal_queue.pop(); + if (traversal_queue.size() > 0) + this->node = traversal_queue.front(); + else + this->node = 0; + return (*this); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator +tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator++(int) { + breadth_first_queued_iterator copy = *this; + ++(*this); + return copy; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator & +tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator+=( + unsigned int num) { + while (num > 0) { + ++(*this); + --num; + } + return (*this); +} // Fixed depth iterator template <class T, class tree_node_allocator> tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator() - : iterator_base() - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(tree_node *tn) - : iterator_base(tn), top_node(0) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(const iterator_base& other) - : iterator_base(other.node), top_node(0) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(const sibling_iterator& other) - : iterator_base(other.node), top_node(0) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(const fixed_depth_iterator& other) - : iterator_base(other.node), top_node(other.top_node) - { - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::fixed_depth_iterator::swap(fixed_depth_iterator& first, fixed_depth_iterator& second) - { - std::swap(first.node, second.node); - std::swap(first.skip_current_children_, second.skip_current_children_); - std::swap(first.top_node, second.top_node); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator=(fixed_depth_iterator other) - { - swap(*this, other); - return *this; - } - -template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::fixed_depth_iterator::operator==(const fixed_depth_iterator& other) const - { - if(other.node==this->node && other.top_node==top_node) return true; - else return false; - } - -template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::fixed_depth_iterator::operator!=(const fixed_depth_iterator& other) const - { - if(other.node!=this->node || other.top_node!=top_node) return true; - else return false; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator++() - { - assert(this->node!=0); - - if(this->node->next_sibling) { - this->node=this->node->next_sibling; - } - else { - int relative_depth=0; - upper: - do { - if(this->node==this->top_node) { - this->node=0; // FIXME: return a proper fixed_depth end iterator once implemented - return *this; - } - this->node=this->node->parent; - if(this->node==0) return *this; - --relative_depth; - } while(this->node->next_sibling==0); - lower: - this->node=this->node->next_sibling; - while(this->node->first_child==0) { - if(this->node->next_sibling==0) - goto upper; - this->node=this->node->next_sibling; - if(this->node==0) return *this; - } - while(relative_depth<0 && this->node->first_child!=0) { - this->node=this->node->first_child; - ++relative_depth; - } - if(relative_depth<0) { - if(this->node->next_sibling==0) goto upper; - else goto lower; - } - } - return *this; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator--() - { - assert(this->node!=0); - - if(this->node->prev_sibling) { - this->node=this->node->prev_sibling; - } - else { - int relative_depth=0; - upper: - do { - if(this->node==this->top_node) { - this->node=0; - return *this; - } - this->node=this->node->parent; - if(this->node==0) return *this; - --relative_depth; - } while(this->node->prev_sibling==0); - lower: - this->node=this->node->prev_sibling; - while(this->node->last_child==0) { - if(this->node->prev_sibling==0) - goto upper; - this->node=this->node->prev_sibling; - if(this->node==0) return *this; - } - while(relative_depth<0 && this->node->last_child!=0) { - this->node=this->node->last_child; - ++relative_depth; - } - if(relative_depth<0) { - if(this->node->prev_sibling==0) goto upper; - else goto lower; - } - } - return *this; + : iterator_base() {} -// -// -// assert(this->node!=0); -// if(this->node->prev_sibling!=0) { -// this->node=this->node->prev_sibling; -// assert(this->node!=0); -// if(this->node->parent==0 && this->node->prev_sibling==0) // head element -// this->node=0; -// } -// else { -// tree_node *par=this->node->parent; -// do { -// par=par->prev_sibling; -// if(par==0) { // FIXME: need to keep track of this! -// this->node=0; -// return *this; -// } -// } while(par->last_child==0); -// this->node=par->last_child; -// } -// return *this; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::fixed_depth_iterator::operator++(int) - { - fixed_depth_iterator copy = *this; - ++(*this); - return copy; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::fixed_depth_iterator::operator--(int) - { - fixed_depth_iterator copy = *this; - --(*this); - return copy; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator-=(unsigned int num) - { - while(num>0) { - --(*this); - --(num); - } - return (*this); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator+=(unsigned int num) - { - while(num>0) { - ++(*this); - --(num); - } - return *this; - } +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator( + tree_node *tn) + : iterator_base(tn), top_node(0) {} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator( + const iterator_base &other) + : iterator_base(other.node), top_node(0) {} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator( + const sibling_iterator &other) + : iterator_base(other.node), top_node(0) {} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator( + const fixed_depth_iterator &other) + : iterator_base(other.node), top_node(other.top_node) {} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::fixed_depth_iterator::swap( + fixed_depth_iterator &first, fixed_depth_iterator &second) { + std::swap(first.node, second.node); + std::swap(first.skip_current_children_, second.skip_current_children_); + std::swap(first.top_node, second.top_node); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator & +tree<T, tree_node_allocator>::fixed_depth_iterator::operator=( + fixed_depth_iterator other) { + swap(*this, other); + return *this; +} + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::fixed_depth_iterator::operator==( + const fixed_depth_iterator &other) const { + if (other.node == this->node && other.top_node == top_node) + return true; + else + return false; +} + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::fixed_depth_iterator::operator!=( + const fixed_depth_iterator &other) const { + if (other.node != this->node || other.top_node != top_node) + return true; + else + return false; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator & +tree<T, tree_node_allocator>::fixed_depth_iterator::operator++() { + assert(this->node != 0); + + if (this->node->next_sibling) { + this->node = this->node->next_sibling; + } else { + int relative_depth = 0; + upper: + do { + if (this->node == this->top_node) { + this->node = 0; // FIXME: return a proper fixed_depth end iterator once + // implemented + return *this; + } + this->node = this->node->parent; + if (this->node == 0) + return *this; + --relative_depth; + } while (this->node->next_sibling == 0); + lower: + this->node = this->node->next_sibling; + while (this->node->first_child == 0) { + if (this->node->next_sibling == 0) + goto upper; + this->node = this->node->next_sibling; + if (this->node == 0) + return *this; + } + while (relative_depth < 0 && this->node->first_child != 0) { + this->node = this->node->first_child; + ++relative_depth; + } + if (relative_depth < 0) { + if (this->node->next_sibling == 0) + goto upper; + else + goto lower; + } + } + return *this; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator & +tree<T, tree_node_allocator>::fixed_depth_iterator::operator--() { + assert(this->node != 0); + + if (this->node->prev_sibling) { + this->node = this->node->prev_sibling; + } else { + int relative_depth = 0; + upper: + do { + if (this->node == this->top_node) { + this->node = 0; + return *this; + } + this->node = this->node->parent; + if (this->node == 0) + return *this; + --relative_depth; + } while (this->node->prev_sibling == 0); + lower: + this->node = this->node->prev_sibling; + while (this->node->last_child == 0) { + if (this->node->prev_sibling == 0) + goto upper; + this->node = this->node->prev_sibling; + if (this->node == 0) + return *this; + } + while (relative_depth < 0 && this->node->last_child != 0) { + this->node = this->node->last_child; + ++relative_depth; + } + if (relative_depth < 0) { + if (this->node->prev_sibling == 0) + goto upper; + else + goto lower; + } + } + return *this; + + // + // + // assert(this->node!=0); + // if(this->node->prev_sibling!=0) { + // this->node=this->node->prev_sibling; + // assert(this->node!=0); + // if(this->node->parent==0 && this->node->prev_sibling==0) // head + //element this->node=0; + // } + // else { + // tree_node *par=this->node->parent; + // do { + // par=par->prev_sibling; + // if(par==0) { // FIXME: need to keep track of this! + // this->node=0; + // return *this; + // } + // } while(par->last_child==0); + // this->node=par->last_child; + // } + // return *this; +} +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator +tree<T, tree_node_allocator>::fixed_depth_iterator::operator++(int) { + fixed_depth_iterator copy = *this; + ++(*this); + return copy; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator +tree<T, tree_node_allocator>::fixed_depth_iterator::operator--(int) { + fixed_depth_iterator copy = *this; + --(*this); + return copy; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator & +tree<T, tree_node_allocator>::fixed_depth_iterator::operator-=( + unsigned int num) { + while (num > 0) { + --(*this); + --(num); + } + return (*this); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator & +tree<T, tree_node_allocator>::fixed_depth_iterator::operator+=( + unsigned int num) { + while (num > 0) { + ++(*this); + --(num); + } + return *this; +} // Sibling iterator template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator() - : iterator_base() - { - set_parent_(); - } +tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator() + : iterator_base() { + set_parent_(); +} template <class T, class tree_node_allocator> tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator(tree_node *tn) - : iterator_base(tn) - { - set_parent_(); - } + : iterator_base(tn) { + set_parent_(); +} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator(const iterator_base& other) - : iterator_base(other.node) - { - set_parent_(); - } +tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator( + const iterator_base &other) + : iterator_base(other.node) { + set_parent_(); +} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator(const sibling_iterator& other) - : iterator_base(other), parent_(other.parent_) - { - } +tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator( + const sibling_iterator &other) + : iterator_base(other), parent_(other.parent_) {} template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::sibling_iterator::swap(sibling_iterator& first, sibling_iterator& second) - { - std::swap(first.node, second.node); - std::swap(first.skip_current_children_, second.skip_current_children_); - std::swap(first.parent_, second.parent_); - } - +void tree<T, tree_node_allocator>::sibling_iterator::swap( + sibling_iterator &first, sibling_iterator &second) { + std::swap(first.node, second.node); + std::swap(first.skip_current_children_, second.skip_current_children_); + std::swap(first.parent_, second.parent_); +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator=(sibling_iterator other) - { - swap(*this, other); - return *this; - } - +typename tree<T, tree_node_allocator>::sibling_iterator & +tree<T, tree_node_allocator>::sibling_iterator::operator=( + sibling_iterator other) { + swap(*this, other); + return *this; +} + template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::sibling_iterator::set_parent_() - { - parent_=0; - if(this->node==0) return; - if(this->node->parent!=0) - parent_=this->node->parent; - } +void tree<T, tree_node_allocator>::sibling_iterator::set_parent_() { + parent_ = 0; + if (this->node == 0) + return; + if (this->node->parent != 0) + parent_ = this->node->parent; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator++() - { - if(this->node) - this->node=this->node->next_sibling; - return *this; - } +typename tree<T, tree_node_allocator>::sibling_iterator & +tree<T, tree_node_allocator>::sibling_iterator::operator++() { + if (this->node) + this->node = this->node->next_sibling; + return *this; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator--() - { - if(this->node) this->node=this->node->prev_sibling; - else { - assert(parent_); - this->node=parent_->last_child; - } - return *this; +typename tree<T, tree_node_allocator>::sibling_iterator & +tree<T, tree_node_allocator>::sibling_iterator::operator--() { + if (this->node) + this->node = this->node->prev_sibling; + else { + assert(parent_); + this->node = parent_->last_child; + } + return *this; } template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::sibling_iterator::operator++(int) - { - sibling_iterator copy = *this; - ++(*this); - return copy; - } +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::sibling_iterator::operator++(int) { + sibling_iterator copy = *this; + ++(*this); + return copy; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::sibling_iterator::operator--(int) - { - sibling_iterator copy = *this; - --(*this); - return copy; - } +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::sibling_iterator::operator--(int) { + sibling_iterator copy = *this; + --(*this); + return copy; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator+=(unsigned int num) - { - while(num>0) { - ++(*this); - --num; - } - return (*this); - } +typename tree<T, tree_node_allocator>::sibling_iterator & +tree<T, tree_node_allocator>::sibling_iterator::operator+=(unsigned int num) { + while (num > 0) { + ++(*this); + --num; + } + return (*this); +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator-=(unsigned int num) - { - while(num>0) { - --(*this); - --num; - } - return (*this); - } +typename tree<T, tree_node_allocator>::sibling_iterator & +tree<T, tree_node_allocator>::sibling_iterator::operator-=(unsigned int num) { + while (num > 0) { + --(*this); + --num; + } + return (*this); +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::tree_node *tree<T, tree_node_allocator>::sibling_iterator::range_first() const - { - tree_node *tmp=parent_->first_child; - return tmp; - } +typename tree<T, tree_node_allocator>::tree_node * +tree<T, tree_node_allocator>::sibling_iterator::range_first() const { + tree_node *tmp = parent_->first_child; + return tmp; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::tree_node *tree<T, tree_node_allocator>::sibling_iterator::range_last() const - { - return parent_->last_child; - } +typename tree<T, tree_node_allocator>::tree_node * +tree<T, tree_node_allocator>::sibling_iterator::range_last() const { + return parent_->last_child; +} // Leaf iterator template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator() - : iterator_base(0), top_node(0) - { - } +tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator() + : iterator_base(0), top_node(0) {} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator(tree_node *tn, tree_node *top) - : iterator_base(tn), top_node(top) - { - } +tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator(tree_node *tn, + tree_node *top) + : iterator_base(tn), top_node(top) {} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator(const iterator_base &other) - : iterator_base(other.node), top_node(0) - { - } +tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator( + const iterator_base &other) + : iterator_base(other.node), top_node(0) {} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator(const sibling_iterator& other) - : iterator_base(other.node), top_node(0) - { - if(this->node==0) { - if(other.range_last()!=0) - this->node=other.range_last(); - else - this->node=other.parent_; - ++(*this); - } - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator++() - { - assert(this->node!=0); - if(this->node->first_child!=0) { // current node is no longer leaf (children got added) - while(this->node->first_child) - this->node=this->node->first_child; - } - else { - while(this->node->next_sibling==0) { - if (this->node->parent==0) return *this; - this->node=this->node->parent; - if (top_node != 0 && this->node==top_node) return *this; - } - this->node=this->node->next_sibling; - while(this->node->first_child) - this->node=this->node->first_child; - } - return *this; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator--() - { - assert(this->node!=0); - while (this->node->prev_sibling==0) { - if (this->node->parent==0) return *this; - this->node=this->node->parent; - if (top_node !=0 && this->node==top_node) return *this; - } - this->node=this->node->prev_sibling; - while(this->node->last_child) - this->node=this->node->last_child; - return *this; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::leaf_iterator::operator++(int) - { - leaf_iterator copy = *this; - ++(*this); - return copy; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::leaf_iterator::operator--(int) - { - leaf_iterator copy = *this; - --(*this); - return copy; - } - - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator+=(unsigned int num) - { - while(num>0) { - ++(*this); - --num; - } - return (*this); - } +tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator( + const sibling_iterator &other) + : iterator_base(other.node), top_node(0) { + if (this->node == 0) { + if (other.range_last() != 0) + this->node = other.range_last(); + else + this->node = other.parent_; + ++(*this); + } +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator-=(unsigned int num) - { - while(num>0) { - --(*this); - --num; - } - return (*this); - } +typename tree<T, tree_node_allocator>::leaf_iterator & +tree<T, tree_node_allocator>::leaf_iterator::operator++() { + assert(this->node != 0); + if (this->node->first_child != + 0) { // current node is no longer leaf (children got added) + while (this->node->first_child) + this->node = this->node->first_child; + } else { + while (this->node->next_sibling == 0) { + if (this->node->parent == 0) + return *this; + this->node = this->node->parent; + if (top_node != 0 && this->node == top_node) + return *this; + } + this->node = this->node->next_sibling; + while (this->node->first_child) + this->node = this->node->first_child; + } + return *this; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator & +tree<T, tree_node_allocator>::leaf_iterator::operator--() { + assert(this->node != 0); + while (this->node->prev_sibling == 0) { + if (this->node->parent == 0) + return *this; + this->node = this->node->parent; + if (top_node != 0 && this->node == top_node) + return *this; + } + this->node = this->node->prev_sibling; + while (this->node->last_child) + this->node = this->node->last_child; + return *this; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator +tree<T, tree_node_allocator>::leaf_iterator::operator++(int) { + leaf_iterator copy = *this; + ++(*this); + return copy; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator +tree<T, tree_node_allocator>::leaf_iterator::operator--(int) { + leaf_iterator copy = *this; + --(*this); + return copy; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator & +tree<T, tree_node_allocator>::leaf_iterator::operator+=(unsigned int num) { + while (num > 0) { + ++(*this); + --num; + } + return (*this); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator & +tree<T, tree_node_allocator>::leaf_iterator::operator-=(unsigned int num) { + while (num > 0) { + --(*this); + --num; + } + return (*this); +} #endif diff --git a/core/opengate_core/opengate_lib/tree_util.hh b/core/opengate_core/opengate_lib/tree_util.hh index bc69594f0..2706b3e2a 100644 --- a/core/opengate_core/opengate_lib/tree_util.hh +++ b/core/opengate_core/opengate_lib/tree_util.hh @@ -1,13 +1,13 @@ -/* +/* - A collection of miscellaneous utilities that operate on the templated - tree.hh class. + A collection of miscellaneous utilities that operate on the templated + tree.hh class. - Copyright (C) 2001-2009 Kasper Peeters <kasper.peeters@aei.mpg.de> + Copyright (C) 2001-2009 Kasper Peeters <kasper.peeters@aei.mpg.de> - (At the moment this only contains a printing utility, thanks to Linda - Buisman <linda.buisman@studentmail.newcastle.edu.au>) + (At the moment this only contains a printing utility, thanks to Linda + Buisman <linda.buisman@studentmail.newcastle.edu.au>) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -21,72 +21,70 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. - + */ #ifndef tree_util_hh_ #define tree_util_hh_ -#include <iostream> #include "tree.hh" +#include <iostream> namespace kptree { -template<class T> -void print_tree_bracketed(const tree<T>& t, std::ostream& str=std::cout); - -template<class T> -void print_subtree_bracketed(const tree<T>& t, typename tree<T>::iterator iRoot, - std::ostream& str=std::cout); - +template <class T> +void print_tree_bracketed(const tree<T> &t, std::ostream &str = std::cout); +template <class T> +void print_subtree_bracketed(const tree<T> &t, typename tree<T>::iterator iRoot, + std::ostream &str = std::cout); // Iterate over all roots (the head) and print each one on a new line // by calling printSingleRoot. -template<class T> -void print_tree_bracketed(const tree<T>& t, std::ostream& str) - { - int headCount = t.number_of_siblings(t.begin()); - int headNum = 0; - for(typename tree<T>::sibling_iterator iRoots = t.begin(); iRoots != t.end(); ++iRoots, ++headNum) { - print_subtree_bracketed(t,iRoots,str); - if (headNum != headCount) { - str << std::endl; - } - } - } - +template <class T> +void print_tree_bracketed(const tree<T> &t, std::ostream &str) { + int headCount = t.number_of_siblings(t.begin()); + int headNum = 0; + for (typename tree<T>::sibling_iterator iRoots = t.begin(); iRoots != t.end(); + ++iRoots, ++headNum) { + print_subtree_bracketed(t, iRoots, str); + if (headNum != headCount) { + str << std::endl; + } + } +} // Print everything under this root in a flat, bracketed structure. -template<class T> -void print_subtree_bracketed(const tree<T>& t, typename tree<T>::iterator iRoot, std::ostream& str) - { - if(t.empty()) return; - if (t.number_of_children(iRoot) == 0) { - str << *iRoot; - } - else { - // parent - str << *iRoot; - str << "("; - // child1, ..., childn - int siblingCount = t.number_of_siblings(t.begin(iRoot)); - int siblingNum; - typename tree<T>::sibling_iterator iChildren; - for (iChildren = t.begin(iRoot), siblingNum = 0; iChildren != t.end(iRoot); ++iChildren, ++siblingNum) { - // recursively print child - print_subtree_bracketed(t,iChildren,str); - // comma after every child except the last one - if (siblingNum != siblingCount ) { - str << ", "; - } - } - str << ")"; - } - } - +template <class T> +void print_subtree_bracketed(const tree<T> &t, typename tree<T>::iterator iRoot, + std::ostream &str) { + if (t.empty()) + return; + if (t.number_of_children(iRoot) == 0) { + str << *iRoot; + } else { + // parent + str << *iRoot; + str << "("; + // child1, ..., childn + int siblingCount = t.number_of_siblings(t.begin(iRoot)); + int siblingNum; + typename tree<T>::sibling_iterator iChildren; + for (iChildren = t.begin(iRoot), siblingNum = 0; iChildren != t.end(iRoot); + ++iChildren, ++siblingNum) { + // recursively print child + print_subtree_bracketed(t, iChildren, str); + // comma after every child except the last one + if (siblingNum != siblingCount) { + str << ", "; + } + } + str << ")"; + } } +} // namespace kptree + #endif diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 802df587d..1322c1d1d 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -394,10 +394,12 @@ def initialize(self): self.InitializeCpp() -class LastVertexInteractionSplittingActor(ActorBase,g4.GateLastVertexInteractionSplittingActor): +class LastVertexInteractionSplittingActor( + ActorBase, g4.GateLastVertexInteractionSplittingActor +): """This splitting actor proposes an interaction splitting at the last particle vertex before the exit - of the biased volume. This actor can be usefull for application where collimation are important, - such as in medical LINAC (Linear Accelerator) simulations or radiation shielding. + of the biased volume. This actor can be usefull for application where collimation are important, + such as in medical LINAC (Linear Accelerator) simulations or radiation shielding. """ user_info_defaults = { @@ -407,22 +409,18 @@ class LastVertexInteractionSplittingActor(ActorBase,g4.GateLastVertexInteraction "doc": "Defines the number of particles exiting at each split process. Unlike other split actors, this splitting factor counts particles that actually exit, not just those generated.", }, ), - "angular_kill": ( False, { "doc": "If enabled, particles with momentum outside a specified angular range are killed.", }, ), - "max_theta": ( 90 * g4_units.deg, { "doc": "Defines the maximum angle (in degrees) from a central axis within which particles are retained. Particles with momentum beyond this angle are removed. The angular range spans from 0 to max_theta, measured from the vector_director", }, ), - - "vector_director": ( [0, 0, 1], { @@ -441,11 +439,8 @@ class LastVertexInteractionSplittingActor(ActorBase,g4.GateLastVertexInteraction "doc": "Defines a batch of number of processes to regenerate, calculated as batch_size * splitting_factor. The optimal value depends on the collimation setup; for example, a batch_size of 10 works well for LINAC head configurations.", }, ), - - } - def __init__(self, *args, **kwargs): ActorBase.__init__(self, *args, **kwargs) self.__initcpp__() @@ -453,14 +448,17 @@ def __init__(self, *args, **kwargs): def __initcpp__(self): g4.GateLastVertexInteractionSplittingActor.__init__(self, {"name": self.name}) - self.AddActions({"BeginOfRunAction", - "BeginOfEventAction", - "PreUserTrackingAction", - "SteppingAction", - "PostUserTrackingAction", - "EndOfEventAction"}) - - + self.AddActions( + { + "BeginOfRunAction", + "BeginOfEventAction", + "PreUserTrackingAction", + "SteppingAction", + "PostUserTrackingAction", + "EndOfEventAction", + } + ) + def initialize(self): ActorBase.initialize(self) self.InitializeUserInput(self.user_info) @@ -476,6 +474,7 @@ def initialize(self): self.list_of_volume_name.append(volume_name) self.fListOfVolumeAncestor = self.list_of_volume_name + class BremSplittingActor(SplittingActorBase, g4.GateBOptrBremSplittingActor): # hints for IDE processes: list diff --git a/opengate/managers.py b/opengate/managers.py index cd79c99ec..4d4f0422c 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -119,7 +119,7 @@ "DigitizerEnergyWindowsActor": DigitizerEnergyWindowsActor, "DigitizerHitsCollectionActor": DigitizerHitsCollectionActor, "PhaseSpaceActor": PhaseSpaceActor, - "LastVertexInteractionSplittingActor":LastVertexInteractionSplittingActor, + "LastVertexInteractionSplittingActor": LastVertexInteractionSplittingActor, } @@ -1194,19 +1194,18 @@ def dump_volume_types(self): for vt in self.volume_types: s += f"{vt} " return s - + def get_volume_tree(self): return self.volume_tree_root - - + def print_volume_types(self): print(self.dump_volume_types()) def dump_material_database_names(self): return list(self.material_database.filenames) - + def get_volume_tree(self): - return (self.volume_tree_root) + return self.volume_tree_root def get_volume_tree(self): return self.volume_tree_root diff --git a/opengate/tests/src/test077_kill_interacting_particles.py b/opengate/tests/src/test077_kill_interacting_particles.py index b36776d8f..fc342cdf5 100644 --- a/opengate/tests/src/test077_kill_interacting_particles.py +++ b/opengate/tests/src/test077_kill_interacting_particles.py @@ -11,8 +11,6 @@ def test077_test(entry_data, exit_data_1, exit_data_2): - - liste_ekin = [] liste_evtID = [] liste_trackID = [] @@ -29,7 +27,6 @@ def test077_test(entry_data, exit_data_1, exit_data_2): Ekin_entry = entry_data["KineticEnergy"][i] Ekin_exit = exit_data_1["KineticEnergy"][j] - if (TID_entry != TID_exit) or (Ekin_exit != Ekin_entry): liste_ekin.append(exit_data_1["KineticEnergy"][j]) liste_evtID.append(exit_data_1["EventID"][j]) diff --git a/opengate/tests/src/test084_last_vertex_splittting.py b/opengate/tests/src/test084_last_vertex_splittting.py index 956520b59..390c5d1ae 100755 --- a/opengate/tests/src/test084_last_vertex_splittting.py +++ b/opengate/tests/src/test084_last_vertex_splittting.py @@ -8,63 +8,81 @@ from opengate.tests import utility - def validation_test(arr_ref, arr_data, nb_split): arr_ref = arr_ref[arr_ref["ParticleName"] == "gamma"] arr_data = arr_data[arr_data["ParticleName"] == "gamma"] - weight_data = np.round(np.mean(arr_data["Weight"]),4) + weight_data = np.round(np.mean(arr_data["Weight"]), 4) bool_weight = False - print("Weight mean is equal to",weight_data, "and need to be equal to",1/nb_split) - if weight_data == 1/nb_split: + print( + "Weight mean is equal to", weight_data, "and need to be equal to", 1 / nb_split + ) + if weight_data == 1 / nb_split: bool_weight = True - bool_events = False - sigma = np.sqrt((len(arr_data["KineticEnergy"])/nb_split))*nb_split + sigma = np.sqrt((len(arr_data["KineticEnergy"]) / nb_split)) * nb_split nb_events_ref = len(arr_ref["KineticEnergy"]) nb_events_data = len(arr_data["KineticEnergy"]) - print("Reference counts number:",nb_events_ref) + print("Reference counts number:", nb_events_ref) print("Biased counts number:", nb_events_data) - if nb_events_data - 4*sigma <= nb_events_ref <= nb_events_data + 4*sigma: - bool_events =True - - keys = ["KineticEnergy", "PreDirection_X","PreDirection_Y","PreDirection_Z","PrePosition_X","PrePosition_Y"] + if nb_events_data - 4 * sigma <= nb_events_ref <= nb_events_data + 4 * sigma: + bool_events = True + + keys = [ + "KineticEnergy", + "PreDirection_X", + "PreDirection_Y", + "PreDirection_Z", + "PrePosition_X", + "PrePosition_Y", + ] bool_distrib = True - for key in keys : + for key in keys: ref = arr_ref[key] data = arr_data[key] mean_ref = np.mean(ref) mean_data = np.mean(data) - std_dev_ref = np.std(ref,ddof =1) - std_dev_data = np.std(data,ddof =1) - - std_err_ref = std_dev_ref/np.sqrt(len(ref)) - std_err_data= std_dev_data/(nb_split * np.sqrt(len(data)/nb_split)) + std_dev_ref = np.std(ref, ddof=1) + std_dev_data = np.std(data, ddof=1) - print(key,"mean ref value:", np.round(mean_ref,3),"+-",np.round(std_err_ref,3)) - print(key,"mean data value:", np.round(mean_data,3),"+-",np.round(std_err_data,3)) + std_err_ref = std_dev_ref / np.sqrt(len(ref)) + std_err_data = std_dev_data / (nb_split * np.sqrt(len(data) / nb_split)) + print( + key, + "mean ref value:", + np.round(mean_ref, 3), + "+-", + np.round(std_err_ref, 3), + ) + print( + key, + "mean data value:", + np.round(mean_data, 3), + "+-", + np.round(std_err_data, 3), + ) - if (mean_data - 4 * np.sqrt(std_err_data**2 + std_err_ref**2) > mean_ref) or (mean_data + 4 * np.sqrt(std_err_data**2 + std_err_ref**2) <mean_ref): + if ( + mean_data - 4 * np.sqrt(std_err_data**2 + std_err_ref**2) > mean_ref + ) or (mean_data + 4 * np.sqrt(std_err_data**2 + std_err_ref**2) < mean_ref): bool_distrib = False if bool_distrib and bool_events and bool_weight: return True - else : + else: return False - - if __name__ == "__main__": for i in range(2): - if i == 0 : + if i == 0: bias = False - else : + else: bias = True paths = utility.get_default_test_paths( __file__, "test084_last_vertex_splitting", output_folder="test084" @@ -113,7 +131,7 @@ def validation_test(arr_ref, arr_data, nb_split): W_tubs.mother = world.name W_tubs.rmin = 0 - W_tubs.rmax = 0.4*cm + W_tubs.rmax = 0.4 * cm W_tubs.dz = 0.05 * m W_tubs.color = [0.8, 0.2, 0.1, 1] angle_x = 45 @@ -125,31 +143,30 @@ def validation_test(arr_ref, arr_data, nb_split): ).as_matrix() W_tubs.rotation = rotation - if bias : - ###### Last vertex Splitting ACTOR ######### + if bias: + ###### Last vertex Splitting ACTOR ######### nb_split = 10 - vertex_splitting_actor = sim.add_actor("LastVertexInteractionSplittingActor", "vertexSplittingW") + vertex_splitting_actor = sim.add_actor( + "LastVertexInteractionSplittingActor", "vertexSplittingW" + ) vertex_splitting_actor.attached_to = W_tubs.name vertex_splitting_actor.splitting_factor = nb_split vertex_splitting_actor.angular_kill = True - vertex_splitting_actor.vector_director = [0,0,-1] - vertex_splitting_actor.max_theta = 90*deg + vertex_splitting_actor.vector_director = [0, 0, -1] + vertex_splitting_actor.max_theta = 90 * deg vertex_splitting_actor.batch_size = 10 - plan = sim.add_volume("Box", "plan_phsp") plan.material = "G4_Galactic" - plan.size = [5*cm,5*cm,1*nm] - plan.translation = [0,0,-1*cm] - + plan.size = [5 * cm, 5 * cm, 1 * nm] + plan.translation = [0, 0, -1 * cm] ####### gamma source ########### source = sim.add_source("GenericSource", "source1") source.particle = "gamma" source.n = 100000 - if bias : - source.n = source.n/nb_split - + if bias: + source.n = source.n / nb_split source.position.type = "sphere" source.position.radius = 1 * nm @@ -157,15 +174,15 @@ def validation_test(arr_ref, arr_data, nb_split): # source.direction.momentum = [0,0,-1] source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) source.energy.type = "mono" - source.energy.mono = 4 * MeV + source.energy.mono = 4 * MeV # ###### LastVertexSource ############# - if bias : + if bias: source_0 = sim.add_source("LastVertexSource", "source_vertex") source_0.n = 1 ####### PHASE SPACE ACTOR ############## - sim.output_dir =paths.output + sim.output_dir = paths.output phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") phsp_actor.attached_to = plan.name phsp_actor.attributes = [ @@ -178,12 +195,11 @@ def validation_test(arr_ref, arr_data, nb_split): "PrePosition", "TrackCreatorProcess", ] - if bias : - phsp_actor.output_filename ="test084_output_data_last_vertex_biased.root" - else : + if bias: + phsp_actor.output_filename = "test084_output_data_last_vertex_biased.root" + else: phsp_actor.output_filename = "test084_output_data_last_vertex_ref.root" - s = sim.add_actor("SimulationStatisticsActor", "Stats") s.track_types_flag = True sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" @@ -197,9 +213,6 @@ def validation_test(arr_ref, arr_data, nb_split): output = sim.run(True) print(s) - - - f_data = uproot.open(paths.output / "test084_output_data_last_vertex_biased.root") f_ref_data = uproot.open(paths.output / "test084_output_data_last_vertex_ref.root") arr_data = f_data["PhaseSpace"].arrays() @@ -207,4 +220,3 @@ def validation_test(arr_ref, arr_data, nb_split): # # is_ok = validation_test(arr_ref_data, arr_data, nb_split) utility.test_ok(is_ok) - diff --git a/opengate/tests/src/test084_last_vertex_splittting_angular_kill.py b/opengate/tests/src/test084_last_vertex_splittting_angular_kill.py index c9f058618..d1fa56cf2 100755 --- a/opengate/tests/src/test084_last_vertex_splittting_angular_kill.py +++ b/opengate/tests/src/test084_last_vertex_splittting_angular_kill.py @@ -8,28 +8,29 @@ from opengate.tests import utility - -def validation_test(arr_data,vector_director,max_theta): +def validation_test(arr_data, vector_director, max_theta): arr_data = arr_data[arr_data["ParticleName"] == "gamma"] - qt_mvt_data = arr_data[["PreDirection_X","PreDirection_Y","PreDirection_Z"]] - mom_data = np.zeros((len(qt_mvt_data["PreDirection_X"]),3)) - mom_data[:,0] += np.array(qt_mvt_data["PreDirection_X"]) + qt_mvt_data = arr_data[["PreDirection_X", "PreDirection_Y", "PreDirection_Z"]] + mom_data = np.zeros((len(qt_mvt_data["PreDirection_X"]), 3)) + mom_data[:, 0] += np.array(qt_mvt_data["PreDirection_X"]) mom_data[:, 1] += np.array(qt_mvt_data["PreDirection_Y"]) mom_data[:, 2] += np.array(qt_mvt_data["PreDirection_Z"]) - l_theta = np.zeros(len(mom_data[:,0])) + l_theta = np.zeros(len(mom_data[:, 0])) for i in range(len(l_theta)): theta = np.arccos(mom_data[i].dot(vector_director)) l_theta[i] = theta - print('Number of particles with an angle higher than max_theta:',len(l_theta[l_theta > max_theta])) + print( + "Number of particles with an angle higher than max_theta:", + len(l_theta[l_theta > max_theta]), + ) if len(l_theta[l_theta > max_theta]) == 0: return True else: return False - if __name__ == "__main__": bias = True paths = utility.get_default_test_paths( @@ -79,7 +80,7 @@ def validation_test(arr_data,vector_director,max_theta): W_tubs.mother = world.name W_tubs.rmin = 0 - W_tubs.rmax = 0.4*cm + W_tubs.rmax = 0.4 * cm W_tubs.dz = 0.05 * m W_tubs.color = [0.8, 0.2, 0.1, 1] angle_x = 45 @@ -91,36 +92,33 @@ def validation_test(arr_data,vector_director,max_theta): ).as_matrix() W_tubs.rotation = rotation - if bias : - ###### Last vertex Splitting ACTOR ######### + if bias: + ###### Last vertex Splitting ACTOR ######### nb_split = 10 - vertex_splitting_actor = sim.add_actor("LastVertexInteractionSplittingActor", "vertexSplittingW") + vertex_splitting_actor = sim.add_actor( + "LastVertexInteractionSplittingActor", "vertexSplittingW" + ) vertex_splitting_actor.attached_to = W_tubs.name vertex_splitting_actor.splitting_factor = nb_split vertex_splitting_actor.angular_kill = True - vertex_splitting_actor.vector_director = [0,0,-1] - vertex_splitting_actor.max_theta = 15*deg + vertex_splitting_actor.vector_director = [0, 0, -1] + vertex_splitting_actor.max_theta = 15 * deg vertex_splitting_actor.batch_size = 10 - plan = sim.add_volume("Box", "plan_phsp") plan.material = "G4_Galactic" - plan.size = [5*cm,5*cm,1*nm] - plan.translation = [0,0,-1*cm] + plan.size = [5 * cm, 5 * cm, 1 * nm] + plan.translation = [0, 0, -1 * cm] - if bias : + if bias: vector_director = np.array(vertex_splitting_actor.vector_director) - - - ####### gamma source ########### source = sim.add_source("GenericSource", "source1") source.particle = "gamma" source.n = 100000 - if bias : - source.n = source.n/nb_split - + if bias: + source.n = source.n / nb_split source.position.type = "sphere" source.position.radius = 1 * nm @@ -128,15 +126,15 @@ def validation_test(arr_data,vector_director,max_theta): # source.direction.momentum = [0,0,-1] source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) source.energy.type = "mono" - source.energy.mono = 4 * MeV + source.energy.mono = 4 * MeV ###### LastVertexSource ############# - if bias : + if bias: source_0 = sim.add_source("LastVertexSource", "source_vertex") source_0.n = 1 ####### PHASE SPACE ACTOR ############## - sim.output_dir =paths.output + sim.output_dir = paths.output phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") phsp_actor.attached_to = plan.name phsp_actor.attributes = [ @@ -149,8 +147,8 @@ def validation_test(arr_data,vector_director,max_theta): "PrePosition", "TrackCreatorProcess", ] - if bias : - phsp_actor.output_filename ="test084_output_data_last_vertex_angular_kill.root" + if bias: + phsp_actor.output_filename = "test084_output_data_last_vertex_angular_kill.root" s = sim.add_actor("SimulationStatisticsActor", "Stats") s.track_types_flag = True @@ -165,8 +163,9 @@ def validation_test(arr_data,vector_director,max_theta): output = sim.run() print(s) - f_data = uproot.open(paths.output / "test084_output_data_last_vertex_angular_kill.root") + f_data = uproot.open( + paths.output / "test084_output_data_last_vertex_angular_kill.root" + ) arr_data = f_data["PhaseSpace"].arrays() - is_ok = validation_test(arr_data, vector_director,vertex_splitting_actor.max_theta) + is_ok = validation_test(arr_data, vector_director, vertex_splitting_actor.max_theta) utility.test_ok(is_ok) - From 79184da5ab374f96b0e649a7f931b00072ca91b5 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Wed, 13 Nov 2024 14:53:51 +0100 Subject: [PATCH 51/82] Merge with last_vertex_spliting_actor --- .github/workflows/delocateWindows.py | 2 +- .github/workflows/main.yml | 1 + .readthedocs.yaml | 27 +- MANIFEST.in | 1 + autodoctest.py | 3 - core/opengate_core/opengate_core.cpp | 12 +- .../opengate_lib/GateGenericSource.cpp | 135 +- ...ateLastVertexInteractionSplittingActor.cpp | 707 ++++ .../GateLastVertexInteractionSplittingActor.h | 137 + .../opengate_lib/GateLastVertexSource.cpp | 112 + .../opengate_lib/GateLastVertexSource.h | 85 + .../GateLastVertexSplittingDataContainer.h | 165 + .../GateLastVertexSplittingPostStepDoIt.h | 116 + .../GateLastVertexSplittingSimpleContainer.h | 217 ++ .../opengate_lib/GateOptnForceFreeFlight.cpp | 129 + .../opengate_lib/GateOptnForceFreeFlight.h | 122 + .../GateOptnPairProdSplitting.cpp | 97 + .../opengate_lib/GateOptnPairProdSplitting.h | 52 + .../GateOptnScatteredGammaSplitting.cpp | 165 + .../GateOptnScatteredGammaSplitting.h | 53 + .../GateOptnVGenericSplitting.cpp | 105 + .../opengate_lib/GateOptnVGenericSplitting.h | 122 + .../opengate_lib/GateOptneBremSplitting.cpp | 112 + .../opengate_lib/GateOptneBremSplitting.h | 54 + ...GateOptrComptPseudoTransportationActor.cpp | 319 ++ .../GateOptrComptPseudoTransportationActor.h | 137 + .../opengate_lib/GateSPSEneDistribution.cpp | 53 +- .../opengate_lib/GateSPSEneDistribution.h | 9 +- .../opengate_lib/GateSourceManager.cpp | 4 + .../opengate_lib/GateSourceManager.h | 16 + .../opengate_core/opengate_lib/GateVActor.cpp | 4 +- ...ateLastVertexInteractionSplittingActor.cpp | 21 + .../opengate_lib/pyGateLastVertexSource.cpp | 22 + ...GateOptrComptPseudoTransportationActor.cpp | 20 + core/opengate_core/opengate_lib/tree.hh | 3412 +++++++++++++++++ core/opengate_core/opengate_lib/tree_util.hh | 92 + docs/requirements.txt | 4 + docs/run_rtd.sh | 3 +- docs/source/conf.py | 4 +- .../developer_guide_installation.rst | 32 +- docs/source/developer_guide/index.rst | 9 +- .../figures/example_lut_davis_model.png | Bin 0 -> 246838 bytes docs/source/figures/flowchart_lut_model.png | Bin 0 -> 101931 bytes .../generic_source_spectrum_discrete.png | Bin 0 -> 60728 bytes .../generic_source_spectrum_histogram.png | Bin 0 -> 53293 bytes docs/source/figures/kill_actor.png | Bin 0 -> 125552 bytes docs/source/figures/optigan_working.png | Bin 0 -> 423722 bytes .../reflection_types_and_microfacets.png | Bin 0 -> 39864 bytes docs/source/figures/surface-definition.png | Bin 0 -> 6184 bytes docs/source/user_guide/user_guide_actors.rst | 2 + .../user_guide_how_to_convert_example_1.rst | 331 ++ .../user_guide/user_guide_installation.rst | 2 +- docs/source/user_guide/user_guide_physics.rst | 58 + .../user_guide_reference_actors.rst | 44 +- .../user_guide_reference_simulation.rst | 2 +- docs/source/user_guide/user_guide_sources.rst | 164 +- docs/source/user_guide/user_guide_volumes.rst | 41 + opengate/actors/actoroutput.py | 126 +- opengate/actors/base.py | 10 + opengate/actors/dataitems.py | 90 +- opengate/actors/digitizers.py | 2 +- opengate/actors/doseactors.py | 41 +- opengate/actors/filters.py | 2 +- opengate/actors/miscactors.py | 85 + opengate/base.py | 23 +- opengate/bin/opengate_tests.py | 4 +- opengate/contrib/linacs/dicomrtplan.py | 413 +- .../contrib/linacs/elekta_versa_materials.db | 30 +- opengate/contrib/linacs/elektaversa.py | 1717 ++++++--- .../contrib/tps/treatmentPlanPhsSource.py | 52 +- opengate/data/rad_beta_spectrum.json | 425 ++ opengate/data/rad_gamma_spectrum.json | 84 + opengate/data/readme_rad_spectrum.md | 11 + opengate/engines.py | 30 +- opengate/geometry/materials.py | 19 +- opengate/geometry/utility.py | 6 +- opengate/geometry/volumes.py | 27 +- opengate/image.py | 54 +- opengate/managers.py | 61 +- opengate/sources/builders.py | 3 +- opengate/sources/generic.py | 151 +- opengate/sources/phidsources.py | 10 +- opengate/sources/phspsources.py | 9 +- opengate/tests/data | 2 +- opengate/tests/src/test008_dose_actor.py | 5 + opengate/tests/src/test009_voxels_dynamic.py | 1 + ...generic_source_energy_spectrum_discrete.py | 148 + ...eneric_source_energy_spectrum_histogram.py | 150 + .../tests/src/test019_linac_elekta_versa.py | 4 +- ...19_linac_elekta_versa_rt_plan_isocenter.py | 3 +- .../test019_linac_elekta_versa_with_mlc.py | 22 +- ...linac_elekta_versa_with_mlc_phsp_source.py | 11 +- ...019_linac_elekta_versa_with_mlc_rt_plan.py | 96 +- opengate/tests/src/test043_garf_helpers.py | 22 +- opengate/tests/src/test046_rad.py | 4 +- opengate/tests/src/test053_phid_helpers1.py | 8 +- opengate/tests/src/test053_production_cuts.py | 4 +- .../src/test066_spect_gaga_garf_helpers.py | 245 ++ ...op_simulation_criteria_multiple_runs_mt.py | 35 +- .../tests/src/test072_pseudo_transport_RR.py | 231 ++ .../src/test072_pseudo_transportation.py | 194 + .../src/test073_test3_intevo_tc99m_mt.py | 2 +- .../src/test073_test4_intevo_lu177_mt.py | 2 +- .../src/test073_test5_discovery_lu177_mt.py | 2 +- .../src/test073_test5_discovery_tc99m_mt.py | 2 +- .../test074_kill_non_interacting_particles.py | 212 + ...75_geometrical_splitting_volume_surface.py | 185 + .../src/test077_kill_interacting_particles.py | 216 ++ .../src/test083_kill_according_processes.py | 73 +- .../src/test084_last_vertex_splittting.py | 210 + ...084_last_vertex_splittting_angular_kill.py | 172 + opengate/tests/utility.py | 48 +- setup.py | 1 + 113 files changed, 12009 insertions(+), 1322 deletions(-) delete mode 100644 autodoctest.py create mode 100644 core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp create mode 100644 core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h create mode 100644 core/opengate_core/opengate_lib/GateLastVertexSource.cpp create mode 100644 core/opengate_core/opengate_lib/GateLastVertexSource.h create mode 100644 core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h create mode 100644 core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h create mode 100644 core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h create mode 100644 core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp create mode 100644 core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h create mode 100644 core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp create mode 100644 core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h create mode 100644 core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp create mode 100644 core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h create mode 100644 core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp create mode 100644 core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h create mode 100644 core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp create mode 100644 core/opengate_core/opengate_lib/GateOptneBremSplitting.h create mode 100644 core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp create mode 100644 core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h create mode 100644 core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp create mode 100644 core/opengate_core/opengate_lib/pyGateLastVertexSource.cpp create mode 100644 core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp create mode 100644 core/opengate_core/opengate_lib/tree.hh create mode 100644 core/opengate_core/opengate_lib/tree_util.hh create mode 100644 docs/source/figures/example_lut_davis_model.png create mode 100644 docs/source/figures/flowchart_lut_model.png create mode 100644 docs/source/figures/generic_source_spectrum_discrete.png create mode 100644 docs/source/figures/generic_source_spectrum_histogram.png create mode 100644 docs/source/figures/kill_actor.png create mode 100644 docs/source/figures/optigan_working.png create mode 100644 docs/source/figures/reflection_types_and_microfacets.png create mode 100644 docs/source/figures/surface-definition.png create mode 100644 docs/source/user_guide/user_guide_how_to_convert_example_1.rst create mode 100644 opengate/data/rad_beta_spectrum.json create mode 100644 opengate/data/rad_gamma_spectrum.json create mode 100644 opengate/data/readme_rad_spectrum.md create mode 100755 opengate/tests/src/test010_generic_source_energy_spectrum_discrete.py create mode 100755 opengate/tests/src/test010_generic_source_energy_spectrum_histogram.py create mode 100644 opengate/tests/src/test066_spect_gaga_garf_helpers.py create mode 100644 opengate/tests/src/test072_pseudo_transport_RR.py create mode 100644 opengate/tests/src/test072_pseudo_transportation.py create mode 100644 opengate/tests/src/test074_kill_non_interacting_particles.py create mode 100644 opengate/tests/src/test075_geometrical_splitting_volume_surface.py create mode 100644 opengate/tests/src/test077_kill_interacting_particles.py create mode 100755 opengate/tests/src/test084_last_vertex_splittting.py create mode 100755 opengate/tests/src/test084_last_vertex_splittting_angular_kill.py diff --git a/.github/workflows/delocateWindows.py b/.github/workflows/delocateWindows.py index 9ebc7b572..3991c1089 100644 --- a/.github/workflows/delocateWindows.py +++ b/.github/workflows/delocateWindows.py @@ -48,7 +48,7 @@ def hash_filename(filepath, blocksize=65536): def find_dll_dependencies(dll_filepath, lib_dir): print(dll_filepath) - if dll_filepath in global_dll_deps.keys(): + if dll_filepath in global_dll_deps: return global_dll_deps[dll_filepath] else: dll_deps = {} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dbcf6ff56..80cd2a02c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -544,6 +544,7 @@ jobs: pip install pandas numpy python compile_opengate_tests_results.py - name: Pushes to another repository + continue-on-error: true uses: cpina/github-action-push-to-another-repository@main env: API_TOKEN_GITHUB: ${{ secrets.PUSH_OPENGATE_TESTS_RESULTS }} diff --git a/.readthedocs.yaml b/.readthedocs.yaml index f245c32f1..bd16e0d42 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -6,30 +6,9 @@ build: os: "ubuntu-24.04" tools: python: "3.11" -# jobs: -# pre_build: -## - cat /home/docs/.asdf/installs/python/3.11.9/lib/python3.11/pdb.py -# - cp /home/docs/.asdf/installs/python/3.11.9/lib/python3.11/pdb.py /home/docs/.asdf/installs/python/3.11.9/lib/python3.11/pdb.py.bak -# - | -# { head -n 1 /home/docs/.asdf/installs/python/3.11.9/lib/python3.11/pdb.py.bak; -# echo "import sys"; -# echo "print(\"DEBUG: sys.path = \", sys.path)"; -# echo "import cmd"; -# echo "print(\"DEBUG: cmd.__file__=\", cmd.__file__)"; -# tail -n +2 /home/docs/.asdf/installs/python/3.11.9/lib/python3.11/pdb.py.bak; } -# > /home/docs/.asdf/installs/python/3.11.9/lib/python3.11/pdb.py -# - cp /home/docs/.asdf/installs/python/3.11.9/lib/python3.11/doctest.py /home/docs/.asdf/installs/python/3.11.9/lib/python3.11/doctest.py.bak -# - | -# { echo "import pdb"; -# echo "print(\"DEBUG: pdb.__file__=\", pdb.__file__)"; -# cat /home/docs/.asdf/installs/python/3.11.9/lib/python3.11/doctest.py.bak; } -# > /home/docs/.asdf/installs/python/3.11.9/lib/python3.11/doctest.py -# - cp /home/docs/checkouts/readthedocs.org/user_builds/opengate-python/envs/fix_autodoc/lib/python3.11/site-packages/sphinx/ext/doctest.py /home/docs/checkouts/readthedocs.org/user_builds/opengate-python/envs/fix_autodoc/lib/python3.11/site-packages/sphinx/ext/doctest.py.bak -# - | -# { head -n 188 /home/docs/checkouts/readthedocs.org/user_builds/opengate-python/envs/fix_autodoc/lib/python3.11/site-packages/sphinx/ext/doctest.py.bak; -# echo "print(\"DEBUG: doctest.__file__=\", doctest.__file__)"; -# tail -n +189 /home/docs/checkouts/readthedocs.org/user_builds/opengate-python/envs/fix_autodoc/lib/python3.11/site-packages/sphinx/ext/doctest.py.bak; } -# > /home/docs/checkouts/readthedocs.org/user_builds/opengate-python/envs/fix_autodoc/lib/python3.11/site-packages/sphinx/ext/doctest.py + jobs: + pre_build: + - cd docs/source && sphinx-build -T -b html -d _build/doctrees -D language=en . $READTHEDOCS_OUTPUT/html # Build from the docs/ directory with Sphinx sphinx: diff --git a/MANIFEST.in b/MANIFEST.in index 967802d4b..7da24ba5b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,5 +6,6 @@ recursive-include opengate/tests/src/gate/gate_test* * recursive-include opengate/tests/output_ref * recursive-include opengate/data *.txt recursive-include opengate/data *.xml +recursive-include opengate/data *.json include opengate/tests/HEAD include VERSION diff --git a/autodoctest.py b/autodoctest.py deleted file mode 100644 index 36d5f81c5..000000000 --- a/autodoctest.py +++ /dev/null @@ -1,3 +0,0 @@ -def test_func_for_autodoc(a): - """This is a test docstring.""" - print(a) diff --git a/core/opengate_core/opengate_core.cpp b/core/opengate_core/opengate_core.cpp index 6d12106fb..9d2f769d3 100644 --- a/core/opengate_core/opengate_core.cpp +++ b/core/opengate_core/opengate_core.cpp @@ -345,10 +345,12 @@ void init_GateSimulationStatisticsActor(py::module &); void init_GatePhaseSpaceActor(py::module &); -// void init_GateComptonSplittingActor(py::module &); - void init_GateOptrComptSplittingActor(py::module &m); +void init_GateLastVertexInteractionSplittingActor(py::module &m); + +void init_GateOptrComptPseudoTransportationActor(py::module &m); + void init_GateBOptrBremSplittingActor(py::module &m); void init_G4VBiasingOperator(py::module &m); @@ -379,6 +381,8 @@ void init_GateVDigiAttribute(py::module &m); void init_GateVSource(py::module &); +void init_GateLastVertexSource(py::module &); + void init_GateExceptionHandler(py::module &); void init_GateNTuple(py::module &); @@ -552,6 +556,7 @@ PYBIND11_MODULE(opengate_core, m) { init_GateImageNestedParameterisation(m); init_GateRepeatParameterisation(m); init_GateVSource(m); + init_GateLastVertexSource(m); init_GateSourceManager(m); init_GateGenericSource(m); init_GateTreatmentPlanPBSource(m); @@ -572,9 +577,10 @@ PYBIND11_MODULE(opengate_core, m) { init_GateLETActor(m); init_GateSimulationStatisticsActor(m); init_GatePhaseSpaceActor(m); - // init_GateComptonSplittingActor(m); init_GateBOptrBremSplittingActor(m); + init_GateOptrComptPseudoTransportationActor(m); init_GateOptrComptSplittingActor(m); + init_GateLastVertexInteractionSplittingActor(m); init_GateHitsCollectionActor(m); init_GateMotionVolumeActor(m); init_GateVDigitizerWithOutputActor(m); diff --git a/core/opengate_core/opengate_lib/GateGenericSource.cpp b/core/opengate_core/opengate_lib/GateGenericSource.cpp index 88748a25d..74d7e743a 100644 --- a/core/opengate_core/opengate_lib/GateGenericSource.cpp +++ b/core/opengate_core/opengate_lib/GateGenericSource.cpp @@ -9,8 +9,16 @@ #include "G4IonTable.hh" #include "G4ParticleTable.hh" #include "G4RandomTools.hh" +#include "GateHelpers.h" #include "GateHelpersDict.h" +#include "fmt/color.h" +#include "fmt/core.h" #include <G4UnitsTable.hh> +#include <algorithm> +#include <iterator> +#include <locale> +#include <numeric> +#include <sstream> GateGenericSource::GateGenericSource() : GateVSource() { fInitGenericIon = false; @@ -143,13 +151,14 @@ double GateGenericSource::PrepareNextTime(double current_simulation_time) { if (fMaxN <= 0) { if (fEffectiveEventTime < fStartTime) return fStartTime; - if (fEffectiveEventTime >= fEndTime) + if (fEffectiveEventTime >= fEndTime){ return -1; - + } // get next time according to current fActivity double next_time = CalcNextTime(fEffectiveEventTime); - if (next_time >= fEndTime) + if (next_time >= fEndTime){ return -1; + } return next_time; } @@ -510,41 +519,105 @@ void GateGenericSource::InitializeEnergy(py::dict puser_info) { fInitialActivity = fActivity; } - if (ene_type == "spectrum_lines") { - ene->SetEnergyDisType("spectrum_lines"); - auto w = DictGetVecDouble(user_info, "spectrum_weight"); - auto e = DictGetVecDouble(user_info, "spectrum_energy"); - auto total = 0.0; - for (double ww : w) - total += ww; - // normalize to total - for (unsigned long i = 0; i < w.size(); i++) { - w[i] = w[i] / total; + if (ene_type == "spectrum_discrete") { // TODO rename + auto weights = DictGetVecDouble(user_info, "spectrum_weights"); + auto energies = DictGetVecDouble(user_info, "spectrum_energies"); + + if (weights.empty()) + Fatal("The weights for " + fName + " is zero length. Abort"); + if (energies.empty()) + Fatal("The energies for " + fName + " is zero length. Abort"); + if (weights.size() != energies.size()) { + auto const errorMessage = + fmt::format("For {}, the spectrum vectors weights and energies" + " must have the same size ({} โ {})", + fName, weights.size(), energies.size()); + Fatal(errorMessage); } + // cumulated weights - for (unsigned long i = 1; i < w.size(); i++) { - w[i] += w[i - 1]; - } - ene->fEnergyCDF = e; - ene->fProbabilityCDF = w; - if (ene->fEnergyCDF.empty() || ene->fProbabilityCDF.empty()) { - std::ostringstream oss; - oss << "The spectrum lines for source " << fName - << " is zero length. Abort"; - Fatal(oss.str()); - } - if (ene->fEnergyCDF.size() != ene->fProbabilityCDF.size()) { - std::ostringstream oss; - oss << "The spectrum vector energy and proba for source " << fName - << " must have the same length, while there are " - << ene->fEnergyCDF.size() << " and " << ene->fProbabilityCDF.size(); - Fatal(oss.str()); + std::partial_sum(std::begin(weights), std::end(weights), + std::begin(weights)); + auto const weightsSum = weights.back(); + + // normalize weights to total + for (auto &weight : weights) + weight /= weightsSum; + + // ! important ! + // Modify the activity according to the total sum of weights because we + // normalize the weights + fActivity *= weightsSum; + fInitialActivity = fActivity; + + ene->SetEnergyDisType(ene_type); + ene->SetEmin(energies.front()); + ene->SetEmax(energies.back()); + ene->fEnergyCDF = energies; + ene->fProbabilityCDF = weights; + } + + if (ene_type == "spectrum_histogram") { + auto weights = DictGetVecDouble(user_info, "spectrum_weights"); + auto energy_bin_edges = + DictGetVecDouble(user_info, "spectrum_energy_bin_edges"); + auto interpolation = + DictGetStr(user_info, "spectrum_histogram_interpolation"); + + if (weights.empty()) + Fatal("The weights for " + fName + " is zero length. Abort"); + if (energy_bin_edges.empty()) + Fatal("The energy_bin_edges for " + fName + " is zero length. Abort"); + if ((weights.size() + 1) != energy_bin_edges.size()) { + auto const errorMessage = fmt::format( + "For {}, the spectrum vector energy_bin_edges must have exactly one" + " more element than the vector weights ({} โ {} + 1)", + fName, energy_bin_edges.size(), weights.size()); + Fatal(errorMessage); } + + if (interpolation == "None" || interpolation == "none") { + double accumulatedWeights = 0; + for (std::size_t i = 0; i < weights.size(); ++i) { + auto const diffEnergy = energy_bin_edges[i + 1] - energy_bin_edges[i]; + accumulatedWeights += weights[i] * diffEnergy; + weights[i] = accumulatedWeights; + } + } else if (interpolation == "linear") { + double accumulatedWeights = 0; + for (std::size_t i = 0; i < weights.size(); i++) { + auto const diffEnergy = energy_bin_edges[i + 1] - energy_bin_edges[i]; + auto const diffWeight = weights[i + 1] - weights[i]; + accumulatedWeights += + diffEnergy * weights[i] - 0.5 * diffEnergy * diffWeight; + weights[i] = accumulatedWeights; + } + } else + Fatal("For " + fName + + ", invalid spectrum interpolation type: " + interpolation); + + auto const weightsSum = weights.back(); + + // normalize weights to total + for (auto &weight : weights) + weight /= weightsSum; + // ! important ! // Modify the activity according to the total sum of weights because we // normalize the weights - fActivity = fActivity * total; + fActivity *= weightsSum; fInitialActivity = fActivity; + + std::string interpolation_str = ""; + if (interpolation != "None" && interpolation != "none") + interpolation_str = + (interpolation != "none" ? ("_" + interpolation) : ""); + + ene->SetEnergyDisType(ene_type + interpolation_str); + ene->SetEmin(energy_bin_edges.front()); + ene->SetEmax(energy_bin_edges.back()); + ene->fEnergyCDF = energy_bin_edges; + ene->fProbabilityCDF = weights; } if (ene_type == "F18_analytic") { diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp new file mode 100644 index 000000000..3d4e345e0 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -0,0 +1,707 @@ +// +// ******************************************************************** +// * 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 GateLastVertexInteractionSplittingActor.cc +/// \brief Implementation of the GateLastVertexInteractionSplittingActor class + +#include "GateHelpersDict.h" +#include "GateHelpersImage.h" + +#include "CLHEP/Units/SystemOfUnits.h" +#include "G4BiasingProcessInterface.hh" +#include "G4Gamma.hh" +#include "G4LogicalVolumeStore.hh" +#include "G4ParticleTable.hh" +#include "G4PhysicalVolumeStore.hh" +#include "G4Positron.hh" +#include "G4ProcessManager.hh" +#include "G4ProcessVector.hh" +#include "G4TrackStatus.hh" +#include "G4TrackingManager.hh" +#include "G4VParticleChange.hh" +#include "G4eplusAnnihilation.hh" +#include "GateLastVertexInteractionSplittingActor.h" +#include "GateLastVertexSplittingPostStepDoIt.h" +#include "GateOptnComptSplitting.h" +#include "GateLastVertexSource.h" +#include "G4RunManager.hh" +#include <cmath> + + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateLastVertexInteractionSplittingActor:: + GateLastVertexInteractionSplittingActor(py::dict &user_info) + : GateVActor(user_info, false) { + + + +} + + +void GateLastVertexInteractionSplittingActor::InitializeUserInput(py::dict &user_info) { +GateVActor::InitializeUserInput(user_info); +fMotherVolumeName = DictGetStr(user_info, "attached_to"); +fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); +fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); +fAngularKill = DictGetBool(user_info, "angular_kill"); +fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); +fMaxTheta = DictGetDouble(user_info, "max_theta"); +fBatchSize = DictGetDouble(user_info, "batch_size"); +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +void GateLastVertexInteractionSplittingActor::print_tree(const tree<LastVertexDataContainer>& tr, tree<LastVertexDataContainer>::pre_order_iterator it, tree<LastVertexDataContainer>::pre_order_iterator end) + { + if(!tr.is_valid(it)) return; + int rootdepth=tr.depth(it); + std::cout << "-----" << std::endl; + while(it!=end) { + for(int i=0; i<tr.depth(it)-rootdepth; ++i) + std::cout << " "; + std::cout << (*it) << std::endl << std::flush; + ++it; + } + std::cout << "-----" << std::endl; + } + +G4bool GateLastVertexInteractionSplittingActor::DoesParticleEmittedInSolidAngle(G4ThreeVector dir, G4ThreeVector vectorDirector) { + G4double cosTheta = vectorDirector * dir; + if (cosTheta < fCosMaxTheta) + return false; + return true; +} + +G4VProcess* GateLastVertexInteractionSplittingActor::GetProcessFromProcessName(G4String particleName, G4String pName){ + auto *particle_table = G4ParticleTable::GetParticleTable(); + G4ParticleDefinition *particleDefinition = particle_table->FindParticle(particleName); + G4ProcessManager *processManager = particleDefinition->GetProcessManager(); + G4ProcessVector *processList = processManager->GetProcessList(); + G4VProcess* nullProcess = nullptr; + for (size_t i = 0; i < processList->size(); ++i) { + auto process = (*processList)[i]; + if (process->GetProcessName() == pName) { + return process; + } + } + return nullProcess; + +} + +G4Track* GateLastVertexInteractionSplittingActor::CreateATrackFromContainer(LastVertexDataContainer theContainer){ + + auto *particle_table = G4ParticleTable::GetParticleTable(); + SimpleContainer container = theContainer.GetContainerToSplit(); + if (container.GetParticleNameToSplit() != "None"){ + G4ParticleDefinition *particleDefinition = particle_table->FindParticle(container.GetParticleNameToSplit()); + G4ThreeVector momentum = container.GetMomentum(); + G4double energy = container.GetEnergy(); + if (energy <0){ + energy = 0; + momentum = {0,0,0}; + } + G4int trackStatus = container.GetTrackStatus(); + G4ThreeVector position = container.GetVertexPosition(); + G4ThreeVector polarization = container.GetPolarization(); + G4DynamicParticle* dynamicParticle = new G4DynamicParticle(particleDefinition,momentum,energy); + G4double time = 0; + G4Track* aTrack = new G4Track(dynamicParticle,time, position); + aTrack->SetPolarization(polarization); + if (trackStatus == 0){ + aTrack->SetTrackStatus(fAlive); + } + if (trackStatus == 1){ + aTrack->SetTrackStatus(fStopButAlive); + } + if ((trackStatus == 2) || (trackStatus == 3)){ + aTrack->SetTrackStatus(fAlive); + } + aTrack->SetWeight(container.GetWeight()); + return aTrack; + } + + return nullptr; + + +} + + +G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack(G4ParticleChangeForGamma *gammaProcess, G4Track track, G4double weight) { + + G4double energy = gammaProcess->GetProposedKineticEnergy(); + G4double globalTime = track.GetGlobalTime(); + G4ThreeVector polarization = gammaProcess->GetProposedPolarization(); + const G4ThreeVector momentum = gammaProcess->GetProposedMomentumDirection(); + const G4ThreeVector position = track.GetPosition(); + G4Track *newTrack = new G4Track(track); + + newTrack->SetKineticEnergy(energy); + newTrack->SetMomentumDirection(momentum); + newTrack->SetPosition(position); + newTrack->SetPolarization(polarization); + newTrack->SetWeight(weight); + return newTrack; +} + +void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process, LastVertexDataContainer container, G4double batchSize) { + + //G4TrackVector *trackVector = CurrentStep->GetfSecondary(); + GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; + for (int i = 0; i < batchSize; i++){ + G4VParticleChange *processFinalState = emProcess->PostStepDoIt(*fTrackToSplit, *initStep); + G4ParticleChangeForGamma *gammaProcessFinalState = (G4ParticleChangeForGamma *)processFinalState; + + G4ThreeVector momentum = gammaProcessFinalState->GetProposedMomentumDirection(); + + G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, *fTrackToSplit, fWeight); + + if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(newTrack->GetMomentumDirection(),fVectorDirector) == false)){ + delete newTrack; + } + else{ + fStackManager->PushOneTrack(newTrack); + } + + + // Special case here, since we generate independently each particle, we will not attach an electron to exiting compton photon, but we will the secondaries. + + + if (processFinalState->GetNumberOfSecondaries()> 0){ + delete processFinalState->GetSecondary(0); + } + + + processFinalState->Clear(); + gammaProcessFinalState->Clear(); + } +} + + + + +G4Track GateLastVertexInteractionSplittingActor::eBremProcessFinalState(G4Track* track, G4Step* step,G4VProcess *process){ + //It seem's that the the along step method apply only to brem results to no deposited energy but a change in momentum direction according to the process + //Whereas the along step method applied to the ionisation well change the deposited energy but not the momentum. Then I apply both to have a correct + //momentum and deposited energy before the brem effect. + G4String particleName = track->GetDefinition()->GetParticleName(); + G4VProcess* eIoniProcess = GetProcessFromProcessName(particleName, "eIoni"); + G4VProcess* eBremProcess = GetProcessFromProcessName(particleName, "eBrem"); + G4VParticleChange* eIoniProcessAlongState = eIoniProcess->AlongStepDoIt(*track, *step); + G4VParticleChange* eBremProcessAlongState = eBremProcess->AlongStepDoIt(*track, *step); + G4ParticleChangeForLoss* eIoniProcessAlongStateForLoss = (G4ParticleChangeForLoss*) eIoniProcessAlongState; + G4ParticleChangeForLoss* eBremProcessAlongStateForLoss = (G4ParticleChangeForLoss*) eBremProcessAlongState; + G4double LossEnergy = eIoniProcessAlongStateForLoss->GetLocalEnergyDeposit(); + G4ThreeVector momentum = eBremProcessAlongStateForLoss->GetProposedMomentumDirection(); + G4ThreeVector polarization = eBremProcessAlongStateForLoss->GetProposedPolarization(); + G4Track aTrack = G4Track(*track); + + aTrack.SetKineticEnergy(track->GetKineticEnergy() - LossEnergy); + aTrack.SetMomentumDirection(momentum); + aTrack.SetPolarization(polarization); + eIoniProcessAlongState->Clear(); + eBremProcessAlongState->Clear(); + return aTrack; + +} + + +void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer theContainer, G4double batchSize) { + SimpleContainer container = theContainer.GetContainerToSplit(); + G4String particleName = fTrackToSplit->GetParticleDefinition()->GetParticleName(); + //G4TrackVector *trackVector = CurrentStep->GetfSecondary(); + + G4VParticleChange *processFinalState = nullptr; + GateBremPostStepDoIt* bremProcess = nullptr; + GateGammaEmPostStepDoIt *emProcess = nullptr; + GateplusannihilAtRestDoIt *eplusAnnihilProcess = nullptr; + + if ((container.GetAnnihilationFlag() == "PostStep") && (fTrackToSplit->GetKineticEnergy() > 0)){ + emProcess = (GateGammaEmPostStepDoIt *)process; + } + if ((container.GetAnnihilationFlag() == "AtRest") || (fTrackToSplit->GetKineticEnergy() == 0)) { + eplusAnnihilProcess = (GateplusannihilAtRestDoIt *)process; + } + for (int j = 0; j < batchSize;j++){ + G4int NbOfSecondaries = 0; + + G4int count =0; + while (NbOfSecondaries == 0){ + if (process->GetProcessName() == "eBrem") { + G4Track aTrack = eBremProcessFinalState(fTrackToSplit,initStep,process); + bremProcess = (GateBremPostStepDoIt*) process; + processFinalState = bremProcess->GateBremPostStepDoIt::PostStepDoIt(aTrack, *initStep); + } + else { + if ((container.GetAnnihilationFlag() == "PostStep") && (fTrackToSplit->GetKineticEnergy() > 0)){ + processFinalState = emProcess->PostStepDoIt(*fTrackToSplit, *initStep); + } + if ((container.GetAnnihilationFlag() == "AtRest") || (fTrackToSplit->GetKineticEnergy() == 0)) { + processFinalState = eplusAnnihilProcess->GateplusannihilAtRestDoIt::AtRestDoIt(*fTrackToSplit,*initStep); + } + } + NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); + if (NbOfSecondaries == 0){ + processFinalState->Clear(); + } + count ++; + //Security break, in case of infinite loop + if (count > 10000){ + G4ExceptionDescription ed; + ed << " infinite loop detected during the track creation for the " <<process->GetProcessName() <<" process"<<G4endl; + G4Exception("GateLastVertexInteractionSplittingActor::SecondariesSplitting","BIAS.LV1",JustWarning,ed); + G4RunManager::GetRunManager()->AbortEvent(); + break; + } + } + + G4int idx = 0; + G4bool IsPushBack =false; + for (int i=0; i < NbOfSecondaries; i++){ + G4Track *newTrack = processFinalState->GetSecondary(i); + G4ThreeVector momentum = newTrack->GetMomentumDirection(); + + if (!(isnan(momentum[0]))){ + if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(momentum,fVectorDirector) == false)){ + delete newTrack; + } + else if (IsPushBack == true){ + delete newTrack; + } + else { + newTrack->SetWeight(fWeight); + newTrack->SetCreatorProcess(process); + //trackVector->emplace_back(newTrack); + fStackManager->PushOneTrack(newTrack); + //delete newTrack; + IsPushBack=true; + + } + } + else { + delete newTrack; + } + } + processFinalState->Clear(); + } +} + + +void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G4Step* initStep,G4Step *step,LastVertexDataContainer theContainer, G4double batchSize) { + // We retrieve the process associated to the process name to split and we + // split according the process. Since for compton scattering, the gamma is not + // a secondary particles, this one need to have his own splitting function. + + G4String processName = fProcessNameToSplit; + G4int nbOfTrackAlreadyInStack = fStackManager->GetNTotalTrack(); + if ((fProcessToSplit == 0) || (fProcessToSplit == nullptr)){ + SimpleContainer container = theContainer.GetContainerToSplit(); + fProcessToSplit = GetProcessFromProcessName(container.GetParticleNameToSplit(),processName); + } + + if (processName == "compt") { + ComptonSplitting(initStep,step, fProcessToSplit, theContainer, batchSize); + } + + else if((processName != "msc") && (processName != "conv")){ + SecondariesSplitting(initStep, step, fProcessToSplit, theContainer,batchSize); + } + fNumberOfTrackToSimulate = fStackManager->GetNTotalTrack() - nbOfTrackAlreadyInStack; + fNbOfBatchForExitingParticle ++; + if (fNbOfBatchForExitingParticle >500){ + fStackManager->clear(); + } + //stackManager->clear(); + +} + + +void GateLastVertexInteractionSplittingActor::CreateListOfbiasedVolume(G4LogicalVolume *volume) { + G4int nbOfDaughters = volume->GetNoDaughters(); + if (nbOfDaughters > 0) { + for (int i = 0; i < nbOfDaughters; i++) { + G4String LogicalVolumeName = volume->GetDaughter(i)->GetLogicalVolume()->GetName(); + G4LogicalVolume *logicalDaughtersVolume = volume->GetDaughter(i)->GetLogicalVolume(); + if (!(std::find(fListOfBiasedVolume.begin(),fListOfBiasedVolume.end(),LogicalVolumeName) != fListOfBiasedVolume.end())) + fListOfBiasedVolume.push_back(volume->GetDaughter(i)->GetLogicalVolume()->GetName()); + CreateListOfbiasedVolume(logicalDaughtersVolume); + } + } +} + + +void GateLastVertexInteractionSplittingActor::FillOfDataTree(G4Step*step){ + + G4String processName = "None"; + if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) + processName = step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); + + G4String creatorProcessName = "None"; + if (step->GetTrack()->GetCreatorProcess() != 0) + creatorProcessName =step->GetTrack()->GetCreatorProcess()->GetProcessName(); + + if ((step->GetTrack()->GetParticleDefinition()->GetParticleName() == "e+") && + ((step->GetTrack()->GetTrackStatus() == 1) || + (step->GetTrack()->GetTrackStatus() == 2))) { + processName = "annihil"; + } + + G4String annihilFlag = "None"; + if (processName == "annihil"){ + if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0){ + if (processName == step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName()){ + annihilFlag = "PostStep"; + } + else if (processName != step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName()){ + annihilFlag ="AtRest"; + } + } + } + + + + if (fIsFirstStep){ + LastVertexDataContainer newContainer = LastVertexDataContainer(); + newContainer.SetTrackID(step->GetTrack()->GetTrackID()); + newContainer.SetParticleName(step->GetTrack()->GetDefinition()->GetParticleName()); + newContainer.SetCreationProcessName(creatorProcessName); + + + + if (fTree.empty()){ + fTree.set_head(newContainer); + } + + for (auto it = fTree.begin_post(); it != fTree.end_post(); ++it){ + LastVertexDataContainer container = *it; + G4int trackID = container.GetTrackID(); + + if (step->GetTrack()->GetParentID() == trackID){ + newContainer = container.ContainerFromParentInformation(step); + fTree.append_child(it,newContainer); + break; + + } + } + + for (auto it = fTree.begin_post(); it != fTree.end_post(); ++it){ + LastVertexDataContainer container = *it; + G4int trackID = container.GetTrackID(); + if (step->GetTrack()->GetTrackID() == trackID){ + fIterator = it; + break; + } + } + } + + + + LastVertexDataContainer* container = &(*fIterator); + G4int trackID = container->GetTrackID(); + if ((processName != "Transportation") &&(processName !="None") && (processName !="Rayl")){ + if (step->GetTrack()->GetTrackID() == trackID){ + G4ThreeVector position = step->GetTrack()->GetPosition(); + G4ThreeVector prePosition = step->GetPreStepPoint()->GetPosition(); + G4ThreeVector momentum; + if ((processName == "annihil")) + momentum = step->GetPostStepPoint()->GetMomentumDirection(); + else{ + momentum = step->GetPreStepPoint()->GetMomentumDirection(); + } + G4ThreeVector polarization = step->GetPreStepPoint()->GetPolarization(); + G4String particleName = step->GetTrack()->GetDefinition()->GetParticleName(); + G4double energy = step->GetPreStepPoint()->GetKineticEnergy(); + G4double weight = step->GetTrack()->GetWeight(); + G4int trackStatus = step->GetTrack()->GetTrackStatus(); + G4int nbOfSecondaries = step->GetfSecondary()->size(); + G4double stepLength = step->GetStepLength(); + if (((processName == "annihil"))){ + energy -= (step->GetTotalEnergyDeposit()); + } + SimpleContainer containerToSplit = SimpleContainer(processName,energy, momentum, position,polarization,particleName,weight,trackStatus,nbOfSecondaries,annihilFlag,stepLength,prePosition); + container->SetContainerToSplit(containerToSplit); + container->PushListOfSplittingParameters(containerToSplit); + + } + } + + +} + + + +G4bool GateLastVertexInteractionSplittingActor::IsParticleExitTheBiasedVolume(G4Step*step){ + + + if ((step->GetPostStepPoint()->GetStepStatus() == 1)) { + G4String logicalVolumeNamePostStep = "None"; + if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) + logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + + if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()){ + return true; + } + /* + else if (std::find(fListOfBiasedVolume.begin(), fListOfBiasedVolume.end(), logicalVolumeNamePostStep) != fListOfBiasedVolume.end()) { + return false; + } + */ + } + if (step->GetPostStepPoint()->GetStepStatus() == 0) + return true; + return false; +} + + + +G4bool GateLastVertexInteractionSplittingActor::IsTheParticleUndergoesAProcess(G4Step* step){ + G4String processName = "None"; + G4String particleName = step->GetTrack()->GetParticleDefinition()->GetParticleName(); + if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) + processName = step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); + + if (std::find(fListOfProcessesAccordingParticles[particleName].begin(), fListOfProcessesAccordingParticles[particleName].end(), processName) != fListOfProcessesAccordingParticles[particleName].end()){ + return true; + } + return false; + + +} + +G4bool GateLastVertexInteractionSplittingActor::IsTheParticleUndergoesALossEnergyProcess(G4Step* step){ + if (step->GetPostStepPoint()->GetKineticEnergy() - step->GetPreStepPoint()->GetKineticEnergy() != 0) + return true; + return false; + + +} + + + +void GateLastVertexInteractionSplittingActor::BeginOfRunAction( + const G4Run *run) { + + fListOfProcessesAccordingParticles["gamma"] = {"compt","phot","conv"}; + fListOfProcessesAccordingParticles["e-"] = {"eBrem","eIoni","msc"}; + fListOfProcessesAccordingParticles["e+"] = {"eBrem","eIoni","msc","annihil"}; + + std::cout<<fMotherVolumeName<<std::endl; + G4LogicalVolume *biasingVolume = G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + fListOfBiasedVolume.push_back(biasingVolume->GetName()); + CreateListOfbiasedVolume(biasingVolume); + + auto* source = fSourceManager->FindSourceByName("source_vertex"); + fVertexSource = (GateLastVertexSource* ) source; + + fCosMaxTheta = std::cos(fMaxTheta); + fStackManager = G4EventManager::GetEventManager()->GetStackManager(); + + if (fRotationVectorDirector) { + G4VPhysicalVolume *physBiasingVolume = + G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + auto rot = physBiasingVolume->GetObjectRotationValue(); + fVectorDirector = rot * fVectorDirector; + } +} + +void GateLastVertexInteractionSplittingActor::BeginOfEventAction( + const G4Event *event) { + fEventID = event->GetEventID(); + fIsAnnihilAlreadySplit = false; + fNbOfBatchForExitingParticle = 0; + if (fEventID%50000 == 0) + std::cout<<"event ID : "<<fEventID<<std::endl; + if (fCopyInitStep != 0){ + delete fCopyInitStep; + fCopyInitStep = nullptr; + } + fSplitCounter =0; + fNumberOfTrackToSimulate =0; + fKilledBecauseOfProcess = false; + + if (fActiveSource == "source_vertex"){ + auto* source = fSourceManager->FindSourceByName(fActiveSource); + GateLastVertexSource *vertexSource = (GateLastVertexSource*) source; + fContainer = vertexSource->GetLastVertexContainer(); + fProcessNameToSplit = vertexSource->GetProcessToSplit(); + if (fProcessToSplit !=0){ + fProcessToSplit = nullptr; + } + if (fTrackToSplit !=0){ + delete fTrackToSplit; + fTrackToSplit = nullptr; + } + fTrackToSplit = CreateATrackFromContainer(fContainer); + if (fTrackToSplit != 0) + fWeight = fTrackToSplit->GetWeight()/fSplittingFactor; + } + + +} + +void GateLastVertexInteractionSplittingActor::PreUserTrackingAction( + const G4Track *track) { + fToSplit =true; + fIsFirstStep = true; + +} + +void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { + + + if (fActiveSource != "source_vertex"){ + FillOfDataTree(step); + + if (IsParticleExitTheBiasedVolume(step)){ + if ((fAngularKill == false) || ((fAngularKill == true) && (DoesParticleEmittedInSolidAngle(step->GetTrack()->GetMomentumDirection(),fVectorDirector) == true))){ + fListOfContainer.push_back((*fIterator)); + } + + step->GetTrack()->SetTrackStatus(fStopAndKill); + } + + + } + + + + if (fOnlyTree == false){ + if (fActiveSource == "source_vertex"){ + + if (fIsFirstStep){ + fTrackID = step->GetTrack()->GetTrackID(); + fEkin = step->GetPostStepPoint()->GetKineticEnergy(); + } + else{ + if ((fTrackID == step->GetTrack()->GetTrackID()) && (fEkin != step->GetPreStepPoint()->GetKineticEnergy())){ + fToSplit =false; + } + else{ + fEkin = step->GetPostStepPoint()->GetKineticEnergy(); + } + + } + if (fToSplit) { + G4String creatorProcessName = "None"; + if (step->GetTrack()->GetCreatorProcess() != 0) + creatorProcessName =step->GetTrack()->GetCreatorProcess()->GetProcessName(); + if (((step->GetTrack()->GetParentID() == 0)&& (step->GetTrack()->GetTrackID() == 1))|| ((creatorProcessName == "annihil") && (step->GetTrack()->GetParentID() == 1))){ + + if ((fProcessNameToSplit != "annihil") || ((fProcessNameToSplit == "annihil")&& (fIsAnnihilAlreadySplit ==false))){ + + //FIXME : list of process which are not splitable yet + if ((fProcessNameToSplit != "msc") && (fProcessNameToSplit != "conv") && (fProcessNameToSplit != "eIoni")) { + fCopyInitStep = new G4Step(*step); + if (fProcessNameToSplit == "eBrem"){ + fCopyInitStep->SetStepLength(fContainer.GetContainerToSplit().GetStepLength()); + fCopyInitStep->GetPreStepPoint()->SetKineticEnergy(fContainer.GetContainerToSplit().GetEnergy()); + + } + CreateNewParticleAtTheLastVertex(fCopyInitStep,step,fContainer, fBatchSize); + } + step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); + + if (fProcessNameToSplit == "annihil"){ + fIsAnnihilAlreadySplit = true; + } + } + + + else if ((fProcessNameToSplit == "annihil")&& (fIsAnnihilAlreadySplit == true)){ + step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); + } + + } + + else { + if (fIsFirstStep){ + fNumberOfTrackToSimulate --; + if (fKilledBecauseOfProcess == false){ + fSplitCounter += 1; + } + else { + fKilledBecauseOfProcess = false; + } + + if (fSplitCounter > fSplittingFactor){ + step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); + fStackManager->clear(); + } + } + + if (IsTheParticleUndergoesALossEnergyProcess(step)){ + step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); + fKilledBecauseOfProcess = true; + } + + if (fIsFirstStep){ + if (fSplitCounter <= fSplittingFactor){ + if (fNumberOfTrackToSimulate == 0){ + CreateNewParticleAtTheLastVertex(fCopyInitStep,step,fContainer,(fSplittingFactor - fSplitCounter +1)/fSplittingFactor * fBatchSize); + } + } + } + } + } + } + } + + fIsFirstStep = false; + + + + +} + + +void GateLastVertexInteractionSplittingActor::EndOfEventAction( + const G4Event* event) { + + if (fActiveSource != "source_vertex"){ + + //print_tree(fTree,fTree.begin(),fTree.end()); + fVertexSource->SetNumberOfEventToSimulate(fListOfContainer.size()); + fVertexSource->SetNumberOfGeneratedEvent(0); + fVertexSource->SetListOfVertexToSimulate(fListOfContainer); + fTree.clear(); + fListOfContainer.clear(); + } + + if (fOnlyTree == false){ + + auto* source = fSourceManager->FindSourceByName("source_vertex"); + GateLastVertexSource* vertexSource = (GateLastVertexSource*) source; + if (vertexSource->GetNumberOfGeneratedEvent() < vertexSource->GetNumberOfEventToSimulate()){ + fSourceManager->SetActiveSourcebyName("source_vertex"); + } + fActiveSource = fSourceManager->GetActiveSourceName(); + } + + } + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h new file mode 100644 index 000000000..dfe0ee850 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h @@ -0,0 +1,137 @@ +// +// ******************************************************************** +// * 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 GateLastVertexInteractionSplittingActor.h +/// \brief Definition of the GateLastVertexInteractionSplittingActor class +#ifndef GateLastVertexInteractionSplittingActor_h +#define GateLastVertexInteractionSplittingActor_h 1 + +#include "G4ParticleChangeForGamma.hh" +#include "G4VEnergyLossProcess.hh" +#include "GateVActor.h" +#include <iostream> +#include <pybind11/stl.h> +#include "GateLastVertexSplittingDataContainer.h" +#include "tree.hh" +#include "tree_util.hh" +#include <iostream> +#include "GateLastVertexSource.h" +#include "CLHEP/Vector/ThreeVector.h" +#include "G4StackManager.hh" +using CLHEP::Hep3Vector; + +namespace py = pybind11; + +class GateLastVertexInteractionSplittingActor : public GateVActor { +public: + GateLastVertexInteractionSplittingActor(py::dict &user_info); + virtual ~GateLastVertexInteractionSplittingActor() {} + + G4double fSplittingFactor; + G4bool fAngularKill; + G4bool fRotationVectorDirector; + G4ThreeVector fVectorDirector; + G4double fMaxTheta; + G4double fCosMaxTheta; + G4int fTrackIDOfSplittedTrack = 0; + G4int fParentID = -1; + G4int fEventID; + G4int fEventIDOfSplittedTrack; + G4int fEventIDOfInitialSplittedTrack; + G4int fTrackID; + G4double fEkin; + G4int fTrackIDOfInitialSplittedTrack = 0; + G4int ftmpTrackID; + G4bool fIsFirstStep = true; + G4bool fSuspendForAnnihil = false; + G4double fWeightOfEnteringParticle = 0; + G4double fSplitCounter = 0; + G4bool fToSplit = true; + G4String fActiveSource = "None"; + G4bool fIsAnnihilAlreadySplit =false; + G4int fCounter; + G4bool fKilledBecauseOfProcess = false; + G4bool fFirstSplittedPart = true; + G4bool fOnlyTree = false; + G4double fWeight; + G4double fBatchSize; + G4int fNumberOfTrackToSimulate = 0; + G4int fNbOfBatchForExitingParticle=0; + G4int fTracksCounts=0; + GateLastVertexSource* fVertexSource = nullptr; + tree<LastVertexDataContainer> fTree; + tree<LastVertexDataContainer>::post_order_iterator fIterator; + std::vector<LastVertexDataContainer> fListOfContainer; + G4StackManager* fStackManager = nullptr; + + + + G4Track *fTrackToSplit = nullptr; + G4Step* fCopyInitStep = nullptr; + G4String fProcessNameToSplit; + G4VProcess* fProcessToSplit; + LastVertexDataContainer fContainer; + + std::vector<G4Track> fTracksToPostpone; + std::map<G4String,std::vector<G4String>> fListOfProcessesAccordingParticles; + std::map<G4int,LastVertexDataContainer> fDataMap; + + + + std::vector<std::string> fListOfVolumeAncestor; + std::vector<std::string> fListOfBiasedVolume; + + std::vector<G4String> fListOfProcesses = {"compt", "annihil", "eBrem", "conv", + "phot"}; + + void InitializeUserInput(py::dict &user_info) override; + virtual void SteppingAction(G4Step *) override; + virtual void BeginOfEventAction(const G4Event *) override; + virtual void EndOfEventAction(const G4Event *) override; + virtual void BeginOfRunAction(const G4Run *run) override; + virtual void PreUserTrackingAction(const G4Track *track) override; + + // Pure splitting functions + G4bool DoesParticleEmittedInSolidAngle(G4ThreeVector dir, G4ThreeVector vectorDirector); + G4Track *CreateComptonTrack(G4ParticleChangeForGamma *, G4Track, G4double); + void ComptonSplitting(G4Step* initStep,G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container, G4double batchSize); + void SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container, G4double batchSize); + + void CreateNewParticleAtTheLastVertex(G4Step*init,G4Step *current, LastVertexDataContainer, G4double batchSize); + G4Track* CreateATrackFromContainer(LastVertexDataContainer container); + G4bool IsTheParticleUndergoesAProcess(G4Step* step); + G4bool IsTheParticleUndergoesALossEnergyProcess(G4Step* step); + G4VProcess* GetProcessFromProcessName(G4String particleName, G4String pName); + G4Track eBremProcessFinalState(G4Track* track, G4Step* step,G4VProcess *process); + + + void FillOfDataTree(G4Step *step); + G4bool IsParticleExitTheBiasedVolume(G4Step*step); + void CreateListOfbiasedVolume(G4LogicalVolume *volume); + void print_tree(const tree<LastVertexDataContainer>& tr, tree<LastVertexDataContainer>::pre_order_iterator it, tree<LastVertexDataContainer>::pre_order_iterator end); +}; + +#endif diff --git a/core/opengate_core/opengate_lib/GateLastVertexSource.cpp b/core/opengate_core/opengate_lib/GateLastVertexSource.cpp new file mode 100644 index 000000000..b903d90c8 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateLastVertexSource.cpp @@ -0,0 +1,112 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include "GateLastVertexSource.h" +#include "G4ParticleTable.hh" +#include "GateHelpersDict.h" +#include <G4UnitsTable.hh> + +GateLastVertexSource::GateLastVertexSource() : GateVSource() { +} + +GateLastVertexSource::~GateLastVertexSource() { +} + +void GateLastVertexSource::InitializeUserInfo(py::dict &user_info) { + GateVSource::InitializeUserInfo(user_info); + // get user info about activity or nb of events + fN = DictGetInt(user_info, "n"); + } + +double GateLastVertexSource::PrepareNextTime(double current_simulation_time) { + + /* + // If all N events have been generated, we stop (negative time) + if (fNumberOfGeneratedEvents >= fN){ + std::cout << "LV: "<< -1<<" "<< fNumberOfGeneratedEvents <<" "<< fN<<std::endl; + return -1; + } + + if (fListOfContainer.size()==0){ + std::cout << "LV: "<< 1<<" "<<fNumberOfGeneratedEvents <<" "<< fN<<std::endl; + return 1; + } + // Else we consider all event with a timestamp equal to the simulation + // StartTime + std::cout << "LV: "<< fStartTime<<" "<<fNumberOfGeneratedEvents <<" "<< fN<<std::endl; + return fStartTime; + */ + if (fNumberOfGeneratedEvents >= fN) + return -1; + + return fStartTime + 1; +} + +void GateLastVertexSource::PrepareNextRun() { + // The following compute the global transformation from + // the local volume (mother) to the world + GateVSource::PrepareNextRun(); + + // The global transformation to apply for the current RUN is known in : + // fGlobalTranslation & fGlobalRotation + + // init the number of generated events (here, for each run) + fNumberOfGeneratedEvents = 0; + +} + +void GateLastVertexSource::GenerateOnePrimary(G4Event* event, double current_simulation_time, G4int idx ){ + + if (fNumberOfGeneratedEvents >= fN){ + auto *particle_table = G4ParticleTable::GetParticleTable(); + auto *fParticleDefinition = particle_table->FindParticle("geantino"); + auto *particle = new G4PrimaryParticle(fParticleDefinition); + particle->SetKineticEnergy(0); + particle->SetMomentumDirection({1,0,0}); + particle->SetWeight(1); + auto *vertex = new G4PrimaryVertex({0,0,0}, current_simulation_time); + vertex->SetPrimary(particle); + event->AddPrimaryVertex(vertex); + } + else { + + SimpleContainer containerToSplit = fListOfContainer[idx].GetContainerToSplit(); + G4double energy = containerToSplit.GetEnergy(); + if (energy < 0){ + energy = 0; + } + fContainer = fListOfContainer[idx]; + G4ThreeVector position = containerToSplit.GetVertexPosition(); + G4ThreeVector momentum = containerToSplit.GetMomentum(); + G4String particleName = containerToSplit.GetParticleNameToSplit(); + G4double weight =containerToSplit.GetWeight(); + fProcessToSplit = containerToSplit.GetProcessNameToSplit(); + + auto &l = fThreadLocalData.Get(); + auto *particle_table = G4ParticleTable::GetParticleTable(); + auto *fParticleDefinition = particle_table->FindParticle(particleName); + auto *particle = new G4PrimaryParticle(fParticleDefinition); + particle->SetKineticEnergy(energy); + particle->SetMomentumDirection(momentum); + particle->SetWeight(weight); + auto *vertex = new G4PrimaryVertex(position, current_simulation_time); + vertex->SetPrimary(particle); + event->AddPrimaryVertex(vertex); + } + +} + + +void GateLastVertexSource::GeneratePrimaries(G4Event *event, + double current_simulation_time) { + + GenerateOnePrimary(event,current_simulation_time,fNumberOfGeneratedEvents); + fNumberOfGeneratedEvents++; + if (fNumberOfGeneratedEvents == fListOfContainer.size()){ + fListOfContainer.clear(); + } +} diff --git a/core/opengate_core/opengate_lib/GateLastVertexSource.h b/core/opengate_core/opengate_lib/GateLastVertexSource.h new file mode 100644 index 000000000..dd23c124d --- /dev/null +++ b/core/opengate_core/opengate_lib/GateLastVertexSource.h @@ -0,0 +1,85 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#ifndef GateLastVertexSource_h +#define GateLastVertexSource_h + +#include "GateAcceptanceAngleTesterManager.h" +#include "GateSingleParticleSource.h" +#include "GateVSource.h" +#include "GateLastVertexSplittingDataContainer.h" +#include <pybind11/stl.h> + +namespace py = pybind11; + +/* + This is NOT a real source type but a template to help writing your own source + type. Copy-paste this file with a different name ("MyNewSource.hh") and start + building. You also need to copy : GateLastVertexSource.hh GateLastVertexSource.cpp + pyGateLastVertexSource.cpp + And add the source declaration in opengate_core.cpp + */ + +class GateLastVertexSource : public GateVSource { + +public: + GateLastVertexSource(); + + ~GateLastVertexSource() override; + + void InitializeUserInfo(py::dict &user_info) override; + + double PrepareNextTime(double current_simulation_time) override; + + void PrepareNextRun() override; + + void GeneratePrimaries(G4Event *event, double time) override; + + + void GenerateOnePrimary(G4Event *event, double time,G4int idx); + + + void SetListOfVertexToSimulate(std::vector<LastVertexDataContainer> list){ + fListOfContainer = list; + } + + void SetNumberOfGeneratedEvent(G4int nbEvent){ + fNumberOfGeneratedEvents = nbEvent; + } + + void SetNumberOfEventToSimulate(G4int N){ + fN = N; + } + + G4int GetNumberOfEventToSimulate(){ + return fN; + } + + G4int GetNumberOfGeneratedEvent(){ + return fNumberOfGeneratedEvents; + } + + G4String GetProcessToSplit(){ + return fProcessToSplit; + } + + LastVertexDataContainer GetLastVertexContainer(){ + return fContainer; + } + + +protected: + G4int fNumberOfGeneratedEvents = 0; + G4int fN = 0; + double fFloatValue; + std::vector<double> fVectorValue; + std::vector<LastVertexDataContainer> fListOfContainer; + G4String fProcessToSplit = "None"; + LastVertexDataContainer fContainer; +}; + +#endif // GateLastVertexSource_h diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h new file mode 100644 index 000000000..474d6cc84 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h @@ -0,0 +1,165 @@ +// +// ******************************************************************** +// * 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. * +// ******************************************************************** +// +/// +#ifndef LastVertexDataContainer_h +#define LastVertexDataContainer_h + + +#include <iostream> +#include "G4VEnergyLossProcess.hh" +#include "G4Track.hh" +#include "G4VEmProcess.hh" +#include "G4VParticleChange.hh" +#include "G4eplusAnnihilation.hh" +#include "G4PhysicalConstants.hh" +#include "G4MaterialCutsCouple.hh" +#include "G4Gamma.hh" +#include "G4Electron.hh" +#include "G4Positron.hh" +#include "G4eeToTwoGammaModel.hh" +#include "G4EmBiasingManager.hh" +#include "G4EntanglementAuxInfo.hh" +#include "G4eplusAnnihilationEntanglementClipBoard.hh" +#include "G4EmParameters.hh" +#include "G4PhysicsModelCatalog.hh" +#include "GateLastVertexSplittingSimpleContainer.h" + + +class LastVertexDataContainer{ + +public : + + +LastVertexDataContainer(){} + +~LastVertexDataContainer(){} + + + +void SetTrackID(G4int trackID ){ + fTrackID = trackID; +} + +G4int GetTrackID(){ + return fTrackID; +} + + +void SetParticleName(G4String name){ + fParticleName = name; +} + +G4String GetParticleName(){ + return fParticleName; +} + + +void SetCreationProcessName(G4String creationProcessName){ + fCreationProcessName = creationProcessName; +} + +G4String GetCreationProcessName(){ + return fCreationProcessName; +} + + +void SetContainerToSplit(SimpleContainer container){ + fContainerToSplit = container; +} + + +SimpleContainer GetContainerToSplit(){ + return fContainerToSplit; +} + + + +void PushListOfSplittingParameters(SimpleContainer container){ + fVectorOfContainerToSplit.emplace_back(container); +} + + + + +LastVertexDataContainer ContainerFromParentInformation(G4Step* step){ + LastVertexDataContainer aContainer = LastVertexDataContainer(); + + aContainer.fTrackID = step->GetTrack()->GetTrackID(); + aContainer.fParticleName = step->GetTrack()->GetDefinition()->GetParticleName(); + if (this->fContainerToSplit.GetProcessNameToSplit() != "None"){ + if (this->fVectorOfContainerToSplit.size() !=0){ + G4ThreeVector vertexPosition = step->GetTrack()->GetVertexPosition(); + for (int i =0;i<this->fVectorOfContainerToSplit.size();i++){ + if (vertexPosition == this->fVectorOfContainerToSplit[i].GetVertexPosition()){ + SimpleContainer tmpContainer = this->fVectorOfContainerToSplit[i]; + //std::cout<<"1 "<<tmpContainer.GetProcessNameToSplit()<<std::endl; + aContainer.fContainerToSplit = SimpleContainer(tmpContainer.GetProcessNameToSplit(),tmpContainer.GetEnergy(),tmpContainer.GetMomentum(),tmpContainer.GetVertexPosition(),tmpContainer.GetPolarization(),tmpContainer.GetParticleNameToSplit(),tmpContainer.GetWeight(),tmpContainer.GetTrackStatus(),tmpContainer.GetNbOfSecondaries(),tmpContainer.GetAnnihilationFlag(),tmpContainer.GetStepLength(), tmpContainer.GetPrePositionToSplit()); + return aContainer; + } + } + } + else{ + SimpleContainer tmpContainer = this->fContainerToSplit; + //std::cout<<"2 "<<tmpContainer.GetProcessNameToSplit()<<std::endl; + aContainer.fContainerToSplit = SimpleContainer(tmpContainer.GetProcessNameToSplit(),tmpContainer.GetEnergy(),tmpContainer.GetMomentum(),tmpContainer.GetVertexPosition(),tmpContainer.GetPolarization(),tmpContainer.GetParticleNameToSplit(),tmpContainer.GetWeight(),tmpContainer.GetTrackStatus(),tmpContainer.GetNbOfSecondaries(),tmpContainer.GetAnnihilationFlag(),tmpContainer.GetStepLength(),tmpContainer.GetPrePositionToSplit()); + return aContainer; + } + } + //std::cout<<"3"<<std::endl; + return aContainer; +} + + + + + + +friend std::ostream& operator<<(std::ostream& os, LastVertexDataContainer& container) { + os <<container.fParticleName<<" ID: "<<container.fTrackID<< " process to split : "<<container.fContainerToSplit.GetProcessNameToSplit()<<" name to split"<<container.fContainerToSplit.GetParticleNameToSplit() ; + return os; +} + + + + +private : + +G4String fParticleName="None"; +G4int fTrackID = 0; +G4String fCreationProcessName ="None"; +SimpleContainer fContainerToSplit; + +std::vector<SimpleContainer> fVectorOfContainerToSplit; + +}; + +#endif + + + + + + \ No newline at end of file diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h new file mode 100644 index 000000000..38a9dc2ea --- /dev/null +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h @@ -0,0 +1,116 @@ +// +// ******************************************************************** +// * 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. * +// ******************************************************************** +// +/// +#ifndef GateLastVertexSplittingPostStepDoIt_h +#define GateLastVertexSplittingPostStepDoIt_h + + +#include "G4VEnergyLossProcess.hh" +#include "G4VEmProcess.hh" +#include "G4VParticleChange.hh" +#include "G4eplusAnnihilation.hh" +#include "G4PhysicalConstants.hh" +#include "G4MaterialCutsCouple.hh" +#include "G4Gamma.hh" +#include "G4Electron.hh" +#include "G4Positron.hh" +#include "G4eeToTwoGammaModel.hh" +#include "G4EmBiasingManager.hh" +#include "G4EntanglementAuxInfo.hh" +#include "G4eplusAnnihilationEntanglementClipBoard.hh" +#include "G4EmParameters.hh" +#include "G4PhysicsModelCatalog.hh" +#include <iostream> + + + + +class GateBremPostStepDoIt : public G4VEnergyLossProcess { +public : + +GateBremPostStepDoIt(); + +~ GateBremPostStepDoIt(); + +virtual G4VParticleChange * PostStepDoIt (const G4Track & track, const G4Step & step) override +{ + const G4MaterialCutsCouple* couple = step.GetPreStepPoint()->GetMaterialCutsCouple(); + currentCouple = couple; + G4VParticleChange* particleChange = G4VEnergyLossProcess::PostStepDoIt(track,step); + return particleChange; +} + +virtual G4VParticleChange * AlongStepDoIt (const G4Track & track, const G4Step & step) override +{ + G4VParticleChange* particleChange = G4VEnergyLossProcess::AlongStepDoIt(track,step); + return particleChange; +} + + +}; + + +class GateGammaEmPostStepDoIt : public G4VEmProcess { +public : + +GateGammaEmPostStepDoIt(); + +~ GateGammaEmPostStepDoIt(); + +virtual G4VParticleChange * PostStepDoIt(const G4Track & track, const G4Step & step) override +{ + const G4MaterialCutsCouple* couple = step.GetPreStepPoint()->GetMaterialCutsCouple(); + currentCouple = couple; + G4VParticleChange* particleChange = G4VEmProcess::PostStepDoIt(track,step); + return particleChange; +} + + +}; + +class GateplusannihilAtRestDoIt : public G4eplusAnnihilation { +public : + +GateplusannihilAtRestDoIt(); +~ GateplusannihilAtRestDoIt(); + +virtual G4VParticleChange* AtRestDoIt(const G4Track& track, + const G4Step& step) override +// Performs the e+ e- annihilation when both particles are assumed at rest. + { + G4Track copyTrack = G4Track(track); + copyTrack.SetStep(&step); + G4VParticleChange* particleChange = G4eplusAnnihilation::AtRestDoIt(copyTrack,step); + return particleChange; + } +}; +#endif + + + + + + \ No newline at end of file diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h new file mode 100644 index 000000000..e3069bf2c --- /dev/null +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h @@ -0,0 +1,217 @@ +// +// ******************************************************************** +// * 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. * +// ******************************************************************** +// +/// +#ifndef SimpleContainer_h +#define SimpleContainer_h + + +#include <iostream> +#include "G4VEnergyLossProcess.hh" +#include "G4Track.hh" +#include "G4VEmProcess.hh" +#include "G4VParticleChange.hh" +#include "G4eplusAnnihilation.hh" +#include "G4PhysicalConstants.hh" +#include "G4MaterialCutsCouple.hh" +#include "G4Gamma.hh" +#include "G4Electron.hh" +#include "G4Positron.hh" +#include "G4eeToTwoGammaModel.hh" +#include "G4EmBiasingManager.hh" +#include "G4EntanglementAuxInfo.hh" +#include "G4eplusAnnihilationEntanglementClipBoard.hh" +#include "G4EmParameters.hh" +#include "G4PhysicsModelCatalog.hh" + + +class SimpleContainer{ + +public : +SimpleContainer(G4String processName,G4double energy,G4ThreeVector momentum, G4ThreeVector position,G4ThreeVector polarization,G4String name,G4double weight,G4int trackStatus,G4int nbSec,G4String flag,G4double length, G4ThreeVector prePos){ + + fProcessNameToSplit = processName; + fEnergyToSplit = energy; + fMomentumToSplit = momentum; + fPositionToSplit = position; + fPolarizationToSplit = polarization; + fParticleNameToSplit = name; + fWeightToSplit = weight; + fTrackStatusToSplit = trackStatus; + fNumberOfSecondariesToSplit = nbSec; + fAnnihilProcessFlag = flag; + fStepLength = length; + fPrePosition = prePos; + +} + + +SimpleContainer(){} + +~SimpleContainer(){} + +void SetProcessNameToSplit(G4String processName){ + fProcessNameToSplit = processName; +} + +G4String GetProcessNameToSplit(){ + return fProcessNameToSplit; +} + +void SetEnergy(G4double energy){ + fEnergyToSplit =energy; +} + +G4double GetEnergy(){ + return fEnergyToSplit; +} + + +void SetWeight(G4double weight){ + fWeightToSplit =weight; +} + +G4double GetWeight(){ + return fWeightToSplit; +} + +void SetPolarization(G4ThreeVector polarization){ + fPolarizationToSplit = polarization; +} + +G4ThreeVector GetPolarization(){ + return fPolarizationToSplit; +} + +void SetMomentum(G4ThreeVector momentum){ + fMomentumToSplit =momentum; +} + +G4ThreeVector GetMomentum(){ + return fMomentumToSplit; +} + +void SetVertexPosition(G4ThreeVector position){ + fPositionToSplit = position; +} + +G4ThreeVector GetVertexPosition(){ + return fPositionToSplit; +} + +void SetParticleNameToSplit(G4String name){ + fParticleNameToSplit = name; +} + +G4String GetParticleNameToSplit(){ + return fParticleNameToSplit; +} + + +void SetTrackStatus(G4int trackStatus){ + fTrackStatusToSplit = trackStatus; +} + + +G4int GetTrackStatus(){ + return fTrackStatusToSplit; +} + + +void SetNbOfSecondaries(G4int nbSec){ + fNumberOfSecondariesToSplit = nbSec; +} + +G4int GetNbOfSecondaries(){ + return fNumberOfSecondariesToSplit; +} + +void SetAnnihilationFlag(G4String flag){ + fAnnihilProcessFlag = flag; +} + +G4String GetAnnihilationFlag(){ + return fAnnihilProcessFlag; +} + +void SetStepLength(G4double length){ + fStepLength = length; +} + +G4double GetStepLength(){ + return fStepLength; +} + + + +void SetPrePositionToSplit(G4ThreeVector prePos){ + fPrePosition = prePos; +} + +G4ThreeVector GetPrePositionToSplit(){ + return fPrePosition; +} + + + + +void DumpInfoToSplit(){ + std::cout<<"Particle name of the particle to split: "<<fParticleNameToSplit<<std::endl; + std::cout<<"Kinetic Energy of the particle to split: "<<fEnergyToSplit<<std::endl; + std::cout<<"Momentum of the particle to split: "<<fMomentumToSplit<<std::endl; + std::cout<<"Initial position of the particle to split: "<<fPositionToSplit<<std::endl; + std::cout<<"ProcessNameToSplit: "<<fProcessNameToSplit<<std::endl; + std::cout<<" "<<std::endl; + +} + + + + +private : + +G4String fParticleNameToSplit="None"; +G4String fProcessNameToSplit ="None"; +G4double fEnergyToSplit = 0; +G4ThreeVector fMomentumToSplit; +G4ThreeVector fPositionToSplit; +G4ThreeVector fPolarizationToSplit; +G4double fWeightToSplit; +G4int fTrackStatusToSplit; +G4int fNumberOfSecondariesToSplit; +G4String fAnnihilProcessFlag; +G4double fStepLength; +G4ThreeVector fPrePosition; + + +}; + +#endif + + + + + + \ No newline at end of file diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp new file mode 100644 index 000000000..990a5629f --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp @@ -0,0 +1,129 @@ +// +// ******************************************************************** +// * 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. * +// ******************************************************************** +// +#include "GateOptnForceFreeFlight.h" +#include "G4BiasingProcessInterface.hh" +#include "G4ILawForceFreeFlight.hh" +#include "G4Step.hh" + +// This operator is used to transport the particle without interaction, and then +// correct the weight of the particle +// according to the probablity for the photon to not interact within the matter. +// To do that, we use a GEANT4 modifed Interaction Law (G4ILawForceFreeFlight), +// which modify, for the biased process the probability for the interaction to +// occur : Never. This occurence is called during the tracking for each step. +// Here the step is the largest possible, and one step correspond to the path of +// particle in the media. + +GateOptnForceFreeFlight ::GateOptnForceFreeFlight(G4String name) + : G4VBiasingOperation(name), fOperationComplete(true) { + fForceFreeFlightInteractionLaw = + new G4ILawForceFreeFlight("LawForOperation" + name); +} + +GateOptnForceFreeFlight ::~GateOptnForceFreeFlight() { + if (fForceFreeFlightInteractionLaw) + delete fForceFreeFlightInteractionLaw; +} + +const G4VBiasingInteractionLaw * +GateOptnForceFreeFlight ::ProvideOccurenceBiasingInteractionLaw( + const G4BiasingProcessInterface *, + G4ForceCondition &proposeForceCondition) { + fOperationComplete = false; + proposeForceCondition = Forced; + return fForceFreeFlightInteractionLaw; +} + +G4VParticleChange *GateOptnForceFreeFlight ::ApplyFinalStateBiasing( + const G4BiasingProcessInterface *callingProcess, const G4Track *track, + const G4Step *step, G4bool &forceFinalState) { + + fParticleChange.Initialize(*track); + forceFinalState = true; + fCountProcess++; + + fProposedWeight *= + fWeightChange[callingProcess->GetWrappedProcess()->GetProcessName()]; + + if (fRussianRouletteForWeights) { + G4int nbOfOrderOfMagnitude = std::log10(fInitialWeight / fMinWeight); + + if ((fProposedWeight < fMinWeight) || + ((fProposedWeight < 0.1 * fInitialWeight) && + (fNbOfRussianRoulette == nbOfOrderOfMagnitude - 1))) { + fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); + fNbOfRussianRoulette = 0; + return &fParticleChange; + } + + if ((fProposedWeight < 0.1 * fInitialWeight) && (fCountProcess == 4)) { + G4double probability = G4UniformRand(); + for (int i = 2; i <= nbOfOrderOfMagnitude; i++) { + G4double RRprobability = 1 / std::pow(10, i); + if (fProposedWeight * 1 / std::pow(10, fNbOfRussianRoulette) < + 10 * RRprobability * fMinWeight) { + fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); + fNbOfRussianRoulette = 0; + return &fParticleChange; + } + if ((fProposedWeight >= RRprobability * fInitialWeight) && + (fProposedWeight < 10 * RRprobability * fInitialWeight)) { + if (probability > 10 * RRprobability) { + fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); + fNbOfRussianRoulette = 0; + return &fParticleChange; + } else { + fProposedWeight = fProposedWeight / (10 * RRprobability); + fNbOfRussianRoulette = fNbOfRussianRoulette + i - 1; + } + } + } + } + } else { + if (fProposedWeight < fMinWeight) { + fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); + return &fParticleChange; + } + } + fParticleChange.ProposeWeight(fProposedWeight); + fOperationComplete = true; + return &fParticleChange; +} + +void GateOptnForceFreeFlight ::AlongMoveBy( + const G4BiasingProcessInterface *callingProcess, const G4Step *, + G4double weightChange) + +{ + G4String processName = callingProcess->GetWrappedProcess()->GetProcessName(); + if (processName != "Rayl"){ + fWeightChange[processName] = + weightChange; + } + else { + fWeightChange[processName] = 1; + } +} diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h new file mode 100644 index 000000000..398f27851 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h @@ -0,0 +1,122 @@ +// +// ******************************************************************** +// * 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. * +// ******************************************************************** +// +// +// +//--------------------------------------------------------------- +// +// GateOptnForceFreeFlight +// +// Class Description: +// A G4VBiasingOperation physics-based biasing operation. +// If forces the physics process to not act on the track. +// In this implementation (meant for the ForceCollision +// operator) the free flight is done under zero weight for +// the track, and the action is meant to accumulate the weight +// change for making this uninteracting flight, +// cumulatedWeightChange. +// When the track reaches the current volume boundary, its +// weight is restored with value : +// initialWeight * cumulatedWeightChange +// +//--------------------------------------------------------------- +// Initial version Nov. 2013 M. Verderi +#ifndef GateOptnForceFreeFlight_h +#define GateOptnForceFreeFlight_h 1 + +#include "G4ForceCondition.hh" +#include "G4ParticleChange.hh" // -- ยงยง should add a dedicated "weight change only" particle change +#include "G4VBiasingOperation.hh" +class G4ILawForceFreeFlight; + +class GateOptnForceFreeFlight : public G4VBiasingOperation { +public: + // -- Constructor : + GateOptnForceFreeFlight(G4String name); + // -- destructor: + virtual ~GateOptnForceFreeFlight(); + +public: + // -- Methods from G4VBiasingOperation interface: + // ------------------------------------------- + // -- Used: + virtual const G4VBiasingInteractionLaw * + ProvideOccurenceBiasingInteractionLaw(const G4BiasingProcessInterface *, + G4ForceCondition &); + virtual void AlongMoveBy(const G4BiasingProcessInterface *, const G4Step *, + G4double); + virtual G4VParticleChange * + ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, + const G4Step *, G4bool &); + // -- Unused: + virtual G4double DistanceToApplyOperation(const G4Track *, G4double, + G4ForceCondition *) { + return DBL_MAX; + } + virtual G4VParticleChange *GenerateBiasingFinalState(const G4Track *, + const G4Step *) { + return 0; + } + +public: + // -- Additional methods, specific to this class: + // ---------------------------------------------- + // -- return concrete type of interaction law: + G4ILawForceFreeFlight *GetForceFreeFlightLaw() { + return fForceFreeFlightInteractionLaw; + } + // -- initialization for weight: + // void ResetInitialTrackWeight(G4double w) {fInitialTrackWeight = w; + // fCumulatedWeightChange = 1.0;} + + void SetMinWeight(G4double w) { fMinWeight = w; } + void SetInitialWeight(G4double w) { fInitialWeight = w; } + G4double GetTrackWeight() { return fProposedWeight; } + void SetTrackWeight(G4double w) { fProposedWeight = w; } + void SetRussianRouletteProbability(G4double p) { + fRussianRouletteProbability = p; + } + void SetCountProcess(G4int N) { fCountProcess = N; } + void SetSurvivedToRR(G4bool b) { fSurvivedToRR = b; } + void SetRussianRouletteForWeights(G4bool rr) { + fRussianRouletteForWeights = rr; + } + G4bool GetSurvivedToRR() { return fSurvivedToRR; } + G4bool OperationComplete() const { return fOperationComplete; } + +private: + G4ILawForceFreeFlight *fForceFreeFlightInteractionLaw; + std::map<G4String, G4double> fWeightChange; + G4bool fRussianRouletteForWeights; + G4double fMinWeight, fRussianRouletteProbability, fInitialWeight; + G4ParticleChange fParticleChange; + G4bool fOperationComplete; + G4double fProposedWeight; + G4int fCountProcess; + G4bool fSurvivedToRR; + G4int fNbOfRussianRoulette; +}; + +#endif diff --git a/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp new file mode 100644 index 000000000..cf30c3a7d --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp @@ -0,0 +1,97 @@ +// +// ******************************************************************** +// * 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 GateOptnPairProdSplitting.cc +/// \brief Implementation of the GateOptnPairProdSplitting class + +#include "GateOptnPairProdSplitting.h" +#include "G4BiasingProcessInterface.hh" + +#include "G4ComptonScattering.hh" +#include "G4DynamicParticle.hh" +#include "G4Exception.hh" +#include "G4Gamma.hh" +#include "G4GammaConversion.hh" +#include "G4ParticleChange.hh" +#include "G4ParticleChangeForGamma.hh" +#include "G4ParticleChangeForLoss.hh" +#include "G4PhotoElectricEffect.hh" +#include "G4ProcessType.hh" +#include "G4RayleighScattering.hh" +#include "G4SystemOfUnits.hh" +#include "G4TrackStatus.hh" +#include "G4TrackingManager.hh" +#include "G4VEmProcess.hh" +#include <memory> + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptnPairProdSplitting::GateOptnPairProdSplitting(G4String name) + : GateOptnVGenericSplitting(name), fParticleChange() {} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptnPairProdSplitting::~GateOptnPairProdSplitting() {} + +G4VParticleChange *GateOptnPairProdSplitting::ApplyFinalStateBiasing( + const G4BiasingProcessInterface *callingProcess, const G4Track *track, + const G4Step *step, G4bool &) { + + G4int splittingFactor = ceil(fSplittingFactor); + G4double survivalProbabilitySplitting = + 1 - (splittingFactor - fSplittingFactor) / splittingFactor; + G4double particleWeight = 0; + + G4VParticleChange *processFinalState = + callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + + if (fSplittingFactor == 1 && fRussianRouletteForAngle == false) + return processFinalState; + TrackInitializationGamma(&fParticleChange, processFinalState, track, + fSplittingFactor); + + processFinalState->Clear(); + + G4int nCalls = 1; + while (nCalls <= splittingFactor) { + G4double splittingProbability = G4UniformRand(); + if (splittingProbability <= survivalProbabilitySplitting || + survivalProbabilitySplitting == 1) { + particleWeight = track->GetWeight() / fSplittingFactor; + G4VParticleChange *processFinalState = + callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + if (processFinalState->GetNumberOfSecondaries() >= 1) { + for (int i = 0; i < processFinalState->GetNumberOfSecondaries(); i++) { + G4Track *SecondaryTrack = processFinalState->GetSecondary(i); + SecondaryTrack->SetWeight(particleWeight); + fParticleChange.AddSecondary(SecondaryTrack); + } + } + } + nCalls++; + } + return &fParticleChange; +} diff --git a/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h b/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h new file mode 100644 index 000000000..ef4128a81 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h @@ -0,0 +1,52 @@ +// +// ******************************************************************** +// * 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 GateOptnPairProdSplitting.h +/// \brief Definition of the GateOptnPairProdSplitting class +// + +#ifndef GateOptnPairProdSplitting_h +#define GateOptnPairProdSplitting_h 1 + +#include "G4ParticleChange.hh" +#include "GateOptnVGenericSplitting.h" + +class GateOptnPairProdSplitting : public GateOptnVGenericSplitting { +public: + // -- Constructor : + GateOptnPairProdSplitting(G4String name); + + // -- destructor: + virtual ~GateOptnPairProdSplitting(); + + virtual G4VParticleChange * + ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, + const G4Step *, G4bool &); + + G4ParticleChange fParticleChange; +}; + +#endif diff --git a/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp new file mode 100644 index 000000000..bbfe8acff --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp @@ -0,0 +1,165 @@ +// +// ******************************************************************** +// * 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 GateOptnScatteredGammaSplitting.cc +/// \brief Implementation of the GateOptnScatteredGammaSplitting class + +#include "GateOptnScatteredGammaSplitting.h" +#include "G4BiasingProcessInterface.hh" + +#include "G4ComptonScattering.hh" +#include "G4DynamicParticle.hh" +#include "G4Exception.hh" +#include "G4Gamma.hh" +#include "G4GammaConversion.hh" +#include "G4ParticleChange.hh" +#include "G4ParticleChangeForGamma.hh" +#include "G4PhotoElectricEffect.hh" +#include "G4ProcessType.hh" +#include "G4RayleighScattering.hh" +#include "G4SystemOfUnits.hh" +#include "G4TrackStatus.hh" +#include "G4TrackingManager.hh" +#include "G4VEmProcess.hh" +#include "GateOptnVGenericSplitting.h" +#include <memory> + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptnScatteredGammaSplitting::GateOptnScatteredGammaSplitting(G4String name) + : GateOptnVGenericSplitting(name), fParticleChange() {} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptnScatteredGammaSplitting::~GateOptnScatteredGammaSplitting() {} + +G4VParticleChange *GateOptnScatteredGammaSplitting::ApplyFinalStateBiasing( + const G4BiasingProcessInterface *callingProcess, const G4Track *track, + const G4Step *step, G4bool &) { + + // Here we generate for the first the compton process, given that this + // function (ApplyFinalStateBiasing) is called when there is a compton + // interaction Then the interaction location of the compton process will + // always be the same + + const G4ThreeVector position = step->GetPostStepPoint()->GetPosition(); + G4int splittingFactor = ceil(fSplittingFactor); + G4double survivalProbabilitySplitting = + 1 - (splittingFactor - fSplittingFactor) / splittingFactor; + G4double gammaWeight = 0; + + G4VParticleChange *processFinalState = + callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + // In case we don't want to split (a bit faster) i.e no biaising or no + // splitting low weights particles. + + if (fSplittingFactor == 1 && fRussianRouletteForAngle == false) + return processFinalState; + + TrackInitializationGamma(&fParticleChange, processFinalState, track, + fSplittingFactor); + processFinalState->Clear(); + + // There is here the biasing process : + // Since G4VParticleChange class does not allow to retrieve scattered gamma + // information, we need to cast the type G4ParticleChangeForGamma to the + // G4VParticleChange object. We then call the process (biasWrapper(compt)) + // fSplittingFactor times (Here, the difference with the other version of + // splitting is the primary particle will be killed and its weight does not + // count) to generate, at last, fSplittingFactor gamma according to the + // compton interaction process. If the gamma track is ok regarding the + // russian roulette algorithm (no russian roulette + //, or within the acceptance angle, or not killed by the RR process), we add + // it to the primary track. + // If an electron is generated (above the range cut), we also generate it. + // A tremendous advantage is there is no need to use by ourself Klein-Nishina + // formula or other. So, if the physics list used takes into account the + // doppler broadening or other fine effects, this will be also taken into + // account by the MC simulation. PS : The first gamma is then the primary + // particle, but all the other splitted particle (electron of course AND + // gamma) must be considered as secondary particles, even though generated + // gamma will not be cut here by the applied cut. + + G4int nCalls = 1; + while (nCalls <= splittingFactor) { + gammaWeight = track->GetWeight() / fSplittingFactor; + G4VParticleChange *processGammaSplittedFinalState = + callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + G4ParticleChangeForGamma *castedProcessGammaSplittedFinalState = + (G4ParticleChangeForGamma *)processGammaSplittedFinalState; + const G4ThreeVector momentum = + castedProcessGammaSplittedFinalState->GetProposedMomentumDirection(); + G4double energy = + castedProcessGammaSplittedFinalState->GetProposedKineticEnergy(); + G4double splittingProbability = G4UniformRand(); + + if (splittingProbability <= survivalProbabilitySplitting || + survivalProbabilitySplitting == 1) { + if (fRussianRouletteForAngle == true) { + G4double weightToApply = RussianRouletteForAngleSurvival( + castedProcessGammaSplittedFinalState + ->GetProposedMomentumDirection(), + fVectorDirector, fMaxTheta, fSplittingFactor); + if (weightToApply != 0) { + gammaWeight = gammaWeight * weightToApply; + G4Track *gammaTrack = new G4Track(*track); + gammaTrack->SetWeight(gammaWeight); + gammaTrack->SetKineticEnergy(energy); + gammaTrack->SetMomentumDirection(momentum); + gammaTrack->SetPosition(position); + fParticleChange.AddSecondary(gammaTrack); + if (processGammaSplittedFinalState->GetNumberOfSecondaries() == 1) { + G4Track *electronTrack = + processGammaSplittedFinalState->GetSecondary(0); + electronTrack->SetWeight(gammaWeight); + fParticleChange.AddSecondary(electronTrack); + } + } + } + + else { + G4Track *gammaTrack = new G4Track(*track); + gammaTrack->SetWeight(gammaWeight); + gammaTrack->SetKineticEnergy(energy); + gammaTrack->SetMomentumDirection(momentum); + gammaTrack->SetPosition(position); + fParticleChange.AddSecondary(gammaTrack); + if (processGammaSplittedFinalState->GetNumberOfSecondaries() == 1) { + G4Track *electronTrack = + processGammaSplittedFinalState->GetSecondary(0); + electronTrack->SetWeight(gammaWeight); + fParticleChange.AddSecondary(electronTrack); + } + } + } + nCalls++; + processGammaSplittedFinalState->Clear(); + castedProcessGammaSplittedFinalState->Clear(); + } + return &fParticleChange; +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h b/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h new file mode 100644 index 000000000..ff0a6b623 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h @@ -0,0 +1,53 @@ +// +// ******************************************************************** +// * 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 GateOptnScatteredGammaSplitting.h +/// \brief Definition of the GateOptnScatteredGammaSplitting class +// + +#ifndef GateOptnScatteredGammaSplitting_h +#define GateOptnScatteredGammaSplitting_h 1 + +#include "G4ParticleChange.hh" +#include "G4VBiasingOperation.hh" +#include "GateOptnVGenericSplitting.h" + +class GateOptnScatteredGammaSplitting : public GateOptnVGenericSplitting { +public: + // -- Constructor : + GateOptnScatteredGammaSplitting(G4String name); + + // -- destructor: + virtual ~GateOptnScatteredGammaSplitting(); + + virtual G4VParticleChange * + ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, + const G4Step *, G4bool &); + + G4ParticleChange fParticleChange; +}; + +#endif diff --git a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp new file mode 100644 index 000000000..ba894cf04 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp @@ -0,0 +1,105 @@ +// +// ******************************************************************** +// * 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 GateOptnVGenericSplitting.cc +/// \brief Implementation of the GateOptnVGenericSplitting class + +#include "GateOptnVGenericSplitting.h" +#include "G4BiasingProcessInterface.hh" + +#include "G4ComptonScattering.hh" +#include "G4DynamicParticle.hh" +#include "G4Exception.hh" +#include "G4Gamma.hh" +#include "G4GammaConversion.hh" +#include "G4ParticleChange.hh" +#include "G4ParticleChangeForGamma.hh" +#include "G4ParticleChangeForLoss.hh" +#include "G4PhotoElectricEffect.hh" +#include "G4ProcessType.hh" +#include "G4RayleighScattering.hh" +#include "G4SystemOfUnits.hh" +#include "G4TrackStatus.hh" +#include "G4TrackingManager.hh" +#include "G4VEmProcess.hh" +#include <memory> + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptnVGenericSplitting:: + GateOptnVGenericSplitting(G4String name) + : G4VBiasingOperation(name), fParticleChange() {} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptnVGenericSplitting:: + ~GateOptnVGenericSplitting() {} + + +void GateOptnVGenericSplitting::TrackInitializationChargedParticle(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split) { + + G4ParticleChangeForLoss* processFinalStateForLoss =( G4ParticleChangeForLoss* ) processFinalState ; + particleChange->Initialize(*track); + particleChange->ProposeTrackStatus(processFinalStateForLoss->GetTrackStatus() ); + particleChange->ProposeEnergy(processFinalStateForLoss->GetProposedKineticEnergy() ); + particleChange->ProposeMomentumDirection(processFinalStateForLoss->GetProposedMomentumDirection()); + particleChange->SetNumberOfSecondaries(fSplittingFactor); + particleChange->SetSecondaryWeightByProcess(true); + processFinalStateForLoss->Clear(); +} + +void GateOptnVGenericSplitting::TrackInitializationGamma(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split) { + G4ParticleChangeForGamma* processFinalStateForGamma = (G4ParticleChangeForGamma *)processFinalState; + particleChange->Initialize(*track); + particleChange->ProposeTrackStatus(processFinalStateForGamma->GetTrackStatus() ); + particleChange->ProposeEnergy(processFinalStateForGamma->GetProposedKineticEnergy() ); + particleChange->ProposeMomentumDirection(processFinalStateForGamma->GetProposedMomentumDirection() ); + particleChange->SetNumberOfSecondaries(fSplittingFactor); + particleChange->SetSecondaryWeightByProcess(true); + processFinalStateForGamma->Clear(); + + +} + +G4double GateOptnVGenericSplitting::RussianRouletteForAngleSurvival(G4ThreeVector dir,G4ThreeVector vectorDirector,G4double maxTheta,G4double split){ +G4double cosTheta =vectorDirector * dir; +G4double theta = std::acos(cosTheta); +G4double weightToApply = 1; +if (theta > maxTheta){ + G4double probability = G4UniformRand(); + if (probability <= 1 / split) { + weightToApply = split; + } + else{ + weightToApply = 0; + } +} +return weightToApply; + +} + + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h new file mode 100644 index 000000000..d7cebd78a --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h @@ -0,0 +1,122 @@ +// +// ******************************************************************** +// * 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 GateOptnVGenericSplitting.h +/// \brief Definition of the GateOptnVGenericSplitting class +// + +#ifndef GateOptnVGenericSplitting_h +#define GateOptnVGenericSplitting_h 1 + +#include "G4ParticleChange.hh" +#include "G4VBiasingOperation.hh" + +class GateOptnVGenericSplitting : public G4VBiasingOperation { +public: + // -- Constructor : + GateOptnVGenericSplitting(G4String name); + + // -- destructor: + virtual ~GateOptnVGenericSplitting(); + +public: + // ---------------------------------------------- + // -- Methods from G4VBiasingOperation interface: + // ---------------------------------------------- + // -- Unused: + virtual const G4VBiasingInteractionLaw * + ProvideOccurenceBiasingInteractionLaw(const G4BiasingProcessInterface *, + G4ForceCondition &) { + return 0; + } + + // --Used: + virtual G4VParticleChange * + ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, + const G4Step *, G4bool &) { + return 0; + }; + + // -- Unsued: + virtual G4double DistanceToApplyOperation(const G4Track *, G4double, + G4ForceCondition *) { + return DBL_MAX; + } + virtual G4VParticleChange *GenerateBiasingFinalState(const G4Track *, + const G4Step *) { + return 0; + } + + +// ---------------------------------------------- +// -- Methods for the generic splitting +// ---------------------------------------------- + +void TrackInitializationChargedParticle(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split); +void TrackInitializationGamma(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split); +static G4double RussianRouletteForAngleSurvival(G4ThreeVector dir,G4ThreeVector vectorDirector,G4double maxTheta,G4double split); + + +public: + // ---------------------------------------------- + // -- Additional methods, specific to this class: + // ---------------------------------------------- + // -- Splitting factor: + void SetSplittingFactor(G4double splittingFactor) { + fSplittingFactor = splittingFactor; + } + G4double GetSplittingFactor() const { return fSplittingFactor; } + + void SetRussianRouletteForAngle(G4bool russianRoulette) { + fRussianRouletteForAngle = russianRoulette; + } + + void SetVectorDirector(G4ThreeVector vectorDirector) { + fVectorDirector = vectorDirector; + } + + void SetRotationMatrix(G4RotationMatrix rot) { fRot = rot; } + + G4ThreeVector GetVectorDirector() const { return fVectorDirector; } + + void SetMaxTheta(G4double maxTheta) { fMaxTheta = maxTheta; } + + G4double GetMaxTheta() const { return fMaxTheta; } + + G4VParticleChange *GetParticleChange() { + G4VParticleChange *particleChange = &fParticleChange; + return particleChange; + } + + G4double fSplittingFactor; + G4ParticleChange fParticleChange; + G4bool fRussianRouletteForAngle; + G4ThreeVector fVectorDirector; + G4double fMaxTheta; + G4RotationMatrix fRot; +}; + +#endif diff --git a/core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp b/core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp new file mode 100644 index 000000000..ce9ff2324 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp @@ -0,0 +1,112 @@ +// +// ******************************************************************** +// * 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 GateOptneBremSplitting.cc +/// \brief Implementation of the GateOptneBremSplitting class + +#include "GateOptneBremSplitting.h" +#include "G4BiasingProcessInterface.hh" + +#include "G4ComptonScattering.hh" +#include "G4DynamicParticle.hh" +#include "G4Exception.hh" +#include "G4Gamma.hh" +#include "G4GammaConversion.hh" +#include "G4ParticleChange.hh" +#include "G4ParticleChangeForGamma.hh" +#include "G4ParticleChangeForLoss.hh" +#include "G4PhotoElectricEffect.hh" +#include "G4ProcessType.hh" +#include "G4RayleighScattering.hh" +#include "G4SystemOfUnits.hh" +#include "G4TrackStatus.hh" +#include "G4TrackingManager.hh" +#include "G4VEmProcess.hh" +#include "GateOptnVGenericSplitting.h" +#include <memory> + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptneBremSplitting::GateOptneBremSplitting(G4String name) + : GateOptnVGenericSplitting(name), fParticleChange() {} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptneBremSplitting::~GateOptneBremSplitting() {} + +G4VParticleChange *GateOptneBremSplitting::ApplyFinalStateBiasing( + const G4BiasingProcessInterface *callingProcess, const G4Track *track, + const G4Step *step, G4bool &) { + + G4int splittingFactor = ceil(fSplittingFactor); + G4double survivalProbabilitySplitting = + 1 - (splittingFactor - fSplittingFactor) / splittingFactor; + G4VParticleChange *processFinalState = + callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + if (fSplittingFactor == 1) + return processFinalState; + if (processFinalState->GetNumberOfSecondaries() == 0) + return processFinalState; + + TrackInitializationChargedParticle(&fParticleChange, processFinalState, track, + fSplittingFactor); + + processFinalState->Clear(); + + G4int nCalls = 1; + while (nCalls <= fSplittingFactor) { + G4double splittingProbability = G4UniformRand(); + if (splittingProbability <= survivalProbabilitySplitting || + survivalProbabilitySplitting == 1) { + processFinalState = + callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); + if (processFinalState->GetNumberOfSecondaries() >= 1) { + for (int i = 0; i < processFinalState->GetNumberOfSecondaries(); i++) { + G4double gammaWeight = track->GetWeight() / fSplittingFactor; + G4Track *gammaTrack = processFinalState->GetSecondary(i); + if (fRussianRouletteForAngle == true) { + G4double weightToApply = RussianRouletteForAngleSurvival( + gammaTrack->GetMomentumDirection(), fVectorDirector, fMaxTheta, + fSplittingFactor); + if (weightToApply != 0) { + gammaWeight = gammaWeight * weightToApply; + gammaTrack->SetWeight(gammaWeight); + fParticleChange.AddSecondary(gammaTrack); + } + } else { + gammaTrack->SetWeight(gammaWeight); + fParticleChange.AddSecondary(gammaTrack); + } + } + } + processFinalState->Clear(); + } + nCalls++; + } + return &fParticleChange; +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptneBremSplitting.h b/core/opengate_core/opengate_lib/GateOptneBremSplitting.h new file mode 100644 index 000000000..47319e536 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptneBremSplitting.h @@ -0,0 +1,54 @@ +// +// ******************************************************************** +// * 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 GateOptneBremSplitting.h +/// \brief Definition of the GateOptneBremSplitting class +// + +#ifndef GateOptneBremSplitting_h +#define GateOptneBremSplitting_h 1 + +#include "G4ParticleChange.hh" +#include "G4VBiasingOperation.hh" +#include "GateOptnVGenericSplitting.h" + +class GateOptneBremSplitting : public GateOptnVGenericSplitting { +public: + // -- Constructor : + GateOptneBremSplitting(G4String name); + + // -- destructor: + virtual ~GateOptneBremSplitting(); + +public: + virtual G4VParticleChange * + ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, + const G4Step *, G4bool &); + + G4ParticleChange fParticleChange; +}; + +#endif diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp new file mode 100644 index 000000000..13c5b3cfb --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp @@ -0,0 +1,319 @@ +// +// ******************************************************************** +// * 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 GateOptrComptPseudoTransportationActor.cc +/// \brief Implementation of the GateOptrComptPseudoTransportationActor class + +#include "GateHelpersDict.h" +#include "GateHelpersImage.h" + +#include "CLHEP/Units/SystemOfUnits.h" +#include "G4BiasingProcessInterface.hh" +#include "G4Electron.hh" +#include "G4EmCalculator.hh" +#include "G4Exception.hh" +#include "G4Gamma.hh" +#include "G4LogicalVolumeStore.hh" +#include "G4ParticleTable.hh" +#include "G4PhysicalVolumeStore.hh" +#include "G4Positron.hh" +#include "G4ProcessManager.hh" +#include "G4ProcessVector.hh" +#include "G4RunManager.hh" +#include "G4TrackStatus.hh" +#include "G4UImanager.hh" +#include "G4UserTrackingAction.hh" +#include "G4VEmProcess.hh" +#include "G4eplusAnnihilation.hh" +#include "GateOptnPairProdSplitting.h" +#include "GateOptnScatteredGammaSplitting.h" +#include "GateOptneBremSplitting.h" +#include "GateOptrComptPseudoTransportationActor.h" +#include "G4UImanager.hh" +#include "G4eplusAnnihilation.hh" +#include "GateOptnVGenericSplitting.h" + + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +GateOptrComptPseudoTransportationActor::GateOptrComptPseudoTransportationActor( + py::dict &user_info) + : G4VBiasingOperator("ComptSplittingOperator"), + GateVActor(user_info, false) { + fMotherVolumeName = DictGetStr(user_info, "mother"); + fAttachToLogicalHolder = DictGetBool(user_info, "attach_to_logical_holder"); + fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); + fRelativeMinWeightOfParticle = + DictGetDouble(user_info, "relative_min_weight_of_particle"); + // Since the russian roulette uses as a probability 1/splitting, we need to + // have a double, but the splitting factor provided by the user is logically + // an int, so we need to change the type. + fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); + fRussianRouletteForAngle = + DictGetBool(user_info, "russian_roulette_for_angle"); + fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); + fRussianRouletteForWeights = + DictGetBool(user_info, "russian_roulette_for_weights"); + fMaxTheta = DictGetDouble(user_info, "max_theta"); + fFreeFlightOperation = new GateOptnForceFreeFlight("freeFlightOperation"); + fScatteredGammaSplittingOperation = + new GateOptnScatteredGammaSplitting("comptSplittingOperation"); + feBremSplittingOperation = + new GateOptneBremSplitting("eBremSplittingOperation"); + fPairProdSplittingOperation = + new GateOptnPairProdSplitting("PairProdSplittingOperation"); + fActions.insert("StartSimulationAction"); + fActions.insert("SteppingAction"); + fActions.insert("BeginOfEventAction"); + isSplitted = false; +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +void GateOptrComptPseudoTransportationActor::AttachAllLogicalDaughtersVolumes( + G4LogicalVolume *volume) { + if (fAttachToLogicalHolder == false) { + if (volume->GetName() != G4LogicalVolumeStore::GetInstance() + ->GetVolume(fMotherVolumeName) + ->GetName()) { + AttachTo(volume); + } + } + if (fAttachToLogicalHolder == true) { + AttachTo(volume); + } + G4int nbOfDaughters = volume->GetNoDaughters(); + if (nbOfDaughters > 0) { + for (int i = 0; i < nbOfDaughters; i++) { + G4String LogicalVolumeName = + volume->GetDaughter(i)->GetLogicalVolume()->GetName(); + G4LogicalVolume *logicalDaughtersVolume = + volume->GetDaughter(i)->GetLogicalVolume(); + AttachAllLogicalDaughtersVolumes(logicalDaughtersVolume); + if (!(std::find(fNameOfBiasedLogicalVolume.begin(), + fNameOfBiasedLogicalVolume.end(), + LogicalVolumeName) != fNameOfBiasedLogicalVolume.end())) + fNameOfBiasedLogicalVolume.push_back( + volume->GetDaughter(i)->GetLogicalVolume()->GetName()); + } + } +} + +void GateOptrComptPseudoTransportationActor::StartSimulationAction() { + G4LogicalVolume *biasingVolume = + G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + + // Here we need to attach all the daughters and daughters of daughters (...) + // to the biasing operator. To do that, I use the function + // AttachAllLogicalDaughtersVolumes. + AttachAllLogicalDaughtersVolumes(biasingVolume); + + fScatteredGammaSplittingOperation->SetSplittingFactor(fSplittingFactor); + + fScatteredGammaSplittingOperation->SetMaxTheta(fMaxTheta); + fScatteredGammaSplittingOperation->SetRussianRouletteForAngle( + fRussianRouletteForAngle); + + feBremSplittingOperation->SetSplittingFactor(fSplittingFactor); + feBremSplittingOperation->SetMaxTheta(fMaxTheta); + feBremSplittingOperation->SetRussianRouletteForAngle( + fRussianRouletteForAngle); + + fPairProdSplittingOperation->SetSplittingFactor(fSplittingFactor); + + fFreeFlightOperation->SetRussianRouletteForWeights(fRussianRouletteForWeights); + + +} + +void GateOptrComptPseudoTransportationActor::StartRun() { + + // The way to behave of the russian roulette is the following : + // we provide a vector director and the theta angle acceptance, where theta = + // 0 is a vector colinear to the vector director Then if the track generated + // is on the acceptance angle, we add it to the primary track, and if it's not + // the case, we launch the russian roulette + if (fRotationVectorDirector) { + G4VPhysicalVolume *physBiasingVolume = + G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + auto rot = physBiasingVolume->GetObjectRotationValue(); + fVectorDirector = rot * fVectorDirector; + fScatteredGammaSplittingOperation->SetRotationMatrix(rot); + feBremSplittingOperation->SetRotationMatrix(rot); + } + + fScatteredGammaSplittingOperation->SetVectorDirector(fVectorDirector); + feBremSplittingOperation->SetVectorDirector(fVectorDirector); +} + +void GateOptrComptPseudoTransportationActor::SteppingAction(G4Step *step) { + G4String creationProcessName = "None"; + if (step->GetTrack()->GetCreatorProcess() != 0){ + creationProcessName = step->GetTrack()->GetCreatorProcess()->GetProcessName(); + + } + +if ((fIsFirstStep) && (fRussianRouletteForAngle)){ + G4String LogicalVolumeNameOfCreation = step->GetTrack()->GetLogicalVolumeAtVertex()->GetName(); + if (std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeNameOfCreation ) !=fNameOfBiasedLogicalVolume.end()){ + if (creationProcessName == "biasWrapper(annihil)"){ + auto dir = step->GetPreStepPoint()->GetMomentumDirection(); + G4double w = GateOptnVGenericSplitting::RussianRouletteForAngleSurvival(dir,fVectorDirector,fMaxTheta,fSplittingFactor); + if (w == 0) + { + step->GetTrack()->SetTrackStatus(fStopAndKill); + } + else { + step->GetTrack()->SetWeight(step->GetTrack()->GetWeight() * w); + } + } + } +} + +if ((isSplitted == true) && (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary)) { + G4String LogicalVolumeName = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); + if (!(std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeName ) !=fNameOfBiasedLogicalVolume.end()) + && (LogicalVolumeName != fMotherVolumeName)) { + step->GetTrack()->SetTrackStatus(fStopAndKill); + isSplitted = false; + } +} + +fIsFirstStep = false; +} + +void GateOptrComptPseudoTransportationActor::BeginOfEventAction( + const G4Event *event) { + fKillOthersParticles = false; + fPassedByABiasedVolume = false; + fEventID = event->GetEventID(); + fEventIDKineticEnergy = + event->GetPrimaryVertex(0)->GetPrimary(0)->GetKineticEnergy(); +} + +void GateOptrComptPseudoTransportationActor::StartTracking( + const G4Track *track) { + G4String creationProcessName = "None"; + fIsFirstStep = true; + if (track->GetCreatorProcess() != 0){ + creationProcessName = track->GetCreatorProcess()->GetProcessName(); + } + + + if (track->GetParticleDefinition()->GetParticleName() == "gamma"){ + G4String LogicalVolumeNameOfCreation = track->GetLogicalVolumeAtVertex()->GetName(); + if (std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeNameOfCreation ) !=fNameOfBiasedLogicalVolume.end()){ + + fInitialWeight = track->GetWeight(); + fFreeFlightOperation->SetInitialWeight(fInitialWeight); + } + } +} + +// For the following operation the idea is the following : +// All the potential photon processes are biased. If a particle undergoes a +// compton interaction, we splitted it (ComptonSplittingForTransportation +// operation) and the particle generated are pseudo-transported with the +// ForceFreeFLight operation +// Since the occurence Biaising operation is called at the beginning of each +// track, and propose a different way to track the particle +//(with modified physics), it here returns other thing than 0 if we want to +// pseudo-transport the particle, so if its creatorProcess is the modified +// compton interaction + +G4VBiasingOperation * +GateOptrComptPseudoTransportationActor::ProposeOccurenceBiasingOperation( + const G4Track *track, const G4BiasingProcessInterface *callingProcess) { + + if (track->GetParticleDefinition()->GetParticleName() == "gamma") { + G4String LogicalVolumeNameOfCreation = + track->GetLogicalVolumeAtVertex()->GetName(); + if (std::find(fNameOfBiasedLogicalVolume.begin(), + fNameOfBiasedLogicalVolume.end(), + LogicalVolumeNameOfCreation) != + fNameOfBiasedLogicalVolume.end()) { + fFreeFlightOperation->SetMinWeight(fInitialWeight / + fRelativeMinWeightOfParticle); + fFreeFlightOperation->SetTrackWeight(track->GetWeight()); + fFreeFlightOperation->SetCountProcess(0); + return fFreeFlightOperation; + } + } + return 0; +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +// Here we call the final state biasing operation called if one of the biased +// interaction (all photon interaction here) occurs. +// That's why we need here to apply some conditions to just split the initial +// track. + +G4VBiasingOperation * +GateOptrComptPseudoTransportationActor::ProposeFinalStateBiasingOperation( + const G4Track *track, const G4BiasingProcessInterface *callingProcess) { + G4String particleName = track->GetParticleDefinition()->GetParticleName(); + + G4String LogicalVolumeNameOfCreation = + track->GetLogicalVolumeAtVertex()->GetName(); + G4String CreationProcessName = ""; + if (track->GetCreatorProcess() != 0) { + CreationProcessName = track->GetCreatorProcess()->GetProcessName(); + } + + if (track->GetParticleDefinition()->GetParticleName() == "gamma") { + if (std::find(fNameOfBiasedLogicalVolume.begin(), + fNameOfBiasedLogicalVolume.end(), + LogicalVolumeNameOfCreation) != + fNameOfBiasedLogicalVolume.end()) { + return callingProcess->GetCurrentOccurenceBiasingOperation(); + } + } + + if (((CreationProcessName != "biasWrapper(conv)") && + (CreationProcessName != "biasWrapper(compt)")) && + (callingProcess->GetWrappedProcess()->GetProcessName() == "eBrem")) { + return feBremSplittingOperation; + } + + if (!(std::find(fCreationProcessNameList.begin(), fCreationProcessNameList.end(),CreationProcessName) != fCreationProcessNameList.end())){ + if (callingProcess->GetWrappedProcess()->GetProcessName() == "compt"){ + + isSplitted = true; + return fScatteredGammaSplittingOperation; + } + if ((callingProcess->GetWrappedProcess()->GetProcessName() == "conv")) { + return fPairProdSplittingOperation; + } + } + return 0; +} + +void GateOptrComptPseudoTransportationActor::EndTracking() { + isSplitted = false; +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h new file mode 100644 index 000000000..629d31b69 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h @@ -0,0 +1,137 @@ +// +// ******************************************************************** +// * 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 GateOptrComptPseudoTransportationActor.h +/// \brief Definition of the GateOptrComptPseudoTransportationActor class +#ifndef GateOptrComptPseudoTransportationActor_h +#define GateOptrComptPseudoTransportationActor_h 1 + +#include "G4EmCalculator.hh" +#include "G4VBiasingOperator.hh" +#include "GateOptnForceFreeFlight.h" +#include "GateOptnPairProdSplitting.h" +#include "GateOptneBremSplitting.h" + +#include "GateVActor.h" +#include <iostream> +#include <pybind11/stl.h> +namespace py = pybind11; + +class GateOptnScatteredGammaSplitting; + +class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, + public GateVActor { +public: + GateOptrComptPseudoTransportationActor(py::dict &user_info); + virtual ~GateOptrComptPseudoTransportationActor() {} + +public: + // ------------------------- + // Optional from base class: + // ------------------------- + // -- Call at run start: + // virtual void BeginOfRunAction(const G4Run *run); + + // virtual void SteppingAction(G4Step* step); + + // -- Call at each track starting: + // virtual void PreUserTrackingAction( const G4Track* track ); + + G4double fSplittingFactor; + G4double fInitialWeight; + G4double fRelativeMinWeightOfParticle; + G4double fWeightThreshold; + G4bool fBiasPrimaryOnly; + G4bool fBiasOnlyOnce; + G4int fNInteractions = 0; + G4bool fRussianRouletteForAngle; + G4bool fRussianRouletteForWeights; + G4bool fRotationVectorDirector; + G4ThreeVector fVectorDirector; + G4double fMaxTheta; + G4bool isSplitted; + G4int NbOfTrack = 0; + G4int NbOfProbe = 1; + G4double weight = 0; + G4bool fKillOthersParticles = false; + G4bool fUseProbes = false; + G4bool fSurvivedRR = false; + G4bool fAttachToLogicalHolder = true; + G4bool fPassedByABiasedVolume = false; + G4double fKineticEnergyAtTheEntrance; + G4int ftrackIDAtTheEntrance; + G4int fEventID; + G4double fEventIDKineticEnergy; + G4bool ftestbool= false; + G4bool fIsFirstStep = false; + const G4VProcess* fAnnihilation =nullptr; + + std::vector<G4String> fNameOfBiasedLogicalVolume = {}; + std::vector<G4int> v_EventID = {}; + std::vector<G4String> fCreationProcessNameList = {"biasWrapper(compt)", "biasWrapper(eBrem)","biasWrapper(annihil)"}; + + // Unused but mandatory + + virtual void StartSimulationAction(); + virtual void StartRun(); + virtual void StartTracking(const G4Track *); + virtual void SteppingAction(G4Step *); + virtual void BeginOfEventAction(const G4Event *); + virtual void EndTracking(); + +protected: + // ----------------------------- + // -- Mandatory from base class: + // ----------------------------- + // -- Unused: + void AttachAllLogicalDaughtersVolumes(G4LogicalVolume *); + virtual G4VBiasingOperation *ProposeNonPhysicsBiasingOperation( + const G4Track * /* track */, + const G4BiasingProcessInterface * /* callingProcess */) { + return 0; + } + + // -- Used: + virtual G4VBiasingOperation *ProposeOccurenceBiasingOperation( + const G4Track * /* track */, + const G4BiasingProcessInterface * /* callingProcess */); + + virtual G4VBiasingOperation *ProposeFinalStateBiasingOperation( + const G4Track *track, const G4BiasingProcessInterface *callingProcess); + +private: + // -- Avoid compiler complaining for (wrong) method shadowing, + // -- this is because other virtual method with same name exists. + using G4VBiasingOperator::OperationApplied; + +private: + GateOptnForceFreeFlight *fFreeFlightOperation; + GateOptnScatteredGammaSplitting *fScatteredGammaSplittingOperation; + GateOptneBremSplitting *feBremSplittingOperation; + GateOptnPairProdSplitting *fPairProdSplittingOperation; +}; + +#endif diff --git a/core/opengate_core/opengate_lib/GateSPSEneDistribution.cpp b/core/opengate_core/opengate_lib/GateSPSEneDistribution.cpp index dcc864f01..648050ff6 100644 --- a/core/opengate_core/opengate_lib/GateSPSEneDistribution.cpp +++ b/core/opengate_core/opengate_lib/GateSPSEneDistribution.cpp @@ -8,7 +8,11 @@ #include "GateSPSEneDistribution.h" #include "G4UnitsTable.hh" #include "GateHelpers.h" +#include "fmt/color.h" +#include "fmt/core.h" #include <Randomize.hh> +#include <cstdlib> +#include <limits> // Parts copied from GateSPSEneDistribution.cc @@ -23,8 +27,12 @@ G4double GateSPSEneDistribution::VGenerateOne(G4ParticleDefinition *d) { GenerateFromCDF(); else if (GetEnergyDisType() == "range") GenerateRange(); - else if (GetEnergyDisType() == "spectrum_lines") + else if (GetEnergyDisType() == "spectrum_discrete") GenerateSpectrumLines(); + else if (GetEnergyDisType() == "spectrum_histogram") + GenerateSpectrumHistogram(); + else if (GetEnergyDisType() == "spectrum_histogram_linear") + GenerateSpectrumHistogramInterpolated(); else fParticleEnergy = G4SPSEneDistribution::GenerateOne(d); return fParticleEnergy; @@ -109,9 +117,46 @@ void GateSPSEneDistribution::GenerateRange() { } void GateSPSEneDistribution::GenerateSpectrumLines() { - auto x = G4UniformRand(); + auto const i = IndexForProbability(G4UniformRand()); + fParticleEnergy = fEnergyCDF[i]; +} + +void GateSPSEneDistribution::GenerateSpectrumHistogram() { + auto const i = IndexForProbability(G4UniformRand()); + fParticleEnergy = G4RandFlat::shoot(fEnergyCDF[i], fEnergyCDF[i + 1]); +} + +void GateSPSEneDistribution::GenerateSpectrumHistogramInterpolated() { + auto const i = IndexForProbability(G4UniformRand()); + + auto const a = (fEnergyCDF[i] + fEnergyCDF[i + 1]) / 2; + auto const b = (fEnergyCDF[i + 1] + fEnergyCDF[i + 2]) / 2; + auto const d = fProbabilityCDF[i + 1] - fProbabilityCDF[i]; + + if (std::abs(d) < std::numeric_limits<double>::epsilon()) { + fParticleEnergy = G4RandFlat::shoot(a, b); + } else { + auto const alpha = d / (b - a); + auto const beta = fProbabilityCDF[i] - alpha * a; + auto const norm = .5 * alpha * (b * b - a * a) + beta * (b - a); + auto const p = G4UniformRand(); // p in ]0, 1[ + // [comment from GATE 9] inversion transform sampling + auto const sqrtDelta = std::sqrt((alpha * a + beta) * (alpha * a + beta) + + 2 * alpha * norm * p); + auto const x = (-beta + sqrtDelta) / alpha; + if ((x - a) * (x - b) <= 0) + fParticleEnergy = x; + else + fParticleEnergy = (-beta - sqrtDelta) / alpha; + } +} + +std::size_t GateSPSEneDistribution::IndexForProbability(double p) const { + // p in ]0, 1[ + // see + // https://geant4-forum.web.cern.ch/t/what-is-the-range-of-numbers-generated-by-g4uniformrand/5187 auto i = 0; - while (x >= (fProbabilityCDF[i])) + while (p >= (fProbabilityCDF[i])) // TODO -> std:: i++; - fParticleEnergy = fEnergyCDF[i]; + return i; } diff --git a/core/opengate_core/opengate_lib/GateSPSEneDistribution.h b/core/opengate_core/opengate_lib/GateSPSEneDistribution.h index fb4a02be1..59c565520 100644 --- a/core/opengate_core/opengate_lib/GateSPSEneDistribution.h +++ b/core/opengate_core/opengate_lib/GateSPSEneDistribution.h @@ -13,7 +13,7 @@ class GateSPSEneDistribution : public G4SPSEneDistribution { public: - virtual ~GateSPSEneDistribution() {} + virtual ~GateSPSEneDistribution() = default; void GenerateFromCDF(); @@ -27,6 +27,10 @@ class GateSPSEneDistribution : public G4SPSEneDistribution { void GenerateSpectrumLines(); + void GenerateSpectrumHistogram(); + + void GenerateSpectrumHistogramInterpolated(); + // Cannot inherit from GenerateOne virtual G4double VGenerateOne(G4ParticleDefinition *); @@ -34,6 +38,9 @@ class GateSPSEneDistribution : public G4SPSEneDistribution { std::vector<double> fProbabilityCDF; std::vector<double> fEnergyCDF; + +private: + std::size_t IndexForProbability(double p) const; }; #endif // GateSPSEneDistribution_h diff --git a/core/opengate_core/opengate_lib/GateSourceManager.cpp b/core/opengate_core/opengate_lib/GateSourceManager.cpp index d183567e9..d0772fe15 100644 --- a/core/opengate_core/opengate_lib/GateSourceManager.cpp +++ b/core/opengate_core/opengate_lib/GateSourceManager.cpp @@ -241,6 +241,9 @@ void GateSourceManager::PrepareRunToStart(int run_id) { : std::to_string(G4Threading::G4GetThreadId())); } + + + void GateSourceManager::PrepareNextSource() { auto &l = fThreadLocalData.Get(); l.fNextActiveSource = nullptr; @@ -255,6 +258,7 @@ void GateSourceManager::PrepareNextSource() { l.fNextSimulationTime = t; } } + // If no next time in the current interval, active source is NULL } diff --git a/core/opengate_core/opengate_lib/GateSourceManager.h b/core/opengate_core/opengate_lib/GateSourceManager.h index 893ced872..1b559a07b 100644 --- a/core/opengate_core/opengate_lib/GateSourceManager.h +++ b/core/opengate_core/opengate_lib/GateSourceManager.h @@ -64,6 +64,22 @@ class GateSourceManager : public G4VUserPrimaryGeneratorAction { // Return a source GateVSource *FindSourceByName(std::string name) const; + + + G4String GetActiveSourceName(){ + auto &l = fThreadLocalData.Get(); + if (l.fNextActiveSource !=0){ + G4String name = l.fNextActiveSource->fName; + return name; + } + return "None"; + } + + void SetActiveSourcebyName(G4String sourceName){ + auto &l = fThreadLocalData.Get(); + auto* source = FindSourceByName(sourceName); + l.fNextActiveSource = source; + } // [available on py side] start the simulation, master thread only void StartMasterThread(); diff --git a/core/opengate_core/opengate_lib/GateVActor.cpp b/core/opengate_core/opengate_lib/GateVActor.cpp index 6ac2dfaf3..cfd6e7873 100644 --- a/core/opengate_core/opengate_lib/GateVActor.cpp +++ b/core/opengate_core/opengate_lib/GateVActor.cpp @@ -80,8 +80,8 @@ bool GateVActor::GetWriteToDisk(std::string outputName) { } catch (std::out_of_range &e) { std::ostringstream msg; msg << "(GetWriteToDisk) No actor output with the name " << outputName - << " exists."; - msg << fMotherVolumeName << " " << GetName(); + << " exists in actor " << GetName() << " attached to " + << fMotherVolumeName << "."; Fatal(msg.str()); } return ""; // to avoid warning diff --git a/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp new file mode 100644 index 000000000..4a6a8c130 --- /dev/null +++ b/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp @@ -0,0 +1,21 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ +#include <pybind11/pybind11.h> + +namespace py = pybind11; +#include "GateLastVertexInteractionSplittingActor.h" + +void init_GateLastVertexInteractionSplittingActor(py::module &m) { + + py::class_<GateLastVertexInteractionSplittingActor, GateVActor, + std::unique_ptr<GateLastVertexInteractionSplittingActor, py::nodelete>>( + m, "GateLastVertexInteractionSplittingActor") + .def_readwrite( + "fListOfVolumeAncestor", + &GateLastVertexInteractionSplittingActor::fListOfVolumeAncestor) + .def(py::init<py::dict &>()); +} diff --git a/core/opengate_core/opengate_lib/pyGateLastVertexSource.cpp b/core/opengate_core/opengate_lib/pyGateLastVertexSource.cpp new file mode 100644 index 000000000..0cb59f146 --- /dev/null +++ b/core/opengate_core/opengate_lib/pyGateLastVertexSource.cpp @@ -0,0 +1,22 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include <pybind11/pybind11.h> + +namespace py = pybind11; + +#include "GateLastVertexSource.h" + +void init_GateLastVertexSource(py::module &m) { + + py::class_<GateLastVertexSource, GateVSource>(m, "GateLastVertexSource") + .def(py::init()) + .def("InitializeUserInfo", &GateLastVertexSource::InitializeUserInfo) + // If needed: add your own class functions that will be accessible from + // python side. + ; +} diff --git a/core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp new file mode 100644 index 000000000..a5cb86c35 --- /dev/null +++ b/core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp @@ -0,0 +1,20 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ +#include <pybind11/pybind11.h> + +namespace py = pybind11; +#include "G4VBiasingOperator.hh" +#include "GateOptrComptPseudoTransportationActor.h" + +void init_GateOptrComptPseudoTransportationActor(py::module &m) { + + py::class_< + GateOptrComptPseudoTransportationActor, G4VBiasingOperator, GateVActor, + std::unique_ptr<GateOptrComptPseudoTransportationActor, py::nodelete>>( + m, "GateOptrComptPseudoTransportationActor") + .def(py::init<py::dict &>()); +} diff --git a/core/opengate_core/opengate_lib/tree.hh b/core/opengate_core/opengate_lib/tree.hh new file mode 100644 index 000000000..4f78d5b8a --- /dev/null +++ b/core/opengate_core/opengate_lib/tree.hh @@ -0,0 +1,3412 @@ + +// STL-like templated tree class. +// +// Copyright (C) 2001-2024 Kasper Peeters <kasper@phi-sci.com> +// Distributed under the GNU General Public License version 3. +// +// Special permission to use tree.hh under the conditions of a +// different license can be requested from the author. + +/** \mainpage tree.hh + \author Kasper Peeters + \version 3.20 + \date 2024-04-12 + \see http://github.com/kpeeters/tree.hh/ + + The tree.hh library for C++ provides an STL-like container class + for n-ary trees, templated over the data stored at the + nodes. Various types of iterators are provided (post-order, + pre-order, and others). Where possible the access methods are + compatible with the STL or alternative algorithms are + available. +*/ + + +#ifndef tree_hh_ +#define tree_hh_ + +#include <cassert> +#include <memory> +#include <stdexcept> +#include <iterator> +#include <set> +#include <queue> +#include <algorithm> +#include <cstddef> +#include <string> + +/// A node in the tree, combining links to other nodes as well as the actual data. +template<class T> +class tree_node_ { // size: 5*4=20 bytes (on 32 bit arch), can be reduced by 8. + public: + tree_node_(); + tree_node_(const T&); + tree_node_(T&&); + + tree_node_<T> *parent; + tree_node_<T> *first_child, *last_child; + tree_node_<T> *prev_sibling, *next_sibling; + T data; +}; + +template<class T> +tree_node_<T>::tree_node_() + : parent(0), first_child(0), last_child(0), prev_sibling(0), next_sibling(0) + { + } + +template<class T> +tree_node_<T>::tree_node_(const T& val) + : parent(0), first_child(0), last_child(0), prev_sibling(0), next_sibling(0), data(val) + { + } + +template<class T> +tree_node_<T>::tree_node_(T&& val) + : parent(0), first_child(0), last_child(0), prev_sibling(0), next_sibling(0), data(val) + { + } + +// Throw an exception with a stacktrace. + +//template <class E> +//void throw_with_trace(const E& e) +// { +// throw boost::enable_error_info(e) +// << traced(boost::stacktrace::stacktrace()); +// } + +class navigation_error : public std::logic_error { + public: + navigation_error(const std::string& s) : std::logic_error(s) + { +// assert(1==0); +// std::ostringstream str; +// std::cerr << boost::stacktrace::stacktrace() << std::endl; +// str << boost::stacktrace::stacktrace(); +// stacktrace=str.str(); + } + +// virtual const char *what() const noexcept override +// { +// return (std::logic_error::what()+std::string("; ")+stacktrace).c_str(); +// } +// +// std::string stacktrace; +}; + +template <class T, class tree_node_allocator = std::allocator<tree_node_<T> > > +class tree { + protected: + typedef tree_node_<T> tree_node; + public: + /// Value of the data stored at a node. + typedef T value_type; + + class iterator_base; + class pre_order_iterator; + class post_order_iterator; + class sibling_iterator; + class leaf_iterator; + + tree(); // empty constructor + tree(const T&); // constructor setting given element as head + tree(const iterator_base&); + tree(const tree<T, tree_node_allocator>&); // copy constructor + tree(tree<T, tree_node_allocator>&&); // move constructor + ~tree(); + tree<T,tree_node_allocator>& operator=(const tree<T, tree_node_allocator>&); // copy assignment + tree<T,tree_node_allocator>& operator=(tree<T, tree_node_allocator>&&); // move assignment + + /// Base class for iterators, only pointers stored, no traversal logic. +#ifdef __SGI_STL_PORT + class iterator_base : public stlport::bidirectional_iterator<T, ptrdiff_t> { +#else + class iterator_base { +#endif + public: + typedef T value_type; + typedef T* pointer; + typedef T& reference; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + typedef std::bidirectional_iterator_tag iterator_category; + + iterator_base(); + iterator_base(tree_node *); + + T& operator*() const; + T* operator->() const; + + /// When called, the next increment/decrement skips children of this node. + void skip_children(); + void skip_children(bool skip); + /// Number of children of the node pointed to by the iterator. + unsigned int number_of_children() const; + + sibling_iterator begin() const; + sibling_iterator end() const; + + tree_node *node; + protected: + bool skip_current_children_; + }; + + /// Depth-first iterator, first accessing the node, then its children. + class pre_order_iterator : public iterator_base { + public: + pre_order_iterator(); + pre_order_iterator(tree_node *); + pre_order_iterator(const iterator_base&); + pre_order_iterator(const sibling_iterator&); + + bool operator==(const pre_order_iterator&) const; + bool operator!=(const pre_order_iterator&) const; + pre_order_iterator& operator++(); + pre_order_iterator& operator--(); + pre_order_iterator operator++(int); + pre_order_iterator operator--(int); + pre_order_iterator& operator+=(unsigned int); + pre_order_iterator& operator-=(unsigned int); + + pre_order_iterator& next_skip_children(); + }; + + /// Depth-first iterator, first accessing the children, then the node itself. + class post_order_iterator : public iterator_base { + public: + post_order_iterator(); + post_order_iterator(tree_node *); + post_order_iterator(const iterator_base&); + post_order_iterator(const sibling_iterator&); + + bool operator==(const post_order_iterator&) const; + bool operator!=(const post_order_iterator&) const; + post_order_iterator& operator++(); + post_order_iterator& operator--(); + post_order_iterator operator++(int); + post_order_iterator operator--(int); + post_order_iterator& operator+=(unsigned int); + post_order_iterator& operator-=(unsigned int); + + /// Set iterator to the first child as deep as possible down the tree. + void descend_all(); + }; + + /// Breadth-first iterator, using a queue + class breadth_first_queued_iterator : public iterator_base { + public: + breadth_first_queued_iterator(); + breadth_first_queued_iterator(tree_node *); + breadth_first_queued_iterator(const iterator_base&); + + bool operator==(const breadth_first_queued_iterator&) const; + bool operator!=(const breadth_first_queued_iterator&) const; + breadth_first_queued_iterator& operator++(); + breadth_first_queued_iterator operator++(int); + breadth_first_queued_iterator& operator+=(unsigned int); + + private: + std::queue<tree_node *> traversal_queue; + }; + + /// The default iterator types throughout the tree class. + typedef pre_order_iterator iterator; + typedef breadth_first_queued_iterator breadth_first_iterator; + + /// Iterator which traverses only the nodes at a given depth from the root. + class fixed_depth_iterator : public iterator_base { + public: + fixed_depth_iterator(); + fixed_depth_iterator(tree_node *); + fixed_depth_iterator(const iterator_base&); + fixed_depth_iterator(const sibling_iterator&); + fixed_depth_iterator(const fixed_depth_iterator&); + + void swap(fixed_depth_iterator&, fixed_depth_iterator&); + fixed_depth_iterator& operator=(fixed_depth_iterator); + + bool operator==(const fixed_depth_iterator&) const; + bool operator!=(const fixed_depth_iterator&) const; + fixed_depth_iterator& operator++(); + fixed_depth_iterator& operator--(); + fixed_depth_iterator operator++(int); + fixed_depth_iterator operator--(int); + fixed_depth_iterator& operator+=(unsigned int); + fixed_depth_iterator& operator-=(unsigned int); + + tree_node *top_node; + }; + + /// Iterator which traverses only the nodes which are siblings of each other. + class sibling_iterator : public iterator_base { + public: + sibling_iterator(); + sibling_iterator(tree_node *); + sibling_iterator(const sibling_iterator&); + sibling_iterator(const iterator_base&); + + void swap(sibling_iterator&, sibling_iterator&); + sibling_iterator& operator=(sibling_iterator); + + bool operator==(const sibling_iterator&) const; + bool operator!=(const sibling_iterator&) const; + sibling_iterator& operator++(); + sibling_iterator& operator--(); + sibling_iterator operator++(int); + sibling_iterator operator--(int); + sibling_iterator& operator+=(unsigned int); + sibling_iterator& operator-=(unsigned int); + + tree_node *range_first() const; + tree_node *range_last() const; + tree_node *parent_; + private: + void set_parent_(); + }; + + /// Iterator which traverses only the leaves. + class leaf_iterator : public iterator_base { + public: + leaf_iterator(); + leaf_iterator(tree_node *, tree_node *top=0); + leaf_iterator(const sibling_iterator&); + leaf_iterator(const iterator_base&); + + bool operator==(const leaf_iterator&) const; + bool operator!=(const leaf_iterator&) const; + leaf_iterator& operator++(); + leaf_iterator& operator--(); + leaf_iterator operator++(int); + leaf_iterator operator--(int); + leaf_iterator& operator+=(unsigned int); + leaf_iterator& operator-=(unsigned int); + private: + tree_node *top_node; + }; + + /// Return iterator to the beginning of the tree. + inline pre_order_iterator begin() const; + /// Return iterator to the end of the tree. + inline pre_order_iterator end() const; + /// Return post-order iterator to the beginning of the tree. + post_order_iterator begin_post() const; + /// Return post-order end iterator of the tree. + post_order_iterator end_post() const; + /// Return fixed-depth iterator to the first node at a given depth from the given iterator. + /// If 'walk_back=true', a depth=0 iterator will be taken from the beginning of the sibling + /// range, not the current node. + fixed_depth_iterator begin_fixed(const iterator_base&, unsigned int, bool walk_back=true) const; + /// Return fixed-depth end iterator. + fixed_depth_iterator end_fixed(const iterator_base&, unsigned int) const; + /// Return breadth-first iterator to the first node at a given depth. + breadth_first_queued_iterator begin_breadth_first() const; + /// Return breadth-first end iterator. + breadth_first_queued_iterator end_breadth_first() const; + /// Return sibling iterator to the first child of given node. + static sibling_iterator begin(const iterator_base&); + /// Return sibling end iterator for children of given node. + static sibling_iterator end(const iterator_base&); + /// Return leaf iterator to the first leaf of the tree. + leaf_iterator begin_leaf() const; + /// Return leaf end iterator for entire tree. + leaf_iterator end_leaf() const; + /// Return leaf iterator to the first leaf of the subtree at the given node. + leaf_iterator begin_leaf(const iterator_base& top) const; + /// Return leaf end iterator for the subtree at the given node. + leaf_iterator end_leaf(const iterator_base& top) const; + + typedef std::vector<int> path_t; + /// Return a path (to be taken from the 'top' node) corresponding to a node in the tree. + /// The first integer in path_t is the number of steps you need to go 'right' in the sibling + /// chain (so 0 if we go straight to the children). + path_t path_from_iterator(const iterator_base& iter, const iterator_base& top) const; + /// Return an iterator given a path from the 'top' node. + iterator iterator_from_path(const path_t&, const iterator_base& top) const; + + /// Return iterator to the parent of a node. Throws a `navigation_error` if the node + /// does not have a parent. + template<typename iter> static iter parent(iter); + /// Return iterator to the previous sibling of a node. + template<typename iter> static iter previous_sibling(iter); + /// Return iterator to the next sibling of a node. + template<typename iter> static iter next_sibling(iter); + /// Return iterator to the next node at a given depth. + template<typename iter> iter next_at_same_depth(iter) const; + + /// Erase all nodes of the tree. + void clear(); + /// Erase element at position pointed to by iterator, return incremented iterator. + template<typename iter> iter erase(iter); + /// Erase all children of the node pointed to by iterator. + void erase_children(const iterator_base&); + /// Erase all siblings to the right of the iterator. + void erase_right_siblings(const iterator_base&); + /// Erase all siblings to the left of the iterator. + void erase_left_siblings(const iterator_base&); + + /// Insert empty node as last/first child of node pointed to by position. + template<typename iter> iter append_child(iter position); + template<typename iter> iter prepend_child(iter position); + /// Insert node as last/first child of node pointed to by position. + template<typename iter> iter append_child(iter position, const T& x); + template<typename iter> iter append_child(iter position, T&& x); + template<typename iter> iter prepend_child(iter position, const T& x); + template<typename iter> iter prepend_child(iter position, T&& x); + /// Append the node (plus its children) at other_position as last/first child of position. + template<typename iter> iter append_child(iter position, iter other_position); + template<typename iter> iter prepend_child(iter position, iter other_position); + /// Append the nodes in the from-to range (plus their children) as last/first children of position. + template<typename iter> iter append_children(iter position, sibling_iterator from, sibling_iterator to); + template<typename iter> iter prepend_children(iter position, sibling_iterator from, sibling_iterator to); + + /// Short-hand to insert topmost node in otherwise empty tree. + pre_order_iterator set_head(const T& x); + pre_order_iterator set_head(T&& x); + /// Insert node as previous sibling of node pointed to by position. + template<typename iter> iter insert(iter position, const T& x); + template<typename iter> iter insert(iter position, T&& x); + /// Specialisation of previous member. + sibling_iterator insert(sibling_iterator position, const T& x); + sibling_iterator insert(sibling_iterator position, T&& x); + /// Insert node (with children) pointed to by subtree as previous sibling of node pointed to by position. + /// Does not change the subtree itself (use move_in or move_in_below for that). + template<typename iter> iter insert_subtree(iter position, const iterator_base& subtree); + /// Insert node as next sibling of node pointed to by position. + template<typename iter> iter insert_after(iter position, const T& x); + template<typename iter> iter insert_after(iter position, T&& x); + /// Insert node (with children) pointed to by subtree as next sibling of node pointed to by position. + template<typename iter> iter insert_subtree_after(iter position, const iterator_base& subtree); + + /// Replace node at 'position' with other node (keeping same children); 'position' becomes invalid. + template<typename iter> iter replace(iter position, const T& x); + /// Replace node at 'position' with subtree starting at 'from' (do not erase subtree at 'from'); see above. + template<typename iter> iter replace(iter position, const iterator_base& from); + /// Replace string of siblings (plus their children) with copy of a new string (with children); see above + sibling_iterator replace(sibling_iterator orig_begin, sibling_iterator orig_end, + sibling_iterator new_begin, sibling_iterator new_end); + + /// Move all children of node at 'position' to be siblings, returns position. + template<typename iter> iter flatten(iter position); + /// Move nodes in range to be children of 'position'. + template<typename iter> iter reparent(iter position, sibling_iterator begin, sibling_iterator end); + /// Move all child nodes of 'from' to be children of 'position'. + template<typename iter> iter reparent(iter position, iter from); + + /// Replace node with a new node, making the old node (plus subtree) a child of the new node. + template<typename iter> iter wrap(iter position, const T& x); + /// Replace the range of sibling nodes (plus subtrees), making these children of the new node. + template<typename iter> iter wrap(iter from, iter to, const T& x); + + /// Move 'source' node (plus its children) to become the next sibling of 'target'. + template<typename iter> iter move_after(iter target, iter source); + /// Move 'source' node (plus its children) to become the previous sibling of 'target'. + template<typename iter> iter move_before(iter target, iter source); + sibling_iterator move_before(sibling_iterator target, sibling_iterator source); + /// Move 'source' node (plus its children) to become the node at 'target' (erasing the node at 'target'). + template<typename iter> iter move_ontop(iter target, iter source); + + /// Extract the subtree starting at the indicated node, removing it from the original tree. + tree move_out(iterator); + /// Inverse of take_out: inserts the given tree as previous sibling of indicated node by a + /// move operation, that is, the given tree becomes empty. Returns iterator to the top node. + template<typename iter> iter move_in(iter, tree&); + /// As above, but now make the tree the last child of the indicated node. + template<typename iter> iter move_in_below(iter, tree&); + /// As above, but now make the tree the nth child of the indicated node (if possible). + template<typename iter> iter move_in_as_nth_child(iter, size_t, tree&); + + /// Merge with other tree, creating new branches and leaves only if they are not already present. + void merge(sibling_iterator, sibling_iterator, sibling_iterator, sibling_iterator, + bool duplicate_leaves=false); + /// As above, but using two trees with a single top node at the 'to' and 'from' positions. + void merge(iterator to, iterator from, bool duplicate_leaves); + /// Sort (std::sort only moves values of nodes, this one moves children as well). + void sort(sibling_iterator from, sibling_iterator to, bool deep=false); + template<class StrictWeakOrdering> + void sort(sibling_iterator from, sibling_iterator to, StrictWeakOrdering comp, bool deep=false); + /// Compare two ranges of nodes (compares nodes as well as tree structure). + template<typename iter> + bool equal(const iter& one, const iter& two, const iter& three) const; + template<typename iter, class BinaryPredicate> + bool equal(const iter& one, const iter& two, const iter& three, BinaryPredicate) const; + template<typename iter> + bool equal_subtree(const iter& one, const iter& two) const; + template<typename iter, class BinaryPredicate> + bool equal_subtree(const iter& one, const iter& two, BinaryPredicate) const; + /// Extract a new tree formed by the range of siblings plus all their children. + tree subtree(sibling_iterator from, sibling_iterator to) const; + void subtree(tree&, sibling_iterator from, sibling_iterator to) const; + /// Exchange the node (plus subtree) with its sibling node (do nothing if no sibling present). + void swap(sibling_iterator it); + /// Exchange two nodes (plus subtrees). The iterators will remain valid and keep + /// pointing to the same nodes, which now sit at different locations in the tree. + void swap(iterator, iterator); + + /// Count the total number of nodes. + size_t size() const; + /// Count the total number of nodes below the indicated node (plus one). + size_t size(const iterator_base&) const; + /// Check if tree is empty. + bool empty() const; + /// Compute the depth to the root or to a fixed other iterator. + static int depth(const iterator_base&); + static int depth(const iterator_base&, const iterator_base&); + /// Compute the depth to the root, counting all levels for which predicate returns true. + template<class Predicate> + static int depth(const iterator_base&, Predicate p); + /// Compute the depth distance between two nodes, counting all levels for which predicate returns true. + template<class Predicate> + static int distance(const iterator_base& top, const iterator_base& bottom, Predicate p); + /// Determine the maximal depth of the tree. An empty tree has max_depth=-1. + int max_depth() const; + /// Determine the maximal depth of the tree with top node at the given position. + int max_depth(const iterator_base&) const; + /// Count the number of children of node at position. + static unsigned int number_of_children(const iterator_base&); + /// Count the number of siblings (left and right) of node at iterator. Total nodes at this level is +1. + unsigned int number_of_siblings(const iterator_base&) const; + /// Determine whether node at position is in the subtrees with indicated top node. + bool is_in_subtree(const iterator_base& position, const iterator_base& top) const; + /// Determine whether node at position is in the subtrees with root in the range. + bool is_in_subtree(const iterator_base& position, const iterator_base& begin, + const iterator_base& end) const; + /// Determine whether the iterator is an 'end' iterator and thus not actually pointing to a node. + bool is_valid(const iterator_base&) const; + /// Determine whether the iterator is one of the 'head' nodes at the top level, i.e. has no parent. + static bool is_head(const iterator_base&); + /// Find the lowest common ancestor of two nodes, that is, the deepest node such that + /// both nodes are descendants of it. + iterator lowest_common_ancestor(const iterator_base&, const iterator_base &) const; + + /// Determine the index of a node in the range of siblings to which it belongs. + unsigned int index(sibling_iterator it) const; + /// Inverse of 'index': return the n-th child of the node at position. + static sibling_iterator child(const iterator_base& position, unsigned int); + /// Return iterator to the sibling indicated by index + sibling_iterator sibling(const iterator_base& position, unsigned int) const; + + /// For debugging only: verify internal consistency by inspecting all pointers in the tree + /// (which will also trigger a valgrind error in case something got corrupted). + void debug_verify_consistency() const; + + /// Comparator class for iterators (compares pointer values; why doesn't this work automatically?) + class iterator_base_less { + public: + bool operator()(const typename tree<T, tree_node_allocator>::iterator_base& one, + const typename tree<T, tree_node_allocator>::iterator_base& two) const + { + return one.node < two.node; + } + }; + tree_node *head, *feet; // head/feet are always dummy; if an iterator points to them it is invalid + private: + tree_node_allocator alloc_; + void head_initialise_(); + void copy_(const tree<T, tree_node_allocator>& other); + + /// Comparator class for two nodes of a tree (used for sorting and searching). + template<class StrictWeakOrdering> + class compare_nodes { + public: + compare_nodes(StrictWeakOrdering comp) : comp_(comp) {} + + bool operator()(const tree_node *a, const tree_node *b) const + { + return comp_(a->data, b->data); + } + private: + StrictWeakOrdering comp_; + }; + }; + +//template <class T, class tree_node_allocator> +//class iterator_base_less { +// public: +// bool operator()(const typename tree<T, tree_node_allocator>::iterator_base& one, +// const typename tree<T, tree_node_allocator>::iterator_base& two) const +// { +// txtout << "operatorclass<" << one.node < two.node << std::endl; +// return one.node < two.node; +// } +//}; + +// template <class T, class tree_node_allocator> +// bool operator<(const typename tree<T, tree_node_allocator>::iterator& one, +// const typename tree<T, tree_node_allocator>::iterator& two) +// { +// txtout << "operator< " << one.node < two.node << std::endl; +// if(one.node < two.node) return true; +// return false; +// } +// +// template <class T, class tree_node_allocator> +// bool operator==(const typename tree<T, tree_node_allocator>::iterator& one, +// const typename tree<T, tree_node_allocator>::iterator& two) +// { +// txtout << "operator== " << one.node == two.node << std::endl; +// if(one.node == two.node) return true; +// return false; +// } +// +// template <class T, class tree_node_allocator> +// bool operator>(const typename tree<T, tree_node_allocator>::iterator_base& one, +// const typename tree<T, tree_node_allocator>::iterator_base& two) +// { +// txtout << "operator> " << one.node < two.node << std::endl; +// if(one.node > two.node) return true; +// return false; +// } + + + +// Tree + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree() + { + head_initialise_(); + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree(const T& x) + { + head_initialise_(); + set_head(x); + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree(tree<T, tree_node_allocator>&& x) + { + head_initialise_(); + if(x.head->next_sibling!=x.feet) { // move tree if non-empty only + head->next_sibling=x.head->next_sibling; + feet->prev_sibling=x.feet->prev_sibling; + x.head->next_sibling->prev_sibling=head; + x.feet->prev_sibling->next_sibling=feet; + x.head->next_sibling=x.feet; + x.feet->prev_sibling=x.head; + } + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree(const iterator_base& other) + { + head_initialise_(); + set_head((*other)); + replace(begin(), other); + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::~tree() + { + clear(); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, head); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, feet); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, head, 1); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, feet, 1); + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::head_initialise_() + { + head = std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + feet = std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, head, tree_node_<T>()); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, feet, tree_node_<T>()); + + head->parent=0; + head->first_child=0; + head->last_child=0; + head->prev_sibling=0; //head; + head->next_sibling=feet; //head; + + feet->parent=0; + feet->first_child=0; + feet->last_child=0; + feet->prev_sibling=head; + feet->next_sibling=0; + } + +template <class T, class tree_node_allocator> +tree<T,tree_node_allocator>& tree<T, tree_node_allocator>::operator=(const tree<T, tree_node_allocator>& other) + { + if(this != &other) + copy_(other); + return *this; + } + +template <class T, class tree_node_allocator> +tree<T,tree_node_allocator>& tree<T, tree_node_allocator>::operator=(tree<T, tree_node_allocator>&& x) + { + if(this != &x) { + clear(); // clear any existing data. + + head->next_sibling=x.head->next_sibling; + feet->prev_sibling=x.feet->prev_sibling; + x.head->next_sibling->prev_sibling=head; + x.feet->prev_sibling->next_sibling=feet; + x.head->next_sibling=x.feet; + x.feet->prev_sibling=x.head; + } + return *this; + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree(const tree<T, tree_node_allocator>& other) + { + head_initialise_(); + copy_(other); + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::copy_(const tree<T, tree_node_allocator>& other) + { + clear(); + pre_order_iterator it=other.begin(), to=begin(); + while(it!=other.end()) { + to=insert(to, (*it)); + it.skip_children(); + ++it; + } + to=begin(); + it=other.begin(); + while(it!=other.end()) { + to=replace(to, it); + to.skip_children(); + it.skip_children(); + ++to; + ++it; + } + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::clear() + { + if(head) + while(head->next_sibling!=feet) + erase(pre_order_iterator(head->next_sibling)); + } + +template<class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::erase_children(const iterator_base& it) + { +// std::cout << "erase_children " << it.node << std::endl; + if(it.node==0) return; + + tree_node *cur=it.node->first_child; + tree_node *prev=0; + + while(cur!=0) { + prev=cur; + cur=cur->next_sibling; + erase_children(pre_order_iterator(prev)); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); + } + it.node->first_child=0; + it.node->last_child=0; +// std::cout << "exit" << std::endl; + } + +template<class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::erase_right_siblings(const iterator_base& it) + { + if(it.node==0) return; + + tree_node *cur=it.node->next_sibling; + tree_node *prev=0; + + while(cur!=0) { + prev=cur; + cur=cur->next_sibling; + erase_children(pre_order_iterator(prev)); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); + } + it.node->next_sibling=0; + if(it.node->parent!=0) + it.node->parent->last_child=it.node; + } + +template<class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::erase_left_siblings(const iterator_base& it) + { + if(it.node==0) return; + + tree_node *cur=it.node->prev_sibling; + tree_node *prev=0; + + while(cur!=0) { + prev=cur; + cur=cur->prev_sibling; + erase_children(pre_order_iterator(prev)); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); + } + it.node->prev_sibling=0; + if(it.node->parent!=0) + it.node->parent->first_child=it.node; + } + +template<class T, class tree_node_allocator> +template<class iter> +iter tree<T, tree_node_allocator>::erase(iter it) + { + tree_node *cur=it.node; + assert(cur!=head); + iter ret=it; + ret.skip_children(); + ++ret; + erase_children(it); + if(cur->prev_sibling==0) { + cur->parent->first_child=cur->next_sibling; + } + else { + cur->prev_sibling->next_sibling=cur->next_sibling; + } + if(cur->next_sibling==0) { + cur->parent->last_child=cur->prev_sibling; + } + else { + cur->next_sibling->prev_sibling=cur->prev_sibling; + } + + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, cur); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, cur, 1); + return ret; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::begin() const + { + return pre_order_iterator(head->next_sibling); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::end() const + { + return pre_order_iterator(feet); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator tree<T, tree_node_allocator>::begin_breadth_first() const + { + return breadth_first_queued_iterator(head->next_sibling); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator tree<T, tree_node_allocator>::end_breadth_first() const + { + return breadth_first_queued_iterator(); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::begin_post() const + { + tree_node *tmp=head->next_sibling; + if(tmp!=feet) { + while(tmp->first_child) + tmp=tmp->first_child; + } + return post_order_iterator(tmp); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::end_post() const + { + return post_order_iterator(feet); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::begin_fixed(const iterator_base& pos, unsigned int dp, bool walk_back) const + { + typename tree<T, tree_node_allocator>::fixed_depth_iterator ret; + ret.top_node=pos.node; + + tree_node *tmp=pos.node; + unsigned int curdepth=0; + while(curdepth<dp) { // go down one level + while(tmp->first_child==0) { + if(tmp->next_sibling==0) { + // try to walk up and then right again + do { + if(tmp==ret.top_node) + throw std::range_error("tree: begin_fixed out of range"); + tmp=tmp->parent; + if(tmp==0) + throw std::range_error("tree: begin_fixed out of range"); + --curdepth; + } while(tmp->next_sibling==0); + } + tmp=tmp->next_sibling; + } + tmp=tmp->first_child; + ++curdepth; + } + + // Now walk back to the first sibling in this range. + if(walk_back) + while(tmp->prev_sibling!=0) + tmp=tmp->prev_sibling; + + ret.node=tmp; + return ret; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::end_fixed(const iterator_base& pos, unsigned int dp) const + { + assert(1==0); // FIXME: not correct yet: use is_valid() as a temporary workaround + tree_node *tmp=pos.node; + unsigned int curdepth=1; + while(curdepth<dp) { // go down one level + while(tmp->first_child==0) { + tmp=tmp->next_sibling; + if(tmp==0) + throw std::range_error("tree: end_fixed out of range"); + } + tmp=tmp->first_child; + ++curdepth; + } + return tmp; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::begin(const iterator_base& pos) + { + assert(pos.node!=0); + if(pos.node->first_child==0) { + return end(pos); + } + return pos.node->first_child; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::end(const iterator_base& pos) + { + sibling_iterator ret(0); + ret.parent_=pos.node; + return ret; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::begin_leaf() const + { + tree_node *tmp=head->next_sibling; + if(tmp!=feet) { + while(tmp->first_child) + tmp=tmp->first_child; + } + return leaf_iterator(tmp); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::end_leaf() const + { + return leaf_iterator(feet); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::path_t tree<T, tree_node_allocator>::path_from_iterator(const iterator_base& iter, const iterator_base& top) const + { + path_t path; + tree_node *walk=iter.node; + + do { + if(path.size()>0) + walk=walk->parent; + int num=0; + while(walk!=top.node && walk->prev_sibling!=0 && walk->prev_sibling!=head) { + ++num; + walk=walk->prev_sibling; + } + path.push_back(num); + } + while(walk->parent!=0 && walk!=top.node); + + std::reverse(path.begin(), path.end()); + return path; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::iterator tree<T, tree_node_allocator>::iterator_from_path(const path_t& path, const iterator_base& top) const + { + iterator it=top; + tree_node *walk=it.node; + + for(size_t step=0; step<path.size(); ++step) { + if(step>0) + walk=walk->first_child; + if(walk==0) + throw std::range_error("tree::iterator_from_path: no more nodes at step "+std::to_string(step)); + + for(int i=0; i<path[step]; ++i) { + walk=walk->next_sibling; + if(walk==0) + throw std::range_error("tree::iterator_from_path: out of siblings at step "+std::to_string(step)); + } + } + it.node=walk; + return it; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::begin_leaf(const iterator_base& top) const + { + tree_node *tmp=top.node; + while(tmp->first_child) + tmp=tmp->first_child; + return leaf_iterator(tmp, top.node); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::end_leaf(const iterator_base& top) const + { + return leaf_iterator(top.node, top.node); + } + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::parent(iter position) + { + if(position.node==0) + throw navigation_error("tree: attempt to navigate from null iterator."); + + if(position.node->parent==0) + throw navigation_error("tree: attempt to navigate up past head node."); + + return iter(position.node->parent); + } + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::previous_sibling(iter position) + { + assert(position.node!=0); + iter ret(position); + ret.node=position.node->prev_sibling; + return ret; + } + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::next_sibling(iter position) + { + assert(position.node!=0); + iter ret(position); + ret.node=position.node->next_sibling; + return ret; + } + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::next_at_same_depth(iter position) const + { + // We make use of a temporary fixed_depth iterator to implement this. + + typename tree<T, tree_node_allocator>::fixed_depth_iterator tmp(position.node); + + ++tmp; + return iter(tmp); + + // assert(position.node!=0); + // iter ret(position); + // + // if(position.node->next_sibling) { + // ret.node=position.node->next_sibling; + // } + // else { + // int relative_depth=0; + // upper: + // do { + // ret.node=ret.node->parent; + // if(ret.node==0) return ret; + // --relative_depth; + // } while(ret.node->next_sibling==0); + // lower: + // ret.node=ret.node->next_sibling; + // while(ret.node->first_child==0) { + // if(ret.node->next_sibling==0) + // goto upper; + // ret.node=ret.node->next_sibling; + // if(ret.node==0) return ret; + // } + // while(relative_depth<0 && ret.node->first_child!=0) { + // ret.node=ret.node->first_child; + // ++relative_depth; + // } + // if(relative_depth<0) { + // if(ret.node->next_sibling==0) goto upper; + // else goto lower; + // } + // } + // return ret; + } + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::append_child(iter position) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, tree_node_<T>()); + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node; + if(position.node->last_child!=0) { + position.node->last_child->next_sibling=tmp; + } + else { + position.node->first_child=tmp; + } + tmp->prev_sibling=position.node->last_child; + position.node->last_child=tmp; + tmp->next_sibling=0; + return tmp; + } + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::prepend_child(iter position) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, tree_node_<T>()); + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node; + if(position.node->first_child!=0) { + position.node->first_child->prev_sibling=tmp; + } + else { + position.node->last_child=tmp; + } + tmp->next_sibling=position.node->first_child; + position.node->prev_child=tmp; + tmp->prev_sibling=0; + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::append_child(iter position, const T& x) + { + // If your program fails here you probably used 'append_child' to add the top + // node to an empty tree. From version 1.45 the top element should be added + // using 'insert'. See the documentation for further information, and sorry about + // the API change. + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node; + if(position.node->last_child!=0) { + position.node->last_child->next_sibling=tmp; + } + else { + position.node->first_child=tmp; + } + tmp->prev_sibling=position.node->last_child; + position.node->last_child=tmp; + tmp->next_sibling=0; + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::append_child(iter position, T&& x) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); // Here is where the move semantics kick in + std::swap(tmp->data, x); + + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node; + if(position.node->last_child!=0) { + position.node->last_child->next_sibling=tmp; + } + else { + position.node->first_child=tmp; + } + tmp->prev_sibling=position.node->last_child; + position.node->last_child=tmp; + tmp->next_sibling=0; + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::prepend_child(iter position, const T& x) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node; + if(position.node->first_child!=0) { + position.node->first_child->prev_sibling=tmp; + } + else { + position.node->last_child=tmp; + } + tmp->next_sibling=position.node->first_child; + position.node->first_child=tmp; + tmp->prev_sibling=0; + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::prepend_child(iter position, T&& x) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); + std::swap(tmp->data, x); + + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node; + if(position.node->first_child!=0) { + position.node->first_child->prev_sibling=tmp; + } + else { + position.node->last_child=tmp; + } + tmp->next_sibling=position.node->first_child; + position.node->first_child=tmp; + tmp->prev_sibling=0; + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::append_child(iter position, iter other) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + sibling_iterator aargh=append_child(position, value_type()); + return replace(aargh, other); + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::prepend_child(iter position, iter other) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + sibling_iterator aargh=prepend_child(position, value_type()); + return replace(aargh, other); + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::append_children(iter position, sibling_iterator from, sibling_iterator to) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + iter ret=from; + + while(from!=to) { + insert_subtree(position.end(), from); + ++from; + } + return ret; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::prepend_children(iter position, sibling_iterator from, sibling_iterator to) + { + assert(position.node!=head); + assert(position.node!=feet); + assert(position.node); + + if(from==to) return from; // should return end of tree? + + iter ret; + do { + --to; + ret=insert_subtree(position.begin(), to); + } + while(to!=from); + + return ret; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::set_head(const T& x) + { + assert(head->next_sibling==feet); + return insert(iterator(feet), x); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::set_head(T&& x) + { + assert(head->next_sibling==feet); + return insert(iterator(feet), x); + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::insert(iter position, const T& x) + { + if(position.node==0) { + position.node=feet; // Backward compatibility: when calling insert on a null node, + // insert before the feet. + } + assert(position.node!=head); // Cannot insert before head. + + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node->parent; + tmp->next_sibling=position.node; + tmp->prev_sibling=position.node->prev_sibling; + position.node->prev_sibling=tmp; + + if(tmp->prev_sibling==0) { + if(tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->first_child=tmp; + } + else + tmp->prev_sibling->next_sibling=tmp; + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::insert(iter position, T&& x) + { + if(position.node==0) { + position.node=feet; // Backward compatibility: when calling insert on a null node, + // insert before the feet. + } + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); + std::swap(tmp->data, x); // Move semantics + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node->parent; + tmp->next_sibling=position.node; + tmp->prev_sibling=position.node->prev_sibling; + position.node->prev_sibling=tmp; + + if(tmp->prev_sibling==0) { + if(tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->first_child=tmp; + } + else + tmp->prev_sibling->next_sibling=tmp; + return tmp; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::insert(sibling_iterator position, const T& x) + { + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child=0; + tmp->last_child=0; + + tmp->next_sibling=position.node; + if(position.node==0) { // iterator points to end of a subtree + tmp->parent=position.parent_; + tmp->prev_sibling=position.range_last(); + tmp->parent->last_child=tmp; + } + else { + tmp->parent=position.node->parent; + tmp->prev_sibling=position.node->prev_sibling; + position.node->prev_sibling=tmp; + } + + if(tmp->prev_sibling==0) { + if(tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->first_child=tmp; + } + else + tmp->prev_sibling->next_sibling=tmp; + return tmp; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::insert(sibling_iterator position, T&& x) + { + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); + std::swap(tmp->data, x); // Move semantics + + tmp->first_child=0; + tmp->last_child=0; + + tmp->next_sibling=position.node; + if(position.node==0) { // iterator points to end of a subtree + tmp->parent=position.parent_; + tmp->prev_sibling=position.range_last(); + tmp->parent->last_child=tmp; + } + else { + tmp->parent=position.node->parent; + tmp->prev_sibling=position.node->prev_sibling; + position.node->prev_sibling=tmp; + } + + if(tmp->prev_sibling==0) { + if(tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->first_child=tmp; + } + else + tmp->prev_sibling->next_sibling=tmp; + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::insert_after(iter position, const T& x) + { + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node->parent; + tmp->prev_sibling=position.node; + tmp->next_sibling=position.node->next_sibling; + position.node->next_sibling=tmp; + + if(tmp->next_sibling==0) { + if(tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->last_child=tmp; + } + else { + tmp->next_sibling->prev_sibling=tmp; + } + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::insert_after(iter position, T&& x) + { + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); + std::swap(tmp->data, x); // move semantics + tmp->first_child=0; + tmp->last_child=0; + + tmp->parent=position.node->parent; + tmp->prev_sibling=position.node; + tmp->next_sibling=position.node->next_sibling; + position.node->next_sibling=tmp; + + if(tmp->next_sibling==0) { + if(tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->last_child=tmp; + } + else { + tmp->next_sibling->prev_sibling=tmp; + } + return tmp; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::insert_subtree(iter position, const iterator_base& subtree) + { + // insert dummy + iter it=insert(position, value_type()); + // replace dummy with subtree + return replace(it, subtree); + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::insert_subtree_after(iter position, const iterator_base& subtree) + { + // insert dummy + iter it=insert_after(position, value_type()); + // replace dummy with subtree + return replace(it, subtree); + } + +// template <class T, class tree_node_allocator> +// template <class iter> +// iter tree<T, tree_node_allocator>::insert_subtree(sibling_iterator position, iter subtree) +// { +// // insert dummy +// iter it(insert(position, value_type())); +// // replace dummy with subtree +// return replace(it, subtree); +// } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::replace(iter position, const T& x) + { +// kp::destructor(&position.node->data); +// kp::constructor(&position.node->data, x); + position.node->data=x; +// alloc_.destroy(position.node); +// alloc_.construct(position.node, x); + return position; + } + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::replace(iter position, const iterator_base& from) + { + assert(position.node!=head); + tree_node *current_from=from.node; + tree_node *start_from=from.node; + tree_node *current_to =position.node; + + // replace the node at position with head of the replacement tree at from +// std::cout << "warning!" << position.node << std::endl; + erase_children(position); +// std::cout << "no warning!" << std::endl; + tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, (*from)); + tmp->first_child=0; + tmp->last_child=0; + if(current_to->prev_sibling==0) { + if(current_to->parent!=0) + current_to->parent->first_child=tmp; + } + else { + current_to->prev_sibling->next_sibling=tmp; + } + tmp->prev_sibling=current_to->prev_sibling; + if(current_to->next_sibling==0) { + if(current_to->parent!=0) + current_to->parent->last_child=tmp; + } + else { + current_to->next_sibling->prev_sibling=tmp; + } + tmp->next_sibling=current_to->next_sibling; + tmp->parent=current_to->parent; +// kp::destructor(¤t_to->data); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, current_to); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, current_to, 1); + current_to=tmp; + + // only at this stage can we fix 'last' + tree_node *last=from.node->next_sibling; + + pre_order_iterator toit=tmp; + // copy all children + do { + assert(current_from!=0); + if(current_from->first_child != 0) { + current_from=current_from->first_child; + toit=append_child(toit, current_from->data); + } + else { + while(current_from->next_sibling==0 && current_from!=start_from) { + current_from=current_from->parent; + toit=parent(toit); + assert(current_from!=0); + } + current_from=current_from->next_sibling; + if(current_from!=last) { + toit=append_child(parent(toit), current_from->data); + } + } + } + while(current_from!=last); + + return current_to; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::replace( + sibling_iterator orig_begin, + sibling_iterator orig_end, + sibling_iterator new_begin, + sibling_iterator new_end) + { + tree_node *orig_first=orig_begin.node; + tree_node *new_first=new_begin.node; + tree_node *orig_last=orig_first; + while((++orig_begin)!=orig_end) + orig_last=orig_last->next_sibling; + tree_node *new_last=new_first; + while((++new_begin)!=new_end) + new_last=new_last->next_sibling; + + // insert all siblings in new_first..new_last before orig_first + bool first=true; + pre_order_iterator ret; + while(1==1) { + pre_order_iterator tt=insert_subtree(pre_order_iterator(orig_first), pre_order_iterator(new_first)); + if(first) { + ret=tt; + first=false; + } + if(new_first==new_last) + break; + new_first=new_first->next_sibling; + } + + // erase old range of siblings + bool last=false; + tree_node *next=orig_first; + while(1==1) { + if(next==orig_last) + last=true; + next=next->next_sibling; + erase((pre_order_iterator)orig_first); + if(last) + break; + orig_first=next; + } + return ret; + } + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::flatten(iter position) + { + if(position.node->first_child==0) + return position; + + tree_node *tmp=position.node->first_child; + while(tmp) { + tmp->parent=position.node->parent; + tmp=tmp->next_sibling; + } + if(position.node->next_sibling) { + position.node->last_child->next_sibling=position.node->next_sibling; + position.node->next_sibling->prev_sibling=position.node->last_child; + } + else { + position.node->parent->last_child=position.node->last_child; + } + position.node->next_sibling=position.node->first_child; + position.node->next_sibling->prev_sibling=position.node; + position.node->first_child=0; + position.node->last_child=0; + + return position; + } + + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::reparent(iter position, sibling_iterator begin, sibling_iterator end) + { + tree_node *first=begin.node; + tree_node *last=first; + + assert(first!=position.node); + + if(begin==end) return begin; + // determine last node + while((++begin)!=end) { + last=last->next_sibling; + } + // move subtree + if(first->prev_sibling==0) { + first->parent->first_child=last->next_sibling; + } + else { + first->prev_sibling->next_sibling=last->next_sibling; + } + if(last->next_sibling==0) { + last->parent->last_child=first->prev_sibling; + } + else { + last->next_sibling->prev_sibling=first->prev_sibling; + } + if(position.node->first_child==0) { + position.node->first_child=first; + position.node->last_child=last; + first->prev_sibling=0; + } + else { + position.node->last_child->next_sibling=first; + first->prev_sibling=position.node->last_child; + position.node->last_child=last; + } + last->next_sibling=0; + + tree_node *pos=first; + for(;;) { + pos->parent=position.node; + if(pos==last) break; + pos=pos->next_sibling; + } + + return first; + } + +template <class T, class tree_node_allocator> +template <typename iter> iter tree<T, tree_node_allocator>::reparent(iter position, iter from) + { + if(from.node->first_child==0) return position; + return reparent(position, from.node->first_child, end(from)); + } + +template <class T, class tree_node_allocator> +template <typename iter> iter tree<T, tree_node_allocator>::wrap(iter position, const T& x) + { + assert(position.node!=0); + sibling_iterator fr=position, to=position; + ++to; + iter ret = insert(position, x); + reparent(ret, fr, to); + return ret; + } + +template <class T, class tree_node_allocator> +template <typename iter> iter tree<T, tree_node_allocator>::wrap(iter from, iter to, const T& x) + { + assert(from.node!=0); + iter ret = insert(from, x); + reparent(ret, from, to); + return ret; + } + +template <class T, class tree_node_allocator> +template <typename iter> iter tree<T, tree_node_allocator>::move_after(iter target, iter source) + { + tree_node *dst=target.node; + tree_node *src=source.node; + assert(dst); + assert(src); + + if(dst==src) return source; + if(dst->next_sibling) + if(dst->next_sibling==src) // already in the right spot + return source; + + // take src out of the tree + if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; + else src->parent->first_child=src->next_sibling; + if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; + else src->parent->last_child=src->prev_sibling; + + // connect it to the new point + if(dst->next_sibling!=0) dst->next_sibling->prev_sibling=src; + else dst->parent->last_child=src; + src->next_sibling=dst->next_sibling; + dst->next_sibling=src; + src->prev_sibling=dst; + src->parent=dst->parent; + return src; + } + +template <class T, class tree_node_allocator> +template <typename iter> iter tree<T, tree_node_allocator>::move_before(iter target, iter source) + { + tree_node *dst=target.node; + tree_node *src=source.node; + assert(dst); + assert(src); + + if(dst==src) return source; + if(dst->prev_sibling) + if(dst->prev_sibling==src) // already in the right spot + return source; + + // take src out of the tree + if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; + else src->parent->first_child=src->next_sibling; + if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; + else src->parent->last_child=src->prev_sibling; + + // connect it to the new point + if(dst->prev_sibling!=0) dst->prev_sibling->next_sibling=src; + else dst->parent->first_child=src; + src->prev_sibling=dst->prev_sibling; + dst->prev_sibling=src; + src->next_sibling=dst; + src->parent=dst->parent; + return src; + } + +// specialisation for sibling_iterators +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::move_before(sibling_iterator target, + sibling_iterator source) + { + tree_node *dst=target.node; + tree_node *src=source.node; + tree_node *dst_prev_sibling; + if(dst==0) { // must then be an end iterator + dst_prev_sibling=target.parent_->last_child; + assert(dst_prev_sibling); + } + else dst_prev_sibling=dst->prev_sibling; + assert(src); + + if(dst==src) return source; + if(dst_prev_sibling) + if(dst_prev_sibling==src) // already in the right spot + return source; + + // take src out of the tree + if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; + else src->parent->first_child=src->next_sibling; + if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; + else src->parent->last_child=src->prev_sibling; + + // connect it to the new point + if(dst_prev_sibling!=0) dst_prev_sibling->next_sibling=src; + else target.parent_->first_child=src; + src->prev_sibling=dst_prev_sibling; + if(dst) { + dst->prev_sibling=src; + src->parent=dst->parent; + } + else { + src->parent=dst_prev_sibling->parent; + } + src->next_sibling=dst; + return src; + } + +template <class T, class tree_node_allocator> +template <typename iter> iter tree<T, tree_node_allocator>::move_ontop(iter target, iter source) + { + tree_node *dst=target.node; + tree_node *src=source.node; + assert(dst); + assert(src); + + if(dst==src) return source; + +// if(dst==src->prev_sibling) { +// +// } + + // remember connection points + tree_node *b_prev_sibling=dst->prev_sibling; + tree_node *b_next_sibling=dst->next_sibling; + tree_node *b_parent=dst->parent; + + // remove target + erase(target); + + // take src out of the tree + if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; + else { + assert(src->parent!=0); + src->parent->first_child=src->next_sibling; + } + if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; + else { + assert(src->parent!=0); + src->parent->last_child=src->prev_sibling; + } + + // connect it to the new point + if(b_prev_sibling!=0) b_prev_sibling->next_sibling=src; + else { + assert(b_parent!=0); + b_parent->first_child=src; + } + if(b_next_sibling!=0) b_next_sibling->prev_sibling=src; + else { + assert(b_parent!=0); + b_parent->last_child=src; + } + src->prev_sibling=b_prev_sibling; + src->next_sibling=b_next_sibling; + src->parent=b_parent; + return src; + } + + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator> tree<T, tree_node_allocator>::move_out(iterator source) + { + tree ret; + + // Move source node into the 'ret' tree. + ret.head->next_sibling = source.node; + ret.feet->prev_sibling = source.node; + + // Close the links in the current tree. + if(source.node->prev_sibling!=0) + source.node->prev_sibling->next_sibling = source.node->next_sibling; + + if(source.node->next_sibling!=0) + source.node->next_sibling->prev_sibling = source.node->prev_sibling; + + // If the moved-out node was a first or last child of + // the parent, adjust those links. + if(source.node->parent->first_child==source.node) { + if(source.node->next_sibling!=0) + source.node->parent->first_child=source.node->next_sibling; + else + source.node->parent->first_child=0; + } + if(source.node->parent->last_child==source.node) { + if(source.node->prev_sibling!=0) + source.node->parent->last_child=source.node->prev_sibling; + else + source.node->parent->last_child=0; + } + source.node->parent=0; + + // Fix source prev/next links. + source.node->prev_sibling = ret.head; + source.node->next_sibling = ret.feet; + + return ret; // A good compiler will move this, not copy. + } + +template <class T, class tree_node_allocator> +template<typename iter> iter tree<T, tree_node_allocator>::move_in(iter loc, tree& other) + { + if(other.head->next_sibling==other.feet) return loc; // other tree is empty + + tree_node *other_first_head = other.head->next_sibling; + tree_node *other_last_head = other.feet->prev_sibling; + + sibling_iterator prev(loc); + --prev; + + prev.node->next_sibling = other_first_head; + loc.node->prev_sibling = other_last_head; + other_first_head->prev_sibling = prev.node; + other_last_head->next_sibling = loc.node; + + // Adjust parent pointers. + tree_node *walk=other_first_head; + while(true) { + walk->parent=loc.node->parent; + if(walk==other_last_head) + break; + walk=walk->next_sibling; + } + + // Close other tree. + other.head->next_sibling=other.feet; + other.feet->prev_sibling=other.head; + + return other_first_head; + } + +template <class T, class tree_node_allocator> +template<typename iter> iter tree<T, tree_node_allocator>::move_in_below(iter loc, tree& other) + { + if(other.head->next_sibling==other.feet) return loc; // other tree is empty + + auto n = other.number_of_children(loc); + return move_in_as_nth_child(loc, n, other); + } + +template <class T, class tree_node_allocator> +template<typename iter> iter tree<T, tree_node_allocator>::move_in_as_nth_child(iter loc, size_t n, tree& other) + { + if(other.head->next_sibling==other.feet) return loc; // other tree is empty + + tree_node *other_first_head = other.head->next_sibling; + tree_node *other_last_head = other.feet->prev_sibling; + + if(n==0) { + if(loc.node->first_child==0) { + loc.node->first_child=other_first_head; + loc.node->last_child=other_last_head; + other_last_head->next_sibling=0; + other_first_head->prev_sibling=0; + } + else { + loc.node->first_child->prev_sibling=other_last_head; + other_last_head->next_sibling=loc.node->first_child; + loc.node->first_child=other_first_head; + other_first_head->prev_sibling=0; + } + } + else { + --n; + tree_node *walk = loc.node->first_child; + while(true) { + if(walk==0) + throw std::range_error("tree: move_in_as_nth_child position out of range"); + if(n==0) + break; + --n; + walk = walk->next_sibling; + } + if(walk->next_sibling==0) + loc.node->last_child=other_last_head; + else + walk->next_sibling->prev_sibling=other_last_head; + other_last_head->next_sibling=walk->next_sibling; + walk->next_sibling=other_first_head; + other_first_head->prev_sibling=walk; + } + + // Adjust parent pointers. + tree_node *walk=other_first_head; + while(true) { + walk->parent=loc.node; + if(walk==other_last_head) + break; + walk=walk->next_sibling; + } + + // Close other tree. + other.head->next_sibling=other.feet; + other.feet->prev_sibling=other.head; + + return other_first_head; + } + + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::merge(sibling_iterator to1, sibling_iterator to2, + sibling_iterator from1, sibling_iterator from2, + bool duplicate_leaves) + { + sibling_iterator fnd; + while(from1!=from2) { + if((fnd=std::find(to1, to2, (*from1))) != to2) { // element found + if(from1.begin()==from1.end()) { // full depth reached + if(duplicate_leaves) + append_child(parent(to1), (*from1)); + } + else { // descend further + merge(fnd.begin(), fnd.end(), from1.begin(), from1.end(), duplicate_leaves); + } + } + else { // element missing + insert_subtree(to2, from1); + } + ++from1; + } + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::merge(iterator to, iterator from, bool duplicate_leaves) + { + sibling_iterator to1(to); + sibling_iterator to2=to1; + ++to2; + sibling_iterator from1(from); + sibling_iterator from2=from1; + ++from2; + + merge(to1, to2, from1, from2, duplicate_leaves); + } + + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::sort(sibling_iterator from, sibling_iterator to, bool deep) + { + std::less<T> comp; + sort(from, to, comp, deep); + } + +template <class T, class tree_node_allocator> +template <class StrictWeakOrdering> +void tree<T, tree_node_allocator>::sort(sibling_iterator from, sibling_iterator to, + StrictWeakOrdering comp, bool deep) + { + if(from==to) return; + // make list of sorted nodes + // CHECK: if multiset stores equivalent nodes in the order in which they + // are inserted, then this routine should be called 'stable_sort'. + std::multiset<tree_node *, compare_nodes<StrictWeakOrdering> > nodes(comp); + sibling_iterator it=from, it2=to; + while(it != to) { + nodes.insert(it.node); + ++it; + } + // reassemble + --it2; + + // prev and next are the nodes before and after the sorted range + tree_node *prev=from.node->prev_sibling; + tree_node *next=it2.node->next_sibling; + typename std::multiset<tree_node *, compare_nodes<StrictWeakOrdering> >::iterator nit=nodes.begin(), eit=nodes.end(); + if(prev==0) { + if((*nit)->parent!=0) // to catch "sorting the head" situations, when there is no parent + (*nit)->parent->first_child=(*nit); + } + else prev->next_sibling=(*nit); + + --eit; + while(nit!=eit) { + (*nit)->prev_sibling=prev; + if(prev) + prev->next_sibling=(*nit); + prev=(*nit); + ++nit; + } + // prev now points to the last-but-one node in the sorted range + if(prev) + prev->next_sibling=(*eit); + + // eit points to the last node in the sorted range. + (*eit)->next_sibling=next; + (*eit)->prev_sibling=prev; // missed in the loop above + if(next==0) { + if((*eit)->parent!=0) // to catch "sorting the head" situations, when there is no parent + (*eit)->parent->last_child=(*eit); + } + else next->prev_sibling=(*eit); + + if(deep) { // sort the children of each node too + sibling_iterator bcs(*nodes.begin()); + sibling_iterator ecs(*eit); + ++ecs; + while(bcs!=ecs) { + sort(begin(bcs), end(bcs), comp, deep); + ++bcs; + } + } + } + +template <class T, class tree_node_allocator> +template <typename iter> +bool tree<T, tree_node_allocator>::equal(const iter& one_, const iter& two, const iter& three_) const + { + std::equal_to<T> comp; + return equal(one_, two, three_, comp); + } + +template <class T, class tree_node_allocator> +template <typename iter> +bool tree<T, tree_node_allocator>::equal_subtree(const iter& one_, const iter& two_) const + { + std::equal_to<T> comp; + return equal_subtree(one_, two_, comp); + } + +template <class T, class tree_node_allocator> +template <typename iter, class BinaryPredicate> +bool tree<T, tree_node_allocator>::equal(const iter& one_, const iter& two, const iter& three_, BinaryPredicate fun) const + { + pre_order_iterator one(one_), three(three_); + +// if(one==two && is_valid(three) && three.number_of_children()!=0) +// return false; + while(one!=two && is_valid(three)) { + if(!fun(*one,*three)) + return false; + if(one.number_of_children()!=three.number_of_children()) + return false; + ++one; + ++three; + } + return true; + } + +template <class T, class tree_node_allocator> +template <typename iter, class BinaryPredicate> +bool tree<T, tree_node_allocator>::equal_subtree(const iter& one_, const iter& two_, BinaryPredicate fun) const + { + pre_order_iterator one(one_), two(two_); + + if(!fun(*one,*two)) return false; + if(number_of_children(one)!=number_of_children(two)) return false; + return equal(begin(one),end(one),begin(two),fun); + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator> tree<T, tree_node_allocator>::subtree(sibling_iterator from, sibling_iterator to) const + { + assert(from!=to); // if from==to, the range is empty, hence no tree to return. + + tree tmp; + tmp.set_head(value_type()); + tmp.replace(tmp.begin(), tmp.end(), from, to); + return tmp; + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::subtree(tree& tmp, sibling_iterator from, sibling_iterator to) const + { + assert(from!=to); // if from==to, the range is empty, hence no tree to return. + + tmp.set_head(value_type()); + tmp.replace(tmp.begin(), tmp.end(), from, to); + } + +template <class T, class tree_node_allocator> +size_t tree<T, tree_node_allocator>::size() const + { + size_t i=0; + pre_order_iterator it=begin(), eit=end(); + while(it!=eit) { + ++i; + ++it; + } + return i; + } + +template <class T, class tree_node_allocator> +size_t tree<T, tree_node_allocator>::size(const iterator_base& top) const + { + size_t i=0; + pre_order_iterator it=top, eit=top; + eit.skip_children(); + ++eit; + while(it!=eit) { + ++i; + ++it; + } + return i; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::empty() const + { + pre_order_iterator it=begin(), eit=end(); + return (it==eit); + } + +template <class T, class tree_node_allocator> +int tree<T, tree_node_allocator>::depth(const iterator_base& it) + { + tree_node* pos=it.node; + assert(pos!=0); + int ret=0; + while(pos->parent!=0) { + pos=pos->parent; + ++ret; + } + return ret; + } + +template <class T, class tree_node_allocator> +int tree<T, tree_node_allocator>::depth(const iterator_base& it, const iterator_base& root) + { + tree_node* pos=it.node; + assert(pos!=0); + int ret=0; + while(pos->parent!=0 && pos!=root.node) { + pos=pos->parent; + ++ret; + } + return ret; + } + +template <class T, class tree_node_allocator> +template <class Predicate> +int tree<T, tree_node_allocator>::depth(const iterator_base& it, Predicate p) + { + tree_node* pos=it.node; + assert(pos!=0); + int ret=0; + while(pos->parent!=0) { + pos=pos->parent; + if(p(pos)) + ++ret; + } + return ret; + } + +template <class T, class tree_node_allocator> +template <class Predicate> +int tree<T, tree_node_allocator>::distance(const iterator_base& top, const iterator_base& bottom, Predicate p) + { + tree_node* pos=bottom.node; + assert(pos!=0); + int ret=0; + while(pos->parent!=0 && pos!=top.node) { + pos=pos->parent; + if(p(pos)) + ++ret; + } + return ret; + } + +template <class T, class tree_node_allocator> +int tree<T, tree_node_allocator>::max_depth() const + { + int maxd=-1; + for(tree_node *it = head->next_sibling; it!=feet; it=it->next_sibling) + maxd=std::max(maxd, max_depth(it)); + + return maxd; + } + + +template <class T, class tree_node_allocator> +int tree<T, tree_node_allocator>::max_depth(const iterator_base& pos) const + { + tree_node *tmp=pos.node; + + if(tmp==0 || tmp==head || tmp==feet) return -1; + + int curdepth=0, maxdepth=0; + while(true) { // try to walk the bottom of the tree + while(tmp->first_child==0) { + if(tmp==pos.node) return maxdepth; + if(tmp->next_sibling==0) { + // try to walk up and then right again + do { + tmp=tmp->parent; + if(tmp==0) return maxdepth; + --curdepth; + } + while(tmp->next_sibling==0); + } + if(tmp==pos.node) return maxdepth; + tmp=tmp->next_sibling; + } + tmp=tmp->first_child; + ++curdepth; + maxdepth=std::max(curdepth, maxdepth); + } + } + +template <class T, class tree_node_allocator> +unsigned int tree<T, tree_node_allocator>::number_of_children(const iterator_base& it) + { + tree_node *pos=it.node->first_child; + if(pos==0) return 0; + + unsigned int ret=1; +// while(pos!=it.node->last_child) { +// ++ret; +// pos=pos->next_sibling; +// } + while((pos=pos->next_sibling)) + ++ret; + return ret; + } + +template <class T, class tree_node_allocator> +unsigned int tree<T, tree_node_allocator>::number_of_siblings(const iterator_base& it) const + { + tree_node *pos=it.node; + unsigned int ret=0; + // count forward + while(pos->next_sibling && + pos->next_sibling!=head && + pos->next_sibling!=feet) { + ++ret; + pos=pos->next_sibling; + } + // count backward + pos=it.node; + while(pos->prev_sibling && + pos->prev_sibling!=head && + pos->prev_sibling!=feet) { + ++ret; + pos=pos->prev_sibling; + } + + return ret; + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::swap(sibling_iterator it) + { + tree_node *nxt=it.node->next_sibling; + if(nxt) { + if(it.node->prev_sibling) + it.node->prev_sibling->next_sibling=nxt; + else + it.node->parent->first_child=nxt; + nxt->prev_sibling=it.node->prev_sibling; + tree_node *nxtnxt=nxt->next_sibling; + if(nxtnxt) + nxtnxt->prev_sibling=it.node; + else + it.node->parent->last_child=it.node; + nxt->next_sibling=it.node; + it.node->prev_sibling=nxt; + it.node->next_sibling=nxtnxt; + } + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::swap(iterator one, iterator two) + { + // if one and two are adjacent siblings, use the sibling swap + if(one.node->next_sibling==two.node) swap(one); + else if(two.node->next_sibling==one.node) swap(two); + else { + tree_node *nxt1=one.node->next_sibling; + tree_node *nxt2=two.node->next_sibling; + tree_node *pre1=one.node->prev_sibling; + tree_node *pre2=two.node->prev_sibling; + tree_node *par1=one.node->parent; + tree_node *par2=two.node->parent; + + // reconnect + one.node->parent=par2; + one.node->next_sibling=nxt2; + if(nxt2) nxt2->prev_sibling=one.node; + else par2->last_child=one.node; + one.node->prev_sibling=pre2; + if(pre2) pre2->next_sibling=one.node; + else par2->first_child=one.node; + + two.node->parent=par1; + two.node->next_sibling=nxt1; + if(nxt1) nxt1->prev_sibling=two.node; + else par1->last_child=two.node; + two.node->prev_sibling=pre1; + if(pre1) pre1->next_sibling=two.node; + else par1->first_child=two.node; + } + } + +// template <class BinaryPredicate> +// tree<T, tree_node_allocator>::iterator tree<T, tree_node_allocator>::find_subtree( +// sibling_iterator subfrom, sibling_iterator subto, iterator from, iterator to, +// BinaryPredicate fun) const +// { +// assert(1==0); // this routine is not finished yet. +// while(from!=to) { +// if(fun(*subfrom, *from)) { +// +// } +// } +// return to; +// } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::is_in_subtree(const iterator_base& it, const iterator_base& top) const + { + sibling_iterator first=top; + sibling_iterator last=first; + ++last; + return is_in_subtree(it, first, last); + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::is_in_subtree(const iterator_base& it, const iterator_base& begin, + const iterator_base& end) const + { + // FIXME: this should be optimised. + pre_order_iterator tmp=begin; + while(tmp!=end) { + if(tmp==it) return true; + ++tmp; + } + return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::is_valid(const iterator_base& it) const + { + if(it.node==0 || it.node==feet || it.node==head) return false; + else return true; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::is_head(const iterator_base& it) + { + if(it.node->parent==0) return true; + return false; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::iterator tree<T, tree_node_allocator>::lowest_common_ancestor( + const iterator_base& one, const iterator_base& two) const + { + std::set<iterator, iterator_base_less> parents; + + // Walk up from 'one' storing all parents. + iterator walk=one; + do { + walk=parent(walk); + parents.insert(walk); + } + while( walk.node->parent ); + + // Walk up from 'two' until we encounter a node in parents. + walk=two; + do { + walk=parent(walk); + if(parents.find(walk) != parents.end()) break; + } + while( walk.node->parent ); + + return walk; + } + +template <class T, class tree_node_allocator> +unsigned int tree<T, tree_node_allocator>::index(sibling_iterator it) const + { + unsigned int ind=0; + if(it.node->parent==0) { + while(it.node->prev_sibling!=head) { + it.node=it.node->prev_sibling; + ++ind; + } + } + else { + while(it.node->prev_sibling!=0) { + it.node=it.node->prev_sibling; + ++ind; + } + } + return ind; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::sibling(const iterator_base& it, unsigned int num) const + { + tree_node *tmp; + if(it.node->parent==0) { + tmp=head->next_sibling; + while(num) { + tmp = tmp->next_sibling; + --num; + } + } + else { + tmp=it.node->parent->first_child; + while(num) { + assert(tmp!=0); + tmp = tmp->next_sibling; + --num; + } + } + return tmp; + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::debug_verify_consistency() const + { + iterator it=begin(); + while(it!=end()) { + // std::cerr << *it << " (" << it.node << ")" << std::endl; + if(it.node->parent!=0) { + if(it.node->prev_sibling==0) + assert(it.node->parent->first_child==it.node); + else + assert(it.node->prev_sibling->next_sibling==it.node); + if(it.node->next_sibling==0) + assert(it.node->parent->last_child==it.node); + else + assert(it.node->next_sibling->prev_sibling==it.node); + } + ++it; + } + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::child(const iterator_base& it, unsigned int num) + { + tree_node *tmp=it.node->first_child; + while(num--) { + assert(tmp!=0); + tmp=tmp->next_sibling; + } + return tmp; + } + + + + +// Iterator base + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::iterator_base::iterator_base() + : node(0), skip_current_children_(false) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::iterator_base::iterator_base(tree_node *tn) + : node(tn), skip_current_children_(false) + { + } + +template <class T, class tree_node_allocator> +T& tree<T, tree_node_allocator>::iterator_base::operator*() const + { + return node->data; + } + +template <class T, class tree_node_allocator> +T* tree<T, tree_node_allocator>::iterator_base::operator->() const + { + return &(node->data); + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::post_order_iterator::operator!=(const post_order_iterator& other) const + { + if(other.node!=this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::post_order_iterator::operator==(const post_order_iterator& other) const + { + if(other.node==this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::pre_order_iterator::operator!=(const pre_order_iterator& other) const + { + if(other.node!=this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::pre_order_iterator::operator==(const pre_order_iterator& other) const + { + if(other.node==this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::sibling_iterator::operator!=(const sibling_iterator& other) const + { + if(other.node!=this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::sibling_iterator::operator==(const sibling_iterator& other) const + { + if(other.node==this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::leaf_iterator::operator!=(const leaf_iterator& other) const + { + if(other.node!=this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::leaf_iterator::operator==(const leaf_iterator& other) const + { + if(other.node==this->node && other.top_node==this->top_node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::iterator_base::begin() const + { + if(node->first_child==0) + return end(); + + sibling_iterator ret(node->first_child); + ret.parent_=this->node; + return ret; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::iterator_base::end() const + { + sibling_iterator ret(0); + ret.parent_=node; + return ret; + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::iterator_base::skip_children() + { + skip_current_children_=true; + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::iterator_base::skip_children(bool skip) + { + skip_current_children_=skip; + } + +template <class T, class tree_node_allocator> +unsigned int tree<T, tree_node_allocator>::iterator_base::number_of_children() const + { + tree_node *pos=node->first_child; + if(pos==0) return 0; + + unsigned int ret=1; + while(pos!=node->last_child) { + ++ret; + pos=pos->next_sibling; + } + return ret; + } + + + +// Pre-order iterator + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator() + : iterator_base(0) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator(tree_node *tn) + : iterator_base(tn) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator(const iterator_base &other) + : iterator_base(other.node) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator(const sibling_iterator& other) + : iterator_base(other.node) + { + if(this->node==0) { + if(other.range_last()!=0) + this->node=other.range_last(); + else + this->node=other.parent_; + this->skip_children(); + ++(*this); + } + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator++() + { + assert(this->node!=0); + if(!this->skip_current_children_ && this->node->first_child != 0) { + this->node=this->node->first_child; + } + else { + this->skip_current_children_=false; + while(this->node->next_sibling==0) { + this->node=this->node->parent; + if(this->node==0) + return *this; + } + this->node=this->node->next_sibling; + } + return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator--() + { + assert(this->node!=0); + if(this->node->prev_sibling) { + this->node=this->node->prev_sibling; + while(this->node->last_child) + this->node=this->node->last_child; + } + else { + this->node=this->node->parent; + if(this->node==0) + return *this; + } + return *this; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::pre_order_iterator::operator++(int) + { + pre_order_iterator copy = *this; + ++(*this); + return copy; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::next_skip_children() + { + (*this).skip_children(); + (*this)++; + return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::pre_order_iterator::operator--(int) +{ + pre_order_iterator copy = *this; + --(*this); + return copy; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator+=(unsigned int num) + { + while(num>0) { + ++(*this); + --num; + } + return (*this); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator-=(unsigned int num) + { + while(num>0) { + --(*this); + --num; + } + return (*this); + } + + + +// Post-order iterator + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator() + : iterator_base(0) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator(tree_node *tn) + : iterator_base(tn) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator(const iterator_base &other) + : iterator_base(other.node) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator(const sibling_iterator& other) + : iterator_base(other.node) + { + if(this->node==0) { + if(other.range_last()!=0) + this->node=other.range_last(); + else + this->node=other.parent_; + this->skip_children(); + ++(*this); + } + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator++() + { + assert(this->node!=0); + if(this->node->next_sibling==0) { + this->node=this->node->parent; + this->skip_current_children_=false; + } + else { + this->node=this->node->next_sibling; + if(this->skip_current_children_) { + this->skip_current_children_=false; + } + else { + while(this->node->first_child) + this->node=this->node->first_child; + } + } + return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator--() + { + assert(this->node!=0); + if(this->skip_current_children_ || this->node->last_child==0) { + this->skip_current_children_=false; + while(this->node->prev_sibling==0) + this->node=this->node->parent; + this->node=this->node->prev_sibling; + } + else { + this->node=this->node->last_child; + } + return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::post_order_iterator::operator++(int) + { + post_order_iterator copy = *this; + ++(*this); + return copy; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::post_order_iterator::operator--(int) + { + post_order_iterator copy = *this; + --(*this); + return copy; + } + + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator+=(unsigned int num) + { + while(num>0) { + ++(*this); + --num; + } + return (*this); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator-=(unsigned int num) + { + while(num>0) { + --(*this); + --num; + } + return (*this); + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::post_order_iterator::descend_all() + { + assert(this->node!=0); + while(this->node->first_child) + this->node=this->node->first_child; + } + + +// Breadth-first iterator + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::breadth_first_queued_iterator::breadth_first_queued_iterator() + : iterator_base() + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::breadth_first_queued_iterator::breadth_first_queued_iterator(tree_node *tn) + : iterator_base(tn) + { + traversal_queue.push(tn); + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::breadth_first_queued_iterator::breadth_first_queued_iterator(const iterator_base& other) + : iterator_base(other.node) + { + traversal_queue.push(other.node); + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator!=(const breadth_first_queued_iterator& other) const + { + if(other.node!=this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator==(const breadth_first_queued_iterator& other) const + { + if(other.node==this->node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator& tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator++() + { + assert(this->node!=0); + + // Add child nodes and pop current node + sibling_iterator sib=this->begin(); + while(sib!=this->end()) { + traversal_queue.push(sib.node); + ++sib; + } + traversal_queue.pop(); + if(traversal_queue.size()>0) + this->node=traversal_queue.front(); + else + this->node=0; + return (*this); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator++(int) + { + breadth_first_queued_iterator copy = *this; + ++(*this); + return copy; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator& tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator+=(unsigned int num) + { + while(num>0) { + ++(*this); + --num; + } + return (*this); + } + + + +// Fixed depth iterator + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator() + : iterator_base() + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(tree_node *tn) + : iterator_base(tn), top_node(0) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(const iterator_base& other) + : iterator_base(other.node), top_node(0) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(const sibling_iterator& other) + : iterator_base(other.node), top_node(0) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(const fixed_depth_iterator& other) + : iterator_base(other.node), top_node(other.top_node) + { + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::fixed_depth_iterator::swap(fixed_depth_iterator& first, fixed_depth_iterator& second) + { + std::swap(first.node, second.node); + std::swap(first.skip_current_children_, second.skip_current_children_); + std::swap(first.top_node, second.top_node); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator=(fixed_depth_iterator other) + { + swap(*this, other); + return *this; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::fixed_depth_iterator::operator==(const fixed_depth_iterator& other) const + { + if(other.node==this->node && other.top_node==top_node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::fixed_depth_iterator::operator!=(const fixed_depth_iterator& other) const + { + if(other.node!=this->node || other.top_node!=top_node) return true; + else return false; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator++() + { + assert(this->node!=0); + + if(this->node->next_sibling) { + this->node=this->node->next_sibling; + } + else { + int relative_depth=0; + upper: + do { + if(this->node==this->top_node) { + this->node=0; // FIXME: return a proper fixed_depth end iterator once implemented + return *this; + } + this->node=this->node->parent; + if(this->node==0) return *this; + --relative_depth; + } while(this->node->next_sibling==0); + lower: + this->node=this->node->next_sibling; + while(this->node->first_child==0) { + if(this->node->next_sibling==0) + goto upper; + this->node=this->node->next_sibling; + if(this->node==0) return *this; + } + while(relative_depth<0 && this->node->first_child!=0) { + this->node=this->node->first_child; + ++relative_depth; + } + if(relative_depth<0) { + if(this->node->next_sibling==0) goto upper; + else goto lower; + } + } + return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator--() + { + assert(this->node!=0); + + if(this->node->prev_sibling) { + this->node=this->node->prev_sibling; + } + else { + int relative_depth=0; + upper: + do { + if(this->node==this->top_node) { + this->node=0; + return *this; + } + this->node=this->node->parent; + if(this->node==0) return *this; + --relative_depth; + } while(this->node->prev_sibling==0); + lower: + this->node=this->node->prev_sibling; + while(this->node->last_child==0) { + if(this->node->prev_sibling==0) + goto upper; + this->node=this->node->prev_sibling; + if(this->node==0) return *this; + } + while(relative_depth<0 && this->node->last_child!=0) { + this->node=this->node->last_child; + ++relative_depth; + } + if(relative_depth<0) { + if(this->node->prev_sibling==0) goto upper; + else goto lower; + } + } + return *this; + +// +// +// assert(this->node!=0); +// if(this->node->prev_sibling!=0) { +// this->node=this->node->prev_sibling; +// assert(this->node!=0); +// if(this->node->parent==0 && this->node->prev_sibling==0) // head element +// this->node=0; +// } +// else { +// tree_node *par=this->node->parent; +// do { +// par=par->prev_sibling; +// if(par==0) { // FIXME: need to keep track of this! +// this->node=0; +// return *this; +// } +// } while(par->last_child==0); +// this->node=par->last_child; +// } +// return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::fixed_depth_iterator::operator++(int) + { + fixed_depth_iterator copy = *this; + ++(*this); + return copy; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::fixed_depth_iterator::operator--(int) + { + fixed_depth_iterator copy = *this; + --(*this); + return copy; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator-=(unsigned int num) + { + while(num>0) { + --(*this); + --(num); + } + return (*this); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator+=(unsigned int num) + { + while(num>0) { + ++(*this); + --(num); + } + return *this; + } + + +// Sibling iterator + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator() + : iterator_base() + { + set_parent_(); + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator(tree_node *tn) + : iterator_base(tn) + { + set_parent_(); + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator(const iterator_base& other) + : iterator_base(other.node) + { + set_parent_(); + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator(const sibling_iterator& other) + : iterator_base(other), parent_(other.parent_) + { + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::sibling_iterator::swap(sibling_iterator& first, sibling_iterator& second) + { + std::swap(first.node, second.node); + std::swap(first.skip_current_children_, second.skip_current_children_); + std::swap(first.parent_, second.parent_); + } + + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator=(sibling_iterator other) + { + swap(*this, other); + return *this; + } + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::sibling_iterator::set_parent_() + { + parent_=0; + if(this->node==0) return; + if(this->node->parent!=0) + parent_=this->node->parent; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator++() + { + if(this->node) + this->node=this->node->next_sibling; + return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator--() + { + if(this->node) this->node=this->node->prev_sibling; + else { + assert(parent_); + this->node=parent_->last_child; + } + return *this; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::sibling_iterator::operator++(int) + { + sibling_iterator copy = *this; + ++(*this); + return copy; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::sibling_iterator::operator--(int) + { + sibling_iterator copy = *this; + --(*this); + return copy; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator+=(unsigned int num) + { + while(num>0) { + ++(*this); + --num; + } + return (*this); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator-=(unsigned int num) + { + while(num>0) { + --(*this); + --num; + } + return (*this); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::tree_node *tree<T, tree_node_allocator>::sibling_iterator::range_first() const + { + tree_node *tmp=parent_->first_child; + return tmp; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::tree_node *tree<T, tree_node_allocator>::sibling_iterator::range_last() const + { + return parent_->last_child; + } + +// Leaf iterator + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator() + : iterator_base(0), top_node(0) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator(tree_node *tn, tree_node *top) + : iterator_base(tn), top_node(top) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator(const iterator_base &other) + : iterator_base(other.node), top_node(0) + { + } + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator(const sibling_iterator& other) + : iterator_base(other.node), top_node(0) + { + if(this->node==0) { + if(other.range_last()!=0) + this->node=other.range_last(); + else + this->node=other.parent_; + ++(*this); + } + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator++() + { + assert(this->node!=0); + if(this->node->first_child!=0) { // current node is no longer leaf (children got added) + while(this->node->first_child) + this->node=this->node->first_child; + } + else { + while(this->node->next_sibling==0) { + if (this->node->parent==0) return *this; + this->node=this->node->parent; + if (top_node != 0 && this->node==top_node) return *this; + } + this->node=this->node->next_sibling; + while(this->node->first_child) + this->node=this->node->first_child; + } + return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator--() + { + assert(this->node!=0); + while (this->node->prev_sibling==0) { + if (this->node->parent==0) return *this; + this->node=this->node->parent; + if (top_node !=0 && this->node==top_node) return *this; + } + this->node=this->node->prev_sibling; + while(this->node->last_child) + this->node=this->node->last_child; + return *this; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::leaf_iterator::operator++(int) + { + leaf_iterator copy = *this; + ++(*this); + return copy; + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::leaf_iterator::operator--(int) + { + leaf_iterator copy = *this; + --(*this); + return copy; + } + + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator+=(unsigned int num) + { + while(num>0) { + ++(*this); + --num; + } + return (*this); + } + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator-=(unsigned int num) + { + while(num>0) { + --(*this); + --num; + } + return (*this); + } + +#endif + +// Local variables: +// tab-width: 3 +// End: diff --git a/core/opengate_core/opengate_lib/tree_util.hh b/core/opengate_core/opengate_lib/tree_util.hh new file mode 100644 index 000000000..bc69594f0 --- /dev/null +++ b/core/opengate_core/opengate_lib/tree_util.hh @@ -0,0 +1,92 @@ +/* + + A collection of miscellaneous utilities that operate on the templated + tree.hh class. + + + Copyright (C) 2001-2009 Kasper Peeters <kasper.peeters@aei.mpg.de> + + (At the moment this only contains a printing utility, thanks to Linda + Buisman <linda.buisman@studentmail.newcastle.edu.au>) + + This program is free software: you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#ifndef tree_util_hh_ +#define tree_util_hh_ + +#include <iostream> +#include "tree.hh" + +namespace kptree { + +template<class T> +void print_tree_bracketed(const tree<T>& t, std::ostream& str=std::cout); + +template<class T> +void print_subtree_bracketed(const tree<T>& t, typename tree<T>::iterator iRoot, + std::ostream& str=std::cout); + + + +// Iterate over all roots (the head) and print each one on a new line +// by calling printSingleRoot. + +template<class T> +void print_tree_bracketed(const tree<T>& t, std::ostream& str) + { + int headCount = t.number_of_siblings(t.begin()); + int headNum = 0; + for(typename tree<T>::sibling_iterator iRoots = t.begin(); iRoots != t.end(); ++iRoots, ++headNum) { + print_subtree_bracketed(t,iRoots,str); + if (headNum != headCount) { + str << std::endl; + } + } + } + + +// Print everything under this root in a flat, bracketed structure. + +template<class T> +void print_subtree_bracketed(const tree<T>& t, typename tree<T>::iterator iRoot, std::ostream& str) + { + if(t.empty()) return; + if (t.number_of_children(iRoot) == 0) { + str << *iRoot; + } + else { + // parent + str << *iRoot; + str << "("; + // child1, ..., childn + int siblingCount = t.number_of_siblings(t.begin(iRoot)); + int siblingNum; + typename tree<T>::sibling_iterator iChildren; + for (iChildren = t.begin(iRoot), siblingNum = 0; iChildren != t.end(iRoot); ++iChildren, ++siblingNum) { + // recursively print child + print_subtree_bracketed(t,iChildren,str); + // comma after every child except the last one + if (siblingNum != siblingCount ) { + str << ", "; + } + } + str << ")"; + } + } + +} + +#endif diff --git a/docs/requirements.txt b/docs/requirements.txt index 152249441..5d23b8347 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -23,3 +23,7 @@ jsonpickle pandas requests PyYAML + +requests>=2.32.2 # not directly required, pinned by Snyk to avoid a vulnerability +urllib3>=2.2.2 # not directly required, pinned by Snyk to avoid a vulnerability +zipp>=3.19.1 # not directly required, pinned by Snyk to avoid a vulnerability \ No newline at end of file diff --git a/docs/run_rtd.sh b/docs/run_rtd.sh index bf4212f3f..00061021c 100755 --- a/docs/run_rtd.sh +++ b/docs/run_rtd.sh @@ -16,4 +16,5 @@ python -m pip install --upgrade --no-cache-dir sphinx python -m pip install --exists-action=w --no-cache-dir -r docs/requirements.txt mkdir docs/output cd docs/source -python -m sphinx -T -b html -d _build/doctrees -D language=en . ../output/html \ No newline at end of file +sphinx-build -T -b html -d _build/doctrees -D language=en . ../output/html +python -m sphinx -T -b html -d _build/doctrees -D language=en . ../output/html diff --git a/docs/source/conf.py b/docs/source/conf.py index d861e8a33..56adc54fc 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -23,7 +23,7 @@ # sys.path.append(str(Path(__file__).resolve().parents[1])) # print("DEBUG: sys.path = ", sys.path) -# sys.path.append(str(Path("..", "..").resolve())) +sys.path.append(str(Path("..", "..").resolve())) autodoc_mock_imports = [ "opengate_core", # "colored", @@ -131,7 +131,7 @@ # # html_theme = 'sphinx_rtd_theme' html_theme = "pydata_sphinx_theme" -html_logo = "_static/gate_logo.png" +# html_logo = "_static/gate_logo.png" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/docs/source/developer_guide/developer_guide_installation.rst b/docs/source/developer_guide/developer_guide_installation.rst index 4873075d3..e13a5ff13 100644 --- a/docs/source/developer_guide/developer_guide_installation.rst +++ b/docs/source/developer_guide/developer_guide_installation.rst @@ -20,15 +20,15 @@ Virtual environment ------------------- :warning: It is highly, highly, *highly* recommended to create a python -environment prior to the installation, for example with -`venv <https://docs.python.org/3/library/venv.html#module-venv>`__. + environment prior to the installation, for example with + `venv <https://docs.python.org/3/library/venv.html#module-venv>`__. :warning: If you use -`conda <https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#>`__ -instead to create your environment, be sure to instruct conda to install -python when creating your environment. You do so by adding โpythonโ -after the new environment name. Optionally, you can select a specific -python version by adding โ=3.XXโ. + `conda <https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#>`__ + instead to create your environment, be sure to instruct conda to install + python when creating your environment. You do so by adding โpythonโ + after the new environment name. Optionally, you can select a specific + python version by adding โ=3.XXโ. Example: You can create a new conda environment with Python 3.10 installed in it via: @@ -49,8 +49,8 @@ First, clone the unique repository that contains both packages: git clone --recurse-submodules https://github.com/OpenGATE/opengate :warning: When you update, the data for the tests must also be updated, -use : ``git submodule update --init --recursive``. This also update the -included subpackages (pybind11, etc). + use : ``git submodule update --init --recursive``. This also update the + included subpackages (pybind11, etc). The subpackage ``opengate_core`` depends on the ITK and Geant4 libraries. Therefore, you first need to download and compile both @@ -62,10 +62,10 @@ STEP 1 - Geant4 and Qt ---------------------- :warning: When using conda, be sure to activate your environment before -compiling Geant4. The reason is that conda comes with its own compiler -and you will likely have mismatched libraries, e.g. lib c++, if not all -installation steps involving compilaton are performed in the same conda -environment. + compiling Geant4. The reason is that conda comes with its own compiler + and you will likely have mismatched libraries, e.g. lib c++, if not all + installation steps involving compilaton are performed in the same conda + environment. Installing QT is optional. Currently, QT visualisation is not working on all architectures. @@ -139,6 +139,12 @@ The pip install will run cmake, compile the sources and create the module. If you are curious you can have a look the compilation folder in the ``build/`` folder. +With Windows, change the `:` with `;` (https://cmake.org/cmake/help/latest/envvar/CMAKE_PREFIX_PATH.html) + +.. code:: bash + + set CMAKE_PREFIX_PATH=<path-to>/geant4.11-build/;<path-to>/itk-build/:${CMAKE_PREFIX_PATH} + STEP 4 - ``opengate`` module (python) ------------------------------------- diff --git a/docs/source/developer_guide/index.rst b/docs/source/developer_guide/index.rst index 25c01eed8..1aa5a3fc6 100644 --- a/docs/source/developer_guide/index.rst +++ b/docs/source/developer_guide/index.rst @@ -1,24 +1,21 @@ Developer guide =============== -.. code:: {toctree} - +.. toctree:: :caption: The Basics :maxdepth: 2 developer_guide_installation developer_guide_contribute -.. code:: {toctree} - +.. toctree:: :caption: Get coding :maxdepth: 2 developer_guide_code_structure developer_guide_how_to_implement -.. code:: {toctree} - +.. toctree:: :caption: Good to know :maxdepth: 2 diff --git a/docs/source/figures/example_lut_davis_model.png b/docs/source/figures/example_lut_davis_model.png new file mode 100644 index 0000000000000000000000000000000000000000..ddd68a01f423d1ece44d89f508778dfc6a219f45 GIT binary patch literal 246838 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	>r=fIV}ISkxXa6B)cDS*WJx* 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 literal 0 HcmV?d00001 diff --git a/docs/source/figures/flowchart_lut_model.png b/docs/source/figures/flowchart_lut_model.png new file mode 100644 index 0000000000000000000000000000000000000000..c5250ba93c05509df3fb64a69cf8dad1238d6bc0 GIT binary patch literal 101931 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%0D$8}V43Hg6=uRe%qCdtY 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 CbQ=Bu literal 0 HcmV?d00001 diff --git a/docs/source/figures/generic_source_spectrum_discrete.png b/docs/source/figures/generic_source_spectrum_discrete.png new file mode 100644 index 0000000000000000000000000000000000000000..b41b795bc11165e46cec4a90ed30916f49891d2b GIT binary patch literal 60728 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 literal 0 HcmV?d00001 diff --git a/docs/source/figures/generic_source_spectrum_histogram.png b/docs/source/figures/generic_source_spectrum_histogram.png new file mode 100644 index 0000000000000000000000000000000000000000..7122ede8a674b854d3269b18cf8b318a3fe563f9 GIT binary patch literal 53293 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 I`uW%Y2h`<FUjP6A literal 0 HcmV?d00001 diff --git a/docs/source/figures/kill_actor.png b/docs/source/figures/kill_actor.png new file mode 100644 index 0000000000000000000000000000000000000000..5aba507676c9d7c6313f873c429d323ea54f9d15 GIT binary patch literal 125552 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 literal 0 HcmV?d00001 diff --git a/docs/source/figures/optigan_working.png b/docs/source/figures/optigan_working.png new file mode 100644 index 0000000000000000000000000000000000000000..05fc34f6492a0f57050b7df634017c9c058a599f GIT binary patch literal 423722 zcmeEtbzEG_vhHBPf`tGfKyV4}?oJ5qZoyp#w*bLCxJz&d?h@SH-QC>=-muTv+56o4 z&VA?o{oef6uok_#s=L1GuCA`G36hl-Lqd3u0001xB*cZ~0RY$x007Dg?iIMC?g{G# z06-uz6%vw_5E3GgwX-%dwJ-z##Dl)a!78H9z3(~s?3zvLM?^X~TI5iWiS=>Z(hph? ziBJ&@ry{>Cn1~26n4st_j7n~=I;tEsHDs_BTQ5;YaR`i@+{LRoN8kw8N%O_ij{5~@ zJDFpp+5Uvb_z4ghJqs!Dh8l*Skx%R0=2|;kMBtazZU}^Yz(?;_^vN+~Hpp<NFo92l z&y$T@0QKJ5*I2Hghl^)QQ>)MKVF45XJqWh2m{1$G_G4ThdOX0ph@Ya$4Z1!^s6XJN z;E4{a+L}a=wO4mV!XNIXZBP-{Gy$hyST%Z~yzB4^m>4;@b5P15^w4?P(jrcI{P5Uq zEocEGE`CVSF$$Qo7<#F@tygQHY~#z?Gz-=f96HYdl`*xC5+AVz1Ly|6|I#P#5<{HZ zV_1B)w|$C_j$wSlwbp~}WpGEqA~_(;gjOYhAt8){@biXnk8)EXsRZB@bbmF;>3_9H z@QNVjQ$(M%j!F9`v|{w`i}i8r;a=%(`lef4VyJ+`pu~>=#2tOxe74K7Y=O%Iuc?|h z5I=W9y4EYbrrdcUZPby}aK?04w&?+HAOWSv+h5?Au*`S;5nrMDkYqr>Y%ZDt@O0q@ zy-9yUNw*<6LwTXTCTe@%24PEpo#=wu3!}jgK>^v=rY#F^)}}WK2>%+B0hI!S^$WuW z>a{Q0DQaLFyEBAQ8y7u_04j18zoIyVynhveNAK&309*pLUMvJ(q6~^a*t5*n6j<{J z7y`Mnq>6|vf_#F;86l%ACU6ZXVS);y=DWn9zJ2sa$^q4S2D9&-0z6iAommFIDy-qM z<1fRjv^kzK)Zw+jt$2&+qCG;Ui9o!C7ZEGSd@VOkf!U9b5fm%}FNY)NpKqs#R|qrZ zzb~{T7$iH9k77cd6l|1%H0ER?;{ccRRa(Sa7%Q7<3~ATNf$kpD^)1iWC*MrnSo-^S z8lhNZxE&o|^bqK`5{MI62Mq_|jBqcIL%PPxKNn+6qvQBUwUzc@8ldQElrPN^9=>zJ z{MPYt_0sm5-3u2dKAfuUs0Xh@s|{(R+xFy)q=|C*^+|X{=f#=Jt(`mi6SwC(KA&`Q zakMJ98fY)b_@Cjjg8AeQXjX5!;V*<uiDFD-UXzib)}m4c(sT}VigmJ)ypc%#B7{KF z6~h`sDNZh~B9Wbg@&&u%L!yYb$S}bP2|g(><|JlCmg22tFgj^;wDg>WX^v(VcV<(3 z#;11@&Js4HtP+Y6Rg$kIn_@}C%_KM_OQQy(sN=awT@>upg9&nJx}x<ZEn^F#If)%r z%#@QTEh&%aQmM75mq<LMT;*SpXVYwub$suO=b*NiFbWOMGLuzPM0TXAPIFD<l<pW$ zV5U=tED3Z<VdYPb`W~f4i9?x8qe1E>tt>1yR=uZvN#Y6*A^{<TvQsKjDm6nFk?S$* z_3An9NYACu8P5IBOU{YU@iE>ZSRn)<G-7bR{Xmt4arG7oqw*~ZrM7a4wxEW7tzTu1 zO<x}=N~|s^PcA|3)u(UY@4p*;SBm9{h3S9zPWMr!XjCah@r`1?;&9RD0(m7o#SNta zMY?=m<6g!TY>7|=Q91)rDTyHk8le(pOMXl3)PfILX?e?qPpV(@^-9o+<bI50Gh`PO z?&T#G%@yDAhh!L*VB}TH6v>!NCl}!TXqIXea*O^ra;d*fpPU%sK*1T#S-|PYS?gHd zFx?Q;0P6sIrOSiZ%-&4doP7VYCD*g*2?9h273vcPv63;oT1q%KI!8b8bIxWm&}!2> z)_79ya$I$SecZX$HvJ3>9WjO^#x>^K55^zMKU{uj{U{r{9Ar0AGO8W=xP82xJjgYq zLo=oGzVdQ)+znF^kuSs&lLs@Mri9u*jwVh)IZ0Vgc}f{rVmb{m-8HRZNzY2lnrtyV z@BXv8N~)H}^r0~|Q!7g=W@(dgyCT8xYM}dY>S*q|`kDwfKq$6@5H>11iFl!W_^co% z0DWdJaKf*$)ZX`kVJDuJj8-t7HJ&4m#vH0>0b^@_O?{Vhm23lZU3u@iQ)?-8XZetK zLvO8oIeP(pV{fB=;i5FY0;{KJ%)EP{-|Z`~3}w#PUnX}%L5D{t(>fmiaObGD=VZ*T zWb1JY@2PENz$@Z;17O5&)+XaK+veR?*k&y7L!e&3gDRhDy3|DdjCPi^j^tJ^P+DA6 zTqgg^ZsBP6Xg&SR&vAOWI@{ZTQyjElv@^8SsJh<KUgD@qswL%PEsmCceWhQ8Tt$gR zl`1+K*>z;5L-p0s=5yvUB6?ZvSxH&E<)Aso!#R_(-Thr`<dDjU3*UoInhoi$*`C<$ z&z<u<aoy7Ahc0f;3bx_4TecHt#kc*pD)4FWEb#V0PeEenT@rc{C4IM>`I}7c*q%hK zY$BwjT!!KqbQGw8@|FrWKZfBv{KUpHs-{d+_n0?f7t=Fi&a}Eseh3*UZ9f!`7Awq+ z&pjSKIl3M=6({%Fj@;?-xbhf#$dsxd$1RQ_4da-x3F2h4U0aOL#<rTeSN3cN&~(xS zX?PAZ^-&GEHL_TktF+iXZ9kTuaJQJ#MpB?+E-o3>mKbrlx;<X|;B9kVpWMp#(llv* zkyq5dEJ#pr2f0n0P_<y4gf4N&>Z{q=U^!uV&7>}cH{yKCm9+oVbA5YcN@oy@5!bxb z=yXzjp1Z~J5ct4&9YpC)#d%|Xyi)zFV5qqz{#{bZyjuP$e7_!Q5vl_=6}e`TV&?6X zJNH|!UF{MvOSe=ftvkKLwZV>&eW8N~&!113XMyWcS;XlP=Mlm0ec!v{l)T4Dj!pWM zw6-0(U0_fYj`RV@vplCiE5Go`nuZfX`b&!Qlfh&JQ2IXQki*cUaI0#1Z=cmnLyv2+ zrPSjGajDQG_85ml_rqtIcrBF$8Bsnwo+783{glJ{=!?#ayPYm9JK8}f#D+Y&bK3B$ z;v*O5kB{R=%Wca(1HAEN+P1BB1taw*?Me?t#$|KbT=q-%u02QtI7c8%kR_kS<07jr z$EfGTzR^y`knYTAMdn3Ecn6ks{c?ld!ohV|)E2Q2Q1E!Mv2nk2_ruNdCMf5j?86t{ z1h2X~v~9UHgMywdnb@>RP}YNFQLns%)udz9^ImWsN)NjMou|~p)r9jCQ-9?-{8Ui4 zL}g#4!m+~F5fxs1FG6PqKCRQ_OY12*NA0wyrM;V{#M8txm3Ec&nJ(+M%eWqmmzE<I zZHF<yzLV`8gcYhL>swt==83^<PtaYlOq~oyx+5RZ<LvHSJcSw)aaQnJh00qHGCT}s z^-2yvfd-I;MSK2fH&zQ;v(ISdNPBJzSw1eJhA<J3AN6hAd$kJvW<EJ;S)5vRSeG1- zoa`0C7mI0bRWlnxEnM8^R(>1C2&kh4@FI4t-$Ci0?YMraxPjP}*18NqY$u|<j|4%6 z^@=@nJh6cgzbkYsX2P;Eft|)+LsbbQDJcLY_!$lW4e=fT1Ac-4e|aHrem{#sPynF* zD2D_9zL^4`|5-;Ge1G{wg1;{`|F}a%2LNEf|Gfc!oiiZ+tPPuy0rlrIloj|LKtMrA zLIQkOFt9T;w6Zs`c4+xH{o>rh+lZ^#0|1z$FJA}=d6H8w{~1$7RR>k6&zuI<mb7|b zto03Poh@x%_yD+_Il+&Xh7NiJ&XyKd_MFZ<M1R!a1V6tN(-9H;QN_WWhe%aQmO#kb z&X9nWmY$ZLh!=r?fPmZXixH>1u;@R@!T<3PnK(GuaMIBMfk0Xy6RowKF&zU32L~NJ zBON0n4Y&r4y^EEDo->V=J@G%7{K-ey(B8n#)W*To+KS+Xub#fOqXQ2S(Mv<WKmX{b zp|j~<Em_(BQ!KE6bT4n{7-;F~e)9&Ca=(;v%9=VGTBr$|T7oeHx53NK#?Jjm{r~pn zuNMDCsrnZs0|yJ^-%0=W>c5gI*&EskSzChJbm09<U;iZj`^$e4a?`zt{x>N8f%6}w zV4!&sxaoc?jTd19Gxt4MkGQ77GK%0kc$B?-AZo#XDE@H=KSODki_Jv&0s#B~31I<6 zXNbd8Slzc%@BN=6Ma6fUZ;;1fa<D`o<bL^T#b!}R$rWVH%F4bK$fbbvUG4CN%gU!{ zmON<=&=x}QX}VaMoBPZ<7s>A(ney<Vv62aRy!fy*2XV7_@yyFW(Jlx{!vCLMGvaOE zATL?oZ0`N%Gywn@lV!O7y#7Z)Z%Bt=-7m!dImz1$7nDc<{C{}mz(F94e)NqB`w#1b ztI2wA^Zdskkwg+fQ}E}(iL(A*Knsy{^B)Tk$sdfG0Z1i!HwX8h${hKk1gQTU(f_Z6 z|D!hkzY_k>!|DIcnNS-F@US?jO`&;10P*{R^;tAbw{c%5`=6%lqC4c<4C^P{pRj*n zYoi3VCOR0bX~O^BO55OpO;k&`%^dSz+%;vuyvoS6YNY-LI+hpSNGxeWf7L1Q2Egnp z(!(D@{>AIRT1tLgFz=1kRoLI+y{U7A19!o3JD7*`FQNZ>{epzgr6Aa}yC#~3$3TmF z|8Wrvp@A*g_K#R7qJK-Jkp}LNh9?3C;7@iwm!Do3rMiSC|H<ed?Edywo(^o~3xHO$ zOn*{Ayb+HDa}qu~CHVJ}wMh~1L)??`?~qFTW=22?7EKyH*y8KIZB3kj6s!Shi6o1V zzv@^KfV;qk%1S5l7rKAx^&jg=BncSF7{8$J{{#fwD8bbWCimNZJpUe(OX(K?TA|oI z|CG*0Bklzzr~O&mKa2hwqT-b=3)5V|R@?8^#yP=#H1!DJ;QhB$byqK%z1j}tA^VG? zkT)c=HT1uwA@G?L1^3V_8SVPpUw}l5gUj9sl)wKU@JAvB;~$W@?f<)r;sk~-*s7&j zg#E?puebkD)9U=o3~&mt@cFBb6~YVtFkEz>{s;VDoPnUWHNw9{5g&R%X?sp@<-a!a z*NE_V16C35PHOgVNW`IDsC*<gQU5(v*o$Jj$-vQ$KY<kIgSNtenD6N!2Yp5m``yn- zO8Cys&YXVFa)o~xFp-!qNQ6}OGerHlKII@n)<y${@I5BFX!-=A|540eNRkn<c55A_ z$iZ^>_HStOzbEB~;0%+d{`PxJMqa=0e6Sxx`zJ8~UXu$9%FJ*i&A-fhe!G`xfJ`&} z*97-b6b5UQGU>IG|DUiEOuay3mKT6W{HKuMxi6mmg3efw6jBGL?S6{Ig6*h7uC7mt ze2(t%7a$cSrMhzI5@8Z2quVs<N0kQIiUn~La=#Beqys9}@t|+veO)2(IhqB6cEHMg zHjCYOTCMJ@+8}8~aU0PB&6ZuHMpnAmYxu)?97?h9R7`>Xnoo7LjNhPhlLYWz^L%`` z#%BS2RLb(+98M&ix(vgUz<=pZgv&O??^#|N2A)3V8$Ff3OJ4LyP!Fz19{=uuJdyty zNZvOq;gxXToYpFHf9+1+w=<9N%w`bPba@~D1`49V6twVVe^%v4$Q_9G?rG~a_EQ(# z8#L@KqiAv<>)g*(rkkCE=5o^mPg8dtKKn%O`ilMlf8w$m(m78~BH5cX6~molS*CRz zGL%S8mi`|}V|>$w36EDNoF*F#jO)$P<1LmQiW6*3BpxsBAMbWzHp!n5*Rj4B`!M{G z-{bKQgS+{n3r+pv^e>Pe%V`ukGKuDzA@J~{+2n&!aymnzZVcmReVF6T3J1=XG7m19 z4hKx{ht=UmuL$@fvH?eamk%pNTYaS+c#ZDfl%k5>+FnpFyI~sF5-|@C+4#KC<SKEB zH2lB=CebIYrscZoZ>^vdW^>i9UlGX#QT#`k&4we(#C4S(U&x?$=UMu5lB{Y@m=&M2 ze-2+7cRr-%f}oJ(rB-I6E(O5D=sv+XJo?XJv^xJ{bXYyP|JA}rOM)xf1(kb7!(aLX zKvQ4fMp46Dc~;2Mpg5-qkBE6VYvZ2i_;AaPO!;Uctvq@XKvMfEai$yEAu8lNC%gCi zSd1RqZ%{G&qIjSMpS0v_6a_e34@0>hoG)+!$fhBVi0cXYtv`1m?<z+i$6-8Mvgs|g z-d8nNwG>SwZFC}P;rYx1ui};-!V_YQrLifhi}Gk8T$g>XP<z(Ae*ioj(FZF_gI<PG zdr!>op)?K;o=|&VvN%5fWo8I`6v0N+p)fJHY9{JVlC?iz$ZgB+bLeEDI%k^s7-ohN z_tA20*yYKUEH|7+4Sr<nw#J%e2u43~iH0n-gP_^6Q%3Ph73_F?o$%A`>(r~Vnd|Nn z8~&9%WD_4iDvJ{0D9r+jsP@?r{qjTaE!t8J2F&h(qehNuV@yjr`OdI^vav*?+rg)& zCJy=HBXdiW?UMn+6`u`JZ!2Uro{LExD!H_D&6nj#GW6;%QHWnev7P2H`nxE}65fzi zUp<|jb3#E*rGp90imVoMbPNpfLu2bA=YlzfAL)1(<n6Y5*V(@&&j@DV+X+WZ;Eu^= zxH(x7gWjNKl;wghZLB6>U#)5{nYX9N;9V3lTRJaDVPC)UTeS*6U8MBOyT%1e)JjS< zb4Z`k@<9OhQmS6hSq|)1yalfroL#aNzm2#L^Jg%$@6$Aq|Gs1pD1oQLQBs_gjg8YT ztKjvaSejhE$$q~QgH%X!DzTdp_QhGq_)ja51MneKSZEW)`{moqIr;*{mO!xKxGF4t zkA?=5X_ubD0~DZ)qat#NB4+0xg#^tuKIh|dyF+G%pc!!(WA??fHEuS%5@=cBLN~;P z_98u`fd<;$o6PXoo~Ps!v(dM$gtCyZXM0f0os7lP_H_?EY@S9b$k*x8ZdhGj4MiS0 zl{&O1R$_%}Xm;ExQ|c&I7{a%Vf82Ivf*U<pW1L}hq=w#o^^usrXH_zTFrxN^lbWN# zUp&gUp49ro$xW!I*Lxh;s%4x+!;LJtA2LDBxO^^{2Fv8wjJM*y9yxMV*SMoKYp`gA z`#IU$I&8hLC?h2iuzEjYGrb~lQe!~gWwGJU4t*~ER@G2xly#K~d*$}i{zmVDCtt2Q zcC(X-Fw<^z)OM}{l%e+{T-A$j#jlXmG(G&N)sbt<UDYpE!1*f6-!Az?H(FX+pOk5( zx4a3@VB&7tjYlW#P=cGws+!4Jxn%CAMuj0#+^4Nz*VGAF_dJI);hJJXqo<1v7jR^L z@y=$>Z8aYknaBU6HNDTj^hS9*GJ}MrB=iQ4-DrD;m(QyOH=4!%2T5vr?5BR$k(TUR zE4=8ex*bW3k(2Lo^79kM_g)7;35QEk?sSB&hWobP$a9~I?T^3)U587k=J$5~f3yC9 zm%SWX@W5xlZzO1{ayE&~w^@hH%(pCR7zYqlZjUe3@BF0JsMh-N#I8e>mDRa2qw;if zbzs+Xw54D{MyDMbR~Y8j-?eK}wMSg^bg#->d@vj2)f<-zyD4AwX)TUYo5G8>zS-3j z`*J@UPAIuf7yXs3Rd3$J){4kMSfumaxnX?%F|kWjUhSvcH|MT5PV)7Fa%Bc{H5ZZY z3{Px#dfWO;o*Lh4MeZ7^`}>%VlKY`_x7B#xoqhcd^<L}bs@>yU@SEGF7ZYX>XL_cd zq_cCZu&5Sup5g1)-Zr}tmiQkP7+W7Xw~#8muG5Qhj9YDI!=t#86~D{jb6a1#wdAYb zU;i`}4U~&oq~zV^Bn{riINHH!UciwXywD_nZmO>w@YPv~Z^bH(8EH)mlO5vPZ1<-~ z(LYm>dq}(<`m#!eA_=G|%f;n!QeVd9R8l6wKl>2)Ff9Let=n9g%Kw0y2!_C$W1#JZ zJY>6j`a9aruvjl?;3g-&fPl|#OnSlnws}OK!O?q88OiY)gd1y*TRiq-))m+ZdO@Xw z+p8A05{*V^EM1Gv^#C#(esc+G=-OaIYrVl|YE8Na!g0<J1mn~+3%z4>kZqd={8#bY z;hvQN-`wOcu+R$4WhEaV;fKw~a*{5EhtKwFzdNuo?0)^oc$If&WE&J<*Wzbi^~H|q zBC4k3pf}P-CK*I2f3oNyp6xJj&d1Am6%hU?*K&7EXH&0RdNa}a<cu84baf>nG#v4c zX@VM#-SM9LTD2MB^rovaC$h>yTgWv-$J05$IgSMt7614)lOqr2_+li$A*$>r`^JL~ zX8(Pdn+TTR>{mrSAmBWg?@C-@#3&l1wqtqvv!KFtiPpSjiRM<fMbahpgzs64N=xSf zverL>d%o1C>25xo(PC1@*{US6gbUf1sfBLW=Er9Uvd1SksI=H|QUq~D#j5({MX#A& zD{f$Fu37H-xoZS$t{P+rg*x9gYnq-3*2MKhr>Bu#(8c0bQElSRWJ;^b#)Ixq3DO{& z&@0pBx<UufLVxCbg<md7ZW?%J_Z~ABO1;%1O4}{i?qhlV<M2Cd4cXBTxNaL=Gp(`p z&&3P|PnXTo2|IpR%!9X<i7p@ZvqCQP)~@&7_`#rK^kbqS(JY?@H7-7+;#BUG#%e?L zB%T4CixV#)^7OJjF+F&#=4)h7lNUXill`HDmYw^}`BIMTxGZ1X9@g!s>=)nWkE^cb zWj5=ySfu{xH~itEbHnGjL8x~XDWkF}3wX<tFW2f^`ku;n$HZ7HxX6AuIY(P`><J~P zvzdmqqZwA;ftg%Wt=hLg);hvB0hGUnG$0be8`SZbr}@>gZ%Rq+c*r8}B#@!-v6rIU zNQh6jBX{;r2v^}u+@Eix!u3enWp@r`GewB{tk}To!|Vgv-gJ32XsqeR!!665$ECEs z1!2^KFzIvRQ`sdhKw!=h#7<tvt4N`t`jmK<c9$b)UfW_Y=oE8b+gD{<*3Bg%ax}A& zMzBNJom9?YR!0Y=$H<49n$34i>1Y<~IO&agyjqs^bPD9^kD+^wh4G^q17pBQ2JZ_d zwR2I-tS3fs!wnAc{&cv?Vk%}IGTS~}pgK*GGEn7!XV*ND-7@XL{!sKV$is~{FlF?n z>%*t^X;nWV6<hXwB21XNlkaup`;cK}d`GlEwveNU>7RD|C~eB2DiG=-Em*Ezdbou_ zL(dbcqo->b#-RzgH~adXL&l8}^v7}|25D?lIAt~Q@oDCUh0WK~Y^V{>PN{F)p1BsB zt1N}&Amk)L${vG2?OG*o&!dvF^N}p1`!Ov=xRP)F6qLwbr+X-<>0VvC8@~1<fe8ET ziijZyEx6ND?4e6YdO}^7P9sg$PD>%&JGDv_WGBea>6M?FEHN^^HBS*PKjj?LUxanN zl1sa(Z=BFiS<oS+U*RK`64;5SiGPSeVPUoW{NRE{n|gYCHOG0f1Kiw#(COo*4m@Lh z-Vd08;YHe43}p&kIN3*<`AMoelENr!#+5eKI;ciFa`S}SUXCyVucMRhb>CXXvQztQ zLrY{aoJUmmgI0UB!Q-&B$x65TV{}#hgVT<c>56H-+Rv=7_lF?qbGWpXVLONdJcqHN z*u4v_#xXIvnYv0eOnmpzuDa1wOZRUuvOM=;ER7A^9<^&cp8e^=l(XRr`MlOw^#jrW z7h5y%h`*oVQw~H>zuy3{5jT?Wzt@P6ZKe6(i5|W=FrH`J_yy5@nXLN_Ki9g=XIywf zs1!Ghwa_BDT#}U1wF*eRui%CCxDcHf;>nZIK;COiwr#k=Wx=!l;+KF8j-WVml3&Ez zk2%A8jAXIJ0T#*(vRy!pD(5@@hqXfU9V<~S@9*y&Vq)?p`>+iJI}0gt+Kb|(>bh4D zrv)$Ne)YWZsG{3vt_eXH)sadJ>EP9!5><Q)!a(l3S?iy+yS&BTAY<7G;Lpi4acnf2 z8n+^}nIA#S)kq)v(Gn0pO+PW5MLX64w`GYT>0O{+&wj5a$n0LvxKK{@_FD<BS>O(S z%N`Yw{eG{H57p{0u$KZ}V0L+as-NJ{76Sdr8B;T`zoX$(XsGw><daXp@G*($Lc<QP z{Z*ft6ka6fh}oFZO?lseg?eRp1m08hB2dP%QcSBx!Ao2Bz8vf3YoakO{BZ0Tb+{~A z)qnyp1fgDnCOvBixJFr1YP4mm9Pb5oq6zLlM2ZZsX;?W9<_5%kc<7Vt9y6~!d7EMX zORLpXZxMCzeO9AOBU`A3#!&e_iOS8-fIMU#S9Q#?S!%IUE1l=QZ^`d$r}q4&yYRz~ zkIYjpI7O!%F1Gsg>P*v|9Dm`!7B%>f$?%kdEGE{SusP4k<;E0GzJk6<iwi@y;LIw+ zRt1<$pF{g8I_{YVVPtr0X0if%pFMr*%sBHHf23g(x4Luj?@v;jcaGqDY56u!qd8{I zlxky^PPe~X_^8%khSTlzDVfEiADO}F%vy+4>>9OFMh8>S+X@jvUE>r+XTYBdZY>oL zd0k%q^#<2lS3yOKshtR(r#q%^3F1h{0nv4OBehFA+^VBlEAU!5nQ|zYv<sWT(%yI1 zBLo`(z!<mH)tUjB$~gVSY@0@PgPQ9H@jiW*T+B2kY$y=nY*}pzsFd@5iAnAq{qy$$ zET)Sea?r}_T!qLMxrHTrs_`I(!IoH;vZburM4i-`#28rdqxaK9gkesyMR!zjLb%4T zgu#O7b>9bw-&TvNjE+u*#9NE-(b%eWn9+6l`udjUL%N<!nM@;wNKIbh8r&~c31EEG zXdqzMVFZo4t*|$Yad`2i@EMz=z(7kw%)63xVq~0nJmLwD&6;&*-hBCy5o(0?x!}#6 zRNYA~yVJfyLVXKZ{J{2%6|}}(UlOgKSsODa>7oOpH3(RPP~oJ)#0H16=#;I`)t^46 zAWkY^7VUVfvwUt^VqLNJ0DG9$AdXGA-vOgizL&U5Iz!oS=k$*UX~;coZ%@o&HVhG^ z#|O*#WNB7-tCvmp4}nK2f18RBW3Ncv)XihXZ<^(1l>FKGkPtqi7g93Vpie3E?XIdF zdaW#%TyPw58S1T0Fn6|sIy{@S<-UOd5kmw^jf+%eK=TmIM(jmLOd&;=4_1?-2B_Vd zzN_mT_(b2VXcfd@_T!FL+H0mV_ck6n5u1xMo$&YRg#{zhSuLBOVJ~rP1Q$g7*UAV{ z&1EcMt2{ohJTx;&bW1&Tq%34#G>_x!`SXNSC~wb)kzG-CK!B1Og9ARdf`*sv_Nj&K zG=;U|(pzTaj!iMCVhe08x7h4aD_oL<g}#ip<TpATqkJV?-JKPFvtA~6zk|<vf=OPT zwoPsIFzHqSvoS4Sq;8scwS;5DRY3RN_clDPtj{yBU_L%01AxuAYU;-jPW9!WPRGj} z@*dM)ok8?e-S0t4=sQ<PA4IcZ8TRwkwzo3cNw}LfleEp&W3LC&Cdfyo%l6j1Mct9^ zfavv6?Q;6TPJ}o6o^gW#{3vM1DNR$&u%kU{*y>X<IYE1gA8KIp*1%iV`bn0~W-aF8 zYaF;3o;|ONK~;xml$Fb%4L^^wYC1zsKV3_q0e~O*$@0h21MP=`wM&-xGE9uRosp`P zE+bfN=B8`Pz_j`BTHH}2^&18&^M@xD+C(N8LSYB4i&Scb3hyrh+N&6F>C5}&zm9ni zo@~<{m}aWWjB$U~&9pwO=>S*K-5#YoW=kt|_e9w<zSYs?J>Xl<h%bxmnql-EWg+sZ z^Pb1m8#}ICj4aZ8ec-mjmmY5%27=<sB;TcRO}ozkhk%g}?iiVkE%z|z<{Qg`W%k>> z)aK9FYshD%kPh)Gnjx>-L=9b#5SND-@b$2W1{0Kty{a)4J3=B}`<rWMO<do|v}WH^ z-y)2?`AqtGux3lAyYH(uEM(P>Pou1!43G|LmQtZib<e6Dd?f7R+wRX~g11=oJ%{mL zSk{qwI`_iuQPKQ6np(&EPBZ7N*oa$QGbJ#z{dYId%@{JHDhy8rr5!^&xik2zw`yZB zZ=7s-t$DC=O=gdz1BEk1MoN6TSPTMM;eO4kUuHbW5NWyrg<Wn9mc5S8%p1JpIcvr{ ze#}M@R3YJgt(Y+0E^;pAx(GQ?t>$xjcMiPgB}`4X(qn0#VSb4*C8eq(hyLkQ62QEK zNCgrsg8wY}!_AE5Cs;xR$I3eMO{N~Y9;8|(s2D0!y$avr&RK9-mpLe}_po5um79&! zcYVZo`*gR(6T`%k$;6}8$|t=`Oet(GH?!6;VoIw<!z=3S31c9cf9Pu<?OzjOb6@at ztj1C64sy+>IyCjR@wM}b#soPA53m&X2opyNW@g}>3Xf&Rd(9ro)!E##;7==px}p-Q z-f0vY1hVFPbKtPi!Bym(Q_F07$g#ORUnv089odH(@704gfW+mtpKL_vbK&mhwqDh` zH6^f7P4=@hL313Xy1*FO?OX-I5nD6*SE9Z@Ut{VD;TxW*D+3nFkN^Rl)>Wnl+301K zk;7zFeqDt51o{eNT;qaDwC3p;I`_ln+`Eia$n*~;(@q&^{*I~&Jx!GRZ)B|Gd=9bT z3@I%N5S4`_7uGW>X&j5$_)YdFP#+y14R}4}j;+;t6z9AnPo$bRy_JEd7iFHMvr*{m z+0+RvB;kOarf!4OZ++<(Gf7$~RfWXae6C{_=5UVzHhc%|h+1r;Wjh_?P>57-DDk}* zIyQM{9&}E^w(R+$_5CmEifY_1Y0x3~VN((S%~JN*jl+eSE%~ANb3(yy2al}8*c1nq zVav2B#cBrOW_SkSCTuC|dGljd{0d&d4MBm*Hn>{gh<2c4T2FXO2<=7>vLgKYAJRX^ zujvfae^XyllI)JyO_OCQdvItm9#k`m9i$opf|Sb!_L%mE!?;$;A*;GnPb8c9ctW-- zyh>qPi1UA3tU$Cspi*0%Qj^Q1#spt!+wE*m`{OmVb<kF$`D)-Dt9y#TS#e4EVyJ`Y zCOkPd=dLv^bkm>rlQ#_4CtDfPZK_s)-?-wNg2n-B8e*B@n<AA*#(5q;u9l5Yq3`A~ z&KpuUI10qV0L?b3YaHvK;lN;Bj*KCFbn3%d;Az61PJ#ZN)H8FahhXl}#K|w0mO0$< zE+kjFJwX(jVbn#}XY)-MF3qNbRp&AP_ub_Ya*m$+v@Uro=;7ImpF>_k=Ur-B3BQ*u z`6O_>GnTCX`gh46p>x}pWvg`V(GdNW(ggv~qsDN(%En>2{pwttTr!8f!DSrdfIQb1 z9-;)<MCnTQgCBk2v4*vXea*CBD++#j`#tZIt^xMbu>B9$vZswNj45%2`EU7eH%Z~H zifA>gyWZIDWVA%U?=@Q=sYF98)R^QG>L{s=HDyC-yRb{Es#;zg)+6>lH?Carpy~mO zR+Ps*K58tE;lU%0+VD3*?|(qecpxX>Padtbfi<Ih@3!e(I-cLy1%O?s4#pT+!n3i^ z6s$Y@adQ<vw1AS(3}h(?a9^QIwv>6IoOSS|)SJm*EE}JF^B6nf<G2y?9rm8K&VGh~ z`e%3^t)S-f>vFd8tWs6S9y3rsMo3>eUlU%QD>%NHV6#MdEG6P;F%*-UlBF5Eu^i&f zq;b+(KLIO=p^|XrH;;WaMeSlft7tDSRPns}MuKI>869qPeq`|j@@_Z}A5&)b6$#Z_ zw@3e1KLncc2dbq*REB!m`1#ZxPT*otZoZ=N^@^oGhk|%*j9~j4?$Wz#O%`?z8i#i( zm-xOTVy%7<`*zu=RbmGZ#BjVe2d@A%q-6Zo{=1ed&-=xW%M0_f5Ps*S%T;;x0q0)N zVR>6~JxPsa8}*qt;hZkjUi}3+WvXaeS~ZqSkX0xtO<&iwMyzCnegWHV9RNxUrF^&{ zyoa32%Tw4I#javOg3hjZ&-e+}T*aP(JE5KK;E1%%ku}seil>QUQX76RE9t&K*%2Jf z(9g2YGhI9ly*;$9&&vT1?3wb|gwGMULd-ClM>`Urb-%t^yiwOdY~O`NES*P-PD{KI zH&A-4F`=D#i;?k|6YvO$Ww62sDGtGA%^W+xZC_|n!$=A0__=oN-SK5k8a@t<;KAN{ zNUG}3?3JiuH5=acqJ><KolHjSC(aIqi|SfneNjB&<$ZHRkEag#n#%;_2%fcw0`cOU z6#=H2Doe||>!vDmk8gLne6Ws>l>B9PJLu>i_%ds{Kew%C60Rhb*K|Yrm&Mfc6K1m3 z(>bDpqZJK1-<p4)gNft4Or3BSUAn(dlyMXv2Ri<hu$1BVMAsjOI6_7XRVd$+Df>@l zghEsRhg4Jw({eUM0VVe-1w|D>oU$y>R{C3@wda_%Y-7QQz^De6{=QJ_hva?BNNYv= zV6!pz8tdk#gt_bMth+3n>L3gOm8Uy~bCDg<mbn;FxKLr*FBndWKK=1rjkX7@{=I5N z>K|*lMl=?{M??f0<j?*W25;VecxXn1a2?0rzC4B_FCuj330Z&$JKz$s(a@{pN<1@% z&GQOFT>~m!a`Oxu=OrjNsq)4QC{a0|r}J2%rcCCpr<i+<jt-NWImqMI6-ON2SzDx_ z_BrubpX8c3M%kktec$UvY3S{^nYvM)I-H4|&{{K@M0`aIJUpH<*qF4w=l7cX>4JZK zN5JQF@`WoyDYg<ur}7kVlNMk(!!a>Jc2j~~;yA~G<J1QZA|~@ZJw4g!f@Ze8Y4{Id zM^G2RPSkhHUg8piY5D`lD7HEFL?setw)lP5xJtF2wYNttI1?I4EX9zpS6Js8gI(L| z5rmp|<R|*aWffJP4nL{Ib}eX@p-mj0%-)%$8t0vwN!v9K>Qu;maCZtce=X67l_o&3 zyYi%I7*HaZix8)dnNi{dXIwTjfjjzeYH_w5J4HW$!uJ3%e=5L!;cFw`!<i>#5Y&|4 zrh`RPr>_#F)Nzx1=X5$VadbC>+t0Z&Uk=#h<NfK`WU6&x3iQN=WKQE#qQYbotl?Na z%WAjGy{AjG-D7d|2{C@>^6(Z3sudiOpJ3BUSaW}sggd(17?)_nKO>rEN;9A=wCV9# zM4Qxj=`-osQcIqTl&Rm2k7tCCFO%66^gv-X*X9R5=Gu^&tdRh>Bqg?yWHVLS80`90 zbSh=I<E#8P=;Tc|n|sgw15hoD)Ro4FQ6-YC;>FnZ#T-QMix;YTEN(o{AcVI}%xer_ zOc+cdo`kH7tql=M7wp^E?Qe&>r@oxWPYdFJBN)%q<lxi%>2#s+-$&=G_uzTq0m8mO z_?MIIRg%bKnB8AC_Tji}IJ5)0Y7l2E4CMkM6<iC=dgocJr799F&-*Oo14YYDh#8_n zXFiSgewza^G-KC`^-*$?2Zh9nApzw)PUoRx`iinDdOYQCAlQuL*mc830}r@5*o%5E z&)dot?;H#k81PsK+gYfuJ#Hq$(P?K&^8`UNsE1R+ONhujEj$~XIwehIUaRxH#`C!> zxT9w<CV8jpuYZ+T2@H8zU}1bg;5;v!e@H#Ce^RHBX=cZ5bSF4Bj0B-}Fu$!Je;x`E zOH8R9wm-6z^ga%ah?~s~L3Lmdl<zM`Hy(U^SN{65XYzf3O6G!<3=IE}sRh=-a<7#a z^Xpjud7?bQVks*)D-5A@&pm~zZ)XXnEOfUPt&MbBvw=J{_FL~RQsp4#k$<HkBJYNk z;*MfP0UK4Xc7#_97CP^Kz)Lzz-`zNQaOIRY%L;R4-UQX2F1S8^%Ef@*#jxSu=)R!j zFEVMt$f}Q1(J>3*L+Lc1)(*{rJtv-ToA>%|u-@glynAa-bn<vuFuh2ti2(jZ30$A9 zT$PQLn$OR*<$k^p0)D<5AC6+_MK7bKof5MqP$k34$gVqiRI4!*mvShp^iKMz#qRkz zg?*jp(K%{`0aiKt2nWKBZEnEQ;N`wZgjM1$H?FaUefG&LOgzJJ7k12j$hKasBrb$2 zBlPb3I6z5{qh3$|X@uw(IGXFDoP~zK^_ovzIdV&%;F`5i?lKYgkJ+~_&RYB2@V)5d z#@JUpDja3@BNX>%tsG#7#l9dIFWWUniACAp(Fu3A>HIh^Acn3TzRI_){2aqV=(k{R znI1pth81|vH>B+=#0zJwv*1@h<2t;Pfp8HdA}zbt7*E5`R!A8m)*3yUTStckPTBA< zm-_K^An-io|Ew{xKFj8QaW(gKX*zjs8}ZN73jY3=*fb;V9KxR^e<Wg%eCKb#%D6e? z3Pvq2OXTj2;G2cZEaq}GG~PLxvT2N2rEEkzu85_m0hmdAAo|88jjFR!N2tqF#)PBi zoW92j*(#sKbo-o%w6Y2-iT_c4ON|M$<Qd6sy2cOv^i%F5p4L`ppZ&J+h@LecxeV{* ztVh3(P)``t`3cNHsBF=9&3NI|S(K!&_X80P?{A7c>{fteM6j2d7v(b-Hv-<Cqr6|1 zmApT&VI!Mdw!-3$?uTDdOJ{0zNXiH$n9}S+dhDwC2?8CrPs!*n;F1sKrthWPaySGR zlkh!kZHD;LKl!+GEJ9WddCe3u*hrBbx^m-V(vq4xOw(rHw!!ex_Ca1MZ@#s1woZ4R zVUu!k3Bo8TL1>i<N#1EtTY%_=Xr94M;bgtz$mnVJS;m)kpt-&sg0-eASFjpZy?&7W zsLf2*?%jhhT5m7tt<5}MWpjE8UoZ`=SEwc8rC58HtQ8)@$$GTUxI1-yzHVPq?2mzZ zS{*~YERwcVgS6-v-Iz&Or&0(AV|=PS9^W5UKTFDw(40vEs)z8RU(T(nflqQI0>bad z7F#g}Frr-eS`XAnmyNX|$6yhes&<?6ZZhIvP&4q|w>|82zJFBX#~iMJHnJbxFyuW3 z!LE5LzXKm;d;$+zG+5?y??(0vF4m=QJZy2p*p#HJs5N%{8pyj`W-eI5;IPQj@EA?F z`2v^L$vy6J%fZjP%Ztt@8-6Zm6E0Y0&O54?d(~3se3;uN&8D(WgMNrG@u0Jy2;K?` z|K!x&;BL9e7br~IeBCCKIZSRo8p4H2B#)LFaKw2-9R&fw0lx-!^kC4~?U$eDZjb|< z0y7?D|B_VEK<9{C;!&sevS6J(l6~2h(`MTfM*Yv~&mh|thm&u+u<yJoUnvO_Lpf8~ zLhp8?X0+x2j>44G=VZWkJ3#^j>~P89oApn*(UXfOQ5P{eyy$4Xc|0TMUqQnr$u5h4 zhP~ZbQh_{OMCkP8&;bo7JpcTiG^EJ_4ri%^g_HkIEP8p>ri3Pb16d`naI<(i#uOUq zU>&kx2xnKnJ)GEu#LUetzw9Gqh1;p<SYqNl4WC^~O;(3f*B*eDQN&|hTBgLKRSKW_ z)QTOlnL8u4(KGO_FAZZ&D_(6)Yhdb#l3XU8Dev7{v6lPV;9S~v3~lh~N)n=~7ravW zx^y6B8tEIpq+u~yeMd7|u~`iybR3Vfiux8Um8FErRveN<Mz!@&1WU>~)4e-)oV^{e zhSwBeWNG$Iq<SHakbi}7h8-w5H4e{M%+k<&z};+lx`#=ZSY8Zj?B0256#a0b&mpO_ zx>CDses%-o$iRC<N6+Y>6&63+hf6MQh3TNF-{FHB4ceS#{c7L*lHF9xa(KMVas#`m zA$q&s863Plgc>(PTVrD7#mbQ}1rcV)0$H^c2<i+aSLo*79I>>Tm&b~`GsV5}L-9mq z%VN+jwgmNQ%A7@o%Phm(SUb&CIi@p<hFj7kLCrhyi;3w(6V<g|YK1h&I;MpVAifT} zSjHX0Ivh<NwNRUx;hxxcFFQaODehT5eyHn{C|yud(1$U>-|RUeOCq1CWHwoXMK(eD zh4*!FzATZ~dD=Gk#vgGzTFI-Rk@xKUgW!%A<2{ywg#9OTD^nUu{@SmA5)rKiL}Mg| z1^DKKE-V}V1CUDTXF|VK=?18IyC#DcIbSBjSmX#6drcx<uT<onN1$f|3=!>@Z<4?Q zZj{>6W#X&V-l-}G`0tx=qwjs)U<GZ=CBmWRU6$;3It)K7_9iK4*wceI;R8<chALfL zYY0!6h=G@|6eC$X&y^-Te!D{z4qL2q6&|gctZZHuQelkrh7Q)iG}Al)2pe}af?Jm+ z{N3fRw|9`oW;FY=Y4B^0pSDE&7M}6jkIhHaH%_w3c>UdQyY{@N4-SA!8Vrz^ZMNG* zIU{xmqs!vCsi2qqT+RIz&HLgpARk@GTKm~aNwt`CUq7+&kiAmt&OPpU$`voW7*?N2 zeQ}6x7TqV&oci8?E?SYO+7{ty0(kh-%BN%I>f7Hx4v743IW{lbgFh0WUhz2&^4vX& zonvXXs56V1cG>yoYVHYUA)7kR6kI5YR5IowydxyU@kISxpwE?YbnThQ4UX_rn0tLb z1$B&g#)6Mi8GBL$m5anyqA{M;j)#v^)d##)K1iIpyX9@yPr27RsUe7&2W8zIMrf}n z<;wwAcH!+e+cMFQU58T|K&#%$W7-1)bc=Kzu2uFk6G+C$yJ>J^OJy@cw&4e`c~I*1 zGBS=F8CUnD=1xvCH-p#*ysNXf=gA!B-H{(fcc|4-^}?U4I!$Dr8dbu+>(aRx9N~^S zRZ{XB+tm>sftHt*nvgquUnx1X;!Z2g*OVTV6wX<1&Epx?C=o1&rA$b#759NaA{{#B z_3t6nVfZkfs-R!pCogskUk~|^G+FoomY^Z4%v|dyA&wA!mE@@pD^pcfq)u?aOLZ5V zcOv2sEV`J7uFl;RV4h32lCR_5Jko>LUOUvAT!fr^&?ujiX%Oln6$>o${ry$m5;|H2 z4eTX2aQpq0Bjs#f<BHO*dk(SYEk*z5j&ft@j?R929^RNluVVU(GQw0bkK3k#QeV@B zJQ|1wivq!lRTXot%o^&O)~!fdYiN@bN1hS3i={U!z(uclm7|NP!P+hFn&W{S_)*I0 zP6Fs!Agse75fuKN%Nsj>E<u}_`Uj=C=AFPq84SBr!aAOw-A8w5!nPinJB60S``Q%0 zgVE2N4HdfE5AXpKPH9C8lo=X3&!prsX>2nypyxqntm6x!hL7Nri#_kOOAxoqR#=%- zDmc%uFTg+jWfdrvy=bH-XBe-Pw3vkRKz2G|n%3l#fMgc5p*q`sn}ObuY7{Q#-QuE9 zkafX9J<8>J?2F5e7qH9HBF~`1+UEnqn>I52Ge4x_epKR)?vi<2UU<EZH%igHM%aW{ z_~8q^eid5ISey=j#-^}v-1#q0c}}-L2`ZZl(E+NMKRlwywh}4+XRGi=!2R?G6gQcF z$;>TC1;CihyfzJ2NAlo&9Kcr2H|-hRL%F+`)ce+x)RFrjo8^*)mq+R{0iK@b)z%BL zydEgD8gwGh*#)%FiCS(E3GXTO3lj=*-BuX7Y4Z5Y<M>TBVQl2@AMTP2;G5m)*J1B= zFVe$kv+p8r>d%Q}Qm$)Ftn@$;Rbt21-kM$IkZF~v6UObR$_-`tjVc3(c*jw-+FpYe z=E%EcWY0lq{GTPjYTNQw_TlVqgu37HN7bI(qp`{C4p^N=R4jasMhkR!*t?c8+)c)F zVEUCcIC<A#wj|vQ&We18(>)#YEDbkG2;Nu1t&rlj1MTei&3lf8;cT=<Gjd_t&!rm# zRhNym?}aC;3pdJCODWp9TMdS*b(m3V`XIvaREYVDnCexp!n35En|rM%UXdonyUwJ^ zx{Fc6S|dNcvl^)mUZo5$w*_}*1h2t^vJ#b1TN3D}jvvXN?`S`Im6bW?=;MQ_Rh3cS z4BNstN9{CvA_n0;joas`eQcg;KIHHDxT!XI9c7!|^hJ=|+)e#6vI$0pT+0XBE6B@b zsGc0<i+Cxn`}4U-+S}4epx5;xIN7CXaXG6t<rh#iBp_#I^itgdwko^~yzL()gRbR= zmeNMvl~lD*cx&~XPcg_AyiHoFRvkMhy(^N=(9z280Jn(CVgDtHO~-9gQ8FB$g}tKC z>{U%d$$!9vbQ3=f!sY<SlpQQp;H<VTLWI1hMc64BivlN*5UnPI(q!kV*%aRsmDPUV zQQOR}O&2NjA={qI+d1IL&Rh<`7>*g;t8iOEJ8*{+pKnEh<sly7o*$gNBIBnMWj}pL zcOT(?zQlD5DpEZu*<JS*y}7pk1^_P0tPny2QT$e?&)6h_p<LTk^05uF)J7qW!kp({ z{G#rWtuD#<FFj)u71*X^$g7E-WQ?Em-9aQI6fui&Trtd@_S|hFbkaRUr#DBh*fX$> zc!G^jMI^nuTgxpV)SsJky{HM*TaSoZOo)NvWCe-jMQ{7wSPTbEQ1JbBo=6hGK~<7$ zeUZPVS%UMZ(ZGj>JoJU{%D-$Wt2OGyTP%6X1`9^!s~5220u6Ijqx`zyBgmpbV-r3& z-t-|!P}9lp7<bC1&L>04*F0O_-TPKG-XPm+0mThf=$7{${7hyS{rWRUV?=wW56tWH z)R6ozPfTlA-X1#m`6{jDbYu^0eH8$wK*2Xxxn5e<mDVN-Q9>d))R&f^E_&&=B8;91 z*%Xc5PStuPn3Hrtlw>C45#m;8Y2z_#lkroiL@_|wkv)CIZQ56t$$z{(c;nJ?vvVv& zsa#}I(iQVuDh$2*liqJt6xk$udYz>n*(8R|ndJOcWB=uuM~>c7@O2OS$@^EgK6wTm zRITGzo-WO9npiFqu=fK2Ll3qe1Vwa2kWHFbV{EXVc~AK4Er+&18-6WDT$xx@#izKq z!3@KC7&g^)_WT=c7mKKrY_t`N<*uJo&w18*CxfPKwgx<(;q+1}_cEOgKeo78ru`Il zLPBdcJ)?**(15YPs3=({&gR|KZ+Gc22XLjVK=3^}w;J|Q^hI;TG@4n!S)D*uiNef= z?*d$(-R!~J>w>I@A0@D|RIf67?R71LHny;8=nh{?lw~SE3&UN1+6qbPu*DmNS@UiY zgtLkVCuhS}X@6eY^qRdu61WtjTdS-tWeRnm@}tB*NwP-zwX|ip0HFo3Zq|Mi$^~6( z_R%{r0pI~^$A4n8;~fAsPdGgtVAHx#z1qF!-ExpAP+<hJnz(&Q{R)>^%w3ZH8C!DU zyGsP1X=vM#qrS9VMpQvK(nb@!XJF5|a=92=zzg**v8+B}{tQmaR%R=QgCMLc*A*Lp z5$4QaT36!83bChu`lQN#zJp@jC<ILY6prQb<DgPY64g$T?!h1d9$1>b5wKe7d*Z6k zk?|a*E$ZFv8{a*J>dWO6<yX@cef^TgZEIiHe!=><V2Xh`fXi+#Ff1y%zJiaMM5og} z!~M#X=P~!)6S8~0D>nRfV4y7;qIb>uN`iguaXf{^fjxw4C46%U5%Ux2#+!7<)~7}? zC`|P`iOfL(`7&9nzT>DyZ85y?u9%0l7T&KSA}~K~XuZTm8*;1{kJ_mI@iU1<eQ@A) zMIXKU`0qQB#q*bNtnbGv7}<EyuOIHjTNc#}-<&h36sS^5qp%#7HRy+#grfJyQczr= zNh{+qImtoD_;W99oScwbUs}<nrZo&aW|+>5Ew-oX+-W*B*p(J0D~gmwcNL#5DEts& z#)ZSe6RrVNWzvo}63V*885Y$fjsC*Y;_%E1^%O}W?kU6^03UuBGf2xgx^oUB<9m{) zd+{tBHq?sHY;sk1el|`urG>3R7PaT+g4s179}eUsR(h+P`IEl|H%s4LOKex$QnF*_ z8k<{=mcWat;vI316+{f1$_2mq*=BkI0FNA+?Y;_Fz3q+6d_=M(auHONfQhgZ8+SM- zxH$pj^0B;P&nx1e#S0fh!YRH;fW7G$U-_2U)I1!V1m#Mcvd{TcgppBe!AB4_f5~KD za#U5{So7Lzr`}omSqF`?L7^-vi&mXx*yg|q&T8po5TYcVrPZ0ZZj(k}2KGi(8=LEq zwDz{pvFeb|LGy#Rfy2tLjd%8;+|X=i;&s8*`Lji9LC+ek1MbM3L}7G%u5#*2Y7_1? zI@6Ym2JQOkHn!$|B8V$om^3EMU|X#+nOZ1<CApggKca8P4_*l#p2hrDOVjz`ta!va z^!Q#gsMebn$mPa{KIIp^^-<Nua!fQ&a|!!tGp86Db=`|zyq0uS0l1sOcQ*<&&9f+= zf*TEC?G@^UC}9)58~2a-4y9gJi+!<l+v54gz_GiogU|D!o}H+9x^mm?t;2W8ZFQZ9 z-L-8?+|g!2egkd^#9x+4k;FlMtKmZsM_+MALF?`j2di8LV(vBwqxY}xAV*NU3RiMZ zT3u;=HSH_I9WdnQt^9bWh78+Gd+M^Yabnn+kjSaomqdy_?4?zxM61WkrQ?}Uud3~Q zg?Lj+Y9G*${@oUL6t#xM*I(<$BfXC6HNH9T{`adn_H~eSx0Kb4TxP?_Dty-6amhok zm}{$ZYtwW|iB^}8x3nO#Nx-U8r6dXt_;(@C)CVgD57H^$`Ph!*n&Eozeeww@KWqq? z(OT6flyDEbRQQQE?&s#%)C@YGnvxc2wy%&kj&%<rHIlYJ@&uDdBMjZchD3+Lg<0~5 z+&rwW82%E#&UL8KEePtx7x*B@$IJ(6sl@tnw-#f^`rZ`dR3=IAmybNHu~8os11D6w zOXcrntrMJrk2m0}@h`z&8{!u$ffie4gM>jEqI_WTkvR@hZ&;Sx0co?eB;bG1^_4+! zwM(~y1@{2K-5r9vYaqBwaCZsr?(PyaNN{(84(<eZcXznMdF7n%$E7H0YUa<LXZK#+ zy?XURfToLH;?H_L%KB&1dYumJ1rbg57~0>zKwBoR0=$w8t&kM6+B7|0#SAtW8lR1> z1W;<e{4utM5pZ%5($#SWEQ)2V(@1N&Dg6Q|1uNJ(^7FO_vlp74RC5JrLd&{}$Q>nP z%U|x40xOYNa#p}USF8=kjnClq4yT574*TA9)O+Xl-OpHOqg$u=!n`)qa-80w<m!R& zn;ysA1xLGMjK$!%@^VaKdb#ylfChI(L8`Q?zJi#cK8AMw6s179z8C+x52hYw&l*eq zRSGkAQqV!f?ue~3r}}G4yG;-VhnHZ^k<jN#7cE%Xq?mH?)N_!GEJNgWnDWdeabS_H zhTIws<Tvq`7l1&^nu_nL@578kJ$POAQtcaO+T~U!gJg23cCk2i;0uF)Y*&gA@RGhp z9g9sr`x^6?%C9<D>hcKmIx<Xut(dpy%n^B)AWOpMddT18MR$ffaAIOtCAJ`8toMU; znrMr#$WeR2#t)Uq`r-H_81v`eR)#pF3TC@9zw^n)vT1|}fHI`oU9Gp;HFugl;p@#7 z&jkrq+dcR43oD(gKzbJ&T$;z7-1e{vWd|my;F%;Zh}L4nPl+A}&Gk4c)PR8lNMcPY z&KzYPYR%@IGYjq8jmRsfsw<%9YMdyAl_sfK80ORm#59+Kf0hnnf$4scDVJG2mi;Xw zPiK?rUo}<)5YEojC~z6ADQcHN$;3MHY!!9?wZ*k&59**87zMs`fd@l|+Y54h2X&4z zGhk8L+c?3RGXBI(cZ6(rYmq>R0;E%XddrJ)%bP#?u9EgoPkNvc7^!60mK$n+4dfbS zM(4(nL+X)mX7UO2R!6JZu6U;pPFybgDRn!ed=4V(npl*0?zmL@TLf`^Hw)c|>f!vX zkRiRR`Lq~PUPv>q4{HzWf(unZO|rbJ%5hFF@gxQX!CM1a9PbyR-}i^iCq}Pr3t~_< zrZ#R{zH=zcP2Pqv%8)Js$9F(=-WkhW412*d^WgOcV8MzjNDyXgqq5Bxz1M~I>{SIm zUVIfddCw!hGWIC>wOYN;(DV13is+cHGQ|Q5w2X*n=aE5m#$0X0>ulZGHP-{|8wG5D zqekcXt|v^N%OC6MqLv?%6~Sv`X<VYE+hcSX4#pUI1HvNKS616%8X-Pq)_fQy!goh& zUhXd_K6=y<AF+4r<tU!<PmTjtusZ&3;so8va(z0}-&AE|0`^k>-3|H=Z$SZK!oARQ zJ#?q&ebaMb4I@b}Qn-eHSv<CyL_P{dd2W_&kfyOf4N{&tB|d4*U098I>NZ&5;(6o@ zgJH0jD{ak86Ozp${$yBJYgwga?6k{yxR}u}$M7bbMVW|0z3-mgdffkr^R-!gDNJgD z@6R3nC>Y<oww~jPJ9jPzF@kYz1H?vbA98h3jH;!a!k3^F0<VE|%kQA`eBySQB4qjg zM>(uFrrHK$z+&h{;i%N^6<fQQW{5L_@@)mC8~>Obx(-xoSw4Hb;^InBIW%PaX$Xe& zxf!*MJ-YYLk8vqB97h=Y80uMz-xi!<<1PTXPwz1bJ5M5bWDqDs=xHjPH=v8JwI9Ft zqSj`{pFpdLU+e;sV-lO}4w2wuM%N)Xulc0ci|V==wq+v2yRHqGY_9Y)u+P-Lt)#BP z22<pK#kFt<SLSeG1uGez3EYWk(?gS7m$YnXky_Q6R_oj};LbUR+gFtO{_LX{%$E^} zJflTy<IJtE;ND;R#3Du^`vRY%9Z)@B)M+X95iyN#cPnKR=BQ;2#E*PyG>jzbSXT?3 z)D3T`h=Rb`Y81Ak)bpLgo3KR!<&=$9L)V3BR1f3pPSLpgE9;>3*X2yjyIY6L+7zbM zQ<UWjOc1AeB<$Nj%JLm<q5(qFp4(B-vesRXZ|w<wK1PaRo!#|m#rY-AuILS>dO3~O z9kf-paU3$eMEB?7yZWZ*-b>}4rKC%7&r6(~i18|T3Lt7PqGc&dOzb!X6-^6&$ECVu z=P<K1hF`JK1e^1zHQnyiMaN6c)d{4MM>wNEL__XAL2#1?Ta;oxu~G8v;CC$Byy{Lc z-Ql5oTnOo-W5zMTee#_7RX+Zq?T?SLAUg?5G-&T7RvOCTH<S)$o_#sIMh-*B#8}LI z<Lm*<%iWN1rfqYPvry8G?_9OIwfX&tr#g&MqrMz<I)=~Mp&0%Sk+3ZzpOa#=A!<GY z-fxi!yxG`wFtFEnfk8bQeR~D4!1G(z>z+wSj=!G-BAxd|--@D)fd8(_f_gWo|J0jO z6DZ|<r6uP<gvb|9o6$S@#=B><+X;-OgR21pkrO7-Kg2OPQ)e+=RC5Lj@G>*Pl4|em z6&;(#^x7r^Ip!MZDXWn2a<IR^7-0l~wiAV|t%HsvvWBKj8RE~+sip-;F<Td>Ud;w~ z8RXZjg|&E=_K$`x%Aeecp`F&Uk-3mFPIBU8T(+AuYVe!&dPqsWJk2t9a80j%9{9Cf zY*-!T*-F5b`z|pnvVPj358P6@YiuF7G1b)4A4#F)=~owDJ-CAu))*M)JKnDRG3fdW z$Ix5QrNP#=?2meliD}csO^DgHukKY6aNdGYe(b3OVjvR2b0;0&;dz@Y(_*x<A&ylG zvqYo50O9ni>0`*&lvXwV1h+2632#q~2haGD_o&d*MT)@i?l0&KnkdC6td$zG()Hz= zg=C8H3b?JYO%M@Z+U#f~hO~bVUN@&UVCvJfs`hP!avicZq}*{Qgm?91ai+&V^r?MU zV4nT6YxAy>?gxqM5>}Kvg>r&$jjunxHAA;A5|9|$IF4M2skN6m>h`b@BJu4YjlLcm zjv5W(%4ZyoF0~UMp<1Yfyplqi*%CKpS-;)R)1Z&JZ6&{`BY?Tw>cC9qa$XKt!aQ~G z5q!iL;EQO{Un+sXnJ-hUZW_tr|5F?Obfp;BKF{>z{$}nJ$hFpaz@NN~cye{lXhKAH zCp2w3ZEOt*G45AY$@o3Z79%%D0?Eut2qa4|z(gQ(I$=f8W4o(Cb!BeI&!8k%9z8A# z!JAmJhQ1Db9R$-E)0xkLz_A{Ab|G56GF~2V5+TKp%w`(kTxTPJC~bZ?y708a5b%#Z zO(*5H1s?~zAVqgSbdt<Bc{e&gn=Hhb4?^!5B|&S>CSh?e<ICfLAmAz%LQ}&`-B}(? zv$9ew3%FJMOs&<$eMr(vS(YH4OQXTt$@~2bf9jXx5vPBWa}g=@=P~o3P%NC+RjssW zyA4JX?WR>9Rf_<d)lvr^9AePy|54WHV>UUh3w|Onf_<oPKVmm+J)wI(bgcjS*nwss zd_wh;wE*2QZ6xnR4C$1ROW=e{vf;h!!ZBzmvg-kc)THcZ`Hvd5wOV|CEkcS?wz92e z{oTl^I#GUL8ZeyBun{9nl5^?&EI3I*PKFFKR;S~mH6hCxO}weEZx%U`;LsjS9LqvL z6fU<Mx%c0DLw-*0-UQqkc1S7zqvu>NdKV^47Xy%_c|d(rUd=#sQvS+=D@a!qM9VZ2 zIIL=F#|)$KNGJ!SH;tvVd)ei)TMo`(YCT++k>jChI}Ooy-yf9XLY$QE87xiJYLnFE zv@T~F0iI0Xl=`esz%B&ZjVv-E5@+$4&K)_Ie6&<^6vm}w_5MD`Z~198L*w)K)fnFo z^-p#%#Ivk8sBQWknd0kTO1$y=+Y1=JBVPP^_<ob^6pSx7<g6UI8X|^sD66JsS^b;i z&0E>!v3<4=*ZoX|MvqMQ^t2%IM0{@YxrnC<M)b<b6qqR$zR9KZ35g$(+JhED$H@`U z+>1<n{SX8n7f%~<lR33~E9JI&aAhyx5K9Qh=l2;xAs+s;n>V!wZBQ#)kxrRt&ELat zKD(!B?@FIoK?s=2ysr9tq@#!hL`PMQ2ksjb8mf!3VwZ?F7*tQ~>{F#YX^0tOgw59D z9R;(yWme0R4sQ>oYiN@G5+{!d`igaNCqnhg*YGx@p%B70#vY;exjLL6qPO-QPZ2y* z?B^K*_H%)@4V^e`st7oW<`P58pB>0-+p>$yC9hrZUVD(iob7Yow>Ai3;XR3&%%urc z{%5a>w=tu?jTQW^B?aG3O<+#1Ij`_o|7RrMHA(^sN|QKO?Rjd!qth(ffhH(32{xPm z@&bQbn@CO~Poe}h>MINf`n)BuswbjB6j!6BXPufRIbAGZp(=a6K!{FDeiT94WgisN zW70CIe3>NdiuzbC5S*XP068VFF_}URUSYzQjlIz(!kcFzN+R8Ji@s-sA$oD8iR{H< zz_}>((@fXQ$HxblG^uCPlb22cb99m>&;8@7Ddz9MoWcQ(P|dByxd{G;2K1YGcmJSC z{Q=Vy5uxlgb_nY&u-xqb{uZXoKmHereDAviuHniEua++i59WO)q;O^0<pTOkZW$`; zH&<6FD55&lSpvn%!k=R{-D;$e0!QcCCa87B#B>v9L#L`v_pt+48x)a#-j+WU&=YgN zV*KJKDgK-}F?(esrn|p>pVah^wZ|0a`&D}SbMgM~RVqvT=PJ>bz^q?nu{j`JY4!<P z|J`hem3}wuxD8V%uC1iaY%sMv6y^6je-&U@ip-n{k?k-h6(pT$2yPlnr~X<>gfA7~ zAVw~qtbZ}@Ii|Zjm~B1a-5_G7r%?qOdz%{1f*MbTOEm52L9NO>PF<Dr2GRYd6SJRo z0!_K_B#^E*yfA(E@zl3d%YR#{_&Eu`LqM7t(!W+TpqU%d?7NBsu1NTb7ONhAVjo$= zuGnAd8`j@)y>-lYZ<s?$si3`C9^|25FNs1phjuk;UBPD}A`eW&sP?UM*a!Dj9ImkE zVKhgLnh;2-q&&Amg&+e(Djs2LN-ZaGRx`@O-`HUY0;Jswmm(8L*KJPzIr)qOl2oD> z&M-agZ&ppBW}j@7lvWmKL<hpx9}Qax1230Ra>{mxXvAtwTHyS6yKU?!|BmTp#dl^) zak_8pzYz>tUIqg_mP*}Zx-c<`2D99n0=mD=Yb|F#!NMX*L-85@^L>0F6<OaW^c*~s zlan>zn^&m|!?CVh;tr6|gZWXV(q1q_j}Y~yurZPGZn$@Y@h8yal)cL)NivegFcb5N z3=(>Y(-N+m<2FFOmI_EHQyEo9)X5a1@e;?HjX`X#a32|Q5vmwJQq8AI9~ME`yQ`r4 zOxR7w)pA8%OPE{ABeZhazsEo3Ad|TL&#Xy!|7&R0MgM+}Xi}-1=bo9*1h#Z1i6ULu zE5Zp8yp)8-|3Li{Yko8D%l?<9dn|apTDKuXKlMN?(E$_cw&E>3Ol;c%i%fB=rLg)~ zPHw&i)T+`qR5ZMG#x(Gk1fOp)tPDDliAoYKLk~(J<UYV9awhc588wBOq;~e}f@5B1 zAqGM9PEYJ;Gpf;!SMPZEO~;$Ig+k?9l!V4dn)E&DziEtEig#-i^yxvNe~Avzi)G>W zn<JdiSMlGQ<C_2)aZyIm+Lqn7-E^ToILx6fxE|N_3VO~F7Tm;yPdqr7B!wKR#=6@H zn>LL_faT}^-kI63cGy_92kMnJKpG)ja08s#L$Y9#&aWuRB|2D8QnrGRqJMMVX7e*z z#^Q2c3nw0UR1o=;#4&~>=H4GC(IWqKRRx5gu$Ug#b}aZ`_N62MboMjM$j4Cs#YwUZ z(0kj&JYM|T{4W=J54Q6{%?OjK5#oinlWoBJEo4+~WMLJ7#isD(9~(_N3rS!4K6-~m zw-8Yx+a?WGvBo1KnZ0$%>Q<s)yJ$uq>yk6y>&3x+`J%^1Th`?L5$WUxG%<gOm{eqT zRi%z2t-b%IP4~}HuD<VDoVfx9{&zNLm;a6ylf7<y|J-#@JxvVyItfTDIR015vsy;i zBjjjN&p>eI`E5}b&_p`q00G6)>*bl8QM-q6)gwfiDxUEGOVVUCBJP{R=7cKD25Nk< zne)Zfc&a`;$f;uII_|{5YRx_Hu!26#5mT-z`!rIB0n1s$(mf>-raP+}VZzdR!0%I- zKd8Y6e*v=Yk@J!a)%~FUCXCY0qjozIk9~&C@~L^%es$Km-%O%=O>45bD*40rGBE1Z zx{Z_Xm$w-~AHr%{l8-cyp0;m7>;LK&{=e#Z&^&gpC;z{<D%RrtWEN+%gVmdj*H9n! zp9FR$fLA0G@-;2lD(<~+*ZANC4_g_aAplYw(x|0${t!8R+@kge{}PL$Ft{XD9|Hr1 z4K%D#Xg$W?mwlmE4lGU?sESt~n-Ra&zEpcHc-~$Jt!dAP!SEO_kMm=DZg|@m=nz5a zs&pd4$0UxnA`f=bvIA@CmyFN44bd=B!|MLfOq9XUp{<|MF4RKG^eKuJXsiq=c4}$> zqrUD)VKshX-4$EX>G)Rs{5($s%jd1>>GjL<58F&1ej>9Vftl*CHl@<n<*!?swrd+( zmp#inHTF;5&5*k6l&w+QZAUkr%xj}Z%E|0sEF|r~=Xs(hcJ;gk)Lc#x+3utq7GomA zlOJb?flAgmc_+TxnL{mq9Tv5$;9G|zuuq11nU7uK|4f+8qu+ssqYw|}pI{es2bHPZ z=B!?i%TU;ChCfF1I^j33#7H%9Hqai6JC=NA!0=O<B@Ywn>G01==rsZYMxc~GMRU3e z|CS4pPUa<#t>ogIgfDA-`15+>0PB_w?|~YX|9hMm`dXE<0)pIhBfP(({2{kz96!OT zDOgXCf>bgGPUY^3n_9WS57JZyTf)U?MogpAI!E@!I&4IzO-@eGXqIq?ew2sUl*PL) zgeZ%-OCD#IjxvkDhJlSX8O2ASQ<UpF0k7+;e`M-&t0#v?SSCMia1X(}Fmz2Y<gg{a z<`9JIDuagbW(avsr$a3Q{##l35**m0V%;8j{DTG-E+mk4ye?7Vw1T$V&TW8NzsCDX z*X{Mh)@bbqdR}u=f(pyM)VxH-(6D?%e5BtikUJ^q?`VB@8B5#&&i$PeL0J>@yDkkv zI=wKIn7{cU(5PRK2IMsLIE0?R@pJFjD#e+6HoTt-e(m8&-<x)Y>B=|w3G4uW6c?VD zwwWToZIM@_)}H`;m&dAO4FNUb8mxE{3|vyYde@7asXocT`Bf6r=+neal~cb|E7%1W zk4FNxjmS*SDk*Rl<D^U|+Ad~%;sIovL`TY7sf(pkRA;%6906hpmtUgaPX(#fD~KQ6 zP98EDyKbK6YY;DY>I8^4Po0~)Gli>$y`prO9kBVF32N2ZTH5p4E}djbR6Cj+CBtk* zMphj6Q`@)>eJ<~6q8zZVHZ`0)o7XE&pO)LFN)?=&g16v*ue~%#@;}|WF8F-%uR6#v zr3Ky}Z_)O_?Z)4E@RHP*CHgHb8OjlJGIq63Z@pYN9B*@Jj{`S{<}Yq;5|H)Wv<ZTn ztiztv_(RzKI@1c|_sT7VR_lMs10pb6&=Gr(_PqQLAPl_&f~WuIk?YCnJ)`scg&OlW z1$1|<t4~Y*wKq1+xi8Nzg=QUiQ#Y&biq-0V-x156ZbmNJYka18fjR}@+q4SPI(wIe z5e^#&ixNv|HQNDg_@sOC_{m#O^U`C&jZ(?l7gy{OnF8dLNH(##bZ%rG9W`he-XKiZ z32<bUBn}<*XId?ueL<jf_M38*ltemFT8^DPMIy5&Wx32l&5@9MSmDgw+BLXE6@V$y zs5SDx;*=Z<2^ub8#Ufj5ip=_57GnfEMutylK{LL*Ch1^=C^a>!V)yEJApWqQp#hWA zj<v_RRG0o0jN2#sc&hUX!a=QliHwTL`_JJ9x+$<AEn>U3#L<3nLJS->Y;mz>thQx| z@4;OCipCfYNa>XwjFPo9GyOPrxl%56IhLBUJ)#k46QeXi@!NIFCcKv8HUJrK6WeGr zw!#6r*P3Rc+NkM2)@U5>X)!}6YyMx4El~F;IS#rB#d`~TfBxk>ZOlPg2t9}2SzuhF zL4*JPRQ{q`#8!li{UKsy%oL=k)O9o-rCT91BWhHmI#p3rj=Op;38=x6+g}!=ky+iW zmxcbkb|n=!&0n)8K&@M!WHgis;eW|RN>Jv(Wm|oC0XcZ?NV#+G7Zs2F+o7S2e0v!f zKr!!FA)IvHTo9PWMH1r5kdPaT7vE`Qoj}TcF{%5oKUKeE^oOH!sK%KGRc`UX<~EMx z!EyLy)DoEGOiQxo7;1L9mGi32&^e+7OUzwye|U66dt;fHqAR~l%0`<M%Fz($4stFd z@1Yz?2G(Ly;CbUxmH>3>1R1brZiLJcQtCT%Y%_~Kr`#1~K^xuq<x|Mlhr?<<PvmT& z6cmr^z}Y|fit5x|+8#0gQmV1zcLT4u<>jhry7IBmq9G_-*Qu99g_RXN?m$5$C+&Fo z((!sh*o;C@F`AZVy*rwwligCuA9%!j%6_u-7=1lOQS5XwREV<6<4t96s$=sP$Y}e3 z4!%;rQ1_pX=dY(H6Lg1o%<cZiHq7R|PC(D0!@!=mTC>HQpzI;Zh0|3`sM&GqYlWNS zx9c}SKu8pCOo~q-w4v#+tEW(9=t1>-97N)3vi`Bns3Y%yL1$Yk5uBC$qV<aL)`b$d zZxbH7sDM(~D5Sp12sJ#H(3M+$YBKAF7wtJtiCV-XQ$d3A13ryi9&eeh&!!dLz|l7o zZiS*4$-1BIG<Lmw@Z}gyYbA=zaSIP`RLO)sdA#_Qm~sP7jd_KJKOVceg0Q)`!pZGt z+V*(Xir25gqlV!DbQ56wTLaf2Z6ktWt%H=?PKif>ZoR{wI8N5~vFrc|<8v&5Aw8M7 zFa`w4pFaUQd3L(k%O~GT^xDhsDP|b{q+x)|L9pFgula~RBEHF~+ANb}<r_MRSY}wK z&&7geod*2Lgh;uuqoe$NqrVU9wsm!IIbQPx4IAU&w1&IbQkjk+AKE#dccGSypDnEJ z%qAB1FDRM^skZiyqEoT{wPksK;l~NO5d+=b$p6JFyQBBfz)o|E(?QKKpM#<a<3gzR zgx1b|Gn1%~>Rnl3t;PPyCX|EpICO?&P~Jo3GLeP0f0HNpLwd}yE`882N04E26RO#G z)*r?vjyF;<TDFvRs|RxKOMg+ZV5zTmF2HpxTsh5^fp4b5#4NTF_a{?mp}Z56NLI0u zO+^vPR!Dc(*nwxdw!*w+!`uOtKJkw`^=SRAga(Iu)4M~7dRV{tj_m|Qi0A`ix7xEU zsP9P5YJL@!N_KcJ@jrauw0f{Sa8EH@Z1LyLJ({Jm#G9z6Npi?Z_;cf<*M_;#hzjR5 z;H~UfI#6v|qOOhOc#h=2GGC&&=ukR9aN7aJ?Dbko&vIUSP~)gab+`b|A)IH55X%s= zOg+v&p<YADtliX%vTHV+Ei!c!Ahmxb6uN6KcH)ttobqk(ER$O4`FVAfXXtNVY>tR` zd=kr^|KBVjS^17n^J#lc|HUV__bQn`C<PV$VIqu`#cP#Dqsb;Dlb6ts3CN5>1+fF% zW!srFVT<41uCM&$#r!*Pgx||3CGqDR+K(E?A+gX=P#R4fyrL{L`t_tR+hcvit<kq) zHWL*%ffq}iv4GU)2J$Ks9ovVq6FGZt2xYSKRLIFZe&;oDr-Hf{Vgx<3w@BiND0Ek1 zTVM3&1gTEoS;Sl~YQoF&bAin)%hXj^YvHYR;fg^`OJa)Oba4X6Cho0)6K{gwE)tqZ z`|jlOnRK-9Bi^tY&Ei~ff(ejA;Dwfw@3$Mz_$;$BosRnI>1*grY3RhN6dhJRBe0H3 z28F>R8jsrn+*AX+F&vSr#yHM9>nvpsWj-JPJmF8CYDAoms!eiAh-{)E<+%QHgV`x5 z7#P^)bI)ku06IMy!b(<TL>0|{YQ!$YcgpC4->&ihDS5iY-W^c?Z9>7&n#A6-@oTS6 zhm+JTp#X%$wYx^Oab=<?(pt(Qdwe;JNY*kMhP|$^>o4C}Pt|Z$Ga4`<Vh6D1j63UR zTww>}1WImd>+B%MM=HpDaG2SXJ;K1{Nf;%>6z3l`H|S0RmY>d92W|%^+btsnb#k5X zVJ7Ua$ayNuz)v>WmLdgP5x&*`5Nt(;hK2&Rf3X;ZKgr0@2HyG&{xnOgwTp_(ybQ5g zJ#eCblC&#|oWkB{;ptMzB!#WmYzNtEK5dx{5}Gj{8k8se=}3M7t^3zs_HF;dB22Ux z>^u3=)p)dYRy-aIEJn!z!F#mltSK|UmhTu*(A7??{G&_kq1pHu+{Wz5?X`~B)tL!n z{GIq4iRpNbBi^N70=N3=$;Hn)tFJ20yqC&6G!B1pE<_mbQ<89s0G)sBP~TH5)_Vv? znC}w(8^Tj0?<|Et88b@WxR|JvzHbgB3BsKU229q*Ud(XzjgMqe?6TrdPtto6!|nTy z8#|`6*!bBq^iJPMNk06j&p?#Wxam(C#ar6aK_;V6UYPz4)ikamlDEwM4RgqWJd3rx zL?7!~DnwivIGTT4iFm3)I~nXe2f?JV`P6$dJ2MC?6NYV>jK$H{@-7tl@y<6$;W2X| z?8!bGcSJs{t@VQ)J7Mer<5YCLi|To2@}eQ{pU7RzNk5G1w8#a^-;V}fN00L1zkSB{ zSg|lR(obnnY?ht8$7nkSwM5b%qSB+Er*O$h&_Ig_Vo)bT#&E+wU&o~{$T;gyu0SeY zhhQIG^9IkSVc}*J?`|*2Cv_rQq-~#5s>UBN*l0saCl#ZJ|LKbTW`sdRh(#Lg?f)7T z6ScpSTDm6bTmM!vl8&G=UTdRPNuaNjB;74Ndgw}iK>L&l!Sw?-J$CV_k>Rt=E_mS) zNzRq{h1k!d<se%ThW#c3*WL$ov=)yfNTU&w2bo!R@VZ6?ok*C3E$F$Pmo_a2j8ZZ# zxWo{VM>udM93K0_a@2$Yxg2PY%J7ovje-cw5&9K^&ZSUb2NLrQ)o;0&`&*7QUPr&5 z0o)Dul0k|=+c!wR3PByDZ|wK|vxvz_7Kcl`huuA&o!*11Xv={HGusl*iE=sPgRA9s z#R^ekS5CX9@BU}0p`hM$h?dYBS01F<tZP4Vxvq}_?FN!|Popn~v5UxOC$E}0PWmkE zZ1zz}uo0GkaoqG0uaH^pZny5@BijEJj_V&l(iAU<Z&!-{%~&9|5&(ivH{8Dnl=r_~ z^zYu&B<QBJy<y7Diy|pRlG#Bpc&Of*mq|a3wL3QU<kK?8G#oLQAN=JBRw*cvtdoU} z4@v1<e`a#E$b_pkOFV<KVoNyPH*Yx{7&P7AKW1o8HN&YTTe+bDJt8ke1G5s9ek}D0 zFnuEKpcN{Y(|)cWXnZ@j-}268vHT(0^I(nj!rX!8z+m~hhj~-l5A<tyz9W85PiZe_ z79ob3qGBr_*oLIWi#4T3rO79iwIv!?8eTdq-%uaE|F%LO;~`<0TFvTeMPBaZpMq;n z*ArhVj3BwIr+HSRO3!%6!N@S07$h{BfBiZWjmSt4sy)icKPSw+yaw~F7Tq;$pc!Ft zx#6tJ3H!6E%P~CLl{ul>K<#b<!Q;>tnkUA$r4yHXx&L(Cc82e)O?8_a+kaUb&?BA; z81(>6ThtpYGPGTGNXoTpm%p5i|C;dgf{q5hucw064|W?DXfPsV0$MO^gDUhK2|275 zDEHP^6?HalEee&4Px}l9>01o%zOl}c1YJoAZRfOU7{7IIHOWV35MF?kzw{5N?1@2; zis4-ctlw(fp1?k1fjm!&KT;?16sF5zoQR5w13~tXX&pcJT|91BO7ir^<i7g4OjR`) zGQ8!qmjNO-4_i1;go(OgA^lsm?CDQ+3k2hYtHBE;<2L&Wzb>!9(Vv2TDulox5#!%7 zIe1!YGomgOB;UrY7x3NO_ePJmvs}k!yLzV8PNj!>FUWNqY%lCw;n7nM)Nyw5Aaoxf z?r#!OFXH`=%lb5sp3il&pH|{BOdEw?xq}M^RaNtv#2Sq#TR*WZGUkMz)sx1qM>8<D z#}A)4wvyQ|dTwHKgtftcM_f3$5=L1hjc(*^c9lXarVZ3)K;yFbY!A_OJfeOEEoql< zgQqxRYN!idSIoGQsNgsUbnn$?m)4g6qir?Ij-0CScI!Kw1=D-&KNTt8kE>N3&d%Rc z$Q(<>>{W=Ss~^SyRB6e@_jD5Dz!~6~_nY!NUli%A2wgS9EGhjmA~4sgj4a4Aa`^-> z>o3i&wFr^btP?wZ<X4OUtE<r=D>D6juvQVBC{n(=f2t6mioJ|l=P?lHIAzl%>$ZP4 z!;nO~_y_g)Y<Dd&(6f~<V};>Iv!Q#}weJDgGz97p1O)*92Sdohr)h{HXj$Y`0wzBF z|9tBA<_j$gcoeXUBXz)noiS!u7H5mYOL^Mc72V$TzG$#VYo1?!V6i?GXG-llx9<*Q zdL2)v4zkj{T*9(l6Ik)R+pm>R?pLqs3bVuFYP4<ZjlxF~pB7jVlzAZSXG|c@e^d2{ zI!uoJ=9TVQlpdfuT{8`-QDYIqeWBI(IF+Z+oEtVmS_I8M1>qIU5PLB_|8rS@{TEd` z&-}|^Jc&sI?fjY4YfYBQ7H^(JQkg`NcvEmXz-dC}^0<%ofY%}}6972DIS3#VjNIbY zP?D-Z0FpaO6NV8^D=nkkrJ+y$!0y%rALWp<RdrYlQ{02n&-~=DXoGgY5da?ha;lT4 zu1gJ|d-}P#+UZ6A8e_X@N)xY1=lpg^ENQzJizROZ!5K}*e?UBme=be1#vgD$={=b8 z>4{MV&$Qp)1RwIJKpS#wQ`l6QgRrHk<Q{cS`_Z7=gW;Gam(w9j{NktIlPfDpqBk*X z$#Dg%>`ZBN2Rm9wLjE3n2I7uFNr=ADn6qEf=NZUHEs_#BnKL1F+&y$V-Ds6w7t6OB z&jdUT=tJPS_h0YS>AGBTN43dlhALDja_UNz;MUnO>jF1$m{o5k4rq5O5uvwBIu$mT z#L)U9s1qI=Vl%OosCFAxD<(P~I;!<g2(UTLvB`!tibnTq<5qB_)BG2}QZ3=ptV~Dg z{|SoFp9D-``=Rc}BQ9zGMhGZ=-b3cu=f)RS@Xe>a<}Kd)f&V*+MtB{0^3Pp%uP*@Z zURn;z4a|dO)8NY#nOyjyfO@H=<20qg{?4}*!1YZ=hvKgd!Hojjyg+-Sm(30#&kL}G zm(?^JjVB+MGtMwvO7U1Hy~nPqY8m4{f5=O*qYeSSr%T%rY$e9U7l7Qm(#I#~UySs) zK5hW_;W6~7j)Bj8inN+cwUPMCn-LEK<@Y{Uf@j26#=j%$OJmtbX{0LUoR8r$*=_S9 z)0>3BytE@HtDV_euR#1o{ZrvyuA4=pV%joGm4wL44Ax7B`%A|*?UGb|_jHd=1zz^0 z*wR+H1G;a+9h?saqfl`3XsnyVTeW~L<Er7aP!Z0XubVW2wnYEgFLkQ2k3;FI%XxtW z0%?p?V4>~D9y?XMEgq_QZv9wiiOpoeYH%tI(K$5bA9>DJp@?>`V0$h2>BFW!qV#`v zal8w(a&fx2Yo+Jc=SJ7}w%a0`>WDMKjsPB|$Zx+U1^fhx@fto6z1d4zna1Bs)rLR? z)VuoktPaoHer9r8b#I2EwjqV!mZL*{=n%IzSFKIFjLN?G(xsKw;glh(UL`~zQ191r zT;}?$9uB76YSsN!MUp|c6CTAQ%j%D#FwDp4lsG&3sbbmkE|Ve0BjyrVgeHk78fV&% zfYzqK51}DozTS}J!O<cn!!Cn%onCjUXW$(=WYE5QlPfrWUrww!2_AQ8@5|o`3TwW< zfVn+{GuK-0Vo(;1;4jLt6kRIBZ#1y(CclHwdk~2lA*TYIL_4@mmi`xn{`2XpfO{8Y z?wf$_tdBxYIiRj^6ogrqePxqovB!%(;^fkY=z2J_^wiV|<Xr`)xFuTcl7nfa_Y0(y z3^L7&UK7$-7g8V9qHXXo3i{wO#}ELE2BvqBDc|i$DFA$UfX{xh!GPQlA1Oc!DPVv0 zX(C=Qj0$kQ+_zL9!U#S=QrJ00d`00h!&vh0!g%G24?z6j#0dSN&c!<m$LCkNa%_iA zhx1~Oy}rU7R7U#U8;1(q;QfzZnMF^_(3RQ{5G~rKbwgr#v>)nV1$~a$?zIy-jL6Tp z+!VYCIs{kSpzcSY5d~#8Q>`HU%u=Xq;+JWQA`i6nJli${^JO6Oa!7TrP~Dyu_2Nw# z$m3R)5g)+#JKex;udMCwLLsUi+js|DO7So-TDefB7D~PA{2sh2MUZgg7x}TniL<Yb zn|)4x_DZFy_JvanbE9@yV$o#fVtuPdrUG%2gaF@Gu^8C?hPj5~S4QKpp)3}cS8lgg zVF<sQ%Xne#PMrX_OY0zir&#Y#HPf#wCaaohwFbqIZJ^M({?_s_Y$v5C36+(bXKLwE zo%IM;ux$Q>8wub}zujmn)V|#=eHPXPG=?`$P>XF^^El4C*8tcgpP;Vj;HXm5d@1#z z5Bu$4{S_JuuMU?<L{8VYm!q<AmPBfl#jW7zPNfIm+n?W(Prv1u%ab4GXBN4&fd*$= zKyNfQ%hS>#f@9BLIjjhpBoLZ^zEv`CzkC7aAH@16jlCziuJ<Gd#_-#x8TPK*cg%t% zw}%(9MzCTTt7fN;xE)6-!M*TzCPyA689;{s@VPkJ3$4!)PEe^xDdox7C!L)NqCvSg zqLb5eSrMB%7{Lt7T)Fr@#aNIQplK*5@e$SrO7IR6fcTd9%H0Yp=*aXY69sP&ozqf! zx}?3JDAUc0ee6DE3PaXnYKFG*tFw~G%NzECZ+3&`Bcj&Np@$*|A#4FU1F(tXMCxv9 zSM#3st7Zx>*nNg2Z{<f>ZKNW`h4Ee$*m$(ST{)ud+I-1Hqt#1~r^R?GT`LNh4uSg> zVng+q7ppI!TS8Y*!irDT%e+v{Hh^h+PnL+cd|b5q*R0+fAQ5BEgLIoHb{~f9{ybxL zC}Tl0_Ar<Cl$8VWQsRz_vvbJxtyWk<>YZY4{B86{uKP8vhA92ph~m4Qpfw%0i{lQ_ zC;~0CqGvdw@4U0UOC8=U8{>#99t$qLF%3Z51ng`sYen-n9WH7$st_F?hLt8tQj?1K zBrRl()^h76$;oBxLXj=$cpgWZ5XalrGolwh2)aXRAL}F@WP*I2b5hSQ`prT~Bo1Kk zyzD1@uwS1sSp1)&9IFk!OS4wx9I4Qe!EW}~F-`yp*5DhC1?as7-ssf;qP2J{Bz~8x z6+&(g1o|%8gH<5|6>DBZQLWJd&-Y$1$xA~Y!^n~%_To3PaHy(yd5zP}zx-Y2tPi{U zcCt29A?r2Jo1FaL`sXj`fd}P1vmO<GpM!?*%2wxjSm(s)xW`1L+Y#!Z^)Ii>2Z&rF zqi?eis(?ksSfDS`LVg<j3l%cK1F1r;l@|Y3mO^<PP<uJKkWX1DxuDsu5`pe9kYu>@ zdXH7V+EF47XdwpdGmiP{LI7}(Yhn6)KLAEetx62^1?5?=CvHji-^R1Sb4|g0`|mAh z3wS?G++?~$!^?b<Pgg|;TnC222#T~klu~z}Tt5cwtnZ~?dm$d|OGPve)?u^)o;zll z3}iX41Wv2)Z9+G+U=fycj*_YqvXG{beKQirJM2y1FU6BNSIx=A+X?{Rx6F1v0J|Xc zr8=k7r^X3%#U70=cW+pk^7X<c$^+wxj0x<adD86ju(i@7ckaxJ*V?ZD{rO<|US)vo zlyL%S|Lq0xZ2A7&bq)*I?>Djpq_@|#(DU0T8GD<Uv*^_&=s%Eb!!TLFwmbNg^|&Uz zA1~?21KLdn6h4oC5R?$-J&b>-9U70KkyZsviZ3~DXGW%0Ya(U06g(R7?2$MRVI{h( zC8cbKZ7w14EVMjzArswNXmmU<{Tai#O3!tDwK6ZXzsaj{*<*g@VXy@USGLj3_+D*T zT4R|wL|bhr>Z(}!LR`~9x#ce3-T4%gy<YITdrG2^CmJ88gCn)@n=8=1tjDl5CM#vm zt9Uq{axqVU&sJZb?4e#v#T;(Ip}&lM^dHQ5<s-P)J|lS}h)Jbq1C`9{hJ0PFRxfG4 zZKwZF0^55Jpa?xox&`7a_b(wM*%Zi<Kyl=8vrB))pzqaRC1LrwU(~;FH<oNHXS{Vy zHpa&Ax*Ue{27fdd+Fp%kS0TBHzd}~VC?bLVl*8+&6Hk5tS4|(8;kGe^e{ma5pBRqB zAji1SW^^-kKjgB80`WL|1mN;I4GzanrB{LrLtj!Vgg*;eOW1R`Ja$fs8d=;nYb~?f zKF1`b+oMTkx)yqf*9+q5WbPr=mcOMh)%pm`Bu-;$FjJR>$jvD!$iV?^EARcVLlqa+ zu0=5SWU|LiZb}`~dbym%Ws{>?0`}oD_b`RwVZz$%)dzVdI^?&#Rx)RE+HigN-IH$e z%5z5mTzT?FLphf-Al0QXRHl@|d+y}{+yAC7=!o;IG~nCebr+WzkZArJvQ17(7=PFu z!E9CUrG+gPGirz?gfF0ts`8GsS2D`aR3|wSNUl0ELHQK5`g#sFRjhUfx8+(anfz6; zSTgi`DohPd_`;`&*mWdIz?YJ_X;WKYwog(u>#MNCf8Z*(tlrYDIJWywpby<mq*FdC z`y3kcc|Ie&c>6*T%O@d}VHxBFyILI>2i=ZZI$a*Kg+=mH25Z}d7r@TAm_6W)U)`Pb z9#|fxJ*wQ!bl2Q{Do%nIzMoDZv*+E(pd!2&F?xYaf@N@gu7~pjvxdke?*8>@f*wDE z@R<KBj5T)kCeVrTOy+eraQ^;z%L{`~Q&}p3CC(kGCi1<L0h6?w#v%aaDc#1!!g)%w zW29FIo^d8`i4k2=A6xFoyGGaKc@Fp|>>E@-6b^lQUAcxV4$)jyhNWbubL@=w(+8Ht z#*gZ<=GOl0i4D=%0?oa*($te+kK*AU>mu(4_gv5Xd4=)zB^B~$eE*>9e+E0b4?U4h zzRXCG-?<H1q0z0=s!Ijru8@c0X7}iudlV0mR-+Y&dRU*);)THJLxnygj2ucdj{kJv z-MSBLHPr?jG5fcXc6huAxe)MEc8+V3N{s6Yx!?ju*B;UfuONIYi1=YQ7j>cvGj3_m z?{TtYO67(%TzxC(EmD#REs_whiNp<KvN{<8ihsqf{M3u!Imuh~I1E_PZm?rdYcR^N zT6M(%@D>1*nkB)Rrl`+!O4J#g<+bD>pTv@#JHvc{o6b*tZ$ZqewGq8B=pqz1<`t=n zp5DO=>w9U#jlimdvh5OqExjc=xc=d$>@S5*-By>?<6>Io|1CQ`<&ZrqT;s+UC~Fzs zD+m5cXc*ojX}Cv@9C~q-#ikcGEz>!Zf4XH*rI8KliE-wQmQE8cPmp_d_$YQ@I-Je5 zl!lTAsdX<5fC<HnMn@G1vC}VF3=qmeJw_-6F`aLLQv;CSB9xEwx@i34WK5k?3PTsT z9lI%@z}vhH!`eeqaAM4CriwJk&DZjzQ2++?pD0dRz9=O8YIZ0{4ocvG=Gj!W<s}n% z<5tNUe^TL2<M|Q5M!%yd@kRZ|V$b(1HX6*>?0~&)<l^mJdOhdNFxXxU+ElUNYa0Y} z5qaXDi;fe#3(a?R%RVre;ptRj!bHgA5k@H$0$vbIEKs$YU;2C*7Lm;3J{@?DkHwWZ zatxLSX>D&NwYn(A0l`|f-)P#eUkfXGk;RhZa=*Qy`V8&j5A1ML$YTpDut7V2rwk?i zV*j%E`V~8&KTym{{A-DRv)+tfq&^m7&Q%#W!4pCjBkeq4;$7;#5f@?448`{s+(l9S zy|F9__Ph0#BDsxUe{T4A=3qg5=RT8Ml+feeK`qvhiWQNNh?j8Eg`y0GAzlCZ0+M#w zWP;idHJ~mAJ3xU;>f^mEN{eSn<&3)Hhd*n=gwo1yDk_pF=j;8bRF+9}njJC>I;|00 zHouq{K72{yjr%y1A=DSb1|DqNr97-zfZu}o6Vu16nwr=y><F!gwdMS>DplpmL)yJA z#P{1=p7jpBuRc~LZ<||z()sG!1MLBa=Z|BM{+c15{y4<~afnn<6AT{4+d@IZZ>!tQ zYUxyo#d*wqq3NaR7W%{6&vioFWCZLfin#u@WT!{EPr>)&J^c(S)B+SAt_hnc42vsN zUu+dS>plzd={%0cLxhSwpy(IaOfIjG;hvVX=*Q2#6{ULrIc<d^YfAaGTDJHFBV<Z% zFLAXghvSTwIJ0R$PudR2J*ZM!Luq(lXp~&%u-C<y^K|6NvUTLmqc!ux_b0TA0cX<h z5?}AN>I(V0@bj}c9lvy#v#8Ia`<gBetKn=#0YWJaj;1QD`uiZzqZVe^!Nga2LZWpc z)t@Kavwi-cMvf-X{<{9%o&XrPHBD|cf_A%oE^O-2qil#tZVwp)5+<k)b(3QC5$Y$c z`uM{-mtn-MwQzhDEU2w{)vVSk8e5{0h}J`LNQalQP^F=w$4OBlGKq!U;Ov3|e+ho0 z*0Rg80mIvIo*KwO7Th;M9G0|X3detILj3+D=ivE*E?9eK5%+scC4f0N2AE1+w*4ua z&nV>5ZSaYb6maJ<N-J{%_n<P|gkm|DqXm*GSYDp;VHui>+hsYk*(x)@tg~2b!hOLc z+$u&i5Ug!8w!8&0dYP13j`mTt>A8933o{7dc=xCaV9{M4D@%0Pa)__IRWeaDNe1>a zcUSj5twB^9#8_dMMB@rHnfV?czBEx1@J?aw?C-G_$&lb{sw9-|TpfgDdZX77a5_}u z@!5+J@O~JMi>gzPE-xwYc+!kV&}TOkoPK+LX?GWWTA7&^3$(4gMkEnt6}E;LDCL(z zx7d6m_G8SBdnu+%`tmfUZ>4aVmV!*^IjGUV$IDw_xdo?Ks6Iw?_tj3tQgNOUOt3Rn zEq2Nq&gF{s4H_W7CKLH5j6B)Zf*9o(j)FyF#M^ZYn4eKh?rvr42J2?kXvR!bpQZAQ zf|VY>D8=$Ii1+SGv%Pyl{D}*c;e$?>Wf<bb0;LFQ7(oyG7g%Lk^EZjcZRSg;Jb_he z#|Y@deAzo_Kx3Say}VeB-%Rw$9_zQhWDyq6ewyZ!r%&@;9|Q+3uVahWKBgo)4@o-l z5;||$UR{%G881KITtbeMWHg7WEq{tVW?%&I2a5VNCz&s*fFbqjlh8=LCRD{G*9nZd zBNBU5j%L%N^myW=7=+9^*}=ModY~BE7T0R6$`|UW`>9j)0<3htp0gKuh~nXf0MiSC zi?Q@f*&W{dFSv`<*#&*zas#G?Y(EmBkeiD5`sw2Pj_!ub=I^7bJ`INkxULCEJkuSZ zLN8t5E6SUCXZ+1$3HDxh?YU!xrM7cu8H13%tN0wyvv9uF2YuEz{}x~*y-&it!Ujkm z|1`#Nk@05scvt*$iSFOt6U2Yv@}1{E=W=oBRy{N7bMj+?OAiA$(PW)J`*+=FMaSv@ zj`Lb1l2*!~eLfBkRcoIn@TC@iCO6Xogp#KuM-30h=unui(^ASnz>b@<Zg~dzO+<WT zj2C;&%mXW=3uth70W*J`R=0wZ=Jy?<-Mdj9$nv7os?|$}_+zx_5BQKWI_E1|gnvDt z$KvqYA?aj3#}cfUkNAlRC_jCq1_Qp>_kwWXY4lnkr??mL&0zzL0((J~{>KTf1Rofg zmF{Ck0pxk^5`^zrT11_KMrxDpC<&m!WxrU(d*JoScZ1#WSYBAQj+Ip8DFg|$%W43x zxwH`*J6eXAYkr+ec&rPf;z1W_RWrILxBpX+wPwp~Bm}|X?bymDXaSRPjlT0bqlEb| zPR22&Pb!&_;LA3?1VxnDFJG7T^AGesDTqr%8RjF!D9)pAc)0l=J_nqIA7}`_P&9qM zkLfg~6Qssi`ijVCDPr|tPbGg_$AW6@V1%LD<OvN=*^+lF2z49>l#b<ZIvN*N0yE3u ztKb935vo8__9qxw8nnMABRb1@<$CMB{Tha*YPRp#_b{~>36MM_F};gU_^!{1eL^Q# z*l+!YQgbfS^4b>j;dN0k-Ak=MRmU3rncR2uIeIJR;rg*UC<p<g5V}h~AF-AxH?8j5 z3ED>T`kY4oh#r|9MYHEk_Hy%EYU?>WKYoS4(QC!+JWG>lKGBGC7TD-#qvVQ3yRWP3 zZbd`g#f4upXQe5Bw%Z*tDt1XVf%)g*yUV3uq$U=>)#F*5SA9=)3Dlb%+Dk!w=}?0w z|K3kkT~Sz5Xv})u79GJeXICv7Q?eP-CphOR_edz93sU_T6n<pPObS}S9{99yWYoc! zJ*jfYj!(n*5kOG)HX`}qc$fSIL*0Vz=U#3zi}~D#&lnHF`#!vY&t+KG{lDRtYRA;& z%$7o8^w1dahz4<{?(^H8s`#T$vcH8N<kV6_hZ`I^D#*0jB!E#Z2~w63($D+M{khez zSxZ>s+>Htj?0?kSXe_avcklm5W7XH2W;x3YKGDuSuM+Wfah*e!5Xl9*s^?LuSe7op z-J7yL9ZA93IsFr?p}S~`Ovu;d?&N@jKP7tz8{cPqaO<Zv$FJR?L#ouRfTiu9hNri8 zEa`HYA_YX)N~N?VyP>c?GcYw7NZTznQ}Oa-dOqL*t`X_f57^!P@c2E@C*48MDOQ4` zpanP#!hWjy`sXTZdg^3aK4b#~&#t^Ez$||$Z7lh(OLdA^(DH)cJs-*N!%f7%<#xA< z<TpJby4^i#6CUqNJUBsbr=v*THZN6>`1NN~?6k{WD6K0ap)!JLO-T+!VDv^39^JkY zpo9-h7ApWPAS3+XdHa>T&nX(gm#YXXQ6J4X){J@c2lYO7m+33SBZ4@)pxJFt*n^?a zYGx9T@pUGfK1`?L%4~eIWPG4~p>=?RVyD5kQRL+3n2R=@CX>eURd02A<*A5WLoy8m zM)T17(I5w7lz9~OKJ6K1Ou(%k?rngWEJ_APdc^i%thW2-$|B6P>x?Gl+ngpde5~UD zdH?TI$n!<tJLrTk+tkxd@0?MqOL>s8=?BJidi>dz9HZk8))Gf)vv5`7d-qN4UaM(G zYI^pBuT~R`uvWdIBJB5%%FPpik+1aR#F)E*#j>V1sRRGCw(D2!zT+q37Yvtf9>)JE zkh}IS0Xc-)o{vZo1b4b#l<+Z@l@leKkkXiEf<OeOMOtwq6fqK7qD9Djf%zonBj$g` zOrghqZ@d6L=Tq^A`Zuc^|0>vZDsuGzR{sR3Ze{QAux1MV38e1r+hQg}-_yW60Ic7o zn0Vilt6#XWkcr-+au{*KlPuT>BOq04_xxi|vuK>v90iK{<7@Hcp{F-sf>szZ_fG06 z8UXrdbLvdU05iZ@i#gyLP8Qw3BIha%QuF+ai_0mV`Nvoh!E01?)66t<-^PEu0OShn z(G<b(B-N<o#gmdRH+YDDNrvefbE<Xps<FTW3TzT<$5adGhZHdEZ4@=g<SvEKIMr@J zl(083bc}B7k}IL*o&t4R<Pqi#tV6r{{Jcu5)+g7{w>=ywxSe5`TPrjsi5%c6#2#S# z;Rn)3{}u}s1~S7B9BPaq<*5!WX|RI%<B%~1ec?zh%PRDI=tMEJx)f)b>A-fTh0j&w ztuG(CaNIC#f}%1tl%#LzsDt2f2M@{a^Bcp;cRPz+mEa-Ng>0*y*{uaiJ-wiE5242Z z`Q_i$sZcrOWM_j<)v3N#JK5CZCnG~i`(s$zLy!sNOLk}<d>r(cpO?SI2QNdJ7w>$v zH}DwDt`|A!(jDT!Dw$DH3EBO5d+-X3?gQhhDGb(b&M`>4<UV?#%(s6E_k)|Dpi*zs zblr9lLKtj>vr~e~GhhG^HHo^-1S#>*?ml{4^C|3#&((~b;@eW5d85{xk(b>;cEJ(( zVQeN`6mZ!9bNBKBowcG%1#px-&bu$vD?}Mo=mmZvO%$A%Wlr`B43I3$(*+PwE^IE{ zW`ZkyF?41@Uit?4J0#$RSD85%LO|L-W8RY_5yt6FtF7$MMVp*9LRS!G;@;k;sTX<k z?^`c@4H{r(WK#o#!^3{esa#$T0Om=fwK(`sd-~TkMCz-V2Op2Lm!|oF$qX9#A(`!V z!-sFFvBzW3D*iM2DLTQn&0m9ZbLR-Kfa%oDR`+F<q94v3FfaQ@y>as4NE6nu?r_q7 zOciyrMq}b`S=~JAT4WF!O*bB}U~5TAigatR7Ja=N54rOb&Q0F2xY_tjpj<ofMrRzc zH#4Qhj{%74LptuH^riHv`T^`U7GN#7@k3q%KGKKm5DZWwMxR07ge2>~9Ex|<81G$Z zDt-Otx$FU&Q~V&y$$7Ap#zx@Dy`#p@=HgL9oT?3Zcp7=gICqp^-utR0$fuAmsxS`D z-*dX*z36e?)>2t#83u*qEd9rCqVgO!j>MGpv&dCA|71TC%(ISUG8vdrQ{@mrRo6|! zCGyrCU^+F3hDhwvSaqE(34z^;uV{tw7;NlJn@fEK**(M^IW4BTMOEhq`zS!rwG>{D zK;hWB$SrgYcBqJzaj9JI=vYHha;v4Vs!2bb<{ozH|MB%!L2<Rw)^0cM8r&1y-KBv9 zCwOoP?(W)n2=2ilcyM<M?(XjH?x(Zs|MtE+w_RPUs%6df&N-ejreG(BR?6m^!rnF& zav|8?x+>q@w(fv)jXkNjXS4Vv9_#+zWWYq_c7qQ;*7H5Xe?kZ2#%Mll?(G`>Yezqw zitoV!Ra3w^R{!%jw46^ouuMPf!tdCK?S-#9dHZ8u*Le5-AXQP+0g5>qsJ*`ul^OpW zG5a_BZ*if)SYZP6KfEJu%x6Gw1RMS$DzeA#u#z-^yqe3%SE+KPi_C+c=S2@uzwt+y zdC$%;u@@1)8S!qCdOr7&DX+RwT$0HC?hWDOIr83p*ErKziTpK^xs}K$(78ox@L5FB z;4lr(_b2SAn~044a~?cK#B<Qp(tQOf)B-V?L*cZnYBVn`SqC3LgKl;7VG`XgLu?Tp z=Xi<X%U3CHFJ6}!UBP*3#uajoBwJMW!^}@cGKb)YajGlRZ@v@A6V&saXW5Lwla;by zVfH0i{SadsmbDmBm88KjakH}9tA^}vO#xS*&R#<mM+taMnuz)3oBRa*3>Z21a3N8| z?`0q0I!A9P2$vc>-YpJA91zQS&NRin9(A4xHk@+*e4%2_#NMn*yC)rT)2)oTq+V-6 zF@b}6sL*BZxUp4(&cXYuZt8x-GoAYX4$++=+y3LX%va;JYmBpy5vf1RA+X&n4ny?f zaWsvm`g}>?@i{^}hWfGRg<KO0PNx2|;$7IOC2a2F%Oudk6zNo#U#e^G|7n5$7=#O! z+fF}HaQUirQQQWzx?nOCvsg=1hbWfI$c&n0+}qAqR5?T@1@nCy6!X@g4Uqs_6F{~i z()>UlH9)({R=+pMI0Xm5J4SpRU-y7>j3!lcr<F#l)@G=Oe$3a8DwAi4Vh)&yLOD=^ zOxktIO}btTIeVUokWIhuR(q88#<1y?yOK4*SH|_Zdbxj2q|03rvt5S!*#U>KaL}(L z)1D?Ii|}}iEg+#iTm@`jqDYGGWJr49J546vU>6mVAv&JU8SAX)Z_G@PE5z(f=iQQo zHMYtRhdLg(N@F(*^p)ECdO~hOE-S~u4vC>1tN1O({!yTzxLo6^ScMXcry={2RLwIK zU8Zp#qCWc{ocLLT`}|N|y3j6m!cX!vk3Se(zmYr+xNE|KTQ1LiMfGGvzMvcg_A?t2 z-`3P~GVLT4ZZ<0cK|BE(t54&Q?Z@>cZ`bZBQljWbi;4;#@tD_>{u+R$ZyeHyH|9R- zv?Rdo(U{Z>9%R5I<u$ur&{5?|cB4xep&UR|@jq|aoWk{|=-U=`Wo+3*n$cNKfb2BG zmx>-A&^EG<NF|pxh<m|2;##Zzo85pVTv4|ULggh(nh0S%DPqji4}M9HW05X6?to#z zs{IYVVrX0@Q71xzyaa~%NVe?Ik8qs(`yJ3A-c|+dSJhwbh)42(BKUr-SN-?2iY{7+ zLyEC0fOb@;q8EYB@3Q`iC|c<ejD+y-Aujx(H#T2-AN~BfO_eTLUEw$NDg0JD8LFlw zQ{q7RtoTV@qZ#u8qwdp~stN0--TQuYt70*n1K8DTpO0n0sWT`9DWiAE8!LKLOP{j4 zTl5zE34p`C{7F$(weR)QPJdhX!g4LXMk8yf9d@x&F_;UL$5foXxxdq+v7zo*n$K(K zE0M`|yr~^M&{7GCJzIW}JwIX4;5I(Cg2?}RX&pWB6AiGZdkUP>V#c)4glo0zQq8DU zuX3B^F#4Xkh<aI=dssNjlRpjr(mvoBQGKKHfI(U^mEEBQ_$!!#0cWsvG3R<uz0Gk? zDyVPDvSi7WWJ4Dx62G+HmIe{7Bj5J9SkyQ}mA_?Mci5sRu@WL7^sz}iUgQ|45=|>X zwKC5zxkuO3PHI%Dz#n1$`^Naw5fjL|z-Op%wUeCO?^-)(C~R;yjTVX|_;-c$h19>E zutVzDadYeGCKY<IUfl|fCr@O012);6x7{C$pN(J2vy@-=d3oDyYPAIaa_ZnGcIePF zS;RU<!xf)vD$YDK#|SxPjsw;NntNKGBB3EPIs)+ehe-iU!|N@t+ip@VDW|OM^`if} z`c^k_+Y%)@wJ(73nKFW@!>yDhg#voi|7uegEa>H}qW3HUh5dwss|(hBIb*_wh8uVW zCm+2CNa@9^4ZpzTt_p33DKv`#l5%sMgJMC=H22KKFFK7qUoa*Gw`FgB=>ZbPhg8;O z0hOziu75ePSfpJ6L%^6d-=HgfXu+uRp=UshW^bCWqxxE_Z_bg)@>)l~2y1PwM^6$A zos-z!viJ+xTIaccN!+&UeMF!yfAXd<`s1<EkFYk!<L|Ry(;9U0jQvZeilwOrQ{lo{ z9y=dCOFW(?#;lVz*vIQMx)PmD0!;fX&gWX2XXw-PAwm|Tcwt!r_I`z?zmZ<9b4>l& zy9&u*7E{@7YVZWD&N?51>%gx^qMfMoPvT-aX3l1xlS7oN7Svs@wn9F4*`5h|>MfzC zGToCZSEnl|g*P?IRLsrhf8YJ6ID^2ySwzFp@t?3sZun2wbREC_u_#xg_CJ7yB07dQ z8UjM+`VYwXXxryrIRZnQkVh-qSm8$0e{yiUiJl!fOC096JS+d_`4pP}uC(zU3;%Wv za0d3cwmP1+JKMZRzN}Qs6eYW!7IaS)jnZ-{xRG7{6bU8(nkPy2aS;5L0DP%1V+Mty zwH1^+aftWX4^O>4(NX!W)CzU#jwJ4edY}Dl>Vlpao<B<_^Cd00g8L4_24}(m2zTxI z9akojuH+NN<~(mup{!w+%;k7KkZb2QQ_Zwr29^%c-`bqEdYf0{Y>;oH<+Bn@EWu>K zQ`5MW5`Xx7rpXfB`9NM9eVvz%O=&L$h^@E1#<W;4&9iiKt|V$;CNhT)Ix@LfMo^7= zm6@ZAv~H6N_MPE3KJ+WI3^vg??=z5-P_y`ZJ&6p8WmXu*8|6aw8dP7L=4A>1X%((< z)H)rkFLW{Gxz7G|wQvEXzOGk0gAG%csSvuqKJxqEI3-Ba;k4SV+K0hXmvBJt^|%*A zT>4^gxk0>>j-&S!F~NkfhHZbHEI?Dn%oJA5A&&8U)hYyRVhar*2(7a|U865i_}%E} zY^4a$G6NmYuOoosQ*^3L6-JzlmdM@?m-B^pPENbb+H@sSJfLLz>)&(mv(jUvvN{q4 zf#S*HQ<x*O*VipaZqS_)LWO9t{$q_N&4&O^c;!z+l+e7W(QJDU4~H<P3CH=aiGMX< zrv6%B!a$ZAy{aY139>8t8yudT&DDN`tKA)-WE?8eW9ztWZ5LhNqrc<;toK{KjAtzj z5~`=O)m<kZ<|W{7;0~rR;q*D$6`pWb_YQeaBQq{rNfk5EE_V3-x>%dDS<_$c^v)=7 zrCSxYQ&t3|3KNHzMU08*a+Wz^#3-_iH)l)mp?rs_R183-cAlncY#LVtgmOn#8jT$v zqKQRjZ#8H{fE`ybLLcT3hhKld1k*dm${O(2ypIwlB>4+jhqK+hyikLN8n?ZL9jXQ! zxPB5Ugf_N0-wI3FiKjr_dVO~4{|N5dil@z-7F_2hD>q!ZrmUc;V2JGWL6Wks@x@KR zL^)>~hAZ-ll62nY8r3O^{1PRk=zrPTk;@;}q(v}p*QZtBwfv$eO?zCH#sOFB8@2w8 z4>C}N%5Akv_UxOUJN*YH)(V1(A1=QF%$Z3Ep*_6U*6HFsO7=0gWdsOK2Ex@2nbqwc zwp6n%?X+vtxVx)k#7;MiE^nAE*@D~cUiQS|Em7gkc_@Z9@!Kt-CM58Jt)89apEBFL z#kJ7WsTv^2Q@wY<?KA>8jRLWPgo`ctAzKvdGj@S*kr#bT=Zyeh+U^4fx1}=?^wa8c z!`0RxNo8>s81WS=`%;@KnS@8lGh$K7K)oX-*?_S_;>A2*mhltXSnrig*z+y))mrj0 zo+}-I|7S~R&;1~h{UQ;-jWcYERQ#i)gCt+<>;t+=pxSTHqQ^rzN@lZIR8~vWCqj%7 z6{T$EE7XNn5+Zf1bN4E>BZVuVq>$$p3k3QIc)0`0@97Eg<-7&-igbwi3b^gfRNKxw zJQ|(xZw3G%V==RDUyF2ZY*b_&O=NH^ogTcVtwyr0c#PZWSZq0l!uyo~&TFL>-ITjF zqv2L;4wKY99cw?_fPS-%uf)3_mVMMk9djaM8Mmbc`tQ;3AZ=Px&reKUn`4g=yZ;cN zAH4q~(PI|&o67zhTs)|fZs=eF59TZM!#g@UPC9VOk8OL<oi5o{pdcM6{2-F4bLvAZ z+qyygA$QjSYs;jZ`G3;iyBPY*KF~aXUY%n4!76m=O67VeNAEAHcC<|3w~i9TPROpA z^~PI}WAah<Y~<tQAOP!+8UhjqTi2zEdOl|-$(UDfAk0aM5P*TP@1aGWt}7C;)jQ@) zkFTT9mwuoDr?M+W%;OZu0M(icxWRg$05#%G4L|jiRgr7^q<6T)9`_gU!LrbHq*CZJ zAjiOLIIhD`QH@m7j;7;atu%8Lt7A>=Mc%HGuN%K(Qs(L~&;%l~-W_Y{=c^bTvV-#v z{K6$nb{lM$F3E#80Gf`A;dLs72qC1a{4&yl&-9?yNGPp^cL*#o5X+Ps&=cVDt)RxJ zbeoi>>pNG#_uIoq1ORTuT}ez%_@Dd)Y*X`s01<jR2R%Qi6Jx26ep?D|)AB8DL}$tD zu`0%ZSe?8}<=$Z&HmEWQIL>Pqb6xr7`jSJ9AeZ;5b*I6wW_`9j`qE<su6*$l%eiY~ zP;$w%B(MVl;E{B3In>5LKxb>y6H~qSg_y4*RYnm%ACEQQ?g#U%19&HmEgY=x&(}cJ zpsS3jiH=B(yk9DvRVDz#&jQdp8gPs2H^8zqtxjb%o9jTi*Y7d`+QKpcEiCxk+vHuN zDRyhx&vWR>A1GKY%Gl5Uz%n~Rftz`#*DaVZWq(!o5|0y1QzzQ|C_Fv!HC<{R4IRYo z@!o$Rf=l8wLYby5e$lmJJgd}@>J*GWS#p!G<eb|n)SP=6%m}hpTmAbNT+k|j$2TDZ z%*#B7$(tMA=J7Xz8W_v;ldj>8<#{lQeLJ}^a(T?RK{fhzTo1R#EzZvvCfYrIRwZ5H zgo%nbJ+TEE1pfgBGnE=y0T;4S1o5gT{sAAjpl^u8!VfQdPKm-+(XdLZOY@FW-<R8< zQZ`>q1WKS)jd{Z^$6N^rCItCTqw5tbDyvp)s0?zj@b?mK3zar=oQU>)|3SDTT8&@w zonT@kk!5f2C+Kr_bEAVt75wULbwr;)ODidK0M7*|mq^R+UD$ZSd|G-hdiuGg>_e&i ze=;)YLyeYjeqgVbzG^`Bu?}KwkwS$N5m<fN<6NBNl)Q!RXK&QU-gc+y<6z>)pO}x5 zQ(MM7>^251>qk!GRmcv%aRg6I8?Tk@la#mJjha~+O9urj8@2Ex$kBPbceaD=PhfrB z(JLSMevSD49DPCStU(6YR#5)XN16E5cPZobx>dZ=(yG5E=trycyBE-i5@yT#d$5)o zg9Ksjwyh*|mu;s(Xq)nz<I1EF)eL*+#UMB27%BU5rW<tQh7y)Yp7>K_lrdpWzwPx0 zL|;<16>I7X=6@K{QGEz5;F6I#wi}k=yw67FV?^wJ58mG&4CgVKnAxq~#yBHsFYP*b z_e}gLb=+)1iJAA7Y%5H=jb|M<%ya%R5-p-d%Afc!&usW8j>*SyPuU|7Vw!=~$Osur zVv5;B>x2T7054dA9@d1kRg)u%<6tzwM)LdC&^Cu`rm-(gt`)aFUljU;ArOOX<HZ;4 zIRl}<`^W+Ia!HjzgkIvI*0ld65HQD~y`$1l4t}`lj^L({KJd7MlGmx99}lMs9WS1= z7v15*HAVie=wh<)`@>0?N#Se$Zb~t@k!+kCG5p_BhV`$M9UMi%{rMHEHoPBC_X+cv z=K~R&Zwd-Q8w?;hRA)hiB4OscKp97Nodq`@U!zA5>sda>G$>Txo*tu0E_V>8GR#H0 z=?wrlV6&``cQ8~Bq3;sXOWaNX+Qe>9dqpz}o7EegTRavF8gMlYHU3UWb1~&NFB;2< zM*`RPY+v`nIN%}!M&ImouF~SNT)##6mU_MOvAdkX)yW=O%a~6OGz&rqNz@}4)s zA&pAm{h#vb->!37?;@;Z)7~TniebVWs1VV<W!rYVMFb-<iRu5!F68m;2>QTmRCRPW zA5pGc3)o@|G1x9Bi*1MbZjkV_)?!<vYZ(bVjW+VHMz!vZF-%%EsXe(!o&C#IBL@#H zm+~8gV`gz{6xsEl5ADi#T#9_CE7LvAtkVvSQ6D~=70MHhlGIerzLBf8k63NIOR6Sf z;!WVE&C^HDYzFz<$MNA%#PUS4kq#Qi{%m(w_ooub+u1du@|`_oP;*a(oNrxSy#0ma zt1$%EJi~f@6TuFGF0WxoNZf#>J_z|2S&xET2-6VDrG?Knk~~t)mYQ)JmTfdt;R}-c z_yCKl1qa7@Zj~Af)O}AgWUub9k>gMLq6YKl(*)ehQ^w1ME<v3Q417kUY~sM?c1fIw zwn-G?1&^J^T&fs!cA=4oVme#)V)PDQDt(yKTDyvaAE7uv1DV}gL)7L*MEKXMnQL@S zrhMwDmpn5`Fw-w%^bl=qO>QhR+X(l&C8y1YyUCvf<0`q-WikGr@QlKlruxXX^^=)0 zp}(WB|JhICm_^z{Eg=wfWBy4GphC8ZLvcBp)oSNEqn~{q^?YZl1i!#c9ty1D(+4<Y zWM~pdO_B+Ce91_C@w!0HdP38t-7cv3wrV?@2c<E>jj@OTf@kS!lbD@+@&ZhBEL;&T z_pr^m9f-MePtZZ9I^Lh)*@|mqj|82xQAdtji55;+FGpi2G_&ER927iL*d0bWo>Hew zGP7eK%cW}eHD-=oYFABB^o~{!%J2|OM3Evrzd?A(Hs5D|XHq`>sPt|_X4WLG7Y;=_ zZdPBm-4_PG3|Ze&-6iIcu<K0nIig=9cKU4z$t<K4uGc;u*GDO>(1DMqR({?c$`O|8 zJwKy}spz)^B(cXe*04k@Oe21QE?fLGGld$_z<e3*eYdfAzV|W0)ey}+dNZH0<6KXe zTxsRE^zYio#GU(YBAT7Ba~!IUQTK5NtOB^AbgXO_#+CCYjfOgI){C}Q&m#uk0dZ*v zYAk-2b_dmg>ZE=aw{BM6b7!SZ9r11))&>wxj&0sW;#FH=qePqE^AZ$6WR4DIyvsiZ zBp)J{g2Fa8nRQduL{WC3j-qsHeY52J3HxrBbzpLZROocyxC|%UY_v4l^ZTF#XkNLv z_ilF63_vVN-G${FO_5(DaC6DpyZ)wS9VJVOYzy3`WzJnb&!#n8iC0KhKM(ME3<t}i z74*K;ccA&3yLkqYrmS@YQ_6m5e<J?L<MzMZB-#In?T_#+2Dmo=#CE(ZFdC@C{Zj1Z z<z=U{j;DH-$2$naOg?lX+xWs0-L+QA^n3v8Q)-E)8x=$9kpQP~OZqFxMycTSum7wA zzes<WlRk_?Pc)y<XhtOnpwj8lEH(rWxy81$N9UXFim|UDiXR&>SE7M9>^=;%uL%n4 zR*_~TeyP_2_EhWy{h^`0hFCic<xTOR`5`bjdv@Xs1B5Zkwp>itFWZd)mQgl3eC~Zk zm9TW}&s0%9ex|ToVl`^clR=x+`td!`08NyHu<!(dP`Ny158*)%wq*pGu_h*BgG1Du za9i+Pk3-d;lg{ZCP;f@X#K16Bg`j-S7@t<pG~7x(#Y(W9$z86Eb?O19sU@|@PGS9c zqMztj+jiljjMWSQu8Tk=iC{~6$7avL2!SE|p3-$3mNcGDdY%BSgb|;IhvMe9<)qLo zppefR_2JVpqnhr=9!=ph%CYJ-+*fTQTx<|hf*{}Y&=G9g3|+NKctF0)`{i4cgSS=@ zORq6(Jc%4fQm;=hWYQX%7Ke|u(w{D6f1NJ^5orFr%ekRhdkTGhnvcm5c6x(DXSi!I zIx%zkNK6({(dzK*z~{abkS^#YULN4^bjL)U>tMqe#LOkP@P20SC$8#G8V*=uNumx2 z3RMZv_V@geB9CnKmfq`^1d+x52!49z!Rd=Ahy}L6pqOi!Bh!}xIG2R7TOcamgi|WU zM(_8+5pT7|?$Dq8?az6H?~xr$6yjby^#ul+Bwq5{F^*bmx7zJ-@F0rFptiE__{Uyo z8ar`n)UmSg)D#j<&T0}<)&?N>R+ZEJ>yN0JL|rLR*<G}G4k+xTe38{-r+yjqvh!g1 zv2e6p5ymunQgaB*ogn9~%eXSxpD%+S65XJI(hbY-gg;$V`5r-4;^#Q6`Kp7j+~A3M ziy{{r&S+~ay2KD#P_&NJCb;d!w_?|NZtg_w4EU8f?Y`7p1~Bh2H)YV`A244AKpX?p zwE;Jd5kMhh{|K5ivN=MJXP8f2LSKOs$g2EiY}tuDzAl0afrlNdvj*Z!uk%?Q+7#lM zkbrZ_(KjK`3A+2YFvj#N2p5vBHcPP5e>ts4QoN{*Gw@JnigK_ai1Tzg&UGlOY1$_w z90Y6Oo%I8@Gi5yB;LH{a=Ul_MqA&6=?6t^}IB3R^J=HE_J?vpLIoQD7xZ%Kn+v{hG zBykw7P|{}!MpJbuBT2aUlsi)J?y%W?@A(-YcovYRyGj-1#t)d%{4o7^w%f-04iYq? zNix=yDNb8lKTld|YDAA)PNDRTlWXk2RCqeILUe!p{6e-GXBJ%^DD?W-e=gxV3f;Ai z0bnHnZI#SE=gl4CQ{tbenBsAxTEMu*sPevKt*eo{8&288UKp1}loW5+#Ic!iJ<aC& zdRhJ=u+y+#v19_>2AnXJPL}h1Y_I<CN*{=X!-~Fd#<<Mfa4sYk@7~QWC}|8aAEY^3 ztBOwL%LR;Gn4<MfEJk`M48=g4aDQi)kJ{&y9Rrx-oUN6W2-TSA9qh}xjd(5umRj$P z`H+L-?~~S{tbAXa42807V1Aa&lxct<(mLT8q=>Qq{}nM12H@$)YVnDDk;VqK=bTc0 z1wF1;0OS3mw2p%MZRVJpi*CU(MENcr9{u~j8ajvjh6p-H#N$cqN;G8Ve9(y^&F1n( z|3l2if!9}rdQriZ-eETex(<1RBh#<%7JA&}6kko&*a6zClennZxe3*1V*{Oel<bjJ zauvkf?Ab1ewn{#T8Nf0&7(@^Y57dMoD8TEl<IKj#^?8>3t>QmOB*L6<HyFgI2`Cov zMGK>7(lgAOePK1+bNhIwl#PSn0vMkC8+@b3r^({6pOpf4`%@WH^=Wf<LgSxA{023$ z>$phrF$aaKQ_-X}&=<Jf|6u2t(TQ3mY?!{IlYg^z960ceun8TpVj5_p7pJ=aI*#W0 z-CyqwG9m%E-ca^IIr*d9N(oG8C2#C4Mgz0UI%%&|9GCFL(@pp}a$B8#2LZ|`&gG^l z8(Y3JC=MoPEwE{kpdT7dmAXtp2-d`)&NVh@fbR#nk!l0uDeu-U@ER^LbaSjsH#)@X zCZCD>*L|QitXo?)QU-R$)m`L4?!l##c;MT4caYFQ5+RhiI*Mo{7$8+a59gEZ{0X`_ z(I1GK?NbD6Zl_Tggn~{3C>zABQ2Eqobo^(E3dX{z?R)PZ=lB{t+hX0^5ocr>duUFR z?+N+nh`$Pr+495r-kyl?CnCH$T&KUQaf^P<fUTH6J$c?IRWX5#zGjQr4G_mOvVlW_ z1||$f%PFng{bEg8KD7e)Zyk+(%9Rd7YOoKDQHpRLj5#Ro?{>OuoqsU0p(o^IrC%w3 zyc4EVjZNW`V*$<axZqp>S_%rR@932|T>8^0A^Q<jm?_n&Us;)DCa&S{eTc3e5{e2B z?>p!h2Onw@zn66Ifc4p9^hKGoK=p4g+jcO14C#>euz%BPPMivnUWS*01iZ%wHSvl< zUX2Qt@HU@0M*wN#sbt4t5KM5)A@vK5YND7QZxJVoy;t7qi~O}S8-MSeZkBrD6pkx1 z7qC)hF4z?N6*Gcg-j%R{HmH)5^}G__?rmq3@g9yj<MYl8!oj3tH~yoP`82M47K(np zlR;H~-Mwsvx&D|Lw*djv6MJBf`LyIJzhAC9fk$}8tl?vPz6csp$8a{46yr}BEA~eu z%dNHEK-))X*vCxFby~{=`6@CVgVo1zhMz`D3eAVLaX9#8RBasO&3y#C9+O_!>@rGM z9X_xX*9CKph=c1tzm-lkLB{jx)PwIQG)4knbH^xDk_W#H7+N8rY55onziV`q=3Pl= z|J9Y#Z`Q4<ITyJScRl4hIwJf+Pq8ER47W=q@5uN8>F{au>Fc9aIMl+q@I{UAbqpfg z?eBrkI~SbkjH=~sqqMt>7~zN4c87kvd4zMoH>X%)0u?E(9#pdf++4OE@m<5FeD_Q_ z4XHnDY7zURHvc=K!Y6%LIsb^6J8#PHKO?FSq~w5CTnc^KuGfc4BR(m>)AuU{CFe>L z%4pOSTkzx}mSh|ZTYBK+$V<oDD7H292=@QkmNu~er=m=UMnucRgiI$`bj291sF!$i zL}kFl*X?(DRPS$SCKd?gxe{F8s-%p8O<&Ip4uW7nMBBc~5?9qEfHG<On)FtErGOT* zN_m|ijX{_h3)t%bAZkH{?azO`-8YnjOw?Ny*L4ZL{fNUjFi>dVthL>UrYkbA4}Sv2 zy9U=bw}5ofwt84Bjj^`Mor2;JTLDe<DJ4FgA3Akcwd5CBr;YcBg(dbx`qGX=0Q(YI z=2H7EIpEn@8SbP5J_&zfNwsL8ES`fk_V8!=CeM(+3a$=MALZTG4ILzPg&K!tHj4?z zpesq^QpZ-bZ&g*k@&V5&GH+~HD=K@X$@=hPYEEs^K3m8pHR2pYXwj*)Sc6bh>h+NE zqY_zSFxJW!@-BJ(;6%u&?xg>v>}WibFWCC!nMVy*gtki;H$qOYu%}DfJ|37+97`3A z3^;JWCD2paAQ2VZBSRXrlF<*Hr@elbkEf_!$2v}a-0qL0>qRn&Wh97`mXWe9kWbX; zl{jDVf&<Li{CSU<eXfCcL5w}$E3j4~0S2MLqGhsrHSd9*+{)pS3%<E9Jlpvkwx0G9 za5s>F^mQsBRpa1G#{0eN?y@6QN1Wz1XEtY}qkF4fIA*oQGh5<L%Tlk&=Z^ET!AO9! z1x?i$!G7|kuf@su*sOS`o_y(D;4?3vpgAs20;+!fAzc~@(YEiofMk&!3&5F)+Jz4^ z*G<hrrX&WM3oVoMsF=LpkqWZFN9k1#Mb@;HX3f<Ykyt#7Q`yiYC@`B!Nw_lO3bFc? z{RvKR=I&y6tLWskz92rHh;|5k{(@wzd}7s)KcPC9trfz-piP9eLc<8)#Gmkn<FE1U ze*f%!tM(^zgJ9w}z#eK-f@5e<pf8%cV0u<+AvyI2@U)2aH%GS1dcB22hvk|9DXRT^ zeXV1ajr-gDA7#bWmO)4EL^nGwa$Crp+_mFYOZZtq<_xa_ZjZ~-UX`Ru;c@qF1b-rH z{@(wA#1q=}3c^7f(5`=e*<HpbC8IQb*}>v*;J^HlxIhT~0BxL;CEQ5ez;dF@mL+6F zgQX7ck9hVz9}*2Coz})GWFn8Y&43zy#MGE`pya1)H4=9v63y8?>^>f7QHR*IlRjHq zZ3{f~a)`Eqxlj2V^+HB8=p@H<)W$J{P;tIlCJnM=bV=Pw2wD%Td=YuQTyNY@67_wK zZnE0_9k*_(#4L6Tc10bMZu=Qb<tm5nanqyU$Fx8jPSJgOKSO3wD<FUb-X_eyk|B}` zEc|Sq3>kn5V|zvGQu^5}u#9I~g^$;Uv`7u*A?EqD@GFyh@wd3qo_Zr>0S*fpSK%Hn zZm;ldW`NFjT{zj-3lO7=_1j2{T)!bqZm}e0CLaZGukU8B{=as~8x!KRzQJ>7(JVgn zf5#)Whsj3Q^<vT{rF^&ck=?iyt)@XrI059w_f#bx?$t#8Bc9Eyerd-3cY$A|2)z(P zt&KwrChHc(S56u@<ao#a=+{iAGwIRj*-}--K2c8rtU4v(Scvu%9*%~4QHP<?i3FyG zCXCmOir<{WpP&u3K8wMh5btNitp2`O6#&^chYsSQWeN<4-B`1Y-7swtHv|dvopKFP zm*7u)Sw3=#j#(nXps!gOXxtV91|ZZb8=z$Y0)KJ)a2oO_59Zc)Uw=>Bs5ib6;-L{- zJe?BuyOdG(nO$Lb)-gUju~kN>G8AY3B>v0sBFdqTLzMJ|!Z7XX$u6*hJvctN!)0E& zco`*b*BnM)u4$o*r1Uu^!l75)mN)yT6xQI!c9sO>J}S+>wB;p)(kR5*%q97CY+PC6 z+uwLB2@Jx-$mOffofY+Nr2(KK+NuHW#tC*-=-|iMq^+LCO~8J2v#$1J5qLi5?OB%I zdOdRENp!)CT2a9Sy0yUsBa(0oi~Tw%8|M79!`nJBS9C@o?S5Wj?NYE!gh0LMx?kk4 zL<>irWPnUi_glJUJg#l<L!Dp6QfRJ!4tsW*8$M0Kejx$yK!my?0ocK!+&}IArP*rM zppIlp(wA12)fmtZXOfI4d}Iopt($i<zi6`76ZJ%QD-Yl$*n0h=F@nz+aj%^-qwZ=! z?Do&6DB?0NhSd{xS`083isXXhL<_<#SrLX<R(=3U)LE!|?4T0kO)HW+)KB0zGE194 zVPq@?x{OB{$eq%Cs{4P@Qc0&ZPlL7tQ4V$tK*v0TYJdsSF2Dpm9H(L7l`oQJfR{h3 zuuvJ!7VkY*kHelu2ErZtPx~S8nZO5+LY1$=tj2Jhl#PhrrXK_ZzPmHjmH8<`3Bmql zq?PzcKW)T+yx;v)7A;#qwt|ezb(Dz2#*l@*{?4iO_#-?QQW7Yd8G7PhMyr6H6x5OS zH2yAsYhkVJ)>~t=_g%?-uc5<^FwwCfi}D^I@0IxE^mMt3yyf+Z57<@R%3yDR*}~rz z5CLq+6`uw1?!}j5Y<<>OF^Mx7edrZFM27zHl^h5W(t=Uk0Sn_0%mAtqjUa(*Zv6DN zMM3I4%x`#B`yRDWhZ2`^kVuV*y6h+^37=nF<2a8ZqnnzhjuBfM&8)PgVhv{CfX~VV zH?J<MtrGk<Vf|~G96x6%xqcwU$=)84#RUz!;U2`=x*NmV8rj@+VYNv*i>f>aoByRu zIqt*x21N?XTCivIs|vSh$TLnR`ZuRA#$d;S_%jjJ*pZ@S1FwbCz#EeH21boK?Mxy= z(|EQs9G;K}ctD1L?2&yE!QIkm!hgqxi*e75^%n@b<=Mx$`VZZj5bW$Q8UW83fykQg zWVoNH(V3P-9EIxzG$z-`D78Q0N#m@m6<;5)uQsdlFhGPdzn)jqoc}I|@fA{DYP-G* z+-D?_LhlVNhBuUE=Wx>%01br%W@8C|T;3J?^+29pwG;Rg5Yy;bFhA6U%<r52*gu1s zzyg~PId}VSWQ^L?Hjsmd@n@8Sv$;HDP&5=Yp-S0QsVE%*J_?}CXx!ovE}M)21rhv> zExU%ZzHtgRdy`ii#k`jnLYkzmCX%%?<bqkOjEK1r<aX&VYgVpd?GZStWwqTh$^FWf za*r%R9O1;ocqu<ET;flyraPSiQnnKYv6dhQp!fED(@@pVMf=s@Rrw?y_yoi1fE1$i z%iwzNjCOiz2GJI*FEtDpL%%WsH=lZn3mSwa0+#7jMvxQ-8Y!4?rl&MQ9nz+5B6>^m z%3=$xTI})yzgTKPXSZ0uZeAzXbLCG*&qges))Qn2Xvew|svw%w*(rVL{W^zDU<9H8 zLO&rGnhFr&aZs9~m=7c7!nh^3bC~q~9sQ%<n&dT)+WXm8apRX}lP8zY+qW2?PnhiX z#~PZK(yevZLmpYvk@cQos>PbTI8?vex|t6*B{G3;)#nqi>GmJHB6~@?D+dBkUuY3+ zBStTAC!%~d#9-Lx0y&`eZGh>=n`7wYK1hrP*~R~Un={BSG`bY+4|5Nk^~;61Pn8eN zb66HhuxolS#r}1iV2?f)75|FvVMbz|dfW+Fr}wf?I<0y)6{DTOGe`>F3k$$$9>`}P z#Mdqp^G$gXFsunRs7pgjwDJ1-hnv3-Payz*0{8<sN_2U#w=RB@&)hZo{QhvKgar0Q zvbYz`#TEa17ic(*nT|e&Yv>P)r)7_F`E;%=BFDBDC~GIUhOfMDMeZ`zYSWoc9mr34 zyyh46V3dPQW~gg#r>vEJPe9dp`viH8BzFfy=hAdW^!PdTZIjZ*4qLdqhA<^mO{<rn z@m2r2?ZqlR?>CLvI5Qpxf7>S*Cmu7Oh1qaPw#%3k1-G0-xbwF@MCjBMM{&bf>b$wb zd1-v~SvKYX4w=JyLO}(gbzR+0ba2Aw`F3gO$)f_%C3ia`sWNjcXGA)fM~$})SR^-{ zP*o+8V(8eIhW_uJ((%AobY7e<%23aB@1H!p_e)zn-!u%N&7-m?IebOd0)elpD;V#K zn9g(BSQk%CD{anz%QU+m==fs}*oBe=(K~mKx&|8^e_szjBF34_8%p=f(4Mci$4bUr z_&V>SfcApuzt+0_5xm-)<+yBd6U8bVV}N*hReMVyrW^4HbMO>fThFLah+f(1{dKaP z@#fMhyXBmv$M3%?7h^eJ;${Z1q)Ygz^0fv9Ex*qXBF}eewCA4TF-5-ahobw(vHDsM z#!|rIPge!{pbL9kQiFHZV%CLC5?`p@wOhPJEmY7;O$Y31;<VeNO*VG&tI_Ta0n}HV z;`oF;ySCqI8ad0q$<3OrBXyGw@jBk6qNG-fp=Gq^QA6L>Fas`*)o96Wx*G4KF?bg0 zj}9Z($1enJmi<Cb_#MBl!Qg{z!Wh3`l<zf=SxNuNLlimusA}~3f5)4bntw?}esdkB zhXwF&B3_*kylvBp#U$SDC(D`%I!{md*KGgELoD(<^uJvf=X$SM!hU6bewT1*sQ*QV z_`jz^T)s=2aoDU)NRRV1`*wq0z+}>ZC*!YKs#73Wo}X6k8XwSLLo2NyPt;DZD{=}D z6~OP;q_0T00}{f7ewr}08uWZIAT_WymYLu;YWVZ;r|#JRO))J3g}$pzL*lDW&Je0@ zfER)m7A?UvRWQsw$Vxkch(g+a-3-N?0eT7cYFP=#Abx_Oz6#;EzE9C4Ytj_}<<#e< z*hzuS2<ub>i=WQPZhcouf)r-UQg{B~@N%QpqcyivwfehxXmNF$3ho7R4+jICHTNvu z@!&X*PZq42M>K2I)W(1LYbT>B8nhXCHI6S-!yvotn44$yO*Ps|x8(f<z7CM}L+u(- zHhPT8s-9Drjjeu0)H>OF1q|@&rE4~<n-1T25aCo3AjS`*y`7oCl5!!QmH#3BTF?(I z@IyG7WVl;XiJjnNf;8d1&&3I--0bL!PI0ZlV})X#uFjSn2+YPdv5jC{x)o_LYgG&I z8l-<fx7IhIH98uoSI*bbvsvGazs%5fp6QcOW$k1+%PQJAx8f$H6u8e)>h;skEqY<Q zfDcVyI*Z*-&Q}gx2M{VUFY*E86M<Tvbh^JcCT+6iTC+8OL_GriZLX02yQG1OXQEoV ztPmA9h$M?#e3W58D_PAttL2gkfaAn9?Gy_K$|Iib{s79~6B10gB4gSEk@tsbWlbF} z{hdls?0w&Ju8$}e2jICQD#JIejX=l{NHm)Se_~|8ju$juTvE3T{Hjrt-LNwj+h-B1 z?V26~0S7e4ZvoG3BpMgZ!IA+2N=XtwYGmMQitu-hw?+IewWjuLascLN8>c^5M$Ixg z1iUs2K9nr!KE~{$1253d_;!Bzy{!x6`Jh)eZu&7xY>5SIWCkaMRVe#`3H=fpgxbu~ zhjQ^Jno_lnX^eOpE8$GqA!XVEqHQ)xbuMs2IMzr^=w>USW5`RR?z{duGWMbP&=9C> zTgy*{uLy>R0b*mWm&<5D)b@|#4{jjbFYkI`R>-IkZT^%b5P1Zb#)w=!kYKm&^T(29 zd^BqY(P5y*iQ|XTU5jdD=I_sw4)PDr#!wTYD<AftF%75i!lQ*Z?)T}po}0HDM3i0{ z?+iZreuCC}hDHz$bstTxde!+Ye#bBIk_P}!({}RCpyfu)vycWpdW#wMT>F>0TH|!{ zDI4=#LVtSF?UaQzC>+vM!ue9b*>yPohQ2%aD!YX%gbSJ;xY{}tl8zBfZtc0BhF7PA zq}MEr`wRNVy!+DLmy;%`kCSddM&;-6m<Ek4y3=P{ua-}7W$f72JL(ndMc(v4-{Fp- zbgd;n^{+qo#%SklMPe?Id4`ToC2<bM)`@e!A9P==gjRpm?y(<>6%OXd+JOeY#zn## zbMBO|=yrro6B-G+qM-NImLp^E+ggKnFUE0LtDnIZt*4z9b>wmk$md{pJd5y$@mqS_ zxW+2MK-KR;VD@k4ydOKp^*-TMtky9E#@mSa+pOkUVwMTtG8m2$Z1truG~^xscyzq_ zi%P3${`2Dq23<8|_^hbN&s9#T=U^CwJcCvxcAfWM3+DX|{qqk<Y{8OI4g3GZ79_$) zV6FK2`Z~e@ZY0(}Wc_(iQ2weq4;tJFZk#lbFq8WS0(^lISb~$P!4ig-XMKuqTxRTn z9QXc*x&M0r#0I0~=DrOY^8-euMWFuuVUkZH4mss!JU-)8FFSj|@coT|UEV;+0mSa1 z4%2rIJ~)cTM6<V)sx$-`xG<(&m$V}ntm^}^lS^J9&r-A$V43t~$N(4)B=IfBwr`=- z7(Sj#&ggy7HsI|VC<TEC%GTVa>Ry?QLl)oD7nL=)F*!)OW|aYn3K>lWL(-W&veePm zpNyLbPMM99OL&O0cl<rll~)r^3VdV`ZQEMCP59{n;m=_d`?3~`P!~;;Z=dDP%3n>z ze#XW31h_W{BTdkl+tkVhFAa1{DyY5Fsb?aFfBI`h=%m|XJMye{$H{K-*-9GD?@)pa z{b`$}(th5+5KkQzZ-4pd4txSDWsg@cWq<sLPv-Hq6BU<UMT9Ex?O<G}Knoxki}j^? z@Go5kDjj6yI0kVcTfxoYsu2~nu~=jQWg{kZ>U>u23T@Y@EBpK6HP6f~4VcG8RU6ix zaF`e6qVWv9yVKV<2QB_q`i^F_(SRHiEXnTEMxJZUIpnQ!l)?sfd9Jef!q(TG-`WP1 zz1a{_Cp&P_E`O8v{b^1!|EA>#r9Ewqf$0@9YmFBEjnJ`Bekm$xRG{-fb?@V}L|yC< z+(yRbDZzfG#6~3*pl73$4{%@tDR3N66D#wEH_Sb_O<l~&`QCa2m)a^}VZlVrlZ6l) zYJB1`x@G9Z+1n~%ky&hJ$U5=44HMe|pQ&qkJ#f#HzxwQ2wC{P+h_MmGr}v{*$kZO0 z%k_ry#q_`*%|InSe<Vm6s~`5+=NF;4CqLxa>TU=kTepqhU`DRIqy4pd;s*&$GMTK^ z587y+<Q4W4wZ7mBZz%2-?&@LZ*<-W4j$Znm*diW6#Afo{LYZ25oVAaPXQ@m8J%7m! zdKV7-iE#Ui$|zWZZfPfmMIEj~m+ylZih1SCV;TrK3@8uqe5sv18fvgqMq@Pf0(e4E zHd@%&lifH%KKP-lOyhLKeJa1f!><5XdU|1@C<3EVqPUI4dpSDx)*h$0dp`=spkJ(| ziI6M{BROh_c~A9-7|FSNdzkmEiTtPXesa~X@V@<?&w}qmpX!??(MVb-Z?z*aL}U@r z@Di{|Kpl|it{dft`i_A-mNn$Hjr)_eZknHLUMDLqeDE=sOBwK%lZ)9|M>3g=OMh|M z!?Pe+q{lQo<b8IkfH8|+4qGe-cyi)JCeKD{OQ^0mU%U&Mm#mc}c))KT_79i}tQq<p z?jPHEqKrNcpiV8rCPJ^b`Jg7<di2D%Ge=;)!Z@xVs_ZBma2iReL@mEx%3rp*stRsX zDph6cPPT)S#}m@;w_Elh?h(qh;%oD&Tuu5XpS>%2a2JvBYx%YT0=8T)c}Q%m4pI&Y zKUXm$feKCRD-HADdClC=TCNUf9#gv#Vi}qDR*O_5y9bVWna0T1yheSH&-a%ANCw!W z>!nx71Q+K_qJR^j#d>A>hoRW7zdt*u*%)~r2pi!IM|JPdv{_3tvSBc9B%hZQJ<Rm2 z<DRx|V!x(6>`SCv-G6I}wko-g>;USxFIMQ<6l)3c0BqdE3lzDP_UOS&fraHUX>Md1 z>*~M4OOKf48KkEPdvRn165>Xt|F`uN6}tGc_T^&{%ZTZ}lu8U?bo=&6+Ag1)CFI48 zG<NS`@4lII89B0O1LthxyC_Ds+%J&!b^O4uhW@{Se5D5@YS5<r!B87g2;qY0;5Y&1 zsWt8ix7u%L7N@zq$UDGkPY<Vp#4W%xzDWGk7R(=Bpu+YG#aa#qG$GbXP1i!rFHA66 z7|<ph7&+^580RerO;|lrKPX;?X`CN-Dn%Y}X@gGOL{0-_WMKUu{m}mjNZtqLUaDU$ z%yXTlkn{KR|5=wQ;s!UE%9hdt3wFWmghW%qE1H6jIs|q|)kn|6t_RiF&;e(ra04aF z??YfyKBI!txOIt_V!Yx!`=7W2C<cO5oxd9#4;H?>v=|GGqXyK#SdhpYfkae72Mfkd z0CIc<=(g`I-#UijBCz%RAWLMz3EG(x)Dk5PT<+g$00&&)5IWvDS_>N(PiwYpJ}!B? z8rI0(ECETaSrI>0Np#XC+CtXD3XU_xKNYVv(K`ciabL7g*>>7FK)BjG?8OA|X+zL3 z^UBnra&&#=5amGH7$)1p91@$;_$FSJU-!eMiiudZwc)E+wbLu^UD^mEu=6y>&;pw_ zk07OKMFB%3X$B;ybq-UtOAYFXH!Clx8gSxvQerD%NEo8ZSe}dFHLxKt8uI%9?TJ9y zXjWRK_I+_{E5h3#GXB-$?k^Js^4$F<N6Gu(%?d(Nv%{pcENgbM24vzx&MdXbn+<7i z0jXGb3DxtF00|0!+q~@pZbeLn_6Lwes%ls^zJs>w318qH2Fk>X0>GtzNOWsJc{TV_ zt>zMR{2+UPBkPXp-u8{<AYYx1gQECNHY;V$=Pvx@INhDO)$d)e@fVo=K+9(7VcYQM z5zc9TiVfDv#~i4M7<WH24+&C^9<D5Yya+8Rz2m;ip2nHOD#egxm|&jGM^B|*`#G)8 zu+a1bLH(+<!E>L=G*<u%kR=s*RqjTcnHzu6hw?fQA^nAr(C^}4-osAsLJNR1hjnq9 zwpavE9<CNv6`c?6Myv;{S2pC9uyMxa-Y%C)APDWb_m`<LghN%&*WN+q#hq=YO~}FB z>iCjN!ywOijY)DqP~pcjbAg$&reIq**XobUVdH^=j<V<0P~_dwZ2Utdavlj6C_VUB zk9htsA2nNw8hWA*zmIk|j>%)rYx=$UvliphUut$$4VhhG_T<fj1gy9VJDTci^lgVB z0^yOD-ZJvM?Qqud{a{XfvKq=x@<W@6R17+=bTC3g(b-R`Wr&Z4s%%_Y7#v9q2&raR z=!w-`y<n`<hrz9-K1Jv!5;d!~Lu`gKWE*tqSe9`VC{Sky_o1v~N$Uw-CblHs2M(so z_eRbQd%=MpNhw>lDP`dMu6<k<kR$e==h0+8xG6}(@SP<a2(33Ystc{h=NUyD1Rc5E zGifZ1U?ZMJVzTg!eG=%rfu>;|!jRf0<Mlu^(uGgbo!LrH%?DERFtX@X84XR9g&NPC zxHw*~iT4#&z9bzpZBG+VEx7k_73pYb%`j6TZuiz_a9U@yJ7e#bCXU9eqg4*5*NpdK z+(W_*|HcVcB{%Os5Q~JaheH6<!NKc!R4Q_AF2F%%Oj9}31us8tD2y2j7pwCU(xX+k z??AW{TgqLlk<Z$LF5!=0SYtOQHtk=iK3UDiX%Q(c-$+HixZ}!7)F;oosLo3$S4p`> z5bghD`(EDvf9$&c2()*>@%az^QcTm{f5q~JL8#LV9(a(<qHh>morM9`^AqXq_|5Y2 zOs+-@o?L?h@yGRC*H-<0%p5k$eLL5mq^ODgugYFjTtVV~CBefk&}>Xj$!FPn<A1TB z`Th9Vvw=uZ?hv)AS{I#`Nw>+@&jY$MCEm=Ebc#6`FyREN_)Qls>IK=X^CL8%Z3Y1p z_$UL%X%d$f0=bD<3_&~urAGtVfCEu9E{m1~1hk}oec8t!KpPA5S;t4?$ER2NPsj5h z`I9X{0`n6UluE;XR11pTfH3tT_qu;LPXzF&^xgA&?58hDnnr$c?7dEwsJ(@T#(0we z^EtnFzX)I%p4`Qs^NC>G390jlIsRJ<3v(S-`u=RYi16bPcAOU_(tyLU9c>DaOgel5 z12d1vCLWKE|78L6BPhq32sm|_j!GsymDz|IuD%;<DiB`g<|%9&F+wqp3jCBF5S(PQ z80kgRcFRz;VC;px^z&wfGGJW)`^d4Fn17xC6Zp8(bgV@Xa;#AAws$iKv3BUSv%%Hs z^=;v_<P#b4iFl3irajfx`X;UO{_Ofd5{|x5QB7q%pOvk58v?yMaVb0ZR+6rT%k|c% zpbsHFH4kUJnsrG$b!;J?il>f}YfO}boNJRUXVjVJW<}iE{pwbbUn%w$?Yu*a`(~*a zXgoI2$`}+%-?d7LwUYE~2f9C3m2<sQ=UF2&X142gaV^zX=(f8{V;kv5v1M-Ei;^hd z6h~C7^S5v)RttS_c~3{(F=-Eukvl`8dr$`ur_Wjp{@fSid~DNKDb^Aut69`+1eh0b zVdQN+h?s^!zH$lH^z24GnrNbiTW7((w{gPxH6-i9{l8c8VGmyol%GXtN{B+BZuhfH z)E@&3=<rJHMZ;}d8&tqRdHpJq<Vzyan5C3NSm-Bw{E3_Qbce2fa6MZ#rpihfjGGjS zxiC$82qm8%Jm)ljJhh48VvXsKI?a3U*F1pl^1$!B!|^N)_ceV%-oex;MhEskO#^`@ z)zMFl6WE9SMS7jEsB{?EYm?vn^VjQ_DdorwbTrwESbRHgo@6)qZN<{HB7rtC_Eu|; zjpO5dhtF@T+K23xIWJ+bX8vRYZ~2Q7NHpJROVV+WrS?#485~fMKh)NKe}4gAA##f} z`Kh(WnIxKLI&?YT#iyCM0S2Pz>8cZZgV00|?|)W#zb|1*$Cp$$2B?)STlM^EM-OrO z(32R2+b!GcQ?PRP>{hWe7Tku_FOWegl_Fb}d9KR`#-F_J6;QJ!RzPnRsMd?PO$)E% zKH)>-K6h+#yl($qO;T03-5VF87B2JYCH(9xig%=cqi|I*)|~=9^C6ri0qy~%HYHp+ z+g!7-7AhyB`X|tyW!+IqVq6=2BdaRUrk#30!H+019MOjEhJ*Qt|HevNoBqQn!AEyF z#PghK^#R<V5%<R{E~E*WTxSosB1;rYeRW4;IlA&Z-=Nuvy-XwA!H<ku?w41I6zY^O zQN1w^9o$LYi&L<giY8L#w*NWCK`{57``&nA8|AENW)Kn-#4EIc5B>BIiC2B5H)NlL z#TG{sc`I5?8C0#kB^Ae7AjF{s|NGG<^}ik%;^+DBZ`7x-+529F!k&GdbXHU_zV)%5 zSTm!GV6m95{Y_fF*^9RaJa?_r?y0Wz&OS+B_9tq4m7+k>d4Q=fiCJV;6)RX@eb%^v zWH$yL8vN!1WdwqkNi&U6Jylg_Svq?L7=LA$JuZwgk&6G!MskX!;raoPQCF2pw!<*O z*HbVj$}R-QNpqho;i7U?Q1iY51^$jh8AY~;BPn4`3Qm7*;fYE33V=1*(sm=2juv&o z5m!yV$Pd))kfp>c{zlQ^{8ZH(*T}V(dt&$GUA7}+QW(3b!kT@1HBQcL4cU1-$a~{u z8C$p`%}F1tPTcEwGGFFf$)=x@aAI9uyBOVe94bp+8Ssv$U2|9&ky(ue!Dn%tl&>)s zyJ7)L?sS?<wjw@(R@ez&KBpMy1_<D4{3vvbOr&#~L++x5N|b;c&Y=u`UoXWj;-~BS zGoFEC!;M8WrlTp^N)-am1M?i|D}DJaPBV&gjzmO18KIaPrOa0qZL9bAnGhRNjmm{; zmitl4Njc>}=Lcle1p`C~l8}>6YOCdLs|SCSE7CW_k-NG@fBaJ7jLqJ4Tip<NDtqV- z;jT6%0TU2h#5mnozaqhKGM<D81BB?Q8ixL+6obpSO_+!z^_-+u=8$!Jccl%Sup;k@ zd_zB;hG8p)B;PLEG@SAiTrCJVjC={Xw}N`S*mB)(5;uUj_UKA{&!(8<mAbaHp?)Vs zzt@{8!Rm$HNDd2EHb5fn0-iU2LIDS5oclR8`YtX0KepaFD6X#A9tHvr5E2OP?ry=| zHIM`!G`RcV9tdv1Ex227m*50<x53@reuw0F-+RCMZq@v8ike~O?CxH@di6fLMfM(q zu7+Q=M^rA}KP%N<FIVoiz#||^fN1<ed}cy0<BDg6o6{jC>w7BJpH9$QvdCUbxBnxn z#G6S;A&f_x@?$8^igV%qhePYO>-L}---2`1<^%k2Qc~T>v1b(@-?QrvsU=G9>sq1J z@FDjwIqUno8a>}2+JWwl$9u?LlNLi)8mFd@DyiYXfDk)4^|#sl#e@(&)42-lTat0O zx~<(Bi;~->V|ToLuETK(3nZPkgRtB)_%dGX^t+R8h;?lfi=o=CMS1+z9lLxBd1{GO z7+sn5EHETUP3U>q8e*P<WL-lu!}9h{KW4%yw`*_Obu_)r9GdW!O~LTC4`;LjYqYk> zB@G=cLA>v$x$uc=7jMKp-(}4d)mN)700M?I1bXX595P?(ouXIRE?2ITB&RS!OFs`; z#icV=q&z*{wss+|G;042fijx*#~5z`SrlEiMsO&4<zH1FMx8F+trUmXRQ2#gKHPFy z*^^p7%v`VXpH^nJIe_I7u&u~CbrQQ+Pb83bj$&4?!4UX1H*tNEHX3HSSs2G_dt7^6 z$L+fE<{tCX*|2iTgsnTKGWca`fw&iSEz0^0xw;ontNg;e5wQ`MPG}Mpi{i48<16 z!C+g9SHHJ)4ZTL@wF|hH#->Sx6r1D+O|51a^^4@5O4DOqCa2)JGHv(RHsTnC)!dPa zP^?g+-MG$Vx5>Q6{j5*ZQ#Mw*($hhq>C4v?3X`1va9?hh1l`SP1*zi)j~iCspR>;} zLn<CvNvx|<x?yfy7tc>Z&)7dcKkh2Ne-v2nF|5#OJvB&J-wPda!~m-_w`wkaS-IYk zD~Nx}5mTDE8~2W@&@^mK+ZJtoaDPMr-hGiu*>fM9hTf!G*8ua(IK5<Jy%308A&wY1 z^%WCMb)&Z23x|E6yUPG71*Ed#vT!Uh>807cq0Zc43dXE438oHsjsvq<$Ofy6@oA4h z@#=s2JF@5#d4_k2cq;{m`KvFzz~G(r2%o2eAje-V8n8Rmy@gPOh(bafKHc43474%s zPXr-j(UvZMr$)2z14E2ZP<mPCWNU(a%s<S}&b|(>D(aYZakI_qSoT8Qo|-!9Xi&=1 z$3TI-v8$+Drt_0*LU6{zb}K?a+GBHmUspuVLA6y8a)h_SAw)HNQb?2nk}K@X&#HVa zXl|8Cr7vfrISa=X#b}*RKmAxvTgJ6TcTj*(lG4akr>&LZuk?^xLSL+a6D_;0rKnQ3 zj2BoL`9W85)_oPHy0wu+P0rQ`y<xYem2`Ui?U{}II!y7XAi=_Q7w;qXulhzVEoZpa zYi~iz<SF=#y-nd~tKyR)&%C^dWzf;Py2XXf?$?))jHB^OiP)~L);Z*W=pNe{mo>Vw zy^#P>yA=VvlqW471_K_3eKCyJRh+UEJ?<JZBh2b=oKmH7i61qNuHpP0d~Y?l=Y=}| zacAt*R(yU-R8YdX|4}z~3|t_zeQ6ZrYk9x1a#tJjb!$b%+w19V&S5l@9CRb!7j5jw za#y64J8y5aa#B33c9|1@7aHkUmrfH%oAr})EXiU^_Ytk2-Hf(_DSpJ!f*wQcz;=rm zLwu9%S)T#{I<(vGEV(FB)||s3X4N_K-I6SO*>BAZ<U(9`Rb~vkdtXz+FCBMFs=C0} z%CO=Yg09stz1J<YcA0YWgF6%z&9!w!>(r-BQaWA4M4q_eLyVb$hjdiwt$K~9F;fyX zcDO_(%9PBHj|aZqK^zw#^7#eM-yoA{7tMy-nB$J3xFxZ#1+&d6i#E@|J0Np2waM?O zIc?6-zE4JWm!D4vELS5Lov~jCSwFBa@jD1!NZ|K=*n2v>z3f@K{_(Z9E7xtdrCw*G ziJAXxoyS%S4YQLMX@A8BnUdK3wo{?w&J>&Cd(V9dT*l(l^bxK1ghJTRjWk?MZ4=W< zT=LYw_(Nzk90_1@loYC96P2%>g7C2}Inejz9eqy<yVGaZFJIZU(@n`VNOO{mttdb= zpk4R$P%RV<{@1sKVLXA^XiM0nTfnSKZnU3Tw%%fAFl54Vmh<2?M~2=mUvvZw;ZQ-l zPHG9Cr$$O)lxE6l;5!p{fSBMLvDFqs5<FjO`l-;2>2E+Hl`s`ZLkZVVzwe&@i_LCs zaivtOBnsu&19#{$@Dt}Yzaf@9wm=PfO|R2>Um5tjQ$)~s)!l+t`lhnKAgSs|IETvk z6<<_xl1*7*!x4=+$}xrrAByWH(pNG!sD&=RfX2?J?SYh0Glq6XXq|HBDUHz6aK$!w zhkP0Ja0FD3{W94_uVeHca9z*O=@08d`vU}Ys@}KR3BfJ`ojJ+!_X7wW5U+c$g~!@y zFs>q>31iCFs`x=sH`&VImQs~TQID$Tg9_tEWCEeW<<A`kDWuC*UMHI<YXpV)2H?e< zHa@jyrILl2pr(`gkDhi#*3B4N&rhLUEjPD)14QjeU&gocLSEgRc|R<7Aw^==tT^}E zFWv=Qld>B?S@9zO2>v_pD_cg`yH1^1Bz%izmJ1m4<wBv?v|4K*Y$G@tL>=*a1q$6! z?gN6JNG5S;_lN6LQLgV@O)0Ghg?`mf3^hXN7iX>-fB#XZv=7V$ONXHn#XCjVEXE94 zVU+E~1?%vdA$&mzXgBV;N=CFq(?6CfP}KK-zPXNE_a=7hQKz33HL={y=@R!?O*4^M zTtz8ru&{KhJCCT9B=ER*t*)E=^4l*oI$nv+=mBDXkAQ6Lj>RKU^@-OaAm@@K!^wh` zOvL{qdHevkKAgI)2_Nr#N)$;&c1!SD{Sn?VXkE)4y~`pP)#{at=ec!o^aIUwzaT?v zlf2d@Ju~ksE^ml-BE@u%s;hK8l8}z8MZVo!i<g{Z+QlS>T}ulA-GM@iG6t*q{>P=g zmB)l_@poR(Qz9Z7y*i-8Q1AOm_pYhy-A`Zmd5v_4T%82GTJOvb1Z~yvDoQ7`d&YrT z6wJEZRhmLhq83urPv=~bt0R#4wlWdz?FE=x{O$mrn|bQ8_NHN>x4cJQHu@5evFbM7 z=U4D{7vebuR*1mi#y4zsnys?U+ZRW;c`J_<B!9;<7Q7o>Q3N6lPJoZn*G`dS8pdtl zmn6TJA>{RFG<rPEaQ7D0O@%bsA_L7S31dH6wo24=<fC(FOJvD_!M}6KfQe)rz^peG z8kl$}&(O(SxQh&^tIxCy!cDfFVBm>U+w0E(FUGY4X1`URckPRj)l@%T)OiIPaDW8_ zZJk1Ai&-PQ&%6ag*s-rIYf40|EyQYqMn+@?H(_H(Gy^s+2qs)L9R!e@AH#w=X%qRM zVNipV!gI@MI&Iz2QsH-^iVo8Y<|781f#X9J*iW-G@gm46s}N*pzwB$?uQg1F5!DR4 zY}GK{td$e{HWox+WK^+@pNw6nhK_jepcxKSv~b9|1orB9Ahmf&i?2OOZPWpeO7p5g z*mRwT0&Z^Nj)fJB8@0RVk=Df^jd#8JJdbtToEc(WUs|X?=Fuk%ceDjB9Bqb*%SXJs zTHxb7S&b=QD-#k7{2CaUa;9p)q;$oX-X>WxmM{Cg%E!AokLdYnpbb)}#+jX4!>Ful zz%i1f>USLK<Jr@J+tL$NdxzvrU$ye=`PS>9iTaZoGi%A$tXhx?w%ks*jrWsh{R5#~ zx6ysyw?u@<AgRA$LoNb@jr2#O6pDAmuoq8Fa{NdfTwK|;svUlWmhA>olYAdNtk4kO zR@qC0F+pR6;H0JDVP#*MSZ+|a_MMpj9T({a4C`#9W_!Wloj9MKP{C_$h@a*UR1=eN zm_{}ASA%aFR<Y{SpC>o#<CyB<rK7rKNW2g}`W(5Ial{{o*DcXQcJM#<)m9(s=#*xx zFsfEPyPntdfE_In!7`;-!|=6&0h1+UsOikYqMwUsr{~7}(9yCRDz@JJ#@6!32<~27 zp7+^%Wh0a1i1C3>ptgZ%WI=O3cbRIe^pOUew5j0<C*r_s31NwnT`g#!3Dz-~($VwM z%Q1w~NoS6Kmz;sZqO<fc?dJX*UtHc3Ln>nCTYxjF@6^(b$&|!p+;%XBqq=-)p;i0w z(-ql~>y5-D*Lx(|g`!zXh=*Y7Sp~iAP&d6iL4Dt^0SE5pRxrxb_K)6^`5r<)_5H#r zTQb3iG6&nowh~cS8687R;0)vSA5zoOy3Bny3bH!&<qnGJp*FG)TM||I((J=97e9Vy z-?RyNB3N|SGz!92bA_w4*q^?6*vX63+rn@q<Mb)btblWVSd%I7I#3BtS|TXgwRdMx zTJ(Fqo+;}dZ<uJ*_AVen`8zqa2M(CyxvrngEscf~152iDJ2N%4W`&yZ?8Pm}kl~*E zu0=aN*-Ar_na(boq;u{=-P6IQ<ex!;@7e&g>^)vQ|1F~@4a)$Xj5R9eH!Eg@nelvG zr<mA##uWth*i!DD6Zl=83K}iQ8A!eEI?!I;P|PhAgy~I}M>JnpYrplbV~$ng2tD>t z|Fl(gRjyr`32t0>w^R0C9rP=tNoqhNDmR)cISs>?NGuaPp?XctEN1N05Y#J3{*HM0 zk}i*_RUX2DEm@O4js8@IY>-5-E31iA$afec<&^a_OC*oQx<F6nd>H*Y_!;0HZs`GG zi!zd_*8Hvr0G|(LNsEM-<p)bReJ+TMxeTv=u1Gi?fKQLMuzss494gEw=Ra32&9Gg} z1R^3m`R608>Y$MKi5dSzS23haOF~-{!^xn3k@jY<m$_Gh9!oaO6nm2qh~os4>2)=4 zH>`WQNEre8CkGMhe_Ee_tzTojtfD*9BAlU9XCrDt0r$k7xYYZ2VxEOl7jPy_?K9=> zTv&t21gwIkn2Y{Pjg0Omhv|=FT@AH&RcFm?fiqfeRMt;7O&?VW7~K}8+~<k;?FoR9 zDkGJuPFbZb0?P8GMI$=A=tB3?`JbXv3Xd1;$p7-5Ucgq7>;3DaGJ3IyI|3diCe#Nw zqYHHS>pDIWY2U}+U}nEk|GXt)raH?u?=)ehDb+i@&hr<{yQqYr-SkJ8%OhYV!-SO_ z@O41<wfs?FQqo^vRkj&%D_S9?U%)o4BlQYz^%{f79E{_;@nLkB5u=2KfLW7Agvu#h zG*5%qM;%D}wQ?c@HI9aF>(m>JjdCEm;?2#w2FH@dlHvz?uA`IF9%yk3GbEq1^b|Gf zWsANUNqfHA+_#ialj8e6UKu{XQ{z=Oe;d+esK7>MZ>a0GI5lPtyEQz_iJ>S&PN%tK z#5Ju9saIFt#uye~&T$?0CX6^*g2L>e9%=iy=uRE#e#``%&5uq^SC?hF_t_A8q*lMY z-z7qM22v&@6uJ@|V5gR#Q%*^>4=%FU#}{(qVZzAd8t-s%kf1!OKbGNt3{lsG_sC>* zZP7su*j}ffX;s=&2rbH<9uCYcPY>Qa(f#2@h=&BaABUG4*iytMy%pd#nM>W_{hXCq z2={#6)O(WeUBUt5x^ctAXTsV_W!<zbAG6|$9FO+<{*=RJt?kI`je9p5FMm2yPJS-l z420PInx}6FxrrV{TV4@<d}~@DuKpGvyAdscYcbde2SbOatH~-RtJBO6NR@2xqj^hE z*MFhgAOsjj&B{SY@CGrA9>$FrZ`aTLKJ%Z4Qo1J~cRG*TE5WEU=wFPpAv(wIV=0P$ z5t+xz0*{2lz5s(B1U&;v;B-4tP5Mu;za(Oj7h`(rKc4;E39q|eHPC(LNd|SWewA2@ zO>7OW*SjU~*z?5xlG{b6U>kJ0e+>>cUHs<1@m_A@FqaQA*dp?%!_KZv;JlhXSHXF# zQVe!)9b&w&>6C4G#j^xbg4Wb_yBE)3N_QsJI{PW9Z8xKKETJ&QkYl)!!Yf#jrD_Kv zUMyI!0b`dg3_k{jCqB%c+xVBJZ-SJ*$JiDkH0wU}86|QFJbYFPROc6J^GZSJf9~qB zy3YytDV?7DJuEVa{?Pkzm6E1^g!6Dg_c!=(OS}H@S~2AQFfEH*^OB!!Ia1eEQ1JR! z=qJ@!3+%e9&-r<+h(h)I<u^@TVb)x%*`*An&O?;+CzPm7`HCU6cy;(@)@I!{&v#k% zNV;DQsLd(oYA2mCS(uSlTGn)J`!zy0LFk8cp2tS=&mLfQ>xOj^0xqk073E0<to>u> zwQNWwKbXX7{)4UJpJisr1JWLm(!{S{QSJ3)dYZy@wYAAWoYL=~BEao<4SkW+heDcS zYn%*70vc8>OK6}zv>aN=Ya~*n=$ART^)I2SrAQ3__um0ldp80^{<XmUP$?lq|CW^h zVgqEbwr}qqogVisQuh+MSyy~SX0MW`r^Rul)QLG0Vn0<j6P23GSk|cK$V@_OHB`fG zr$^qb|4EylaUKRlZyL586X=|A+65gu2r|=hDK@GOS+KT4JVaBQ5U>(VC&pi14qZ5Q zCtwccGt<OStq|ZNCp>O)3_$2CroeJkC_n^fKlXYzf71;@vPNg;r4cR`5LHlZL`@fR zor05=Lu|U8{*5<q`-@r^^Lk+<`>YR9<`eWhC3rmS{_tA2_A^9Yv+Q?G^PKpBfLA5- z%Ka_zX;*Ui&RIZd_$6uV>z4HU_HLTvUja@Zv|l~l|L6^ODUF}}0zDsYW^`Ns7_azc zZn4m|>E7nU*xY7ki?{Aqz9Ubx8b~YuVjhw!#Ob~kU&wMXlA5<URZknE?JtArL3+uO zMtR%{XgKb$me3$=TZ=b4X@PsEGh)>~L_6}7?yuB8;<RU>VQ*o&N2Ln0x@El4`0)}- zJ@_vlwd`IpxMB_RSZZg=VW^GA!NQi$>0ou>EKZ0fz!hzaAhbK+(U6NXeK)lmVY zM$b9|ZBcCl=W0$eQdSN+CI`d9=^k{*=li!g#+V`Z2S(8vTj~bVTvJ4n;7zQY?bu9P z+2>Bp>Ja>c?dg{A=j%Xk>9CF8G)Oy(C2u@VLaJ;Z@6|F%sM#lNHly(#EH8yVJeZ`T z`ZE^mGqRMHAaHr_Y1f-iC1svX`@ZhKyy-tC!aj9l<fS0`VST6PeVR&p)01D`#W$Ro zPfI26fGIpx5ks|nHlYtD*gXQl?RaAr?<(p#poVlMH!M&&)L)OXdYnhV|8$!8L327! z$&6B>#fBu*xGR>((~zS%wT!lhmvWNsEELivSOq;aG?sOxj9TM;*|OhF=~5UpKXzK~ zZV}`9FPg;wXjZ|)On`|P1`ksUWu+;s<U@2<5={F3rIb+b+JWn~&Y_lS+O0K^LTRC{ z1$_Snw31&EAC>3ThyTEsFpL-pKu5h#Cg+{bhriBx*W(?l^Wwv?GUbwMBHlcjVzJH~ zqmYO#vh2UwUz>w5MRlGB1Vjm^HJ|~_Y;RfHl1nrN$<t&^ryk-{ygN?6DV!eMQ@9(3 ze+mxq3?zp&CE=m%?{a8e>rBuuR7c#$EW|w3P4^^hbKB8SCMu1N{drR|<B{BD-@3c5 z7L;;!Tf`LDW8T2A5=#7hgxLI$*ENmYxT5;&RxBaiYSC1YmL4H4l7H6ogr?PboA@-V z{t@~5pY?CYRCdmf71uuk`74@_YJ;I3@1~b-u122x>zq|&o$706QhZRf?KoQ*j=JRx zmb{!TXvjq7`N^#7o)5jBeAJfb1BMmC4XkXeOVrt3wcQ<=gkRPPIy%lz#<}Tdsg+FJ zb4e*!vHZ;jtCPUEa!+&aDr~gGAlM6?j|RfUaE?}1jC4hNrwOtkVU!uI;#b+Y4Rn%X zi{Pf{m@PZU2>}maRKrZ)9P%F)A*}m?LlQx^f6e*FHj+R>AzJM!h{7Sw>zO`^P3$X| zv0P6VIzhSdfL^?*7_hslY<OkAPq1LT;VPy1YA@ng%uTgIT?AXgI3S{l;>qEK);)S) zP_nC%&`$j#<5uSN$RNAl*K|t387*&BK_z{oX*8}BV|TRcK#6sM=9;nIGaruW;&g~b zP>$I2qgL}=T7H*)So2%C$AkS~8dd~u>Fr+a9e_Q*m{o-c9owvet8jN|JN92|-5)s% z-`me~su8rKc-{UC#<vP>yd#n6Js%p;p%gvi>62GHR8=WIv>CB!S3G`%zar$NgETTC zG-Xs2wLOdtQ0iRH^?}jKn`*EWT-+5YTQ7*)x{qE-4I#m=5s+}PzjZK{_mdLevh`j= z(}sYXX@rn;@4GPd8FjeBnCblQS5g16u@gXq67XP{LqqJshi<_>z$h20g}W##;(cha zH*WsIO*vCs-l8ykWsvz$SY1CEo+UrP1`?p!9XahAp~Zd2NBWl#q5-HR*3tX>f0@G# zun-FIeMAKE!+G2gpuM(=^$vB0<8H#;d_%}CR%P;6BXUa(`M{W-vL6B2h)(OHOZ`^z zQ22PjsmGAyeLKot|2&o;@$r%eA>X`^J`#M<AAxG8(l-^!KgcQ-|1%{viiS05q#L`d zsGfxj1SO=g;=ge0Nr1wC#Wv5!aJ_&h=Dt>9zsR6}5Mc6}-o{z=ZdC!;*6wxgZ28V& zOZpQRD=9RPS3PgAg9+29{Ok{=r<WsipKo~<&-?HQgg%)_n#b9Z%E9h0SU({!@mN%- zyY+UZMOe{5w3wggz?vuF#N}8s2h`;DWGI)s3{=sDTk873BT1mJrsqfY#LplA7w}1_ z{!Emg1!`6|%OQ9vF_`sW;S#78P1qfwPd<W_O6sXh`i^!`a<YqwvFNp7wc3sgeiyXB z0qXy*EDNx*uOxe3|F4zhe97uRTz4=?FD2}#A4~b3f{NB_$Rh6KJFQXE29VBeJ8Kdk zUlY<cX-bes5VABsaV-Kf>SLbJK5>H!cbb;qmUi%_lR&oRb3X~|R<2<=g9q#|lNdAW z;88KH9X`zA(&qWbTn>?G-AAWIn|E#RQ&2g4uTP9vTwZKNd3G2vP_NQiw1Mx|0Hb?N zbq{nuUKu5F<aPzkMT0N6NtlLo)t@<^G_{;<mR*I>MI}Cqb3a`(dw+y{V3v~otWUEf zSb*)kR`&!^bU6qJH#M`WiZMFw$jKsDygl3x8?bd|o<2m48_cY{JM3g$RjAk~E42t3 zIy%>y>1oIx>S1oX$2H6Y*UcYQL1O{w4|s9s0?wBHdGR0&mI?#9CB61f;rZn{6Z8-b zb3vQFey{<B6^heRKN+y=N+;Pws0~g(^Q{p3lS6%pUYIEATlT*t>>s{r`NCH(Za?kM zPery-iEb!CA{rH77k;vs*Bo(HWC)!MSDgeG4|h6NIANwQiXGP{V0ma0_P{i*b&1F) zdvbmsw|Ao@Oq(L@*o$aUak-hh?8&;1>T*U78ZAK?W?%&c9-~=&saQ?>9OnF^31a|T zp|=>{d2P9^%|E$OQa)Wds62$@JPtdJG`!fVx~4Fw`R+8~ls%YT8+?$X{#XQCs@m5- zdNWRpdc9yCeNbKa^Km4B3m1EhS5UKARc^x)K=VgtZ>WSe16(xpEI1p+y0#)b-TQL) zZhN=%REdyXrc~!U0*_-k;`XpJ{d{Y8c?)Z^PUj<xPxsw*qtjTl=GW?!v6%*{#i5a{ z+gcv`y5*ww_RhtJAHNX2cr+AZ7$Q)C_jbLveMiB<gxK|Z)E#*SlKIN<mq2n%QasK( zt|fEa!;TV|x`(%4G^7@=#%cpDOe3N_0l6FabRRn9EhXM9hRGmX;j-&<A|XEfUmf62 zqRoEMiAD<my1EV>Y~s<|qL@{dDFF)_IBRe(W~zWyXs>itI!(IP4jK@zo(jY#uY(&M zoA;T{i;*5+`o{IE@*|P7clc20tdc9ig2cg+`wosdesU(H`gL<aLsBC1Y54XCjhKuM z=jZR}wmEr3?#M)97VhwsT`+DvIb<6~CgR{-Z0Ni8XmbL7d(jeM2GzR+aasHCLDO(F zg9B`=G1+u;)Sr)TWGZk9`KeR~4@td1LNW{)+wzPeUJ6vHC5b8G49l(C@9>gUXd-SV zB(^drgccCC`jO)&oMsf<!|7W*730EWO4G^b9q9;Sj1fm!%IfmtusRs0u^0dPVGkz| z`3CM@+`MQw1F#ok4TP^ij2YJ<P>&=)A;xYbvkbL`8Dvx)G<F`cC@m9K8<vM7U<|0> z0B{6}0k&DU9e3^jFKnQH1sKn-k!(R}UfXIs;+x-x+#Xpv@B7pTwvVjbyACTua*laz zaLc@(>E!8#_jPtKa;k%+wN$i2Ya^KCqgiwEuPlA{(FfLt71HhS3ua1CR?)@%6{5I@ zBoD&T#wCI{KqVf*nuoJPWz4dkmhrL2fh0D2W=n~cTkokDYvJ<+YvjH6dTKFwfsM>g z_d9&bf@i|GW-Y%a!z}|}+Je9<-w*hWN+L;~QSDt68Q1N5eoZwsm^C#uv(zvb=D=#$ zSGJr{50>z^p=M)aG({z$Mc|jV!7GQ=atUnzbsv_>7ly{5TdVLt42@h8@TS$VqBm|? zwa{EkSMI?n%}V;j#S-OtS2Yb?l+JX5PMZxkTP7>?cLq14lDZs$-^-867bsLOpE{>p zEQcidNsmf6kWb;6c+k^hD+&TQx&F((O@Nh7Yha0kmCmq%iH4~L2_F|1{dxs&e5oBx zDH-RpH!$JwpiU9(h80&IoxsGr8UkhIeR1u_D7tX}iI9cwrIX}~_|i^*O;y%G`;(P{ z2od%GIFdlMt7^h{6MZCPTK55txvc_SP&eWIY#l;cOi~21#|E3n(JO5DQaytnQq|rw zLvkv+clr8OW3**iiql-#kakr5Dr<Tjt+rRa=6GSJrGm;D+!k!D7twNdN8@u}cYIX- zMLZHgAfrS?x^Ag_Z<l`e6K*Fy9;*P#bG1+A;foO5PPCrDcPoY8)j&ThmkGn<8)LQ= z_EIf^)v=U4gJsG~RRReB4Cj($rbGRo-{0;}4rII?r6RHmkzfmtg(dIab>>zhEjD;J zyWT_0;&n=gyw&ZM2IQEKv+RE|?4l#5Cf5wLV}i4_qEd_?^kR1`nnDt0;ffwvwf#Zs zn$Bupfckp&v)#M!QbIPtw)){(EwkeL?a5e*b+iXL;?_5CJY%dW+5hyS|7)5cgO?a^ zIdk2mU=e?z_)V*VE;;{PA8!w&i*@l9O`R)Eec<{j4v|zGej7S=*VKWP1NOnlmI5cw z0#a2wEWqpCVC2b|eg8-pfQkW36aABTuS0X~rpE9*p34GHFN!t&PQRxah;ADiL*!<3 z%=LveS$#SUxLr12y)O0xD;l!UHYwo}xmw<^|3><lnh+i~NfA7i>Zd$MO-Vt*!Gj7A z!jrC`@k5BQyF)kh;{6W23wWJrlB>30*wWuF;q{0cq;|2}tWuC92?Pq-FB3$WDFImz zcalT?QaHd_e1HOQ2>eo?sQ-1rVkplQL4ZfcZ>tw4#9_2|KzxGn1;|&zVaq9{mBNTD z*t<_(AY2MqqTclqyTSj$1O>?pZCha*g_pYwFLk*HxU6@$oZ$8-!SeO#2>_-iqnWsF zLNso6xrAt^dy>}P@R*zJx5qHI1Za9y;*}oi7cz)e{O&Gdc9&pLK>fu-$%EJ*94sw= zks&R<TP(ri^=w3+?RYT%jq2ev-NNp^MKL*X#J<XUGjzTLMMNLYG@&cws!#0uCQ{y7 zC~-6F0QT^x-zS50#D5(N7~K1M^t2Vm<&lEL1*QVVF}Jzi?O5ma=vQi9CyP8PSe7;x z64q>AR|z6lJ8lB5dBCU_Zi9ad4<JI<C4G+WKdbxw$EGjh6ayNLB8n#;U(?U0LxK8p z-~6A(kF_KU;LJl&FKf<L_s*&MyR6M6@#Vc_w?(_>n43<6@*p^{$>4i+tbsLgY&Q0B zaUVg#=j*%TaBiz@4vsoVZ~`af>i)^Mv)6(Fm+!_$j7QRoLWvWzLA$SixJw25N5p;^ zFL6M_7^HrQ^IwIH;$1mx&qRxK^Dt_83TzLZuR8SI=otFVqfX;0QSmv4*1GHkEK8km z*N(oPR|USA%#t?PLig@I0RPY0_^<;jGpv;;`#-=(!6FaavmmD1wi20*HsE@RUJiL` zYhyS~#E+(kAHXh6jXd@~eWdw45<!-YlIujwAHw{6LS(rFmS)ILTXGJhFN8{qxYlvx zwZ0v6kESiFKP(l4JprdUuTx(O`f@EV)1Ot==5|zF+II4<265LiX0<HCGNkaq={}57 za=X(K2hH&}{B4z!KvS|TA4zaq0G?(L2ryu6N|=jd0IJgvdEv-UgVc(X8oUf}TRwK} z988*WE#YDHFVjdbP}KkVcmsTbZkGA@ufJvlZ1(3R>EG_+xw9~~i!O3Ya`d<mS|F$q z$*gm)kXj?&wUk)5s5w7>I-<t8w&Hz?jarN^?sK$%HBWh(K5=q%o)8Roq0%=jk7PH~ z*n~{bO?WLpdepuWJ3HcaKDvmNQDfuTtCLi8Z<WA*aN^cNgZ1fQX`8uQib)7RH-IPe z%H>bS`V%+M$rdkebOLm=nv08(N=r)%%FBDdpk{bC1p*ByMMsaB$+m$!!%!T?__5Fs zZJNH5#J1jxV|L7}(zb~UX3l-*k(*Xf#2<m@+e@+<y{X{*50btl17g^oVkj%!c+mU% z2WS@oB0gGkUB>D0J>cljUR^Ph{Kl0dO&<kCxo+UBy%3wn{i(hog-BColCnCB(_$fk zz?G*<SKx4{QL#6kv!s!%;*P4P8pMt+Q3-Joo1uB@$)O|dAre(l$*~2Bykzt1r2qNu zc9%BnwKv}Zq#*K@hHfk?LzapNg=$G53Y4^w>`@)b#r0yqV0DuzM9S4zT5{Y!CSE#t zoW&CW${b*2$mM`r={qyp{~vUDcMVH*1~??b1m87r&y4p2d~=Pbx6q=^zP&9eBL`eU zS{2U9?IH)7CNHnCgOw+`s?aT+_4CrYsnv1RneXZ=qyY#IEvpE_kE)-R%NXSP>5Qs# zJ>gsd+o-p7c_@UJ=(`{rabErGzh6l%sYkXF8iw<p6WCCshamoX)EwG#L!IL&4{B&? zO_+pB!ax%KXFY6U^0tVdOVRj>(4Dit6=CTVjU->Y0g#N_{V9MfAgVym90FMQCoD1{ zum4oB8u=%EPbvxu|0_2w5GVwRsb25rhbmyy3>0D)qi^uTXkFDnAXn{SHy413?#L=) z^6_8E9BoubnR4<d*1uO_Lz>dt{t0j$W2&0Uy+*fK?m#)ZO9>V#g>m$o2^U2zQq*K2 z+-68&oE~OpZJ;2QLDT{9qfE3hc-HS+ut-l77Zr_8Pk-x=p-yrC`epC?5b(voE0qie z{Z?lKgVd0V;$7W~u6PU=4&`~l?Ii}yYds_8&CE~E>JuOVDQcPw*SNK&pmGrvec!$F z^x2RHAR(s;fVo`KMIzFHTUAFa(Wv@oJKek0AZ0tvnMlXuRHB!k3UCGBi!S8#$b>GY z;6H=I8C|@VRQWIGRV^1=v3nKYXH1lXs)-?td^qSJI5W*yCsH0BM4;GjFXd`R>4s}q zTD4kmKoDwqCZ6?NMc5i)=*1=DXHMlnR1$X_My*>Qs&UHecHpumj~iWFv|KDUF*IzS z_)32jy_a(M_sid;wTri6<MI-|XmX?Y(3(EL1`c9AFM_sZj{G{hL2_Rj7}^(ZTm*j4 zQa<}Nw(BQOX<NFbqTIh}fa*o8KsV!bWdxi=j4A`hK~Z&>PQLA=14-=Y>)V&L6mCWh z3Jh!&L`}g@0oi?CG*Kfe{v}xPZP{UmhVF_`1Z+i~3<8ag&qvf=F$Gf}N~ucXEql+& zzNk#mRMVpsnrdp;dnusN<*|T^NkiHg*vN$sV?0<GBYy>8=?r36$eK<LF^;-Fu-NZj zY{i%XoOA=XBO+`V_YRuXzvqCzA`DAy2}zD$TJea8Yo1A7WS;7mz{{=`O28+ZhY5NC z6_SMvR#~qV`00cTs$cGIx6_>m4aok4ukZ*g<T%qtSlqaM`&=YogGm>v)GE!?)LKNz zA|>ZGioq^*;I%_IheJ7YV`u;eW172;=HOq^fyDReT~M2Q^wY;LxPW6kz558b(!Lea z&bmIdr&{;Hld=)+OYhxnAQ>17AX*WoqkJH0-8kc-IliJ%n_{i$Uwhrc*(d+P#BdB= z2zdqKnk@j@k_qh&BLaac#i~5Wq5)<@{~_%xFdjR6EIJJTl~q}qVO^Nnn)&DO5WCQQ z)sc4g_RJrO^y6V?GMhGBK=Anx5Vc9+OYuVqFjCj1?yy=3Q+IE=o-V<c+@MGE!eRo5 z_5qiX=Z0Z!xj=hAT{Yn}%(K0^nV{?RTOaT58ZpD(jMiau50bsGa8|kY2+frls9UI3 zX3;@nPnc$G+(({O1ZllyG+VR`Z$_BvbRa^~#_d>-AdVGbOrKft5>qWSBo|c2u5_6F z^)KoZ6_CHfLgE27T%YEq!wpa~bl(|WZ$f(265HP$xF)H-m-{n(TddK4QdouwaNi5; z_**YN*FH;`0psW`Zi<;fD;l}vy3rR4bu3Yj4BXE@a_#+a*X8uaAy9`zmN6w#Ft5m> z(g@xSm<nNq8eN2WFgNQ&-nQhu1T%D|;TPaul;nY6ru}F52>5spr|*Xp&M&*;xe;Hr zs!dTjZI%xbd&U2XA3qRJGHpgI6aOH#K?3m0EBLqw=7bSXbPO}P)PbDTPW8?VT3c5a z1^nxAKaG7g8_J3t@`QmVE%!!G<V$<dn8WKOY55aJhpE!eu@(0}<*Q!x3spYE)q4hF z%pjyaf{>5JW|_a2btstw2@?}D!d0H)k8-n<1VwyzHTaF^1+ZD1UOv_2&>8*q<=+31 z4ci3b#S%sV=E8<9FPna~A{6LsWs8#Z<F_w*U}pQ?b`#}LeF`Ir3)agA{(-<tBZM5! z7+B6Oa$m7P<jm&g#nbW7kPzF8olpM@zI;$T-Hkg5{`hATtmXB>)v{SmEjYZznXdCa z8qWX6@<HHVrC6f?Pu{mwShRERCVwd5560InvCCfSlfC2xNzy|<ruF?{CJHaRAM*X! z?qB?#3pP19IVw7Oje9vKhoZWshUfn@`yW6J<A+`j4dXYs5{5yK^I;U;KPLhI@_e|l zThwNKn+AO>-eRaT!%dxWcUDaxgoOzmD!0@pcWjMo6*_w5m8#1Y2sG$e{^;M?=$&7H z-dz|GbomTY`p7o7&-An^1vT}5eXjjw!Fe6N(ESO37r!ecZFi4W>P%{hY8R(i-SE4w zBNKN1=<_3JLV&cqkU^<y2Lcrzgrog^YKd(gEA#Nt-@iQ+2pAkppn8yk<Kv?Qt_NVx z&dzMlHe`B9RaI0ntE(qjBmdoWHw?tsOcIjPztHCctMC@M+f=;ag8*16GUx>u&|9=g z7@!CCT&rJ%Fp7wNYVZ01Jylr5TzJv380WBKl0Re&-J{Xfm4apO1x)TRY@uG<D!e_F z$Km6rPix#IZ}B--FD4ZK3w{6@7Q6s&btt3ZAj3qDx#A94aR*g~v-tK+VJd(HA$ty6 z9g`KJKG$RhMpWF$XgJ;g1oMu=T?>#20}uWXdB^mlN}=}2zSa|oCJv=Ez9<nzX(p;O zSTubk{Qbw)|A+wrrdLn_^ds8+scfHaw+#T_j+nXG$&Q399edRo5TXCQ<5VER%Gt5= zzPAe_`fs)$N!_XUQ9kont&M|>cT$#gu4@lcX9XJ*!We&@S4g)1kaI6z59N<YRT7Bc zk8Hszoze8Nbo~+2euBQ*(s#mc6iV@q85XFNYJ6HvwhgP{(AMFg%h{0;a)9EeT)5lx zPtP!RRKU7gKLdvlKGZis8s>lYD*_(&wLMVq0_fe2ibY_b7wbO0PJcWwAv{m~+nZmN z2cqMTqVbQcuo1r;Om$=lyOmV83Ar%5_zse}=bBd2k2nrx+EHSH`r7qz1<|+j>*Fv^ z$9c+zq(d8=d5E(PCdc5P(Y;rzhrxB*+*-ef5%W#cY>Uh?>!=Qy>ZyIdkO3a|1*YFv zngkhf7N)v!)!NjQg{l6y@E!ELA~Y{@f|!<;eHOJ3&n+|q9W++FOH{l#D=!n(4{RA2 za$#WM5rmCm02f$KX%FXG4oZKz$dAB2O*{60uUmF2{y$f~KnEM)2^;GQMsnIA_@soG z{H!pA|8|9r_=qy+y5F}S$<$)r)RveDk9Qo4acQ(~A%5!Xl!vs!BwJpp*@w;DkBasO z$Xg)F4gUok61|bAmKoB1HXq$Bh3n}Aj+m=K4Wm+<Dm=rqq=p1$fpMe!1}et-y3ZS3 zmdWHPQ~uzC3qppi*lqw^7y##g6Hl-YL@AALSsBwTQ~eaooipa9=_NHPGNQZ-3?vSp z-Tpx`OjT`3qmamj|BDC+dgoaE(e`EcjjuWobq2vFsem4Fg!r&QE%a-MYvrJCZQi21 z`|*0^_&>-F{gPbv2LlQzSU$qU-w>3p1Q1;YnbUYL;*yTi_}b_2rkjro&fRb1$ek@T z!kC&}XO?CoF_v1HS{w^YspWe#u3m>e;Jky@=(a3ew{MD$it>RiSJUqZz7(@(8=u&S zfrw=F!j)s#%gIE>bM!*<%;>22Ph&Zj<mHYw$Q(Es=i3Wmz(hBeNeFeQYHuww!wq`W zm)2xjWGRh<JsoczDw!Tkt<6x%LY*9)oL>H@jU&Q)V>`yUPe#j%DkCE!9b1#hgvUM+ zW8~>VIv_e7fP~nX;8beoU;aDnQ>*9*%?DBL6@qTnkp3-N#5dnwQifPK7|3|BK333# zw8GCiusc|<N3}@oB!!Px@(gb)e&VQq-jfuOR1ccI7t;*N!yz<5`kEjI(*me%OGpI9 zfKj@reWi$8Vbyd!Yu3BVwHnaMTdtD-PnQB75pmW~1fh|BiWQYR0&A%DCQnj+H;_vp zep{MWU}fEVdMBwo!;#fm2**)rV`+`G_9<Y;$<fhK8l6t6NO<R)T37c*j^Vis8YwR| zJ9<C^4^97oWCTJ4E-hl8yqstVLR5d2q<gMr9m1^j=|JfvxavYe1tVLfdxu>qR#%MV zUGX|zu!b(xC~i~(LeO1_U)AeTFVqY^%HoDnB#sZI1vX|eibx8R9P8p%<mN`<JB_CC z)lM4d+GL{pclQ1JaMrG<A~O=^<{`~tQQu-?DGVv7vr1Tq;dqktG0B-}D5y!8wa3wo z-r|n&0yCQY_VZ9!z;Ac(4r{R*DHG`)$b3mr0ZfWIsbE9w;(#`RcKePn+HW@$fUO8a ziAyGl4fnHp@N1=I$e0!?5UooueU@Q=THw-7L2n}OgL<Q5A$dSYhbRG`)8PL<U+853 z`v5_h{3<(HD!c|uMdm;92Fm2nKoa~V*!0_Cz55mAXEayJL?WA~=BU{VVNko*q0$Uq zm(}r#CGno-aHVv)zhm{gGmb8`m~IQws;Up;Bc%j)`-wJ8Ofsrzh$pBMGD~O_{deCR z=H~{zJCw|N^NXG=KVr0+3OuOM6-ADmi&2+}_Hj5mg;H3v5E?Z@C1ko#1?lE4UX`HS zN@OY;otx&m1|FIC*J(2E``>b}(<-T|J%lmz!M%t3m0P391FI!6ty0s0u4BUUmwgt< zh-@o!XKbP(I#|C$kyzY+Q^?Z|N?LzIm?HZ~E$g~8DGKT~Su)h{_cx^vSfOCCe<8v4 zmUF=BzVcPaUc6S77i9;8)<icQ10lqXT-Yu)ULQb{l^=+~#v)$?>TkWr9pmHsIfl>| z4&VSFMei-F{SC0-or)P16{t!2YZDWyN=jcB7Z-n2CjHgO--!u7_bCrRTLW(sBhP@T z#>`bCUY#W#SgUy*mW3JOgCi{FKppYJwEb7ndcmwNZEubK>7u27uc}?k5+zgZ;~65X zxcEg;jbAi9BlhBF-RG8McnQh()&63K^A`nT2tlhfvifeaSsu-jj;WjtuvOxYsh?v* z4<^M8sX|hYG7zl3F-{E{3%FN~)DwS!X=LKvoLKy#@r#&u+NYH7I(yHQV-GKRlt#ZA z)VV+KI?XGIpl(K;CnbG$%f#8%=R4*dE0J4JL2xBg*n(4f>wu48()IfapWz{CNjopB zym(B5>1zAfjT~|%f76-}Ra8)~5nW&k*MNAXVdw)g)@lUoH)((gh`|Dr%is0Fk}+oB z<KbnnW*jeRgiv|Q5-6?Z#sJk6+jkiCk2Ly@!AK4vK+8eLGTd>Zv<BENH5kQ1!SV@^ z(0|ad_wT=OKUEkbBO`8)o1X?L%*`h-!$Qv<|7D+!)a`+-OGzvNu>UBu)3oI!gLASf zC<#)4(R-(hn_?;-kem+6I?6obxEZF0r>OVG$g&*rG0@i4Tt~_2GK0eAzcI2kshGw( zfG0|_G9EjEBpo@Qy*>z;cQSx;rC%tMXUR-9C2uqXw09Mo(#O=8@IuP&L~Wgrq)oWK z0eMMtKT7TpwWFP(ah&W47fx{)UY=hK7_Cm0z%eM#g!kq@x7$A*l%s`i(^dG{SjW|` zkip8uF`-6z>JuHR4H(;%m&PC4Q>mVe#}^EH-R{h@I34iX;E`+TGTB;O*wkdqcvK`z zgTH9~yr-0`M=6wH+Az-h@>xdEIQBQv>zEMMmiJORAZZp9GH}kX8GJ*5-SKgWto=a{ z!E_yk9l7rpk{5E|(?=yuqJRh}HkX%|+;$MyqhcC~>_`~LR3=27D=35<msg3JtJnjk zTwB<3vqZNG!U3iO6W<pv`&URC=kB534`l)FyzT?kGj=R81VBmLUPx`bo0|aH%};ka zxEba<?!s<8f%`wqawpf0^;IQBX-ttTZui2VL~}DkV{-HNp5y_qEsiGrmBC78Hn+wK zLsk|4Dv+W=AQT!9;d==JjKhzdp0ns4quKD|-<Be4W~OTrLqh3L&2b;q?K7WJREy0b zH7R9OSSiOHGhQ)yQ;)|q(?oNRRpl#mj5n~eI@m+oPoPCn7?;zD7FU#T=JRsbWulMX zR(ki`DSwEEce`c(IsBiq0L=SJvg|xlP~3|Z5;-b7M@R{HROydqrp7h=4b=Qfh`|-s zp@Vz2nneTryJ8|~3_}w`%#h09w?quZW_M&?Uym}mCW+AB<=V7$g{YE(5(;sqLkFDF zJ$Snbn-n2|)Y&E6C{=m!`eP-9`n2g^OAMLO92w2NR+>GlMs7`}?td~&j_b5R!{LT@ zFGXplvUn-E_@E!P_s?$uY3iQ^m}D5o=JMA%&w2<HeUEDg{6N|ADpXd0ssyEb=U0>= zAsUX*3ztuCfJg&0S%K8I-0QNq{_$#o(7pNTZXbB;%=v!`CO&}8AHHM`mUl3<+?Ggw zbd!Y<3rz4FAGFE%i7}&3p`1dR4vY3;Os%nheT0?7Ocx+R#Zw`Ph0pJi<)GPbP=3p4 z+)sAgpUJQJHe^&kkgm7U%)3V)1ZxSNDgB&zTX1Rd@dI*DpXuCu(WM3UBAKYWVS0M> zH?tVopDx4l42e9rxwnPVr?R?iwwN^GJu*gPwnP!k+T?p)OVk~<f${}CF3T0y2%Od{ zUkTljWj##N?J7#M9NH<dPAUc8yE(KsqmsnvQ)yG{2-3YSrnB6Vn)!+_u(b(SC0(*s z1XX(aE0<9ly(hN`HKX)0o0(<@37se*X!Oj_Z-LQzA*L}ZDLUv@>9_6tpqcvn76Ml^ za?rX|O4)|oOij(My5H^=P2wOSA|TZQ90)OD3gAF*pu?e;mY0!hd{lwVp%$C*D?yeQ zh>5Qh8Qv)JBdDy6u<B&`jta@&;3`Y9hX$g?%P#AJ8srEJnM;6PD$D$fd9L34^nOuB zWLA%wj(bc5&vAPz7&U9(*tFi}{g)FpqOXdr9LXn&!U&*$Tw}CH5^@|Pcr1{nb|Z_J zt_|*U!LhMi_HBt%DX=mVYf={oU8GjD6vx4E{(aRZblb^1TTp942p<Mzl{QaScXXJQ z87L@e8VfJlnDG35g^oI_7B_q{x<xKkuu0s~@wPFF0Sj3NHJ>^GOMlL<L7m$K@3&lK zmnaC*-v92xz#{bubDsPSvhw^o=Q|kvjffbu{o9PL=VhREm{ND#0bTLU#vGF#Y*UFE z%GjenS4LJn?2}~qVal}*I6oZ=b>2-xB=mwp7``;)E4x`yaXh1F{pRqJ*PQV5H-s^0 zwZMys2!`NS0!<~bCQQkJaO`;*EL=m)sU&heXc=G8ZARK!A`RDKv64m%(1IxSo*BYE zZ2#H$ejy?3EhGsNE=D@KcgD(GPMB6xd3pBl;;YZ{Oq|JW#YcwhgrMT1EP1)G2*(M+ zyQUoC!wQ5KVTcsa`wj--q(lL;d_%BYtVgAL&u69MnpkX|@cA>&ac?ln1YmJ#l6auD z??GGRLA($b4$xYXuhy<oqyM;X5bS{0tHDpd9uzc!UwhEgSXa9`I%Zz&1X3OGvI{QZ z?9U6xFe)?;HaE&(gp&_j)mzm0z#2dJ9ra+2p#Q5XIhAbd<4>solR8k#Z|~%M)hRmO zdu=)<^QL+KMqMlor7Jpd_YLwd%Tehu!M9cU2AjA@EFTP@LJ`tvz+J9Y(M&2qPV<h< zi*D4b=m|4d1T~@bS3~fMtGkyA8{PhNsU;SYa_;$qG5E`{m>F>eWpC0L<es>)9BiN7 zt8k&OX#7rwe4cij6BakEQuWJ~Z>|0!pPL);W8eGE$-jgZ+)wx})U=Alk<eV*Zmw+l zj7S&$dl#d#j5XCTs1|A<VNk@?i}y3wGx)Vt_-85o%iKd=tKmWg$|OmcX}lVpN|LvK zlpq{Hy?M3XJx;-*3?s1%&A{+lPbLXJ#vzL_`|w_8ztyQgph6I^hxE(lpmG1Xm+N|p zjH5U<cJ{IP`91EZ1RXcT?H}%C{|${S#2w;5j~)CWe-nyT@%HcOR%1g<nfeu$XOvDQ zrf8YB>YjYl*7LzLD{h)y+vxh2mWBdjkIF6Rb-&48gUGU`zP*l!-zhlUiJr<cn<;Ry z&v{*0x?|RCQl;U3?jwa{fxA@9R`^^7Yyl_RTB$dd6QUDLiB#+|sc^y?`?SQ8pHHQ? z75lk_+mm!lfl*S&oa}o}8K`A`$p0?k{gV8|G-(?-Vj|Dayev6?oeCZ)cN6R@0)9Vg z-KDT__lWGwQY1mdf2>+)GMH*EH=*Ow$fx~MEtHb8QaD=T?+<qZ-Oh@92McNQ0;=N& z8)yx>Hdpt)gAH~E$A4g`q0Uzq?{{8MoHL>yp)P^e1Kd4IX@&>dagnyy4k#U1DTUDc zW%k_0CfSxW$k~Vb)NbS>J4~DeuPH{^fg1VZuPEq=4m>Ij2yos7G@{zrj2ZV-y=W9H z?_uWp<8fM)yLNV6ttlv6#L5~ko!u`4_DApZtpLDUqXt6eCaF>axJDFRO?4RlRb1Y> z=fY99<b>b)bXopiJ&Yt7h#ATARa~H}A#B8}iCn$Gp~<9Nyw%7lbVY22F|bWIt6Tvu zWwAF($CU7>&x41cZkPP(jHD;%D;;-HqXj1_j0!<~?IYU2oV2r~^c!P*yHPZx4<WT* z#7(W_o@X3N>9f&4jq&fWJ|>pQ<Cf-s0F||>_+w>$da!g?&X~k$-w_Advl%O-glvYI z81&1dW=^1mQC3Us%XnI`*;gqZdK&696Q!6TDa5&GCb*11oSM2LLw=i=cv{eNS}%mo z8*8BJBidyAPM~EvH!E4i%rg^rLVY*ICdiG%hQiyMY!S^>Jf6I0OWFUr^p(};r>f~- z9@gGGeA;|hr*t}hcYct@w2}!uT9;uXHy?hev3zgCz?K2X^`CKNf6hY<W|~B7{6Wai z9&&-i4<lJhez=R#KCb`^aSML|T4cg)2(wtDc&|V$Yi4hD;OSm}T8LXNzhe*wz=|Js z9PQ6ecC$?)1{#?tci92n`NbGyMQqCeK&4N7k$a6ZEF*wx$h-R8ycFABLT7HBQKJOy zZ{82<#Q&9vgQx(~JjU-L`I2JBM&jWZT|Wrm6>mB;5gZ6C`h)SEzbxf3a)VY#zL)hm zk_qB}((vE(K~2d083gWu0bxp22y+r4D8GHrn?X6Jv7!oM>AWayY0CK^Hy`tPKK>!! zN}ZT6I!9eYl~sl{MU``XMqaK!HB_x8=t@q@^waNXDpL6^1?Ju&%ySlJS;0!BX6YzU zkqlm^+C<bPrA#K{8Fws>3B2U!lm*tXxz-|OJ6<>SeeKs87q3n%%d*+XdEV3Ib=lYi zl#Kw`@Bzkiy%~0Pvk&2<`4dLd-wrrNW~GWTt2DRN6^A@P@TxpAqZ6yfLI>>K)jz>{ z_N5$#aWi&_*w_?OxkhQ032M{D^(ZJon@)aO@?LJd3MpLh*d?jqg}G~qqj+E~gO8e? z5{}l9B3DaFUy~N2g<%0>iMOu;ZQXVsgq>qyzJDAI46;!()Iy;=zr$Ska96S*zQOjL z^oM$S{7k~iii$@y=5oi%OpmOFVi^b+r~YNfDbx2q*Bz)XH*0}*LmV^QI%*&gp1FlZ z_hqOc+rQ880UbGjtA!(<zw`~1j|UYfIBs=O!fzPhIl|LiE=#ptj2?}ZiONNqa=^jC zRpQ$5pU+q!RYU{Eu%=6Lq?$glf%$Zkb7VC}*z~t&ZMSUh`|o8B_Njy|Qu0EV1B4DW z@n!@$IIBa#dl0L`$mJN)M?h>!kmupfi*bHKS@|7US{%07Y2IH$lL-dyHu=5lIst~B zwR!I0aLZ&Jhs)Vum)jvi7m3A+nfG8>f1?_YE4*K-jFDE*^APF|8wzQ&POOZ$pR>{_ zwc2>a8rvi45}G+;Fx78$<}0RU4Dp?eSw=2$vsnlO=9TC@6F{N^)T=T4h@LStM)pcE zwrg{t75a0^vB4HchNn}d|BtG-jH<E=yM_1Ou%$s-q`N^%x}+QF7Non8?nWBv7Le}l z?(PohMmj{ko98{}J>&c580;U8wbp&bob#HOeYf+onQ~$w;h9LZth*D&gk)zdP@)l3 zeE=As>?~L-+*LqseH0Es&t`knOzQ)(cyPQ>HK*^3haSC>vT|%f0#<b((YU}O4sdxF zubMDEf+a-Y->?*lKp*e#?;i^1IE6dJHTmDy0n~T==s-1=Irt$1mJ9(FD!5dn_bavf z9r90+D1~|#gOPGNzPqEeMUMmc)ZWv2p1NDSAvMJJ5yr8$svp>YXPyQ8HkABZ!(gCl zRQvH%ffq4zC`USGxrvd%B77-N4x2>eYd(6CNRwJhsBH<hHSc1`E%{5ty4j=b_5J76 zr0^NsvHsyc#}92Z__SZqWxzbhpAY+NaMUC3P-?><x|(Z&>YO$`!DYPm_eSJeo|m8e z`$4#I^Ewttv5Zp1>|f<PKJX`$m91oDLu=I<8ba&7#?_iJr)i25$o4kJUYgLFWr_PH z;w?n{3A19dq|?-?SD=;eTlzKDeJNI>nEr$5P)nj*Qov#jqvhxXUFHoZz*7PT?%vk} zu#tXx!0RWi^9AI=z`V^I@6~4!a5JvOD4Aa@c?`|EdwYE@PD)COB>K6u1%W92LG1Qe zqSXJ7OLT?EBr<gEjinJ*T;Xrrzwy5QN<U3dt*S-oxN33IwUQs`9W#EVl`5o}y{T zSk7g7zXdwl&fn?jJW<u3tw#sXqoU(s?rfuI3Th|j2;0j;&5C{z%LuB072l&EMwVx* zmgK$Ro%W}@G$-Vq56Hf-TAnnFx0<U&)N(FMoo_IwP*I-9YYpGjQsK1z(q3D;LOJc0 zO}Hz@&1HGWm)_>YB<(m8OTpB0Lx<&O<6fFJA9mJ9p>V()VRucrY_;PBOHg8s8F6o| z(cp$p7o8!R_C-(f;-Fa(yi8`2y!7`+j^?{A+K+NFCNvvVr5?Z5e-2>q)RY8al)|(y zTaT1>D7I^9T6Q$c&!IGwyJdY}WLCTQoy(ttQuAwPE1eDJ>qGa_ptSs3dNWPj>fky5 z^;&xCpkT%Ueq_>Fax_UHgo9;-Hc=4@;Js3O0KF1?0c1j1!0QHuGe|>vBuKh;2XK=J z#BFStrYXNiXbA(Hj6l0p81Quf9sO3`P(ARaP!ag8{}y7_X?&|)Z<S)VFpBgajl1=l zD|+M-PUSQp^=H%`Q<!p|8@j4>8ZG8<^R`RDM~@~>kJj|+=;z->>8sNbBl@8Yl#@%O z=^RTKpa7J#jKm*CpMx2tG?7yL_f{~>{P#_$zg%_{SaNVrGGA3KHw;@O+HA4W5|aw0 zx%3&=M$N%b`JviZo%Eax1vTEE<ZG~+zOPn_iySd#^FHhjtc<dlabnr(KsIfX_OSg@ zSv|KnF@XQPSbqnf>1E9CqDmwz(t;-7w|5y1D!V~8WMs?=6(8k6P>30-+0*u@WOJ(c zK}KicqL8M`NF}(MRF3Zj?zT~&8_a2kN1u!?f73)mKVB0)d5VEcz-9f9UB`PXg=S(x zJXI$t@R5zYwmIIm1aGF~2M!x3GfoH@tQ)^|Lli%x{95xK1PHxaH2HcLlA+CIq<GDK z1$0M%2J`}u88<z^#UxSZc=jUlaC0v;ZhgGVBA+5%0eIVY>_DX)AU+TdT;Cs|%@otj zF3KdG>FxZpl?F8BU-G9cS$MBED+I~KyvSir0wY{U`&u|%evYluMMYbv!~6447pdL1 zR;|(GQoM4Sy<1aUOSY+*c2*Ys&m;atgt#R1BSYHBC!G0boNT!|eP?T-mFndg?^18M z`Uh62y+NSU3Q^-PD!8nfUud>~W@PPkPcQ8@9-Od!Xe))M4a<z#t<n0+)3p21T7Hu` zqFj#KGbg-Vb+zKWym;Wsl5@L^u;ug2Y8WbM!PUv_;0S_JKed0wifmMQtew}DA`_o5 z6*I%&5?=FyG&**VoReU6N#9Q3Q#f|Eae9GU*+F7#F%3=IP!i%CksAx?)Lbyx=NxA4 zZ!F__^&qiJeHN+t7HSj8+sktUcoy>C=B)s8C)rv7(i22Lj`RiCE=x-hLqqc7k`giB zacnS;rCDV4>$zvc4-zt$XSFN@%GE;6D%3sqG<?#Li{;c_*;YIN&6NnI*9O?uWD-~& z9-dKrXE`~!DD~@Y;+J<{o{!Z3m%`HrQoxsTSd4=-->Dt{K6VD1jK=l##%++Ew;@vf zc?lN%0@FRDKEn8iNzd<G$|DP7>?}qAY1i|5V;JF-S|XjLmG<8H^VJCqIij{wHKn9h zTxOB2DzZ<Bmf4Y%oAM}~CE{zjuGR2tn?~nQ?m?(-caGvj+BZX0<&A0gFMP&+FYciq zyFpzxt1dKr;t#nHK8-^kjxK)<=ToCHPJb*WqsGgvUTdWbCY|Nct{9Zr(<UNr#Zc4r z5n6i#k~iroXNXLxy?{+=MaRz0ceIRLlAm+-DjciPusqgM*;@51eVDImd_KLn=Z!tE z=`nX4t~^SgsnYLPF$%Nn3zQ^5Pr`ANazQ_n0(#<ev9JH}W!W9D*LU%RFv~dEFEo&A zd&J3Su|Wrl52dizsFyW0-*PG|-)d@VQlZuYrbM-8J|T7VA77CWQQ+i5;A7?hoQg`o z59lM_NAq2VZU-@aX(y=Y_}Bcu-5vwLxQ+Y}f_PynW+)2M&DD)8<fNMLJx{-B{o?|a zX9tFFJKo=Mlf;!uHY!Q?i9e&-QmLvMV7rY8Nc$vUV*A|~7Q@jmGs51Xrz{6jl2|}3 zk&qG#@TF>xwI8<FitxyVN^Y&6`6iW^3ZGhgg(5;HPm?+8WJ|Lru+&$TW0>rH>Le;W zRV>gNXsIzBkgn!R=GdNoE|d>EDt+=W1*Lsc+ge)q_*J=&lzpgaB0kiY&rVr&!`KyW zUws(g;j^4J{eTg)qx;HSx!&V@={rsGy~T2zcme^{E=_gkN`B>aq7c238&<JGPV_8( za2WcTt*}%9E@0*pfi$59xY(U2ez%)Rs5M+TbcEmPx}X8Jzw1#%QD-H|LwbZr=An04 zfKXN(?EY@B8sQZ?Yzz2%QW{S4YW4uLKlq-)jt(d^1+WvA;DE28k;?A8?d~^b*1c%- zLZ#^hk!+Sg+koe*x&shsy<$$TU%;*Z+Tj;JrG|gky)3f)W8l~)%y%=*bHeR}RVKjM z-22E)ViS7vvpbh@D@cVazW7TVCRWz2?C|k7yZET@n}uYx8B~m#+IGx%1??1C{so9$ zThj(ztQs;;;Y;j&vsE?U53CV>&QHJ)hAekUkAA7zl1pgpNC{)A^4AN#QqK&Y_LmKV zp7_7Jevf2k-ETuzP=er4?0I9Tn;&bns{-|CkZl-lX#QAyy!>Z67}zaXC5-zXq zZ~V*<<Wvsvwl`yxYn=7hp-cOQkf%3mvM2L?{w()nTICr>jkaImbPi^V77ghwiMa2) zi!t*2<J>^VvhQ-$px(^005_(@)q8TjNg9^!2)l=3$38@NMAmqWNbK6FrTj+zZh?@u zycC%OltB64yIXseE2Y2q+N+_Up!5S*@ojh0Oyah(ZUSIGU>_BP&YuN%Evcag|Huuc zFy3*zh8q-qUchVP9W(6F@$r~kWb?Mma{J5k`L_1PW1=$7f0&amY#`7;JK)6t49WLw z`tG!Fb2@=<gIzhPRD4g>T7u`FoV}2iy?&c$=5cl)I%&<Dxh4kwVogWi$t$nVd3da< zm8C6FFryH;x?aJ^9+T=-*XTwf4yvi+E@?k8RFP|Goom<&X-4JHUtm!cBbfUH?f_Jr z6Kj--lZ28OU4Lrb*zDGajgtjyknyAt$Yix~CYNK@6bh#hob|Qgk5#pDOPi<8Fo2Am z-(;ODWV`w~2)FO%cBQ4Hm$GH2g_5YTJ%+@1l%NR2C_DC^1z5v1^u}Dl%*><=na#%~ zrSK>R5xq!hvdT&nolqeUT6>GDrO2ao9E2EO5GfVo;KkBaX4B|}LNhGYb=l`=@7J3A zp@;;RmaIPQ-Lz7_JKj%Xkx&BE=<P(`o0=JO!U0B%S6Nsm=v9nLEuY?*MOO`&NBI&* z#C6(XOs*(s`;L!~i#s^5nt}P#1D9|Rkcok0+cOZy2vB;m`Ve(JH%cO}>(2x9=^w4% z&mu1Koj5ae{U_!MwFTl2{>Ifr$$7@B!g}Sb=k{6RViu{w;I7Ni?93^QhVj!+uDp+G zxpbRiR&-lo7P!(ZcjROjhdye>Cy|$y*kOFAs^MIA)0n3o1T)H_+Oftx<e_GzD>(^q zRd}(={lBzoWmXwmp2<Evt$b+ufWMz!^>&}(i*IhJ3WcS2C5p#J*$_gkKGLK+3*Ca2 z<1a{U^#U%YMcT9L*3GHw!%bOXapK|x80{~{)6#4L=i7vQ`Nr{zqn73yQ~C7y09C=b z#u^UiI|sFn9NU?|axKEJD0Y__0>(TK^xS-5Hz86}z#+}kuA&0r5W#yiStXQSEhF-Q z4R8YZ>R*a&&(HL!ZiGB>W3OY;#Z4X{oG<k9crgorh;g0ei#Ta*Z4GP`qZ4`ooCnEk zea{4i5-I!)sO;J_gmAI2grEmNw*{Q_Xwq10r`_vdtPaKNyfL8fx#)yJ+XNIAr+?mO zx$WVVYSk70-$=ywUnI)(()0KN%K(rMTvG?j`4@hek*?NPF0J>=+J)w3xUno^)`5NR z)lP$)F<B-FifGItJf@U>QodV(<knmNm|Lb><hEAR?!sUZE4@`&o&9^zeo~*Ve7d^1 z{&7w%=QG1M-LILa7^LWzit4&dzo_5XJ$7q0p7^t|)KTCU<rcdeGYI1^CHGTJ7cmOa zi;;IA8jsj$I4+BZt$)iy_wo5B*ZOd)SL@``b*dWSs)_l#TB|Idk%fy(qI>l3>M43` zc?c03+z*3_SeqYXTGQhvbrwG+Yc2`~65o|=@NoT@U%OQDA6N)aSK(yhePKxVS|&@_ zHwpD{`GZ43RyzPcsTGfpa4pyQH|^b7(8jk024Ns?3d8qm3BBZ#A%*ZdPz}!gVU^VX zi~iw1E!{@1eXqTQFT`n8j+;3dKo~y2YH4eK*3m(ZyxXqzx(Fcd4L}n(coob5Rcffm z>%+d2@dZ5!3pvMrd}w7<n^36mLbA193ra*qgJfdacbuX}D3FhZ@%?%!VxT1^F9_^2 zZb_qIQ9Kq4Wik}=b%8U~ELo?){e{uexO&#h5cc4)r72V8D$jj6jTuXElq(S~%N{!g z|BrNziz{o7>?hPL@_BW3IfxS&Z{5&yMQSOL9~_OiX<VS^X1)9Evf_y>DWycJzEZa` zpJ$U+jz`bpVC_VY6RXd$aK#{6))eZVBciOzi{e5sJPFgH>%}7cMf$CqmacsScJ0VU zIC};02<}){5C*xYyjxYHX%co0Zg%+qxrGGB5GF?Gt0wOq3E!)<?kym3R{I|XY#kY< zDq$0hML-Zxiv&k^{xUrgm9i(TuC4~6g{KBE#f|(rAJY?+fbyz*Re=CIvG?s>sWd&k zPoE&Srz;`V)vN=a|LM4e{@=^>LAVzUusXRg>?wzFeXK(%<g&veY!`)H9U|k>0%qEp zi|?(L`nA4hB8CD=K%84E7DW!8njln_B&?xv`RI4)<|5M^8|B2~_0Uft;a#8B98Ty` ziAQTiWw|-MLT}376n-SePN5vG(-5{C-C)C==;rN3`Q|44Mv1RH21K}IRJmebrCeVn z`%FgALYY~$H2QZC0<V|76KJ9dJ;1yn)Tr^s&-8RgisGydyEtNTu1nR#oK8ep3Y}Os zgRjpNwVijLs;zuWsN4O(aQmK2<|ff}IK^@%Xwo`gQtP7IS@*!loepQ5$$soCPGfQi zICSaHADMV<DHA8M!dh!#L;pAy=IdL}pma!PQ$w1?dn`MCDgBSjqbV>uRwj<X!NGAq zGL8(4*c1XIHopQpo#}*dr=WMVeq-{I`tYxMV=z$(aO%Cde$L192D`&Q`1P(C8&>^^ z|6PRe@395EN`8O;cx7mgN3WWnM1;gdu~&I`p2c-Mtyh>@FZ!ueh-^iF2}<eMQHSGD z`{yCnk;!O5!U0&*Kl8^P#fjMW&d~JbBx%psv~X6ky_5?5_Rk>lk!!Ejvb#ZfGS*1N zA~y@Lb!iPpRE1~nk6K*9uWLu$xl6{b`wyo2^LaW|-p(@3SZLmlL_>>oIXbFV^T((N z<E*0b1z#<>TpcA9QQ+}246w6!1J`nXGP*wQ-ned!otphYKuA(w#-yI>o?uSwru%eE zL1&V|V_DIg&Pf<wXOe#9A<f~qpltGCO^Zj~Yot)NI%?cJUiPX32ZKq8frO0xFChj} zGj_`F%;&bG^k}3Boxt2t(LoOcQa!BzAZ&;UEu8Dmdwn@&1tt%GSwtW+zK40-(oNx- zl}9?bPxjK=)u6a2!o#Lv95zKhsN&)#z(f!QurNY!K*(#KIf?6MJd$Xz-5(j%2gpzr zH8st3I=FE3{}aOZ(gJ`a;Zj)i4@a8*?+oW#HoA2p*r!@6UW?yODD;+FVJsWevYXp= ze+xu_7{C6UsDRYo65wR&=1v~!l$@A_(N)otaw(mXe4X)$p0eD*f}dRWv3n=pt3o>X z{gXJ^?`Cl~7pN!3m3pb}T1GNk9q7DOABr>1AY9+5E&qYi|1rOwL;fRniglOFU2xmw z!ZBKSC!I7tm4lp`<`5<WoxrgbX9T3p@En1!<5sS8qY1Z6fbksL`*-GAts%msi(t5T z+|3FEhyOVh{b)ttW7zY3IM(FT=T>cW-5QpN3T?a+-#*C&+0F$@`|On%vg6-~VK8kL zyBM<L3LH3-?kTqeIiysvLRjFZ4P6y71>hO}2R**p&ftTa$q0X(x;>YUeFaXkM{YBI zhF(!k7VO^GRLzg^&gQlCNuyZ-l!wWauxCyCL^|KrocZ;(3BRrSRe0{w|L?<&1s-<P zXz5CGTw8+&%W{1Bq6crrQ<X?(CL5asUG)$!bwFKzA`ptZz8pOERIIr1{w=yvSytb) zDy)`_1mJd9#!XZSvQ`V`hZuiFm@MFsQu{NFBMSG4p;}D2A_Qu-V~Z^>GMxio?BJEl z9)7fpP|>)Rsgw{EL4kT5-pBSi;Rjh&j%seb01V!YSyG*t$?nq9QrO;}NsWS(lrT_G z@e!fNS{p1Qt5ql|4$4Chm>y<7elzIVYjbt$!8s}933ky$vjCStYULBPgjSx-bUl#9 z3>v~>Zv%ce=+<{UDW*M_0k9s_RqDV%H!wmAI+{9(NG8<cbt>ubWH%q@)~Eq{0G+vv zB@t+xL(~epSu$$0C+cGX>vlBXHUZUJOs774FXCi$D99@Y&<-nBqaq+9`6+~g_`@LA zm6b;16d&SVNp}j}`q#;hEqFjQ{;FVa{d44GpP!$<*z%1zE-o&;8sIH~%d(dO;6@3P z<r|jfXPUM3l~3Q#PPbzZGs;jCefiaM9^-&<0xxC!dF!IiD_fhMa&qda<$U`rF<+dp zvBB|zTyIU{xjm;S58Uj!KZ*)_owH%mGgxb-UONpxn7CcgdAkgV=koU8j?LVSZel=X zkw-ykI<e?tl5@!&MCjQOH5io+LptdVo}>jh#$=ia;@IzR*fKwApG;@@&CSjAM~t_7 zJ$-OKQW?$U$A?0ppEDgxORz>vyK;&D3`ZUHqqa7>AkAAZM+H#hPcoqnMc$XP=jqZj zHCjWNonRYt=-f;qQr>yU-g=Ypl~DzpqUL?IBPs9}ED3=%i~PZu_y>V(Xd+GPd&IcT zckGA)Qp%|a(W%Ydm%<y4O@pdWt8W3!WmT4gIRREY5Wo!4yN1Q3^e15j1mvIxeB1uK z930qs6~Tpv`;mLu9>%Xkp&u|lz_0j~0T9yzb879+r?%U0g7?DF1m6FyvbKZqZ4Ukq zuJzUykhFihH(i}i_<FY-|8L3K>tuECUzy-=hQmp7Z2swX=J1``K>0`;hN3@zWfB8+ zvgLd2KS$cO5zuRwlL{N5GzE$V--T-L#O8iG1>JXPiB7glZ-EiNI@?__ynC?>v$0JR zY?Mrm?dk#3tR*q$jg1iQs4?-66wuQ89d}uY$9!VvE?^$Tv(-<2r=YpYY1~N1^UOS1 zH8BDU5GN2p4+dp6I(!6)yzdS89(LjfM@M_je`N%-P4HnN;WCNJ%cEjoV90(q%V6C# z8}JdJDqqafx_Se^qkcc-2oJD^RKG}9?z%Esh7Gyr4d6^orfUvSO$>8#l-^)T6Rrvu zMtiHBcYw+)wb$%MoGt!Z0p-m}7d5WQRyP~Y@79@payCYEDGY+uD75tn(n<{i%*h*` zWL)Oo@9>y2fFSP=^e4{^y5?MyiNv4%UxWCsxXuAlU#4FmFS^(oW{h`OuMlH*zclct zKe9sG&$213P)0_^sCUD|!<Ci`<unmE1OERFk@#_uz5>32Q}qQ{Een}eQVK+MBPpJr z$nt&|{_f{NmU48ixtmWh@2WM*k~lkUC!294km<~%j41Uok%s;%RF*Hs*ZMP@rd=hj zV@F(SF5dEmK^F!=+)Bl7dGtGa_9=HAVCam;Iu^9IqJrpco-ydijheB-W^)=D<RClc z_YN~Kb?)Oy!OfzPM%i%=qmVXP0agOZf=!cvZ8nl7(5Gan`HzeXVmc0A#T;RKXwYKJ z>0IzUk^A4O6#Q}`EVpUfy-PHmi~1+vdO@OAp%>Y5-i{B*SM?lKd<E`S9jlCnFl5s{ zqC7l2{1&6evvD3{#9d*+{Nk32pwQk31{~6YQgH;Eby9(LORT373y0J|F6y*u$q{cj zf+&vrn31`@am~ic-DiRk%;SKQVWCAYDT7CE2He@Iors<+&5%@2&<L&+!GYM|m?Z0K zfu8Y>bu7%ce8aEw+pmwwK)3<yIjz9WMMXody-(U=bhpaBQ5(=MdSYh?afX&3ZT@#K z{}E;v_;pkjFlGXWE&=xjC>}No(ro<uJ<iOZw$OY+wQZX6e_0j0I)Di2ywX&$DK;_% zVUy?qYwx!DLmla~-@DPJ|3usDbDvIRGL*ofN70<GTYFM8A7rvb5UXPR!zbIB2@{dv zRq#+ueXQbq58+y$494ZO>Q3dX{~a1SmC^s~;?MCV(SdqkKHWlwE>FXe1O=AyXI<Gk zEY5zD(q}3zt(v5!y7#D5sGg>r3vC7L3@OXrLj6|dB(Bz%Aw?R^#ZV{QX+GU#t>nlV z+|kb2-voKmZ_P$<R`q7K5}hgE8>lSLdMOaM1g|j8&$1Ag{WLGg&=-E<V`Z*C9b|QR zinSIkP*qd=-33S7YwLZ5U&v8w6BZWM|ERfUU!l>2L9d<}<aUMg&Yp*o5`i%HlR80p zA0~78K!3)Q;sO;<o3@_p2S}|s*`v(j@{5U__yi{U=ZY^u{<|UdA4XL|yd6Z5NiRP% zCaQj2&|kE`CVq=#)=?q%+mCwNpABbAsp>m(!nP=_DDG<Ph3hIl__<+eWny6ch&B4P z{sit2kO9BevMunFPc%Y~1~Hw5=#4H%<u4QK9zyEQ8EzZV?#zXsDTHzZit5cj_BZxM zZOi_y%Gj$NN@eEXpg10*NHLlpH5zvBsO3m8iu<NyVj}mOB5K?Il81;l>U6O&;85RC zhy)fQRhX?;QGWgWGSU8oV^8S^Q+Pq}wCXs1_SA9L*7<O{zr^~@#II?Sr~$n)T+Z)G zxf68#aA;H{3s&yL3jMr!eK^Ak%#0nLpGPCyp}D!aIh-uA&@wPw3%hVYfgf##t9I%= z>MiHo-4EuFV$cL%<Y8_UiX2A6ObR)p{u2{pi>8O^q{ceaPn0>9F~+J?JP=IW8CS$< zN&o4qK2E!vl{^Y_AOJdetk-jMc_MN%xbrqYIO#gbhv76tGwT#*dY?UsnrWB*ft6-3 z64zFv82@n!B=fiGlR2>8eNUjSk@I;=3sbO#ISZ#99W=&7f(vH9eHxR5my~x<oDP1p z6C^PY6ZY-*r9g#(!^n3rK*vU6$st`*l|*}9O1UjmoleUl26}i3-xgM>nI4UZz62}m zk9_6vIiI;0w$pPH4Pom*GbNarTUZ#uRNy1+0CX$)_4R6R-ssOo1ehb_UR_=p9afj@ z6foMx9&_q4U+t+&s@Y`D)?N=AGM^;0g%s!45Id5hM_|x=N<h_$dGY=Ja}1ni#aC{1 zJBWf-X9f!*Bshrss=<#b)Zsj%mGU{6R3j;Sd{83pE8nA;v%C)UUN^09BQmTcwQdya z1GE*=1HS(G15asA`Q+!R1!6KJNz?BMlszH>;vpv&dGVeQJU;yZmGukD_XQt>aS+YF z2ktUi6w&j{F)RMubDx*I{(dIwU#;i4#|A#jI@y1B-Kd0Bnbe+SpJ%{$5SlLGn`1dD z*iWb!1r?q;49j?SgvYSJ&3zF*2eFUnn>mddpPW(@g=<|pNk343)C02zmzs8h4@~P% zA-?O>W4dh^vZEkh`3RWot+-^$T#{eBz|A4yqf|EHm`xW(kHBU3oc7D{f%w@$g9Il@ z*E!yYww7_jFuZv`tJE;wxKH(YgUmi@FQ}t=K1WdLJ$tF_k2l>M=3YI0@VQCxbQ9oP zXS=ygWy{>fP`g?Wso{g{10!~IsYj=$P|N*q97n!X=H_@Btz5b|-DK`P!OjkAd-#-f zl`5GV9+K-^di1*BML+6EI`3=p=JhFMy>*Q{wqDH-!|2d#OnorQmFZvNm`$MuEaUva zNcAt24I7nuCfKGa&}$l2t=T3-<QMtOwC+#;9o#!jFvRRV$s{Cor5ewdNRvK-1<Dix zIZxB3#jY8JJUW<JJ10YYzC+$*zr4F)sG!wH)R{tL|M+qE@59v%G0C66&5;2@UYEgC z4$I$S+R4D*lOgKgA-B_atf+OV)jXcE$4VB&9$Zdhuwy+w;w0}s!B2?~;tuVMm^_R_ zRJKpZqaZ3N)O8NSylaG<d#Qdlkb`+5HyqHT80etj%nTIC?_<c2P0i*T{N|wbHmiXS zL9h_I$9VfFM{tl-+ogAC&P^WT(#Bw2M{V?BGP3tsk@@S~IcZ7O_P^n3va1vY8eR*5 zCr{+NyMMzUd_<GbnKKWWDAKJj)TRO_wptIo$x3T=y%^Y~svR7R$J1FgoAxX;tNu_G zogz!0E-@XV7D#^pGc`IhJtSs&pd@+O%r*u$8rW^~JBX0A1<3JS+kxCaEHVZy(N?>0 zfY=$bO>vB6cy8Zt;;xd@o3m6QUhr~#?!d2NtfPK?$f1bHaFZ{}DNF<I(o|oS%j60= zv2!{-_Em;km0%{-d-b)csq28#roOq*+?E3?>$hUxAE|fH`Mliu2!3VzXtPQK%pxm| zMoge%Y6t#z+{!9ok3$axxOqppG6*m1w%c2h=+HPDcRc>UPLgp_e}Y{4pxNw9SZ`^F z;Yt#G(u9<gL^@XW9`Y{f$FJVaQdBR}n*z1}2w+0{=I!$N4{5SzR3;;(H2h-HYkkWt z4`cW1&*PGAK|<edhlRfK=#w*KS3OlZHl1t@?La3f#{Wi#1We@6XErS`Pf(oPrLhE# zMS1Y%)&0x6Zs6>pYgUir^F@`@g0tcdSt~LQK0|Qj-?`}=`r?%yeK*lF5{4Sxes}m= z*DIyH+|@dlvX(je58}YhQ!jPnjeK|eBs8efUyo#=RF^)Yyj`Ad!b#HEtHcSaTnWd{ zn4J+kihdqiE~xj{HAolU*JUFS-k0e8(PyLsd^N`SmGTZ85Rb&PQX^;MlS!m{R_XfC zPv2)<qs#9<>Sq7XWkiF8h&e#~=pn<)PC+ymM34i`8KdP|^RG;e6*0mMHuBO?WFduN zgBtNPNI|FQ=j@sqWgKR`Qjdqo*@9PO=CUf<%PP+diSsq3=nvEDGkd)>l1BA}_CVc{ zcV~PJr9p4Yi9HbSkddVo&V8L+Zlri`vChs;WB+yfUO=_%KEQvFBSG1L(j~xG5K2jb zCP2fh6(7m@RUN9QDU(dai;eO~2<$D6ktN{+<i_?lQ*6Z=H5>mhhU4O9oJlwS!r?3G zYXYUR@IQkl%ho`XE#T}s8FDcDW)0*Za0_P=RWPoH$Jwn%NfC*Oyh}n5<6kk}b%1^v zC)3{Qz2xv!lOrP@X+FdZcfTylYYs6xY$XH|_v0PNO7=$+<%3f4j{*;&nP(S_-_NU; zl}Y(-?@qeP$L&WQUrso-=JpL!9e+0|2E35!I`N%*5l$K3JGEYhGEIa$T-@HeaC`r4 zR8dotR##VdMKkNa@<>QeWe5UIIF9U(6swlZLGMy6=ZA&dGx`2ZGc7qDG&;e;XDg?W z|3xwM<|kO2lX|P$5S<CXfDXRCkb>i1Q-XRrY#{^q7R9wRIzu*RnGT(8)4%ti10AUz zGCY_`Oamio3H+0DvUtPv(jUNmsrvfhUNAMLrKRmw4YL=Wd_+13<I$ThFRJJS&S@Xt zH}et3nm_#-@ttqxwTKzvYwr1@wrMK(l;R11$OKh*O-)UgkH-}<nwm?Y<_z6H5|i~S zD)nu<ZmXP%guD11RHZ_UYC{Ece{g~#Nq>uG19h?ql-VyxlsC7s=~|*=v_74lTcB=R z>?lXu2kGTaA#<Q_a#K=j#@YW?n^sn$;N(>fN4zvLvComsN~G?4&{T?vCw*ALi;Iti zT!?58u2c+?zZW6-1^#Qt(q0zpRQ2#{TA3Z>Gzm<bYq=77`aG=Szl&pIT&u<gJ@&0V z<qLz^*BBkq4q`@(v37<5zdRmTnha=bMaF^#h>*~+>fHI{2cKmfk57g(=OgSSP2gyN zS6n<Rh0NY~jCQ#;wG6$96zZmxga7hPh)KL9-pxe^^Hl1ShY#U8+TK=INyptpD{I!o zaRu_71Q*(y=+0CLr%XG(Za0O66>5ddgjXrS<(HS6FG{+)WtFM+tJRd8%<3i+Oh16O zD_+~SO}D(wr7S^~8J&rSdkOH9;qQ>UB`i;zZ#OzYIRn+I4Z9bGx(wV%^*lV|-{`-H z%h^ehWt@CIy{`NIHgEni85bd7@r5^M1AL$=qYw6qlx2qX?U#P%*mWvad#r(Y@q}~! z#HS_@@!u!u`x<hi)eDAW&km`gfROn;9&(=QrGk^N2bjj1m=rMOM*ZWx=i(SC2C70o z1K=ePQHAoSLJE9oFHEo+fmxDW!195J<rsjhNTUV+-G~0cF#z&hL8pq^YyG%*;LfyD zA;6gxlJ&tU_~s<r=^A#&N}^(RmSd!-S==0CXJd03Q@-x4!4H>@X6j?72&r@rHUM(P zRxX!lHkSZj6Ph)I!5q_`YD-o|J^|TRPz%@<6f;Ig8&{no#%d}K_H~NdF)omDw}>Or zdcZCF*+Vci!-xVOV(~<-&~B={Eq$#1GX1lqMFQdnTBW6y)Y8&A2$K8?uNwS#Y?dC3 zF2t$d)rrJ=JBS3Dys_5Bh`|CKIKgG7;QR`qo{hoaWpwLF+3MDzJkx}EhP$+Mj|`9M zXa%Lp8J{jgZo?wGr9c_Ul1K-7LsCQu*hqVW)|^%e`HsJ2K2mAAdYgO0DP}OpLpR34 z+{=oJV&RBA6}<uNt&FNFHjY=Gq_8j;hbD2jdRN96^B<EN3Pa||u!)SkO*;i8wbVBs zq2i<Q;dtgVpxTU<VXN;Zl9YAyH<!rC3LglGiP6)=*1+roR@j5e$_VTmxez{Gk*(s7 zF1_`Yhxlg3W-+CKvTfHJwM?*fr0J-HT=jYn^Pwq_Da6GMvD~>z4<P9_DgqJwOX1hV zb*#o(L|0HoRu(ht<lmOMmxjgsvL$KYmG~ij26~5}LSu6v=XPSf6ZUKKxEt0wnHlhU z7Uy}@=nY8-K2F8vGOqsj{nM5J-#`CE4hopumgMLGy`r`{@5*4=_cGhqh%?*4(zGMJ zbG=fc7=K4pH-45g{fA5QotTb3h$$Gs_JDaMmM3XLgy^m8myi|%9;_(7yflQ=WnE?E zxIoi4rFlr;to;e5c*?7N$%pTrw8L`oR=!8GiY_=^m!3<dJdf}-hXz|y=2R>EU6pW? zP7|*87wC1a`Px@;zrWJbF~lI3kN(Pcb3oLK$&;3*>trwe5-=!}bU1d(U%lOlfB8ET zMrm!5Y^_Ta0TEpLtxK*ez1ZOJnx(Wh+T}6`(~ZLmG=24=_%SM)Sy;KB@4gjxU;3Wl z&7>77-Q5*GxJ^#F69J?f6H8*5IGU99QHLC9M4AH0F*-Z;SI9-X80NEBd=@T1Ovi)} zxl5jPyqG!^<Ot)8W-+JPE;6Lqwt`oh7!tlRTnrA6f&=$mpUz!{<TK5?x&ytcuj&ky z7T!!iL6a67<zLh)vceB3!`h-)GJ9^mk9nqKescWa&eTjq`L$hNz7xWu`DpBIo}<%Y z5@w~-CMtbdjV%;CeKj)cT4GX7qwj;8!a#0($_}L}cx^h)>6CZu?!JdDX;}WCwS&^w zF*9W2c0-my*VrEx=li6}fPO=Y-*evewM>&l{5$vM`G@E8@fYGMDV^8`%LOLQYNMh5 zmjDX?djOwrvT0xqlOu%rs$dx9;j&>Wjch{t963reHVXN`_nzYNoX%aRE}*kIxEKtz zQD2R`p<1&&DTn>dsER-mX2d;~WCSdWt#01EzBx^Ca%(%GH}+IL8FpYDrwNAP`0(k& zYR@h!s0ehigp*Kh#%WAWV?5Y0?0%EOvr>=G%z3MG$V+5!Fu<|x=T8wm_d{d1>#3J6 z?+yul%1pYTpcx~zLk8MD7Xr$eUowZKth9`bKT1!m>Y{?1=t~#jHcgtO^_IUW(kXDl z2V)wZ>DZxteX%a35JFKIk3>42k!ah3Qz@wDnt&S)?lK(RL}q5wxfg5NQrQW6ah5;A zPW>Yd$^hM`voF%KU9uW&7aKb~bI?}~<|wjqSFoe7{j-;7+`v_oZRP9XAo_O_VX8Nd zcJ}fYu-WJ_9RIkMxO>E*K61G`Qni0YAf=!%bB_N54-!k<t>GwTh=vn8%TYgc+g34h z>lKp!e&Y(XTFtiZRaBTkETjkQap6BI{wl#AVzYg7x~h`I1v}>ANUDV9Lu^))6$)hu z15FkofHj-!>LCYn5&P1l-T_K6X5aNB5NliLfu%1J)V-b^za_e1KR~H|L5PIDxws(S z`TkprHR3h`xjUd1)VWL3*nM02tf{l7V+VLhB}1<h-l-n<s;B^gLWMx}IQ})00IW+} zTeZWkbgNZ4LDfGF%lW~2>lF%Y=AW-kEPyabe+Du{=Dwr^4B6@oYl5(7+QlR?2?s0< zH;uFDN`7^-JSt&XY;iowH0;8dS3nr4j3(TbXC$fs=BJ-n7=*UB;jpMePYJCUyP~L6 z>abW}bqOSbLKZD~&^>Mn$XZEwb%#x{M}CJZX{w+WYQ@nv9RdR}Ja|oEvUOsgZ<^iT zzAt8~SDWX4h%zgjnwcT@xI2qK_j!5Ie~KK)E2)98X;jV!sZI7#_QfS7F$Y1#SPlD; zY4e<&K38FFre>(qSbGIrijU!OBZAh}@q3d)ZhiOsA%t%^rMgp_24eK#``GfMkVMes zboN({Dtdx-M;cYgvq4&>9K}<6)t_y~9ps1fISHcgMponamReovO20IznBeS4@|51M z=`^b_;<xEoV9$?~oVJrZVyT}OlBG1%)v1>K#>Y>qya#Rk(9Dm^$Oz(J%<=Ocfy+TY z_A7A#nYi;F^n5ET%9pt%DosdoX`>2}&-l?EsN%WskZ&YFG#h>QZ?^NEnB}UE5^ufY z9*^1Npeb_l?Jf0e{ski$e0F^BBL%;?(0rK&#X^N%>65?0h~z-~CaTbv(RocxWp(gV z%it`b_HhcW9XHlH)a<N6*+kCf|N3EDpdbE&bw8s7IcU8gn%wi}&6LJ5LHynUG;ISu z$fZ!AYWNU@<qFuj>;llGl1x%;^G%V07b0_hhoDkz5YR*bKiGZ6EB3kbGDiZ-TQBl< z9f@tmU2di44#l|@-IP#OGvb;HP!CUnjIZCWV)p!CQ~1Pxb}Cyadbh&Rr76sdq^q9$ zlTXs$N%D6Qn(a?ixqRP{Zy<hOJwA>NArPss&;jX?tW;;_<PxjTID=+=r4G(0W8$IP zVO(r_wpt0Sq)0%e&0KvpLt_FJUxbED)U1DDzpTHuMR{}2a^03}falZ73mM?C_6t}G zFV69k(v3{V0W;<E02YJzNHEk1ee>!%C7<=%`lpl@qb69Tn*8E+yzRA+AUKW6#AG^= zN}do}cMT|~zw7Lut1ftyNOK*M_x{dAT#xjrm>#_ec=$I10*8f`50lbq5op~i1hA6` zT{fM2U`<6eiiXbT+f-wuJT7X;`w-5XXzPjHQH8dL%T#r5wu4OmS!ecu4TY1Da+F-@ zRc1cnvTW^^$DOx)guqmSz>==p`|_nf{2_gO`y##AoNS*YI*LU`!miLwt$MCV<S%8= zu^I#fD_5C7Ax!C8s<<taj2^_dVB$Er1s%$TF6f>AG}(}hOh^MX>vPRg2<?Yr$nCe- z&%0HUuVsM<*q!b0_!v`5h?9e3C|*K&CmVdgqEcg~yg5y4r+&zysg!65dYqQBqD+I} zih*G!amB>xd;cpLLLpxsr;~O7&A&JW$@rRr_a)}TWQYJ3b-(|AP)xEr;-bvP6~W9~ zK8j7%a!CCrlDUv)KWz~C7hZSi`cr-yTLMAGvNagim>7r<^er@7reQJ69ij)m$M^(? zko`G0fSW?0>N@PkRljfLo+hMnHUn1Yh$uM>oDkL@Z!n!2=#CNcNXbZv{<c}RT(?P7 zjgj_8++SG@MygIZCWklR(v&Dda%ZHBcCkOl;@I&ABpO$CjBnuw@7nLY9vXa}IE%^! z$~ULJ_4OEJeWc~qbhs=SQl(!Q+*>(|Nyl^C87yK`T3Ka!4ggI`nM2}%joj3;XYj|n z&{_S!-%Ddzc8FQK=_M6DfqsJ2&y|OcHhdx)*0O0|<I&3=VVUq)?U*+GKs@Lm9!^H# z#IZC=PFoF_yE8`$L2}i#=IFJbi_KKs@e7O5+$+6*@W?dg+LpZNS-yRG`78>Ty>{2p z!VIqf_sjVBw(at_-}ZFW91ZXvL0DAK$#HZAwwpMLW~6VncVEmkun^dV*@~A&qYRt} zXq$pLE^;@(W`Bpb?(#p-i`83cP~xi@<n&*gYux+0p`Sfb+SFnTQesthM9792)XFDA zwhKUDCm$alSRr+)p<`XPfiixzDq6dIdLdAolS}3)*ehQF_V^`G9mo`c#AKG)K&FuW z2)&Pv1FYM6p7++#)5f2r^t>O8HQU^8{$ELv`&v?T#HxG{lMIYcPwfA`X4m9Z(4?(b zQW{J6v=L+Y>-}Z{H;tDNa{wqVAe*DMkTNufum~a`WZ=9v0)clP^l(EVBm$vRwZH~` ztxbrM#*#k%L<UN|{(kDD4k8oUg`E}m7^9d?%S`>=<{)c${C~Xwa;}Wmj>M`>%(+*o zO=VC$oqr)i!SC#=9jy`|X0`}~#iM<qL{t}qH}A_+d{~y|<|t232`@*!{8RG&SCb{P zJ0*<(``FXA^$9%O83d84)MBQgN%&Dxk@&6cTtKtwjE8$^m5TY?OO8%q*5Gfv!D;xj zU%WElMey@vKoSltrX)}Fc+x*3@sP&^7JR-B?w^#n*MH&bo|{NR&qW_~!wA1QMdW|f zU(lbOutV9AfEPl0L*Z5_VX|X7R0kjl{Twn?4X!ME3amIxqFTgs4oW(?91-6$lu1if z8>z3g-oBq!q;Nj!vF$8+eB)+3A^gyDMy|9lO)kvzQA);-CYhLy0`~VE9yPY}6{*fe zC4PY&%+CIBC2Z0)i&lJcc<t5!hEK($z)h-<q9FXY7lh~P%2HU6dtIp}tGe{20V(!d zB5&fa;gxX8`s`2uefU{qX#X1L6CqPLldCX~Lq9>289;)V9{VllCzIdFYeE=rTr&oF zR64ZWO@-jfyoL%HDf)r`LIn*Jm57f9=#_t*ot+g<F0L%qn2rFd@nR35{{F?+!Xp`? zEc1%`5p7FdVEHQ$q1_-O4<Gi+FL@Md-L|@D$agY2n!dc<WMjxOxcP3K^@H2*2?7f< zyGen(C*1q%%ne+P<yobej><&}6aChAFFvG*^iAH>S&uQr{Ir9j@7|s&l1K((P92uD z1dnW{Dfa%zoH}EIWd&W*<zbUu77~19IzpwxTr+%>Wa{kd)KcH0^n`UlbkpAjGmV1h zXp8R7PEe<V&>D-whH0}=X+=d+aWNgY%Sp1;%5CvU&)3?$8aZHf_hcL$6{dg!eM01d zb;7sSE2gE^U6Y~S^~n&6qn-RVxSh^eOuHV!1d`jo&Y;x!vm9ZkfKES`kSOTz9808y z(QFx9W-2>f`jvkO^Duwk10VHvB|8mupx^{818czv_a3+<$N6Y<I)uWv|9h|R=s@Og zr<zuaKRPKv&sl8Bet6_i<Ps+gzUrU*OWstJfwyqBF!zLJJS$`#sKiMd*1R+lmT8-D z#8+@0B-9ibLYUp32HyG>RMu8z7D`-yqbrCcjl}p9P|#sm$-Zr?g@IFUL;<&(x{I_M zY^gpteTkvbXZRVS)D`>SW{Izy#IF<k+Lx9O8Z^iQ2|~_q3-nm@Gwf1pzcfOv9oq=F zSKNZmtWiEH^)VKZmqD?nh4SVly*}vi($a<lKqNY`b2|ZxF2RB{>rY3(oVdIhx^>9^ zya(0S_i#4o&WE+9gcwPXK%R~daw;~r`jHV*Z1m*UqvZAY{rU|CIAg{b<N-m$;4?Qd z5q${k^yNUE<sRGzvER!!Vo=?1u%VHEx=8AB{Vp92B4QFH<pdr@4ih7Sx}rj!BlSvW zy)Pc*)Bf=C?v>GV_yg-VaX+Jc5Cw#_S{%+%Gbg~bG>kG`ThOro0iNSKGZ8|@^V0e{ z6X3w_0GJU5jsH@}QUl4sd+m+louGw6VSJ}$C)UN<;XtOo6EhJJMm@oi4(BarKjhLl zWj+rfA0ki<{t2d9a=xfMaV=A_z;DU3eQPLpx3YT2$qqvNqk0_kk>|}%m;-cG<gjs@ zD0ef$pLv^hkLUzy8b&&Jewbj9bd|F=j=1xi$@tI|+G-4&h6?RASeyzmrQ|ffCeen` zSPXIVuDoc(BFr<mESN4~^W%d!X;(p4&lT{J&q<!sO_#149m6&Y^_!3bb<lfr6T}GF zcO{Eksa%g4NZBIRS4pt$DxH~s?5?8M$_<|RD4?1})#j&knk^2oql`lVs(hz=A`!UV zg3<;{C;eEVB|<x#TsF1P|L`TCyjY)+laL6RgghP^hB;*SfUXO3HS=ig5U^c&LD!cw zpp5U<k+U<il%+G_A76or*Q^p`X~O+q(ZFR375MijdL$ZvC_c|@c-^gCPKux{eMt~k z9U^<B4_IH}XhR9+_pGr^uI#YwMf1@xi&qa;miQz)=x+8eq}&e8!p-in^rR>x(^<77 zE~QdZm}c=XE$vM6p9TkvMvuEia&q9_1e57V{#7tR#NN^&wag(M+?7VI6CK%JFLPoZ z^U@wMH5%zxjWBvH&R0S#Ibdf`D{saGXCjm=Sp+5SPkq0SY+=vw9D0ZRrl&Gx#quJ^ zP(&o4+ZbX3*~g@o*N@(}2%1DbK0a2S9K|IG9OZlSMnfKy%2lSMq?A`$s#3n;n$dby z-eNQ&$0=nX?1!vh>0ViPn-b;X_PmJDs3Sn}gG0_Ya&bZ%mjE;%%qjL#X{S2GXN-)} zV}=8G9$`<fNAH9ldg_u>PMV{4R4URqRO6(>Pc{wlA^We>+I8`_(4a-&eE6cqkWk%i zH8dvK^=~iK6s!(1BM^AwiHeLYg|_#_S|)_AZ9DsfOM`Zda1Fy37j_Yj7w%qClH_=I zj{fqo2Zd0K;6-HLw(#fQvleZ6*oD8nf#7RI(ED|a#tu&$oyZ9$Cf_IP25MI+oA)?1 z*&XR<&7p6+Qs3#~``DL3f6@Vv1)eHV1UqoeELazy)CeSOA!50hXCQGXj^zT0<5%a3 zsTx>lf3VhkhJuYv`QDb{pQi0=0FY&PWhqMkRL;NRl32>V%(=i`KMLM&X9RVouyca< zU1;KRhOt0S0)DFoJ;}E{8ANXkypIr%srZvJUgH7I!q@UJMkATI^{S9k)s1($h6$cV zR5Jr3Nz5Z=-bY)0LDZ-CS2Y%M;N!?UxjiGQA;(3$DMx|xAYv@qtq38y&ia!hlvpt# z@GDqCtXkDKETB?PmH<)GL|Sn#VQ1u#V`Mhyth8IFaYoLU*T%Lg44JQ$qtwIwDy!VS zJi-izV-go#U^R)rf|b~<#QMz%nbRIp|9F&q&=?WdLVEFM%l*{U_d(;eo&p}Xr4Wlb zly18|FD<Rqo!wo)=r0Tl8+(LgnAiR?Zx7)6XYQd$ID>@WT2<Fw23Cybj^<J`jr9+L z>Zh&Syc}9@zguniz@9BANGp3pU!&!BVslpN%qF96mRMQ&yhBAhry(2Q`J|akfyX~k z+5FXFN2D-2`NA%&xIV2gT{iiqw{WP^eN2)d#&|cs+J0B5$>N_C?f6g(@(FR~Cht%R zZpWnEVf-zX*55*bOHPBg?lXwZ`6UW2M5S@oPiWiUa)g(w%3KBiUd-qv!})+$bE5`5 z?K%)WO>#a%Eu%)RbLPZ(26+jm?b24qFCMr1;2CtJq!B>^+oBy2S67=+gdYACTES38 zs9i?}#o-aMe)^+hicyUPQ#<5uM&w^Ly}y@bqOUmCGUCN~h-b$(_?m41!|02|;p|!M z%{W$aC*rCUh@V#*?4+ia2tGjIQ=6})*9M9Z_1k;xb0BbRveI-W%{tYM9h(ijBKaeN z&ohFNiM_x7r&mdM?Nt^H$gys?`VC0o{Hxk(*qv@i+JFAqeaL{@SsdayJR2bD6sdK9 zEkJ5ocFsqi4cw%I5WqT<&=E|ycUQh0@?zs_uowH#(BH_q>g6Pm#rSxnMZm<uBfZkM ze4xht84{O~N#kgWGw8ga1C3E3opEHNB((VvAG_9sBJSc(UAD~0R6ls-9X>w3_vd>e zu*J)6nm_OS_!zf&#>SkRJ~C2ndVZeH+1XhMy0A=L94*69nyxUPu7YHC_AolrG=~E~ zPDy|NdeSm6y|H78I|LvybMvB6rZh(58q=SvAKfm8sBd-zrU12D0`Ive?|8#dwebiA zFYgL4Me<JA(e<|TYjb;(Dk?!)tokis?DkEi|CvHery}d=rztCcaf)8y&372`SsSww zk~NC4`?{hUM_hP4_WKF_z2##z7TuS47u@`Ais9AD#%q@k4k)*%S=vezGpsWdcoEoK z6Kj4?u}A{H3Z$^##!?nUuMVfA3&5lBc)%#3aIUUwVH+%LSri=~B(;-R2W24H6l^mf zzO;X^m8|>&oosiEMelMz`A)ADvBCZVb=H3K!J7D1;#?wJKuY4*UXONX{~Wz>U+a<* zB<W;HBf*z(H{;HmG0v^vsCN=|c|c65^B<E#Y+&tISwDIeKQp9*f&walm3OZIAJ98} z)M%jghuEgs>!^(mA;OqBcJZT&_buxqrk4O9sv}3$Tj1?|fHE~hYJJzgKNCJ&y47wU ze|c&4{1fqofHABu<v$SQ@D&895)p6Y6@4t~96U9W9$8R3Id;Dg{b{vkFLX;J0NxB_ zcWOg!@AlH;fH_N9*pf~}>V=i;QI^6E=<Z~~5wcWyH_K(#qrh*sJ%WcMz~%*;)O@n< znTx`Na%&$Xvv$HBTY@v17^%*jl#iVcP?=PRg9*Lvb+oMpv_7zs?`i47O|bdQ4rP5; zS7zwj+W4Mkr<<y)m4N!Ya9>J;f%78=KwbVS$gh(DG1ayOvE?|5iwE!Q><o^kuo?gR z`%rB$N2l5D*);mbX?$#K4*TAzYpl-{P4I8!-|Jt3bJu|Q4sG))BQ<3A!nj}|V|&2m zjS!(WZ%cl0JwoN5%2qv9n38*<Ln7ZgOUUite0-Z>>?5p@e_s#wIKG<iI|l9&C9)6@ zHN2&A!3)-38x}*P)yRusXk&%*2)6Aeg92oA&>|ZjhZshcqA=Q*frFrdGJ7T1$A48- z2S1-8vz3JEJjLy7Uwqlw8;1V%s9p|RO5|79i#0<K?AGMz&0g?*6LPM3B<_6;|AK3e zVODY(A)b3au#@h_g%O<#zM%KzCvYB=`2QA?#w%jR7RqDAjM*p!*;HB46Tc4;*MDDl z?$Q<1Y$q(}ltjE}xb^(!iBH?{3UYP+C=+7$EkHUmjS2_ejEA(w`GBS$FDP4m%o%6W zsmmJVo(|Vr%n21&1PU7XZjH+S%2-@&@Y&Vh^}BZUFCU}TZaF(1<Q_&k{*Fw=X{7)6 zgLrA6&gJ!Ngbx;Zi}xWG=esUUfJ5zVsg<B&SX)-fj*21NDigKTCo^ILMG36mijflB zu(F6L7fSq}Ns*O$TlI-ivk5ny%1EcluI))e4T#+Tcr&DLOMk`tNpSEJ*+ALwWH&XD zog}DIPp8c)$4aM0XD<myQcpTAkE{i*NNZXHJ6dV&qEOgZry24q$dOox?asbnMO2HE zIK=sC3RwR0V$wWUK#5eA-O}9bRFw=fZikHu|Njy7mSIu$U)S&q-Jo<M-Q6*ibSX-W zq%=r(H;5qJ-Jx`MN;gQiba&T#a{cf3c)l~A_|2ZZVy~5wg0dTwNCZDu*jQ4cxPF!+ z6?Q4Fu~_r9V9_cgaF7Jnr=qR&OZ^~Kq%b8A^&Yio)2=f(G&GINH+5=E>oj|YYMh&T zWX&wiKeg}<$O}zeMvNS+{Pc5j0TzE<t~iO!cRYLDkdUNPbRWr>+>m>$m{R+oB7FVS z!JfT?M*YC`GL?5(@`-eEY6UcP;@oKWB;WYdt5oIsEIcqafN_#rD)m#>c4>juouUuo z-u8iPG*nDq41u4oh+z9v%vQ{uJ><)x&=%a%IXS;NlT8|bKx<ZI5==X~_+x~v-ybX= z#8Xip{H68x#Co8S1ltLv&tVcEB6MkeBmHE?H(_sLu$v67muZgBois~gvK{=f+((D^ zV&cI=t}tdViv*0btOXsKD1V;s_|+}`C)-k}1L(;8fFpYO2q0P2X*@XLSu<g5(sgkW z0M_;4e9xLE83z9sFmO=ShA01{RZnH;Ps?BK%g^zFx)#HP(^4BR1<1?YSY-0(TVO2$ z@HpY?$mx~|rAM$;&qgB|LBP(dtX6Su@mADbdTRPSTmN;{)ZZLrRz;YR0$=peoQ2$M zU`q^p>Vu(D7=a5r`o{9KBpLK`Q<tp)#LFZ};b0PzO(EA1LP@`vNRC~K$!v*qnhX-e zEf{484N&E6{lHt&CO=cIO>aC^MGdP)$KmA2(PA2_T}|Mji+Din`>+oZtA+}|&ehh| z$t#U82<Ymt01zqW{@xw53w5ehI>601BDF$!QjPhf-<bBRLx6Z@L@FqV6Sl+hI&Hqo zD|HXrjAbC)I%IWxM*@o#QX9v{WDf5I_#BgY0DUf*iw-R_`LFqVq|oF2xvn1G`_&M5 zWKxl*S2TF&l`F3+<gRLwDwL1PnrP0ZxU<ceyxlER<K4Z$zHKPGgUUh~ca+xDy>7?o z?11wW#5$mv7R5bBr^8~p`FCPAKfeq*Xo@%ZlX!MugwHxbjY&g(;PNdqjBL+mvEp1@ zlESi3vOkU4GglS;<bgc56nNiG2P?=lcU|;3;P9m!9oG)O#a`E8m<umb{$xo)oWq8& z*dXB1bT~+;V1=m_VJcsl{cQC6ax?9c^HbC}LJ`~QBY{}+br3Z4ecF7h?Z_%kI*TT~ zy?#BM&==#07W+o43))7)l|Vc;ZQmZU#+dM)ck2PMv$$_Ds)#@7H@s50>p$kIfkC4E z9cBk+_Z?`r3YDR2@)cQ2eO|lX>odO6-tkWj3u-iTt?^0J`SYuk9{4S50KF>6zM2Vt z|Kn%EvO5ynAC2!8X4`m8KEApgwoaapPkI$ik^i?I?Y^!@u2k>8!sw$|OoonRMrK&Y zh<;mSY6cT8>hB3gMIq#xg3L0fX)wj*WxgOKxeB`Ol|cl`F~m4nFe6<(LjyBfZpIW< z^oMI85jSU;deBEr{K0bsfaWD@*@h8Bpr&JyD@)O1O`YvDzG~T1O^OZ_@#T@D|5UV$ z<WMmQpLr4L%0iA6InGLe_DkrfH&|N0X&*=b!W_Q+s=^rpf2ti&ao^-_4)IWLYHqIS z`tygnmPkoiE}>;fIGMvMSmJBbL<a1xuwkzv11m#<Rz4Rli9@+*({u!64uLGU^>7VU zAwzNFLa3C=O-FB;%W7kSW)BrEA6b1oo^BsUf?N>8Hp0ut1F`aiZQ1sBEmO}$AurA4 z`k=kt#Jb&aGHJ+I=fejqo2%~;7Y6Ix-IU$y5QE!&d|nV|*DpTf3UbrTn!lNGe9l&} z)jWOr=&Nkirt$sLmg)mQTswXJl6_d**zXUCJIK2ip9LEt`SEE<JfA48$!+2@j;;6h z4`X=6c8+zIT%3e%(Ll>nQ$agq0I-j$8P?XSvPdtPuaHU+gtgCUQxd#~@g0R5yT#~! zON33lDzbomvG(1zbFiN(cYt+Tro}?is&feGsmnn;Y{gKQnoj>^C!5T?s5nk?v0h$7 zveerKk0ptY&w7Ene#x`CM5kG~U@VEX*!(ivm$Jfw3cMOJ(H4^U%R~Km^P}%Y_lt>2 zhWZzUS7@F?zXs-R-5Pfobx@P6`aJ@=d{0Pj*%x2fVFenu%mF#dqtu<OuLk^Qhedw1 z!y@yL%n-ajM^&8LxjqQ3C;<KY4vzt^lGfdIeY+Lg-`>xIuamCh{i`vhmz?xpwzqF@ zFovdR_tEtf+ll{`Ll|7JsxdnUE_)~_#g{gYd^VaTONYo`vG2VXtzLbuSO+&mtekA_ zHt~EIH<gms?bgp+;*f`A+olHnzJqM?C%Z2d=lFg+MxH6&=HLRZ!e*}}zJiooMBciH zc!M%;2j6nLC=$;Pt4nr1Bn4CGkBlHGGlx%HAYFyI+Usa4nLrOgjqd%%{3~XC>kls^ zTd2q|Yo9d*Oij0t{5Y2_9LP;2b{ZXq*3~^Sn6;|l#*AYSG}<RZX6Xfo5AaEy+isX6 z>4wIiLzuG!qz^gYIcnIn13K|wXD(0G$xlO7xBC)k3qlg@fS)v9fi94L%&Eh)L=ZIX z<kv0gwNP}rF7O!jo+gyEy6F3E%P&mMW~LojGPAqzAY`pj@#KV1k<oSc=KESN)w_E# zbBmJr=Qh?ua2n8rIkxOF#8SV)7{)>C=62Uqzet|z_L-~iDFKvnJnsXX_YAEpdraKW z!jGnFQgjLspRBDoTh8jS-K^XgXtkLHkd#{{c!gNzl{J#K6Kjt9J469&so!4iMSr~H zDV~VTzj$x+?yoyWMXc6R9X*6*^-acoUMBllo5plQVy4w3OV=f7{O2i-9kJl6l;Edn zzHf8%E^SB3^R~-Qs(vSy*47bty>13<q-;k7%GZ+uGXI_;RIa_X9$vxR`Rh}w*ANJU z|2alV90xGbY>~I>VGLL@e)Vymho-lZIjt7BIq;Y4;HoG->HE{`gWUbm0(rWDaaYC+ ze|x?`MSl1FwJLzHdV~D7194OIc|-JX`fySxi{F218TJQYEhF~%fOI7&mW7!iOvTo5 zbxo!z%I9(oOYsI$2qFU$tdF6Ry(#K5&G3vKC2-+yz{Xvp$|*zP-Jo{k5{o<vEff7k z6qH#aJqGucJ<V12&wz+ebu6~gdRnsm1G>i0FL*8*K2m*x;+n|eKw?bOyQ;_=zh_@) zNL^j6JX49(-M{j-=WuA0J)0?1WNO-pj`{tbw-89PhlxdktAfqJTDfd<W9z+vcy?Tz zVo3glB50?Pvph#)ySvr-<Ht8kHih(A%76=@HPig#UZk1XnaSn(7Fv7NlC0aIaDLa^ z`Hx6YN!Aykrmf55YWv+=^Lei#s4}l(QIBO5JG__<i{A82m8FeQv_Bp`5#1`jkCWX! zA<EZuj>?bVkM$`u+sT+8WfTdKOkYU8_Gxyhk}CJouz*qDoKG3c-7zPBQ3cJ-u{PJW z(=Y0|lZ7E-7_N(PaqUa^0?j1-N_@-Iv)T@&5rj8O+Y<iYtv|-A#C#EL$0XsWz<m4% zc027v<nTG5{_dtQWc<|if#=6w=Gg~ckBF9s)|-Lm6^BekrW6Jq(*>ikYzqNRC;61M z9O6-&3Ridsm@pO`gAl}kc6>uvvqWKMF#&S6k<MOw2%Nzi(Y$u>p4@;cG=^meFHWwf z1V%v3O-$GO+I+s+GBK^oKPDrCOs@N;5e-$kto_-2-hPUu%KAx=nKC)M><C><FC{vK zG5?&8>b~Cd*X7Xtt7|S%=9TrnGZden;C(p%Y+d_0uLMw*<Ut~QRA^Zv@HYZ)wSZOa z9S%E4<Xvzl(d&maa$p`2(bmslF@@PpZtnhF&9*<+LM?wL&;8$G*ZCiogq3LN=K3DV zb<}!3`Al{G7~y6AHmcGF=^>@-o^Uh;oYP_2Z-e{(PTSKt#Gc`weEw$$w_wE0@D4`P z+O(>6Z;~f7m97Bf<kYe#Z@n|y^v|RYXTvns+S{8tHB=d~Nor7`QNushfw8#Mnev2# z8Roc2i9>kwFpaOQkxlL%Ybk{4BP^vXlM^f1Wf&@-Gd~P%1G0zH?`0SyCBE-~Rb&!T zOa!#1_<dNUqX7kinGhWW8gc*~F>Zso$1>S)aW8NoToCkQk125VZ51VQ$7r&7NE22x zx|b~`e$E(DS2)q(?@NtonAogxzAUUJgbTy5Xd(ekFKG*+lzb+TCNk4q=letCk?RfH z`${;y9|#X*y8#N4Slu$h=DuUyh-nZH#Wa)1`*GPsNLq-Sj+QsHsnOpENu|r*_j3s^ zeZO+_))tphPnQ5E{)4c}kYeI<hVuN`zVj;?V!Zg9t=s>p;qvl&mmoeqeqeiqZ}6yo znfA9v`OH&;fOYKpX@}+QqXGZU$q-vB^Xk*Fi}6F<kv$&XLjZw;H}TsuQu#a@2ds^7 z@Va&@P+&ihX#n0x`CZ=IqLk=<i0Zxhhfu&a4~mgl6kV<3)g*H(2D1$p?0>wp{N~QP z+0L|x+$a;~D!6wv=yWQQY;b)%cqlB-wwQ9P?XLsAM8gUH?{WTNc&I3LJROyX;I%$m zVEn@`nrh>%F4aTC47TJsq37Y^*zsK{xAV}|p$rj^Eu5wODI!5pHWfT(ZF-lMBdh%~ zFQDeCDe6yiV`PolW^;6}$tDRNS2FIJ%Xs9S(Q!!aGB6Z-Wx#Fi>;Hf-W<EZ?A;45w zPUK6L*;xTq0@K?n*wt!(%E%@wy*&|i=(L*Z!bwVcwHGICd@bb!xLv0A;A1U0h24jf zcAsIgi=G9;NRi;g9bChLTDboawAB9*v}Rc2wx*jae&WT#)68F7NTSlq$-%<=i4^Nx zC7FJiaT{+WnC?DmZcboyOQflEBPVJnp++k0OmY4_qggU{PYiV<CArvokBi6PJV*NR zZ{t<N`*4HoB22vT{oqu!E*LIk_E)tXHSKA^7}tI(Sgk6nM0PT(AEa%8XnTEgW7xjU zRps;Io%WEoe`{yX?;wqoCZxZvXePrhSCI~5*5jo=H8p9lm-a3w2sAp^?$+raWV_x% zFz)6TmoVa=x}ps~l}wl%lZN7z+bj!fg_MgoHnDD29fJEy#c_$9hqX7?;R9*$`)JMu z+(2AM%<Ee#i=#N$KINe{D!R@7fvF_6*0&&E4B7(vKn!%opx{gG#*VkhPe?kRjV<>M zuD>NWit#fuVEY`i`7>#l_039n2(bnE_QI7Xxy}6ae6u!a01GwB!3?FA%e1hTuuDPQ z=9VmLmudx4c5CaW2z9df(lML(K5`$$zL#%9TIuyQs(P@uo<M?ZzZGaAaME+0n}mN~ zGk9@_SQw&FN8^ssc%o&b`*BfF6C(OJ-hHlj`r`tyh8u85wG+RCG1FY1B6UHzX2?Vj zdm8h)zBn&)S!I&4B^|38t-F%7GE61;U7R=@p)RL}-bObk*(SqI*T4_n!Ysynw00n~ z{r;%ApT*(#qg#bfy=lJ(x%bf;#!P^<P~qh5sl#nG>c3$M7*i)m(9oFaN@pLecI6b4 zgu8tVPNza=*SSEIg?Z!W;o;bv7RP}?V$aL=eE8!AJr7Cmi`3PA@(ed=<-dP-ZNxd5 z*G)Tuo|~KwHp`liV~DLA#D9c^;m&xJew_G@XaIwmIin{ttp6q!3cevdt+rKl=^?TK zCh$g<Z@usKsw8rjUJ1(JwR*KLSx9yTqY_wc%>=Y-DG8_*VU21(-MT9W3{Vt>U)6{f zj!Ha$>FdBP0O=`u?|;gSA%9#$G`w!9sQ3Ux_3HtcaaGs^s_-gjzg||nuB;JodOnD@ zbyr<Zs|S6Xjie9xZ$ZUOVx~0}@PAt~{m3QphA>uLqi`S2mqZ!X&f-FB`<_l1F_t~B zde6V!rTJjsVi6NA-i~9l(8wrDw*<!a6r)=6b_CJDNYEN!uHpC=234aPZ~W@vG=$dG z7@n+t>NA+vc90nTJ3UAUGWBnC*A$vWNqG>dZZq=zg>$%`hVnfPV@;U!xTev^!A;=C zCfMm>SgXRFHSTN2V9r0v<OESAuv3z6<&#-N#sUOkO`jk$7n4^A?jUE`PKK_L;AVkj zI+n;TZ33qbo)7N4wbg5KVxERhWqJs;{5bk4BvS6HNd{V;iL9wUmUUumAYQraM$Sm~ zq8(z^9l<{J)hu|u3!&g59do1SLpt=mCQ6-5WE8LG*G11VSpmf!!6-vpkh%U5nrjMD zN22eUa)<nn_r8A)!>NnuQ{N5YjmA#{N312(m;eVugKJFLNcQA=1T<n)S>7n$9p$#i z<LkPDFS2x5mXXu&JB$UzHxgLB{3S~9a4pQw&mP^CZmQ~i_;j`A`%nMDlbCQ+>id(Q z_wvIXrq6A_V_*AA$l*%$mu1Cf=ZBO0BFj#p<Cag5i{$nwprKI?VOvz;C}_*a!!$C@ z+#=eLPI<tf;a^#aRWz4Ql8|P94n~;(`C7u_69!eRgvREK-DVKqeK9EIf;ErYl<K@u zYfZe)GBp!cb`RS!&|}=KOA1TdGmFKk@h=BV44R#8)(f&E7%;lU?ZLYNhK$b%V<Gka z^Hcd|&|sr{1Kkej%E&&PE6i?ucL?T?LInX`;7X-o2ynf&-?Fyi1JG>a0(QVszx%xI zRuM%uB)<ZxAs=Dd*l&Y9esDFuoO3q^UMBu0FlN>h**KdTdVk%F0Dd(J)6grqE#?O} z&u3x89z<Su7WnU`a!P(fMF2`0qOVgk{_jbfp}Z2_alj34YA($LKlI-7;LB>vnAfqx z{~Psi|BZTjdj2jMNsmn7X0LYPcFWAa^^S6&<N5%&P%+lWNFzD~c!vHTH|I;@IdjmZ zSXlk%3=PEZ!blDrtV<WovUB||f7GZ7F>8s}ekA=6`#T*byemoRYxI`FSX|m4$CyrT zY@SP1&q;IN!fa2Q%?dQyDzaQ6u2Jbg;{mlGs>|+={TK6=fr&+JS8=G1aH??Va*xZX zH=K|Y`$2GLdvkaHhPr{3mK#!^Np)kMM_}Q~F-TbK8SD|{4{`=K%#aauWXx7n6oaEg zoiCF}ehWrCl|>G{Nt@%<=3RCV*&Iz_a;o@Y{p}!z6%HdSeK0B4mx9Oflzh!C%Hann zMaWvHBm^wpl4?AR=NI%XTBt^`RSE^K&x6!+Ti@AbOQiD#E1L?BY`oUK3smAZktpu# zIPS=hu1kMVuKNz?SBP^1{k+qv@RWA0qu2hyYmPMDD(QT(QVO~c+Q|%YIz5;~V{(m< zLEZ+7GmBrA*022fu<!+<9dY(mZ*1q(YCS$AQ^pgB)cllqUfy>9OE~h`YOzk~hNIA9 zZ<mVDdTY=Afy(FF)PAfLuo!Mz=wPHHI<|o%T=dDf)8er;^C}+Z2n9YbzO(WY--F#H z6`MvLD86N-Ihdn=+~PMt!)3&VIG`nCkZV)_Lr6w9X-=N0?HAu27m-1GFK|xWb9zeu zZxq&S82&mF^=-^uYhb*m=#HdVN0PD0I=7a#F0F=I+@OSJugcJvzp)3=8dsLUpshp9 z)>l7eTdc58`#~P2K=o|c`O?hJCXeRNDJ~d2!O>(cnVB*jrZ3h@l*HwWMIOO`hdJB~ zeWS1E3NU6o=5EK%IlX1-I3tB@$2JJfIKI*f!zfr{GR<q=d;B2BFyJ8zqp7DeV5#dX zOdxA%M3cSpu0qT|my=+y<pHS>+k(G@1K3vp<J&3Q%Q@Tm-IxzqFN3Q&U#}hDL>K@j zgyg{JC>d40)c;JcZLh2t{ZpsGE?zx0v6u#*WB-Yst6JTOkK}@TL#7Q85P-y8iYw{M zF8Eu5876yOhS?XRl2r-kscJb$NDK$F<_{{dZmbXO97*ytzh<6uY>h9%ANwej(@-PS zgh<Ekbe!F|l?13jolPP4XVMsbo5BJm|9CC~)ptv1i5*>y4SAX@=?bPJlTT?wLqqv3 zIV5XtO-3D3Y&U~6X*nkAyxF6udyyxzuG)8|kiJm7$|I1u)0I;NTzT|9Hr?hpG2+%$ z0OA%sg`=cbKwj6rN6$Xbd3L0?wuw4(4EEKCJ=%C>U9p;=`&taKF}g$`oaTg3|8S+O z>Qn0$7nUJ?M61)qz)#K1WS8f0oHqtM{6z0LeedQGP2GN;!FwwEr~AAGIr?Zc*MNwe z>v<I1_Rt!4(rprzh|A!^S)*#=sDs8Ps+Y&~)a8a;pxxM`kgLC++_%WqmTpqj$F{b% z&J*feq^z6vXtk<MevT8Ruw~XLt@9pi1m2!*C<6ALQHThv<qFVhTD(>5LfkLV8SBsh zTCsuLW@8RMoOraCXtrrTl%|8}$pSSUrB*Q&c1JN9Hp*<`tPmpx52ml|>#8*b*GHQX z;RN3akWNv+wzFw}?-0IKmT-h0y7xPYh#;3v)kr(;P<zLX&u1G)g(xG0meu|7IJmJ^ zm?bRi%^W^ASky)?^j2xN?{?iyTD*)og-<5oUz$+$hk|Zm2}b6ePp8CD;b<6;2SKem z*zb;0dI2Z>Gp2wr((d)%`DlU0w36R%_1$P#Jd?(L7u;wV5Zlzr<CEQFk;&YS+}7vZ z+JIvFnERsG(vbhf{FU;b#$C%CA``n9OH2F(a}H=x#gN09cH|OY#BumrzT6|Qh#%r$ zigVAa%j!oa4DEjo32yl3hxV5*5WqIEKQvvrP-iwl>9(64aw)Z_Y0k0&%zxlYP!(8M zq!}<dvHuvNGOOQ^m&X?BEifSdw#X~Ye30mth-PiT#e%5~^ut_JAwZIf$8Cd0QI*|< z@q{J@xp_$-+TP+YEcTquhul3vG+LM@J~X&>W7CgI=)jGw!1L)nTDFf9G<?PL9f;Gf zN2}pu^tBeyf!AoEbZZm<S%b!GGj%?Gd>4k#7A&-!uliA!Rm!$B{aA;EY29DSxJY3^ zCeVCQt2QG%ZLSYglKy28TG_|4L<?J^uAy<TGV5`D9L=}lD%tRI23q?XHIBswQBWA# zJn}iGvtJE)RxC&yiNCKVRfPw6ZT>Mog!fcmXl~Bu!wQ@MjaRq^P1#SUS40tD4X(|n zzcD8G*T*HS#mzFsK0`VzW3@aT)HunFiy+RTzOGIv^vO_f_1!?Q%~9_Z-R9}Dh^kAA zs(m%*CKb7Rd&|R$htb|Ycxz#;0;X?&RN79xj%sHs*e*BDA68#<BS-`&OG`_e^!RUX zw#hnP@BgPM-C3{Y3wXFidT#|pzIG<^`(I4d`)%x$ELi^ax_lMT7VB*igM$&$>m3Po zP!7DWYca}p_Ws4PdyPz6Zldbl`nPP&)XS<ntH-GyyZCK68=iG02b=gF%DW+x;qH11 z@o>p3G<oDTl6n+i>KAVPQDjz@orXuL`LQiQK-Ba5ic4YoY$Nfz0m)wfoIC0+L8HW~ zIc7n(xdh*%+@w9Izmk~e_zX2<HQ?{2_(1UdgJQ#oZk>Jva|wN{R?pO0E6BQ@E2h$g zKT#1VfX9j~{zyK@{X#4yq6bu@0|z`ZK?$356=ne!{%1>EX?W*0J`amqO$TLDJ;<H{ z17~YKkE;<(3gD-s$wn{Zt3`Lq*DXZ`{OmRa|KYJZ1a!Z(0d~79(Gt@maVo|6wAO_? zr|l{+qmhRU=6*T0VQpa3mK-ub(Z+ZcH8A2!G5)94fA9m!XXK9C9<03>1k`6PFCAY= zWtPm_KXkqF7H9t<Q{dp`dXkS)Xw?hRcE;s;C=3x4=1d+nWeUv?c@PaiKo4Tk@FJsD zKg36m@k(odYtNA`grrA#GM9TIr%mE?_F;JEBfX?Wn54oFoVD0L+Eg8!v~waLu`^Mp zR`j(%yez1Mg6WPteLNEW(g5JzZmozYElo(<q*yvl7|aQ}Fj{Xx>Bu2O$*tzBrbhP} z&F_MDVJtDmo|nei@gTOSI<VnSbFYw}&E#hxNu<CW@oc`OjI9WtX<gcJ9m^20n3g-x z?2>6)Ga};4DC+|@z_>`DCBz>8IU*`nkHU1Zd24u{SHP)2PW;kbtly+z>$n#(_;k7{ z%nx^pgrxM_Y+prl*Dw1ARt$YkoCY4z0gIt+aP1`_!QK*8wb8L|254MXK?7P}*fm&7 z&!%94yQQEs{bv5Ti#%`t>WLHSW|B!mn+Axk@YvG1S)$6fO;!HAx;`~xiBn2gn$gI| z?d1M1l<qSh%!9R<OPeDj8=J<OY?v9eV^ShxrefvSttzJA;{KvY3#)`NEBjo#PrGgV zj%lWsIzI8`<z>;3YZFfU^@8PA-J<oBz}cK(<Ny$%wdM@bgFLQd4mGbnSuby-d1PHY z{8`VUaq#eH=H}s%qha3Eo4@|ma(m8eZfzc4-}XdWWpV^Kc!Kh6bzqq{h4NeJ#NE$` zyG6QTNf>atu(2hsb_h=2yo4ZRUfHHS?YVFD{s@&TcWc7Q4$nsQ1}9;KDLE?Z>*oEd zr(ojwhyqFxVA~!|DO<hUlX8oJO<XiLX8;|CqGWPVeZctA729r52DC^;Wa77|1}rQ< z;U`o?pK73z*lSZnDILkEZ=d19cw2b5^;F%N+^)^?I1JNB0sC`AZKlwt-=;Ifud53Q zV01E?z#A_IiM1Pn?(Jn??{w^Nf!m!Cco*$t=5ZVX7vQ-^K459W{-@D>FYg$LrQ0Gs zaSdtn4bP<Hjl&~*c|Lh5W79S?JRJDngnx~Pk-pp+I<Zb7C$rg)GIb`8dlla2>mYl? zAT&xO7;j`re0Wh;xVf-ku}#4V#O#kqU~~3^?|GqZH)3>nO|GDg@(JTe$8xm@oMI3W z3rZ&wV$M7tVpb)52{lNO2K`c&x8vgQpSD)5Z5?wbSp}B?a&l%OQ}SGW`~$xXdGt)~ zVH85yiENN5`sD8rwa;%GrLDd>q9<Oj1YnkP>3uyPyVraChcB@PT!9)a4<)eiyRo;b z40-tfW%*(q^K#V=VdJx&XWrY>sz?htFxG6W>lm!9t0bgo(o#;djtk!AHTOf^jv&)K zkVWZ*;Vp7(N8@l5))y?RDnf3=9vjGG6nyuAZtAx$Blij6SS)Ki7$1y_ZP1+g{ee21 z?=~mTa0$hs%2;<#u|bVBx@^Y8ZGFCQ(<Sm*uUNzjTE#hK41_A4C#(2Cd2QOn?W-I< zWE_;QtOY!63utp$NQ$YHHrQ|)9Iv!`Our4i$J#8@ZG~DlZsE<>n8$wp{27TQEH>60 z-P;^<ReeUnshDh0p<mHuqWxj{#OPqA)MCJkNbocXfoUxW6<;D`#D3!^-=KZN9IY83 z`}?q}K<@Iccd9d=O0<89WFgNNdtrs~HSlL|eG5AddK_db?>`pts8n%{PlM>t1&yyM z=-vJJ)~mf2gnHr5EYGh7bjKZhsnB!fTY5swqm!lE(@YXu+ywmcC5Fw|SyY&Ht3Rt# zBfr}t?!YT|nJ$|ykG<>tSN}uA<<w2AeP`gd%QTNhUf2ECa8d%3gDK8H$(|G^HtU=M zD93@Fe3Bh9P@#F#?#&!fdKNO3uo^PA3P-LoH{64qh8p-#^P}Lmvpe3J@aqsjN4}jc z*DLps`lKQ{<|*KuzJT)nr9km019}3y<~7w<edh(u+S)q7s;@HX@^DGvV|@H8dfmzH zKo6ob-e}=oguNH8siH3EaAgG49pUNvCO{eb@22T_BRGK#uq>StFz7hi*;PL7%814^ zA2%JeOn#yK-)}?6>)6jZ$MXFw<MROn7D=SitDU5%B)Tjk3vK+KFty5E7S&M-CQy<i zK{vWXhfsJ>Tr*d$+x(HsFCsvY1e@&m-^sEXRqrhU4^7IzEs}39HlsZ|c74T?VxIA) zSmvfF^&pSNNFvLDdVfwMO-wq%hoa@bqamPZrbs2CN?mFffh0!zK030t_G9x$hyZUJ z3v2~~^|x<0P^;%{zV_%uJ#Q^Nvvc@DSnIO%X9yG17a(d<_wLdZLiJm{v;)#u>>FLt zD=zS4yR|$&O^03)4AEN5^N^V3sVxpW<{d8AWp+n`L*y7^$L~1(i;~hUF;^7Z!9@Hf z2J2&=V#rvJ!}B0kNjf%irXM>tDAPbCH6@K=zT#^-0{(=QfPkv{=kTWt)4}jA^B9cZ zGf%b*&tZCxpPzk2BEPKF#U!-eEW2cSuECOzi@0-D*7IP_^al_L4DEi`C3<t8B^`LP zB$I62XfCB(wCE>NV}7MLdla}7*hI|x4eR-7SM`hJgWdTNwd^*k1ylt4$E1_1v2i|B zWrEY?`f%Rjs>OH+FDM590CQ8qk`Kxa%88SXR9*m$Y4#wXcYD(n9L(iAhv?|{?^vYS z?xiUWqQ(?nXsw{Zn)vcm`=Vk!SEGdhT<zw<x<Lh<bkqL81VNdV&Ay%Z#Xm+t2QWME z`3i#k%_NNF;1DpB5F-%V`@Bc}nVZWKGyAmrJ7U{4Ru%}Q#+8MTS?i}he706^!ciQG zNru1UEX@000tEclJboJ(7>NsabmaxPDl5y*Y%ye8cwOnrw@UlCe5K4GD;ldb?E5Ul zs=W4idOYmF&W3y|K-VDVccTpwvrM*q$r-F#EG`7~x!vA4?q5y1zaB~?%KMptl<=1p zf8B4w!;UA)TQkk#5Pb4ef`=O~-uCxU%xCsvU~hX(bI^M#b^h-ik1F`tcpZE>-(vI8 z(qqj~3BGAP@j72V@z&7O&QNhzF#M0U$^ZO1_Wy1T{l>VJ_TISEx;qMM-ddZ028oF` z^f(>YFQeKc{zLM>0cV3;PIguDT3lT`J0o5;NOt~_wnSof;<wtl{WFbV7)!*#Cb-Hv zRmFaze}?e%755vyGW>Wa<nRr172y0@YH)5W(dNbnap{QT5a2EJXjh7CUkD)P8&HUH z`$2lBQ;U2<4W;;&mfSZ&L?5%ES8sEA1E{M_$D)Dj!PxVRn>KvBv}&iw4RKOSIx%*B zY7rgZ46BM^^8+}?Z;4IY>gHxg<2wm}!RBN?8Zs>IH1Ja-2m37t=tG*Jq|j{tQg+la z+Umqx^3!?nj<}pcXQ)>O*VJ1hsC7ad4jv4D_b(nFqEVP+1cmEw4Ju>(vO%ev>TY25 z;#cEA#mO13dBBymn^aKCgU(5e$4RS30<$hm*l`U37J-P#GX#nsJn`dktutW1D=jK< zJb|^iQHIKoO2=;1Z6|#{B{iY<&h0KH&ENYVEj*dhP&9C7c2{nm7HaO0Y0;$a1AKx1 zvT*CEV>`cPEQi+0i__oG>sA?s*2U*n=?I%QD$GtUnFKvi3`4XWRIfor$6&`5M1@s8 z4AMzQSWV5fHV`F4F++vt3bW6Jiw_-fnGR+`8iki_-^%nd9l-BjC1DrpGw3bO`L|{p zp#QAKCLX_`R08#&={v!Mq2{1#dv;7U+w2I7Z8)l*CdAXiKmy1&JkW<s8+26wH#+q| zmqg5xVhh$^x9G6OXet`eIL%#SOlCYov3aqk7&y$d!<v6aCb`Bdf&{9u$?<EnsN<tG za*jE=Qq&m7T%$u_1Tss$>2OlEN3aJ_RCaC;kR>xGQ}j~Mzc?R!S4($SqZr0%4*Xx; zO49JczT3PEk6cgF`w+Lb#z)S&j$f}@k{Ns%TT%4EcK)uv#Iw$*wyn%>!<&MRKd*5X ze+W@6L;q9fa~Z{4{7>A*JRCEdKVk<{-uYh$t=>gI3DW=HqyG=h#*qfb>wBE|ws`;2 zdOn#+Kkq$*zVjpt6w<Es#%QI81etv$i(!-K9p_QiUJ`oof6|UP{P>lc_!nwt!v_+! z9g}b)q-BozU75VnrW1DTTuvA>_d_KRoRTlRqxxXf=V4}$FTiK9eWB=ab9DpVlUV#z z?~HkTQ?E;n#HWS*e#cfo0FiEAoL(A#M4SH_$jtBPsukDat8Se*(yja&<hfP;0<~^_ z;ysK^FSKhbToAJF2f(ez!<svB$D4{qTvJqEXG5kEU2txyt(Cau7>Kzr0GSi(k9UW- zzl@m7Fb86d7TYotHX1D>*=Ca6>hWh=30Ktw63sGG)3=<o3gWs=I7&l_Uo~U#Hn-)K z-D$h3X+&6FedjdDGZRb5kKEeSQ`5Jw?Ve)S#|t#Awxr4~^V-}lH<>FmKw~#3<`QNt zVz=$#p?@htb4yk9xV!~5>Wom#tjiPH`F3;a>Kbr<ZUSKLQ{vJ=1)Kf+Vbw)_u(aXQ z&B+pPPzqt&X%Bbj%>Xu?5#7Dat=w;Cs0H@-^Bw}0>e<pSbbPCT#Yn4Kx?brFui?<p zQS>Ry`q-ugb?eR2#fg7kq=xw_QbOZ5Uw1nz5N1<++rhERuheT?jZ$AgTN7_a5KEk| zb8dhYU7Yu8?OyL{&i#@CU8sN;D{Qho*+-XUYvFRdLGSnqp?{*7a4Z^GEfCj~yZ9nU zQF=t}+MA5lksilQ&G3En&t|{8N^}deuj%21c3fhpKr~fQh4*N8R}2YgZ#dj>IP>!( zLdQQs>vbpR3F=G`k#PlE8SN695CLTSO^Pr{HKc*OP)+y-?IrW&j$9|Lqf(!rWxSEX zZ)V)lr_K2YTFbfPd1lCNqLZLM$<R@G)&4HF>}*E=bzO0RzkTQwRk$-11Ycfnv;wCw zUY|?A8dWf}U@<)#(7k*H%*>5k4#309aekOl{BP_Rf%N33Xl{T6M7tILCGUr$b71TP zim#^aiW_tGf8P**<p=yLAGrufKyWDimOP;GvEw+&X(y0yjW>cX!li>ckY>UF{o!s9 zvBPfuGmh|t^`6FqdZ^K?uN%+jr^bw|K`!jOS?BG1r_!%9x^EPd<nUWL$JzD(8z?lX z6y*N&i)N0B7ZJ3erFDO<6(j1I<TJzcHt?YcIme+P&ZfYJ!%5p8G2+b_1hfH*h|z!Z zGIlea&aG#0erniXH|2GSKF4-v^Kxxt%mOlz=e%~3CUWMJ#%`MHm2+8at*ANa37fRm z9IJEcTEJyfbjuIoF4tLZ{T^_|bRCTW`iB5w4B+;mvF|0I?h=eQ<;-I5WKWbe%FCyh z9UdEr(iZDkD&e-fA&h|?nklJ4YgkeaQm_`2ZnBp?*qu)9x$fp{cvftEJlqkD4e`EW z5;*TAj`>kTyvF#DOa4@}<T-^`t8cqTtaT-J@>OtGq8Q!jjK~hg(Zz-7&}+ZA(8<OO ziaa|#TdZ8;*=BL4X1}v|{CSt(2XG$;5T8<~oSbJR(Mw_~e;{+k7^A)~GWqnOuz3P! zJyLwP&+d1LFiO}?X_L>zGq8rY=8|5Be0*>ksh#ZysC-7gWyjdzOi{ge^vmIWLY^;k zbTEH2pAJoJr=M}~7}F*bSKC(G1jt_dIr=kSM13$sL}@P=8QBrC&nG<n@1H3sxO%e@ z>H0F%Cs>Yv05zRSzq^j7KSE7SmA=^;_xx<^*5a4x5AQF+4#AF;*LTW2B#NCDZ~S*Z z3*#HU>8fdA9zgUQ9qRQ2*^c^Mc_=#*;ZadjiELvj*H3Zb^L<dLQSqU$ogOV}+L7&w zJHsfKY~c@n({}Mt>$0l*)O-O1DpQ5%3n-&Hk^MaY3POS`A3Xk|3*w6>@zlrzUi+`! zTm?p$rJr7zb_U#2)NWzlm5<9zUs4CzjN{eofY%$I)6r|rQ)TWgKL=m0B^F7UAe9WW z@hU~x;jI1wjs^jQFan(OSk=S;2?T9<>Lfd#5cITtUbU&$1or*WGtYC)*PKm2)=B_2 z{aJ>ARU3&c=Y|#$+bTJ-k02e)XNg-yBqfdhKw*7OgUcSFKz4SL-Y@9-Yek3$;ne7z zT${EOEWE!liBb326IF=6NNf(=bcOKqEd?r6Tu75okKzKZXb<GEe!x1j=I^nQlF!*n zfoSmllQKMF24xg0>-QXCT@0l)a?c~fdcK(R$yGNmw|pL29Ujr!wSd>7#sOr_v4Sp% zIJ}f_DqkZ(|H@|Y0OV{yg}x;(h%>VP`}u%*uv{KTjR`H^QK1_5P}e-{?(SF{M4}gR z_>Od(1#({*athim3`q`53|-395a%w{iPdjS$$7XoG0<CeZ1||W!IkAmH1=6STC<L_ zwM&W=p$2|K<a@Afy9LJIwx@^9otqcM=ZE!|gr)^SFLOt#k`Kt)vo@M+jTJdFt;O^C z92i~B%ZWsLk0*)>mSKlWm7hvW>#dg==`57Hc--otu!&<V8c{DZ^=)cpjoU;F$q>?` z-~*e@!cpdc(`5$Y6q$y=W6C`V^Q4gDO5#efsF{KPfNoy|sXdYU5ZV}AT&9p|#J52B z>t&bKQoPk5TQy~c#b}I#fmlR6=c%TLS6h_uc|X;@dP-Si<7%~KxjYf8#Y~CTy-TZ0 zXRUz9x+~~th~&?jz0V!RAZ7<j#>}}$MZ%bLZO#9m1#seb<v^b0XhV)ieR-{~=iXq^ z=YVD2V8h=xMy^Z+vukt&pxuX9RMZm^-<`t}wBa|*5<rj1uA<mTEz<Ls66o(3=ICss z^|YYti4dRjS>RiK;&eXN()(bQ^W}13s`h+8LSt(}hFr~Z7ijv+J`uV1`qJcR?rPVe zrlFIcu=hyT%QUnHf&R|<Vt9is2aYgp5#WkmFtg$S_MOn*9k!rWY|y<HAV9fvlo)@~ z^+SgnzgYW;jEVy2uyN$p2W!;o!1hB;CT!XCx;xFIj6?UIPJG&-O6lcqadC0augZON zZof|cAMcs?--{o?*MJ)*g&U_M+KMi1-aHL;KdjsbT~dI#^~294O~4zrY(vP$!9UfJ zKA@wD>`b(Oa~VKZBH<53R8LAq+x7fG#?Wr#i`f6H_0V-b@dh#L3<OG#0f9tVK3Qwk ztMY=x5<7ItVaIKs!-6ZwLUJXKq<Q7nGi<wI!Hg=3fx!)~o)Ob4wp~NE-2xo}c-(<w zuSY~s)ANiCDUh_w;z&hNKCTT=x?7@Ms|wuws_c6A!h@0~p0e?jF|X%ugx(5%Z_S?W zA{^%g%?<{zTN)Gl!;N)%FCl^m5N(5ojE6tg86d@esFmTUdAlw0j*10!DY;<X8dG1h zwhJbKF0;ER8as3ZS&30%7%p!QB<8iXrLno{eQIO->B-;GfKBRn$OGts8Sq$rAk%`c zUrJ8aYWqa59~WRH)u=l~?XSa~jb7FpsiC&F(jEzm;vl-lRnca$@bBMOtrnxorG`7{ z`6}ZuW@rl%-XMHgW)UM=*1R{_H=o<@mke0Di8Bf6wMF^U7oSu_@;3%VT9Mba#`bxB z<9?eQ^jK+eLz$Hc7;7VXTFGIAG3+vFGNYS&9<lMxJ*_D#Q)bD25<XpZaBp5;z1V#U zK5odxObqrqw{A|i29JhB1p{<St@dMwyZQWx-Nn|!IsfQDh41!~zFzE_g$Vr2q4xKv zA5zEzwofD6f*~DAR49T<%XEc6%0!6j`^m<XV9|&B)nT6RjblB?YDxGtf!nUFU?$Jw z`VsHAxVV1cDDbaD`bADoj>OIC^+l#vHiodsoDbJgh44n9FM0QwrQh^709eLqfuElN zd38l=85X3&0VL;G@xjy9!W@9JY>>ca&#j4Jh({-8vF@^^XF#Sa?D!`~{n9V5W8av* z!w2-VA^M!_d1R?Kg&(xDc%YQZJ0SXUA$pMEvXk03`QJs|sR=G{-l8nfB?3d*v<CUG zOwry$Hclpn3G)v~`1L5uK+W>A6s|lASX)o>A=}zti<S{+xqtY``SVQ=2k8+%q8Jwk zpRcq`Qes48$XbLjyb$IXwXbgbTfA|FJpN2j3QIV`IL5H$8)#Pr*`MeT^IR$7->s}q zTX&nT<L5D`17`E`VZ;0T7Nuz(a4!RktE{P&d{&suW`KYpW3*UzgpwzE@!~CggI=J4 zjku-b5qe4)l1cw@BY*;6dpY;Dkw!?<BT+Jd$O?8o71y6}(#yA$GJT2$RPU;V(hCB^ zSzDW>r-$SYP^UQ0It4I_7MA9y+x>};5xviRNnDS3f-(XN$ve85JdRs6wi6OzQJUPl zao^W6xuak+ZiEF@P2i<01gBuZ$y$B0NFXh5V;uWxv%8b&gYiH|o;W(nDhH;{=ecZh zvaCd`SVVLM!qZIAf4WaQE)8Xo8g9`Oyi~X8hLhi5d&7GOi=r|eq&GXu^9*8u7PdRy zCb8D70s?H&*2Q4_Z=>zuWDCEeo$U5|#(z4?t_=hcxH_Jqz%`%+i^`BSgiE-o1aQa< z)VDubEnlRtt&fVNeZMa@JPz(h$7V@y<Oe65297oCyh$dGp46lR5^TajG0167*0v-S zd^OL7hU@#`o6at*amd^gr)FMc0<_lW7br%nw_b~_9`*YJUQGgQ4W|5hy7pw&fBeBs z3VOBf@xw_F$yUTl8$nvLhA(-xy3<-Y$a$b0tIHb;7@m=oudi=Z4E)^KF9p3+?iLMj zAv;Mh1$<oo(bcrdYVb2#K-ABj30Op}Z}y(Y>D8X=)H~_^`Z0|P7|+n_=t$4^*C(q! zD~zW!!w)Mrkh>6{TSTvq?kj{W|F>norsg^MpY`CXK*df(k3pBw7MfyowB0Cz$Qp=H z+=svpC4yzwsGVB!=fiH9#>rn!+;d#=GYua}W}_n<z%?g;gk@zE4Z{wB_YfQNM?FrR z4FNDO*bHjXMD+c4XtlI}re_))8E^Q^%t!fF>W^^xH7$8t7i>Ov3a{xSQ2n=%cGA<k zSK1%O6nZgPdO@CX7l4C@10G(XaxfX2@m598`l?2wTt8;cR~R<ypEXYIt$fc<US>g+ z95QC3EBYBt{{WBt{l1jt=IOfp-4<+nT!_yTJ9m+)#)3A*4bV~`3jowoRd%bC{hZX` zY6Fsu-ERCH!4N?#EK_CR7j;8&JQZ{gRNhXD#1p3yt$VlV**n-9(Ugn;klY34-bVd4 zDK-_+WkVbO%`cF~es>{QbE3}hWo;HTlIi2kbZz_`aOmgcL{~EZiCwoElrmeQMFsR1 zM=!2#H@CECmvZE!9@iEW?BxEQ`B_tQ?!6VSvDqj3T-kIF5X3W&;X86emj~tTx@~7a zZHj)MRI-#leTA*-Mjo<%Ow;c-JK9+Ko@W3qBZV->@HO3}(Ckxvw)@=Q({^=#dwqYt z=jZz%4hS=4Q+F<ACo5^zlgQaNZFeS(>%qiE6FE{Sg(86wmm`TLvjbV6zMmJJ|NEI; z35R*Shz2d3fLkBl*FR?4t)wyxp1&IZq(10dJu}P;vwgh1;dSWrdIMKWspzMW2kfs3 z)FrsD>SjU5mb&i?!9B1!<>O7%lnAZ{ura?pj=g9<Z`(XeH@IYx{wGgre*@f9<)7l% z${O^ngs#7c*gl3o;(3UX7nj9SXxmP~q_8$1-uaUZT;VpFa^15Z=HI2dhI&_m9gQ;H zgAy>q_Jc6bTP1dORhPi6e>dS6So(X(#ubdae@StE0@nm+)%ZIjiYHP|j60;EZk@TK zhtb*+u<36aguGyk*K+uAv1INk-<_^^qdb~9-4*)W7oub)GHc~jgt?i+D;+38XGT*I z6FcSw07lHtTY5bI?v7-_Dpa}r;_cQQ74-M|_-Nt!bmC5T&cz*j`T%okp}$QH?kaf2 zf#@*s%N}4D)+6(HbWsiSigl=}##!;c?e-}Ev5Si8y<Hy_*gjRdT8^-2R)!%b8~o#p z0f(@5->d{aj#Yusyh`G^YJGqA<I7fdBwt2AKWl<@tG6u}SeW9&htZaAQhGkTSc8UA zQa)87hCOGvpUb%svNzO~srRw*(P`+o>D8YJHrTdlm9+vNV(ujyt{@`!-kr+^Vj%UZ zWibkjiCCdoz_DfKOK}(;KOF&t)I-xjxfc8b@EsL}T?1dhp{^b@M^dls5S!HU#5);_ zjZ@q0v%XV)atN75U6@0f!+d?1`e73}??0q-pO=SKLypd=CjNDx!~q3ADPwgA=>Cw} z*|cZ~E=;v{r;z<oRYM*L(Mq>b21oc@3VS)^YqA0mra|>p^t;J8j4mW%Ec+}_vRcs5 zz+`K+!q8~f1`9ZtJeESbi2%^_ijUspS0eofdw>i`PlmfaK&2`bV9i7Hw7VNn@m6g+ z4Px8X4abj}eZ3F_yd){Ft75U_E(hHH3>c(@x+}Bi|KM5*7mSpV4qpQmkDcW})lCrc z?L+h}Vz=MC2qL$^6N`NAg5W`fu4T-4?H|nUgt>7(e~(Q_Vcz08Hy)Y}Gd@zgFp}0S zXj*fADuCxkfk`6weqYJ@b8=t0*unLA=<e_;cSsrv`40!(ZbUL^nh4>sY8S1Y0=#uS z7NoGSu#Y?PG}`#jvD6SXhH@`;n<D17$dkkn_vif-EWc>$POt>{5(mo?rk75ZZ^>Xv z>RJVyoeB5X%IsT|`Q3}C#|f#<baZ~C_11S7*-Z+uw*rRYR^i~qxn2R&`sO3&huNv) zn;G{DlabUblLFK2OkFNZrQ55P<P7KfMS<pYTQk#dun4fUI~2&&e$;Ueo~!i-e_LfV zG$;a*Vt%bHIVh+9&^!8c^Q&#aqmXG`W{4F~YZ|Ew(U7r1{7T%jrIMKNOoRNp@RjjS zTiyhEi+lT}@Vp#ej2R7WkEq(#i1DPv#Kt%=C$Q<UYIMTgUlLBya8XGf(4Mt89bmF= zJ;j&kJh1e#s#qNSoZ<2kVrw#rUh=#c`Sql@ItCP{i2qB|tSnwpc=IE5PwXaB(FaH( z0+QEiEkr*(q}XB8Dt_RaaO{&%;IBQ+2NHB#4hp)?Hu{*>7rcrKy}Qn>JXF%p|M?8I zo$9Z7c1`H7etWqMhl6-YPW|hc)$=KRe+h^e+$!=Yy!c|U?$f{2ei=Z3YbruPM|$oR z!>*y^ZF_QjDi{(6CRBXG2klf67!1EKV(=)rH~KF5`GJ_25c+PI$6xIO1Wp*#PpHOP z&Uw$Q4se%ZWahK=&k?><Sq~3LP1izjuOGC=a=jSg+j{le(TIw+r{b*VHl9BH!B*+l zdz#i8Na8T;qO-5W0EhTWuAyPshP5Fuvud9esiE(_y<1yksoMFF&0U8g{4O9Zpf~3X zw)D~xKM);k^*xRi(*w)S8fllJ(j>LR*<CyJ=f0zLNTk;N;ZOg)vp=_*1L;u=3Oh>C zjRG4+uJXde_PLQAp((}Ch_~bEgi^w)QLdA8xBP19=|r|*>jwQsATjsK0|>%7`Gv=w zD*pED)skQc$W_1YJi#fm8;sr2d<p#r+TRH!Nl7r?1`sN0!%=eI8)+qbUR?2=U!Hi= z9khL6<DpRP>!2J;cUfYHeA=Xo;iAzP(L_X;Gnrjto^8(hFlWjefeAZHORA)$p`rHs zZY#3#jqh`u%kct(+Vr$VZwkZ*pRElClBO{T97ESa>rM>K1}61*cucfD3f5FNx~(n5 zGi@zcmc<itnf7BPT}I@!+z+RDlpZ)zMy%T%#3}N)G}`&)?K+H!6jF<L;YN}QsarQ+ z@iyMHdDM<v+9`ca@eV6*J{BAR*?BV_U5(bF9SXG%-y`pi>&EsMs;I==aStdB-w~&{ zPC>Ecyi)k}oVppcDs26r=qSs~Z=nUv?G0GA?NY2|XSStwNSAzYk3{1DO0B|$yQGWo zJ+|rIT@doc(iDSnw-4m%6!Go->tw7?xrVgsCs0<V@!!8%>`hj|7?6_C-e2zXWVsHr zoeY;k;JFzs6ZfZJI|TK=zsagcYLL<kNPDLw^cmLOMZEaY?TL5ZX_w&mOMX5nv({(s ze$J?ISy?hA<X<2+Y5Sj*2?jzJ6a0E`+=~Qn)}#Br73?WiY*B#Z#HJh_<-ev4!<>0; zEomo1?BdXreyG7f2TT`xKz8PoI0z5n6>BMS+{r)NzmHO9${BsUxVxIw)2LqjrVHfF z#035I%zzjDbE#9XX{<l|(1y+UB^`9AMVf6?+<Fu1(HX;jU6PB6OuWG1pi+HFLnprp z9R2#L>}nW2L>&Dxh)Wfc^2Qiq=Idg+E}F<abzgg`@C&dGXxH><IVwa!eA^A<_Xa1n z5<bw-TNhH3-TujEiJELkjFpd<7fH!y$7rYjPpFnG$vYwE!>E=PVe`PogTO#nLLrk9 z=C(^!EvF5}M!1DB?s-ydVpL<BNX_eo&Az^`!7v^;4dY6QjNDVoQ;X!Dr+(wY+&uQh z*-wDfD}l!XrM|oxc1_tW2;H?i#a8}$sd#YkF&9CTQV8N*QY7cS)&Y(WyIJ+o^OB5B zDxiU}fT(0bbV8pk%UjiHRWg{1PYKCzEoi-c*SSA5b=%b$LO$GY-mhcXBBhDKNg(f9 zQDnUwBJE!Afy!t@#E@Vv#KqVU!6U1tk3x)i?LEr~!&p&ErIKruVjXYB@jEFRX!-ve z84f=|BS_C9tB*fhE+-X!DLK$yogpp(y`nh<jpis*@W80(cxPMi-|I*^H@MA0KK2Tt z+X_=^HtO?(VJJ6`+2&<emlx|Msh3G(V3-WmrfN2c<RXRNJk&RMhVg(R@YFY}9MIWt z<M!{!k`$DNb2vFiA-#75%<FH*NlRlO)Z%!GV+k}ASq^<B@)lofI#?VN)h({>vw3zy z5`OUiG$H$m1Zf}FpcX7rlRHnQtv63LdbmE?uXg$OCv?1`RsFc-LOI(;YdTF0d%9p+ zhyAE~^fK62K*vKcKGgenU0b>6dMY_d1#gr`2qtL)zMd!expFlj01;ExQL#k3^=nW{ z+^ce!mtWXP^#9THRdG?h-`8{_DcuYplF~>G-GU&}jWBddcO%{1h;(;{bPb)-DJk8& zhwtzGU(L;YW-iWop1t>4Yp*3R=1QxfStYsPaltjpfQx_vK?|4daUYQzG0^iU^+aUZ ztn7S};+{QRKTQqSkY)cl)}bbjp2XITee0gZY4+w(kS&>MgENg}3RDV@0Gc9-TQ2oI zuCd0LE>V@7Y`nlp3GyZ`5PS-ncHtY^9bs)%pm1G><zCLp%Bn!Op82~gh5T%q-%z3L zDe$&NH(T?r-eDXw0|;lVF`uTN4b;Q(Sp!U(XU8&Da%5`{la1hefHq-A1u`;C$g8^; z*qixn*`amr$Y`7ajtp5~Zm(8PJsEg_?CjX<j~l$*?d>^Vg@r%3890q!^-x2xa8i<0 z-Z9mX!~Vev8RvG3p$0AvQJ(dRR^U?TT9@)gU-NmI_wnj~&SkOCv3dZQ@nT`5=B@*O z;5XN?_fg?&CV4|~xj~0%%b>~vxjCCPwzX41sy>F&pT&{%O}V1Q=-U)#l2WrudW=Jb zlybq<Urt#yx*01UgEBb`Q$h;q&n<P~mWB$E;86*q17~Eohpj{Tx1)V2k5I5pKa;a? z2(rksrVA*trf1UP1^<#)@l24I_P4KMKhFP`Jw^kOn8*JqYvsW;m3-i-SA&^(Ke2J3 zoOd@EOM9Ow4;QOdHv{?U5&k;QoHT8{nKhuhTS+WH>$~)H-XA^uEB04os=U^~iIoq5 zH?HV#z8oI)DX$HAY<enAg%~z+^>zR0HR3Q;#HRy<Da&J$<+Gys9#M4!ofzd|R3IHB z)9d_!@%%$Yaqj0QN5}jw?S*$$z)qYw#DYt5lYfVmh5K#V-`bryZCf==4U5kH0H*@| z8<dnkU(0RFSJj0Nh2nECxz2mX0w}vqF97Fl<T~p{i}i8+DllM;?|sFqsAzVN*yv~N zl<R_`aaX^lcAZ6v1K1C}1GFE?n3upJLNThdg?Rkq9#SBnbuKoy(fY7%ijX8qvlJyg z@6zgG?bJL@0V~j9rXR=D4LmZU2ow!Wh*nZQstqobl02J>DA&%*8AS&lWmJ(duC!KC z(N2I8O4t|EB*ll&^IAu$s+wf}y9C&)aB@1uEhJR)^LY)o2V>8-<CRJ(_dn-LT@|7v z54`@k1aM8PtsBHgKrD`9HcOBeYuJ=J7VE^{P_V=z(d8I}!2!x}sFI`jybf}LMA8n! z=t)%7H$wamH8&u)1uj4LC!J(zPsIFB(%?ovUz^0HP#$7lLB%BI6vpsErMOt}5E)N} zg1CvgRjWI$9fJCQk4-43kh<Z`o)5IKa{j{>!qb;LVNCs#5*%Kat1fF^&>S5M$6;}N zvt6^*XEJ7_2CSv@>8l}(;xU@pv);$Mz3dTAA6Kcl3T-B?5J4KpCc#UnxWiie&2WPG zG#E%ye#l9Ej495c;<3sslKD1R`op}f?U<<Y{cWHyLSC#E)VBSKz*ES$7Ho_cDlxk= z_iv1hM}dH~O@ceR_$ebXkuD4x6BW_pjG7>=x(TXN6VvoALUr(Y@}sD2U&Z9EBaW#= z;`991M|i%x_WX6OT$kaB9bMRB`<>g-zkxvd<zhd1MI9v8P|gK@aoZK?N*t8I55ctl zThSlNOG@Hi9p!&zdzTb87e{_tgZ|EVZc1j=eb3wd<q^&bJBNo$G3S97`Lg8$t8q3> z*FUrf+w7zxG)ELZ1SEJkZ@9DyVrPo-M676_$Lwamu81aNfOC`qE1jU+`a07$v5nh1 zlslmC0q66z2msIbH;U)uzk%~o{U<qY0e5(KMbTBDla`aH)M@4@JLq0bI6Qx?D)8vA zjr*izN*3oljo$}{?oAh6E(1NU{(N2ZrqVAVUH&P=fyA0)ENTJvh$_PmV<fZSPd5pT z?)p1M)57LaCEDIJ)xu)l{|H+Ch$s%0Mj{f4bCn5jn!$~vGMt-Z4vQ`4pg_3XxhNdz zIf(z}B=zHW9}pe=@Z%iA$87kGsw7J8z9ksq=ovjeOGijoo}p@ae*15$<nHm8$dj!1 z?FO<T8F%(UYw~XVK`ZZDlKQBEzk)QNgYfY1#C;DySQ6-+s-327GMvD84#VYh>N|8^ zR)&g;hsnf@kM^RG{i(}Z3%9+gu=ruO(DXK2n0n3UrQP#<Ph_OgvNN&m5so`ED<c7P z0EJkGfRg~F-4Y*m_;jIS;-R|UFVm)9{@9dBskx?=@S9Pr=4xhB!gYNe)4*bQjeW0_ z8?lY*s`m{!zos)Tx}~^bjn_{>)a(Vvy$LnP2~#E{e%opL;cRrXXqbO>A+sjC4~n%b zb&xYU9>%j9&T_QGgoJ%07#oQcUfV1!o}A{}l@*=Y7*lTw=ZJ-myr0ZOq}o}!$0ysz zPS5#)2NeJe8av*2E3II#4{+4GS@0`v2=V>+#fxB%QTEY4qEtA9fU&Uc5S*~U4zj?6 z0aOLB^m=}@aGcNMfX|omsMYK5J%OlyfBgdjP+}t}MLHV$nmk{>1|!kMa*u?Z7_MGU z)n$>kRFG33HfOa$PwH}<3=FU4IJi)IXiGTKXLNVez_{<r3Q<GY&|s@`Jd*AyC9iJ| zSs%wz1!o(A#tcv)!Y1Vuu#udVG|SrdT=Ejei6EI;g$WXt<k2)5e$A~M>I`Z|c3E;f zc$zf7MMWHn;$PL>(RP_FFoGH6Shz*f(G+U_e=}WHKf@(zwS&^jcP}>k!r`|QX!HHW z%`2Ol<nyAppX8|wu_*X_U8q%(bnFqiaw!uIb47)jIj3W8yr;M`m>0y{3U!JEH^{9l zV~2;|UkS7WQUoi4TV?0dMfjRVg?MFEuic1Ls`OusZZdeI$k3?sVYIN2-4DEY%qgAV zC)a7Ap#^(mI(|k?n*2z_0C2sx=H<3Vc93OhW0m6ZlDeLo=0hjt_x>Kj>$gicA=|;E z>O;_6aq=)?n5I*<Th`^9)YD%t<UjRo?t@nWfzbW;Uq>3&``g<lPfzly|G26r6zLP< zm95FNd9+X1T2DPT<uD|DT_65od7Ye)|M%>QvDZyO&*Q-5Zvj4iUlcnV<tzNw<Rn#m zJ%fhbL#A3MeLA-@@=k#Hl6RiJeC6#4&|Y2=!AgT~h~6hdfg8XM1;Y~olJ*~y_(-e; zZpYMF(M0lb1?8Q#9J1!J5fHwPq1}<b+Lwr@h$_9CFo+Oyh<S2ciPmD3S9IB0gZlZW z1bJ)DZP0sIdM{ClUi`-MZ%JJ$n-LBsDW$8-c_CYE?kuT#tyZbsrWU1>-q>SWuIz}$ zGy+Qro>5k|2}^PLD()uPzw2h?=Jq!@;M0BrxGd}euOtTtSfW>uyHlKoHo)6Pv!I*_ z{BG&f0&@5J<9J7h24f3!279^A_L&$@0P#%l+@+!O>WeEVpft7sUu4yE^#+}x#O%M~ z<AP7Ezc!HXycmO}?$}eh^r}6B9``3Hhv?XYGSiYs5-@3<H_$KornVe)tux7b>0vE| zd>+PlYlyYx9<ER0rDlfuO2hDKW(v+#QWmo^;xRvjL+kXrxNdLKd1XI;{_yC*+p}id zvc0=K7{`=6sHV=E$nUr(w|k}#7WVUPKuk4eqO8ZMrFMFw+r}90Y7FnHr?~UU($A&( zTGR13X$m(9o3;Zf;t)*lI^kaI_dXqYD?6Gr27B|YTl<Of(`T1>9dCq3%sH~?uZx?N zjQmLv%4{Le6ytso%Ff0{>qdS8VMKO$%qjv)K^Gw~1DxzgdzD)1@vmJX7`qCAt9%KH zd|8n{Thqel;d?p$y_XvLf}N78!_4Hkl!<!s%<ELq7k~l06j(K|Kv+9aOvvDbM16O~ zUg14WpdcvfDjdpY*_q3btvD}R|2>RL)7SBI{IC1){I5Ysar~D~;A<Y-TGAowO#VLR ziYnJ-Q%7r=#ya*NG%q6P&OEMyB1nG9zbz<^ux(#=-)r3F?*$(Zu2Z)`<?KFBo^?6B za9+Mz-f}vqq+D+}>LgiQn^&f<x)*w4lW0g~Qv-hfrZgT)mQt0;>C&K(`B4p(JgrI5 z_jC=0IMS$ILOWBamfBUyAtYA-PL?vOFC>o0DqwFfdE3#@FgGo#aAGf#lvfULA&#IB z(%NTBA+6bGNZRHMuqGU-o;RD!fK9D$Zq}UhZvE<gBl1GKLgj+HB$W#F)cf9xdA^?? zJ)FVs=oH`&B*-Ll7z7K2hIDX-_SX_53?ObDFE@+;D;e~b9eT76H^<3;mq=zUDEN&) zb!mH~<))Bgr7S^N+%tjT9rdqYhR4a5ceLyFII*KVz){&{@6=WQ&~99~Pz>&=mD=E^ z=~G~Zvx`@`iG>+iN6){Lm%Etj7=a#{wlX5`FenXc+jh`R2wp^_x!73Rs_IXOcwY`U z>;e7-fwFjXKLUTMN5!YFv+EZzrLC{FTQ=S^>A&J$L(`kE1M#QNJwfPdS+^(C>P})} z{==&HJhOao_m9yeYs8tY6F##QQb1Q|07KPv*PFJ!dm8qKDre0F;Sz>Ob3W+#hc8`D z^PMdUGlU;=|M_EF#UFU`*Ei-8#~H|5eSKrh{9w47pD#pSRI|MA&AB<h6G$@p*&!1s zpSA*_01Z~D7andr1}$xG{JevK+XV9s5n<&z3z2D)<li(cP-PQ7eG{-m>VY*p8UK9> zigesRNQ7O^-iry>0zXPzL*-jEgC0GKMzFY5y1FO${cSJ`?Zbe<#H!ja9`P+K3C9dB zS+Osrbzdlx^PZM>7IM8*#5u5pzb<!=GyG;4M2T@<Y+fu8N{Q!`4BJ0CvhqwWINTJ& zr~}i=uI8$0iLk<}Ek_}x=dm1yM=PKEe+N=&iWjXd#HNm}@2?IGSDRI&qR5I#CP3Nd zVyRj&8(b8a-d>X}e(FfFi%KGQU$VTeHScVCUH-~@^xUF4h&+m}eV@;a3&%vWuV&Yt zsL=MG?*T9NP9nvpUSVFvWhb+7{Z!;g-^(9}Dhd9F4H25i;KrlxNH)kwLQ#D8%a}Sm zefJ0nepnN}Zn1gD*SolQr-NXr((TS|Dl`%)3^mN6!sFk!WK`~7du%CK#1xG7?DOPA z`kjJ#x|hh6pQ#dy89QO09>!jtBVQGSC6*Roji^{s_42p@@ADLepzpq}Rz1po(iPy; zhRPF9QS7RY-0&Yiw!_&vVk$eIwd!~79#XX)S3>!LfS^BtVu#3F%b&))SYsTvK7l(} zWe$<`e|p-A?VWXp_#ajSR}EAfff?3X(e9LAi|+L8-em({@&r&{_~a2xL6pgd4oewG zmztaVH3A6Z+;BjZnn!6p|6dJ*0sH!xw4e2$|Cb?HlIYl=yGir-s`UaDR06sg-5jB1 z4qb2H<y_yC1bPr>3Q`MjfKL*A6}0c|Y|Rrm%WA0}aO7o*Es?^Vp~@B&Lmww62Wg6` z`c?ga>X@jzu{#;nDSY|G$2KgmsMA)jL(bo_ilmCPY_M!NktttrYQg+KTcexzQ492; zL{h47VC2>iiey!mfH!cyGw2uKaJx>8?J23EsvD31h6QnQXAeY@vT+tFB_B{Z6p;(r zZ%W*1ku!)-i{jEm#@Xj0INcUH*=VqxHo=&qg5xG;{2zfUWX58!P%#~-Q>>3WBgrG} zH>;)T6_4AT7t0)0{Owjh*hqmKo6PH3Ct)`8q%>g{eV^m++?E~WT7#e`(}}e5=N#LX zJJZ1dzObmCeoUsB2EVS<yYOhjk%r@CwRCqOnJuq}YgvA-<WQjtRv1lBa?k52g^#Cr zLgi)z;&JRSOnN#|F67>L$1Bz1virc|JMOcui@BRZc5Es&x~PG^*A+L^wNs_0)-fW9 zM`jnyuMi4`BgvfPz#<bY0twXbMZbM3XCe24S-(Mt!sF<}pSG9`1=UODd`teqI=_F5 z`ae@RG?895mZ{CwJf9*xQJWVK@&2l*seO#L=S1V>S#h{vaX8qNr(<H0_jw3jj^SXJ zs^M*4-{;I|b&EI^+|zq)HDE))w%AqlP0c)Fm=Ij(DFHv;N_+Rz4uF&*!cB{U^-odu zJA9`SH?aAB0fWqaM$aLd%YXL|JQnPI>%-~Gv#lGvFk0~+`v$-8%hH?;$7x00Ax!qt zAxZcDdKteU;PyEd%mTPXQGMvn_eh7c1$(+2p9L2Qxf0TX;>=x5V>LXjkKi&!RB}*G z<ePey`0%S|#4A;{RZhCO^}FIb_f`T*&?jrJ>(Lr(cKex?Iq@Y>VZP>6JdMflZybj{ z043#ee>ZC1IJ^NL@@=G$rRHtNmjmr9*CG295Ucu_@|>jgm;1d;c47J5iFCGP9fvRr z$!Nb+ZHrQRdODdePWbH#=@mhiQPy86Gg<E1fWGu@i}D5KVsxz}xZ>*W@l_x9<1N!n zc6~Oa2BA*m<S1?C;$P#{t>Z#dE&mOMO=5=uw~#pk8ol1b^-<z0Le?p?nn3I326c~8 zGfE=T_vyX5U#9loE+cG`S%IO9l+F!nF&$H{alb+?Y1Tm%i%I8isx~~|9<m_F>=fM{ z%SNtUWQ3OR5+ElO<gN7lc1H-)CvD@Ip0~vb-Mq4#9|CN3nA01qk_78YDVCu2AF8#k zuc+z|yiM-w%sDWG$Bc_H72}WJ5x!iFIuxlrJwKd|Q0DF^cFuE+wYg6Iq*|a<;iV@g z?!Cm^^MDq!G$?vGoebGs7L@1>!EKzqU9IKtBxd!OAI9(@%r2OF{*^r(Lg`(^A-x8T zi2m^-4p@O78e8D%cB3Y=LE=*{Dr`?pcqk)$-}#P(Pbq{zvCbp7#z#j?o<IIUb)G<L zq*yguw*9&8cdyY_w97p3+WCs-Fre5ypQW#_x|C_H+9NnA<v5y?!R;Zy;zH-Y^+w{& zVc)sXuwY&NSySID7^+YX>B^A+pw|G01>Il&yU7-5yAT@yeTg379~UrQ5gQ!g9&5mz zT->A%_O4jwD=rUyk$%0w$i-P3Rbguii#30Yl&-^nRN^^WU~n54`Popcn<iic9)v0| zp+fEhOmkMbFkGeUnEbva?cWVewIO<1$cNyf=UU#m+tLbgU^I_|5V*yAXZGF}aBCbN zNAZMN*~SL$10ai0=xDm|X99(6sx1yxI^u9d7R7R?XNB7N?s{%O4W`5;pf{rm!J_;+ z)<cp4DpXqE-zVaB$?yvbMm?(+z%sdPb8~z>s=-*4tmfwCRQY(3FS=Yg9&m*r?Q~^J zvEv7_WGY*+^e$`O57x86J2y0kzvz#9u|&#t8xLwz(5Z8e*J(^4I(sDU-eyu+r1)au zdqhlh?)}IUD+rTO4QCPYY2bMk*MKgbI4(NdAM$h%xAL2`9?zJ(Z`M-Ne}oM%0w!;F z6Ax+$ZA%QCZo1`P)jyzneYc%&bk|Yhhk`=fiseOuciZt$BRwiv4Ob(o#ewR@pf&pJ zp7{%P_GB-jZ98$V%-_9j4**a0h4iZB@atw9xc&eyc3y;H=F`dkdg~nKL3l%B-| zbvc>;^*NxpXQA}9Td6gR{?et+-Mwi<#48V-8UX@fS>wU3)4Wdjp$@p)f}b+@a-WOJ z*MQhx)1lN17A)c0oE2crB@VH3LaMdZBlOe5?@g--(;P0+d{;YD!*$Iam{{vBU;2N? zGkK_&NQmn`1q_6re-BF7KZNLA9EHW0ZaLy%RJ8HfdxDEUpe_)k&t#=GbRu-jlc`@} z{uo2xnjH^Fx8M`8V<qCYNEoK%eaEa-C$sm#reAQ@{~&dgumSUZ>qj1Mke|qaGIGe) z?y?SS%+u{fnrP>DyIwc39fbMvEUSe}KN4k1Ui+$rcM>(2U+Gi&eIOf;(1wMjl)*x@ zg8f?bhLGE)*RhZf!94(G4+|Ii72bWVTF$K%n!0|1nZz0`p2LQnlH>$O0%ZZ{Ds*;T z;x~Sdp#W_;LETgV0nQOHc4%Pd41KonX+IxkYT2Jtf1<z~YXBS^N|0~}?KpEpw_nj_ zU<LvB%8h}Dk@ZOSUfs|0Fq_Q?TF{4Ydwy{IpVinf%O|N2a}gCWA-PgqaG_=@2sk*_ zEv6IS+;yi0{54!RmGFuXZEG)2fASKKiJx955bRjDyML|eiXk%9T=OJ}G{Ya)Zm29L z-fc{$<7Ck*G>eXP<D?%-_(u!kq!ZoD{X_OvG>-f^L3<heceZHp+x=%DyLI2Ag=X>V zlSO2a5k!?Y4wZ$41g7EIi3+2?Z{ScfnPVoWe@(=sa+>+?rxz5)$o~C3Fi^UiJFh5I zU70YniWE1&h5iKiToo-^<K}bqX2M}=+2pl6oU|r76;N8*lo|1*-#;F6?IRg~T+7!F zMH%#u9{h}px~N5E`#o}u{blw27!V)5U=(G_4lNh-_3`zTl#TryOLdVC_TP-4Wu=$f zG|mT6^N{R8Aht9V$|Q{3-$_lZM_hj;BoD(CF|MTJck5;BqHXnoRZ>$#<gFbI^f38} z5vTj31s_9K09G?DGd@xgjl}_*qxQ{2r$5^_8<QNQ_d~UYXOzP6&#uXOKhq{u*F`>( z+Z%E#$F8>Ffu>u`D;t=^NH$vAP_#xLStLZdF4??5r=c_c8J`pt6NlpIN5mjU`dHTc z{EoX_ytgPb#HG8bvY$F`H^X5&C-4|dhn{X$8C-llO&>Nlw?-d&tB*dJJ}Kk9BPFuU z-Z<)p{OD#HX{SyIH}zDe09I5Aa{DqE8LlGFrdL-ih7JDw4h-a~2$Qu^;EH;e(jA>Z zG$<ulfd}ECbig6(z0?!8lcjUWz}7HMPx{&T4n1q*3hGh7ad4E$f0L~AvZ8{F>1oku zyV|D7o!9yD?D0prH<D|mNDR~SKKNJhv)jdP@tQ{qISMAIE{txiZyIr~(n0<R7KFN+ zvw-c<5xz@VY#2@P2H(;o>y9SD#AfOaHa#Fsi;C2GA;A|X5Mzd$%eVtFJ-O{#{Y_9+ zJo`eDc{hlN9Xc6<IV_3GLwvB(eb*x&9v>U<$$v<OCu6Sz>!eX;&G@gJ6AA`B0-rQM z9zOwO-!_2Pf#51C35nrUMZs$@aA}8uGrmzlK?SPT`>lg%r)gW*L|j1A;||5BKP&={ z5YS%VpeP?guKv!RpO3JO07=YH2KpB3(mQd7la=Nc<wm1Ev}WKC25tMlR-dlE=-1xI zJEH4OmRu%#3czsO&O)u0n)Tn1GX=3hAgKUGZyQ1hq014S+0PTK<B(ma`Whe=3~>I3 zhg6362GnquCch+-T=(%8#}s2osWg8JBn^GUXwRLfmNAooWGojytuUj0AdeIBZW>Kb zWtnD(POK(o<N&d1<<S~`)LtfJPE~7!BecM!U=e%|>;ncz1b<~3!;fE}?w&_Duf#-9 z{2PN$m&ahDs65?pNhkzA@H-4I@hDf8C$3wRtmR<EJWm!W7DczyyWM-f9Ck={Oe)Pu zug$f31t13;051^>?b~)q9nHPPIm`6E&o@%5Qi$W*_M<X(T-eICq!n7-B-{ZS6_z&f z@e3<<bwv_#BTPRFJ3a%3nqaX24S%xwJX<cNRlm!!fN$8G0i+S>`68(H)o~o4&m18? zhXy0KhRD)D;<$Nq#2{6{FbflvG==(*MHCV9U=__O9209w)k~05OrSWQ_hXX<Sma`q za;DOemMfpItMLSg@~Z4ahY&mf60OAVhx2}+!td&#?w<nxld)(z1Tkwj09=DkC?>k5 z1+KPQvnKoUEL}{IF{i-DH>%X@K9g;}={2?|d7rCU^^(~(>j$3PAzK?XfWnnbX8y7U z9ZAxq+p0G7-@cV+>j0ICXs|F;`B9^G{IczQa(3JNCDM1A=?0<X$)Uo&vH3Rmg%T3? zCM|MnQ7<3eeF#gZaec<W+Gaf%3ww-Vcs=~dBq-tQjEtIfJD0zmm6gSB0U6YqOPaGY z9O#F$R&vB}1W@)CIO9!_vx}*FS_DNFD{Tm!9PYNi>A!r@uK_NXAV17?jZNQ(st@E; zva(_qX)lkt7nv_ZeLmKEUxxd}GX-X~;1tVG|FjaOLoDjxXZjiL6jYDZ^}XsX_834Q zCoRF9U_vDMPZwF=vQ=2QLiC-BQzPAo*h%kGA*3tqi(-y;Z*q0Gj8{fV4P|p`zW$I? z661OQvn3bt-?5w^FAr}?VWT>xoS6B$^g!OdbBNWa93&@Zl>J=uAr6vsRw)d{Se)KC zx*$?@CslOZ9O0;H{##;tCp>cc9rOHKibZ>VYop}Xb7qGU_=<}tfhn2G5_qEl3<Nvn zm3(ibW~ciXST_8f1iFW5ryeVwlMEctmgKHxtN(gRfN8ftXj;GCO}8!i%0+10T=XZ| z58YlzqT_E#sncZYF3W-Z1Y$_nos<E<fVU7E0Sx$F>tAoxY~Y-l7FFGT`dEla$qkhg z(s2?4LP0?S*OY#mCm&<PKTpBIwP1cUK9(aeprZ)a$4TlktZwn0P;qs_3_iTT%PDx* z8qBGip#G`r0jm0oX7N)DniZ3Rt+WeRo4*9J;7i=-=vdsMDue7?k%FvJ6ui0YUpRA7 zj57sAHBu|V)8=*Qa^pDOR_9|C)q#*V<Z-m@le%bPDH2ZTD3)=|Gar988@Jd~p$d83 zJI(TJpSZ>etdLX6Ni9Ful5fCeU#z>3a_ihZ^+$dM<;#+tKZI&Eah1wPTFDbsmz@k_ zs81YhhJM1%9WUmlpge*L6SLr(_jO_|E=t1NnN?BIV;T_UqF_RT#S|;9=ny3Vb2Z>P zE{#eG_htXi>DO#F^o#flV~@`w8}=_QHtRiYFjnGcDpF3cSo9YMM#U+-EfCJO3K@wZ zeu!ppXD0LDp~N2YvxIJiEErGvIVs2Vaau`)<+SZ0imc~WIslZ7g_|br?VVMo-KaM_ zY<NQCwJBBa2L0IKaG=smK%iCxOTN$+?2PF7zE+XP$)Yk6!*NbfiuKFtP}c^{sSL$n zKR49<Nx=(-kbojy3)0)~r<}!aT=+UFbLTaOZM^a;;?hv$b-Ckxec>y44ew?G2P+d6 zVOR}u8^&4;Sk3cB_3ShFSd&(k?n?6oS-d`d!x;nXo`Injdj2=5e14HAAYycCJO(gV zlKPx)oh|f6aSfFYKa!x+3t5A?Ph62@i$5gR#F(UNe7!$#6oZ)4Ati!13yohk-q0L1 z?nfm3KdO?{sCUHYi-W|xgDD!cwo6upqU-U`M9g2U1j3Ah1ylU?Tz!yg&j4-l=Tg*} ztY1jNd++7N96M?{a#NMKbL@9B`VRg5;mmz2?F)y9l5$Ndqu%1tBAEPM`TNTpiwDou z`U8loQ<f&^l4q>E(STD-&8s%Su!B!ei#CYieL+E)4q9}woEs+316ZvC)m+Qa%m1{2 zAc*su+MYW(b3)j|yV<#uu2y0{Os1g}rr@EoTywilcKkJX#->j}Iv9SYTy^_ir0Stx zetr1G?(_qCa(N%Ha9jQNyC`e^YuEV>2arTC<Y4*vdiMfznF@i<FSa)b4tRiR{feoX z|HCHHn|ZVlaO3L<)rPFV|JSPmq6a1*8OSd877!gN>$`qU&#xRd<Jb9;cQ8vQpR_Nk zSSK4#fA#Hc1#<{k17W}Q;uln-^=>zP2gro@S*B4<W7X14c0l0O8__Ya8S{z$gM>;E zh88p=`Yj`DJ8z}3TnQMvK{9|ULX;#1i(Dg1x&s9}Fj%VLOXRQVxnr?*I@r`M+8k@G z%1}<Nghn^}1}7f3N&~7$Uo#L*j%6w~>cxP#5go530?K|^oRCc&Zj!7$w4(kSwNSWn zo<yaLtXm*H!J~}ed20Z&EU9-TS>WlP%w?CNIdgAsVFf43#Pc5xg0^C)XE~TtQJ^s( zN-kj?SUpcFD`TDwCP$qf1J37TP~4<dt4Nh%S@t&P7_iTL(rTfSthF%XN$)0+-R|^O zw01dZ-IRD^kjiJ#%)Ok*4It5%Ex{G0^`(}7)hz!(H0#Nj-^b_oeS^AJ#JS^+A5&!( zd7|aT-6c5n!<<u|up?1?7}O@GjEne4vP2oD^jSs;uNBokHZFq*-JemnKa!Yr3oI8+ z8GKlWFZ(0<O~HVA9V42gg~6f_%KKy!0b&glF+48E&b3>ix)A+@=;uc>?rL%@Ocxu3 z_1fTIDeiV*n<KtdULOh`L>-23LlQ59{)_{fT>DCl67?*Czc1{wx5h1<bA{h+^qEF| z)x{iNV7B8)&H-)(KJ5W8-LE@wnt0@R5{Xx^MP7}kPM_KAI9>HnLw6RExg68xk?%iK zNGy$|l`6>ywnX{Bq1fNV2y3kh#811*a4vI0Em-<8RNz?l+VbLCHhxxZgA`Kd{dBfl z5fxoiObl#q!y1*wgfB*U;tIBvi#(|?xgoNUr54%Sc)RUbn2cgnA(HL81srn@iuwF1 zS-<p8xaJL{stB_3rHErwmuH_Eke{x~91gix$6+&CA(MuH7tZ-x9#aQ+INlkkKO8(S z$!qJt@}vV7f4l`OhC9YRk1^NXgI#z*xv9aOyby<}xeYbq>jLX@ldMXS%OYw*9F_1$ z0fthGD*m5x=iy2{o4;kTp=v6h|Ddqk(rYOBb~<c_her63#n>UiM*iEfl#tC`Mi@qB zRTCx}afzVs9c$}8W$I3G+0Tec6-MBQrQVV@8*cjCoAWmFeept5987blxER66CC0VY z$@ug169K;mhAt|=B}TyPea3~ZApE|2G%yTmv!5{sW0|Jfd39EyZ2S6l3;}93XKbD0 zH+~dU0EJEAyB+sRFT8>$Yt&sq4L7SEO3{GRkHp`@!-tBqm^cnj&YdZFHnrL!p!<&W zzR>|FPC7V1$C0%1eO}j(rr7>&MpbF(6`I)ovffSA0gRo7NNd_<>Ti%rkII`WdyRZF zztxlZg>74VycvA<H-fzH!ME|iKoN(CHTDv6$%s3kw6Ir}v2hsftU;v;v(Cb={S;(o zxP!9UQKy#-5=<pC^>AR{V38XpOeM&D-7GAZG@>;iU}5}9A2}35yod*4u*Be6pAzN! z{oapw3HMqm6(fP*UV+k$5)^oF+6cQwO-FSOxpyTk!#P@RR6ox#*MQ+j^G=n$hd%NQ z?Hcw5AM}H8`vq%iYD|t6s!OUz_x>On^`2IIKb%8dbHV)~DJdz(#RdQZYd(NvCWCc| zX4pdsCHLg7fUe*N8l2zYZ|ZvIIT&hUfW;nDy!hR~_TVH9BXfC-LyAU)tp)s$Odbj` z&7>4rN%s#N<C8bIQ0P}PeYGUl{%%orZ<9z=n<#ds(;R;N?J%Tbe4NSmpJR`y$n!z8 z+?ozj(MhRz149znQ^ex&8+Z#=Tn37;Zr$I1b-ke?q8UjcG~GESGHVbY!;HT&^0Cjx zV%eJ4^H7Zb<8dRyxk@fL)!;z3UlU+LQC?tTrj%ro0*OX&9B(^{M!KFr^6AxpE+Ron zRA@VaWKbS|=9kTji5(`0B&BHUp#gy)$!=-!*s$^MvkiwW0O!RM@enf=XAMUS-;O?W zeEOFyk`xOq-0P<wCgyGw-x;<TNZS?5P9&nPGV9k6@%$pr9;H4vM2#Jed>$^!ktFkh zYr~S{LnHX99y47Z?oKc+Rijwbb93z6H<5po`*5AKWcSI<7&@?MWsn1BC8)9Mx?rN@ zn;1~t%Ly{;AxagrDNrCvWTjOxeDW0_9t_O$huLmJSqaqH0<aufv*)OkB+jf8i>5nY zEoip)eK9md(Cd$*F=jchg0GviO`Z;{Lu#iszqGw7x-3?j^~>u<)gNdTF3A%RapjYn z&ej|jOs&j@%u?Xq@?P0-FZ9=(Be}D1;@25D3Z7qN@A&9LF2ccL$-P`6JhXL&)2hfF zwi@pSWYI5*Hc+%^*K;<ye1@qoJk#3|6i$EA!T#}6RJ9PMJaDS;<&Gd4eQW<AkA8_E zf}9#}AUc+L^sOdUeh5}rGQt-#cVoFJ8!8_vh{XCy1nY`zET{hnoihLviD9pkS#swv zF){sX*J7=t9K)2|u>tT<zl$L7C!^w(lxd<f*s;#LBip5mStRvnpi<kpBap#*xku=r z{r?ioCyioP9a)GXD>Ll^SW^AtFJaU;+8&Dk*6okz7eFtNd2m7{eDZe`CkJDC&k~ZO zBXqrB+yYj}vt&nZ++Y5F*_`$1IUITZ&@FFhTMJ@4#e4z_0iO!mNz|(|fl;`+$y`Xe zaz*cmr$WNdKrmbk6BxT?lk_w|1*kN=T0OVc@{uV~{qz1`7eZmmdg|vQVKfs-e6(n> zZfnKwxU*VJ#_KnZlNrS9?U|UhZNzbD2{3RyDO;EcTWS^_M&V{$gpZHZWT*qD@mfGc zivQR1Yk=kzNEs@0^SsBXcvl&d29Ux=^s(ZRq89I!0QR~bMfPM;ntdocApxH|%#x5z z!-ag5!3WqzI6tbL7~Z48HFp*e7S@wJ0(>ZB6!+pld5pU06Kks-9rPsb>j97C-20^F z7OgFxu6L4$`mI~(u+lCsPggMDii9}6dI_qwCK9J6OK_k{<aeUgq4FWcAh2`mBnFz| z_s3~f#NreL%Dkh_<)xUZjHDv|7I8~Mk`_x<9pY<8fjK=g<DAV;C*L1U_TC(77a`6M zqJJ{BG~pxa4bnxTJCBW%XpFO<sMf-|fYQ-1m}~_{iutsx*>S@3H({Fk5r@7^F(tnS z+Zl0%<X$QG)RiTg@OV^`K;^t~QL(TqJtYFUl#Rz9dzY!f%$B)^o|h2VvaU8p+m{2S zn$s?CI_i4mbpe}EjpnU!f$eL7lbC-oz0tytGJ5WJbXTMq!cIjtrg^siGBU^#Klm6Q zwHo$EJn_zCYN`3!eK8hh4CLP_f4DGMePQ2CdT;Jq#wB`DPtHYoxq#W>>2^nX{Pi=8 ztgV8TW!;-*n>S#ZL|86=31L-fW%`tGyT5d-Us$VTVW$ACsOFJk%q2A{Rkk4_HL}A8 zx4;NFrC`)uqESeKIKu8;2rs|6z22nfRJQ+c*>Q;NbB$wiraE9?npgN}o|X>c;%$k6 z{^Swy5?^x|Vfjm%cA@(Hdwtve-_rB+wMQu*Ho%`!+sD|hmUNFLytBM_Q<BWBE|+|T z9UAKRjb(e&B=aax=cxZ?)IMz+`NL``rohTRK8|Sosi!SA73pa7s9*I5I*vHMHKx!> z2~Q&=MQOEiXRwqq8*dqu4&ID8le5a6j<rEtrApL%Wl$lNS4i4afrKt!HAIHvQ9_ko zqh|bz-eM?+knBI)|MLRyQ@f<8BEQZ&kaSM&1EyZ0?N>$w4;ms0?%V}*#H?>C9td&$ ztgxZHe!jc(#x^sT+PN(;#}40Lw^cH609l!t;7aCy$dnz54j2KFbhZ^O@|DtUhAV$} z=?;a<aWF{mUcU}mu|5lnX2bJxV%FXUCr4HfSK|-j@T39n#4&+at6Yd~)t=`3)IU-G z9GpVCQm_Uis#l$cGi9);mSO6b8J=EBE`^*aN-l0bS)27v2e@Dg6bCpt6t|uG5*{x9 zx!rI~7h+aSt&+(CLf2y@aevs)RLc+*fw%%g$AB5NBpJnn4?{9x@#$YHVk|3kXtt!H z<0N7}$w}OYpQYtCt*1pF8OswbaPEXjH$l<hYT>jPXV?p5ZPJ?lw3*kw8sLvP&(F*h z?O?_U>J(AoD7s)aH#e8a%Dj~3=XT4Bg=kGaQsmujzxu0&(E#=Mh^Lcj{~|SA>6W~Q z{N_(RqmF<Fg|q~b|I;^&Ff$5Fs7-mG$XfmYQ-Z-tbR_!S*`gLQ|KFUU6_gkmXv5DO zC@+&{9#}S%lZ%Ve@ykCYR7m^Odw!qEI)^~qbWo0_L=_n>RF0=cd0XwG&3f$YhAA#J zu1r|pY*!)fU~y{(;R1W&sO3BpRFETTeQ&DulcYU#>QsMOd1iiPrEHh==4gpl-}5f$ z7k3L~I_#(SCo>Dy{R@qTZKmTHbb$F+KGpZrgh^;{!&2_?!g@Cit&Fg$w?_Tl*;1DW zf_7!|;gea;Bg)n1@iFJlm#5S3oZ{BH<qhc?^uHOp%<e#`=sh0@L7Zc@whK+7r~`af zPASONC)wiDi8Va<flhq<TcM15k=~#2I$%xX&3k;-V)NM#d}Q7bkR{jrMM}93Sux-g zmB#0-h33KS;@0^&)e);Z5sF*%>rSc&-bNHIaYxelyFl|%Fb#rPL0R_}B|1bkv1wO0 zGoF&&VN?Vm0S2QKA}XfFjy;!Q`G!dXdPX1U3lX?h(>qZpN2bo*MbHR!KN&#=VjRZY zek?)j%FY13=dX~WwPQZU9=+Dtt9qpc#`WR2ksI^EK7Lu{mgi-;^9jg7z{BD<YW5_w zq6!uy`f^C@VBglF=X^AhEQEYs{rM-tZlmST+`g2>6KEJQLfu~~N6vs)%-ub;J%jDu z{&aUMk_@$m(V`L_q5JZlzGO;}6EX_q0P?%3QHH})G}@o>gt02-gU?@+RE)8G{9BRe zUP3f&oNoe#qz0vc%32UFI|t*2JzjqIKfXo~fzRj6g_@;l65%E`&%v#}%N#5KYmcJy z4uPH_VR)=L%C|%Lw$+^+PDm2qErle9fBsLo1C;6?Fx)~#=2FKhS>}3Io=6srp9P{! z5ES1pa}vTI8(rU7MVB6KN*cU8KXH5;PN3k9sgZCjnMnhMaclSkNY%$F9i@5h@2yoQ zEJf237D5X0^I*qnA}w>|^B*Q`GW(t>vgKs~^iHIx^iauIDtK9m04rq<_?;LOWowD( zF6s}zjo{HwTFz{l>a5Wk<0x@MwBy6G%Mhih!kP^xXPhHo|KZQBg085t!=sLp@$Mc( zS?fo3!U+c9sjMn}%@;>=%~Pah6#}suf|&WC&TNdQi~7g4A_AO<OD5-&Gc03v(;}A} zy&*xTe}NRB%TZTWDwX)4v=Pv?RQJk<v9)ur!nJ*1Am$DqiTr%|FzO(~HQjb$4P+hh zc3W`_nVmG8PEq(QkDsNP&;;N<t=7l!>mld1m6)ior1a;VyD$0_C_5hu6qq&wYe(F5 zISAMhIFr?biY#g>d<}vbvIx~^WPu7l5eP|w%(O&?495`87in|cD5<Eq*#`Ux3;RZu zaMQG+W&)#vn12TtP+-O%))p!xxlKEm^gvSdkp;?yeXZeBCJxi}?u8*1?G&UO=ji9N zr<`8zZGD{uycisDQZC-Hq(d7yr|q^x9nP__9o2_{^sIO4y=*Qdb=AiLn@lYa^2e?> z(eO%jy?(9hk}}U<Q#K1tpV22o*ectfTaH9rVU`w-sjw$mAGZoad90RX{E)nnxcG{y zl@u4g{;vMh8;qIQy0N>F8$v;#WgQkC&imc%>Ovy5YsQyXxu7IhF)Haxsp;WKKu9%z zE`t5#Q{))af{ei)CTEWob0bF%{<E~`FuCLCl~6CO@D8UEk88O~z@F#f_anYNJK-L2 zDOJ{w%GgI#R6lka^K=FgcX>7RlV-LxhWxSCB^?V#>ST>gT(#mrePZGi6OTq4TPk-2 zss_jK)OOQ|VHo3|AKM>h)%A<7f&p{AzKo9C;-~Su2y=*^pWCgr6WMO|fX_W2Pwxs7 zUJbpGakQBxT$f!+sGxMH@z?M_$jIw>-qX*5iyaRzW#R_;Xxn*_!W*IlE@!QQg>BWM z)czqI@hf%RD!x45a`5)sK7@>uyeEv{Ft`6nJi0PCl>%1HBh#xrA^_RKcCR44OpsSa z;b6I7_I`VgDCYP!<(f}NiMh5ZLs7KEBy2a~?oPCj1U*flF#SIC-)!qUPWku;;sxG& zn>@mF7v<}mw}IWHjG_Ps>lIF$!-oG@p%(&<eyy<4xeloR$qfKKlo*Y2XrDIg=)ZM1 zt}tJ89GNe6)#(U`SQcadM)~3hSuunWWLzTxQ|CER1I7d!fs^{4+?0{xUz<UfMT@So zEo?IsL9yN<g_M=K7^YgIjK2Axw6?85l*CYuSkEOfkN{gQ__kUCTb3#*khY6pVX5hd zZDqTh8`?}6ukJ}A2ehBKCpMvwC!^0?NHAbGFv-};gftWcDf^lk^R&!65-YU)v${bp z_!~wd=~q35n?D|vI_^Vavha(hp{Uwsx3#$AS|1O&a+f`WiN%eX;~;dKu6ETiHWZNE zB+|4Z;xSIe^*AVXvv^Isqu`pE4G&H8Xwz6auRO)0+(p_P$Noj;S4<hSSWnZcf?(_P zhUNJ;)8|>9xYkHh0Tkmus(CB+Ny7S(uv0?hEO$blq)m_?gZ4zV^@czx<_xI?S`#y# zg-Bf6zM&Aerchr`#HG@IWzpPe0rMd2tM;A9-9pb){J>OQ<J7|X$4jkJW0)mUEK!%9 z3QyX>d>-P=STwvN{u+Zh{2>bD_!ZR2g|e=i)OZHZbAL4m!aZu6Qc}_ombNz_sJqje zE7$*JT3o#GiE=n!I`X%vubYKMy7i82KxK~T@Lp<aszWa(?;y}EvDWU5Cb>dtEvuas zG?ZMrOFLT!{BG^OY1pNp>|pQJOsU)*O;2nCRw681y1epu1eF6~oDU^G)r*VmwS4&P z&ZrdH=X-8!(&Iu$rE9XOKYo-mHQ-!PqIjxad07uQb4(7)(`*v{>R$D>F(KR{Wqvi= zM}-I2@$z^>5jRz0aRvJa&8LH_xjb@HnWmhAFZSJBkKJDw;vq%m->QcOA20B+##-?Q zi!^H3$(9Hl=klmU$QNAm5V(YwMJ1$tJ)<NRzqm(3Y=bAxP;Q^bVuE7A7FFqoQbwuc z@Zo(n3MDd2j_j&2^;BJnQ8qT=upTB62hz{=l$ELKO2iZ)_rbfix;6<Ljvfm(_7S*- zB_4A}n&&T%V=qkVEc*0A3o(Wv$pqX>vB9LDEJ(Cwc^`RO-wf}HBRb@ji7n+`m63j7 z*Jo$xl&3k<)670KRcOu)JjcQCR8xe;&dI+KStg8_Cu*;+G0AeMIQxyr0f!8m!gN&l zHs#FL_H*_|e1EjR!uFt5ie+|$A`>cTF7<=PH#4`Y2?h9;ClhV|<0GC_kN@c-dT0Pf z$ktzg65(<r%J)rpVz?7Z1{MN6eZo+AGv+wL%^l40ETB_Ee;sxS)|<=zj}h9yUZ)`+ zd%LvdX(<zO$j@CZqZJd;APua_TFiVdjoQW6C2~c9-Y_d(UDHEH8NCEwgFkAF`hKk9 zPqLi@3t1Jt_!@yX6pP=#_s3(bPr}7jmlS^fA;&~lc@}sS#Qz5l!ufS(PF@Jb!hPzC zV+)Abp2)E!Cqx$P;HEcG=KGUFc?)Cy7gesJTtl#*87>kiMnUgAYiXD|gi{PtX$bQR z%2bi~Q(Ga(pNNb{LS<`-&i3WgV$(G#f1Pb~oF%PvR9w1ki{?q^4Fxxo0^UMGq$;93 z!2;*>``nF*0jYdUsp+X0vteJ0x_R1Y*PLB*W7T}z3BYkWz=A}DDNO>4Hy(P)LP)5Y zokra+Ag8E3H^iUyo0ElzS*J<aOG*`el5MR)WJcM<+hfAHR-yl4#ME;yC-d{DkQ`w& ze->ZYBQ%0_Y=?rSH1718EKafYdgKFyV+z?x;JZAn!9I~m{#485@+c3)JF1XTdjb-^ znif5-cozKo_Cd;mUTtn@5RS7%)26^!KZ1w0kuIgqbDRbem8n&U!#+e#I!P}c0_tIK zYGBStXIeDo1kp0mL)=ppjk{7if}0I<ikbiq_nYg#H|is=(qOT6gr&6Od)?vF1$jc< z_kcw}rjC6d?(7%++Fho*KQE7;8Vqz&v)2Y(x+>3slAN<PWN*K~3}*Q=jad2eOTNFs znevt$?)(ewAs8f#e7uJxa;v-+fI}-a3}|3Wj`O61c<0(X@4Xg*RfaUpubZh&7N}(9 zZ;0XY{@ou|#CJY+KHGB7l9Q5}W#yNHveIK4TB;?l-zE&Tuy}kE;Gz36#|_5IqJP*B zp-3B(qQCXdF^R%lLuCo#-as=Rt$u?I?Qn_M8a0^EgAJw5Lcj>2->47kUKi)dfh~WL zF;c<(7hQwU3H`hxH9ZM@64w}9bER^A&ohaX2}wP9cO`5kZ`#~Zh%nN41yOi;q{?!R zg1VsIB<R}2++9v9&7xuRzSFRV_^_q2;o><6DyY3YvSq~~1GX21ibHB+5Tk)+|J?;9 zDrDp+3iY)!?d!LLvXH^X29oJ})6oxX)qn_-Ru*jR>AIgnt2gnS_7C`5bL<Jrg2@dm z&{ig4K+SFROTc-6rBt<^(8U8{IvW++%6rl54ccjybvDm9=5~{$2+}N8*~xvjsim%w zu5#-TuZ1zqZ^8;bEwU$h{}od(0|H;jouVBfes)6gxrZrYsjBEnKQIID;~~^+%7W*O z9ZiFuy}H(B$F&Tz!G~yoAe9l&F`uaN=^Fmu!Oq2g7Zg0{qEWO<!~G<!8W5;OOQ@O? zHebdpE)z}4`>8*QoXI!`v9Gp$H`<i&o)V6X_ufP>o!xO)fi4d@&&>wFveY;{M=tK6 ztc8|>gcmrX4mrg?F@<wUe-`9vy`f}E>Xi=P){reu=(aVpR;}d<nEx!&Nkw6}PCycI zc_;^miyugrCq&bwtPJw+nH|?CJoB%Ci{LcJ{5(ZHX#gX`ET@hBKKSlSOwc+a2sW*^ zcWV8@#UxmPES`ERXKX=7n{~~zT}t?Q9e%Oi@mF14!$ELH;=B%%%c^(lXgaU@6T7r8 zNJy>fcZ7KWbxI<rIu!%5-$>njnVMZ(b<pOB%=IB%J53a6t~guQLezGxL)S}aTHz*F zJY)u;hAvN&ZOBB6wdz^p06y)4mr!t@EzJ>TdI4-LB?3Ff!6$vz`Xl}8)YQ~vXcUqt zib`VlcBAo@Nc;*N3p{5_Bml+exjkLC*L-^sWfatW6Mua9ORl%y84qrOluk=jnCT4} zJCZ;Tb9$p5`D%IpVga8j!X=JyN`e>r2T&wNalr)l+ixKpW+tJ1Rexn-G81BDAOou6 z!l<P9g^Gx%LLNUxGx>BlaE)g{wZr&mmVT#yYid+(k5*{`jbpv%{m`$HVm)0|mg*1H z7UqHUQ1vp6T;o`7`@8}JTKMI!8eiVow%;WI5D(K}Cn3qt^0@d++RocPw(WjW+b?6z zx(P-DpWc4D;EMq?-gcxWy$WAQx4jOrw72i}vXWiaUz$;l<(1@<ODkIByn%%2Lwckr z5!8MWAj;!>Pw5$>c5pxqFG;eNSR(Z_Cr!KGHt6M|_ywNi_Gb7jmKH>^)Dzf79LAKh z(NdWsF=w+xEAWOaR&JCS%Fu`J+YIxxGnkIcUon{Dg>n4OVoJI!vs-w87>n!cgeUT` zqn;D%nlLqNq%7Q(hZ`ejN_@h4@K-^_dg<?6;&>gBQr`-PGR&JD4%z^U3UrCR0NGnM z!CJ;m8Z$UOUi^JssWi-t<&`X$x~<a{;D|U@^%_g^p0sYIi@$H&V^^08EMbH<+U9n# z^GoSjhgYYdXMo3xmDjM#qMFh0u$#8VW`K3ks|joXhQDCvVOtmQuaWFL%i!g2`1oQ# z8*i?3=5>Py{mL;41LGyNPlb>L=teyr*dN~=sIrAE9{KYqlag@YMcURPU_8e(5j8uV zFgF`*C|G@xNkYhc7ok%%10HI4UwM7<=|~%<^#r%sHKfZN+x69RTnLXhubI6GSf@8s zv{Ua{l7nT&Yfsq`BQ6^(>=aCn<a!`98;?{A2Bd8RoMfo{AI$HCejg$StY$Dr3O zs8_Wl5(sW3v(0$m&0>>%zSUBlEb4)7tMfs{PIz-~@A^w@VVgM|&!wQ}tsOU$+;kpD z2=aa|C>$O(3N!3y2MYY;Yj=*xMy$hmgWQ@@u>|q-?Q=MTpTe--OA9vT|E$Lt#2{y% zu>LL{cO`j#MxI=z)f8L68tQa}eg72MP18!v0xy60E`%$V8d1abY$--3LKNMK*0mu7 zw^ip783)Oq3hf4mD*ND+pF-lZgJ*_G;jdS=?%QLL0$sdR0fT=+R6<*C$POS)iUnLN zD0kys<eBQi2O`NC)Y<e4z2*1VbYO5lYRY0webHFvL_kU3N7uQm{q09BV<jFNs%vOq ziWJA+F+g8)$S^{?C>|-!3fkS-DL&9{NF)gb72Rc^6Dy2JVs>PEt~H0f6A;iY?B(L9 zw{H%E{0_xwdC;47TwlPT+z=HzsGSg2<y^n~&`$JR#XnEFxmfURx?cP!d>$FMb;j8? z@cMegIFY^5Q@P>j51X|7$j_*>vl-v$?HgDk(w&;(M8z>PYnaV;fBYgC4GO*MtgCyt z+L)rta}tQXEvqN$*w4urQ+kzPJ+OBibcrFRo1Bkk<Kp{5l}YMHw>BMamlg$bUY#jX zx!SAQ#94=JyDZN}!**b$d#}cL&zax-vMA2}(I(@~XhqW_nkLfX&{!r-?>}84^hIN; zaTcoMH{C~<7ZV~3fV`xF%>T%qxrqPqtQRZbR`<t0QZ$v-d8sk-bHfd^uoDMTv`jWi z!XotOxBi~qUsv4^trr`8Du*X_l66_JdLpjBL#C@nF^Wryz9`PWoOV9Thwb0H50jzu z{4UsxzWIN2y=7aKZP=|X4I-`5Jpt*Ofb^uhk?x#ycQ;6PH_|0YcSv_gm!tyHUGK%c zo@Z_A%ljYBJjNLNh-OXdBCKgsB6%3zcKn$v1ETT?&(L71v5W7x`&MVTqPHY=G4f&R z_x?l84*R`j@K>t8-@ZliMkl+<Z$O+v9H@(neWTyD`?a&{a_zE4cW}{m&`sMG&y9l_ zB9Uw_Nk@@jh?iij;25K^QE9pg*}{fb(^X8)vUJiv^L;3OEL6>a)O#sIWiZws$S?$s zXw<j%z%N9D<H)Z=z}-J3xoWxlS&^;q?FK(2g}U}DY+Hyymx*=AOz4u*>V5kc&y-?y zh}1_-q{E5Qw_YhR?$*i_Fla5^c@F(phF=>p)xtS}2BY;WCYvcC7VJ$w@iLb0W~;nt zMr7QF!c6-3tqvg|@Q<Bu-=xG%i-swz)wKBB5@g{~h{NF3sCi81*&Z1WJdbl@+XP%# z(F1bGPhSg?Uir(f4C%+Gjh7Bv>c5)uspVai-Q3H7w+jL0Dex7)RNH!eV+`!>wOFJ{ z4j;w;7#bX_)%E|~^9J^mkc5B?peJaUJvMu4r0`_&`D1gqI2w<PxJp|F>LuSw(2Z>3 zTm1^YBaZQ*)~P1sDcqx;Af$&MiV4c#$){2MXJvqGwO(SHd(yjFV0VJG&%=LLsuma9 zfEKN+t3vbP%a_wFO(roaQ<g3rM8?Ipw072k`UYvx;u`f^n9%CF=_s|ggOsbVk<Zo& zr=h`d38GQgRR=2ca>H~~k$k<#AS19dJx&i^G4()@yL|j~`k&9C!v|Y^Ag51znLDBp zv5_gw8VBEnP%n|gyXU6@eV?wlOeX*7&IC7_1@?D5HLmOe`Ha+-)9K8ltRiOw+?!9# zo_K13Rd3-R`0%ir-|AiZhZ`7S^DxAo5Ok43zdkIP=O$xJ{p#%%j&5?I@1l`Zo)zV) zDQg+YzRn7g5?7&j15km$=<t1ZP?$fav}~ZDTI`Vt|6KKdR9EU$pcjE0FmN_m_Bo1R zMJUTfI-5a13mhO1eZ=Ec@7fUCZK!j-&|CW9RP#Q!GUfi`J!}~67hAqJO9x6`L$=k6 z60u8X6FscvRMv@u8V@sD2?6l$%gv=Y@pw?q9&0>~>*LrXO-~Dwb42gaH|26`rtSAK zQr<JB%8tS;eU=+Qmr3Hh6<lw%i1dD5qB%cauG!tpe=4WVfnWGwJQx#CU!y~}P~EKc z(V!1@_!$o>LKy9)Dl+gjIUjwVXTXh@V{YB!hB)nvDJ9Afzs`G!?QUr@2l5;u{Y$B{ z6|Ka5jw<%J!pFbeX7P=>`%zjuQq}%GnL?ZENxWJbh5l#1fLDmK<v?~OUg@`ce$JtI z7Qd5@n_ZJTsr(4{`v9|z<_P~D%hGcmrvv+2C4dU(_uD*ymyxizsnEo29YwRvG}U^| zNs%L8n*~XcDe7bU@jHUAqnqwVLBhz0X0i?9l!5I(g2L&E9iuM<tf!yBdgvc*MlK{J zZNm*b<-xK+I2_v^Q$%h^40Qe7S<VAf<c>?Lv~?z~jj}|^=qh8UPotOXJ?+7(N!Wbs zoq~u)CJs$bA;(B^_KE&`=@;l6d2Ml1ZxYO!?`)%U;;1nA?~Q>*6v4z>@;}P<1=5{b z5B!`|J2DnN?2W!t!T_)8lk7Skk~TAwY%&j|9n_j1Xp`W4cZngs7#1b!vG$?F<?lPJ zq47^t`AbYx`PwF;dy1N1w+tO8&6p?|IYsP@s&_<<-r`vlihFQR0%ANjL*({!uld8- z&lJlPtf!M%f|V$?&(sY4cBlI>pV1Bi1kbC(aOq*U^I?2=!FetJes+yQ<G=c;w7lNl zIy!(Q?EW_a#sb0r3euCvr?HZjt(y9Ia|zV6a#a-p1d>^CaeJ}OB$skFlh+v_4vc$F z^p2R=*agy?#{g1KMUlu|72eFS4s_jve^%DVfp6TtU2Hbr*GS<_7g0^-BW+t?144Rp z&?IysdpT|k{ozQ5$@1FmV%2u~<_MH?cnZpg_O`aP0E@l6SZ#!^e8*ig<b6_3Qtt>I zH%fimJQ)FCQZ`beBrple5JSW_oiJ+==;m^-e1js>mQeHTEhX?9UoJm^C92=%J)4-Y zF)R9H!nQHs(NGdcmrG>r3lkP$Bo@8I#Lo!41EE}qSVviIDzV3Q24<e36Mwc>e7?M1 z0Rmu_X{zh=a%gf1(b0s!Z<b&7?FLo7**Ht*96xM;R{7i(zfEm_kf0$(-O`S?-QD`E z5#q`<=km}v2ozMh<vq@^P+BI34FQ$j9IwzIARtJKU##u(^?BG|9!w`BCx7z|YatIg zVc;l~GH^mS<p<u@RRDV8G3+&`$^~Dg(JB0|bKjP#Iz8s%+eGrJL$_}Lg9TEoq)IDI zIf3cQI^Uh=EaxzND8AjTy-i5V6(=<UOf;g^`osx=|0%NI%EsmrW#^nMn{hhltttwX znFr4bThVvS+dQDroKievJtag!+Vd}e4WvXQra8iJYzmZ~o%qeH8N+sij__A?ye@BJ zH?A8NTLZoL#ol=02lX0EOZ0uS{ZBbcWUI1XXf0B(8D{g-pQsA0EG>6NnLFXXKeT(_ z&8sVieJ>P?2Y4eAQht_IGzPNotyY{@)|q_Jub+RYYl$N*>r?301PK1!c#^NG0UPuo z>~7~Hkt$Zbyh1(9sPiS8$dg*u$jU~AAQ!yiyX6gHC=v<N$DfxWvP}W~wqYt8G9PuR zF$C|^Dw}P;%{7S)x01RJ-|*fHIan(~)l?qiPywg@IvMAL>rBFA;{^mKl}J5Pyo!Y- zfpyCy{Lhue0)}?E1b=2z8oYCA5gs$8EFCV0*uv05N4%i2*`!s6)9$NDF{szCIe$ET zL!aiv<MPJ9k9U#%q3>=V;B8Egvt&6*I-fQnV?1}}^ZgJZ(K^eX3Ozv_kwn^ym8otP zCh<GRWh3`$oxsh^`=D@fBuD(+k}3#UT$w#bG6P1C_x2HPCNn{GKoXJ}Z<2`j({1)q zWaE^Ok(x)jZs1*N#m7A(C)mBHozEJZrvs!OurlK%Sm6^F#&!fp0EtFa7zG*`L0Q0v z4R<r<k>SMR$EBvK3M3*n(Hjmm1%(WBbXrzfzx}%N|3|aEo5Af449qV#iM<w_{&!y^ zz<!xx`C)STZw-j#kSS&7=Eicg)$2O&iv5JU#KKjPsy?;Gs5mn}>2p~#8fWM5t3*^h zXo*x)Z0<#b5RJbGJl}5r6$I1MS8QAkG2UN~D=JMKdrd9Q!4{OmJm()4y)TZ8G>B<~ z->>^hKwD$$-{_n;M=pnhOmF|8oXRTYkHxPQO44K10WmM8J^C7nTwr1&r3#59IQzaR zm4k`H7_n%OkdpT1+#~!QX$CM=<;a`O@8E;)ccK_09~32&)4R_`!$v8Hbh$iySZ_O_ zE$h8YI=L#6N;DWaH)}a=)HeD1qbwmQsmLjqifkw`E5WqR=Tv0u`I4;G7kg;cSri+1 ziwdeTnldpZi0U<1Ca<FF&w~o29^>36g3@iV=Tf2s5arYpW?j*Z9?J4Z`51ysD93G_ z5+`31p3Lp1fqSNF+k?^Sj>+(R9HopXRX*Zf%hMQm<6mGc7PgG;_8x5N9u#vje%T;R zFUs4v&458Sr7+{{=NdQ@Z*@MOD}FyS>0Yrmn7N0b_ES#u;mD!GHwvD1Q=k5XlD5xD zgUak#1Fw4XKFc=Kh&GN6fiZiozSLQan>*Sss0~RqfcghVZqJN&X2enr;JW7rRJq48 z2Y=2?HcD_Tv0i5g!DeXXtu}X$6NriZJU^2lrA6tmS>c3!Xc{pYS{A|JXJ}gYtQgUi z)u~(%woyA>P9cuseo78Q<I=9wZBrZcxI(H3>7rs|4;`IXuZaURBOu7atH9+W<}Yf8 z<S{gEdwHTA;oPvOYJ_~iv)L`Da5bclc2};1-GO+MeEO908T93&UZ<*U`xBG3Y-!?& z+fh8_PpX;VwYN6$-{Vk|s<ug_@0VE%$DIFx?8a|1V(epB0{-xn%)1xgC_DSH(SCl; zhC2|iBAwL4c%EH`eF~8<I{HnSVKPuOUH5yYh0RYZ1=N=z>~C=SVNPz;oA!Q^YJ+Bi zoDYZaS>U(b6lGYf<QJD@dliIzx&%O}#j#c1)XWtUu*BD2Ap2h1Ub|UW0rJ}OUdr{; zi4jI^{a}l~Ot~E;7S-)#Gq6{QCG0gx=k6#8yR84d&}BW(-Cw7~4iKLsC-HeztM%Cw zip`?q=HQ1Ac0!a%j+1A_=7b3(tjAYBoOiGBz)!ph_=gPD4*qY8o{Zh~P5fNNQ-yc? zJW1k8Fa>8<q#JvW`U<M@v*IS5dNSS7;YA_@#%Ltgl>hDz|NBS-B#DqWxGzxg<Q){; zXerUfZ6E<;{-tsB=oL@rv^S>2I0B4F%4vp~zk+S2n@&OlBK7$s*&HbmISLL9GTN|< zTShA^cTA|zbA(h`s~fRIIXFu4>0~Hc(eAWLg~C1qe&0Mq+B+eq?n(B=oW-<H9Qni; zHhUmUmohT7*|V^x+PU{cQ?8*Kd*HM*dHL|5mSc?wk$b3#YB@?IpaaPW_h6$`hW$b+ zwU8Ybuq1S1+P#Rtp$T~3E+#jj;*<ueCNCXQzz^(Oug{Y2;rsXAg$Fy(w2rTFAE4N= zpgp%@Pg!FyQYbp5qYk;PI)t72)Dw*9ID@1_^(SOy0>cTD&`AX<k-Fe+iF@j#$i5X7 zNvf%R{LyA!=6ArT-8e*@rc?au-C#^42vi~EocR8m#{pQQq&AqCp}cR9UDl11KD9yc zNR4r!?k|r;$RuEtmKUtX16!sOI*or7sIAT0e0GNaQRmmm@KGwZw2mqr%3jQgLU+S- zyc8)!9gSil9_x=HAUk@CS?pq%r;AOIH?6M*LTa*!`z_)XpqVh1!7Z1<)FPXr+aPKt zn;Y7!`jcHlzZM~&x{fJMsk{XhJ7YU3?ECIOAdrQIq3*yaUDfyaiOmrtH^v@nPUqS= zGiF-Lt4oqi7wFjFcBAJ3(9(92)%BAD3lS3F<qp5eq>NAsdWx#`o;bZ-C;u7vw^P!N zL-Q(ozz1nvYDAOWq&_8>oq<p_$iK3@I^NRS`7=rd()^Vib|RaqxcpEGzsKN?;+8|1 zMK7-$&M0_LzJ<)R;uhzjy6P8auxOztJUi4!1mafR4Tjq-q)_Qfrcp6ZN|?HQS^-&{ z0l)+50d?I52z8WXsynnh)HUcP-yz%TNYIVvnFMBB&FL8$N~B2QuWUfF(>}=^nyFV# zj`Jb;Lz)8JH|&gy5AzMDFS^labc@Z{iJ;vEM&-3|_2}m<%HJ$s`Zrm29o|g{)JD}a zNFYr`QDp8gM^pdB$AZzBvxKdp0EvziXtvZjPs>sj3RTIxTfg~KbPPhxwhiR}wu)s1 z@C(d?y8i$htnt4Mwm)_kl=;2|B^=)QEJFEid#entB&eF^m9Wyi*!;GIvPp|058$`b zeee_c|9R*h3?eIv>;hCOPeqxSe#FV^JhMbp^ni5=h<^RcrNRqH@ad1j7m*)LyE@_C zue(iYpCau(6oFg_y8=tpnLqqs`Cc5_+w-1ls6q?cKhM=9EoWRmN7hMF>`y3U8MkTq zQwTfh@REo$W0V>zx&gq=k)UFKhS~dp1IZJr=Vn3|bqXx%ZepJtMxl<`0XGyD3ZaX! zce&!^-qsxV@u%L~5c6^6MT2ZNJRXQW0E46p9EKd~{@xx0^&SA&6c4bBzl0%*Xod8L zZewdGHE({S$`>g}?`Lhu(MQmfq)0E?1$JHXyT%=MDHCnxhkkH>G?D`YE$uX#6u?}Z z@>MEK7IpUUX!&JWB{&5e>;h*nFaZt)T>_c$ByYddd?eGo%XDngT!<b%wUgLaIzrR6 zMj)=~CM`591#Jmbq;NqsN-vHtyOV|03TV`bi&A=bI*kL%L!?FqcQ^Uo8ryl-hGFX3 zdLUy7Jix2crgSo0>S}{`GKGpMJ$*cGurytBPEm${#n11SpyM|Fw&nLs$P`g1h5>Y4 zrDBPG8XTP4SYw?;tCNeRE%3u>BH#&WXXE8*!zC;7Yb4pE)NAN*1Sp6!ZZR6ho+Jg= zTas&qzRZYbJttDf#gYn=*K}QHv;*z8bxsTNR|x#(*F-?+IO(v>R1qn+<NJZHrdxeF z!OVCk;N2*bLT;yR<bP*12q&J5-hTV3YQt_THzU_@5_6XVPXfk=;k1OiiNTF_CJWM) z-p;;80}F3faN&PoDAiCT5o1!Q#Nsnk^s<Uz#L>PbIRB1i!$!2%q^>7GD3D(wZKhQ> z!OAeC&s6htqbK%szLSf)2t)DyTb*5N-%Y%+w2dFH0N?C8Hxc@sg706@aZ^gAu5ADx z6)jAr@A7wAp$PMKi8zd%OKHyA%mlB8VYZ37tnN)Ev+*cntwMedpo?4ImMK0)E#eO} zU^MJ~i|-$*xJapl985UDt?4u@5xp`<OZJ1=e#pf4e-q^PX2_RG{*B*wfDhyJ%Vbsw z@L{CNi%lJ32SlA0xI@n=J1jgn*n5Cnd9!lnght8-{1H7*qZ$s&{@34V06#J^GPg>N z^44q0o4y7W74=(lvxb|h{-#;$UuHJ=1DS`ilAMIDhqf-2jWt}&3(q+sK;fZGn_$7h z-9?i^wv~=3SBp~t6-MVm@A<=+oiTB)$V#jXs@p!A`D8*e8iO`j`!SHw-Wy92G2E&o zzC$u)A}Ml$m9-oc=}QOEo=C|K21<TQw<+FkPLL>+(aVv-aYZXi;Ykz)Gq$^(2zVff zt^uK7<OM_zY41e$&a(l+<Tp8Nrf<U+-K+XihVNV9Kavo54=+wmd4pnFG!FnO&1{g? zQWT+}Z@Uf>;}`6$Id6Fxnb~m?MEXmZt3=El{s_}`C-Q{l@HtEBGuiQj<CP|Q{Be)$ z<r!~rob~6%+fmSZBDGw~18K8@ZP&4U`n!v@@7(~qXj1zIcX%OFX+7V&%rf{F4_L>- zIVIyrT0-eTGroim)7+IL*Rwb5_ck2J|HFA9sa_z&db1ZuM)AU(Wiwf~TpvZ(H^MDE z|DGoElDRmtE+!|`2DP?`V;3n=_YEGJqV5kp;&ZcEYLxdY)`D4R0&`^-8Z!^NOVn*Q zNpPd5K0f`fpUh8wEbxB*iCg|%)NhPly&4|n2`8P^36WD9Byv4XRznanr9Wg*Erpz7 zoCQE2eLVi`rkY&rG5)>Dy5a+{D~=D@ZpgdbAF2B};J5y2)!D%1vJn4E_S)v(%v1AL z;vTa-;bGA41$hafn4p4;y+E{|Ghwz*2ETgnx{M@ZL}U2kNy@=7^jMsSSg;g4%Autg zRf8qS0y<ds^FX!lm?sDo1hz{HB~H0E7%rR)zCN7au~B*jf_iS+laW^_a*kb~g!e1_ z8yqA&`a&r-U9bmd>DLQhzq2K2xq2Gz&(5irYHgal@7Bz$bprf6q5MtVkFH32$ZZ=v zLHOJn<&5~L9q8top~s-V>YB*!!;p@c|5f?9*=j{Vt$GGZq3M~tB+q#zBdfIARPh^9 zl3}#m{+g!+PJ`jcW&1xS{;Xw-kO7yFG-QR12+Jn^pvxTK+QZ(%bCN$l=gs>6>!SRt zwZ4IcN32cmCdW!JwF*9xwZFN+aycd~eceBKF_nj}_~O^8%cTtb0ds;VIJTgGf?uU? zf&}#0T~|x1e9sujW9b%pMZFnreuWi(5v=2%aK@r`j_lmGRBYC=lSCC^NSpWOs6ngx z%(U9{$4HYmeBaChY8!}XT2VcN)Xc+Y&ND+yL}NKCE>w|1(8fAFWsmrW2!T;GgI#4N zIr6a-QFBaKpqlY+Y*`L<ktx(5ss+PW8OO6PH2NGs`wr9OdQ*6KNI<+LD!O*G?OMLr z0_deU*A{7EE2+vKZ3Y#y!9Ak+Cak$}(7j^P449YM+~*?d9*Y@HfSAHzeA4+~do?&F z_U?K?LG2T3FdHmLsg|z8XlX8di)egY42b$BR%tf{#;sOntb_88+cud~0bat4>W!}3 z20hoRb7a(b2H7r}&H2xJn>vSm5^g<B8oA3m*_Y1lFKv}O^<k_Ocvcqlvuc87&sNC< zuJ9ZWJ+roHu>v%Ca&kS5guUU;p0e77Ao0fGxd>wjCsPu)%78STEDNEJ-jCRK3_%Iy z*$;*!n<|FLFO_&3-*SDf)A=sK%uPly1WP>*Q5LQ3C;4xNbZ=JCxHIV-$R6PogPExn zzm%YYYe;^{ar(Y=Jnq~1Qob_p=1*7nEayJ8Jl`MgZiSJiaG#!J!wddV^X1va=$Iz& z38Oh7qNur%hRlK<uAc^XBGI%>_qU9d=U34ak3vo63gQBah+_?EiR;V-&h(K~v@q{; z{5jFs608UZ>)mV$+fy8zgh4w-&XzxP1l%$hkWly&GO<~f;({Bi_Luaw>o$;HR*k0e zC)lJsY{7=_*6lC4or$tbyyt}IeFp=u@2ro-ms)S~=3J&uoGS0(xg5h(?@2T-t;zZH zH}l{m@9}NEkN33M?xbxfa1ty``Nss}pKZ}j*O}PHRBAP9h!;~!Ri-LLT&o}lXZtbR z$Ld}^5e2McPc^Q@gf~iFcCqh6yMSRM|ML$(JJ^7<e#F3o7yqd?vZhj=h4K%Ey`Y*B z?!zl9Z!X^D383%3hS>sOVj(~rz;Z#UkLfaRfH)$~9!V}FEP)h`q|*17kY(UA;4jX! zG`_Ac4zsBSltEM(Qn8E>FYGV<=~gG;aU0xo#sIP2G_yh}hWM4YmDAff7S;wRI{oIF zAOp($5b2B6=k6WPT88Hfesn!Hgp)wlXEt|TO-;tk&J9pa=XJR^dSGpBDQ3Uh`^V>r zxVDo{366Xk`H?`uzl$m#jK4PcoPIG!fv(^(5hUONGmqFW4;ecwPq)+XoE%24Xi!an zxo~gWXw%?CHTpRbQrTh{?xM(^viz&vyw>!5EXdU99n{lnq8-6;W$kA8!)Q&4C$!PG z-lFYjlsSpuh!7f&ceOoUEpC+P&z7KN@M#9|29g-x)7!Vx)4h1AfVTmIvt+IA)<cGk zh`TfWaq)LYXxu0EpZQ$|sZdA!Un^7u1b0eP)?;|sH1tf`WHKLRla>BNV27E<TLY-V zwnUl~(rWyS)V+$89cYh|$L?<GuAH^|Vw+p6W!+>Nt4Pl6ADnU*LCZ1dHv0s4%9&d> zJ-ZgNtd{1u;kW5OGJC<y$2^#KmGf3`UA?#c=Ed{e58p#tX5Ot#Mel^#+QnP51O4o4 z^Y=(h7;Z$y1-quBL_03K#NKmD3q-jM`P|J`d>E8)misCYl^t9_Hn(mVY=lb=i`&|c z$d9h$o~$079(Y*`6{iV<S_jS?CHFwz+B1+g<#GlUZJkuHp>gjBJ?`jO)^|2|LY&R% z=16L=3=uS-pWVz_?Cpvl%M2nnD)G-YVkJf2l$q9#n)ME`-ksN8P~in>8C9YmyX1u6 zuByA+Z2x1@aCS$7GeN>RZ(}#kpU|eUo3wO$*~lgCWaD;>Wv#DBr_iw2)8GTM^mGns zHAek+HU|t+TcUxYJRjUyLaY+ftsY9(w&?5~f?xycsqz>l|6BFB?0)rk9C<BUQ&>|^ z=8rluNF=Z+{v@2%Y0po4-Vls-R#$!kyw@Y&Agb^%<bRHQ04fyq=Ec2uSOa?+7>NI= z$S8{9f4rI7w6Y8&U1Bl6=lX_d&>ZAfnL7$8h%&SOV#Pj|zL$236u}>+H#^7dGqKEG zbKKM1-!GbGAve?KXS1yrw4rcsCa@Bhlf%??KVP%6FZj}Oy=y(%rg7dTw&BE!ZRW8) zXZUM<9JVb&osMSejh%&{J!$i4)o>ynxF_ey?fG-c0D*}5qmM<B>iJhx?s!Q4d{{JL zS8CI`w@T;ZUm4601hS4olYdz)=P&g)deaWX>c38fCgOqW98k|YmH=*<BD|y^D(Msx zsYVm8ft*to<_i{#3$WF=7BDxze@^G<dh!@Zv8Gp!ud5`Il}Zwo2K^-JA?kjE337x6 z+nuO1wc2qJM8Hd8RwlICt&KDsu11<2^X&6IFK#<7thx1oukq&l|Iwoy(YW8C^F%bh zz_O3kbX;dxQeE(;<mLkMu8#n-MlIy_-MP;UX@Jkn#uX}ad!BEx#ZoWgL<lL>-E{5* z+K#rgc*>Yot!PShyKX|=#eDxc0#uNzaWKF?*w9Eg($OqRM|<@gHFt_mG;%3t{L6RN zAM);@9Tx<qv$`SB4wmRc%ZtiS>Esk2UaT^Q6mz@ycKqKG2dGKKpbUAZZRoX>s+!3% z#EpT{Jk9sXN;{-CKiQ0$se0nZn>lJ=ycF7GEbMKR1Oau&sFPiJorAG`N4zg2m6{#m zIqttvG=a{#9it2Bg#a=nVAGB_2OYBo?z~y%3xY$NE7lL|g5h;Lo^#QxHOS|S;G|V) z>2vI8KqGC6L6fpxLrg(VIXC^}5F6$Bh`-kOlI7jvl6%DK3QKS<XPKZY2`Ma&N9+%$ z!6j!1Zm=DApQ2EiX7D$}GHd}=O~|oSmW2b)uu2go<uaAi!DkX$($4iXRl>cY?HOLo zPbNh?4!kQv8PZt4*lO$06<*9x;U8L9sJgPN!Z|>9wSISf7Px2~NUK7k(PchO{erW~ zbi}Vop6df=h3YkGPg)W7Wm&0&STg-Wq*7GsTx%Mk*Jcj#SZF2+@`Q_!jBcMdr(>J; zBc*0&G#+9PwbB#GG1;d6nCp5QC?A0_bov86oy(?fwZZ87do&pp!6|R+C1Lg1Z4Dkl z!snjmW|2H|`1?rzMt<&B@$0t6+fNjG3fyC}p%dhXXd8?kkRQ^G421~(1?PqRDrjAL zCk1HR{Gil%xsm@duK?h13>Ztw^FpmOtc4s-_!bMv-k%vTjYZKNCk{bQ{#)*FJNR-p zdKQ~}zIUVgBd^^v$O@<E4V#=kNUmZcg!)DKvIYbZs5tNB#%w$?PfVSizPlXHe{8sE zq|&olP5HAo@>kD6;0DB#VP9&x^2_G@{?KkHj1(_-icgl!?w>bDqU`ZbfrIfCW<=GB z97YdAqr%d**%nZ>v;$Cc)X(F!hWRwuJujh}Eso##fD4JuZk=y<XlTk!)~`cB(tfu6 zwgxDwUTXKKPsqEDdxz1pD8kG)mynhe1;<iGmIu_j`vn*6?a63+XUsJXN{f(*X9z*u zAel;eJ*CfIg_xFhIY4su%h{B!!@Z85h{wV+B79}_a9frWbX|bvpZXZJCX~*Z<s5Z# z>Xbtf;djLoLIqTidIwoOL~x$@kPWI#5gEH!gKpUsv^>r3B~7(}5()E{-9f0I7pKot zGzqdhFuU0G{)sRgkZYGP#dpc>*W)eKqvaeJeEh(>%eY)H@GL><3-Y_&CKVDm@!v3q znogwU2VXCTk*ekXcBm!GbC1K1M3#+(ev}l2f1kHuuAz}K(yDg1bJ%fy)#$@AtOr-K zgq(!(TS9Z;mm+3HwVBP658J;k^WAC@sE+QO2(ZleU4N7qz=#KOX)O{tq9|<N9)#ue zU`|a%g$?h5@gR8WEVBQ!8)VJoX_KspWH&3l1PN`<Lm${mr{S9@3hS`4*x95YO`BRF zi72FY%jEifFBj<}T13*432N=k%W)EA$c8rSVS6=iwBB<5!q3R@O-v^+3(mF*E4jQC zK#K`F4uRbuzcih|U}aIm!Lv&2UsNNKhsIM3{e-hh8SO1IrsD76vwF<zr7q-iyuZnF zb3%<Stk8(P_*-5e<A&@U;7?i5_Qnj8>f&iS-Jj`~+c6nIG2vH{&0?WEvU~#gODcQ; z@8)OT2@A*uSmuP<Gw9X4L1^8xYX3i(iPygeo;R}h`7lknm<JW5cD-5j<G$Z>$rRC% zBHC){agE$Y{@3aGvC%FYyPNZLGs#bl)}N4Yw%zmtyRM*D*0Iy)t@4yod7UNsIT8i_ zA8|SDp5=da)X59nA5e08H4>#DDJKQ{!kerEeYQfvxcK@VP|gg#I~15qg_(G>CJV$& zfm|tp*%>%4I<*?TDkO;%M(yH^$;uPE!o%qsrZCVAfRjcvdUD}*`=>M23(H7TR8n#% z9uZv<@HjCv6hBv~b88o!OleMMGP#ALytYaWgDrjfA`6>AP%sw)g0^;cE2-yIm{Lr@ z^J*Q$v~<3OU3UG}@ta;*xb*#Gw~KmWHyAaFb)aX*kvi=s0(<_v`=NDQ%u)iZdH2)< z=V9;Lj(5-8D;%a;#0-9q2v)C&<R1Nt)W2Qn&l!H#^|`~wvHWy6G;+O$Z*GIkHsKek zs)<b=oZIiIQY)B-0oW*0*TeD24$xU^AXg+V-k0=X94kN_D{!4An<;=+mqBFR7ZWhP zoHzN#&U^MPW!@5Vop$^NGUu6wM?-s)VWhR7z0O~N0>=?El!tiCV#Urd9!r%cQnrV- zc^Iy_2H$7|dnj5KR-}lp&FMT(&;uI<Z`Upi5~0+J(b32J^L@%U76~OA)n<X$Ru?q7 zC!egeG(5oPRoX^QctI&48`Ml8d{>4Ro>g9&+$8X<>`KH<>+-pBhWzy99c(Kx2Eie{ z525!jO)QJ$w8oDgcwa7X8vLp6B^F%DpmiH%&Pn-ru*+lVKkC!@q7wRwd||x@2Aeo! zTrCqSbv|qV)8<w<pG_3Gt#$vw8Nb-p;zD3p60A2~p~9F7xQX#$Bt&nbrmyIFZX`f3 zPe)ru&(bY=B92cy@V1ul=Ry!cL|7hXW`!4Bu~8Sk#gkW3B4AE@l`*w2xc=9k@M<`; zg}?)x7GBLCP^DaP0TfX{LLc-i&0P`r(hh^_RM^<X>0{UZ2jezQ3U1M$shgq<Ceya2 z@$srX0dkfIpURByv|3ZGO_Lk6O3%!!K6{V4H<=nnqDAkv>Jb(N9O%V~ReYM*{x_Vq z`~7ml*j<1jC1*<Z`TBSz`Jg!6#!^5KvB3V%%r{FH12RtfgKGaBE;+whOE@WW+o)4# z0bN85RCI@_>phx}@J?1b1w;bcDD0zcCM!8XWO+jDgWn+gv3t%I<(ZfD)0eYP@^)t~ zx=(=yLXeD~Bh&~rLdtY$za#A(geCC2@q0>>sl3q4L`9vmJHY7iyHdi3%Zc6tVa7$I z2dYrckGRo_?5j@2KuR#}6AE6~^%ibQxB!LSQcgD4ZXl`FCWK<lELTZzb5h`;@@TCI zxA+Gf8-NP;g|A-%td3n#KZTI0V-YdB(Bt&pS(hN}c0K&l_PE<sH{l<%Q`c?(q?w+6 z-i_f)22e+e#<F?Uxq38G_I8eWu;hxDy0@T^gx`S`*i-Fiuq~z;n$;GTAs(jCGrtzO znCv$AjY+axa%Rhf?Bl$ce8MGElmo?W(aB58PMeiA-9Pl}@V`*zQDaD@8byD&{t&Ny zL)k-E^ZEN}lXb!urq6of2laRtZ<6Worvh_$Z3-%N_#~tU_R1USzt8x#e+>KklL9x3 z9vfMKKBc*?Q_eLxJ5JE<Rx2i_Dgk2678fHC03Y;u)bbtH3HR@ra=d#JR&3sfBhlip zjY9HS8F$BiT}3mBnZk9?M3YgLxV&-p2&D^wK$E6Ee3(6K43$+WvH}^JsCXJuogg@R zO?P$oNz73R6e5|Ey^r4<MU`{+J}4~!s-I^Na4dTT%``Cf-5~jC&-9K1gzFH2JU?|# ze7|qKF|oQULM`h=uw1OLcPJcWc@+Q)T#f<G_AeJAp#62S{u+*+`M)m_^Z=o#Zon=F z$X-S2lw<HpXOlqn>p8~DDTZ{>Rrw5>Ua26LkDdJ|M3dWA?vsY2;|=W*^2J=arUiom zvWU3%7D$D?XdT2WZbOH5?dWm`81rKeo?1MzCPBe^518J<8?+%);BT~6+eqmq()*eY zL2&}9UBM}b^fm1inR(x&$M*rq6;-wcTkZ}!NB)wUACj$d!s$DB*j7!cx}}7P=QGFS zqXWYbt-sw)w#>Y=q(1d>*TmdZXjNpH+X=Nf-+R3?Dy|orwi|7ne+Nwor3Rx)W?=^O zyMMx)d3&Qpz35r`roNrm3vKjr?bFr=1{*f#^-4*khH@B2mZsIWYvcY=L-O%I*{S>9 zarr!(l{Q)>QC(k6aa-<>(Vr=gVeV^a*|Tf{qK5DS%Hcvys)%V=(x*<-KWrR>zJN(X z+E=KPJa!38=fH>}_QvsN>!H2mZvaUe$JU>SlW4Nl8)7-&b$7m-ke!XN1TOMqkhL## z3TYt@rc?SmBgy-}EPzN3d5`Jh;TfjWT`Hv1V7%{$NjkemHuctv?IHrpKUAa!8}Trs zzMnqHQYSEgQIT@*?a`onwHrCG-@j*DsnXZ#x$D~TWK=k@f3LNx!m<vI-;7qDz?||l zu2&qs6op+@-ST0`f+^{5k|H<Rs@_F4?YVSF$P9!d((_Omv~vBV&mf;hnc9xh>#*lo zXubI3OXqs0!i2tUe4_^R<N^|wKn_7X?=esR!pkqR_QpIn_utm#$Fc|Ey`{r^S(cTU z2z9XCE*U7${UdqlTvY7O2a4(rF~AS$`!~PMbgv(4z$nm$auNEe_XYwi=knPv49i~4 z6?IH&<o4f?6(@Y34)sSj9>+IyEO4sz1Sz_2!sh*}tE<&u@0aSNK7$UxOb=LQ3rQk* z3&`(~V4glBLoFdru|EMyE{Ah<p2%j#YuA?5+or>@|7U6EhrelHuo!4)gVY@X-9=@O zhh5JV2?lG)#FidUEq{3DYX8LE#Ua-A{OgcVhT~k*vjJpkgmfbm7VMuJ-t|&BVRmbi zf-gFBbo5k|>>_)83KzzLp0c)W$DH^87WfP$8bu=^-YLIw`iOIQAB~W+^w05Y^M#mL zh=ALx@!>wXc*(|A?OpI*{m(gUVqL5cu4_jb@CIfrb>w83*Cff4ooh^kc_?c$FbVM{ zl*m*+k%F;bW)0{a*L&n``r8U{cw@qeNZX9Vmuer$E`n5T_!dQK)8xJ{7>J7ogJ?$= zD~82$)k2ct!NNq6-{8}<CxROfE2Ro||9yxw`F+4A6=J8ZJvVoJw<2hMyoNpl*MUX7 zar1Ajg6;YliF7DJg@%fYS7N*lAZM~$PE)YckKV5Q;vt^-wOpJz4&IfA&DdL|wW6X@ z*}A!N@OanRFR^7WO0<5J?;qoG4ph3)1SvM$Sub!gy)ce`2VYfN__nndXbJqx4@TP{ zja&W*?W;I3{~d~`I4!{YYoq2~5uMYPSLn<(FrHaN?H+^V)9~^9#VA|L35&IljaBSd zLL7XQb@y+4u*>gOWkkG8q?G2MkopgoZ(w;?i^bRycy@BW$e7$cTCh>Tslos7OKCm} zdLjOp5gn$9**PU!_N0T+@>x%l($4QO{YX%YDU~pfnItj%CZq=~q4>5eg^v*J3sP~m zI%tmhoZ{EdT->inNFSxYVqc8PCLNuEc*XU;p!e4Ei?V#+!?&rz#(}29FG<y1Ppp_M zWl1Fj1)-cZMxT~@4t~G1wN;{%09{$=VY#eTiR5u11HQ-gJZGEH(<eg|C`sCm-RM5L zf-)EFf-9(+3ou0aBkxenjl_Os=D}H&Cj$O}M6XlIa|Fk}|KS3>Qqc`gDW?_}k^LIV zbkUQF7G4MP!$28ID+7BH`@Pew+ox^kZ%*}@=aqIh>y-b!G1QkA{O2#I4W(EtQ@rp0 zl>EiHCxkMqtE!cv98$@ome%OW2&;H4DSTp)PYj>)_Pql}b1QaTU#cuKx*B&bh}~B6 z&6+f?Jrj^!*P5xLxDc0jZbL@TcVmLi$E$-J^IKu|QVq@0ci4wPY`&kMqYr~5I_fK> zx}R1b&b|+Oq9u&^(Bvg=8&R&i_bx{_fA6oSVjI_xf_Nnxs%43qMeUz(aZ{81EDRf6 zYhp~sCo8B=Ff1TS)?ofT87@$;CSF~$UZ2NxTZ{;e{T<hf3d{jXeILK88nb(hrF}mq zleG|hx}0TmSytsppU~61p^&xl3xLA{=H*$Ib6lGpg05qDnr*IC8BVzZ#;xY_y@b~N zd6H^Pf&ImtF0eZ(W($llJH^ND(GzsTq9PD=u7=?r<C5J-Mqbmy02Sqpw}o@8d}#pv zmu(B-*DOL0hbScM+@__vdT+@X)tbins*oh9#h^YMemg&fV;H<mb#EuDGwkSBr=GA= zk0m#T{3hOC-k-$XNGGyVlT$&BF^hKlJbxE9`K*&f7CGAfw7>aZ5=sI=6b%T%xw)aO z4ncs!y!e3_65_rEi@T{jJHr$<!aP)Q;zL%#q$(pqrdNt@r{Idm2qn=GqWZ}S-v3k^ z#`eXxl^Kg})u|<KESS!P_@cO!?WbX3(MX_r(DGz3c}j$P4m`ZGyk%6S7Fgn+7OT4W zZc*=7;e`NWCB9asnZqoB@rs42)<AR^ED1ggJ=)EOAivSO69KEhhnp}O={I2ry04Gi zYJS+h-&VU3*b)$ZuN~c3^nlLS4_)WhuJnHe$*(K)^+y-}6=FmYHQ2jaE%^S!hvBZ5 zhpvi&Z-P%|kvIFrC*z^Ic6)~<Z<-A!4Q7w#i&rXWIl?=+6}_8VN|bcy^EJ=!_Vv3} z&ku#MTd^~y$}GwpS8V8kK3x(e;q(+F0Mti(U8Y%IUM}7Fd~e0kCU^WtCY7lN<k-ld z*RJu}C#eY->9pwakn2}`gl4Nz5AEf&;uRN&;yMN=hC-{nOb6p<m1mLb*f^&#r=-CJ zAbTZ$jab<z+(1^pzA<BmUlAXxzxrQ$imvo@7G4E_Wgt$)L|;?PH&Qf>U{TmgEeC=e zV^5$d1&<7I(WoZtml14ylw#P!UHNz@gY;;`;KFgWa6&?n(ssrC=RAww&Vv)#GjFR# zq;RN5^k1&@D+HmPecL!VMa9M9jlO_y)O_TFY5d-bMfF2^Np8Gmy?I%RZ`8ak=XjXU z!mnTD`WMoeO8}jDYG`sHNM^3LgxeM|W1l)+R&|9{^I5E{(k8wNK;>}(-hdvQ)I2P? zmr0g-S(VX`3RUASoTS)OJnFnk<0sFf%+x8t|8)A`VsLXG#ZqRKiN1y!z`KP)z}s`$ zD*ZP$qHv701gF3=UITmTJB+3V4?^*h`A`%JB^9H9e00uVMei%SAD?m+NSe7_-iD$6 zJv3Z11ews2hT&sFemH*M7Ew6=fCFrzUkxMGg)~Y2of-@8x;9a!P(vGn(JSyLdBB4< zDI;BPb&dO#HJ<c7{QHt$d!xGW<jK8W=QNVlfWGc$3VT7e@tp|tI8f4K$M+N@=)6$H z=4+AyqRd4WMSZ%{?gu(_ex=GdAzuP24&Tj(?^gObCsq|O<2Q|#kW@L~Yu2FucA-!N zf;ALflSm^&myKdeg8@8aU%R}K?qFNCUYq&;HzR6H24M><f)~-!%6Oa=d`f)W7JM!& z5ppvgGt3iT%FXPA;}{_9(3L?rDDL3G&)mz+6x_)5)!m*({0f5rTczwWjX7f1d6xpH z*oaVJ{W!ontO<18KEBB)uTZZ+&*<8}kT}0n@Vig6Y@7iLJO!))_$g21-~}L1T$=Lt z3WQN0$Mpub4#)^M9t*yM^^7IraZ>sV2O{T3gQ}f_^B8&GN59l(+1m3fDLh0Dd@5sE zoCfGfoqV$j!8`E0NcqrAWLE+bR$W2@#-u*!G;rp8BLp0%3i|=ItD2=EGm)ZcB9~Ui zOd(OJj}3%RH&rw%IUybenk&LRbhSCw=n&T;H#V2gO#$#oN_t+$9Ng}uve*uJh5)hr zd<SSYKb}fq_8+qbNbMYLPyf75M*hs2gMb3~6y~lk9N%!6^$G~C=AgeYSs}I>PO>08 zF6&zzPQNzIbH@u4L~yap*T`?B(5#|D{uJ`}S;oA-T8T}ZQ3?3GTBJB_SD3V2i4#=z z3BRGhJ0txVSUFQ-Im&YWc8rE^12(f8AM$(}XLZv~-kx)boH1$ozzB0ZAF0d<_*W^i z3MC*k5O&|c{qc21J+hjgjRQ@nsQhg?FhwB~8Ec3WvH7Fi&f0B$>}5~hK;``1o8nE2 zUp_NM5z1OTGR3m#AWGEDh0h#oQHdhgDL?RO)F$*SgT)^WHDCT9mRW?l0-w$8+t8bw z(;;)Hyi+<)-zxV{{gL0Zr0mDU1M53S!hh%66UQEYnxwVlS@ZfpDvV*Jj-c_sS?|t= ze5{JxS6PT;!%;@Wc5b;-p0l(~9{xS=K{j9I5EAjfHA_6dTe{+iZe4Gm!$Em|`kW;H zly4V?UhAMYwJ0I5_YuFplEGwWW%1?xORm0p)bRu6#(}^J4jYkf_4rS;{6B}Sy)IT7 z*dl?5-%~Zm`&{a-$?t)SHkerv_Z_V5?1GeV6Y^~frVNB^d^>y&e6CKGqVIc0bqCCl ziq4I`VUMEXGO5DG^|Lidb^$?IDl94C#Hiw>W3kAO!;`+(?DO7$hNLm{V%r4w_eAyu zUg}~|D)b)45A@|~yv}1^@6KpNj~iyAdd}G<sLYg#Ku8ihmTYO1(EE`QN&AY%5;q%L znE2+sNEpeLaayOroq?U?mWK1^p4DnfR|acb2XJ9=pb+>sX)#&e3Ix(LR6D1Uj&Z!7 zEVDP<9N&^agdsRJRKn`$H+fXT8%Pge?obgiJILy<Pga<j`Ci)F8+`eZ_hyCB?so07 zyKnBI(%E-`lzy<<6A!oNBi>lR;9r>V65q!pOR4>G9nEYRzm;MfTaDQhcBug%OF?XM zBQupzu6nT+iOZazZKugJTda^A_y{{ZHW7$n5rd+2b`S=C`KKK5*6&%qbHhuNj;Tq? zDbBw!vDU{^9V0KVa?yxi2aTaP8l4CIR|87T`-qYbjCfBhB>dhAdVB(MNkb}076RC1 zA`O-b8!%AG3YN!9E`-0V^H4F>&??B#rk*EY3?6u&faOQ9?fST^U1T6;<@{zw5<)8A zt$|V{*n7gy$RnG>r>Rz{K^}SboDnGUmh$ae5I3#Zhx`_BG)tGy<D==)yYX8EPMdh1 z(h(%11Ousn$@m-Dvc#*21H5-*JYfnnT3YTA0d4G7Jygi}UZ=>=<2l|eAs9n^zIxW~ z==w-ItAy`;^*hs~tF+$|#k<v;$@6$8SBC=h5r44W>|!2J!aj*Xcz?IVc4Gwo_V$bO zx3#oX##x>7cZI(s)4<~j&ioiaWC?jdJ}4>zJ#pnf`MAx(Zd3om#4~x-9OD?n31j}R z<_HWPsA2tX-26&R@KBg>d$BeJVb_Z7K2!gj2!nu5@)?4eNvEYTT`vzG`5v!qm)oV} ztP%~11<idw>67|g#skjKd70d!Iisg748KnRf3}7MnH<I42bKmeqlkRIcd42h3Rdfl z`ZsXmCbZ%bo7JMqJWd=z0wbs2RgBQw#}QynF5vr?XVIv%!Z*Eo%R8{jZQAUn-?$Vz zg4)hM3ohde0m@#H6w53IVE3=bUfFtSQT(UVsZsp?)oPulE#9fwbBZEv%XW7{i`(;H zz#(d#HQNiXoss#aQ_p)#?23Ov&Xh!>`RRJa^zBeLhQK4FbE8uPwhAVl&mn;i!RS5= znAmR9$ST(HfV(DGc@eQ!0D^GhYrkVWkZ+U1UgeDP4?P5o)~1}Y?uk)3to5qrh8et! zXj(OAC^J!e@;cUWKry1Y>k%(S>R*GXR${F)ye4dyo(OlG-`?KS6NP_xLPiY(n^GiM zd=@V;<E>N}8uM?wCLIk97demSNAQOY5~f^9qT;+`LM*O(4J{l;+wtoh&lrX$WkX4! zAISNWFbRTeSe>Jf5=|*bB~no4(SLfXP^-4?rxAw%^;}mK`ck^O*_~Yj-CgcdQV|Sk zg8C_pZDL;sViU8nbcia4{kzQLew8o}aq(gdsq<bVS32}ba-%@$@!rD^@OE8{wYMzx zAe?yQ!Rf(TjgeDoq|Z*5DSA5fgisI5k+&lYK@Rhie^FmOJsx2EOW^()l8}vEfrQhZ zP~3hqeif;6Qws|KA*N8_t5f`4`<<8}Bt>Gk&$aMiIeYzPd!c7VPI<o?v>Ea?LvHv` zFznLV)w${!f=gr~jFm88aGx1Hrgnp)d{rad&sL}YA2q@rc@-JCS#E>SFu8gguWeh1 z?_+>K&Sb72^JUk=K&IUg^=&Y<lw{&E=yZ8`d1nX<)A{sHwtfZBmX&iSt<-AbS&auv zmyQp)`t0k%;K73L9;pXs=6;dmT4wA-GOp~7R<&`+M-On``-f69Jbkh<FtmwLkAn(e z57Qf-%l+}aYwS1BFczW+4>CMRcmJ`=gEK|8_yvvtUPYp!zmcP_#;P8O0n(4|O~geM zZqakc!-LB#S8^?)H{y%(UUD*jK6~wUesHc4_}v~Y^a41cEn9uoMr(%FeZ1!3fu_~M zFF7%REY($2@jvFk<Q}I7g@Q?%PJ5RJMm^V)tVyY<qrmr%k-77>AdAmKIimJ6(bv3| z?ybIyq=OtTyXrCh&-!0WCgR$+5U>-osLc%mA9OLN+R#bD^zGe;|M{cXMz!=|-+jdS z@Ma_=Juy9>+B5O^S$O&_!iJ}~1%)!Ta8XW;Ug`JRG(Y}F{6@KX@kUX)`_S(WLt+pk z2NrTJMtL&@+61wBX^>zfJQ#y2t%Meg2*DJ<PlmBs$doY=7@VGO?j3{@9LQ<<byWXu zB-T>!02(-UG5l_Wu%{Rl7`;jA@HrfdEQKaB_$Brxw8_4?H~pk39*Vd~-QgvYUTl9L zLq5Rs93BYxfH~0`Lsar~>#2UWnaV%>d$g^3dIBE2-*b~Gk8}>HJfPI284~TpC`CYn z<q_mpBI$f_P1k4BbDZ5asM~mX7fe0CSy1Iu8XU<D?<u}1jRFPUq*2EP%>PGFkieXh z`^<9iDjV1zV-3Z?!RR{nq;Ga!PqmQ{!}<sV;6I<h(GRcS-v9e11!M_&;O=g!312hW zO*RZ?GcVRgM{u}5>`-(Ndam1Iwj4<-ZY&EBzF1j(4?@WXO3+_}aSc`rBiU}Nv>}D4 z^F>uuo&}eVXbDM-DA&fH*quiI#2Z@pBVKZ2PUYIIBcdJXmZPl^BpzW+flp*-GEC2^ z#?J_f%+m@LEksc*EA=}WSn<~J#H)4Ujw{W7PtPs%1j+Ui?*}4Vm<Aw%eISFva2dhM zbzo^vZ;0|TK87T$YL{{RNj@g$`)V8YLz#<#XFk^oQIPf~8eFK*v3r3><71`uQk@vk z)J4w7IF60FM8UoGcHDeKLmKEX)cm1QET38Ko5}5K6X^7ZqSkNZlc~!_r_bO0oAcdK z#m)Y#shR}K(wsEB8YH^n_X_dp^?UxCdSr<uDbg~^q27%iZAVPJv@YW;qv(QNBa^T# zwVPNod%b6AOQFescB6BPB6--s#QpI@2&i}4)4UfoVO_xi2~|dnlAr(HN-^~@e+C*A zSjagV#S)eYsqk~>ZqcQ}Lukz|C(~WuC=fnwn`bndrqIcfSbH#TSD7kUG%`HYI-LzZ zU3q=27dTWVaXndQTxqlkTAe=S8HnP0kdl;*n=Tv_{#U&1-)lsh&Z+WSxqe5{BiWkQ zp6L1anQxR#ui=MIdh77UXN^ynxDl~c4o}tOuo=dA-o~0&k#*mcHnB#7=-%5?_kQHw zz;$rf-wAZ^J^lBE&F`=1@ThEpYT8!l%B_6Q?={*!s?YHE{t*5DRv*l&kY3Wsge2_! zU7y&@Vh(sn;$P@KCSC@z$xjXl+<g{M$o!^m)}JKf@W{+%`@b{L4{hz;|FZyr8fr`U z)#8&py_`eA&!)H(p<6T<LtLMlf}^_{)o2B^nf&f&b5#?QiqL${lm;Zv$J1Ehmdj#a zIl~kH{JdOjh>!45G)a!|mYxPmV{uTonVmZS2JN7JUarpYLT#zpr9>xK@Ssrx5W54_ zlu?`%84t(^gT97e2oeU?zoS~JtmwRFjat{3>@O@cr?ws#`FtQoHGa^Rq6$W)x^<Sb zu1OszudlmVVcwbfvpqA77#ba1ffWo?FjYg1x2YY#3FAg4)voVTB;g0hSZR$@#=wQU zpE~@Xu?+}Tphg*AqJPCQ{_Aqga=lo_-gZ4|H_waM!i$^nxa$)Vo=g;Ls!TeY-5+^Y z!QFx0iD5=xqU7W)Pfn*U#eL8B4Wh)Lx}w<2Rk`$ij1EpcUARg$S{>uMPh)XOVIego zyZd1zkc0|_$jAhNT)`v5aJ)s<qYE=1OZ#B)T27m9O06c;9w-+Z>TF!N6ts(()f;h< z?FA4^YOi5Xr^6K_6R<)GP5(WLu^IlgwvZc1NK8iiT#ZF@>`<G6NqFL7!P=<KoPH9? zl!K=Ids#pFQ3Gf7NRS7H81>}<O#5#f1}Vm96O{)B3CGdE^<jO!X{6v!B9$QT7iEJ? zVzmcegla>)>TKy|_%O&M^5uWm=cL9ul<M|*(@^3NE`z$67{;fts(|oBQJ4$|{!&4D z0*ND!7=}MdZDHK>)9b`1|3`)`)BpUT{|C524xCwTm`o!E1j?WNJ`kku%D>xqgIFxm zAP%s|cVD@~uo=b8f7s{m-Sk?W%lH6@nj_%B7%f#QOiD5fO4%%A5XY9vM@ORj|G0X~ zpemzxeOQ_;4bt7+n^I!aY*M<B?(S|0rMo1QPDL673F+8$mo(Ddybr%~&b;URKf^G? z%>Asj?kjsiXWgJgHD0eKXL(;U)LqNlp>6iF=^1hpM0fodS(e096CaGI1i9Dr*8lj2 zSf^?TC9xM64*$;Gui(Lu;drkxaV223g<8NXw&3t})lSchuTAM&T0j0E>h!VP-|)!j zt{J>Lhgjn?yd(qWu<O}PTW9dxdt|ubw}<wmS*f2`H^?X}yaUt_)cKAwd&n$hBs$NQ z5gm<wa<cyPtZXay4~<p`qipTs-moB%+ntbR-8683jCOH)tCJ1`{!EQfjyYLM6zs}b zkDj=JY<iO@+&9E%vD<!J(#&>A!+O#VifGni83ri+gl-OV9a(FRPKj6BzR)<Ev}6=7 z`FSl|*k!)q`U8>PcLS7XUJ8<+bV-n<yJka$yVj=g!|hX(gP>m4$WMnkKmD1}&Xq-f zR`Zl!l}qb9o=b?#S;Fc(GD->M#e$$7MtKy`Lw{|iHsp=`y+H705f$>lp-lm>l1aw6 zE_?7=GU-@-C;%*i>=8TlzwqtiyJ<iPu_k4wt|$+GR(Y(lV)~(}J&0s)fd0+d{MMWb ziF`)*1)MQ$q~rYRVK2t#2myZna!tIJP@i`D7;qgU*1k(?C-C56fhTr}Jxl)kQyjy( zrGDLs{y$x>ogZHym|5SZ@&9w(5xJsDInH1;gIYj_<nR%F`%5x(HX*KjC*7?c|FnG0 zM+CM2k8Fy1i84|&qa%7;c*hi<-Aq3qdtgvVV^d9DvY%6F()nBC7KxDB^)fre5|hfA z%o_U+;m8-wwvrGhIyl4GLI#gT<A_#rhIIA-sqOLKrgC)j;)z4p;Y0k6Nhk-|QVCl> z9&RGM3)iCj&GI9vgMypRabPasdL@iq{)Er$)2?L?rFAnO(!8@4EJQr5-(A)(WHZU) zktGTs1_%@wIx?NBtE;naS&ho^ZN^ygvzypPbz(w+a1G{7iGhKEkNoA09M)4QXp~6Y zrzMkhXMa8uZY?#tiB|<|yD8e|ZK+SH6+8+n_IB6!>{Wi`vFgNyl8VXzs1S=q$!NF6 zJ0|Uf7`h{+wdvkpg@H}sGs(C!nGmo7wqD-~2WTYoTG(h91ra=?sx^fuASYwv5DYrf zvhGbE+B!4i8lnisvbO!x7!m_66fHUG%em&?_4U@d%_9wJk3C<&C$6+ZewfewGw>np zmVbHG$b2a-*#l~s(7zUW{Se+5A_=mwN&4{q)%hZCKDn5-t885I=MnDOXQ`sP9tJtT zwpGN&RFGf&FSikijfOG=ulicNq*Utw)j(gm{DMa^l-qGeWKJded=nBeiuTU)3B1iX zeZrwnKD>og0@4FJ-k;t}?H(>jX}C3FhHxRa(BzY!k|9q5Z%jX-q~Jg}ME73+^0DdL z3dINnawWc`r!xYAI*|1}solxg?^qbMv)fBKNw(RSWw!tGls6D~xyC%go_cqi{2aSZ zG?^x0Fe<;RqeP(9OmhXWln8kqWMs{4M<9SmZ6zkd*`d3@>(?;wVS{oA;8jwZPKT&A zW<AY9@izNlHi?t;w-bnzupN`od(P~J--y?kW#OQsnK?SXZj3L+RTYr@%7PTRg4<|1 zN%Q?sS<n1vgv&qN#-0ynp?_wK99o)d$)M86SXl8X8eee`-Gxzi&25s#yVZ)Cti#^` zJ0}lW%U{ZBBate9iw^}RwgG2NZ)xh3B<yxZ4bKQL1%OasNLKcmn;;*)XwQbrNdZB) zd$xID$xy)cGApar5(5&gjGTf0aUENfw5DdN{Zf;v#r47b&rigY(6;eecVC$n#Y`S( z8k->-8UcF}^B-RGoBkhMBP(o_{<l>JBK{Kl@GkoZEng4>cz=7nDIOX6HKAZF5}Sk6 zdQK8!aK3UiDFDBajGYjB0XL#Tuw<`H#E0&h$T`o|nzMO}(7-S$6ld*fwX@+TKb*iG z)AZ9qQD#?^=gVyaR<>15gQJ(W{!Bxs7a<n?i{Q(*G{PJV!c3zzx_FlhTD|)7=Z|lx z<@kh!8kcLGhCsv6^$A+L#Td8Eey~9)mbD(<TH!&@!E{`|aM1Rsa4Wx+adi%Yew_CS z&D(FnmvRsUGzyAO<C20zex!1D5Z)ESk)*sts_V)Y>9mntmhPX)bU5)A1}S}^8Tt>j z5o<sz#xEJFq?hfnrl55l?d-Ahnit3%)7C~)a_)G~PlD(^h#@8*02DRGMnQZHl7$To zD8QYEz{==?zntNO49GSEcq?nOU)rwz>k@pi68wt_UcC4fx3-FE=+&uIC&`*2{2COa zhaC*qOwt>l>x@?XjzxrAH=~juz{;2e^j+Hc13zN=sWOxl-VVSSTb(GBQx8C&0=t>q zmw%EF{ktin!-?O2e}a>P+n1>Fx=}9D7d@fUUx3d^PunAC;P09lmG}~NSX<vSN$6&l z(#ZtMktr&hsV$UTq5OwR?;~KCbq;<Arlz6ZWOqde>u!iMO(1&t8bFFwh?uV{4Ok}o z@a4;`RMMF)^r`+81$6&%cKi8)D9&`$8yN-OTBLH~2Bp+IJow)R-e0@kUDzb(xx8>P z#@mm5weYj+{XdJJD(yY^M3Y8q+Ri>bYzuUZx63sZ6&FK2mHF$+Wry5vI6r)MZXeDT z(`Qi3p|dH=gn7p$)=s9Ltq^9LtITtkIUtXhisp+f2?qLCDqa$bdWux#oDyH4ND#7Y zjO~f8*uiI=tGye(cpg^yOt;3%Myu**_@2e9I)f8wZvYnBJIrjg?6ug@oI7_R`>iJd z5o~dM-2oV5?q)k?uC@LB+X_pHhPbRR6tjeNE!U|=4s?su-U2EmVgYdyeZ4K%sq3w8 zpZI+3b?G3GuguUi5`s#?AwZ2wjG5bMRAW!BMEdJ?`!4*Nu!>--%nzO<Horh;RAuNZ z{@Tp=r!Y{J5)TcpDn*-JDqD`IDzF|Fz+nW<tByvz*Q%Cs{1!W3wA_ZO^Ky9#<e>u; zFke0pSCSGO2%G|6X!D&!O`Lft-k=u#NeUMI)-mlvl_{EkOFH<12OB%Tytq2+DICH7 z=fxF4_VVKDXSaD)Vd{y@XmDA@wB$#AawgsXr^$PH*f=}%E+|k|IrnW@Hd?ayu<&UQ zUiqOSt}~Dues;C!gcfaWE14{400?+(B{)N6Y~I#sr&fNVqR!y|9+FIJGVHXN-pqp` z{2F!Fv#hi_JyzedzxVjMzc~xpCOV|!0~}mcVUl*dVk|~f<dJ>5!zuwu|G!jR7#W@n zW~|JgZc-j#O&nhn1n2)uS}1aNEvRg^2uXQ^FRy3XMpGjFNOa-qyIz{{j{YmQLnJ>Q z78q7}PwLc0je<p{VdVSNc6)Y0izUh0tn-B4e7_ckwFw?}V>ENK8_8Onp1mp8tjPb8 zRpz?I*zk2=Y9N`>V!6fR3~1CHAg)Um^KVjQ&t&;&Io4&>pqR~X*!w0ng80bX+P7<% zA6AN0H<Wnx`RugU$)d0Go7(cL#zrcVX?vkw$Ia;TQ_*lfCNqMZgp|3I-0Fhn_q{N* z>}w<r@zNO1Z;{rksMe!M8Z4L$J;DClo#8mJhz^UuvP!+T+Go7U07zr-?eD7OY`|cH z+vkS3<*myyyf+L$iRST3wRhfC$K#>X$KnMvS^Po8gpr?6;SFAd)3!E~q?CkwyAP9d z@W}^O7U!dqwLaRJ8UpnS5aS3^S2KyIV9XBzepWRFIEy|X)$V95ElS*io@SU;SUUX$ zP<Rz%0xM$&e6m)Hk?Ul&l7k)X61}luAGul|PXa0|98U$OV@hrvWFaUa?umgiYw<x3 z-xTvi4Y$YgR>#;|w~^n$`>V*{!Z=g$<sJO?jVW75^CXSsXpBL->o-acO3v@#edRdn zv!^g#7&NC7B=>Hw|BY90&Ec#uJpHzJ#Jb9CJ-sA`8xlSFr1DBsbeio9Fz>Y<|6;WO zG*Kzr+sA-*oD)mrA|WBFIEGv-&|rkL=0k@O)H+L!jL-IsBExEl)TMobKe)_(E0JeF zPz-@|*u0IofVD`kPNBHIE5#lSb+>#0`POkK@Yc3ZR7h2qUFi_qNH$>$#z)`k7ldY? z{NAz8-}!qC5W9-pV*)7Vca0ef6&+e!qu205ev%Au<cli|AS#mc2{ZG~3s9~9x_zdG z4iz(1KD6v|=RnXh&|2m^cJ_ldyz_k=`F^|?jX&49b2k=+H>5X)oHe_Bo^Gt;q|N_o z-pX}vf<Cpv@2Fii@ajAdYGYG&mowQML4~S3Df0qzq>Om2_{j2sttkSZ+M0O~vc_m- z;*vH~G%_xbJCS)ebC~yTinS8Wb}BZ1D(P?C2Uw`-PO?-9r@T_K|2ahwK|oA*CN?OJ zp7|EzYJd6aujx!OQVk_s6~inwo>V9a>_@`qA4o#A@H{_Exl!lqq~2GT%dUPV#u%mP z$uyVI)y-@JLO;(=`zVKYyT*A4Yq<&2$h~i<oxCZ`X^9uEBQ%QLr}!ENfBeJ;NCc_& zrCM2_D?wgC7QSu+0AD3d2T6or+GQxgsYrg88@Rr?k7e3~$E0v0?SeedfU^H|c#<;A zDl}BZVGpy7wXj|n_g$8Sfp~G{FTbK;4hr^6DAzOwdJfiZg}#vXB5*<O#smjg>EkWo z<Kv(GEl>q~-RP+bKpzGkqMBLuR_5Qyzl`}lOwEfyK?cc*&B`xugczVly!y8O_y3%G zWblu1uby#;Xux;Bd}i0)O9rDOie6q~xbDKJr{x27zK9L1(?%ANhid@1^h=1$h}|S7 z%SaVB?3p(@N9_4_tb9b?ZE1>wjg4)pHeUEF$`)S@FDrxxI!|_^^qQC2NpCk3J+VMd zDB>0P7M?2KjwB0cL8qn4zaA>!C0siuT&Na@Ur5n5hG8GS=c6I|%T43*%83@lirF7G z>Aon^K*X8JP!(GL3X?7$PaGUKsFYIVCU1UvV56qNrKq+Rnfa$nzoVrL$L|h*6>j48 z`CjZ<Rurk(0L$SX;EzS-@<n@Gtf`SA33k7wZ>|CPo1YPI-i6U!zQobTi(F;I0O7@3 zuu*}tfW8a2ow0$m<Azej<h5d<txz1Q+2mxG($H`oD_LKh9uiA=_?9Ae8D-5G2y0-8 z{d|DjsKyk__?$R~O^UXw^1p)WLBLCbxE!klF}tH9_VD~IiNydzaZgsv=F)T27nUVR zRZhv-M4U)`WWlpO%0j_~l_-Bn0z&_XZqIitMh}E|JoYsJ=LZ*$G(R5^1Sn9=&L<8c ziPUH~ddHF%aQ@4n(ylWE-x?AY%+CXNQ7No54&y&i=r#(&pvAovzCW)9k9x8FlljLv z#n5MKHIP&sH!yokrTt9Xi5WEO|Mu;I^IL?cJax0hBJTsP=Hn~<BKHYYsEPzMO39zd zv6x*nGI7k(`VGf_wXU?t1|MU!|Aaz-LE}t~P04DgJ7R4yc)3O)*m7`ikLrM?3bmnH zH4+l^q6mDFnylaY@1UFio-!B+u5oV>gqsdSfLDqgsr8$6q)%E6+dg0ac5%zbW7364 zpG+BSxeru#(y@=&-;JlRZ^dMXqF|D}SK0+q6SW6Ki^uzpM6Q=;vBVy}zduj8Y)b5Z zi+;7x{k@tg`)y%bFgF<e9{lKv4wpgXh_XHdn`60;&rOP5ydK~%sBl>&Nk-(r+%z{I zD_{r^itxUmM|ZBX>MEBtfTFsjz-D6qkn|=76WD@_#V0)?hQII=o4YsAb>>!&?;Uh7 zpZ@LwN>w&Jti>k5{7}f<>klt_gtPRH6ky?0@bqjLL>9=ImIZ?|Rzvxpo*wTt1Facu zU*bV|Ps|FK4+ZY~>bGP|B7JJb9CcpSd!d_)3oyG%j1C(M9LCG;6BjU)i%{#+XC&l* z_Z6IMU58jVSi`7-EC;v(m@=OvuYY}>cTsn|&GX!-DBrjQTs}wK3#52Ucjm@(bj0%= zmvH1LO0&{M>RSyTe@=;cYq_?S-X2o;kuM?L9Mvwk$5>fd?A=F)S0EpoIzj^T6&3U& zb6PbR7q2Lj<J*^L03xNZ8G+#*3j3OyW!-odd`Hdv-q>M?L8K{8L~P6wJ>a&X7LP={ zZh<WH3xmSl)(AFixG#tT4&#dWDGvb`0dI+TfSaeW5H+xqa4>~i=!6if%DYWvLnPKE zC}>;vUm)Al8OPCors4th#OWJ-zKzI75*TM#aJ4659VG*y7peg-Vv?Eurq!R4E^z*1 z$Df*ch=D9HK>uB5>upf{zn21Fg_x!R%vm)Ao!O<LO>lP*5!cPqwx@-EwNF)-u4^#3 z?^xtK7H4nxS_4)eubFQZm9}*f(9?E*y2`L?-CY!otpLjS51xk$_)|JSD1_hExGbgS z&pu{mW^rTP##MyvuNsm34pG9BD0qrNiu46iU8T*$?NJ3l3d-HPEd6SSa2d1L!|apC z6|z$-x|@8JvG=k{G;Xi9*N<svC}>z{dD<3@*7w)VK)X{-!HOhb*jWkF<@ZuRCzshA zAeW2hg`kkEQ@WhJ^UU@<F97NW&&%fJPWiu7(@sYUZVe$Pg<nkH1HGRhEP?mExNFJR zY5I<;K``J7EWw@g9M$5`&9ixOS2_=JfpUuvhK&bX3KL|Va;qT)b+lrV1FuW3!Q5|K z6LT_$R{l))ER9k!tpE6|eX<Wfn4nfwBrf*%g_#nuOx`Q|$^tg8(SBT*11=|f644|> zBYPI>(9T%ODXl9+xEBKizwlL7xGKh)L^`BXe<8z@!F}!f>na9RxgF$rpOZrWlRu4W zMw+Y`4uToj>81F-DiG4jd5p!G+0V^;CC5)x`6KTyh(qZhCnik!`0b1O^vDcNZj&2Z zGq;l#TS>ZVDS0ddE*H7V^Z9}P-o~WbG>z+3P48ZW8<x_+$MFE}o?46J+B~8&)2!>_ za_)(0z=<dad3GO!(&zgGa}sNP1bpcK>Pf0UL_%f(dc4KX-r#?;<1bSrMg>gEJzP)0 zQaHUHSKoHArI5=*_yaK_Wx&^?`BvExYn3dpDn-*XoX<^Sqy>;Mw?8y5VY)SBl)3WK zA=U(a(++gomE(_G0CmY_D%MFbu-j1VYvgHuy7<}n0)%j!fv-dvp_+L`bl$M`J%%Y= zRz>tMA2s+pwx~j-R;)o_#Vm_n1_E?8UAVY0@kR<aVEM$<nO~Ld$LksMg)*v9$tCq7 zwr!9d+m*@<y$uqv(bzY<jT13qd&@S?^Wj7QpzSjqBYAt`xXwk|L)8_BGW#T;Hra*y zNND6O-u|K|`lHnG7hIyme(SQ)%iK&hPlF}v543v8OPo7P0uew-qjY)CPsh(jyH+_o z76VdZ{DC}++jjQ^$V=|QA`w$u5zl5n#@PXhBm;_a%1}C0e&fMAhI%EYZb@KduhDpv zLy+J4-n<gLUEtj~PBBhuy~T{p-uOXhS{jPe(cZX73ZLc1csoo>cxQtJVQtrRok%jj zoIt8=s?&MxHc3Bye!2L;6(?(%DhfwWc^Hd(GmT-vr@s(?PxDf#_V~sJ@cKEp?rsW| z*~pwCwvSo2Em7o9g~~`NsyB%HNf#4J*g2}-ZP@<LYu%GvS510>)!6pzR^Zu1f}AS< zwgCAeD#yIp=~4|4+d*GXn9*k*>+7IKX(Ajx8O?F5CKmrWA@kb#&r#$n16*ymu!mSE zhV@e#P(3u()QpQ65+Xsx%s?f^$&Ki4X;}>5tFNtRt0mW1Ua}T7;djgbk1*32)jb}7 zCk`p{3NkCe634~cr*L$6Ww#IOD3s-y8-}f3P&b9-7*>)JlCqbidyA`_`6T4YKZzmP zp((t`3S0?ng_PE|mF?%F?_AF|q0<=@Kr{_*?2B*bMWr%uphJH~qyMYrerAThtfEH! z@E_t0m$IT9;s>G%b1>8<E<HSF+&?yC+bcV3c_r|s$^Qqi8+;MvRH}mg{mcl$=t_JO zyQM7ml_}8_Pf#m~HLbU~<;Qh>L+5HtK}m~e_)R3!Ctu3%#V!tq7v2~W%+i+WF+d7G z!$8kCEj>^$h8`$WUV_;&up`)PqZrf*C{LeRNd+8%@!%W+Od8WQH}QcNw3m3whu*9z zPun^j)z!+6;$qJa=YZ-ouVU5uj`+Ev;|?fqVg3L*JyAjbxsHxZ`V)xKX;XAk5}Bv> zQ$%S%*HLhd^Ekl@Jh1<0ztBwCo{uFb?U9_NS4?T$8Ts_hqf#b;3W;Km2$x{eh_YSC z60aHsPU(#J>33&AFK)b+wOCkiu8yFD7F!mrTge-!xYj2+dZOV<;Y$<Wunud~Czy_l z;J2_y%>>!xN4}9N*MkQOGOc+Oyu-Q-SS2EOz$!rdm2^8E(OiyCxMH_~CaHv-gZkb1 z!aMjZ5xvBJ52b!?zjOE^Lb<8(Ou|UtxK+fErqaf*G|CspQZmnjs^lSPY=}L;*&{|c z@t^n4E4X&fDrB&sEe!FRHS+al(qypb8PIvffl8MY%ptlC&aXkct5%PM9KV!a9sh8p z|35e@APs&6mqd%uIuL*qMslGkfhc}0scGBE8hYpx#P`!_HQ+LEr2fkyD`{ic_gN<L zuY&bZOmdcFtQEfxE8+2mcP_<OCrw{zn*UX|YS=Z`xfh#nnKMU|6{UWs8zdFOz~EAE zbt}ozd)iD^*#vYkdT~9xBW#m&UN2kMCWc);rDImMzVU)jy#M`j<HX!lzLb}#>DLoU z_W3vdxTnxAMp04Kzb2xM7_;h{e6{B%+(Pbu$x!z^Z|;?l^WfR7vkb3JVpM593Hq5A z#a-Xwz>qoOO0qq!wvx2NU(?Flg$v-KNHF6o2QU(B4|L5Z7rLKWGY=1?P4ytsANtvn z#$FIHVd*zIYF2qu$d^`DPStljx8HA_Qi^b(5wN|dYzuVQ>X#`KS2|w?pFZ`8J*kd( zxk>u=#t@$Y|I*;E>m^pR-k8Kfy6Vg_a;W9vb-~|!X?yW3Ge(X!0YrC%hsWGssF_Uf zs7V_R63w;<UAW+~*AgOiu$O^^O^=lDhBtNOLfl9C-#0c|PCDe&u;XX&e@*($1aIqb z2N^@)->%T+tD7!bJPnuCx?h2riIX{fU=eYYuQBsm^$4DmxU2t$pqi_!Ivk!5V-X7i z>dc12cWfVy=NK7TYzskqU<e>zi$$O3ICz^?FRlNEFi5PyxWlOGz(7g1;F<(jHps6U zxgQ9i3UR5K$)7iYzF!qUq_W4&&NVU891`5i$kVh+lNi<H2V9i-oTw*{HX!SN2)>5I zpxu~u@W$X<KmhR=4lz|^Mcv&1c0)#CL*AFe4NyoRe5|ya{r?fx7u_QS$2q$_03XqD z=GwA*MLQypn^+FQ9qxw3-~j13%s8C(v_vtj;TL)EJ^3x}^Kazx?CS2a)@qgF62VZ7 zN0Y+$gql9DBrQcw&r+|Z-)gazetfqzJurV32t`ohU}aUuwHi!emUmSDm%5z}MZdPP z4wbo<r3_%}xXoMbH_E^*xiSrv6)gb(P9P#FX~Kq>xT#pdS1*EYZ4w^0g+fZr!H(_W zk>NE4wpVaYT=XB!*-V|#xxL_kZE<PHbqs}U^#gVv7Ei3yH)LbHRh)!_6sb%Q#%z&W zNf*`y*T6_HP&iZH%e<<$n9>1g0R65?Wgh<3bW}&;l<u{8pPKUJ{qnzc9T*+$&G0>n z6fNAt%Hp{^8{)LfIcu;70?!9j0%^blB0#^zrQ{A}c`CG+do(xn@pPP;qVova77ecd z(_a)}DdgWGA^J!Vm_MZ4h3@srGGm>Pv=$d(b=pXwxV#-<jwp+VFxKnxk0{Z@BxY=K z9X*0CYTRctfsNTp(EyX-r--ioTE?p|)(4S3$QhKOSDc@Tup(_Z`F4_FXwD=vDfLaH z7aVd=ga5{ByCazn?%$D?oj&krS?fE#jSvnNJA~TfIpSN(*}!?Pvr2>VFaq#<yOL+K z!4Oe_@B!nYUiZWI`wNJy0?XHZ-18RZP*$2^0yjkttd&x}@GF8sumYUok5wM02(~_} zi|^=AIA(GLR}xu5E{`NwgC?*3N~HN>VY3r3Fu$o``iM7AC)sPo$QLcOK<1eTqPr8f z`xY>D(GYml-3}H{ME4-m{RLN4z*`6?#85R1v;TV|{2y>%93YdNus<2eG#xrNth81e zpZi?$@A3`Me(k<q?#@2nvcX?`GZCO`LVHs3FT<XE#hL+6j^WG}$|{uziqG-IaQfo& zRqFiqoY4GH|KAH@31e8MSAs=wknzb6?8vSsdjYvJ(3uH)kAzIF>DI>vDTJ+>A3szZ zoK`sil{p}NTk&%}nB%@YRmg+Ii#;_xtf7b{fbbZIsx&`TJn{PvkXYo>3)%}=&ecKk zr!#z?4xI|v@7pyD+e-U;y3Ae4<Roq9dry-Sf`8C;+4G{F^q3PPyNyc!nWVEbPXv3? zL209Q8;I^glwVyVK8isU`_00o6mBF8IPsQpyqrQ)X)8K`3{Sc~ITHHdV2<ITBp+Ue zp{`8zLTB#?b2>&j8nB_?OwM7umo57T#HR}s<&H7ogx^Y=Hj~95EUNczK5xvc-;G!; zh8cR<9!~b$yN!u3QU=}>;4bX#?Un035A%-oC($cVHQKOXHN-iDX|wXOaNf;1o6w?K zSGuBM(oS^?-vb#3K%IEf@ij%#U=}j=q<6J5SAitRdeRkLPL{vO|J$*x>$@QJv>5cv z)F1v|$g|7VgK`5L@@uuO=`YE)^z@jc*hXfk(8}R}{{`*_2}_P;cs71?O!axJ2<FKy zr;@1A(xEjEhpdrGQ7%q6DJCQdN!Gy(4z4ao>p%UOT6_5$XFhPZG^ebzS066opx{w~ zciCS7`-lskq|9>p@etEIXUzNJswzj?@?Tw|H9h?2n9O`~T$o(Uo^@|$N^lIRAytJ7 zmx;1VBD#u7nz_Byzs;LmsaPG}TjQM+kwq)6aq0iaaUh&HYxlGO1zK<4WMDsXV1ZVh z@iNPKs689M|9&8(bR&U1g39fNsJULfC~aRsJd|Ee(*HHM;n=`YNzLX<$h&y7oQA=J z%Jvz1X}!_B272XrG7`JIbzQxHCS=AQjyCbQnJ`;BV}Y+=Ya=m7C~sR#onLRrnRoG% zpNy_R5+AzuMbHITuZwaZ+o=s`6m@e;jv?b#15y*%#e5%RYmL_d;q)+i>=3D6Y3h;L z5?h^Hc~w>FamO<N@m@iB2kFRcLj4XG>gCCj!i#<E^&_k97f-Hb)RH^XV(Iwz|NXN7 z8f|xv*>$h?-yYT=EPNJkQ#xD=AnWfD9uA<cv)<v<=is&x+TW=ES`4By7mlKzM$t6= zsM!s&4m)3C%&*c&q~H+^G!qx>?SAllol&%h*Vo@QVVcQr60>O$`abl^jZ8{!i-~C? zCt$3z8f7**FjwsUfVW_EyQbqwPwD5R6Yt|Eu?v_akKdKO;dQm%p**tnME2qMm|*?C zit=*vhnrK^mngPL6)|>zt~7mw>I+4$pg50Pk!Yv7IElD)n<0an4Us52^dX{7^S?gC z<(8!^Y!lgR6(GJchnbU>#>FGY+L;+i?3x-;HW)LB2wV0md^T%Zl3DpUdvG_}hVymD zO*jW+g?^SP8n}p%l%C4L%Mc%Sd$@N3%H`}<OLAs{+Y$sNjj<4p?-2jWBNf6wCL1M1 zbZ5n6&=5e}>?-n|_j*8ofKVpX;#rySQ5)&Kg+SC3-YZAz<G=p7@~Jjyk|468bF|Jz z|5M;$dyEhdP7y<dv<+A7h>vme`n?bVox7<4B*KDzAm%Tq)_&AIlgF~joW2(dPx&hF zMlAo6a9xiT7u}fZojdeDbCypEoUGp&nyRpNXCV;nx9}~bQwC|0#%tJAaF5#Raq@wI z?JcnNj&<+;#{1PJ1ObZKT40E=$pL?(Y~P=v|7Tz?@Ltr9>5#kYy)ZU4LXUd3jy8FU zNbIG`>!fDT7!&&sy{EXkvY9E)nt&QHb%vY-*t-;qL}O*Ht~;Y*TS&KTS@9fvTax>0 z#$Nq`0N>4i?pq?M6M$Rdc;{)K&S1@~j^;5vsCfokh%_JgOP}<Ptx;RAj7)kexbx-` zy!SJHp$6P(A0WX9LGYQW)IIx+fK}RjY@G!gUtst#Fd~;Um*V0mj|>lYX{B^_3kYiW zoxHn0JkfjzI>+cAQ1~){1%Fh}EgTeHX;3eJ)ch=($z@TV6DhRpbWN59<b)Jf7a{CQ zEaE>Iu9MGZ<0<JZCKBhMyn`E=<_gDi4zuJ%N0QuYl&;TnQ^Pgy95&Szv@4IHM3Mkq zujP3(Jj`I{)O_)p)vm@6GjO{Lc<QEnq6+HNgB|k<l<(^2WQ_r#+<W84ZnM9l8f0C8 z{pY^pG8o~$(DLzHyl6QJDT8uOY1ZYkQ-tdKk>qY^i(z4hDHLRKH03nQMyxzh)<p>T z=&idv$5dUgJt7VWzU2#+pk03KS_J~oUGeoBQrwSw<9@5Oy=MDs_Dj*(VM^YlxCVlL z8OQ{#sfE8uI~&s%<h(U$9v%U89&zy5dw2^d_e3Psov`2Pb*x$vr~5GJKc&#-_GUxd z{p>BKy8JTYj{7y2vQnD}P*8kuL_HEoT)xL^`Bd=?C9d(nj3D}(`5B$(!O#H0?+vN? zthIN=Fly5u)0eMS0tFKiUd<xB_e_g6JGa2Wq)&<A48J{ehj!oh_$e5-cnfTNS3yUj zzet+8zNk;$VeRgjeu?};=7a}QnQS2)Mrn-rRk~qbFh_ID5b78R85_#u*PGJKt*t#L zABsD7#0zXAXMuWB0$@~Ezbot@{}(Fw-v{hkFXTnQtHXG-$LqYY+{;%`!Ksg`eX!;> zX~b$qm?xys<j@z9xkc)J9z$v^$0#&n%%)G;6<z-$K3=QM&wj=3vwiNzd8TuA{d}n6 zMUZz%y`qUf3P`HwEd&#GaFw@m`xCcw)oGE%L+G$u?|-rY>iHu4jq2s=)d<=3=!ArX zzWDFjss}v$&AI)7NL8=ZXg~jTI|C}Aj`x<C849(w?O6fjAv~T3bIRLIw5|1rXYxE* zQagK-CE}Hos(HVs0*j+9v+mp7_mUj-SZYPPhO2e7JmdZ=2kZ<qeHW)BpMpnNZgK9I zAb)bJuewYX;<<aRx`7Y=T+NJZKGSI&Yr;kEnh|!8n`C1Cap}D<f1K%JHeEwKG@^)p zfc;#-VjzcDO@~Wy0(V1Pf&}_{v;u2#qXnkTt5h@Db`J<{s_c|_=U)u*FF>`%^B7S2 zJyOn!u@qB`{`hp=p?&CEErvx8tGVF}JVm=i0nk&q==ezFRu#<0$8oVHdeqB+=wTQy z-NbA7ma=+74uk5nUy&-o_QEWY%DL(L3}n8)=YY8`xBsCkdXUR@Aq64LG@&0%YR+;x zf8ZVE#`5Rl=?qanZ$>zKC1OO($EIM3uae>;n85{-a`)7$rDHDHrNl#_;J*o>Jo5M# zg5u9aEc5Rd((wsHmQ_s?+Y5Y8ejitAU|_o)f-fwRfHn<3^R~E{#^IR?(WMpl;ULQp zgXrqE_qDIW!n!m#Qle~=_Zc%W*m0(tg*KvSV96%g9H%bNhsnhI?Gn*@j<$jmC-f~X zrTu%v#YckXta?|Emxa4sn+;A0!<xd{@9=%~OHIxKG*WWSm+JS1cL$r$zM}I7tP{gL z%n;ERIh1Jd)~EmXY$bo;HoS)GBJ#}k3P5UTO958+l5fN6x@2^DGb)PHUWu|#pdT&~ z*zPve$-mljaG?4Hk%D2l2+LIpa)r_%;caZt(F>`nK?WJ<yHGffHdG^|_-gAZ9WU(i z+GdSzm2gLrUd^y^sS^#?V}!3YyAf`F=IlrSQixRB@2}pKA>A4zh#6>A8!*2RZ%cp~ zGATQolrBc;k@Y!FCVzT*dO!dsZ`2X^3y3D~lP3c<EEbT`$n=ZgugPWOYZA;pKAcy8 z8fxi=Fo8SfcYTx!EJyu7K*{uuGn%v28t?D3LjyA7PCrr}9v<_jhugECd5;E;9cHnc zo0d?7;pDt#9op9ToJ8{b_(5)K@mc<QNr5+6vD|=!q@~Q;nl~!a(f()H#b8J%k(w`h zV4hhB)a}dEhc+sg5M*ny^!_Y-1&bM=)8k8Gv8z=a#C!6`cVeQ}FSN6}fS8ZPinHUP zmQ2tY0xAQPJp<}0BJ?jLP$F+DMZ=+^erzt3a^s!XQHr*a57JY+SVA(RN_o7Ct?o}w z+!f*XCSgI3vHm0mmG&p|G;k&VKGkSttYMxcNJ@Sq<E%+AL{WYj=^eZMBG8y6E7<g% z+*BC@m@!n)oDTinqEPi!=J&(ofU9Ua`S2qL-4B;=Q46=p!Ntcrme6RYbYiYQqCu9? zieWZafESdW9`2Y~IUZpa?k7c9d#0khT#!1)f_oJR&NOtkNyDgGy?HKRjmTd-FX3z` zb^@MqoczZrHfZo8oV4nU7Yg1%q>mAtdS&|Rf*$9Pr#73EM7COkVH_z6E0@?H)$`Y$ z?rS$&g9^njU_F3COx@!e5k=pJWkN5m$@V2J2s|!?&O!XgHL=2xy?;%Ccf@a)IDl^| zKqjk(UX+nTX56m%j!j_bAS|;qJ6duLSv<G{BT&^}P!6JwkcA@pA457a;mfLK@NZpa z<A0NQ#{JYU_<2L&QXm%cs**G1a(YCNS4E$O&R+`+y?lDz*(sX(OC>e5+VYlaRgT%2 zjdmlFtgONxWx6EU`h`e^V34(K`X6xx4!CEX5&|ZC@#JJ&I$jZ^Jn#S_C?oTew_cWc zQHlK9VH(EX<Qbkq@u)82V{4U3DDr7HwkeVvYFc`FH~QHQ>`j1V`*NFZeZ)`k;VPQj zhZ8+rK>VB~yPW}4V5@7NI1C_*t@l}GD0lq606lhn=Ipo13uFK&a(Thzd^#bYmjoGA zRn<8e<bR3o+r=9n(FiwhPB-NFll_76N&p5`PYS0yrP~uU?LmbqD9kqJiTG(Lxx1lr zQ=v`f$G-C#H5h(0Y2G<%HMbwvi4J4=pkN0nx7NE>Og#|xexp}YvbiK(w>Wr-45!qU zNQqV0>ExTuYq=J68bZ8$lko~nqF>m)-<BizUB7(nOUgJ>v+E+e!seA`g}E;@Iwo}6 z+0`|vY(E+|EqJ8VA2AQ4PIIHAyFn5Flt`RquRh4UgklxwUX-bqlt(<i5ZK^YC85dU zwg%cQx;bMkoU6Vo0mr4%g&FXGk`D_L)BDLI72(tH_tm@E96axfH4yarj8S=(*D&jT zvjrrB*P9)zxyCs1ws;B4H>YlgY;A81JgN*Fdkb)rKAqgN(vgYGqh^Uj9bjKyHFN2} z!@VMov)DBnVv<)S*^i`aa01`rgfIy5<t1+{Sa@rdvt^0u$}2F)jYkR7lWcT~ss~DR zi|335W3K4;1QJaeTX*#%b#i}O|7a7U)sKW(dSbXTh5me^^UQ(`vl`Xx@7&l@(=wu; z4$-lJhr{4pSaXi`6!s`t6mc+}XfDtqZpA%_5)pgE8%DMFYddqos}!vZvvk@}d`rh& zto!<$M}s!-Y6`^T>m6s#z$j-Mny9U>ZxJ38$3)m(*1Ssd_*1JIKl1m;r&p_;4}PmL zUR+o_6D=6eGv{vQZ)HXscN9FjbbwW8wGzA8=N)1-GyH?!vjQdD$m%>@e$B=810Q)C zka**a_aY(7w*IlK@qb`j1FRQB<M@4J_RiIy9{;Gw#hU0_ykF=>#txeM-^D*H&an}4 z&JNChQgFr0Lxbg4wV<C7OS!w{<7c_T3VV8qA2}=1!2G*5gVT3hmM9m3q$o*@aCFwL z5^aS=kXAN!&DZ&f2096xA8&Pe*jS}`)RO7gL0RLak}TaZ>|m}5sfRJl6uQZeX!sJQ zN3jAc2}=8T&GLNj)7f-c0HuSZE+)GwuIHnR>oTrA5hOhun@zGd!?MZdYbMD!UFsyH zffGo4h#q`*)Fj#0QwwgCo46a!<dsm`A%ExD57ZoM45jJ8>_2anLce5YmNESK{P}aL zH<_rvVAM6n8MVJr-aGwj60pr4G1sK9WB}4?!0Z=*;P<B2FN3Z`c#^=AyRNH=$QAvp zMaYUpiO)@BA!D*e`V0P}blzgyL_GKP1_KuzPKkF21S6CWZ?N-_ZYG;+&}E6EObXB7 zCk6>+YwEGEIvP?pR<$WHsN^Cls!yeG?UcFw!56Mz^jevU#ooslDR2|X3wrXoydPHj zTUp8WXW=fOI1TZ!=S&Pabq=*lp7-uVTIdGoiP{3FvxJ)VyVocpdgcH=Sw8=Qmz3Ig zbu>FA%}h_Hd+=sIb?dvF1bfK6orUC<mvfkg-PdYJ9+ivpe`wQSpBBtWNXq0)E*?#i zCj$$4oF+|mm5ci+!sl*D(HCfbs3TRI5v4>{2#ub<KT!w!Un4Mn=virFyg9Ooj9)dk z{<BleI9NE2R9jdl#SlA?{&1k!=G0UW^;heayP;wLuczf5a@DQC3gK)-UU@kSKmc|7 zE#9O#h1tKC*{9-7u?j3dRW7e8$k9-cwn){O0Y???Ku33>6R)an8OU_@qP{U;vvIAp zR*@gaVNx9ZKm?yQ;|@o?6AM6J_SoXH`m{!R4I=V;x4jHEK~8W>&r2_3sq%%%tyn3I zS(?_FiCPucqyfRP(}Q3$M>o;ocZ*m%HZKMbLZfi03)05S!qL!wVC1z@`as$9V=xx& zStIi5^ux(DHPnUzV!tbwst_%$mJL?U!i#)22fXq_;H1`^J`DkCE8v=e<Qq<6hE%+~ z`L*G4EB62QjRRmZyf163Rg68zF?R9LB`C*lx6@sU$O)nIJ(k1HU<<MkhUxyMPev$* z7$-iVxZ1S%yKmI5*oCn5rraZ#Cv~j5=J}gZ#sv4waGm74%hat4O?Pscf1Law#`;PF zVjP%s`6|pvYxsIC*+U~RE`~gnkroKP6`)0;^T6$gv#u!Mi<{ivBKbs%Y}uioGWY1; zP52U?PkeT(hR=_7rnzxj5J{1=HRP0v^C%_iriD&N;_C*VYnj#YaHjBb&s+@pC8b_S zJ_74*KkyNy-cZjgPA**RDUMiW=`w(NAiN8e$w0cX-XvWvKwR>P(G@{OpI4vnNejm8 zs{h0g^S&sTlkSf@C>NUW@_lkk{Xrn>Y9$3fbWD82O?!*jgKx}70g)!3?%Sr2@^OgL znH+S~FSn`|YDCkdWx=oyGQNtO%hwLGzf-ynoONgjvc~p6Rcf!;XM5rZUI4#Z!_}dA z^}Zs^LOl{p5yf0V>9m?AC9Ntw=1D8X<n`Zag)Wt2Gu4Ng-?-`MFwG&n?u*xp;g*ei z*O^&R@Lv1F%+^_Q-h))ya;@U*VB8RHpsz7J#=-@wbi&ywkj$VPc-^`R1(b9AybcTC zTOjjYzX0lJ+mjr=&A|T{p3EfXv0vzDK07^Ys!oa`Q+gSrba2<=yF3io;Srga*)Cz> z6e6B&OTLR=YQ5*>>GQA2-oX*WMK8zgykk%u;qZd+Ye=rZzu_iU=-Ojpp(cbvNFlAI z6ysUHE{6~&eXB=Eex$vzXVfQ2s7w*ejJh7GWZp2pO8LP<!zjsf5(j-XZateJ3Ogi? z`IX@wyMEM*nSvHrs-*I~1=xJ$rDmszhhAe`Vw5|q!sf825}dl$Y4z%fge-H|g6+{c zSpd~*`WB61U84lSg4thjNZ)HCazTH@D(m*DZyYL{BzHgR@a>8HF4OlI94qcWeMhP$ zPoFeD@Abd&3a52&vg^z%e1$1NKX}t+a)KkK`Kb>dsMd%mWIRK~x9A7k%Jy`8=uHLl zv<auJ{dBNcopN6NFMP-epnH~Hd+|(#@<;ZcWRpsPv6#<t5y2qadmCGo6b0C)Y9$KB z*l5}lnls8*`}(WZbuS<%F`NR#%F3#ui2A9wePQ+Ni=PYg%R_PcWx1&kADj00-!fD{ zV2Jaw+|WvyAT>oh3QYJE(WEPx3BRfuvE%}!7aV#%T=)pNmm9<xHnHuLmlo((h~|Do zRuF$nG&wvM6G;gpS}xj|5e8v8-hI=op~XWw9e(un|5IM3?~Z(yrT0}MGfwtVBV%WD zP%iCZ37uF-SfLjmGyN2%u^I3db&4)Uyyj#>{){lNblvVw@s@c0yGdBv?H%daa#~f3 zYC5Rab8AquoVW#DH1E8CT9I-JU9=r?jB+-Vgae*cq)8~GAF#HqE$$ZPSK^N+MZqAj z2PZj;dhDmf<z>f%KwOWnO?~FRn*dM0(&C1Hb3e>d-5H2Sl5afm%=-X)2v9=wJj6&D zgox`}_^{TAVsW?-IrYX}?JISjkak5f+FMN0j6=0a7){C8hooO8hb`wY93cdUJ{fE% zI~CbtaN6CI=2Voy*sdzP;&*LU<aG~8uL4&iX@duwVmdA1l-P_Faa3=5<Th8w!zoJu zWl^1v=J8l!A{O9Ld7HJr`8(IJ)@OsT)@qg`p_dSH*SP+^OD<FQt@=xHg+dxT(r7Y0 zq2-7TkLcHThuM}}Hm?JgTeLT)wLeS?Vj;i(_6U%oKN^zL(@PM)YprE4J+vFo4y8|( zSih253kgHfK+B3G@zx13x!j*VyO>l#>HDp8pp?x&puqVHQGGpcNV?xZL{1tv`tZ9N zHaD9Ctq869QS)`+P3lvb;w$kW%(45cFxgF5G_UIqg20u<zanDm-mg!GH_*c(ldlOn zHJWj^e@FB0u@_P2XDV-xR)5i!7|Os$FTP==rHC^r)o^}QP#8s|6j7nr#pXxD#u+o9 zkgSV`W^uJilleXah49ssD?R>z*QYo|zn^Yvb|Zzg>+jjVW|MBGoypCwtw(E9^({IZ z7tPS-Wv`1CU>r}$HAN47Um|P9t38$Mmr*eIMD|vTBl5Jdptua90eP2^tae-;!@9^m zOb2Nsk5$4z_F3=l6zr0k$SOW*H&}PHe3NaJO_gUBN;#UGr}fyXE@+xDoXt<+s>);* zVWps@7yA3sjq65sL@G5M)rU$sam?>K9HGm9zL{T;;A8BTfqd!LPyB@p;Akra(d1yu zmPZ|$tXa#Oq2?4W8QTK#<phJ$j!D1qBG$jaUFRP5o3ofK3H~AbbI0`;WOo5bGh5_u z4=La}oW=2<tp`5Y{~^jSL$cvszQrL;hK)j7eanA2r27vEZ@c*00`(G{i5a|A@+wlk z^(lv#3xY<hGRz#}R5AKJOM6&MDoKx*zV!VnnOFQO4kAkuoZDpVE;5H-0Bc`GPzN`C z>W(b<vib7~ON~TRPNMHmQVcTlt3zJgG4=ddEgqAY7)m~gvwWN{gFFXlhk5UUltodR zWq6#=88*&^XnD~r%XWtFR78<*eTz0j{22;%T@-LqLNLvKw*v}IECF07M+d;L*~a-1 z%Ds1_aHXZi+2lHJUY9eC>fsG|2efw?MehJ!1C*L6$9j*-GK&V4C6zCt-yHBFiFqyi z0TLf!WM$9RD9b(iOGhJ%<?@f**bxWUvSL-`H0}Pl+HbBWo5Vs`e}A-p6g5gcLJ`$E z&Ux>$d~5PuTZ$V~NtM`aDG-eBRZv?%bgn331oRGj+jzo^@)`<l5?2dw-UoX3QW01- z@OF}=#*C&C<Mj>paB~%9r!rioa}MN^ze|mI+^?wF+8w9e-{BWx*6mdxVni!QY<Jyq zDCbjgxb2b3%Hek%3~b`p)J$Ld@Sx<gm6|TT<S@xscx;ii{9-<`09t`f+Si--pYq2d z#n$EjS|=^M2b!^yUerKmfJ(Id*(uAU1I6xXN?oN)*VEQbT|Y|;F4yl!SIFgWc+yy+ zS*>Igk~e@sBSb&OECn4SB&BB35^_ksd+K4HSNAlC%WGzu#&DQ`5uns2X2D?onm-YV z5=N7qaGfkpA5iX9Q4^|lo7QO-^%sTkz`AHYWqLR+Mh?O{a5!o8TO-q)W|iFn<l5Lf zh9?Gp<dn5q6j@mkGB;%VH&weWG>p;;f4B1tao^1LUF{!BqhTc0EVp`95Iow|)FjK3 zC9JQ%4rr7Oc?%lr`b+<Li*f8hLu)0FP|-yo)L*nFQcJnZ${U}}0z7`<iLwcQ9CLDw zG!t@c=O3dewfDO*ZQRulsTu#HvA&Mxy~HUWc9=RII8TZ95ncfleR9tAl}e>pf;Nx? z{FiaFq(bb(ydTLB@?Dn|t=$rg&u-JMRPunhwZhO+@t<!6h}C)c)wvoj98=m;8}HoE z!&~ECvfg2pvz6Kwf&Xpj^jjfAqWJ}pO}x_n29tpcKw}4^K3s0{JSY<Zo?S-Fmu%h_ zNA2m~rh@+!Y6c{#zkW$D;m{gC+Wgnswk)xOA=*SQt2ijfXX3I7Tj0Py+4H~I6cTdY z%^(wT(PI&Z@HO<xQSlRp<2`ivdeqA%D3ime{{5CAhFUweB^zMg`8Pa%<6Ta<?iZ{k z9LlT;@Mj&63r!CDng)*krj>xmzW-A|fLF6VzeI^GscfUKf49vZ8VNlbI{DCgTEV#$ z3k2Sd0cGDmzN)zzQ#d?@egxQ;de^=$Ugde4I@lIu{8ppJHQUwC9nCJ*+lUUX*h3fh zif6|!vATaV7ZzT`)x4jyJfn#NB;$#BW$JSj<mJOHQ*W8s*fj8#=*nAp@3jy<RMDl* zeNY|O$pqWf+UhR;$N&;_r1cn;pE{7Te#ef$m4yl|OZKS@g?uCWw<j<30b_*J8az?w zh%#qr+xjob_uoe;EMcm0;s+k7WGL5lF}CXU{>I=t6*BPjwl_v5%R;GkJYm&V>hE6z zodPEj3%nj@^z_VHwG(*8q5=wln~q++H!heVfJ3pl`;7qVj7&yOappN^#}exNtvqz| z{P$<qoa}dsmLs|JjjMsJY=&){&p)nnO*}QNTGq5rHR9Bu_!^pn61Z|yIN{=`)QDhX zu1>`4%_IJR<z+`Nm9cozyHVlBW8pDHF|ViL!ykTciI*A%9JnNmxu~e6WF&~U)S#M0 zIzoLut4@a=qt$Au8uV{i{7J9o{eXg!*T;9G)!#0wukW!*No=i4MMbs&-$bqB^1b|m zs_t;c(#MY<<weErcE7!KG!W);J~tyHj|?FmUC&x&EH8NREyhYJWq<V>)%|JNd3g70 zp*WM*N@8ybt>#tU!1KA-Gvi&G(LT;`=9Q4M*<!6LkGPQ-wTkFd;c$n&Xw-$djiV&G zb<@hTZ_vksl@H9CjJ8`kcn9<xN_V))8gR=f{A0<EZxvf^5F0|)ulH^w5D_AJg5PNH zno3$Tbk3a>6<Tl&EThK#*ttjxJo(bO*opW4?KR7?;$5;;lmJ!^?sqC`q~p&^l^U7I zd=Fbc6)cpYA&Mgd!FR;fjm@qhpRHl}Cqy>$OrkRsRqn(7-Uq)PU6#5(Yc)l!r*}LQ zJzSJL*BpefdL@yMk9bjl$VyDvo_zk6m285rVd}-oVO}u=O>2$`%Q}U1t$)93{9V?c zT?p|)dQ$$)9~G!_5(q2M#y(x4KCg&a$Na%<i5%%(YFp^yws-V^p7%CTw8KxJjMbxF zAng<R6?UYw0H_DPN=e4=^%;-((Sd8T>55_|-0VT}>66??wH5Y=PtzhN`WweL-)3U? zy_d#dYONNiMDP*6iE-BadXDJ|x?34DQq3`OCM6TcvnV@`NpXwiBwfqu3RC1&B<_g+ zHD*TyQ)5f6#X}&3$cSJ<ue%_^9@n`{YGYIbTf~^)_9<H9OHz2?myBQjZZI|1D9#Dq z!A4tl_rI0BpVt{3Sd7S%TNz4>PLhNzhk`Z7yJ?bq9<^>C==&M4m8iauh%>o|Nf{R^ zD$nwcx~0fX(1d|_6u0kzXQ7uudSijE&+xaso{r;JeM7DPJa0`iQ%N-8v*^HN5Xqu6 zRCs1~AtSSTjvQV|g5ey7Oze3FNwO8bnhq9HaS2K>J=#oX?SMnR+5P~)G}c(%u2f8b zUrv&WC{u}S6l;%$_u3?s#o*IeH!UL^aciby1M#s>UmQ7!T`G63&xoIUL4O<D7@lMd zyUZVcP>&r9tHre#?MO<~>6(!r*$#z~NmpQqR+l_ls?L8^nH;(3;Elzf5Qp9UkAui# zb&`DQVJ>1Cx(Mk*jOI?7VPtmUUC7}|SPP&4{@m@LLSGZKxxG;>9E)|h=FR@iU~38G zNbdwr$ZE6W4*AMX?<o*94-RY@|GT^^<A}#EvCKlbC)u%q)6EykpfvbNZf{CBliT4F z%MKcv>$*IlOHaYt6j29L_-pF85rQVR4?te6{lg7ynIK^I7FWjmIWb}5`dI5k?RewL z6iPUBu{W8TWA+3z3_Po>`0S7~71;3|@h%&^B;nS&@2QbjboSCO9HBXP1-^K|_T%n9 zbGE<F{+jLC`_)o-_oB{yzHN)u=jr3l)-SKs<m4!A5hA!R=+{@;Y%LOjXUQEYi%zYQ zuC;FN_I}?b3K|Na<?$5BVNtZd8viEv8%{DYzD+8`lh+np><gN6_jWfvA!~o8qZH!r zOmKw@+g@*#Ta^_?jz9HLq2V20Hl;Z>-WX@R0~ZS>aqGPXS+{-5$bID}_q&xg)Jv9S z1wR=Ztqq+c%_@z+m^1A!^KBH;mlz}te*wgqEh8Dd4e#5NW1^&?rDjNIA<|*`F>Nny z$)mGNOe5L?SKhh)>n@rl&qdqY^}Gkr-=dn(^bqr=@WXJDc6>Bk<-rxn?`U(q1!jze z1+n`#;Jtl?u6G|d5d8l?s=g{Nsx{nOa42bzhM|V;25CgPL{dT|q`NyMq=#;h20==? zyHmQmyZc-0ea`vq?}|GvX03NU&->4h>7_id!$neCH|O5aCsD4_cZ8VE9`*j|Jx9Bn znWTdww$3A~u+<nP?=wdAV5K_B8)Tt2a<BVRGvb)fPp=JOaH$46`6)@?++J*mUv_61 zl^0}o<5QGV-?$4IkvR!1M*MTGQW;=9c+S%Xv%M4C#gdQhThLX+&>=a};_helY+lMD z5((1k`d4+_L+KnKY7nfq6l}d7^ag+QY1s(-=lPWMd%Z9+J<tU{0j+u7{~ptq(`tYN zlX8I?uI&<^x-D~c(koz9pW2stuYN{=_lGIXE^{x(qs|ijZCUW;oR5n=Sx!TA77O?w zTwyCls@0wmwt*!+7fKe4g*)gIHfH{9rCZJemz0?0$ZZqNZ5X|JXw$$8<Gl$jSFK8M zZY$TdVF9k}Dl@=te-K)(RS;S{X-+LDg#HC?i%lCct|w3ThLcRiIUt!e@uH8KcmpVw zuYbR<QQ$Wcei-Hs(qX?JbkG_I5vJES-#3dh%xh0F+h|h4Wv}K4Y2VuVmL`xhp2S>D zARDFyRPaH^Q%7rIPK!ugH!xpPe2Z%8`qMAniLPq{*}|&*@exvbroWu`HZTW`Y)3%i z-F@wO5R3k?M^q@FsZRHozN!7%^D`huz6_xtry;5d4?h41MS{4CYl8a(B!xKge~+Qk zANsj^dZq5gnFqN~r8OvK@T8JD>mjK0#h4v*Y?zbk3r%nr#ImpqCTNlo3}Es!R$-jZ zu}@GzV>9=*Z(6rBXbi~g8=a14nMkc3l-BL6m#cNm%p&%h9v%c#Ilp*6vPW6f==s%X z0ld3Tc#13+f6vL?{%`f4&YS&lg!%4eY+rTo?A8r0o;>e2|13A>$(VHKQ-^qU;i&i4 zv^-i;bqU{YVBaD_?xTy$b~|C=tlu~5Xrr+Iu_@}7^}gE`{)pqKEQZzWZUrL1tHpG( zefVq3_eyKY?j-Smf9XvZ19he7QwM_I1=We5Z&M_HpYSjER;5C-bQWYUbhfZzstO|E zN3VN{0-Y1TB>2yW*k#xAQr`j({dmeXsL8<V?kj>>zh&81oElzja2WKBFfqik&q%gG zJg(VOb$EYNeQ8@iYmdRzzG+u_rTt6fQXtYpn5~<Z=ehKBJLH5=3C;SA@Kc}S#ysGF zn~;%V6Fc4G%EB>}5L_~qj3AA9)J}aBNg%0ECH3B20Jp>^7#`_R>MK?UK0`zn0`W0@ zvpw%r{jTkb3w+I869@c2M@5?Fn4NaCR7Xg|8h;3g)5%3fYH$D5ElSErAMCC+ZD?6R z;V)Lj#f$v)W5oMGn%n2rAs*)_t>m~z;+sc8jX<4$rko8k;>qILH7mi;H9wN6qWC#4 z7JEG(v({dWOfFxp_Pdy~;at<JFml#X%$p9)`%Iti_74vY;crYRGkY#=w6?oxVI@Q& z$9yZl{eMGK4WmqYc~t6_z;3wtd|@OusFx7&^C}ppG@(4h;wVZdD55{_u)%1$`Te-h zKA4th*GW^S06ID*=7;3UgpPg3$Y14pG{K-1lpLT=0XuSvZ>C(3;}dgonTdUa0QdR5 zbG_1)-!z5m=;CLVh8k<A|2%gV2T6s^leX(k+x_O%WO0tg=Mad`;3h7*UHG%@xxABl zvcnYmGCEi%#X!*>HtJ1Gcc4W0hbH4|aQHxj`7|??qnVkRim6l=Qd|&(>HIvaJ-~cp zfTcwnNnyzyFyI&g5dkt{9NiqMqv!%BJ_|1E;W8>gZ09nG1u6Dd42iKQ+eE(%4qAFL zpOYFUUVHF2yjz{C!zrCk*~Gx5Iw%3{YS_y}OmWHD^Zy+u!%ZVh?{~rSBGay|Y#&YZ zT~j4mC27K=ijJb23icX8ks0GumSAf*Pi$}+7jzeR8+K|l#=IDuje5pMdK4#{uMj^Q zj3;F9+1I`yEsd&AI)o7KvuyTIxr2vG@ygAlh+vE7J$EP87nU&8CcOSp>e|W(G9exv zi!X3E4zP%7^qB;ol6YTuqL*awI)of;+mCDqOw~#v{5F`vGYIuK&-vbWG{8z5&g_#3 z>jg;i-${4Fp=rNrW6=i57-RJ>CT32dYOv<roTXUB=BK;8+1s99tri@ZeYdj3JKE)? z&VzU#|E<u^?|J_{SH=Aal4Fk!vVMIowk&?;kHc`9RVkjwV4_kdF(UFcT0|BU6;ca< zZ3;L`s4S5l_SiKt*sew+aDdR=$5|=<8{%*|eh&JNCrClY#8~XlMn*;%+9*x34CC0h z{eK@A`(E`W27EvwmTX?^zVCL1??b9LF<v;kGy_E5LrO?PAu0+*XM$oi3m!DK>s;>% z7u-i`HiMLISw2^3K+9`=$x$zk5u_rWW<HYO+7Y)k9JQ{R6}C34^`5%%eQ%*8S$yDc z$3Js1EoLXNMrn*hs=v<lq1R7I7I%rVExq<6j9j%}P|JQZZo5J-UqTd7`mVWfMVB}f z3+wc#S2{GNz0n~F$vx5ky6eF7gGN*oEDMpeMpbNq)9Ra*{NF6`c6l6gDpgM$Mpf?r z%tw#+poe2ou(a5{v*3uNj6_V?)^f<y(%V8==1=VPf_1N$h-djvh@ASsJRk`wSm9SN zIs%T+E+24Qwm~>MAas=wNul~@dHtUq7T9f-Ko;XXpx+sz)6y=wpvtqhX!{2l@Jc}c zb{biX&|*FEhW+=yT-6L;F(fJIY>f7)Gdu`Nc`IN)`q37=<K8}|E{0DD*yg<r-_DZF z_$9dvR}w@gW#d2-VeuxoXDNc(lox5!#_{`xuW9}MmDWbOum%%Hzs%psimAv(h08s< z9k0pM2{@V?snMv5;@`g$e*Ab7l`#K3hhs7yxW+~SyqIc6)C@VMRDvZH6_pGxi*~J2 zBuCyz)Xy~vrcjwqpl(b}=<(uH_`a5k3dG@Xu(lnDxf(-?EIUj7VP2#rn0`>)GrvmM z!M7m2M%WSW46pk!ZU$A=JfZ53bAQMuz)R*SbLAf_g!tiNY!27dVjr%2<062#RrhA< za5EEh^`U<DQ=l2;606IX;&aI<Qj66sGq9IWuJY?sBMww|5doT3cxW#0GySH=lVxKQ z_bYD%v1R0MNLzgi%<)kg&s*galDB9_#%iDB=HlnN0DKW3^~vM@E%}rU&;oMIuc7|_ zS6`D*>`Fh<m<$XIYy!m7_-PWqb15@3qMKbGno9L}+*!-heoOy$hIgs>&l))Jr`7(n z^3H+&fxi+At1=D9F+-5y(3PU6$mg5L4S-gP-QDDfwYZ+Kx#30W{2c>Sq4ZPQOnBA8 z@7+*Jit>UJuul?4(VPc#npB#o*B9GnMj_x`EdERvQxspOvIAa5LYv%iU4>=NhPhaG zA$(DR-ff##-wUmX%S<T1n|8i$Cf2^TT*ODgBs|7oPPi#(Mi2V+I}+m3yxrLJF_D|R ze*Y`A%}lsS(17dvG29$H9=U~|JP?^(Y(}bbdaf%W5!n4Mf55valVCpem<<bGI{387 zrVaL1@p)IcU!G?XQs?U0ivK*u@LI3K9MN0$E(gcS_RUwgqKI^*`GSIXfi`G=l;;=d zEHyU&%5ya`E3C|XjYZ^ZPV_W;jW*ldYSe&B3`a!J(eEO#&ka6p)UCJI-M6d?di=>W z>Ml{HEz!Zt8vFj6j9={A%n_qcDv#Q4K1^&J$hk-Zb5dWBSrj>ZvO}g_x-BgW?V<G6 z`Bcf3aS?+RgyMT1?n9G(`?Pw`-+0bbOq1gZR+BuhvACIAHh#KVf~ZRHNc86jDhD`Q zom@Y7@GSLnfF$@}6^9oXsBWC$p9PKCG9_>IG|#CSfy1aclT{P(-|=W;`!XI`hZ8q? zK(SVCD+hHc;@h3V4thl%rRGP_Gz7Da%EK=MrT%fs(+hcQ{O%$YbhijHL8qyYd7PM` zUu|dn{2N-0tNMLd@hz~@*Ee{)!7^txBHHEX&1lnjX9^ylkuWaW3%_VlWi_bg;mOB0 z9M@`=YO~6+V<099jy~L+aNT9IaD66F)W-2&enWl!kVA~)@1QCBGs#Q?$_2@YB<A~> z!x^HFHKM7hxhK}}>z5qTHZTne&yx0x74%%LEjxDs#m!kak##T7;!5_#^D+)Ps*CiP zcI+W3y#v6(`y6u%FrX@b<TO3u(T|Z#=Yc#+S30QqK_jA~{u*|BNw^rwj;KR+zpAC_ zBY!SS?&oY7hNcz-$y$NXg3vUPcEC~G#7<XKGH=bYl6f<g!!o)3#8ElQ+uA<zuOUG; z{B1`;j)RxvlC0F1PEuxK=&@naWCWtWD44YfR7yX<#ZZx2IaZ3|=aV;E!_FH2U7gE8 zwKJ;Fb|!c{_-+;t+CRnaG1`x3I%~T_SBX<l-*ZR<b1G*TH*V9UyNDeD?dWv0w6DI( zhT6|7&D`H_uNUyX7MLR(;d34A!l=pu&Zw?xsY->NfqO-!Rj%xjYE3D0fH?)-d3~Sj zic5!WLSgslG4gpQlJm&rv)Npf{NFA-rCy#j9owR2QF>7!QqZi1BeB&S(R@xb95UWY zep4k4xZ3w-6p|J9_qC7-O-7Jw*2hz<3=EB*5#~0-TA$rn-?7s3tR9wL&WG7W{qR~% zN@r%25dCHR_D!=U6x*E&PWQU9oGka1lah8+kjuyi-mMNn_q?3^JSk8p){Owq^$~#y zlAF1l_b7ER*P-9(7J<LldUv?ewKu+J%gJNM2R3yb$2jP~Ks?jL2jrKsn8u57*}b}~ zUsZC={w^27zW0{QKSJi}v?jA?v7~w1)PEygUWVIMpe>Op{Pp({LULZn-w@Zq#w}^( zV$`m5HaKWFJ=&wLM|m@3chV98k7S~K(lVn(Bz<=sV6f3c=dkf=k8G{r`)KnCUPP#h z9yTNu+ZHSxmT-ZnvXq2LH#ic=e<hHlz=CvJ2MxVBG1Y*?zN!$^{ld){%)fuiuMkvs zdN1){$pJ!vdB37eZbQvJhdm~6xBMsF2P`GQk>;9M^V)lQA3OBBp@X>$lJ3xrfq4o! zt0;zx{+1A<At&Afwqb2aioshLV4dA*)vQE?dEff-0eJa?&wx0S^P99#?@Ztw%n{8f z$>zAfZ2h8@#W5h0G}~DJ?bLVlgHlPX#=O2>hRSn5pVk`LQs#|`Z6+eIefI6oe9<e8 zx|Ikayx95YciN=G*`A2(lp&MnF-_!cQ9k&;cl(-TL|C-kzBAVU(xx`br6u?v6IEo& zJ)UjD8~{tRY^FFV)0OpxDurMoDQ5#*Ynm5o35`k3q1+YGcO}$Uj4@2-Fpi){mKI1z zNZ1!F%G&+G|Lfbg$O4rtvt*f_7j>x+;NzgmI?v?QF4zCQ0K{-q0RA`t2UZ@vXI0z5 zfhU)9yXcAIsvXK&{T(P&@e3T~?kTAB-rvT;3~m+i(t7fgn$M#OW<!}II9JUSw*2pp zYG;2GLt?%9lcVp8HV(x0C>z=EYx^BHrsvY>@RS{|C6Pw1zxD)0u*Iye7HP(XDalg% zO8xMum5V%%_K1?6cw=WosDFp+tCu>MIG3cvPAyr6ZOoi00#h)MwJG$6dYM`mlmgA3 z3lP$gD)WB!y1o4EP4(h;M~k~Br@9a*CH_P&rVdn+<}l$Q*l5?WMULRT_0&ZtCK8^i zqAb!f4OOxF4aD2@8Q0I}$(Jiy3_z-mBlwS^o=>u$$?UfW<1B4>p8jd;d%8bH#(Vd$ zLE|05JI#Pe<T`FzUAW#3F4O&h1)@mo+^SY9EU|_f(Cly0>IahYsj*D*{<buW37kSR zJz9Z<qczI%2uG+3>PfS-84MBbl|+l=2+ZmZT-NiN&yt(WHkTBzfS`5iYdiBFIkZ!Y zvzM=s5g}vQReloT-Iiio>s0*kYrC&*R!J~l+6+|gDOyh*bz`XLN6)gMQSOfCzFuo@ zZfZJqjT2(rtX|-?C5Bh8vFZLD8Ea=PgTDjjWbuR6-Ae?R_4AeWC5bb~lA!lpw(w-_ zg~LQCQMW=%b*&A?yM~=^_n%GS{q<G=lxQ=%Og2fQ(7S4ouZI_|kt&k1<4aLbBo3b* z5QJLR{5gJ(vqTh6;B;}d$d0D*1nCKXVfy$p#uviZgb0`Jq<47B(?|_F(Xrx<40^<M z_|MXHNoAqhw23#(y>X4H4h31C%Js;{=rZb*U@!OYgN}(yVyf9Q6_%^WBdTbgABibK zss6omj$y3a_hOF=1jdaSQ)J&ZIFXSLEqBOBt&`|xDB(wq<&I;MK6uueG*$dS@eK`a zUFW!xrh97o%9hE(e53}PU@QQt1tWcqpZ?#&`p=ow>jX|L3pphT#zBASO^7{Dh{7dD zoUuGv;FbF1z@1`E?^Tmc!{VFqSA(28#8F}jmxGDFhnq^W@p+Mc8$%*$*ON0A1bo+# z(xNd&k6cwYi^tCVgfb=a_$#1%vp}cjNZR}&bJWnHjKnY<OZ0w%HuL#6xj2hjO?ksl zmq^!dKkEIMzZ4VpSez>%ONw!T#4GxU6bh)&0oj@#dFFv6Z0!2d+@dH$RGMR{!6^K^ zWSgslSuwo%F!aHj28iSM${jE4nK{`mvAHilC7bwK6q`q({^MFG%U`OaSh`P!T|aQn zZq_3iW|NShlMG)3$5scfroH6gr&gm$62Z+^>oP=SSrf<=zl)K@?X~z-ewQe{uki`C zib}>twIc=@GTE5znIt&X9)4>s1xpGjKgy^#Mb-aw337kC@8hCY)6ke7cjgblvK*nq zmu4T&wWGLtiy^JKOQ%$mC#Czj;w^c+wdpE^?q`E#fzHGyY3kfpDIgW3?<K|T3x7D$ zgUZ)IOh`M%>8=u!nM*osIGMyW%#fADF94-l1a4jpBnrsk>`69Yd;4T;(aLIFk+v`^ zQFQ8jv_N;<cplLR<fh1o^N@pCLe!vP5OUL=uzA_ZtH`Gd$>3<Ol<qoDOnUWhaK6?N z>C=fR9t+{)#P3=n3^au2>7nqn28zv@@Ghhh7iUhcPgKhkJqm5qnE^sDI0v=`*^#2R zb0GE#k|MhL^KCcWo#`>B2%hHfDrA!DCld98pSwqk-v@j5Xm@yNwH<rbu*E9_-Bcko zg0NH`@pAorQGGF)9`Jn{tVm9xR03~D`ZQ{zKys;)K_;t@3}71dDn;h7*?Pxf`WsWc zi}BW*kt+E)x<yO-(@sFiQPTgdJCEk5iUIofpMnpkKX1Oi0=wQ|GAh${R(7Jg+vuSf z8<VtQj$AoKFJSDp!sg#?;TC>K;{s0T_lJ+n3MK(1oPYd-1C!Cbqw2HVsCAY?ja7FQ zU}p&Q^qfVFerbLgEjNd-;Vj$NPxe6ZVlmNEf{$~rnm;LHxt*2<_r^-|1tVmze4^Pl zc{uKKoz=*R3UU5`rCm<5=kC{Ud??)i36}6r!#9_E1Dz^2D+~YUzY|`h;2y6Kr?3C8 zE3_i5&t5Cm+KLkQ>o&b6SmTh)Cic&LJ4ayCV3w%v4P{elrkJCEu#cmk%>Br1yf9rX z?sVxG*zp#&@l+XxFnC)K@nJl@POHZMnXLaaU|B)%WO^~pAczF0(}YqX{yjt3tiz}q zY+DiWyD3P9#VkXH#~&T(pH|Z2_-T+=`09xxSNd2$p1XG4?bT?fa33Y4-qa<*H|8?& zdD=1uROL>8Raoli-@Fs+t;B3!P=Wh6hPo@G&<<k(fhlSZ3)c4Rf{hwI&$TeC&Jlw_ zen(l~06yVxS&GB5<Utbb7AYw%R;8$gEl=C6cS2rZn^@%T`&!Sn1tOj-)rM1qT7T~2 z)GV{{g+@a%k&%(P(%9OGO<}7LM_IWQ&?=bgmAy9O)MLf<Qwo%G8@BI{8@`lkIDDTv zTwFaPh!;+MC#Ga(4zGJpO|M3*gwYBLcD(S>HhaA~gGjYbhY`_}Wg`G1puojVw>tBx zy^ug=SB;ift2{Fcn>WVB5JgXQsyMp#qtYxT6Cx;93+$6T8>H8Wl_?_kbB%!6fl@;+ zD$6SF6GWKhQ%H9o8E(Fh4WkeL;=zGKr-AI4%O51AdB1S|kK2u7XtgIRKp&7F?A>&c z#CGBY92?&2)OU_D5hVP1$QV+*z&62<RAaX(qv_*Pr}Eyut<9!;U7N74oUBhyIaS6L zD-6Ilo##@ehQDK_zIgyzkqVijgAKjU&dw*{t}_H4Y4)64m!CW<+^@lX8L=#~D}j6r z9J2Esgbtjqk4zYy7yaZGb!;XhPOZ%MTb;8BjuMDsT?F8qS0okw_OW3^{3)qj-d#v> z7UP<Hy%z1XibqYmT0l7h^}qf>DncyE@~%OIvaD1DT24VlVB1n>EOBRu^>!qtlH+gM z83Y=tB(M#bTBub==#&K43Ahu)jg;!A)|EB4Z)QyJv@Jv$0KJ$$v&_oOL?!I;7&AVa zkIW4YgIe#y?KxL?D2_`8k!3uH8)w2}c`UTU?-rSJDJw##($<0%J|e;h_PGK>K4;<{ zM&EY=*@;L$zeaCNMsNu|f2IBTt+xFH$u$zAUg3A->(38tYp~zZ7&Ue~og;plJf-~< zv<dfPm{jfA+TAI)ntFe~3Pb8K&^p@yF%a~pPd|MlCA$}M@E4R6cHLFgPDhrp>PNy2 z^__f^<f0CA;&m{u{2=i@u96ik6xcozHn>_ls&$T2`!(6X;YoHUiXxakCeF|HvbpNT zlDA&Jb$+s1i3WO*6xfr!kzFnVIHZkR&<n#Pp7Lrv3Ki4g+1=_LsJ=y3=xW)c_}af; zai6%BcKr(6D7IRTZ{~N!9*2c%aOXa~08*f;+5e+M|3Q>m;Xbrddm>r0dJfOJS*4Qn z6q-r1(G4R#CeHzkW)JzT$pqT0b1mVXID|TH=dDl(9JEEn=Tg-62l_%MuG|Fz4KJ(c z?yIK7XaAUEIor5<3J<X7U!0v71%5Z!cM>+86)x|R=xp`q3gW{NVA5Vu8A3|6`txyn zE2z|aRgv=tViz1RCTwdZjZ(8q|1f-R;ot8LAfg#h&U!mJGfZYJ_3n7?&!=^Mb@geV zU_SD&?{cuAF=%{bc2W|<Ks7e=molZe>|CQ<8LUrf2Yr&lb&~VMeGmYh+Sy7eQUlyn z=<^z%KhxJNI;8;Nse(midG(*;M%q}*wTz2<jKj(IhT-!V-nGh=1o}%!Ai7(N@K*b2 z7a@GRBx~F&%Lu@#FD=*X%Y(1Ax3)NjZYdNHH(;XU$D|aMBFxI+`~spI43lIaWAUl5 zY~ZV&+98!+-}0!X;bRZNcGFVgY59^0gO5`S+0xJ1$Fr>DV&pol!*j^I7~+6Tk_fWQ zkghrlVh*f9QtyZI2SAORQvZkARkmiYIExsl5C|dm<3EZNz6CM=a2929gPpwiYSF(k zeq&d6yOpvzXZ}0x%x>E*4O?Y3eAE8?xVSQ0{YEo=0?o{PKeKxo{(5#FW5W5h%7{DH z6URP1Q(9ip6b^&c)YYhy*LlivftC?8=0n$qvl)ZPfsROlU**Mob*U%`n_;ih5~WaX zmv>W=U-h8XXrmK%deWPBgSe$StFO?)Uk`pjln45*6sS1=meQ2-h)^xg{Dl?6!k#J1 zQka1=fGIx1;WE1w{qqB<nPZ_qBkx_@zW&Y_850zg>yD*O6C))TU~bg;YDB>w0aU9o z40D>AMrBHt9z*HShUPr)i?tMo>)_*w((l}4<LtjDxk+xp^O~sF9fBx@{|WVZE5*8> zrcAN<eSzE7J)l~f99G$z5OO_K9bt^jdChjpKk(Di(9qCC=sNwBG7{k}yD=($*C>_M zjWb+#4YI*v;I*c?EYVWArBZ)Q5#IM0jaIt~0%XmTQWM{6(3~9q-hO)nPz>SVxH+;a zwtPdXi_8h{)lqJn^|ocZv`M?Y{n~?i&1+k4#Gh#ca1p^t-`Z5_L`5PQI1XjJ>atH2 z*Ta*RE_87oe{>G>nfe*@pvQ277fa&YAWU?F$ab;Lb&~!sTf<6-IqJ-`Tr<-|F-Lk^ zL)PeyHAjj-ne7@0atbw+@>EXZ%#}+R-gp?BaWs!1XJrI6tE0iz_ag3m^)*UD@s$nm z!5mFMbSa+t&))1C;^!lOXO}}lj*%T)Kw)3{r@_|Z^=_r*@<-R_b;}C-WpY#T#$j+; zK#&&-Bod15BBm4N2MUhsD0H&o)wf%eZ#DN*=xX4&E_}nr{hsGJwFM14o&PgQpXWY} zZ|!Rtcc+R8WMEVwYSW=9*{3!`lP-ka_>QyA>eNG%(>Zy=ScD+-8$?feC>pO<K_A5Y znqOq@Z`=1%QQEW`{{jQ;?H~RKnPk6Wvn7k;8p%Z^@aB`0f`Hxi!^&m?%U|AlqfH}a z6pD8={0I<}!7gswjV+psx6?|=Ez7?fvXF&(kOb)o3frlCrKl)%yb^qO8XYf7Ui|B{ zNQaBI_dO$O)gwIyE}-`9M!X9VBrn~DL8%WCYm#ftuVX@s?8F<tbO&2eoT5N0VfGg8 zsB1T^Oyjuaw4Gr0t*~TO-ucBIwQ=1S{CR~}Kd2DEumHly3?V~_5uwia(;MuQ3mQ07 z!HB?gY)DIrrq?9H;~0;ES7w5<DvOCpZLply#}Y2xROtK7M0$lVFCJD%{d7Cx&D`?* zcpUe4{bu?6&^@+R2Qk-O@Nd%>Q5?m%oj_Dl{Z#tcSA(zU6l(UTb!t$=FWxxX%cW#B z9G%pbfpvimjj*?ogzT|UgL=2o>(Ont(X~?L{bGN4CdpvBAj00%JXapeWXUbr(vHF{ zPm8N+y5|+aA94j+7F~~AflGhVUXq$+=I<ZAM>Nv($%<JAckkG&f+dIMe?a|e&lE59 zJa+xk3{Ci{Gg7{GT$Z6S%b8eEz@Vm>SotzsqjlmOE9*lg=iufy^@AFmK_SLRA!Tq_ zeHXkUYpn7up&Fo{)zNuQqC|aC9nNq2r`{g)VMaY2D4wLP`#w?x9!N$V%$4B1zjQ-n zWp-kp8>}I(DI+;e?)3g-AETnCRwO^l{K5mMHdeMkq6n6gj;aaXrysM>XF0Y7B2dr< zZMrHajNA>)7cE6nt%bSH@IIBS;I0IJrv~E2{TH^Jh08*}xhR^L1?sed3CjxdVUELb zJ+^CK72rrhCrKduE5jKh(fIk6Cu!O1!RS~$A;EN|$S60%t@=3i6T`!UJ`7JNefhB9 z<u19}<vjV5$7tw*wq+%X&ZrMNCp_2u!4o=0#1+MkKYwkD!~x6w4wKhO<JZPJ9DTfh z$el14(899JQ<Fo3`iJu!)v@%bD@c`adPtbnVYrmeIHxH*!6G>64dh1X&kzPa5u(5w zShsr2Q3HdrUvb#D08T}7Fsjw_U!1Cz_XVfw;Uj}%O;a!nehESg@QhEc=bk<3VxGO( zMC&}Y6nnQ;p`VZVE^!e(9WnY1IaG`$^yjp{9Pufe8mUDD<8H=?FnD<TyeC4GJYw}M zx63L!&Z;9Cu1%iP)CoG;cPgmVlH0rH%3*ZN6>6v;-E8QS>BD0j#btH&3zPXPI;ju^ z0vdQUu}%fmjwa;&bT>qxfLb1ItdF;)Qgm)GjYHiae>jO>^4m}<-KRHiMC88k@(di1 z0(_8I3n0e%h?N}UvAMaqCvgp+GU|3Ad#dzr`80P}(N_q%+t3q)?<9%at1?#<T2NGa z^$=!}pJY`d(#R5}c4~Ybay~cK6=KHN{0RBqEC55R;ygzRHC7~VXt)B-YuM!HEIHsp z*#Q-0riPk;$~*1r6^4D<lL9D_w4tr0Qj}YmQPL1<lIRUfU#9Ik%BIG&+b5Lm-ab|O zz%L%BA8sdqd*w2h?CRgs*XBQsMS2BjjMNnE3u2{Q+6Xp#-aAEkkrvb^{xGD^mu^-_ zQYHhaspvQc_5RE0FTb7M|G5+4y3n&c!)_*)Jo>>g_hFJ_{%T6ACFXj%37x`?=Wn+> zaKDzJ`#Y<i)x62GRPe@vIV@NNW-xr@c%l4Ly7wxa>+0?H-|iSny1CL9c-pGYh-#s0 zyZBMbwG;zws!S=F=R51!Q?)q@9+AzUsD#0wG;d8Cj=ed9Wj0(WgOrFfMH7y=W4?lk z{YO}X@O^htWZ?bJlV+D-eNs8CP!v#vn@#mi>@wxeqs|BIR;7R>f34MN%+Gvw&NV`~ zIyms(El+Jm&prxfKDlon@@_Gusrr-6C24f7o?-+*$TA|p!X?{*IBHdB_ie$t=&aC# znT18&BXEy&0A(6;7_UjP@lLcG2<Sz_!kO_RsKOT-xwghzxT&Fh;#lvi;jj`|!h@vE z&zEb!Ij!~~&q*d;tner0Ry-r!MZAJRb(aF2rVe%?1)mQI8Gj|0kWkC(_E(f3#A8w- zFR_~WquHDW>8s0FQnNfUCG`TibO>_%jj_n?*{{-PY<n4XzH9-nmW@^yTM}*+z(2yK zKA5=FoL{GV=ezqQU*F7EXY+jA!;;MmV@{}+Vr%?J7U*Xg*h(w05`Ff_-oz@2cbbHJ z^ie=*stPF_n4s8hHznu)oaX;wS+!jNdUe`B4yz3lDYasIDP$lp0zB)wlGu1Z_U-mg zFRsc-1P)~>69Gc87&UImRx7fBCe`B;hPXB7-_EvK3iAJL-WshPpeBD7cCC={6CIoO zb-K`_GO4rYSj9sj?oi4MA78gFG@v^jW10%_5lMX^1{3+tuFh!{B?+YdOGjyrL~>z? zk}T=^c$tOY>FCvNqpY+^eTt>E^~9N!gPFE*l-uS+{s7PgyV8I2CW*%xtc|M(e;b## zLDR<uZ8r8>zd#EY_#We=@@=Bz2#`Je+GhODoON8k&s+y6;Nt@FV5bcDp`QhEo^~=W zDJ9*5E#;_|ZqPgPh`vB`B6f1H)I%f-QbUY+Xb7_m4<tl1u%g`}1l}d}L6t!Bsffgd zUetMz>==<JhC<NzN7V=^SrNiM(<ZfruLk9TaK`!hsqsd^pz4rvyWO)?+l(5^`9QBr z`s(G!BWJ)t*TMh<3&~K}54uMF*dWC4N#VAH0%BpUbv9A~7m*O>xsO0JDpr$9Iy8!! zd}px|eGDZi{2q%=$RR}s>BAGi){wuHiJ~eAlL8GMeSF<y`Qu5O%MKIr6&R=a=Vx|Q zNS#_UxKa;Rw`RyHqa*#tUf2AErG@Zq8T8&Z+oZh)%g@b>ucgzGE@>B4y(gFiKFSki z@xVdH->u3e9F%P{6PwbIr12t14<T8u^(_jId`Bt@o3`zlF&KkU9fh2_=8)JZ20$n2 zw$mCcBf=|X!3c9lt(>|U4MQipp?@&(tiYsmaLdRqU9MOoKpk-|vk%#Dn5e=e$Duv| z`w`OnAd}rJuANtfmw9?r3I(uzlYYOtJFn=xT!j$f-dn<8A-atDl;(SXA+&nvC&`)4 z5~lYR)&gcdLxf){puuqluFu5l7+SZU`ol}Ltpk2{ZrRd;)QKxLdX|6Lef0@-@{|64 z4@1FUw13BxE9haF)O*8y7JD3$-4WoBX!A0s0*`mCUyDB!Dl*3BNhMX~#mZQFj3@T= zzhiyEaH+Lu8Dt#BdAi#2<9PxLdbi|ulj=@U0?f>dTrXE-agXy~X60Y77M2_Uf4s{0 zq=yU7L>|Ki)Z}i|)6Akrs*h0rjxdcP(@4>b7wUB#G-8dSR1r{RdfGllq}CoMtS~<; z2$d2miuw81D2yCDmkS*rxqT5YKr2yUIKh4_tx*x{=^$~Y&==j#f%F;KbTv6V$w_xy zo)%G7xc4~CHXkx{N-Vyf?(E5Q3Y5X4M}_L}1(RxNXh1fNHrdUu7&Xf<tg5$%$7+Wm z*7z)O0eC5*doe?Ho`0vb%g_~0{)l}XbSY(Nx`6)Dtdu|_=FPl}9(}<svmu-`;sVVb zmQg{Iq>uJsC+~~hr=6Y=&tqbkA@?eQ>Lh08UFd8y_wXhk4UIdi1RAe8d&cy$P2XEx zngUs?+Ar`+kXv91A$bQiS@+!XRy08_q|P{wI3{yc+7n`95&MjfkTLe>V|u(6O!p74 z&#jiL-^Y!TS}kO5a2<vQkm7Mfp+GQQ_DgE|<qEd>TyxnRL-5`ZSPb$Ox?k+DIi@_q zs7w)Ydjx;SzK=EBs0`z4y4cJ8yu~%G?O%;s+_2duW$2(txWb)Unv~&rg?DzvZ`~2@ zLJ~WXduG{*?Y-BRZ@AQ0w<nSAi5z(-b&4vyKcWdQZ8GN%9pY6!T=4&n<j``cKa5~V zd18~O1atUy=m1-%a^DaABF)9sc~zxt!m<jSCLLbYXFx4%y9yFoESjJaHt3vCW55v0 zg!$p25NP-H4IZVE=ZMCL1I)33AhvtFXan)!7PA1x(P5w44BA|-R;EZpFq60vQJegh z?X7+&_6_6waPv60mi5Epb%&4%LRFH>MPw((`RU_CP{q%@HV52x#ndms^7|j=|K$qx z1SnUQ#{|xr4wn}nbUM>EDzFAMTqMSC<@!yQwmH&t1jnN^Xv6z*$Dzv9f=Gqy1Y-O1 z_+qWtqYpe;beTP)t(u=`-o8-#1*_G=g#VF?+ni#)Q2Se{PC?@+F$WG!I@423^Vh0< z-7MAp!;h2WbKgUpX;JD$-mH2{3Q-D|o`Ark`5l|zNBC?>o4;Mn7&e5q?X%b3KKdp_ zXYh!PpwL>nB+&X-Ra$+<oj;5nMIy_}a*g-s3>=b<h`|oJlHFWNvumtZR!9?|M-2}* z5z8>_aCC%0B&63e9tQ2A!0k4=p64Li+Da3DGl2S9c}ssQ3tEuQIVIHY`M#_~-uKZd zrW0F-wdLt<zryM8v#PLNg%!X`0QzV!MhN6xhm48B8@qMqb&HgNS>fr~j31kkQ{TJa zrSgJjlo07iiwFoNRL|iKZ4owlUPFEcv!0&`Q>DdOI2@8nYgH{_dPFteKb((n9i0On zXmsc4KYRMePMuO-7h{rK@f?e(=$&jn>O*w|<>BT<=7#&#B^6+qO+}$w@vFn-{7y0? z5iEa|oFzn_S)h$vjmyEM?Un7<TVm_(#JqjSBCTb2>okkmvp5xX^?h+G810sk6z3#p z7*AGXo&B~9SYMEXn)wtZ6O|R!e>Bu(+fhlRg;Mx_cEoUEi-m1=o+?47S_JI1^R@2z z>3%d%xl_@pw4p;WeZQ^-=~_TCD&QyBW@8@yiUe}f%DMQOpRBEbAFN7MOz7)XOTk5| zFt%%BW@E2S(x@|f$d8AmggSC=aI)9xKFqvT=(*@LZ&^P|F1%c)b}gdq20K@?abe(a zFbUSLZcSZv7ue1V#biBNlDKF<qQo~rVs3E{bgn~v0GqG5KIq*xr)`&OF!cYkynGB5 zxe+`JF;SF>3AhTVqWS51@x#R1FnL+$$;vc11ri?3-WkpubJRKbao*c}=0<X*A@|-$ zt-Njp{7h*nNIU?b?}G7?BXd^&dK&%(@$f|e5YNE>t&KqNDaxh2bB4<M$A>=LTJuM{ zhQBL~G(sQNOGIBC(h{d7Y=2i4bLm%{>FFmv(oFQ3GKOb;4+EF!m|7C=Ybe&jZ37h+ z=dY{K2RZ0l2SNOUt@zZa8hE_PCDD&yD3=3g$}#)C&m{$*8t`#69~+&se8NgLYisnV zWevPni{GB_;YMf-%F8kf2haT$#M^1dCaAJj7-Uo9k~Zo7{;g46;RL8seJ>Ti^ul19 zjQs>I?lhoV#%tLoR{;d`ppNNn8%l4s<iU`S^`m)bNwwomT$89_iw`d$!WBjX0peiQ z?AJWj*sD(5TwKMW*WpCMvB1Alk1K*8@j2Di)z$a<LO9pY>`q?|<W(eVX=$ZQja+%0 zv?0joAJ0~*PtFqT0#RKmIU^+j^^Qmf)rN%HeiLef(~;!gv|LK0;BDAZ#lBK?p^=tC zzlgv+J|K?_Gy}!bCAVUX+Ba?JUS!4Uzq_w95e=_)JxOM+SGC>e4>Nea1zDt%+C~~N zc42;>bJ_?M+>|*VVfS1SOfWd`zB^_SBpYSk{gx7{=#qm6D(b6c{n^;4eHhE_2+$0B z`UuS(@Lt=Vt`N(fwXOMM-S$!hW6zp4r3JfR8c)cJ&ef(i^DU!XTDSkqb8R=!LqoD8 zbJQE`@rR*JK<dk#Ay064&La~+pPizdC#$YJoO@jGM^B9WgbZ<K7KmZD2HCd>W(6+2 z;}67Og>^$gjpv&DFKkpE48TV9AI}5O_zIa1<fBB6-?^v=J=mpKEP5&teqJ4p1#!}< zY@Lcnq<^iTWggD2Ka;p~C4>>wfH4g7c#qIPnu7{ax-$X-%J+aow$%DHDG|ygngs!x z3nyQnquBWMN#$~bVovaHs6eM*I)8&Uu1+}y{Wu?ciD5GRN8~&?wd;=IaFzko)iq|W zA@ADc$H^{!JN8J!x6(VyPggZ(fbY{!d?8;PTbdR+`ckn(pS25)NTCrEh<|c9{VeEa zzSc>*E)?V8byT<gcZe^fAy+n`AIRe=Ju1mpNZRW)M!7ScY1=VzHlw%`Z9Hw-*S9b! z$XcaNqTd1ntds^yi;F*1B$ZAS`sGpF)$f;1bfGf>>06630xuO#JvTrow-V}QtC2IB z1wif9K1gW(*~2YJ-!62^C@q}y7ggeE!TOcau$-7z<1dgF+|uYcpmsZ*@vN;Ap{`C2 zcKBSh@JjgsK~eBK_hNx<4hyX`LDjLGHR19%uz%h?Gu;-WU-jX!sgRRXs>J~j&0^uO z@h@X$sZ5>Gyn?9wCb?uzX{2q{Vzt5sOJ)-?^25ot{Mq6j3zT(nZHI;Tp7_5q62?(x zz|~{i|Hxjb-@!hm4N-dAyNV^}RMEs2I)Tv$<bNjfXPEEG#{^0N=whs_q+OeM(CnQz zCWedN98A(Ds3qv)L+e}-D`*%8fG0+9_}qv8^O^vBgvJX#;V&IRkm?2t@ZYlBR<ORO zx44A@x-=w<N^;$jB5P4Tc|_U*ZO?3c;x&I4xV~x>WTcyFfG&0;QIqYy>BAUe)7;(W zKrx#a4+!){DVr_WgLi7HC3Y=K6X<45*{vm_wYiJm_HXYhtMGnMrl2Dfj%9oQCm7}- zj>T4QJ@$Yw(FL<quyAaA{3mD=xZN;XH=YL4TWJc$jiL+PWOg%B6Lj$wF0}Ioj$Cem zo_Jl)avC46=FeV~mS<gfUuf$TdA1;@brY7=^wOgCMQLyvg(WTv58*J?9nxyuaizb0 zsU-9{9LmE+pSstLslHom^4`~4R#xSlz#mB{Z6KIPV5ZZNTUM@}*UjqRE-tm(o078Y z45!;Wc>TJ>;D@~>?1V>((zllC3M>l;yr<Kij9!irfo$7={S`X_gguLTTknJN<AFcH zbZOA!HTEyTBblD*K1b8KQ~4yeIF*l2%A_4Z?hBr0=`F(P7Ae>V+MOcm^HpghLiciq zelZt7DaxK(T6ucir%SnGRG@kFv3Vb@?eu#0N$vJyeiK49Yhw8qzoQ8>&lG0SOSRm8 zd^OovFwJ<6VCrnwqdpU`H2b;rVp8nbSN8f68tFYuj^zHG>W#vikF4BH9%22Wl@cE? zYe_*D+8Co3-wfF@v&&{e$XSbCz8xy4L0sMcc`1BPgMe>`n|?QmJ8S2mo=e2kpZ;Uv z2QhA|CGtOH+3w$uYB0BPtTfzN2|7ba(-^mjg?hJdT+(Zi9-}p+6UFUfmBPsIrTJDH zjj3aaFaK^Ak5SIaW0BDBfDS^)GAA;3n1@H=iGDYWmDc}~dt;|e#l?roX0#hKGnp4z zzt%pvf9NZ9QC#}vDAc*_^d~Tlq@bfOS<=J;E=qm*45hVVV3!q`I-t-Y7x?Dl)O4N> zYUnmrM+PPblg8y{c-|!mod2TyT1cO(<y6lGJer;WUbpF)qeU!pz7=9S`{mchTpkhh zq}{Pg0Q$;|tM{N=uXsg2MNvCRJvPLBI9n-M0Nv`Y9?TH_VpY2;QFmB9k6G-RZfKZO zTpwrKdrM-#ukCpktea72$(#_%prF2L-B_+RxeRI6`kZ1Uaf{;m3K>7$<IFak`~Z*j zW_x%aQ%8^G;(mXHu+ilr+UA~d?u!>_w{g4XRBzpbbb@<VH)H)J`lheia{e##ObZl> z<yhIG-}Qzn!+0n;*Qvjp5dHDmz?E6jD@fusyv6Hz{gi*rRjn(CCnN+*9ai<8jV9oZ zm%<9>VIq8vcXARn&ONf8$|0N|`g6H*@mtsy#0J~6z7__yo?zCZgDwO<iJ+6R4wxcp z!@gK6L77@{|3yvYATOv%ICp+ipJL(t;jr$kx})~eqe+a#tPZ)nUe3LUNKY5ddpJ~B zLi$zRqg2M8XUJ~}F_E6F8BK;Cqp#`6C6W2q8RaldtG{}uCdr*_t_45qOg?~+d<DVb zA;*XDDhmuOsf@)|8WifoOwo-O;vr<6C3tT4%48`=HKx*^O`;BpLbgJL@Z{@wX}sDa z{zM0S6VRw%%Y!ELYeMsYl7gs)<7O=#yH7~+T{%7D*pyFqYgKpSlWX?_xHm!^Q)hs@ z`=0<RF6&>>dwXW8kcl{eLK65{WZ3O;ZdKq(@N~;@DbXUM@z6RXtf(AqW>POp10%1x z-$UyC%hod~h~9i_b%S0N2a-eJ?>1SawFwNZ`1W?OoSyEeZZNm}RF~VWr~NWt-o^5Q z^8^qCB;Zh>Jk{7N;dL7NrT2<t{!bI6DZ}e7$gYRPx%Y3O@BQi12c3&zn=d-T`gE(u zt2W)+KY6_HamJf@pHfwXfqXn>wVTId@83uB1oyM^aDv3FPuEN}3x04&35Cd3UNu1f zm{9z^@LM^`J?tS03yk+TZoU(<-=2qSTU9kr{}&y&X+}9t58GPqDq~OH)+IRX+c_Cq zrX(+MZMe@7%2FbX3ri0?c^}Od8Rxh%PJWUGeQh_(x$T$IGCBEvTuHc|MTev0+j~7R zN&p%N0DJ8>{@2Ne2L_ycj`;Ry%?J<bG!ZnP`8NlCUk`M%)C~9*eHxz&nZ0d>VBpwv zgnM&di;&B~ewHQ?;KjmQxI@|F_58WW@#<-3Zc|uNj5d{tg%*!VZI{+5T)oK03F1KP zBLz#K!Oo{>)npsO2$ZG9utOnmeu!oi7p9)QU&>z9+-}fL1PzvQXRNQ0K7vThuV$2( zq_L9OvX%i?DODC9otWPqVEb03m@9_drjoKe3ruwH=eZPq#&_xJL44Q!InNFNu%zGQ z=jQft+3*k5j;O>dd!}Yu%`OH<zO6jr#ngfh%4P3gG`U~z@s@japKS%u9oznlGhlZ; zGYsDzfCD-F)>czPo9>q`Cx3%7F`u7tf3_^?P2x`G)Ge@jPePJ57x*IY)OFka(AiPi z4I5~v+2r=8sC5^dS_&Uj5f{CNW!~32;^Dn`&E|%~B()5eaEDB2IW()-KJ#(&!}LKF zD%MCCfdWx(sEb!ZAvB*Nqm#qZ5Bcr*#DtE;S*5xWA2U1bL-WmPQ?II)<G;_`6m)ZR zajf{ne8w-m>)UVg+XF3T^s8f4%Wp#m;~GwCIT!*R>LI$^@t7!~Jll%yFCL&HCLywO zStj_4F3BY`PEcSZ0G@XA^xOUS#;k{!`WlvdcVC3EPkefggjbELs{cNvx^H{_s`vDc zdv`}hWKg5l4DRs<9hf$w;j-5_R<}!<SX&K2rIf)YSdmAXbLCqs-kZcs)_EL5k{^SF zN>JT&ChJ7&A5JhK3X5T0dhPUJYz;U*O}q1ouCtO4967WsAI7nKU!9lZ3I*%vzyu-x z#Ra+gosZcdDh|7PNQrr0POASC)CeS9XWIrzYcl~(DFZQ%YHJ_|3ro#Q-})D()3%@B zeNiW~L^6o;cgQ5e@5Y~{`2^QV*a@Axh56~q73qegea2Sc<>;xyo?Zia*ktT1l}hE? zM&OeDl7iHIV{|VdI@G5MT?LpA8Se4RtDhf2V`CL$+MT(}RHu2Xt#g?81%;(;=Y~hT zt~kQE<R~1*bEHKDc6vCQS?4>4u6r`hlP!hSQ>#Wrg)SPxE^Mx=j_-^}Hej{{N%c^4 zFW_XUysAQzv$<YG?hapJ4SY!X?8GNH*x3U@B>^t{=c}TL!a$lWf6LWVf>0Nfsr>Rp ziGvOUHr#tXOYRq}ZbcaYO0os)uKP_CCC+)UofSFV;T#eJuC@m|?jhNAJe`{i1d9Mj zq7yQkP+zR}j4!eR#u^0-*I%i<@w9eg*jP=bI+222Ky$0!l8*m={<XDVJV}Ah=u#!z zAYUPkd5E<YumX4kmxooahGL@5&!mVu%`Q<R4n4q+M}(6spv))0jbt+I=HWK=!zv1h z>VgT?#^9S~PNrmtAB-mWJ_{uXIR={0j^{juhy+OA8I5I|oyXWVf2?}U%cCaGl5y@o zZoV&trlpd-1da*-nf5Yz(b`mOBum}=A1Co>Gk2}h@!3j`UREzdhQ1xTPkFr^=)z&O zJ&OH6kgcOw$c-1a{$x11^v7hqNcs$d>)Xfc<pcmASLC*wgU~3Xj=qz+vfEZuRiz3K z4}Uk-5BGXn*S+Ldttr)-$TC?MRTo+QCo1x#==G|J^RBvz;TL@S2Qrc!;C00fqf$uN zfQW`5^qJMO<j-%PY|P(QkzWhh;Sze_EOznU=2+UCN~-fvw|@0jS$Vl(v{Y6hcfZOf z@|^2bRoBV=vAsEvP^{&-HY`7Y&w4YwMeIf#dCo_@4!R4SLe(EREgSlG30rv}TUjoA zvQDGPt_vQ)9!uB0GUSL%_dzSpdGyR^k*>&LtCLGs?$70*JAUAtks)wK^k}%rWK4{w zLmBH>Zhg3peVih6?yw*sTz|n9x)DZ`t^bQHm`J{03zXs&C4Y()ufV?()Th1a=daXc z+!ZNe#~>MX6OT?j1zT1E8wFEBGCpy{1k5l)cLg%Rll#L0mK%|e$B`SP`^E!AC2cjn zI-`5AcncQ9S11_okd5Pd`-`{x+IK%e_?bRJqtr8;HM`%!3^51NtP8Y5s&<+aj?$#f zKHAEvH#scd1AY_)w(sgl>%GvGv-w%ptJ@2yU$6m^7uqdi%ujIT?|>*Vi^Y1?xDkkN zh&)gPStR>>g8r<eV`Kd7n*>qMlV^;OopP+9rM!H2z0`KC(xu1FXcldVtbT%3=`M2` zQQsG4icHve>>>anV7K35tOL6?J3iKIoDJZ~e6)?u5my#LbblSeeVeK{qXxV&vZ+V4 zFZBwl5ghS1&Mo&ks_!Shy;Pat1amaotct0A!)~$m8`DE|TK?iJ*^=RJM3`+_)scP# zPQwtkIq~U!oiFe4bh}-D<Ac4?87s35MEHMP_rW%=H@e17(?(orxi|J(^@tou+N*GX z#3Z}wAjp1FoQ?aRUCsvM<X9bCcg_<An3^EvgUd<+ZmJhbhE*-ceLF86L-gLAs|{FA z<Hkpz<hwoIm=z=dGc?MH``V2)fN=<;_Toj!x!cqF`Tx9yFYz0-5--oYW1uf_E5d44 z)l<f+@(6pjx#f)thg8pvp<J#K9IDYb7DSUyAEy8cn!!^6r_{O_4cqtWyBuyja^m(C z)B<0zg?iGS65huT5gAi{tfJ-SX@m<oK;V1lpiE&?C{WD%#j4J!S)1#(${9Na6osYw z$@NEWHghf4*dnq*M{Z=jy)foBkpvtSl6W@{^_Uw_3Db$80RPgNH>&c-&3WM|X_fd7 zSnm*N#|3Re3f`VKE{sgEcG<WRTY(ko;RXDtTxw26M!5=L9p^>IJ-3u{$Mdr@mCgMW zR_NKHbI=zsbgC58eVl8)4_q2WRHwtP<*-sWaq;j5USz&|YFOd9xw&_vfRK_?IAgVP zP|<nCwJt{FMwfh=NsIa=$(>QVOo_d6eB=&jq2`;9wp`<1Uup9{19Vw^Ee``NCIb2j zWgn3q0a?G9jsv>9`<gy{(OzKzX+O!u>|Sc{H6PpXck~n<>v+fgsa-!%x0{ufm70gF zOqmq?Q8jRZ`34j?DWnxS?LAl9rn(&c?!Zq!<i{8`-^RoBZR_4{pEAk)M}g03i@CBa z(Q;k`yau;CJij8Y3@g<|f+oird(4+Yp<^e6lTq6A^3istp>J20k^F;N5^H-Ef2A+0 zIw&!-480g<%x6k!3{YN-Gc!J#_Wu<m;F|*%ert=)9jDszCfRCLaE$u2Cp|y<l$5o$ z_!?G$hkWY>%DV8i2n?$5q6{y>4<<VkHxdSOUSq4Zo^8nMZH-!MnIEZ#t|dl@u*qR= z#EC`^@5L#UP!&>A$%{+1Xs30BWC6pB43>+FC7tvno1BRI-^XZdYZb9inzumRW+7HG zy*9nZ?s!6ZL}37R-R63NtzX254RK+soOt8cQSD|aXO+~Q>P5@2LXYax-7l@TO=mIT zjBz>E3mDk`gOb<NFQw$0FZnkfkC)Rj$;vni)AM1G@?taCh0ThcV{wNzC%ehKEB;sO zEs`W@Bz8A8xkXyLQ+Gg{gz|Vic1mY_(Y6s86u9?WX4c0cgEWNBUoh%QhinBSMx2SE zAto7dT)YGmiyhUPU+tB^SKBJ0n5GGV(|jhOGx*Zk$)UgWw{stmU${H~obW8G_QJ+o zKqXNDIogF!4#oF+SSS5|RJ~O|oNLlG8XST{65JhvyF(zjdvK?5cbDMWxI?hu?iyT! zyG!Hl&T01S`Okb;aMf4!K2^19Nom^JzzCs>2_`dH2OE7k{kNJ_TGwcLNdR!NH}SM# zZ!|S8{|a2n1>tVAR<J#DF|(pU_GQ<p>lD%P*8g^5EG&o)&lm7|FE8O~!sdIG6&1Pb zed-nLTeRG|oljCMx-HgNnBW(AO7wSk>vqbb^%MNc4>yGhYF4b3WJu4_X*6sG@Ersd zi)O^^e;R-viJjbg5g>73j3SpScoXuJKwqDR=S?NYeuQzt2sP{V?e|ugND;CpPfw?# z>e(+gO6rrm)<})s`AE@g1M|Rju$^^PU?<Zt<kGO0R}hk35GLV=cPa3jXyS(?SRuC6 z=0uT`OrJ#u!+j7yF4`d`9hZ1Qc=U0fNiD-F4<n8I`h6g?kOt~FG0XOyfU_Qo#4GhF zswI9h4at-QTwPwtfde~ZEjXdmd8?)`kj4Ck#5fDTi&Ld}tjRfbt&r!jYQ}DA7;iLa zv}Xk68zze~ecn_#2ksP<B-84)B;PU&r6#{@o>DRX&_z;LKNX5cNh8%s$>CG=lo|qu zA*`v776S^yQ^{?Fh5bvi)XOw-t22Mts;SMHlao=2al1U-orU0f1bS7yLM=9Urvv{y zuhrm}EGDlvX&Z@*r}E-iMB?knpnW)xLMbmVmzom{<N~Lx7ymUWYhBCImFaINmA;#8 zT&48A(tC~g>gNeyFyy-8m|!q$?r}9NuC85>=%0Gbu7=hd?plGWd=hl8t^n9Z-pO9N zkEO9Iavt|4u5<dpRW|S9>S%PiolF<XD$gH1KlWNN&S}~fRe$2evxdKS9Rye=*AKi8 z4gO(4esA;x+d?d9o%Kzg0jcDyu9ZQz&`$T*W6P^_HwZr!z`iYxQpMN(MKa_XR91R# zAE2*)p@ChuJAEz=gJAl2Y~UkV-gP@BP=;gwFqQw6+UbLLzxWP(Me+XY+iOXFg#B<_ z_nQ}a<Fid8DWN+_3`4<zIC1FBa8wFpNinz>max5h^6K46xBi;#$GAv_S>KlD*YE-_ zN65iAZ;|Bbv*?C-^#}nPh$!rN;=f85+xbjXFwobZ9ePx+3W)DUq3amuiM%VtmgC|r zHtA(FE(lkJSZ|rOAE(M$D~r3^!F(jp`+nC)K_xFSQ4uJu5BwDwBBR7AAOJze1rMo7 zb$H|aWNC}1LVDCi(@vye+>}Trc((p=Zj6OmYY)L?tjoh4<WPG;Pfve&yL>iS>HA{Y z=l@c)l>+??#s)~iF$V=DF~w{*8o|uR>r*+}uAys(VlwLub1vK(_6i6#z}Iw6-(m{` zIc*HvEf4SmRC<_dVF(=5y}_racIs|8Uo0uHSZqT$O+z0~x0#ic0w+=jK+DBn);RoV zxwdF_zM;iW4jBBZ;(R%Af~sm4tSr~1<o*(&_xx?uKAsP3G-%k$wdn%(ks5w`aD7Y0 zI6M}2nzo;e0Vr8{rHH17TH8EC?1oZojf(J{V~^|{ZN#~B@eY=w24!FF70sjb{R#Bc z9yfG`OWyO^Fy<TxH)^oo7Nf|*Sx;YscZ(@8>-Q)evF?Y+=CsY)+1nGxB1b#=L&=f) zDm!qW!E1twf(Q|fIxzGf&>6{p(*MKy&};kg5TIorrS*W4<RomrHg2-5GRR52k*y!= zcj;-;49kJsAT|L(gIb0w^eJ^&Y@Z3vZopF;nI#M^<R0of@Jy7X2K-+TA-Ie9pFY&x z8d7?twUp@hdh#f5f{Fkrn>d4kDE|o9*!$G!70gRH8b&jOoN@gf)FpF`7l!I9a6LKJ zib}RU@g2z_WO!ye9!~NjOEso~TVPOh520|;#~RWak44gE0|a;>v<XQF`H~lkkiPD! zkRzOy=jmk!8l`7r&8AZ(?(gcyRB~Ag6i1alH>K1F(ja;SMGQTawkZmx&uLDRFMEQo zj#081sY8-%Awu_<5|cu9G2(=NwU0R{q0DF#xK#2whP<sG9YWG^_=%9fyw35Pw4b7! zx^@#)yd$%`4D2Y?o!iXJ3+1N`zPGi$3Lu)qiAeKNxAFI|fUkNT+Cv70`(XkxMHpwf zVok0QrF>RI8D(YV<?)%ku0!dT>j}t2HXow(=q@CFHB{Nregcj7?Kx0b9+-0Td*4^5 zFJ?VH!m6tJKJ+CMPp|iD*f>?^PiC~O@TrniijHomHm?05Hx&~TOXzi?cgg-rw<PHF zD?4-FEgZN8W&_5uS5_*HVuz$j>2R)vf1EgtcX!+ydb{s@3jr3<bg^+jLy4ym2gAx) zlO}afZu=BQQMfJfaFAEk1De17S>I&#I+a4H@1h72Nagif7bF;VSTe^J_hrrK4?B2# z^l)x``OAB@A1BIIosXMEYvw_!&u7~YS%m0TlADK&fQwF;XU88{@bu);b`9Tj_kE#+ z!8^SS=HI@D0`Yd2CJx?d-8fj%5TOoc$;-lb-4i1F*TDh_KJ!oo932MmP7C|M^=%`% z__KmgkvIb2HLG^rXdB>HPPYyDKKS%8X;S^Kpb5ME--6=>r!I>?ltVj#6c2=v_Sc$> z?eIe(WFN&OQTRk))g<vSRQ^z$9VvtyU;_nKv<3JHF3Fx(VpF~9%?8n0L7|<mpxe%H zkO^M#+?=i-dC;j=p=r@zIdM*>GsuL6n^Odc4y74J@07r2%81w#J<&f&?}RIh7e(d6 za#Gh&ZHuka;^gy`*~m`GHk+aXsq8Pg6+ycRHrdEjAQJ{V8O99X5j_z~)F?&QeZi6M zo?^Y0dM4HR^9PRd<gw~Xb5%dkSmo*4H`K$^-F(6tvSivb&$wMF=kHb6(dBuZ1h3?= zn6W9LncHz)a80a^PGlGGSV#DL`O_}-+H)}tge83Bm2bDs?z?;AgB3chDg-j~vx49| zXu>_+*RPQn^*)d^;dw~~1>c7b0QC~5!4`$>?NAq~bU9yMzinID`gLb;x!gGgF8qbS z;lm<-WBF0NW!mg;k;r21AYf=|R70yd!6YB>XklpynwA$xthcS0#h!eR2LS;2ve_*Q z!R>$c;BTt8QJJYtmiFEbE$3~VdB?aw6e2n}2bA=kT@8A-ScXBcwzf{oc0Uh7QDHo1 zNLQnLTYh`hQ~jPDsL|(hoFXsoKVdHZfW&a_i~2=mLV)Y>x=V<6|66*CeZKd`+kU_J z3%hkpLyn8zHFO5Kff-?P47Yp#7{N=Tv}1Nho86MaULgP3m6$a&f&tRY4!fi&a7=|4 zb3|V7MP;??io4_ti@uu|9#N1>EfV5yAMqx$;dtE&D|<I*e>C{^60Nr&CuTxcuJ~`w zPP($MFB*X1)-~BX{o(!1*hzXIgozW5wsW$y_$9fBXC#^3a>B>v8a}WA4B{!XK+fE= zeHa*e*>=z<|3}I5-w7>}@Ev(q{!0AX;;91Bj?A1hF9gy4q!8tVFldG`VJ}JSDl|oN z5owmNzfrqz(-oAv5VILO-cySpz7{lsnBB_46yqD*zy%37PXfCr+Pb)c54K5$zq1Yk zo?5<N87sN)zyr{<RD|=!oqos#B**~pQ!8@R<^<gy(o9smgAh=&c)bY3RGbwcp239Q zk=k*A(u!Y_3=TIPuEK+VcLHSEkT1O79>I;X8UMmtXHWR`0y@6}50xFGFC0hV^C>zn zz7P{FePqn-Uk394%fsMZHFJm`%Mjlk;8yXvIa;f0qXwe~m{9EeGe7j%L1F;yvQXFo zrWR>CrumYZ<ceVcH^uo2r3q&R4Q6;iIeOlJG+S(g4&u#$CN<Fa<!EnbJ4PT$j#_1Z z>mF>hGP5xL?nSdB;qZx!9+QYr#MhX|{#xTQZTK*rN^U4RlXs>ZJeFj(x&-W0CX<(B zp-(4EU8dXWl7PJw;+4=IRN|>9GwIb>q=~$YJh9(W88RgceV;GHHSgxim-Aa%G>HuH z0WD6u_lm23x#peTPCnyi`I+wedvEO=f(gcKa<|}|TL#bDV?`6bE03>D)P8sf``t9k zpm8vD9#lO*H2)pRX<lHzaJ$zmG@UGcITy{s+%nnpII1dU4y9N7H~>%P7X4?Hfc)ST z5718w+67lqCN?3TYiLaBr%t}Kg_7tHKw!JG$O5v-)H0yNp5wm1t!MSG=9eWI^$hW7 zeDsq3gWya=elXpQSXNe+2R8Di4SYOm1|LahZ+KU$SQ}gx8iJPzM>t#1H&PzY#`bNB z>;HQP24jfJd|-<EZFNrjqdOPai@{bD7bsPLKD>WBsG{QDp!cOi)dim(z6=(k-Pj{| zXJ=>sjPvcuqpogz>0N>9R=Wiw0QB9)G5ucv@f~;weRon>7(XFIE4dl7t|92X!ow3t zURC(!dK+K`4MBfl*Mkg{b3A&6V-NG!ObO*RzeZcgmcu}eyC@rqb_+{%-SuT~yR7xC z)`VT8dRESRQ$g_@U<25D3bhZ2)N^x7tLZv6Yq#4)?*#h2du7Is<<EMcQE4VNOK|`v zNa=_3(n%exPxS5p9hVxx%d)>7CUQkCr?X#GvcT~rQQt?rR=fo&c8jDpIv*;1$wq9o z2_tBZH=MBPn4kwpMzvI`8Uhilf|l=E`o9!g0)Wck1f~ch5#<Or_C{&E(0-R#CxQiT z_ad8Ch^mrIMMi`r(b#cGVqDgowXxvRLmGE(>{6K_SahA(c<}$lz>fz!>0ME3k)SVJ z+tJA6<|0OzhLpa<V2^;#1XND1--l;7tc4@1uB-I#gb0jDbK=JB=12X|vF*JW3sium zvNCJJAJ2p?y_lv^uwY#kGKKb%hS;PSP|z}q-sNb_qOuz{q{2TSVP)NV)`fP1nmBr^ zP%P7U$m@Enl6*)G&hc8IK}JSCG;9N3P7&l90xk9zYt6D}PdE_(7|XWpH%TSUlD}AP zYw@VwRvo;FAu<sSpW?&`lLVix1d|sXcP+F54XF)iYZ54pF1J$F%B2}4mO-*?Qv6Ii zzk)oDWwkRVV3U(H$|jJL(rC?-;y!#^PMj^Ot|1$uA<o4lXAjUgjW@vLUL?+CMu2Y} z92x><lzq}s=x#xvFZMY#3*cT+%gUxXOFi9GbPki7%BncVaeM)PUP;>nF{K_Bhcm@3 z#G7C;hjKEg)^u#@Cxo_>!ScPR-x=cDDRwr0oi5HlzOWtW=Vx|=%^?T}RV*Il#&xc< zt84MZH(?Y~VvXO!FUA?3_?f<xg+X7dlEBn~1sn!5Wl@;O;IV;fDOAh%fANy0!S}Ht zQYrUWGILI&s>+Zi+Jt9L(CNNxaI4nu>}?DJh$u;izOM@3lzB_E%2I^In8dJA9UMg^ z%hD4*?>akO#q*1N-nzWmI7Gz(FP6?6s#ydECK-LEl720A#18Dy1dv?wHvW+N!E z%EsnbcrPMteTSh;@3z##Vm3%^yET8&VsfeY?Pi*>=X)RG8w&=lWf8;#tH1QWTfbeZ z``)NKym{Y^82@I&l$gQ5w#E9;TdBDJO;cJtW~QM*Qr0^HpIePi+#-5pa=Qo(ula}O za%;lM8Ds}Jw&bNN8Ko<u3+E~H@+O{q<M0BB%sf9j)qw)do-r`=Zz7Q9K}n4bVGoBm zf0}}o9`gHFZRb3L3*sDZ0ebrK8l0U2!Ix>l(x>h1f#{KhV&$C*-ZNlG$CC~PBo0~X z$K{k^mviRpg(~fmEU+6rr3!xM%PsFEgvvNWp=~_yy6<~m=vCXd_e5UlO5I-!Rb>9% zX%WIr#X7mQe8EVnXHh$WCx+<~qUvgD7GAb39<SiYa8-Y43Y5fgeDM}{U9jWk#0^)N z0s|AgZKTN{f+8^2D>|Ph3_uGyh)zfRk}~J-6)t{mCnY6nqvu@;HlTgZ7IxA5*$3rx za>fDti`GO}?e#GNqQo+NM`4VH+u@T71jx+@af<>Hvt^x<EmK9z5dxm!cfzh1J0F$j z!4f~AwX}qUQoYwV?Mmaqq7ohBLNQ~OFBCG_9IAzztz&0SC<(hXac+{Z0xF}mjenHz z=v_D9I#)YZ@xOFFfJx=%O`$}p<GoostS4LNn?2oUxH^{O8QjWXssU{Cb~LXs3ExK~ z4%iMk-e@A|`xm56<pKHeGOB+bR~bI&=O=AnxJ}N5#X-Pp#g-4Hi4%A*Mm-45H#Cn- zkhIKK@u-$qXqXJ^DxnE6Hs{6?*xm1z1Czyg28J}*tI@%P55t(wo3;NXd>}@@6F!!p zTl8YNk%@C$RpthlP%|N0^5CL>3k%&{lfbftE(^#2F<HffFBbLh`jB`GnY-O4mdPY$ zS`>@ONEKTJaeg6j)C)QeG*}vT?KKCteo8{5Eo;=;5)v{K!!Xu;Q29}9oU6@TBy-8C zoZ}N6CtaqgV`sUV%VHl0YHFxysTLQ-RL3wO&z`T)p?i-cq-85!+7L5w!uD+0^x3+g z+6nP}7Wu7Z#ydaRHGqn0WD>|4l-4YMm{TL@)uxLwmk}E{AU9YIHj`V2SGA;@`FpaO zk{{U3O@clSdkY(Y6G}<|Lpyp;+`oh&UgkiKJ^y3&NJ9wikB~`Wfzann>gH5)C6!nz zlRo8ww%<ZJ#b!m~PX5A-8sj<&ke%045es@r;o*TLSq0LEXTERD&4i~_R}-8q5o$HO zRRra@L<UG+xT*}12hu=$ewEpkVw7EWYyWtK$aL@RC{7FQgNI}t%4P-Y0T_!fE{Y?4 zVM{28b3RYFVm>>)MU5g2G0+#cC(G{%>xZ|g+cNBISFz{$zh08GY!p>p%*~7C(&D_# zTK8SjgJ_17TBwVp0RqxnskCeF93mr}`27kIq(e2d(mE^w-&N=NVc(~-w-7JH(+<o{ zmdsvOEL^sF=Wi#GO<PqV{GY}1KSrR-aWoXSo+LFeP-J^n!&9QTpu;(&HV7>r_LRU1 z!s3(poHZ%<zxbhZFGxsidyZgF3~?21kr6R8Q|*~ZUCc<b7-@#d4DCVw3^gvGl&1Z{ z%tgVbwU_HUw_5=)IhAwMXycz^`oZ6ElQ!ae-l~ksdDa^v<%Tg1?pw;7cNwvx<Kya6 z**k*Pfwb3lWoAj+PbDu`Fw6GZ#9hoc+q?`s#+m;}5xUS6!0gCO@l)_{-5JxyvqYCS zE+!VAZ>oDrkhwaQqVd~4PfJOvfQn>Rf#^GY8SMuMeu7Q^b6bR0T@FZ(Psq{ieQ5At zqSKPhKWd}@XCk!`Kng~-m<?gtqA-M8OQT1Yvu(hUs3>G8j0s?#QY%=R_9+`kG+edu z1EaxM+lx6cI*BX-ww=9ORJ^Ts_$r>qjIlpX*oxyOs--xbHz%R)g*rKb1%CfN%Vh}C zo!i%=`fICKg|?b5b-cqAIRy?wHWUlc<h<H$zW!c6V5l*wH{G&vj)zi!3rDQ`I#FF$ z5yYBUPOiF$dg^mI>Ke=dI&FGEc{q9lRf5tiD}~E;BtE6ve3)4a)95YNQF6b|$B2Y0 zjggeq37qlIuqsNc$NR(*x|c;rDse+Q)QjDKV&v1py5?jyIL>i_BA+S+12&!cWH3M$ z?fOkS##rcq56321`Xi;J`{t)q!BUm*D^}3VB(D2a6#J!gZ_`?bH{A;N-Jl6^5SYSZ z{?4~Dq1867r7!!l7=6ZEG6~zHb}#u|jywzOPcd5#?j-_e>%KzOc?2?y*$nf(M;^`Q z53&v*)rfbRmX1EAvU*mnxC{In98opT_|53@uSSt*A{{>>K0AeurOVHsmTc+lM;9hC z?sr6SBpv$mbekOtXze9qrx@NZ{k=C}lbxOF0m5kkG_u>wi$Y1!v!7t<BREh8E4wGY zJF8tuOP%V(u1ElD;T3F3d{*;OYCnHQCDJ=P!wR67&ZY2!B2lF~+sY3E7qhhLXO_TM z(a@qhnCP!?pVldkANidpjEL_u`(PmQbkHQW#36B;9F$vyXd*cX;hr|WVbE^YvM6t6 z@ydr6?F>f(X;_NNBrb(Ti>MKzo~h~Fv;f>r^M9jG42n-NXSbwWJ5cR0Vm!Q8tx%~V zz&Y$Q$h?VLDuU`ND!)lHF@e)X^2uOZi{F>-7h8R}Wa;q<Uwe{!JYG07(mKrss>41E z%=pR=n4*@={yS5fi4Z?$?KQvgkL6MQdhoPhV)3AMcKT|Y%tj%j1~vsQkZHkW9iPK* z_4P+@D#*e3xc_NrWTX~*0t?%cZVin~9gOV?sP7ze|Nj<h@3^tSJGh%K+hlNww}PNU zb|$Dxb=0v!neKWjVx?PFGtV_a?4L71K8~p@JdHy?NQ5EKfvvvaH;!$<x6vQUNN|Bc z$H*7Qn$f*-6`cTySrBu+@tYi&Mqb-OUR@*teB_}hE|m>;R5r2skvmG8O$jRKQ-a^3 z=jTW6B_E`wXi~^}9bf3J`0Vp7Or4ytIi?^gUXOfK*ZW4*S-Ks3Mgw^1-9|M%t=?H4 z082qsf)8{LfxNcKUw?arF}RtSTb#uWA9^h6xvRLG@KS#!&OX}kX@$C-gW3XK^!;H8 zcy=adBeN2}m?GI~+8}RP_%mB>Objo)r?jdVSwWuzCoDuK3xCOtQobQ~H+-NW!~|3A zw>SPZ-o4;^jpwqd`w0kZCmwZ9(os1tL7p+*v(vGX@1b{_#q1WJr24c`AMjGJ)YgDV z)BCHqSW>btM&ME6J#!*9G9B%x`6Fg{JI{q^@*LfU!&BjB&Y=L9tIbOuI;lF|Ru$H% z%SG$*vpOlGS>I{a=sc>Z;t7`^ZX*@j>5i&16%kf3_Rq~LivxREo^lBLkWHFz5uNvl z6mpVst>2;Sq*0g62f86KCV5?sW9jv*;~1-L*PErMSXe;vzR$@Hd;E7&+DhU00-AK5 zbSPwN&m^k+oWd~cKl>QK;Upj30C7)3<e12Y-L9xZIsusZ#6&0P+hcdJO9;`uvIqbl zar+89>wt#seItrI^gVvao<TWzAaJOe#nuD;OL~HS?zgNR!%4_h2TNn~FJtt-d1ZuJ z@ibZG;^Jn>h)OB?$wAG2f$vE3u4|YSV5=8Fa?4{{mXqc|25aBx?zB2z$NUGi{P+)x zI>8Q8-e@>zuD|$3%m1Qvlqis|?c;fGje&38y9nll?X+p)7?VCljikPRjUppY6L#;l zn+_Qg3lkg;q=qxOa-x4_Rb+TcRAgn9Ao5O6Ps>8^hxhlV8N$1Pi~d$(Ve)@w9WZMq zfS75L%6qjpgp7OtZv%38FIi~M)5YKR%f+Njhx`_`rB-2RihM2#Qk^xRwjxH53?c#+ zP|ysQ5GY{QBKYiyOLa)rs+L;r_=T;Lo5W8b4I&iiBNSLz%|Ay>96V7PU9e%@=- zC%JDc8nNT7Mafb)+8FUcf3p)%8=K?FIR)i)K$KKeB0nTARitOb$iK+5%+8LKR@X99 zp7=w-C5V#84c_-Tm4X9r!Csx4n$4;n8{KKlY23uqW}`{sop()qxA%AXpG0l2LU-b2 zGiy+*dtHw!VqZc^@?kH$w0J%9;dY1<%e_OVrAdE8>{Dz?9Q&z%P{u!|6T);So<u#I zTCRh^nD{E~XrO%qD;O7W5fuEz{T@FIK%HkV6WqcsNG2P8Ra4+_Hh=j5cP0(Ws-z$| zO+IUTLU~xrLmjEHOp?N-4DZHU-drv?_+5zAAdggH365Le9jh_ufps>QEyKmdML*&u zEieH&+)R~%z$PDR^K%8$g~Bj#;V$uZhRF+RZ<!x0oCt6f44T;2pEEEs))=_w@Rwr* zUp2z^$8)s{)2-!9rz+n0uc9<M*3*3)LnasnU}Z=n-_49NE*0<R@ZJ63npR0sBg49q z$KPJ{-XUJFUeQ7joq!?s8vP5UuPz6P?Qk)Jxrf_I^D_Z=NR)cpXjo~8I!(C3lP(;& z>yCc%8qaq_oq=vK_Sw#(pE-RdIQ(d^eD-rl#`yhMR(zq?mqov6H|CQY=6;wNudQbC zNBs0<ER_Ui@c?7fHI02BEx+h%%6H2PuC0??b#068Gic^jRM%zlY@DOig}IrTeR7s+ zI!4B1uymnh%4Y}&-aj#q_O}odwymLx8loJb?{!poiS}OVPXF0k_2H4C{oe4pw)MgG zmlaV#k(5795Dw-#*ox)f-KEKz_0M574&B8!c83xj#;!ks4=cPiYB?l!ui*LS@!7zf z;@_Y7SNe<;&?TCEU4R>Vp_2FkYHSJt2-1))vMT)zL={~B)oY+kr#(qoB&kihN*fqX z5|{TDZ)e{d<G>iWaRf9Bx`_P|?B0WJT7CI!JjO)iYK1N#-5BTRfLBZ&bu9G@$k5cP zS#V$+b^LOvT+Ht!1l;1GKxSy3&Iun43U~ZClMP<(Sjx*(q((;afB%;F_>ofDMy}#* z-L~PsUI06Lpl_>b<-G*buGT2DxFT?d(vUUv^{P4&@qEir^TUx>t*-03v9XB`*ZYda z<>k|Si`)-2&%0Wn@wdYPVr;CDuw915=1R?m;{~#il4vP1LqXSX$TAe|#7UKYe!G7c zwts)*9r_lHEdH~h@4~)y31<kv;4;C6pAxgf?&=*39gU~xjRMW4V}JDnqi;g1^)kmp zR!oznzooWVSdNf~`i5PVC9e&8=L)LE;o*jbn}|78QrGZ`14F<JH*D__wzo#WPJ!d~ z?YR9%ytcRtrJLc9_j+vVAT$PVs5eP<?8aYO#jg!X4OywlNsD#E{VjUCl2;<eg>b3E z#yVumBQjGW^<|nyYUwK9zqkf`fWUa_FotYCv$Wz~cYXHgjT#&jg_QV!uw#F&+u1?j z`nE!beX!2I=k=Ou!5?o$&OII|^5{``d!EkJ_yYF(9iA)K5@zAp@0b|Z(qJkYLHp~- zEq_e-dTht1eT1Ge+~IyzI3dHv?s7oop|3B*{-+o;+9Lx5AErptdar$7D&J&Zx3gc1 zot#{{j=Ad-tFySs3o1>49gbo7NIffe?VE}Zr<bzq)%;LbOCovGQ-W-{rcHaXSs`<d zY`Pz7m#>+#KA<0|flcs@kgG`S<;z{)nx|MOau%K8g|5rFFoip)d+JU<oWh@S?-bje zB6{s!42$xmCE?zOo?6((<$1b}tXb<!O=_c#xyMfAvj38I{Cq^(9`9tzG4BJN9d?EF zcZAXR0x?&NPmr_cJVdtK!AE^!l%qb&d@Dn9{QBnjHVDsAqKv-+9lGgtb9@IkwLy0J zKMf!U9`f}KE|MQ-%uN29=LtZb32QpiG;2O~x3-cuo+eRoHu4mhI_2^#aOsQn87I_) z{|lro*f|##ygJwkeu<o0Ky&*AW>~$5ZnAIwU#!#eWg>Zp6_q1nT*+hfGY$lzce$&m z!0TmQ#`Enjq?j>U2m<=@5BA&eg5pWBarQyP_Zbx5qM3AmP1S@iRAOig`@Z^_8f<@m zR8a~SEs7V>4Zp;e5bBAiZz{=8=z?=H_4wvttDThFI&T8IG{LI8%|y=!(>8Suqf^q+ zR$^(9539~EbD+fP0j3Jnd=A2pENEy@i=Ca-jPcRkCeB{DuW#Mr1RCOU2}DLhr$#Rp zuwT)vHZe*ppc6%VG7uA)3#5=A!Jtj9d*8J`-nV-;ii_p;EiEllyxr+N|2*cq4%cp< zW;A2aPHMe_n<-+v*OVZ=B}SDLiVxk*#i~DKww`j8`gZgj3dI;Lj3W{0zb%BhLyMc{ zM*{M5aynTJtw%Ykr8n$T%$`i?HRYkXl8pTwN>)3q1o7)}qeJHs?yQ4>x3MC5Sn1K* zg^akM<OPN~TR8<uHX$4NnJV5mUtTXwtAd&_IcfnB40!vy9QnqIgrLwIXrLe)R1%Co zl*lk*vt+w$m<{;)tn4kK>y!U_sQHtu=R?!^FlMXbnEcvRJUPO@xop=qv-3{j+8KWv z$qoegDwojavj64wUzxA(!`+lDWLnQnt@HA{DD-lEEOS~X3g~<w_X^C@EJLyk#X@%? znB8E1^n2yg{j`CRr<{)tCycPa0s7!lxINii6#$p;^x-XK=IclJ;aExc6#jcNLm|wQ zQ<I<6bs!_AKYBXo$v5XX9lm=U?(XZ-H74GHDu2xCeDsV>Oet!Npi>M&vahqUJRNqI zSufM|-lSx09JA4Qfuq-@vhmCB?AVSBA=X_J6O#&d*qg31xN`GGEOdwp@B7*t(!Z0t z#{W4Xou9+szi~3%cJ^S$N1|X(830o|LEXtzzf!R(IMbttHQJQ>M_P<q+Asx&E}mAp z!%5E7SP}NYU+Eb1UHBi103`p<-@D-VKmVK#`oI0;6KT~Ps`sh^Y#3@R4KY-1D0Mv3 z&t-l-ifnI3xehL8l#U;pl}=#ONVZYCZT$dBhs;_dAP;#?AR1|yS5gEP{b1#-1`UQ} z1mbxJr_C>L@LXz;AlHNhkx*2C7bH4P$k`rjiqYE29BDmB5r?RW>h4TC`E;2Rq&u#I zLqG_m0XY5r(`E;`lxco%8qxo&<YoolaYdFmPx5PA&<ga)ennLJd7H4Nrl!!stOU*| zOR|4gA%}5-<}U&LuhG=bJE@%3<vaRw300MC-OL91@LW6rlB4*1e_nHj0OcQ)(8-@S zCeT8M1rD0m={RkjM>x$odvtLU=GUH}iY?E8>BL#Bo=+wo3<wVBy`*dztO^9G7+^c& zfgSq>ZLDFn?<Po4{BS)NK}AaQJOX_^s49|poB#qXCR9OW;9N^bg2bkV#|g^zKyju; z>wvJ(oJztVM~a5;2?(mjQY)lxCQ<lkRWkQ%KPQ#&Syo<F+Vze<MfH_ayVJjNPq_r) zFT}jR@e26!Ok1WTk(Ql*42B&|#UNz)R52vN;j7?Gs2d!(g*%#RIi8FJ@OV-0d`x_P z-V;o=WTKLdN<TJ3h~ln%UzmMg$9*XUUoT=zphhD~ZJl(4D~bn^Y@ntko4s5WI(Qrv z8FX6djV6NS6^~K}r#|uOzHE#zmHeqNg4gBS_~4Am;^0S}^oQ%mabVI`|H4VJ_OYW5 zj#g7OcWM`eXCU`8o0vg#x+t4dbwPcu9LlM?WHb>mf({((>NP^SR_HRd=fwv%Bt{~y zyBF65%+ADd!6z98-8O2dmzTrw)TQkZRHCH2bzi+`B0+}bM%!@<evwERkq>(Q3;ury zIl2CO*7r9A7rH#tZr{4YPh&RU_#nw$CiJnrZ!AmmnLX>iM%6A9c}2sVQj~|fv2+|L zBU*%zwjAq;>WvA-2sRy&Wxh}R!|Tu^M}IBAW!^>hZiGSRXDa*OpZ^b1loZpYDR}L? zw<@*jC#&eKm<mJ0%EU~yuG>VS=jEwevDU#DKcSlullV<5PC!!<JFbGnKT+P0p6G!} zZ`x5M!|1jvI03!h2Ay=G$qAZmSIBW};w`!d$rgu=v9Yca>Yz^D#pd(_I!tX(&kM`K zJl%`8H}UQxh+$NITHJ<cimjZN8RBGrF|~{Xa!3w1Mn5)2oTp_%Fu2#y!0}_fT9=21 z$JpDm6*zN)9CgmFT^z*r(%CO0F@4R7z(&9dD<36K@t5EDkHf=G_<-?T9V+BrHR4?A z!U5=VnizRcNNOtM(lxnaDD^7Y*bcWaBlo)?3)Hml#3#9)#@Y`*tnhnNaJ~dH@-}t3 zn-bM$(1`cK4e5Wi%0v_Z8C&?|Ae<aE;p#RK38EY?bf#mU0P<MNc;`A%7zvJpV$PE9 zP7?xoc?*gQ@hJ-o=<y+q&{$dd$=ZpRJKtVC@7-TNEtO{vCXOV}VsW4*ayUB=P3DP% zZZN5KOzv+%i142WV_@AL?8}oDd?}yZnivGmnc_igo!~j;vGSi_^t}^Q9-Joylqg$Z ziSGs0TMSqkn%;6o6OlGvyFm1E&(Q4FDleDSN{Lmu8poV+4}NwM4r!sQmr+*`n-$OP z65DqS3&Z0cH0kGKXVC&8?K;#%B>}S9SShHx`KArkSoiH>ofGz6(OT)?!05jxZrqy` z2Uf-LEj84-a{8aWpbXz+woMXj0NlP0v&l?4uW^rqr~dt17o|U)p)L5|{j*9QMzJvN zNk0l68MJY0B3j}>oQ8Cd?<|S+n*K}6ulfWYOmHIIAJ%PtrcJJ0b8LTO;gOMu54}~# zU~ud=JTh)`%9Ba51v-^E6z6IzlESw^Kt9&{`Eelce;2B;64}6cS8B29emMF+xIPvE z1n>2jo`DjhBooWFBM#bl`{nFn#QCy^2&`CPEb10G$pNDv(|*N&2_i3^aW}OPKjw>R zrUanHp4R^FM=628(C#2IFfJi$8as(V6UPnUPae!A&;rc{J{fzer_ZSU;iNZ}>1?3E z|EqA-T-hl<G?@E`<bY^D8GLE%ay><kI>2)O)Zt{N5JQ5Zl$!SUvk9mKdHJK><V-9j z^C|+12UMsOFj0q?>42!AZZuBWDxRYF)8Pk8&Ob$=%#SeWMfprmY5^HO$*V_jlD*1~ zR|||Ump7?b4!gtb2Z7wD+!&E12QrR6IrfrhdFTgvuiKbVp#4kY19$V=J}-5~-$4@T z(3V5_$+^sUK|4Gi%B(+fNCp&*sH!|~p{)18J8v*LptzJa{$wJ^=WMp8#?vx=rt6GT zJo`hj&+f7<$mm||75_DBJo8C+Xwiw5ro8-UX62UmtSwJ;`^&vYMS1bh7-OY*VYfre zU0$EMl)~a-kk4i}%tLzT10F@Lc~bh*Wc)<JC|e~Z;Pvf(z?Z@I*36f3IcHTXygjQ+ z6G@f=v{K)Cxp;u+K!7`ur|fv@2@aF>e}bm@`dNLXGp);^bLRBwXb$*dp1KyOrJ>CQ z7AVkh;mAd#c`i7Yc&2BXGv7k7?Mdc}pQkdy(6v`|h~1tokX90|kuCG!=6@k~f@M^{ zzUv;~9<_eEZJh83#&NL-i|G;P&?7%MK2}j)Ucs92DRgmz8Ljdhtr4k(6Zrsdrw4vm zaftsr<aYHCqrJX`Q2ly2W7n*_C~P4Li-ffYU&4~?@1wHH_JPK2rv||dg-0E0qL3|Y zmI3Lw6R)nvX|Vm2w(w=7U>HXCdxU|=|ME&VzKeFDcwea}J|n&ejdvqyjdwL=G`s1@ zWww6h^n^8Lfw-j6gbe--4UO!5#sg&1>pqm=jvkJeBd<yL(cP4arOgHxWkr#b`+z?* zD)ob$#u|C{@-g@2^71ruYEhFh@iQ|(<QL8nDXDc|AU@$1Q&wuKs&Z@N*@~KA*Y(2^ zVoh}ZwKrfC<xj!|{4?b^#cmEV9t1OvI;Ir`MS+r@UggpRc}}C%;#aVp6>RD}`Pf8; zjje6G8bDV4&v=E!qz6ZI;6$wVISy*{BJ>U^<;zBkJNxhPLnd-*ndu=JW~`OkYn1Wf zy>)G%^14o}P3)!e1f}eE0~b7JGN$UUOr7P(3!(Xfr0u0;LM$wqLso@>9d(2@KX#jl z*OM)gAT*DuN54VxR2D4hnUP%wvFB4-e)5TsHZW|iC{fY%LBOpHaVhOTni<T=2&~-m zeY&XxAp!)}o4u7~tL9bIhrpZQPOl}E5L<w;*MmvyWI3_kh(n09&lr~x=Rq5KWq**5 z+6OgP`9_R!-<Nq`dQO`ah4`q~rzzj8XA;~o8zNp8(btifH*JA?RNt>rV@JlYlnsn4 zZB*Gkk#CC;jnLr}z#tB<@K+e;H;|e!Go+?E{H(UG@Jo056MWOuu#gK<!gy5ti-6<0 zCG&BdyT$h&Dkts^YFye8A)&Kb@9K_fO&jfIYCWIFYOl+SGf-(|$JE&e!pln#RGNYB z)fneakKiNO;?0z_g-v>X9V#JbcEbYNc{aUB_kRIBH*Ihj7h8W;sOfPS;LTNL?%BnE zeE&Op-bLeh{>(5oTOL&oPA0Xv$4YL;#EHD0p8y183&g40ZwPD3CcS-7!w`Sb`$Ct& zkb%7X-yeO)#rI(Tc{yp6_+|ovLF_mgDT#EAXF~^9U<kb+orQ@5k!bQ<3!gC=iA_sV zsMf}Zx*ry%SJ9`>EWZ039|76$Q&}>`M7|=cBL)<Njut~rd#$W)2;&C77KfCw4#g<w z7sFC9uR&zm_)pC=@~nMjYg1F)`N+rsyAJ28q!nm;*Sk}Kd!$pv?3Ny~9u^ip(rrvk z7JmMMkZ<I_==A$j;4tgY!P43#%qgt~tHj%z6DgJX**W3B7F`4e3rSw(UWm2Gj|M2= zH4nlb0<y5!X{Br@1UAYtVlIgain5rs2*o-cqp%O7-Yf!e^ba-<*GFeNYVz4K6vBT+ zTr_1vFnND{zJ{4v-yY-}zWn|;$V|-jZGwMqy$9+q;}C+Tgb*X3;DsrfoQxrM9A6gS z7B@lPlz#SejjL92-CW|x&6)fN712wpw!m9E(zj)Afp{h4+dQV)ms>5{;nK?XkEFPo z<Ic_{4<&fLsK{`>S%GS8-mkP#?g|H}bn;nzH19eu;!fBL0oZcgQ%gdL%ZVu>t3!dQ z=B^dCx}djx`BxQiP{>i|khCYN)Z0E1TlV-+D&oBHhw&j9{B@!UR&~fFs0}q!cW9>x z$^|lA$h)x~SPH+y!G5ocac{JMYG}kSJ+B}mXhy2u-S`$pjs}k5&DG`n1~yWnm&3iw z*&?E$@*_@Tq%Wwn!*m;G-g)>uP7-TgNA%pUhxTnMH58>c$IM3BqmlPLfBzhIf^9Tx z<FG9GtvUXAGt0g}cXtZ%;f%F?8v}G`JTaNJNb1Uut8=lmSjCSf)f*1Soab59f4G6} zztA*lZ0o2OWzQLdyT<&mAc_k~=LhBm(tY?9_U{4o-#;4o;G%@#h;|*ibd^z(bTQ<h z;SbDnI6b6)xE!Tuo@9j|1H^hVSJg4&euWhdbcFS-isN;HClXLdJJY{#cEg|;X8a64 z7YBgY6^6tGq2GT{J2AtKI`uQ%{*&A=9Ounz;^-NnqCUmwIEUtRtMQYSoV#yioN<K+ z)wys3&&9z($-5WWb#!#1jBIWZ4~#tx!8!Z$2LcC=XuhR(B45sbWqbKzcEY2gxHyRi zZ3?WTu}JgW?nkb1zxu)sT83m@o2kox13acNR8qU<t6^JoBJoI&2ROlc+pU*=p+2h3 z2ppf=%Ls=yi9UEpS69L4vIVe^>{as6&zClyjzD|;EE3`>@=rZQ4!wBIs>gHbgM=0r zX!Uui;ty^qN;w#)TOhndR{hm!uezA<v!Xk|Lbt*&p3dYA%-5z!4PsLhTJ|*+{p<Tu ztj+7I2KM#NFC31G9Ec%GM14K?UhK3b8r=WVCm2z5l;L`^hMvKVul?t+{mKsbl-yBa zu9;?9&>RDfl&MxX*A$48fc1eiP{M;tqKT~w+sUw6ZI%{zj(H1dd+0$0dAN3F@c62p zA`3kct>fWfvu1~=@xA!dZMDEY9Q7n<wc`u4i00mmm#ZlZRGL%_1!g~s_55BT%$pIa zIa0VQ_qn|27K7HDc+UDw6CuIs6&Q1~n#kz;^d`BUc?9f-IBp51oOSX+?7dpH?WAYx zSzB09nVDz3Yrxn*B`lcL5P2_0_PPZQ)qn1NS?G|jwjBM>?R!S@VxD7w*M_Ut9IPw6 zlM!#ebegzHATo>M90jWh!98!zIo2v`+BNI(9Q?i+e223&{8vmcFDe-!LG_;`r~jA0 z<i-YlJb~5ZE=EP{kU|c@k4RJ`f;sJQwC3mavikYq^^gDuKk$q3M{*^k9ef$dX(GGe zps3_HBcwnW17U>}W$*8VJR=PVB^CM!6mDNAihi%cdmvy^G^>!-D(T_G6J(z6wYr8J zFR#HU72{H-X5O+QXx=e$N@O<tCE<*tuJ`GINPONzk#&xViuogW{aeOw5RZ^B32(k~ zB(YR|Q}D6yx@+fxcvM|pBw~qZ_nbI1h;7U+KC+^+G+7KF;o7Bsy*rk^$?1`2wcMcd zGNu+-#qg8E8BDcE*a+|bdKuuC>%ceMxycd2EE`B&`MWHEC3fM*ii7fSSl7(&8<ZDy zewDP~)Z3B9N6>Wq9%va+-NYC}VE0#n6ifG14O!0B4m7sum4p&++JY_YoIY`b0AoH& z<_O~FD`vqacScE}Jx;q~{R4?aF(A``0IGv?nVwCiAbopdr8KgxnA^h!#KR`)TD+0C zp5oOqyGAmXy{VS&qCHve<@J|OEjDUTnNNXO_4jbFusOwonMLS%PuRH6Jrg@b-H4y^ zjPf2K`_QIXi7n@CS1CgDJhg0A@DjA{LX2TKmRNIcZitm+D|#BabEjnYi5-tl^bJ$R zqclJ#FomfHB2B->JyW>cioX?<e3d|5%Yb(&osJC=wBqtks^MZtIU?$Cm1f9%Y#)uj zmZp2w>%7`czYKI;Q#~<z^k4|Yae0a5KfNb3Wl+0o7VGkf;F}*Y3*`JqvED4=*Lj1D z9Wn%XJ^2%n&sJ$@%=RXi3!<_uNjDdGYH>XH9oaWO;`Sq1CegM_p6+?I3AU$n#%~Q{ z{0hQa&2DZ(tcs@cJquoUcEl*~e=Bn1K^#0#RtINIA`D_sx>LsWT03x%c#~l|oDN%a zdvCU^?89!dA!j!$-Z#_xQ<A|K^dSHkvF!;T@RvCKIjpG7e#HFv<y5@HLnJQJQVg@b zsd0_oYk+HP#Y3V>VSZp?85_vUG3-x}ZY&MQ7D*?x6~HrHLOx>G?At6CSetGo)>V99 zfP%M$ihGA-nD&NWyDJvoYcNLqEV3Tbx!=(t01&+^J=<?}JsT#_UR_<C3TXphjq&&M z?YMTH<s47h=2z#ID8c(ZaUO4eZJx;7_91vYM>g+gCoYqgmL^AmY=ve-6jgniL3P%x z*I?8OSl?AHGJd)}sli%mW3OlDzUtz!*CS#M9;+{mvD=T7Uf)mfvVR16!4h@4K8bB0 z;%b6<d#p2VUDz*EEsI-(`lrlF{-bkVYkVz$(o}zc*UuYp+eRv={G^{h#s~?u3V(wg z@bkG)`c_O%&cRBl{NhT%%uM>Ew?uN*{?gLsa35ROSTBJPW)`WO-ohYaq@ehRo{q2c znOpXP$pJScdM2EZC_9!W1-O^HeZLcl6e%zH27SxUwrP8ZGPI!bO=$sZ?XE>6!VRtJ zoDLEKdjbFJ*pnBOk&XosoGQV&z{PK7ax=;OSfq}OUp^zNx)}ozSZvd}7=0tOS1Hri zzaw4rk~A}lI(&9ajw3itP`&u)dyqV{%%89<4@5pJZcCBrcYasHRgC{v{aB?9-EHiY z5_>CJ7bD3QZ@dda*zX$z>{@z1m$TzqCciH;CHvj$AFJ}~#kwzoZQPAY(I+g8o2}Ev zX~05C!6HPno`0+t{(S{E+=an47g6~Mf#~J}k$%6rm^)?tG`SYbmKxpL*G<ZFbDSNs zKwcdvA%z1IAhA+auO!yA2Tz#1UBazP3BY22&;R-v6sIh$I*Or<Px5MBbUccl&elcH zr?JgczO1B0>DlNq?4PMq)1LQZ3D;n*<x|?46rVi`pbSz4__s-8{`CWuzG}U$kmByr zZ~VbtUyD<&3APOb6!tvNCy#<Bo5qz^S9!_(tSrK2*SD<eyPJ^@@CVF*Y)WOxuwo9H zmoEbb!L1*S*<_p>8>PiV$a0sLmS)tM!wy{drhT2bpJ)2FGm?M9-hxF^bPs#JuV#xa z*6Gg|L@#k*Hzu?jTz!MpS^J}XwFu0OEQwzyDx^@I;P~G7cr#FUR4a=BV(l1l_Vk1k z1w^I}*$jmt15-aJUf71gVS#5ijh9MBsP;>cIlN&ul7r)99>#=^{wJBhN#fji<tEDf z_FS<w{)-ZP-Zt?xTnh@6G2dH^S!NihN&YSIO*pL4MKeKL=Aumlgs4nL(t9W=0mlMh z6{VdmKuS)xjV8PM?&>8MK~m`Rnz>+z08Fgp)r$cJ4;?P{;Q$n9(`HfqwzhA0v)gK0 z+<gPuzL-ydbqM+qb>^Pi;jbN`z(ihM5}KqGA1m4CRGP(*vq;o6PidEKJ+IGoS(!GR zCJIQP-SNOJdTj73TeVKwr%=uJA7+hzQIy|XJIL45{RB{n(~gZJTeD~>8e$|bN{5p< zJm^;U<3&tf)e5Lm_Gm7DR($JPmMgxtf*Vh%QOQCyA^+LqXcSF@16*v3c(kQ}|7t@1 zx#yl!!!P98+RsSz#j12mAF-s%NWTr{Yq!sm?J?@bTZTS~7n!gS#FIY@iyIaYYi5Y$ zDRB}<;1`RW7`%$Wn+ug5Sniq&A(n4UM-2Q>idRiwqE+IgqnoJcC3o}tCfFOjK$;2t zh`1D)-!H{nk#?D4wLC%l<Z6?YsbgJ3xZ|j!io>S8h!9q3Cs?uFRvi@nl8anhS9rvx z0u>WmWMpWwV9d!Q)e~gTa)4sz<|cAA00lJx?3jl#Y(Y(%dLb_K)UUwHcN*t!UiZ`k zE7QmWU8*%?>NCv)S|NAq%xhYUn{i(AatV+P)6{uoTpQ~fC68L1ExtW~H6L%l?3ZJ+ z5(4k%1?Goy@Pt*5Sy?=OfE{Wr59{!I2lV@eFY{BgARi5puRX4SPo=~gHk*DfC@uKM zEnA&f@#o5u1-}iG3lU9We>5UxrocG^?=R<uVhto&&yK>!@jv{z|1?vDe#|$yszI$7 zUG=6s5Ic>j!-z^G#&7H<y63GVNL0VIN%S~Ip16X57)!$L4RF4Nx?<5mEqyr^pvz@; zd|^u+xnjL?+>}OWql|Z>*&Nn+I8is-<m|v>q|u+PSw}u~+*{hHMIgD7THo8TzyU^h zH^A4vc|E)+Oe}>lwuQ_G6Oi3=H{DA7?8tUPH6-Fez0g(0^9+D?!rgIXgNlDa00>fn zCQj)u1?sjq!Rdz|+Y)kMDFf{fu>-L_e+09AMRL#}p6HcALks?;u&Y5qzBYm{KR8E- zbycC;Lx9T9V=2C!_al~)Ph2sPyAh3XVzHBW#x-8G<kE-gJx=OQNaDgmoiLCs)zymS z>XYTb)D2z5P}vlc^M&_Kc>yi$!~dm_6`)m#tpD7P&A5O@HfnlI97@f<zU?u)puZPr zclyDw3i-gz&RFZ_=uJpoCn}+)M3<0&g6mfn<~Oq`$Ua%_emoK<FUN@H>@EIvSRN4F z35rt!G^FU}0tI3vTOzg+_NN4dsA!nOF#zo1tO&8+9hqtg;-jpiYRr}?7n|J!tpL^u z{f7?s9w5Gnn1~go1bmZ1X@PRjkGqX~yQZ^|t1H^yg2jVDe@~Vj2wwAS$xaVW<8pIJ zZswO!v)ixf`FVNsL<z@ill@~0<(HPyaLp|()iGDE2{I+fLCdV`!(c0OMAqTyL%qlM zP~G?B^~+tbRa<`)UVY|3?3XY}HWc}S9Z$5V&ZlR*=Cx_6>GLaCZ!5{~`+Lc~zEwQE zI@OFqCGbASBDIS{0UNi9Y8uR`i{EKT`N$cy(i*FBKf0+1&btYd1n}d-fpiIf27Af= zY_oUk%;0L%n&X_8z|ocSgL4Ap?lpREDnS^OmIgL|VW64zqA@_3U-d_L*hzO}8u1bK z5H}TGxa|9s16}qaP2_XXB>tQjvKV+C^uZS`ZQdp<NYCPA8I@RzC~v}@<Tl%u-iWeu zEF+q?5sN+N0ImgEE8*!E8_Q2D^|>KWTl^4E(AboME@bV=X&*Foppj3)3E<eu^;RGL zYk=R&mmkpBBmZ_n4R-kZb^S<w5}n^*r!jy$hhS)=DfeotjAT?~qE&3zr7Vm3QjNv# zGZz%M(mI)~Ei2@sJyTsna96w-_{vBXRW<a_aQI)rc@&v`NXG-7-?I#fc+WMy#(s5} z`HiaA%aSU9XAM~TqRax!VbDhy#MyuK_q*=3BbU0QnDFZ-`H9i9^cjcJ#8JD<{Z*~Z zRs9QPPVM+7RkXq63x%L+#0tT+hkkBqrK;aFXcM(c$~=sCbF;&z);bUmD__T)Rh1y7 zm-7cggZ>V%YWVk>5gd?=%_KZ-WCjqe#ciVNyhGZ9^_KHH;H}{ssfU-B45faBr-PxA zsi`SBjPiN`aXwus?ZuZ4R`&r1UA6;RFI;_}r~Npx%ReE4z;_oSu)Gmca0(P|hD+Ri z+2DjKC-n57_3-#4&BDe^Cy5qCo+G?yNW=xn-OAR2(qxWL9MT<Nj50-m08p6@{&CRk z@>r+gQ;}BcuS$<CQW5NFKq?D~>zQ&!5^Gf!X|mJR5Ow)T_hfw5fKp5kR~%EUg!$Nq ztj!52H)98r@^k+00~B}(z3n?dY}|3x%k(b@(oL07z$9Epyn619TLG0Ko<8}xUBrL) zXE2qkj(wV&n6ew_D-m?a!W>0END1&+aZOzwk4_hZ(A5ny&`UT61wGK1m(j&1-yJ91 z$+eN<rCH>{5KQuKh8&tc3_{fgTf&21qnh9^%Y_#2jjRjNuPYE7P&iGVH+$`Zh6S9I zQRd~5QM)?JIFLz=42aBv#^G0<L#bP-tlL9wP(M5y;$8k#2=UTJpAqFM3(h*bPSdnW zFZ*8|xjfN3P&bg#8%8Cs(QWoRBp=n03jHh`6IF_^k2rre{Bs$~!4)7zxx2fGg-w|v z+-^KLLwy8k5r!L>;O7)AWf+i;!f-<UyAr|l9x*02mLfB*X#D>p>@9=h+PbY_+}+*X zHAs*~f;++88wnoVH3WCJV1eKgAb1GcI0OR0o#5{9Zl3#`d(OGvkB>i8byw}Go_nq} zZHzGwisU9{iP#1pSn$+8D(J{8yyB8{mBuB$@Y9mVYSsI?MIJ0K&>QW~$y>Z@dYd*- z`14QrYwEZ#ZI>_G1Y#HR>nGMeJ~GIAiIeJBQ&K#Ff^?oo%ZUa8PR!K)&rkk8V;JT| zP~X|vX_SF}+^=q7{{|@2^Pa0g54*CIgD+CtI6kaOJ~|<maouxnC=18efZEBuVfEr+ zIHxmMOzJp!llNkHDM^xPOAs&E?C2fTic!G9nl;B2ru>j|nzd#<M<nN|OXp|A5<}L( zflz~`5Q5-YG05j0Rn)yiv=8^UVeWidkW*OS%~9I6HQa9~zPoQ23ZguN5&|g5Tt|>i zYWq2A8`){6nQyPhf$^+rZhB9h-T`XD_#N?pPesm&XK=3}WN9Ph_3s-|Vh*%G%&>Ev z^-SJkUnoMplK8vgd5(DBkQ##@Rx-ni+g29#9ezmS6BpDgLC2VO{q~nsSK}(xOX$Jt zNX|}#zt8*Z4XXcoskJ^>C(qC32mD3E07NHg*qC=Ac`7dk*l!RIs9WxjS*`Ps9H*St zb7V3f|HwxfAK%aCoLfrvY$k>7@bRdK{eh+X0qCBga@UdlJ5J&MXWr0rv6(b(l@&~f z@+hG?&{_11A^9}#<=QRk?e@f52OY_rxzv=Dazol{6qX$MNKMESs&x=x?y*S}>; zVg>da7aMyu5y^F0JRV&ye1x?Un@uN}{W^b~8*6>?wz&Jx*GB%C!7=p1dS3u&cj%Cu zcBh|FfpDvoL=tQG8b?C~C-PeY!6@a%w33Tqy9k=F65YirA_<~fR9H-KOsUY3#7#nJ zl;d4&r_W`D;RgKqlK-Q>g0u~&wj%SwG%m;G{re_|rLW3jH><F>8{$v6^9`OgKUi6c z5f{cy0t2<g?*24he8=>{+2oxgM=DCCRHnwMZsZb3pN-?+L+XZlhVnV0X+|^djCaFH zyWiC3kO8$}&{`6C7n*p8#KSh$%jw&JUQu}2Mn9bVFq@x+HgTlKzP!EPEz&#soFCBP zeSkccYkf&h;Fu=O<VT;77pDJkxU7ELCu=h7neH^C#DbvgeN3O7Phv{c_$kVR(zT6H zNX&qG$EVHMaD<>5M*LnS%xSem)N#bcb=a(x4Aw_R`=t1N##k}#4M%1u^HmC8HzSa5 zb+XlvFSC+nR}>Iq>Vdm`Qu-kwfciDuLGxfJR=w+glkFX7=;z<D+?+Uo@a=-VjIU%? z*tv!!AEaJ?hHHS2#9jy3q}Z;=qprKE-?zqvy!#IOXVCVF_BN~!K4%*GyHT2#JH^N+ zyX7zUdlAl3Mf}g5GLOxk=j>uNa!#j!!*+2oQi+D9I=IwH-MYNQC~&l#e)OcCnIYdx zCctk_PMwKNM6@#7S6p<nS>ybCFl4gkH&a?kwd=;Q*MJ?#%Z#`wptOx5X9I2a7N%-v zc<OUw&)cIb^!=@pYL)rLWtx4G{Sp#})ZDLp@&d9l>a`SkYCO_Au6kz<p%h06#l3;w zBJs5_G!QF7&k@DXZQIG9aDNw374LE05h%Go|C#GbT6sG1R8U|nFj}=z%X8Av&}aDl z)hUwTf^zuzD*wrI{;OyF;t^$Xe5V-eit#s&ji?XBW&y8Lqy9jU%`V(_oyonhyATm; z@JXw`7}8~Z6Es}k+zark1y`-<ThJ4J98#cyCJ%PAxk@i&5v3SDk$l7Kah-Ole0DYI z?+Tt{u&v;L<wk$^i{8>IT#ONu_yC_in%l-78`gRrwP&4`9?kHlp5aV@nD5@#hxLt` z&8tW3pC44$O5nKWz{C0}LwD`3YtII4Mm^8eBu;1Vh<Zg%Ws?tV#>MR`o1`>y9n$Y> zJ?Esvpp}Uq#3tHMW{wdoi<1kMB|zyir{*vYb;rTpSCn~z48E_LSKq*Aop{#TR<a*j zBeBH>1t6X)4ut&mhEGDM3Ic0PIi8$QRb?Upt##=yU)?~j0N2@W?ai9Zi_w-O2~3WA zV;~{pl){|xM`eITo7_$YRP&9#4w@JS6+qhhK+~nYFrWV#j|NfKCIO|{u_RyWgI-3J zkO-w}DG!ejFJtdU@{|A{O|$Ra>UERs(G?~uF@JU(3wCa)Dj2L55;2^-9rx?}+z@^n z|5#Ls=Z{<6_JM5p2Eux#Od)MSFAY_}#>CI6@(+pz7P$_;e=+=S6E3zlLiH1yQ_y<T zn$kUAgX2gPOjt!rjodF{ne^$*%CUX?rrNEKNU%SsWzc+sSi35e^hr?)mKu58f#j*^ z`RMpbCE#xQS*QAYz9)|LsYvHjkem$y@zxf8eVu)S-_05&q>a*#iMP)67N#@5<`T^` z7E&;1jF3`zS#2m@E-%g$87svpD<Fm>UX@uEPBJ)odpj%UVHI$|(R8uYG~zT)u$Gg9 zz=sS@F%@wEg9dorrZfy#2Cm+vHpf#YjBS&EryCsh#PZ8D>`I(5PZ~h^tKUvh8M^Ve z-_(q`wIKB4ob3&{o$Zg053b(i97WyAiTp~Y%O{5()plgugmSxwmBgxl&G-v4GD!$^ z(+c6A{N`;>!8Zb#*2i=+hzat*x)EY!SY)5T${$c<_ggCE$xFQIx4WJET?47>$S$s{ zhfBjVTp{!5nV_^*)AmHC?ta|w8kn1Cd{^<`7itd&{roKW+>EdRz=>80%ruuEKVz&r z-l8~uqEY5C{-(~r`bv?-xPe7Y^sT4K@^6NznHf4QEv-`M4i@b`1T&;f0FL1<Mv=zz zkCi&zSh{9F$J~>;AkRThcinXmlfIBy-fa<gT9ZeRhhxs^dT=XS)V0h>I)XvMRg(1; z^5HM<tP`z}!AY)SZ$5eTyv!kpDNXj8u;ouI*pl&1c1V4gmfBKX;h<_TnNpAH7da!V z@<xHVq3uid`l>HR!UowTb)_;akYPHEuOAFaqg**>W4~jY9>gy-%%s~OUR6?xyd`N< z-%t4P8nzA@JquBE<7DIUg-(375_q>#LTcTbslz61;Zj~%k+{80FyG+RmE(2n&uniO zd3=2D>SU=gr4A<f0g_A(k`W*#BP;Lsrc#P5w!g)KYfKt1g#N;YbKrM!E9OBSejNeP zdpOeHZoY2nZMCB|_Jn)Tw@{i?(l9@PH8MirUHtue6LUq3!JrV`NE1h^o*VQ#+Q-o% zP;d|#3-f#4(j_!#g!XnM`YGS(T4k^+@GAD<>EcbM(z+&I-ws8M-!3%mppD?9jBr6# za0y4rGF1>Ynn=<bcUXRtqcf!1TTbN_r=iRIxMLbBe*w2hO#G6w{kLPNbnbllT~D;p z2Ay=v&0f!ygl3a@+QZ8d9L!g6W&e#hErEo;fwaYb>#yV9ef|OJxoe<H%8!hpxEvcm z=mUr|`!||W3+de3eICh)KE&+vRGL&pxb(5(Xj!36*IS6c`v3@W1Ac8BdNAS(RwtCZ zp5(9R{m=g1_~0P&I}SM2p|tRMZ=uTMh`k0lK0C&;(k-#Zd?)wo?_{3&i^N39Pvyl* z#WYkEg^@}gNx_vAnDg>Of*3Z`LPbgzYAa1sz8Quz)Wwxpv{}EbmEPI#StxG5DJVpW zpV2@T?_j^wsxPckf92UIN2uiJS!theM{;8q9<@i5VKYo?B3*};QW!?u|9!1C=dmqN z-|BKv@K<{f+P2HYNBaCwJ`@}``Ag`>^uXKnGx6sw6H6~kU4!(rGzH>8FXOEVq?(G# zq^xfUw)Q5R<hi7lz1yQxjh%`JW^T7Q4*{6U3}$~Yq-p(Yf)A{9R3m~s><dCisuN7g zsnXqyteyFz=R8~En0~g|9Z#q5U9l-;4A96k$CsLRcIX9M9DS*`v2UA6AAmPtb9!cT zWBT%m1oNS#*;`ite$<O%QC+GCChM3_K7UE_hhR|b@90f8lsUFe*fFCv&gZLnc_IJX zuOn6p&<Iy>NZ_*TgU0m^3_d+WPTbV9(DW{F_`{#9b<+^6r#NT5n;(+fXL6ze-}KV2 zA3Rt>t(ZQ7TW3K=x)_5k>4pjg<j=jhvRjA)m_yrN6_`Wy+mlnmN709lBD!gioS!+I zYB*@R>;KXa2;L!reiBbeX9PTDG}`t_1>Iy>x;b86no~wbbyR0qcSt4^ida<V+fg}< zI~3dnCO|vws=2*RYbZT`8)=sNf=t8#Ixih?g4+MZ_x@Mh^W^3~F$ZmEFU%`996y8% z$Wn-3BTm^lxGF@*FcdpEzdgc;89wZB*^}_GG?%g7(SU(~PjE+IJq$&_jJA99h?p@< z$(4!G{_x6H=X*K?IBG~s21#1ywpdav2@HB~Vb&{Q0(9+H=UQ7k^<Oo<8Y;aUo$zs# z{w8C`D$)2Uhom#F(Hygf3#%>h1dC1}j;wG~w#m*y;K5W<4l9LfL`fBrZsN!Myg~UC znIm$Gj^SY{*;22OWkynMwIpo&?p^9E0AXyd*7`%5UHp~esm^}C81DVi_viC+#pbQE zLulgcQ8^UpA_G~fc*pHaD=4vA-VXS>K#W-dBE`Bv+@M>l&W`;uwT~vf53l+(F`~#Q z*7U!Xsr|V0JXTU;q*yrpfyv1yEz#Ov2;X656x)8iU?zm3xw!vlxCF274n}IZy*G(4 zFd(CjlVj^t@$UY9>tb)(_4dqaZ+AE0@b}lXEm*9B_m@7pr5k9b8+`Jty$!-w5(=Fn zZg0e%?qUr0h?6!&swjS_uZyopPd`dAz(8Zbaf>%MRug$57I6+*6Z)SdJ>=<<jH?C0 zPc`Pou`+V&gl}-7RSu*Z-+V%CUzQ@hyXj2dT(Us4JWeA34-d=;2-@Q(>oSc_ML=B_ zU{8F-{9icg1HlhW=|EsjBarA;y=X$>EDS6=HLCJ%qoz3sBy%KUgJo3HoYFGuJs#vx zm1ae#C$8r%hA9s+1kiUG!)l`V;j~|*v2<YGB>$iEg$c%Ew+)LVi5(R={lQ1i93Fp~ zYA0z3?}K&5W?oimPn~=#;>v{QUBssGN-bKf^3V@7J~mwDOvO<}6lql%Wbnge4I&!J z8Wxm>Hx_7Rk}5G3?`)IV{4tV+3FcJlQ0m5mfjCz8Ev`C<ZJ?y{n}$A#y%l-&*HWL1 z<?8gb2z}eis<dI(jKzo`qRlsZpMOFyw{wcJ1f}ryMLk)Sb`<hL87uKIt;X_1X;%V# zWf&OxORB3?{z&l5^$>cH56a-gDe3fzcr!N^+L~m|JP8`p+-xsu-98EsZuWPmXGVCu z1`DV;$AR5{$b)yBjqq`v>$}fRm{mZLzg^)e{P7JT>xZ}Iu&}Z!$7^J${Kl<1?cy$3 zYmVjY$vT*wbWY^I8A<i+iqY<wKbGF{Nbe^1YR_Z&jBKw-TzfI~;;Mz;7P6>ro&un& zGtP+*&%mDG>a**^C05yZie!MOFm!iGu3?1U^}dAu&SmW*3v{djr=M7$`&p&m?V<M` zMOK7~bJ93p*Qh_VT2Q&H)*96iL=kpacQ<){aWyzm8>yrr9+ZBV<2-7FVNJ18NhGql z5I#Uqy>=A_K^C@L`Np|zmPNkuW={xfJooHq^6_l=C{_bgd}1bM&hGXTn-vn?o1pvJ zUr%iM^F051HSvc)J+BaTe$SeWPMoC6>Ckr1_;6*(H?-fXq$FdpZ}rJ9St;EK$+*B! zPwhG}XgN=va^mODpFs6wwZ?B7(f#oSG5xU{Ecw4K`k%6lKnfU&5z<I~r2nzcRvDBd zVh*3`&DGsJjeF6?&&z6oo6I93%;7I(F@^eWH^5FdUU`rS>5pyFPx%Z@SmMJQdYqm( zG>@=S#pE#UBw5<cIunrrkliGs6P$h|--Q7R>yVZe{e%NYx-gd2C{^}v15+`QA8xcG zPIKB?a__xc-v+6b482o{)2~theNn@&a`QOfUYd-|VGM=X9Gw%3)b)gB0S(~HoSe6# zvvwJwMe~PXG?MIY29gqML5oNMW%hBiv!ZER@Hb;P=togwwv3=XD=c7&rS<7m(2q+A zI~)nj=nIKCw#b4s4j+T8Acnj6W|->tF2uKRjkL3CokV!Ahg3vH7$tZZi|cq;B8?Yl z0#CNFZ-O0D>WSp+Oz|K`I0B-opyZIz#2nPJqdDtI;RI!3wCJCwJSZic+ADJwuV-8R z@4r3(6@JmfMVWXi#SOeu$kn|zSmJo8K?j2z5OE{(e&ccQ3*-FY;+DKek@4g?=8LbT z%+q0X^4aJu>y2OUH3h~w8II%g2y6Lq|Ir=YP(;}S8#{_k>$gfBK7mN7&o>foB_rm7 ztsag|i%*cNJpYrM{ca&fhe9Od6tfQ@L1{BNN8z4v2gw2$#|ixvQ#%WMz8>pWCo@>9 zVr*Z<^B15hY5!`nn5NQpKdVM?I(^FKe(p!coyzk9TxmLdVUkUg=cS<PTL89Bu&gj; zG8un23a%xDySRsbf#K;uso|O-i9iE0{Q#JPEV;iP_@8!gFd1+$W%qs;Pn<?R6&8EV zd4-o3-vm!mp3gxBbrS!OIWGj39v|f5D?pTGEcS%!EX0miQIVqc(Y7wo%7!A&m9t8j zQ0O^wAtCf~ILx<nllJFh{VmmxNHUJi3@xEB)k>zroF~tf?Cw}mlZ*fs66o%7lX^$b zm-nSun5)?cCtfjzp?QeR_Cz&K>61gmNxT23bAX^KsfetAs^dvQ#5gz3sGUz-1I9So z_)UE4#rf;VOQI}57o%((!bnx%i?GJ(MX@lh07A^F{=BnU#1l$^)hestdJ-{tX?1(r zz*vyV5kU|(9aPHhIHopaixoS0D^$)tI(WoRx+!%Nyxi)N`}Rjs#q~bZ+`}zf>KP)q zviF!;HjKa)`j-gwH(m0sui;gMUrcn5$8ujQfSwOmo~n{27ojdRbxeLk(fE96e;9_i zuJhk?EH@a;dU`0lvEm<jC%ZLvQv7h%P_uOr{^b{KZ5Y?_s8e!IU-x^1@LBSOaa6%A z)BSKed<-WEVeluToA2RnZ`ifb-s_?<&T-@iSsS-h4^TRFfJ$i(ZOel-W4@y`7h7BT zTa>h)4~yOQe7P@fEmcAWe=_aC(#3!Y_jMF{R~h$L1zxL-ebQCrPxfkhwClYbo3G@8 zy;!9Dt|U*ERb{?hsTJD-4X#zTWxaWFu|;Y;+UatZ*ljO4F@F!R%!;-bjE)}6_z`IF zsp1*q{&Qy@kVZ##qFzHq6>KF)=;0t;Uh2uq-i^Z-#)v;m(|xjqK`(7BlaPrIj-eoq zwB~Cpx)l*cO<o$z&oLv<kPIyXe_exy!NhHOHS@zNCcD3cDiNZfP>k}F`-OFwzq+P8 zO(r9u8u8XpQwvb?jHqUcrR7M~NC;ikBTrPWGNv~S6`G#p9H?B^OY1eSyN#^aWD##x zb$k*E;mF9fD{2!3!H^u~l>~|~=jVtp=I7n1+{u)#Jri1|mB5iq6xe7%Qq^#p%;ET; z^(l}$NRYsG@7=mwq(&^_rM-f^Vx(7(3E|wW*yu^K?SUC!RlSQ*?pqM7FbeMl_FXr| z6}t@?#*_2N0NeTf;@q@<Tv#u+4u#{!{+)2=NSU7TjbUZnaF46E=Jf^i<D@L*5NF`6 z;@<xL2H(Xe8NWAg;797`hN_KTZ>9D|Zc$YqzV?uJ^DRS@5Pc%d_ouhvSPBvcXKW1Q zW$2ivxr@A>z_#_Q9xsyv?UT3?Ev9pX)t!1{T$4c?wO~{>Cj`k*7<aGE#!K>wC8;QA zYAG#yd)N4#%tE-P^Ul$a?(2SG>7qa%wVB_$mA}%q{=1U`2!Hyfchx+W1jZ9k7l~4L zWMn$30w@}ac)np>+jOPi7?u8g3Y4RzpZ;e(aqhE-d?rU1+NBf<30lA~Dj3j60^9;k zAJ^gUM*>9_=%wsl6C0f5M}=hIa$qtk>ZqfFep2P>;1DB1-4x|VLNO<}A!Ciqe9X(q z3`S@KWWXdORcV}FO+=`aX@V7d$2Y4%2Z_=&$fJB8CUG)8a`aLjYf<IPgQhDzN&+J? z4_y<+=&9foO*>x$`8_#1g0zrAWrR#6?9b{6Stjv8bG$s(=|1~9%S!}RJ1=wgz~T(* zD6g@Z`)#cei3qB6xC<p8<2xDLvq>Er2~d7A^v&b@g>kCFVCH>nqPh6ej0yh&SH0?< z<O#73F5?ln`Y-Yqluq!5`|CV8$d2s~K}NlgA(O%mwSn>=S#PWDVx{e3No%VBAEot7 zy{KtKXB0X*KC|8=(d8d~lwskR1AYNS@FruhyekoP<2FXm2j~6=kaiKyF10vLn-T7_ zb&206C$$M5wt2<v1K3lFWsL5Es|TVfjEu53R|{!fZ*Gvw4|kI|IgL|UJ$=md#Svh> z;ql*UND?6*MN!4d1{|D8w%8f>@ZeDEQ5=`(V<&%KUD!g}{qCB6g~w;ivkF1F>7;@l z9h!ji#M=iJou0iy+6Bsgma5AwJ>12<Exl^$gm|cHv%1Gj6Ntv&g)-0pesHpSHu&{i zZLOp+Z!dc;;IyWt#JkX(nWPU4vdt=d8|W0WDfrO1VeW#l^x&mdGyg-4F@YidpECQu z&*FUblJP!VUe3hE&-28;Mj>3)d%n>L*OgsPSfK662}^V1fzLOfnzc3y@r_s*M<zpn zTNpPjpiaNYPd_rx+KB5-@z9ee{;;EgHAzpEx#5%g!-A=|q_}}J-PP0~i$1r#Jw%8z z$w&=IrtV;gj@-KDh<8Fb@VF%7vSbmC=r-hQsaX|4@gu`dU4rcSIo}GlTX?a{_J*}k zvV;?rz*NwOxMJ24H}gM7x*`YCOmHgg%(kUUaxd2XTQW`45ja`AV6w7Cv3=UKVMxN< z?DVZ=eYO@dYph%gNbS0im7+-|T3f@puj~_gVSlR6l@Z@Jty%i%30~a>)USPgnxE5C zNOLk_E0u{jPJW90De}6ON}o6mo5=FSRZdT@)!p48Ni-4z(mA)B_9RX6lwW4WVYJOx z(8?(38`Hv+HUcZXc4|GK?k8X07cI6)Wun$s1N}`__tU4Z7b~yGK9DM%dc+0I5^BqV zrV*gHqDpt*a(MX)u7@I>$dhLgz@QyctqEgu*678*v;Yo(UTp;E#;r}49P;-(I5<+O z`}wQYfyppiDY0_k1g4aXs?9JgkIxH=+Dr{KaO5Zi;UZoM_HK<}#Euk3l^7^+@_ANg zbN`AqFV(U=I?&PgZ^6Iji;JHZzTbJpb1B4xHfJVUYMYn6FiDqL?ws9k29iYHOUXL4 zCXV8$eXHWCi-g2>=fc4poW|=f+$&N$B{RsVx6SVG<qh~<Bq`$N5EuAvOe$N4KGam} z6aQ;30Wtc4Uud^^-63H+!d~CUk1rp*&Fb|kCCJwGxZ%C~FV%4uyDL2{WdiPRnQwwR znL;%u#$uC#O0OD<-jPoTW%%VoSG0x<_)PWrIUt8yT*8P#kM>==O?MRgCM}Fok(!0H z(FxHfhl;xHWsh67A9K7qB`e)C&MIJwZ%E%i|5$Q9_sQ$GbK8kq-Pg|c+L0aJD=V>W zeeYN-icX=}Kiv<aO+DDUj3399h;wS@<^;YVAN9(bq$=NILVi9`w=Rj(G?epz`_o}w zua}L^NH+}_$nS#ESp9$aQW+SW_qNNOQ2H({N6!pdy@tusUtytWUO&7cL7yejCIH*z zY<{x?mD!XOim*6QF0+dJ;KI3EgyT&3rT2zV*OI~Ac#rryag>Gs{F?+694Qg>oPVj4 z5wqPe_tBb+q3E_fqujBESTs2fqC13;$+ki_y+wXgwHqLQTtNW^jV{2htZXh;Kxti4 zSxtR%@gjP$XZB9?pCsj425bP25^3NuetnZsm1-do5r0m{!%JoMOQuRR60A6DWlSQF zG(E#aS02j3!IX?o=L{OZq&ZdMQ9|ac`iOy*?7W_ejyK}o4a0mJ1B>u6UQ$z736z}X zfrwMxbeA`V;*&@T6%nG0=FJ_O0mqjo0=<OUHQx2Op7QXNjmpD?tgH(T9MiNQ!0%nC zq9`XzT;IA+8i;DmXeVO2UJETE?1(Y+P4CS-=|fs9hbIpz=*;~&;D(Y{ldOTAnXGHB zu`P2?mz9&IaT5_Id9A}=GWm6)ne>|Z++7u(bY^Fd0wV&uy4Nr8)<f*jBA+r;&u9CV zFP|7?|F*Gaf1itjge0lKW_(y+#g7*F`-a{Ukx;?upVBk)cz1c|YWhquAGlh3u-pMG zMOtCzJ$1ePNyxZ$&)RIs0DmT=U465q9ceLC2<de74K0^NQpvH;*x$v53(s!JT^o&y zRQx_pmv0;L+ZJ>#m6WK9z|g{rY+LzZI6Kk<g6Kq+{2o13gJQr_%d65%;>rODkE(lo z%Oda<ilzS4-~5`7K&h}3QB#|Fgl7_mfIWI+HV7Whh)*5jdw3I{{$5`3XM1~>C@mmV zDgz=kL4feX<q5sOFVE>~q5nB`IWd2MieNT(MxTmSFe|99p+MiRH&N?tM(g2iTf16$ zeGgpYQMTcg8>uS_0YQh~j#!-B;wgvVKW`<D4;Dq4Jz1!|`|~Lme2VI^k>ia`XQ;41 z+aEZhyq*<`;0|Ex9xxbM0t5fFqbXU^DdR5Y1!>o(9b%J(QPcE6Jp1B3G4BRZkcA<o zln)8C1*ldo!+y{(Jo0em9?ceOO$<r{sD6)WP>FxMG#2)Ut>(5d0On}A!Y}=}2~55r zIa5i=Ux-Hn0Rvu18RKX%k6Zf@$Y3$v8xd6w-uOemtnrk?eQt7$&UohotFZm0{Z>Zt z`+V`_uIKx%7c2^hcEL;{8Wz|$=eoN}E#z^)R83p|{x=3#Lf}J63S=}`{dR68fX0Ty zD7~ESS~~Lx^((_C4laaJ34QTUi6PoI#%!eGn*>a=&An_`f4V9ONQnk*=nHQ(Q0K7h zRd5$iZ{hqv1Dmm-ZJU@XWWkubEalyuAKI)8m9n--Vf5fyFryF09pHWc=BNZS0{w(% z(oPX#BOoH<P8suNS+|u_kMhae4+ASs6SJ~N=<`TPNnbiQEUc{j`Can>elnu_j|2b% z`q@D63g@4-pRpW}wq38KHxca=T_`8Q<ZBJ?*NSnP7(3vxH0buAY+GTHh9nAp8oTW< z>T%SrW2%PYjJy&-0@H8GMNsMR7iba_6J;jEcL~$x=X6r}Z=B4LUqPd5{;2k^H3!QX z8acaA`43%UL|_e??}H);OJE+W2O%>_>jFCCn2~UeW1PEe`88HX8f9iM`UX^_`qd}r zN}uWw;4ZGvN6$im4sglnen|xyz)OvV<Ky2y5C2p9ajUL?tBZ@gy0^Y>n2>dPEWPE_ zG#1g!^LRNm@UAQ{DgR-U(RF{8z5Mg%gsUr$`I#np402&$H(5IeuyZ6i?LE7Ou`zNA z&B=-wr+^RQ79nLIgAK<@%Bj$9Ffe4%V~B>HWA$W49+eU!pLU(AHHE=kfa%N`o#=DD zTW7Hub#iMrBQ8A3Mc|{e!`Frh@Dd8X|I}I`y&Qya;S0>v+25yXz`JcZD>?y%83D`# z+D@lH`p+LMf8U_pD6|QWzZ1H2Tuz(o8H-W*ivZX~6u9)!$^I#g0olmF;ERj5xA#@a zBtAub6ac^v9smKaZvbjM>)(q5MTlNvE|(0Xx(+G-$AR}}x#Z1L>g!FnJmtzrw1xLv zoosa%4*lmBzCV8sdF+4CmvyfXgL8)=nFuY~07ihvo5@a`@|X`2jN2AtXae%M(2&wi z`M4Iw#9YE0)3@iF7RI(8PBz9?Q^l);-mrl$3g|H7&h}gU2X0*kIa0r@-2-fUW#{p6 z#H_&y*OJUboDBVHcIBbGD)O_Z>;VZ-Gd#%z8UNJ;Os3G@m(!?7OEO;hofnj;f@FMr zJY<&LU=z?}oU|W^%bl8?WoUZe<l`)pVrr&l4FawFAx{dte_#cJRuT$rZ^nx-!RrW_ zLc3ACF47ds(q>}E(FbrTVlqs+8st)I47y6~o%9=5PoK0vg8QC&wShEzqjBlD>zP7& zE4s8!WXrTp<U&-+{977YFz8^~0>aR#Gt?JcF?^8j|K6Kk{YW>2y&}xOuK9jCpg1~w zl;O1|e$H262Pciq_%RS0>FZZ?73DW~;pd&==w3&3<I29^>oDrx|94{WpMTv8GC+Gy z$LrCpG8q@`Q@;(qvlQ!MU6d~$T#K+{_ysdp54k$-j0&Y-c*xo$^6mQX^Rv^vXjfln zN$cqu*KkTOSbS$h>l`1pc~}i$Y<ScWN+coweFuKbt6yY(=-u$-%CEHA)q`W&Hzm(9 z`&8~U;M`SU^#Oh9GAagnzJ3X@B(OsTmQcHIbo=`UF22I-nVI?`opXc}L%Mcr#8Jn< zi%HqD%Q=(YO^--25oZvv6PMwTD(_1{$C~m7Hw{DLlUW7O${}UyN}#`jib)I0bw5s0 zP@?M+rUK}`$&!qwfI5hiMh)kfl_Z4Yd{c}YhUB~e!JC1(?xGSv<fJwmFS8C{93=G* zYm^6J0@l`Xe{TXdyE51eH#P>ASpAQcP=kU1*sL#47N?sJPnLJPt_rFYU9P;e?^&j| z45$l=w5Rk$@&%HH+LZH(_@5V-HswocaX0%z=7JA<a87#E^}XdsiMgb>aCk>z()_nc zr`I+dL#cTZH}~Q4!dMer!iRETRp8=zzxaukFco=ZGFZ_T)TR#|N`!P;9%;iu85%Up z42ss-v-@0&0OyXz&{7V)WNgAxL%u-ISaA5Qu1RHBf$T7ke1F=o6Mr{4(h?UJ*A>8- z5d(avv@`-+`*&LJ(6jJtRu@`edCPi6uJN{O5q(a0zNg!CCf9Uz(KX*Ol0z%6ctArR za?$bJ!tv1y<77=ya%_5NCVKSHZ2lZ_{6llus|NwlGrzdWzXdbkQugMpHU;_;Hs)VL z)^f9pt8pS@nJ`yTug%9JK<WTYM^0Ur?Z3iH!00;_DQpPrRGRJWXFc*<lkB{$nA0BP zXPgQFhw{L!%0%buqW)dw{m&b~l?41WAFGz(suk3|SMW`|^?}lOzN;GsM;QLAi@BW) zhjABceohCSjgvd%7n)yA52m{ezp;em4)%oUG8Nl(Sx$OoJ6$wEM3CSJey#Rr>s@Xg zoAR;&`T}mMq|aRZafm;I;!2Y=P)xdv_U*QEHxRdWI}fLpb*bsOWkH<$SwD^Yo6fSx z9#momHhi2J)U5K$gkPV1##d!)!J{j{Rf|0)%}vu>F$wc{xOUxI{ybtPo{;;Vp+#@b z2v`tsa79>h;(WB0sOQQGcXsy2Qs4siPM-m~N@G_85c%h%Pw?Z2E#4Yj&ON(I^sjeP zY@110tHs`u9{Eqg3;>;6)#`=+vP$<KfI3x#${CmR?6hOGdH%<Mmo$-j70`2Dpf#)e zXR!mE`xjP9K2jyag^)xy_WQU93{1Tt3yQ2&N<$C?4QYq;8gWw-Yr-VAiv`v~GYJ7& zl#(4DS@}CR0#BS+fkdl16Gv*Hq!Fhdoo%|Jt=Xx8=_err-A47v5eMAs1lmuxdY-v$ zy;Kb60FmNTz8(8UY#8(Q8<Q@o0@fnp`MN^AfD>TQR%=njH<zYBrfE#YVscWU>=KKY zK7mx;+7rqX;XRTK=Ps>`z{4pEVq8P?@iJE#Giv3?4{+1&Y@1}|CC;#l*I*T+%JZ*$ z%XUUq%aX0jX&71njsg#9VG_{PjMXE7o9FbJu;NeD78(2ENn3(~;(Fl(@bb1Ud+)bK zb*=`-#$?J_Vi9;Tn!nmenYz3H$JT%8M*U@}lXj|v`+G)#^^yRCZM*qo;ByhwmmW01 zXM_B|jQ-LxsHA>rAEC2<Relc*llS?&?hBSmB41eRmbCs+ywb1@SJT63`EiM_sjkM! zM-fV9*fk<VRn*vdHt5k;;1v2N*E2c6Z4J~2++owI5<4Fp=W^j5hkC6+l_aoXj3wIL z2+o9qDh$ax(2sr%=JJ{ky)&+5?eE>Y4i;uAY;h*WEs!V^!ZG{Fn8`<w>_1;Z=@%ln zqrhGKaL}(_=|B{pu-V!S&*sT1aB}WU=tlSeYnMlI_9PVZQ<J*DKXU$Ymp6ncbi%0c zKCI2+bd|g4)|uopu;co0=MF&B_jR!`VjQHzvTL27QUtsBphk3`VFO_YGB9lS?`Td0 z!x{`U*I0u;4gH<)y$Iy!;sIga2#<tS7Z%COadzNV)4G~=Gl{ENgfBC}iDM;U#otY) zdhsZ65AG2pb;+yEqHc%{mI#g2=5fk<l_gL0#k8Y*H<h{|T~(}Mn(J5&{g7+EB6N5; z#DxX=a3JP3IY=ec5pn+4q%P~g)p3Hwy~kkbSN-ycQdVVlL@q0O(PzwnC$a6AMd{4m zEqZ8C#LJtbPHX_BaIcwO>}g5D$*>9!2bO>ZH$TjKIC5Im4KXxe&VMSQ_)e>@#);Kr znF}8)$jdh)gGr<a8cYt_{x(3zj(_SsUt6ud?0sn^)b1bJ_%>Zm2nl?NZB)z&;eq=| z4e-i77Mgqi(@$P9*<`7gfE!sQ|4))wHx?8S5bS@OveT|c2~+M^^ca?G)1eD=7fpDG z2HuN}IGZH!64ne)+{6^G(hA|QhQa{osn0OJFj>k&4;BA04{d=P&-uVZ5Z=Im1RlD| zw#eX{thHI6k?$fjdyOfwroc*Ga_h8fNawrU`s(VpEE{(2Ib0p4JsbNLDx6%9SjKHv zRXP{+n6W|c8^q^PLYvKnC9zDYJiERx2z<ZcS0^4kkjirS9jzl!epyCgQA~!|1!vIj zvw?psPpXlMnvz{ajy?uGN{n6$B7r-@oSo+|yf?Nw>Whjb{tR1aDCqopb63YEWy<|> z%*+RMJO4Un|IIv3EMIE%p;bGLzlM+hBd!DkoUOU9-}Plog`~-&`0j!!5F5OToZhNP zq257wcZ%Brb(5$H;NcqW#FL#zXbUFQUNh}l>5Y*6#%qLdq|ib&3`KhDzJf+CBw~D` z(t5uF7DhJb=0Nmt48A>tt#2xlCt)#%sh-4+g=9_lqPg?Wu>V+5o~l8!z?s;Xw!J~5 zOfh+SD)#f-V&L20O^Qnj4(9}a|6py`Pd2XGdhjC;IGLT@wmzi1%+SzApSGJJo4Ipw zgNYr#OB(77?Z5%QT`uQa7efC($khM&dB9xM)apfY-|Cdl_TSC||H9>hrb^?33j$^M z&{+*$u<7nD*9i%Eb{L2W!M~e3GR{;9&QDDWN}`>X+eRn+^@b&SThaWR+@MQRq2v`2 z#hcGaw=*FB;#wt`EU_5*oR?0Ln@Da@=9Z7pw?M+`+^Na8^i5XkSP4B$eh+e?`lGSm z1>5BBQq9mH2RyNaxIVI_n;XeHn{<9**YPUD4iNzbh2)r6^1^Lt68E6Zb6JqZDpxN> zpJTiPsaV900$AM1aE|UcSCFBt*+W55*|0+C-kaZPSL^jF0OoI(-jDE)RzC1+PXK{+ zAV2z6`-=Tv`4Q?%ex$Gcy}xb&3(4#ILG2)8{FtSwwKu3Ui1_HZdL^YN%2vL(=;R*t zyL!}4P|#SA7aLwe>f22-=wL{HG_uzeT;skF3}YuZ&O~|2+x~4U&Jo8hALsM~=*eGi zHBnZ&n0DS0#B>Zk7#Fa0@x+u9i)ZJgPnL(}bhyd_Vo-EC$BP4E@@6J#_r&+q!=h`Z zpBNfVW_O~4Jbbx7QB+U=BD_G(OL~9#bK>I)2P4LDdhEN)iKKXiOt-))x3AgM0Fm@1 zJSW!s{QIRwbPvCb#D`bG8vkb`{t}T1q5A<?5Y;MJ01-aF4ENmC$ysu2sfE@g#G&20 zMwMY_>Lg6DL+x__0$n2`N%`Zq%Gv@FU({=gw6jg5T{7m6nWg|@4nf*Nf8h_y=3wWc ztNV(|d}Aj?K%j+Ph<8tIZ?B(3{(CFu4OHYA9L7lzq3k4p@x4a+K=NUibc&ZRxV_j; zLXmAOOOJ0c8o9NR6}7BED_X2J@XFu<J`yxy{<`clXn5nMQN7N^%XV>;nLeNP+)X2# z5bLYQSIBAT{np9Y=#-%e$5xLba)s#l*vBXX2duQN(wCTF?{oX^?BC`Xhk9woLhsI& z{PQ}nM}CPIieKKj`M-q|flm(_-CBzhYULGVG3i}A^Q0oZmmRReU`Xdp6pY0U9V|k2 zybJX>_+2lz3olLXd(lA<=L1k;R_gZ0(Y+}c(j??V{7R2^>@pUXn`}GUeM}h}3j`EU zKlD#!@UkkyPIGt(g<JTaPSFL(i}9Aax*?o8u+WCmPP;;LGUR-7R_0hfN}Sw-<14mE zit1l7hLWD|D5&)wA-dnOT7Ba|Xx2#tOa4Lq6oR$&g^jA<oXAnZO~+jf8750da)Gqq z@nPzfx79Jn{Cum5otba8*MxApae#u@RjwY;{gMFK?|9IC_5XGTU{Li!QBcJ)#C`X# zK^4>|C}}Bmn%-`pM;eR;6E~$;MmrIr0_pxynS(g*b#@PN-BO8H0{zC9iIPy7!V{3+ zB{H}15mFS6C}#JgvnRCvL$=mjOW|gyXMDMiPq<BR6STdv66q~im4|6LkVpxAYe*ok zXye8%*H7-cTQWNJbAjn6zDpFaj?*Ow?mdsIYkNB(zcx&kfT=XQ3qsa9MedJi<ZHyO zgP#Uxqv2m%oxIStTeWO-A++iXr3_NhGLFbb7WDa)FkPmj9|N2SKbQ#saB>*oKvh#& zrper%+PVfakJo9L$scuPt{ms7WUspD+Fp4M`6Nu@d#O_sW#LRLa1Lm4m2Slh{HYXu zIaLb=ZrT4QpKW+iqqK?cy8P$3w;MGc08DnCC_3Q!Il#euP59H}r6h^!<Hteki72x< zvXy;ySYq!=`eOr4cTlMjj-;*<u^w1RNYvVg3{LVb?17igjSdN+;(Uu5pRzmbeO+u4 zkh@-f8x(Y9-n;37Q2JF~Qr5Z8>}@-orG=(4v7cBd7!JCl$|9F<R$ER_Udi0lc!`J$ zCaJ>ygB2b1mt9z|>yN=3$u>C5#AMkiUzQ$$mdyKB_b;*tGB%b7VxKgBAo|jxxGi3w zR_$Zt7%5gqm?WC-BUDD?wUMv>*%{m|_$1RK7#)Rn)~HXFqYvTWO6O$Y?i2m!;%Xwj zV`eGFzLl8LIIA;cAL-Hid`>Vwu}Ial<-!aQP8}4~+Sv|+T-k_V6kj;+Y5fBV{|>Zh z!Kp7~yHuH(&A)p+FX`>(%hcT4S@$~@QwY>AzIqpEXi>~;i1RIL=!92?PqV5@&++lK zjuVY2yKO?u9@^0BdgqfzRGBgCiM*)u5ku(P%9z|Q8y}4LY^DStq(3ELvNYV_KJ7fi zcz^J#!5a)jYkv)+pPV!#2ty)MM4u-g^fsnvz$uz+xkJlC>UUFBp-crtF$FwxWl5@) zqTnhy+r5<BVRj&Rh^LgMI!3=q@gSbx<wE#W;4~PUCqW!MbVbL%Q6~Vm-9n2E9<UH^ zeLtJZ8ln&GOZll)Ksa;W^pFt(?{fK_)La6h7QtZ=?04{9HmL}4*o+2-a|gKD5j*YA z5pDvOqtbEoOX|KU4WCNFj5@U`hyTK?1UY=x0U9A^FTI!l80Fu|uGjht2lfZ`SB!t2 zXx-m0K+FZ>!0bm?EF>gB?JZh7NqHVsdLD&W#_d0=^e1!xx`;fut+ZS`<}4;@f^SHf zV2szra(l6_E#d%HLx0qY>6qy4kUd4CO=9vN_!}`pc`EwPo4YEr&P`sCi6_nglw@xy zV_`hCuF<`52J%FnUAXm4{#Eyd$GbPkjrrx5(O>D*QiD9E+TUKaUxi3z?a~*(i}Q%G zHXmI=JkuSATkV%zolKRiHTe3_aS!S83yEuXS$p><Y4Tca#H@7qbqeqed8j`4Cp+Ze z<0N%&p?JU!T5<}^{pd<sb%P-eF0!AE@2}M<fHuNc9#YKR-%1tzZM6~?TVLI&QK3*2 z@~(~x4`QGTaYciE16+E)Wd3b9F1WmxP1NE>?NtK*%mrp&@I7tYX^RXG6G%ZJ=a$qK zKQZ77k&?XK?Nq**eY}4dbrgEP_id3FZXT(tYfov$R#Njk^;ML7Kl3@FHs=J>Ps_== zmL#@gB8IcZ56wZf7aJNvR5%Ti5^9n`j9s?}p&FA5fUmMmX<$vLV@;Gy#xx4Nm4q~D zBJUoO8k7_wlr|SmkZYa~({b-T3o03IZ{7#Y${axLh@vaWNsbpM0pNTF0Guzu`&12R zAfrw%^77=yD4=lC1B*XIi{SzY=&XDbUF{*hjzbSHx5Ez6SquD>eqt8Yu$=jO9znDP z<hM#DV`3*m9dfH~zp^%89r>thQt|?_V0v0C<nL2mfi2Et%XM$Q36qpm&_q0b>fde< zJ);)5$}#liflHGFtjQK0m-_Eh3zTE=FXdQ#xjf&$I0i^B$C;YzU=oYzBNP$8>V@5b zoS24h!$b&0Rz~7$e<?sYQMuK5Bkm$jY1o~HMLuDI_%0a30r&M?{6S9g&YN!lvluaj zmMMyG?5fZ7CPms>KOF8dG1~$<lw0z{?3e|dx=B+mH{WLR+c-%TII(=i4Q#ORfYn^7 z$K`R`JE`nO{#gZ~w}uu(&9t2IK}k^zdV8?8kS3xJ9kiQk^u+t<nu}_Q`B|BXPi8lv zolC5QW^wm-xnc<u4JWu1iI!od^Lvyz0pDB245`14?7D%mn?&+I%MiG=&U`z7{zDbb zAK1gv(y_*0pyg5f9D=|SQ<aNUU1R`3QX1U=!En+S7gw^;R854eG!+_MIW05&p6#Xd zk`gjjaDKn!SRg$X-hk5I#|BioJTHeF0yR|e&ymtgy~6quomr7eqy7(l<HHL;|Hltk zv|v9tvLPf#bZ7yd_m)sDB|W4nj8i-!V)V^N?vOrvfz9=*`zhX}6lAa@0FfDxlYISY zvI(=8H55sRB^{4K{xzK>Nk!g(1%BE$P?9QXq435kv8qGGs8(fidDcWHDSnUwl{_<N zgiGI~SkjuD=G0pq_Uu^!Nbn)4rL0bNrF~^`)<>Qwsgx`ETj6W6ExAOq@7w|^hX?Ro z*uKWnj0K5x3{h_t7)(F<ntB;JdRz~MfAQSBTMF3ggP=@*V;r79TmEXKxul{1>K*6V zV$Nf;KpM14?<R+GL;h72t!A+AwHf-|0k*2*{Sq#G10}*m_@9x?xpWjNRAN3nv3w;t zy4(c2&z+uMXCc+^vMulXG4}N5*Z^{YhA~B-A~H*u@DodK;VMrkjZO~$i-&!&G$z#G z$Nw9X@LysQKElk8e|tATp8*))Ub@3rOmRUP?r~NY@K$qVOKC9a0<Y5(0O;o4(Ox1d zRl)cQajGvZFAuUSiDMzX6in8;lta|XrjcTB$;sdu!XsC7Ayc$$hz4mPj5&RwUgm8z zvNF@TyxWA$S5j>Yj=Mq`H!^8=yoHGpuWCR8l%4dq)y_7Cx2@)vN~em{tYIEz^|s$V z6I9c-;`i7sV!g2J!j^~EZ`yC)uPy@A4-ekV7`i0p%&fc!20r;+=vMdhJi`_@6Q43` zx<ZBpiN{_#&lFB3(^wiVf3X@kJ~%kk?b@(KH37rd{>VY1cF^{dyv+t~bZ0Qx6<4AZ z4;8H*M9f*z6O20Q$l$n8Ul{9VdLi1>3BQ$z#e(f$*WWKZWbGUHR_Vis&v4px))4yI zjx#M*c|+rkh5;*2y=Z=woYral^MZY;KucaK&=PF^|I@DUeyKny$kMpr2qd}_--U<M z9ewUo`0j#WxiPr=c@da!M4vqSHno}fMD$zwG0-DpeLPp+O|6)>k|prDQ(-p5N>pHp z$ds7~iEi+6sAA`jK;d0@9Yx0}?@(Wd6V38NGmL{#I6o}yOB)M_OW>`}VCj|iJaUo? zBtm=7P>1n-Y6$?LV$!HCB3Qd69&i8EhAP9iP1}RVc@$;I!@Qx{Foo#0*nEUJIB(xv z$LVAC)nDJbkUBpR2T*g<<tg}(j#tk~l@NpxvVv2&4l~Oo$CRivm0@4`&ak9*-0AC3 z=Bo8~Csu(YIXd!6XRa_M7}lI51zi*qO!OOa(m9AC>1!)RUtpEzi~>C0x<w;R+~fAs zmWM1CPU=8FgOv4mpbZNLb7f$O>|8nc_wf2Zk>3mEB@uuF@hASf;t>M+83K{v03rc1 z5^7h#*{k@J$YzQ;Gcz}}@KpXLj3R}#*>@9#&$tb--^x5LYl2%|7WAqj*uw60QDKtT z0lqEPhde0{4fPy|DLs1Jt4-QGwQ4wSWH5N*Q*aUhBaQq3re|cPd?)8CqcOc&Sq85# zKBaF$YebhXkmzU3Sv7j8VPZsbbN&8ONQ_=6)o(Vqt>>4&I$Vniu|rI&v_gLw?v%K2 z@S`)P^@0;*)c27G&25oaekLkSE~{l79{$*X6g4V!T^zx4$M|Gr4oH>XYW2@mxhAL~ zv-CwxDyT>;Uxr{8=%D}`wp}yK2O!8=TikmNwh4#7Fn<d#SZ#b`unpgK)3R#)QGj1{ z|Ef`-my=-^Ya;r1tgHdH4p9nN!T{6>%=K*l$4k4h{{L+n9|F~+m>a=t{#k0A4hLXZ za)?qD)-3I%?w>&WIe`sjN|Sg}Gty`L45ud4#?+!y^=nA5z96vT#xaeldsRo!5*m4@ zWz1~LQO8LWYhv)H5N~v$S*@9X9H$#y;j+f*6NSoIM&*1pV;ziFSPUfi9HEexrqnPS ze&cIs4;Lo-Y0I<piVMxNA)_oMFA+)v(Ye!zV&+fC?~zj^XL}w)s`8Be<qJKX-^!Gk zC6N-;@OlYJfMjHy<x||bW-+&Gy<N~-zbc5Fze5!BHP^qtWbK1fXu+)KZ%Okygd&+= zEOQtNtQMLs=vspnp-`3+;>)&YU9S%yA8~Z*K}9BiH#MuviLR9jObHu)&-l$AzHwVi z__SfF+LJe3_iGf(%j>0p>b0<D|7Us%WMaVsFC+E=iSdQMySm-a<I9;)zurE&Jj^-t z6NT0tpAxDw(Wl~s*Gt!zXn2GV63AQGH|cyu34b-u!Yq%&&LS@vMgy@lu_=2#_B{e1 z2pz|rnF1EpBki~39iGRc*Ew$Ek2&Yd?MK%S0piqDa8q<tnEun!bns!IEVvY?ce~we zWO+7U<^+ZN(k8eER3^l^Wo?hU2K1ao<hdCw_LLsrJ-Qw!>FWeXg5C8x&OZcGKrdin zwsPA@3h{bx%35a5=fP?trK%yXtUgep6@|8PyoM^n`s$31*#UZk8lk>tp7^#-iMv9r zLh!g@P?zf?%~_--7|gYw&*oDPh2Cc47`!6-i9RmZh_}YcL&bv(j*5ZtG$UUu9V8o{ zU);;6GWFCetM@_d$9ZT~;dmd;{<H%<rOY$@j)luy24Ms^W&z80UXtX2ow-kv0)`7` zY`?V-at27FZtJcPj!BJN#W0Oywwz$BDTddQ9>Lst8RMap@${iiZdoa6{QS7(B>`ht zC#&|v<$+(lU{#XC2qnEs)#L>;)!?EmMlFwInGy6rFvKNX&IA`0goOno2|_}G1}w=V z4T9T6GfP(Z>x2J%`-GDa+4+=|<SI#p4})dZ#05F3eYOzj|M2X~!4VgiFR1)XW=4H? zf=uv9b&5I;(Jy9+bD*D*wQPX!mpQRal|#YsciQ8Iyt1t6yTZ%*QlzRwliT9q>46Bf z%F6WC9*C|xnt362%!Wv0B)q!iFRQnW&H)~+Mky?5>^*gP(Q1>QL*6EA+{uLP6eVC~ zw5;C^{i%=`Uer>I$-~&YpLulMFB4-lNQUYcq~3$ceN8B9<84g#+CK|}Tu9;G{7I{( zmvOxAi-&phq6CYz;JQ>1#&#%oMSB=j+CkpwD{JJeIoN_`8S<7PZRE6r&%Lw9l^{(l z0g@k*?pOxek@Z|gK?9vs2BL)QI4WBTGMeytq$Tr@9`axP;x^sZ%*-6fM_KsGPz@tT z|7iJQ6<mtH*H{kgeuI^;LPH3xT7mLn+5a_jFd7yXv@q;M@yCDIJuK)MZnLoFxw5zz z>qH(DU2liVsvc0cjfgty^&?!&B_gi&%|*{fnGnN;yl{67?ZDVnW<%6LeRfr0@-yu$ zoDc;U>IB<CkEj4zu~9NJ$Y-p=!YZkvc#x{odLnR$O{_uw@Yc)Y3z>8{4ehTi{*MZH zai*wqOe`i9aH%ZDF;#iA^WRc8L>qp($<<Ojez&^t_85w>4#i5>ZL(|s!urSeXYTn= zRe$5J4A4l!JL3)Eei}5nQPE}7Hse#LnhxmXxKfe7$gi;p3NiCOSVixi<9X&;NHwhb zN$AzX@>J%;3QjubR54@-5yn_>jNU$&Pu_~tgGkd3BKTj&Ge=KQi->>Ml^z?9M*oNx zQ-Jd(TCHw3<ZZhyHOs)dTiy3WHo3^tkT5Qk4W1r53?hPAyWLg}Lm$%T8`q>T5W|b5 zGojS8{&wX5n0t?k3dUX7R9^jG_P@Xdym`-tfj=yW5So419702>*z!xA<|vQ?`_9l0 zCig>Z8Lh)3n*AKD?0ChT3jvv`%W`H5oPw##nTUPVpFT;N-x^;OC0h7!@*AV#5~$4{ zM_{OT8X}Z#Z}165Xe<Wd32;KooqHbMhE(Y($`ES6xVsP(78RkW_rSmdW;SwUO-wZ4 z=98iKVqk^N`fWkDRxMdHPkDrIqMFnNXG%V<vM`S$#4VI*)N~FL)}kvsM03GyT;P1L zNxa~>iFPQSDJ`oPCPtbKI(3Ok$wB*nWPNp9RBP8Zj36M=0uqt}N_WT5(n>edF?4rH zNr!+mh;(;%gLLQ6-QB#K^S;k{&iOumd;XapGjs3zzE)jp?X|9Ed58KziF6q*g(wI+ z){|jA<2c9GWrJrCMaM4iAUQxTXA`v=W>L}VLW`D#KO`=iiQ8#z>G{7Zud@3>!H@e< zGx8NSHTdStKUZ}`(s{Fb#Xv&_GEApe%S454yFk;Yc`QVR_lN;(^P^3){J)fg5J?4M z5KM~N`>c2W+-6u6(CNrDc8I|%*e4`af6lClGML<jH*N+zs;m{I$$v_&-{WAP1WT>H z9X+wXcc$%u;}`AWq(44U$l(%5U5$zP!HJia{7E9W2gFXB$48o?_Q3KUT-(zes#7ZF z|Nd>eo{h7~2T@VKioJD4YIKbLslqRB2HW*mk;6(I!&Vz}gBWq})SOX`ru*ASsrgHD zkepSNS3mr=>O|=tQ1ZS^3;)52^p*ds1gw>dDC{L8!*V_!DU**@IhA$jHs@{pnqN<H z=$7cKGJBBSsyS`Q2$Cj!0w&f?*VJjBIsLGbi!7Lsz0-IjKM4UdBjKAf1*kn_&EI)x zWrbAH+u!(4*mmwG>hCUJBH@jn&;Hj3>R^9k$C`KeIDgK3_sX)vqB!Q*6%8k?nqTNw zpA@#j7xmc%HB9t<5i%Z7Ms$AOYVA+&h5uJA|3`Tke1yeFuXt0!O8;Md0w@<W4?|Q$ zpOE)ol@=5gXeY~Ba}5M*1(k~<Gq}o^djr4I5>ndIF8lcz1REkxvltJJ?XaNZySQBv zYBY6^);W{QTJvi3QkG?zV<Lz56{6+@jR;Eeq8j>Rfb1EOyMiVOkOq`-k}_wi9DVL- zImK<=w?H>)p{T~ATt7~22)C_q@G!=4<BHJZPtsgZULK|n3;PG0Bf_eywdN;{iH5## z876_1##u8&tiK;>o?_x)9C~|HLCc<|D|Hi-Gc;;i9ULAOmZchw_pT=dYv_cAJ+3Gl z3o{ly#eT_pF}!-Y`W^lFH<vGhs;VFS177Bx1h`%J!neE7udC2cBa4g4zZw4i2`7#G zl(gH9)BmI$zG@lf<)k@R*xS{BW^=@aZ5G#}P571qQ6yT=0_lN!rv6;t&7`0a%2%@z z{(v2nfDcc`1pN!tYGA+)(t1<N{7ZNLMh7&<M_L29XZxw-!h+2qY{II@ooPrRRZ)&W zil<jnZsz~97pGK00?GbRlQzYWW`;AI;9U~>aG7t+YuH3B_B7+DQ(kJ@yLhecc9xJ~ z^z?7D>uf*#d)zb3FOmh4UzKkYMhOUnL<SuY4uQy#hxe7$x|{<HY2sDdE7puqd^C~6 z+)?DXJa}C9aw!9M$kMmqsC9|^)`T>|j^GV3QaY87Pfou7fC+I<MF=7%>cJVaNyd$b z2{BE@rRArhfyEDb3AFrI>qOAc5d4Xm-l6K(?fdOhwPm=Up}VuEc96GA8yTCGu&}E0 zX5Ei+bRXkf<54&x&}KoRb$;563d>K9TwUDsi^Xlad{syR<+=>wH3><JsJnNR`M;!} zG*T2!#CwRWbgb}SW=;@HblWeD4pNh8rRL85WJ?_nZ&*7WaE)BE7=#pxyTD8?TZ)+* z!}*f&p3aWBiHHU=O@fm~3MSB~T}IW%;N}h0`Y5crZddvh)yV0Ob>APQ1uQ2KEn?r? z9Q5KB-<h&%4wWc4$l(}IrwoXHBJ-k6=)tHqRVcA)mxl8X+obCvrTqwX*B1ogFlips zO1CPj6(OAHy{LBWt_}QZ13XUN^J1&c2UkNp(0MaFk@&bO?2ED%<9lO^GOJqT9;AbS z#nOq(H!^uWcxLo=;TJ=nWm}w8qR~IaKH~^GBKgg~ag-nn;7D4W8s%R&Qbp<&)}cX} z#Rbg@!+<j7_f#`Fj6YM=wkV&Zs=15N!-*kA27EfepyXpt!^p%F%G7OpRW^&BjIuI2 zfz1~NIpN+ga|bORBc`IKDd{*PlVWU`8}c3WQbLe&>)PT!hkzL6&Vf<K$==I<;)bt# zAh@;3Q8~MUn#w0>YOF>Ucxye{Rg<+sX5IYJcrAEL%}h_$(O7z31rixXFpgn{h^2+f zecRz*I#~n@)x<J>ILSr*cs+{+;z#VY!XW+%;=Jn(hlh1{vW5IZXg(49tWFY1>-bB5 zpaa=cf`3r{M{E7%S-L-4-R9X|Jkm&PxXRYtd1X*K|1upMUmVV4IOf$v64fJ1VHTg^ z7c_=W?|=!DdzMZyIgF|=8(;iljP(hRkt{3<=4~&#K72S*lW3iG_ihb+O4j#1VYN41 zYa|8l@lHj%M8@9PZ%8IxeQArCxXuJUuEm>aC^5cD&rNhQd&H}t_>|D$24}K}-mgZ9 zwOdE=rtFO!4VjA&7^79w-V+B-<|`K9nU(ga9LoN!c0UmV8h5-$N)j2+vvB{pqjeBq z+&FMwN82+EV=(%G>Gn*KA}AzMHc0gl-S3BP>VGVK5j_bQzi)TZdO3^k_NXsN0ENBX zFjZ!sm2RbO)TJFunrQs|!g4~9P8DHcHl5vms-8K1ax(UKP&^-Hh_x{5rgtngQ6QXM z$wiz9)nlXKO>7~rG}tp_y{Qcsmis8IZlUaN!CFr8MjZKTZ~wbQL?2B_K-M<Ld}1uO zkU;8hCOAU$3(}1jIj}|OGxzd}vLct1d~?JHaZc36QPPX-eyLiaY@Nfk`7OuuPiU-@ zoL0DiE-uzj6y-jK#8i)q1w`<k{oY4}MTWhh)ANZEcNFy=B7Z?w^YQOpxfTV(nY1uU zkp5$*5PcRzo|ln58r{5!1VRt+P;E0C9sUJB7v=owYo@m5h`RC-c{t<c1yeJ^`wdEv zE9Ex-9havj3Y$#Lf~HKvnDQC?7OxETnu!NqO5c_}*VacZ5i7pIUNJ=A_dG+c{XeF^ z`VRB%bH_(O?EiB>n(u*QW57&T#0C8WqP;GFlWjFbfVK05h;q41QdY+MQhZsMMe@UX zd+`PzsFIQnctvSctG{%-oaUc%<;Kh6-Kx!>8xL$0g?yTEEuRI=@wK^eL>Ffc=oefm z!Mb3904K9WbPSdT92^Yuj^xG18q&X40r)-`=`8S>tYm+`1;|2V3}UY^G?^8DES^`c z_1A8jOtr$>>Q{c*AVd??4LRhIA@`UPQEx}>4q9>^uAeYQzNxfg6Gn?_((s1RL$0v8 zNak+BBMscnI5c~ydNz-xco+-1k^$Gm@*0b#@O@+-RzOUnSYhCoO)=76SbyG#`_HZ* z`n-Ndc*pkdUw>l<Knz%x0&pShc6&dyoN~i*z|I-`niNXWRRVFzh4|f8PEV18a}Z;+ zDc+NdV|~I~d2=)S^Ucp!LCfa1Q|w0_<opEiip7%;)n8s)yp8FzZ#&pp5MmPlk}2VW z3-WyS%ss)cO3BE;2s7UO2Y=~;zt)2bpk+IxohsJ<p8=>X8LZ285|+5=?Rz-pKyOls z`Wgzo44(Lf7|1)VrjnY-hzDX_tpV4gyh&Lr7f3@n1Kh&1R^p)cNudJSe~l<vNcsET zBKbjN;GBr~A2|O)0sJ!x=-<BI`D+(QWI)PloNPCo7Xi#Uu@fbv#{S+hfHt?mM7q}p zHqa|{1NFa4d_oF>IS`=v_+QjNpQzOHiE1)`9+P>7KA0WD8!^QOi6@EsV?5Fa(5c4z z^nme_Bn;R&25I?Ef9sZB@@EtIZy7@LY5YrGvPR?Fu>Wt>k!Q~bn#@6|wnz@Re~G1) z41eQkof5dkt!L@_mxTdDgXs3wU*Z)K?0<w&O$3k@1B@BMg+jy;E@$iC|5e`L>StKY zM-9OL_eOz_l_z@ho)4O}k2M23>f!^|(<AHZ>>R1H+sf6fvsEnp3XDjcw~Y`YQFvaT zblcAY{JsIud4`wVNHHIR>9kJZ_Ap=Ihs|RoYL)B{N_mQj`HJ+J)fB<l{n%>>?vK1F zye>mi)60=0)bbz3;;0Y+Q8i%jLB@T|T%#JAp$~4U63w)`IvUUHk)1z8Lg)$>bJ>7b z@2;D14i1s3TvQej;ScUj8wNgE_pAZy-0pv0{X^W*LG;f-HS(K2Oi-P7>uiN_p2d9i z58&@`X>&L&jsM|%+<scBp#7I<h{^5wPX61u9tT@n*YzlIldYkYiQf`fuBUxe<BOBX zxb!1`5?D+h?{6xB!PB=#0wQwk@=;`>x!O&xtj@<@%@%6QhGgv;Y}Ul^x$SeQaWOH4 z2IA;4gMtv#F8FV^$rHI9^0~bh9i~Tt=Dr?rUEi;-oz0dS>!|I9`K~@cJw7PVF;Mw~ z9iQc8R*=hBi=5%r2&CmMB_KRe%Ik<HdGqT;u5v16*p94&*%QNHd3<>*L(4&S19xb- z11T-7-{lkgbX>dw8qpz#fAeDL;#AW~X1#n84#(UneV3q~9&^<;Yrs%)ScR77<SLUQ zBeU^SbGd^mDsu`9zZ2R;f;pr{@jKc1Ms%4Gk>4Et#a&{L>(<pp$rnqt<zIJ&HLAz* zoB8y&BMZeEk7P;(@>9Hr9PT(CO`ahxp(#k24P#nPn#KbIG6?!qp5T(^vW2M)H;n}m z7OKm#jXuFcN+1$HBof$Z`dc^wLGeZggnbVrTJ8s?C$ZihA*EKxmJV>m$HS8_9-1j4 zW1fi29Q!R10}RSAuZ-ARU$^hVRv9VQYS?Z=AXX%^b-i^3X1>=yJ>Kn{4GYLsr~{9l z#V+gQexAdz;B!4s2TnZ60%%tdUvIyoN5tclEt4b${0mEkz|A_A*=%{1%l?9oe>G8j zv&Y>Sq@-z+maIpJWF84Xv$?ouAH&ahI1^P;{)bhb^A&Muc8J^O5yOcp1$|eu&j`V^ z=&8l^xEjtoIRYaqnN${o3(!k;hLv#{@9wP|=)sw}gWay+*`3(<aLZx2gVmf=#OJ%I z3k#R{)#dX9)qMGyn(3^kdm9|=;L|3?!2%(6gTgyq0*m>wVaKfOu;MI9Y{|LPbrL(o zLS36G{fKyru(rL{a1@q5ygHzB21xejx-ro~+l0Dr{y&cxw8VclOietrQNF<>G#~6A zX=Z@EEy!Bi-rdC>WKVJ+6eZv|Vvi_0yZzm4FLx%@QrbC}SFyFav)Bc(KvvZqT&&(0 zoYFIc%6MDOR?-i;h~h-Mw)J=60lH<iEvEQi&QI;LCwHj%UIlU89+V@S@~el~YoRI$ z8HYA~ecd29JUm;uNZlnC2@Nd=k42tH-UYk&YxjCzjOj?aV7?;>^2mI(g@UHj5j&A( zEVYuP+81GyV&x+Icf8J#&9}QW?VRu5zyCmlg3p>BOQV80b0=A9#-LTts#}0Hr`h7! z#D6}=4R2EWv{dgvBPv?gK>h}H4mrbM9p+ijBD@x2cOor891#ys`6X7ArpI)p7=WZg ztK+NO`1Bf^A@Pzd`?XHzmooPQ%loT=LtVqQ-9E);b0!T5jC2c5<_i)Iq><8zN7M1N z>btbNT^O^`bytm4LIYHvWNNVR=OTA4xss%XyFK%MVxrC1w53omR=SE;MFRIl;@6JP z6=I_5O$NzyXb5CrH&UeD*MR-7Ro%$`+W-T$q6TpOp^bcxwpTt9FeibedQ*7{fy-lD z4Pz@Efu=p-@7}z5BZ4k#=h~kO&#DP|x?YRey|k>~p;T{nufx1cxoYrw^4#zwkd71! zCzR0d$*vW*j|<#jp-qoUWVeviaX;~&gEo8cXt`{}nn7<i-=y0#tCVQ7LZ)bqzc|o> zDrs=~8av_EYkDb&yrryf8p*%{BW`3x>UVphSj7r^UX|uch}xa@CuHFQDHyP~V^0Bq z;Yv$L<QNEou^_%$FoOlWBoD%kpigin3Gn~Yx3J=X*?GAT*^hz*TdxSRw-inTzF{pw zARA~{RG?uoksD=Ol%ie;2^vu<P|m8Y<;+*i!#tR+=%LC^vT9Cf28MM~urp{k{w%KF zrB=eR`AsEVs_|)CtPo%#8aQ}|Pfm`TP?7foPzZMQHkIK_u>AMeN&Y%`6ft%O3I@cH zGydz~0Ny<!WW2b}ltT;fw5C=9eaUBE#{;fUN!9nFX9AkS)+u{&w^#K^V&y!C`=1_P zjr4@4Wl)K=h?YuEAf;{9%-phZytipf;mUlEGm_9T3RS+xNXHPqKZ&v$1B{PGMNE%l z&Ndmmg-e1%<Rs3B^$DN#x;eKEb(aGAEx+US3&jjgn=^LX5ah#i#E}m?Rx9{tL}dzB zi8Q%75jfSnT)m6WmVa@Ls_9?;sr56Jqm}+C4!w5+P97^A7*Hk=!GD`u-?5eI7A_e_ zD{<8F#P4yl5npLEh=03ZF}ytoDN)*bx<8Y{CVxwxPWoF&giii9;$)$kLYIKpYXgXS znVvtM6kb_KRf8#FuC>%+vxl4EERV;ny?Tx1y9Z#xfh29yW+EgPcyw^0+Cl?jSN-4w z^@tyN92R(z21Zs<1y!M|9yMKQ=YP)dEh^*(CmVHg22?TcQczOLU+hliDdYf81=Wg$ z;qL^)h9T%|?^(4xWYeFF%L$BLOy?YEV@|)&e~<WwV`=-{H&n}tlIB}8rI%~WdYy=d zZOx|Rl+@adobNcTy@iB@CtdcEl)PU~_Yk>Be;-lGSN!r|AG}5e*4XLulqCh+Y}@`% z208*DE$>GNw59*T(L5O=piQz`_aTuLB2O&zN7<O$di$-z4o57z)x&^G@GB9&@$Si( za%tXj?GYW=Id!0+uGYL#I1J3Y6$ueR#Sj3{Y^qvR6e&5sMt872ut<4_N67t-$r=8V zhbg-MZa`4nW}&Ba1pO5%YP~_zskrx0=#XGz_f>ey#{zg0uRDw&P-V+isyf#nE%$CI z9EN1|GAUP7FP4ro|1yrE0eVvb`pGU)?`FmIjrxB{ikcW8DMonermBdV%%D7^5wE9* zn(|K3tz~Z4g<2a)#Hkej0n6IM0C>wl)OXwM{>ZmKrX9COGR{v27?N&cC}qfk20u-` z9YOa1Gz1zx<5w7qrN)Hj$6JU}_s!+`xZL<pCVlf+^eDH>{k&U!o)s={prM;iP~X(~ zUP%kJ&233bBk^h`p~!NR+tpZxaNx5|W%aj%5Da-Nyf}nMVoI4V*4m?8bq$5=&jIOB z;m~bN3Q6Z!;eGSYk!~mETg@^((L_!gG88;!k=SP><!LoIYF`wBSm93mk#WZWM@NZk zV)Cy37ve~wpSc|-5I9E1h>^S9Vs3-Z&te3Af2`oY^mP&cHg`Vsr!9|PQ+$7;fjj(H zk=OoNzJNQJ2md^G#s39W%hL{;dYw^Bk!C}KRdzvv%KJ`Z&xeE9^Pb`d`wyFNO0+u3 zW;$9DYJ*h}C1?Q}f`0fo2h>W*;==3HK_iLt?S%-H!hLg1m&_X+93k{S(4SU*AtTtY zMAkDi9u6l1wKW!?s~gbKqxq}R>UVc0cS8?;@Y)+Q)P!*>rMr6z85r2X!N>WKL2ybI zjb}Qv`f(6v{od)aMA)8W^Q|au`0p`_OdtR<SC3C*Z;U(LIMZ(RuV$9=Znj<#NaQ^u z=eA=X+SgkVg5N$_Dnk6Vo9xu`dveb+lh3Os7plZopi|uaYf_8ja}~AWAoo<tgr+qW z1qBV{CamQ!JFMkEk7gRKK40C1?a*maJCJesUP7b#C@X@09z5@K-?4U#cCd9KLaodi zoUcMB$9C-?{!QIa6l(~MX>V<q<9G&x@1Y&U?Z7>y{QK^O%mg1<#V8${mMFuYIw#zc z4VSN)#K;`2<zEdv>raDi^U*GL6|)?RER|a#%yEwi)5C612S(;pv}BzYZQi}Ry@HcT zdieNoe1<w#^Awc-B~MQxU%rkt!^_$tO<5={yXoem#gm<YE-}$75`N*%4&H{1?%i|i zp7}h@1U*x>jjNlZYrBVwTe^L0U9^Yl11ky;RIvtsvL%DNYGs({Fh@V)2NY`Z7FpfV z{HLz=i`A3s>A1w68J}E^{jzF)0W_$4jnZHNpPT-6Nz)T1oT0m;WIV0Vh9oYzd+J<G zaY=X29AmBdOl{0S&uEa%Sy9f!qu;33%^lf~p2*xazM6dA375Nv5_3a>1$*p8PYB(u z8&o%uRX?HLD^vq|NVomH=TON`{|}tLU{2L)tGN1nGlM(Q2;EVk)PS;iy8pmbL0PBq z>$DcgwHt<A#-!FoV>9lBgHcOG@FSPCB5}S&;8e|Ll*a`wtj+bkGv&GRkn+hJeLju+ zMq;m731Eyu4MFud!obvQzb?^=c9i5Bn%~Y}p9ZRuOdkR)X4u9rrWL>F>ltx6wjMrY z?U@jGRiEHN8=gw_f{v6QZtCj}W9{Yaj{M8&^O~Lrl`XdQ&26;Yd-esKe`s$Ezg%ru zQ*RR#RJ=Kjwin%Co;b_k$)kLFCrNX#;1QWUtR~3g`0lg5?R_WSE8PfL3aRBYB@vG+ z-kE07*pJvL$_lVH+Hl+6Z^i16{u4L;rM74QM;($ydM8A}h<Hr{7Oq^Z9(4W*J75%4 zaJDt9f_7s~&(EJqMoyk*I1tyrT5CRAE?Jlg#60Dc0RzT}owY>r>Y&POjRIvlLx0Ue zRVMXnbG#zWy6EVbm>W_R{<$VMjume1MVGA<BiePI!&>3*{iTIKaK+X}jgRC@UzoSD zioY{crmt9MYsl?!Tge@eWq-Cg7<<7kqP{?0Sm$u6;{CWDBbN*K75VsY&yS!w95!Dx zRE^eBBZ&AUYZe`oK8|#lHM-xN?&`8}x<B4*X;0g{8nK>7X-02k;xDZ&)~G2~ll%W% ziV^s2$3^Y;L0m(Xt~;D9MEzd%yT-e>Gm6Fz^(VJx=O?~VMvo(3?3zT96R_^k;ljC& z+)d-`4%1V1)`$(i<mI}&(whV@{Ie(T0KDzH3?Fq@jGAdKdgAivbjPca%J1Df0vTbY zaNOmsu`uwl-krUX#ph2iDmX^eErTAbRSuE=Y^cgO`}Im}WMa!rK7?|KND9y`j#wdW zjw{K!6V(IAl8BQJWtOC?0xawN&C}W1S36UbyF*F*kwjj*GQOYJXau+MPn_=*i|nDA z$MAf2V{Yar$@>L(EG@Vp62q^X<kTK_rD@%_@3-H3{M>aMO<tK;HX<(!qOaZE)0upm z%ITtPe6yg3(N@54VS9mpJYbait?_F|B4Z2bs>3duPw8@-irM(8SmNa=x#Y2JoDVXD z_PxtDuB<M^c6!ZSIl}S99Pb@-=)v37yATE3I(qHVjXf<DB@cF2-pBkQd5s(xl#E8P zhvsT#&TGTLH}hLWE~=kS*JoVLZ&X^A87JXFwwXGPR6<1T+jRApvg;aq$%^1s9!I8X zl$e5suOG`P5&O`;*;q;ReYAXVxQ4C0Ww2Aig<w}5$-Uk-W#AK{QGSw6^E|0g@kklH zuAXDncPCEzSv0KWqCi|Syg(3h_hrxM!XCq}VSW5)C#r3HP=8-B|8q3EJCooBn>n8l zU41FGHJ3MMqxqx4?w^_O#3Kb(v^BBqzTR9VwNEX*rwpF*P^@br37SQZEfiK4O{aFJ zES43XYKv)Hi&^xvGVisO<`|1L13JH!7gtqvvGWHjw+XGFIHonIidRb^DPf_-w#T2O z;uQSxGjg82LhnU@O)M3r;h_AR>H(-Bsb?y6u`HekO%nvTtt2ojF5RJc6J`41SiOK% zFV@r}*PqN+ny9tWKO=Npp29CqjasfJXlINT0I_Ns33CR_6pr^skt12=MB&h?g3)-7 zz2|i5?ej2`ZH^}t<UT?y9NN#S4f-(0EjS7ba-<V}4ZS8{|5N$raK472>2|lE8ll0u zZ}FEXGR5^T-xg5i#UncM?P0^w<@tNV7a=yPhBuOe=I^|Zv>?6?EQNznOYUbfyGu<C zF8KST)tN-JnJI_eDZpBMJD3Sh@<!2Wr2>^29HBr&@m<-`7X8^p`1X=KsUEWz#M<mu zFy+=cKz3ftvpdpg`r?5slpVj?vtKF~#c;Z%!7H(gl)hb&#}Z^e`4mg48k-gS6~5;D z0Hfaw$FM@(Tl@T^hV^)7Ht&`15Wlbp;aE)=9YNfR1(B<Eomq9trwte!2WvOfjIP9T zdGCPQ*D7Okr&F$4Tl}=>I%x*D$R0=|8KiCeqQsDfQbqsmO=)z`TV5t|CDI`^gdX&l z(9Ij<0+HV#80orQ+!mL?ie3zPA*xIL<>><P`XQObQ|pxA4*%<$b!5*$j2f<hON^Fx zHVfl1uDmm>9b|ZLM}c=Tq@A~epZarQP>?+kuS<olI0UV|?E3YyFD#6ZM$i>iiXJBS zc*t1bOuPg*Vrk-jiCUptVeZ(4z=XBDD-{ata&(pNI;FTi8ei+*^R7xwf<f8yQtjbc z3(?ryc^K_D%n@&39}nED9v;D~+>SZ=qwnQ-&U>6=Zb?TD1ZVHKc541b$H+aW?5Xg% zNa1Yhe1OQsIuNBm1A@=vSXeGC#$$O{UKH1AJ&E!KuaA19zHT{SgPAsY;KxV(Cc=Y- zx|roe{2hN>?Q|fFE%4}c34mvyt*9}d{YC3Ki8L@TjK=qyHZe0ZQz?0;w3IP8IQSRP zJ}Ccs3J6{zJ~pf8e=Kud1`8t|t9T!d=R{!cjiPG0PZu8?tYX?EV+_x#_!d~yPE}J{ zY@qNnF)=CTekMIT*RX051)NT$o?-qw+Aw^!e%`=$9I*H|L1%n+o6`GZ+oPE~3C5{k z?#RG2uTSGTo_R__c9PkV6fjQu%B$N=BRS_U?gw2<6Dz42ji(-ljpzM(iJTq@(>qEb zJ)aK>USkXaIfDF1$$-gU+%)hes`^VK_NAC~@;l+vAKf_`c_5{OS@wa44CB*K%M<D( z<$y`hm-s<RSaqEa=s`n806HzB!zl%L@66PK6w^|b%SS6Jz7)GFxqYGkliBFGYvNr$ zj1pTW$li6A>rORG-t^Y!2AMs?5Mbfe7^?JXWv@>LzO%R4z`K!bude!#R5_k=sTQ`| zvNa+3k`_j?Br;Qj*AjQIX-0eQ8xf7R0uuT~odEDOA8H{-;OfI36j;=_yUVTJ%_Z7~ zz#JHPSTsdUTw>JgUPPBw`<DZ<V`jlQ+~$9mU6TEw=J9qRkk~`YBM|Q?;@8>zqWaV0 zjRqA*EEX&`M_Ams(%;lIP-xT2@Qjwf2<>(1XKi$`H=P5#?i{SFd&1uKT04Z*4wrU| zN=5*v)U0iFltK@WiH<gp8$ypJHRyon^wX$;qcK(GBrnpak?oS5Qf^28NgCWhfj9v~ z57}q?L&G|-_{z}IM==n`v0e80tC1jK6epl#d;s=20<rs72&QEx#uom)jm|PE#3Ti9 z(w1L3Eub&AIgV$`P|H>WQAhH>qK;lrWl7*3aT%k70=M7C&Tjt#Lt}N$J0uoLD|H}f zX<*LEG##jJS|6A^+0TpkIPkSIaq+(4q{f4@VWO(ORg^dtf5t~2ES`Tz5>7+w{@~E| z(79c+U38_)V<lQwZ(%P*hb`wccnj5}t65paDYlG4kEpbaY12N?bTEY9=&t{)2#YQC z=Fr${>$-dBw^rcZ{XS;8ZkcxcS~iif)3}#4?aJ8}!#4d-jZPV@yS}sR+QdW{l+j9k zg3h#}*2AWVabJI(2KBpkSIwT7F8^>s9#IZfyj8QhpqbGc+t=E)0#BDc2XG+{wSJD- z*hc~ddaY#I=lJ1}u=Cxd0i^|P2ryx?A;oO6R@0EJ`2gRLE9|bhXfmdFEwr}Wi9KI< zV*oodjpIPREJJ?{vrs1gAwKOuNQ)aU)hRLxSP6|+XGwq(4JV!UV^aZ-g{mi+QKc*& zLUXpB{+_;hNfFS`&sO|?Tfb)*U1WE3Uub5w2e#=*ISm`KSb4cZNqEK$<N`{5p~saJ zDXDoZO+A$kZjptz^IkbU{v_MIS@R{BDz_dFyDQ5^m|N^V%cjF-z!AMeBg{;&^4i=T z(`ir+9Pp~0mir`a8wUHZI*xXxH}aLjoGg_gItkg*p?yP!W$-mDJ~9jXw|AB)8~_Dy zQ4wSZbwBkDHY{w2NdQbr+ObFIkuS%lR|Z2ywe_OF@#vHpu}$QPCs7|U?n3FWmb}@| zC90FT5iNW&*LJGGd~ZpNSRR{e>v968;hg10KC;2qe_Huu)|tjYYdh<;{ef;}IDXz~ zezmS+(kaC9>~RT8Q5xbAN{1sX^L8-|UHG<lbbR;hY5{2a;z7t%%E!0V5L;yiJ_z}L zQ<gv;37{}XR5pFk5qY2G325idrlrL55$Jybg#V^p!Pt`a0mn#D6A5GF;2Owu__F03 z9T&U_VvhPJN1CI!z~<Jh>w_9-v1|vZ*2$^?{#z9hkkGitP9G(r4T;+Plc29osWI<& zxAy`8Rkh+r=~9*yZtPa2cYr7h?GFbbO><)*bCVSQyM31Wz2b)R3^`<zH?1>mlbmB= z97~^|o_9sT9$~dhpRDDI;T>X0t%Q~_pUafu5$`D)%>iB)E1NDWUPo(H^eVoDQlXA{ z33{Rp&xhAcbWh%W)<kD1p9RRM^#kBTAyPDEkcjy7q`GT;4^G`2MA5|eDl-WMG6|<u z4K94w&{|jKSO&;ZivZ{xdWT9AVr`r(7lZ?e<I91Q)5I$FK04xynzEV+5N5%q!57t- zZpnU3gKw9qc`W(4Rr;+qtHtB&mFT)0(g7^f{#0B52R1~P?&nV9K6?6{Jw#Rx)g*^j z)J6;O;h>>uy`BW-L~|wJZC^YL3dep3XLY3m(+j`K?1R(`ThoKe_46w9ulKpdaBVY* z=$Uo%-YSiof{T!nbU7$XWB&Fj9e3E5Uvd21IEUpaHZ@yxn!>`V3Ho%|mkoR)KT`=t zXK^a<=`(mP@Q<(s8l207&dGbmL}~Tj>ws8?GSk@dV^ubeHwVh`Y-;Z=7(E#>l9Ohx z7Zg$^$q<sKO&5OqpZD*jZ3|a9-0{x7swHgXx1tDh_w<=g(Ab?!zhQZqcohr}2jp+0 z3AhfcSPVtHmzvLrwb2>dXWw`{-8kPuH?qucf-b^`8`ymCW_I9e{=}B-<)IB8Ea@22 zgqX5?a{`FQ6F!%#6o<Usr+)ba`BB=pDvCIjxHRDDybA%>qYF~Fl{Y(`c@=~`?#zN< zCra9Kmy0fKqw4wOUX#}*iysz>$Ytx;CJSL{)@`O}tBxXI`1B8r38x>lO6uR{M1A9) z(_?z-4+%TIv8CtthIxG9Cff=kf3M>eSX#Sw=AUVC);Yae5TEMuvwjJxS`{ct_mh0c zpFkmXPw=Xy%5B}YVyMJR*g>6p*lj4k`^B;h+&0WxkvjN)MW{T_5$YDHxDW~YbA&3F z!X0~ib#&w~fPhBy8{mt_(!3r`Q@^p<0y&2tJp1K_1BvYpu5jCThYb-+Zby6-GJD|) z-b-cr2wE;#EA8k|g*Nkp80AEM5AIk7ozz4&7QL{+RKBEohrP`T{Qs3JUERfW!55R- zEuIh31CTV_<7Akge5HbX@&XtX!4~-}$x16|b7dmqaE--$91fkj0nuH#_Y^$0(_!JN z*_!j|df!Bau^c9>`goMZKlx2HZ%><DvP9N3a)zIGUP%!Ci7W1%=oKl#M8<3azKM*e z{1w2rbhSFskaMkr?f&+}GcQn3_*fYK?eo<%gOzFpvy!)VtvTNX0sp`hKmQpzg6@Tx zjHU1Ep$`0$ewzM|OzW{6aT*09792VjSZFr3!%J`Tq3%E9*_P%@_+$t1b5!e%s$fu} zoRK2nyE_w?g$Ro~Q+E1#=0{t_U+;<Qfb5Nl?!NOP08N3RE02qbBu2s$Q+b<35u+{Q zvb(}nO%GG*=Z^%!7B07~GBE9R2T&QRPPM5%z@Y0Tv2-)(@jTU<k&%j(uryVe(4i3S z9KbIN<18~Berkp@DcCl&sJR{VXcXa4RqQBN&v`X%uYwFzz*ehHFco@fY*4;qvW`N1 zaBClU#|x*h$f495Q8N@FE6-F_Bue_hYSSOv+QCBR?V8vX??x3P2|4Jzd$1u_t>=em z<Sn<V!LjOaoc&%HoAe2RFGX`qA(HaSwdU>*Jdi!WznNla{4wjUw=icA^A4w%|6CW} z?Ou`njj3nnlAV0#8mR%B^R~?LCd6oTfdpLRYEAe|g;fmCv<dlIsWh5nr8&UrAQM-{ zzxZ)R0dDaB;VtTs#l5sz?vMfMF4Y_?)?UIctwmbGKdh~)-YIr_YRjw)FM2FO+9+it z{~Uuj;=^5TtMd=0xVPiESb>Phz3#%BVz~r{&uWR+7rY}~N}^q16xM$@nu&n&meG4z zT|Ect^^{^ZRhWlbA?Y2!a|ooKeJR-Wgvi+|1^TEj$>J`6z)!1Sg%pXijiRarblw+3 z70a{V*(owv!{>IHt3Qpb%G`Fd5=gAfr3~x3(I1=5#zGrgwNagwU&rHl?~F~%&C05% zri#m;t(hVv<<0N7|I@a~<N8>4(zy0>iBdsaJ`K(YrS@#yKe9`JpPd7+PMO-5+UBup zI3pF5ms#v|mWr91l;G)xVrq}mq4DC(+ofnLlUBrrnNZ*P<`g6T^Bx-`b#Mnhq4LTU z+~})c<t%OTZaX>9`3kkd<|Kc`PD}@>ExolJpuFIR+u@>D&j&$F<q272o{2$A*xQtZ zZ+Q|OkcIl9!uAwjn;bWS4;;g}%K&FmUoja*o=r`QUW8;>wIxHGTkc+kaDqoxso@*x zwU)B9dftO9Mjc;Q9}eu#MHs?p^MklOTh%+R+!v(XB^r{_lGz1&S7){ikN3Sp6KieR z`N992XhmeQNXfH^$88iT3gliII`nEt6eT^jeCW7Z1=m*hZ0!edE9C*SYb>XQ@=&Yn zw}v#u!=FV;#0PVsVrUJ}M8m^~w;X9pn#cZt4GcvbDTOIk9zNdJhy1aFQ8}tAY1Ujc z=4+k~a4%7odojeFKngC|wE5Omp;7-9c5b>IMYD%4fNf#(Xz@H74~>fhal|r&M%($8 zboUXA#gV+rgLwsigf+!_ag*wIL<X3i^Ou)GpYwzyAZ%4fMjxR$5IcOf!Wgy7fb4Hg znwLpbE(ogvn;MJUD!E75tD?wiatP<mh$FCkjkb3G1ZUDSIF|^W>h@YGt(idfLPEZn zMx-zt7IQyHqPb&~I0va=@uvYEuRfRuMb{BX7Y$kF>Lca{y$CyEMw~1?9842v!7SdJ z$P*^uu(U5SZbI;aH(_NppJ5!oohPO-L0qUstGyK~e>HM2U&B$90K}7eST{<#1M`uj z!X~So->DJwSi_^nFw?)hv>e9J!AB~tZF?;NXW}ooWDE1mCcJn_n$&;}xc&@LPIx`u z&0JV|V2vVyML3gAs}$biz`dYHyX6mBPJyJK)R9%`mLk&<K<AmlM%Md_M}_g^IS`Y1 z2?11efs2?}fqc#k=)-wnd{#vXWx0m;CB;~}mvCx|6p(Z5pHfnlCRgT+W@x((j9rtN z-ks{#r)-ofX}ZZ{lj?H!FrNAvyR}LGj*7h~K}fdX^CBY7UTneSAdMw6Z7J#Y&Uyq) zm~#a%Km5?}hSRH?B>cg>$g&@TT<{>HaXE9kGK7mW^vM#$x_W=O$U5&ZDpVpkZZi97 z%_-7y;v=}Bj%Ug+MMJZIU&Fw0i=WNPw&|==rzc)#z5~+Msj=U_pyGzif1mi0k8gg; zQ4G$6b-8<bs&oTp)s7`R79~WvQ_Cqk1Up?<32o0+XGagt1Th_5Xc4p1J?M4_Zu+s* zUA%t>2%oy(q?SCHl|oiD)L)+k_gPk{I%1`ly+ur8_~T*h>fQ07Q2nleZvG<}jrgt_ zB5k8VF$~)%&bmBv&c_4IYecJ!Mf%MPB*jfxH?;qe6eodu1DJ0bEI~v#A|HTJ0g{nL znik)JlfXzdqv+XHB(I%DtdYZ5O~>Bj^UgSrxNQEx8wWbMMvntA!$H_vruIsA$8P>^ z7ge&3L|n^Cj5{t$%7Dcrpo8%lkk(2Mvb_n@hXjX&WCGK)$DN>fXr1Ha;I-yuS%PUH z*VXS-ip1Z1_Yc`@4uQE$aLBfh1qdCOn9J6$Kb9uvkd!Th@POnwy;fY;UHZ#+?-&4M zfx8!+$s6<WY!o_;YU#;QE6cTl;XY+V1FC1#rBvE=SAo>Lwd;(qdazU0;pYYo^HfNs z0+KcuWi{yF7U8yy1DC`-8ge=-s#tf%{fk(%t&9LV`UF{}Won|rPC-a|G4yv8^g*`i zrL4k<#<+$654=gI*-}rLRmx$FL)zjhf=aP!@LEZ@iYVY#^LQoa6>2%~xF*HHgo(na zzWyq6eaLrxr@?<0RIXa;?T<X=cjNDp9U}moV(o$sJ)TsRnPzd)=^X!p)hmz7EDt79 zO-+5$m(fWuVYNWo%Sq}HP(z4KcRn`Shr5e&6`NI-$`RTw8S(M+<`BFse!b&%+sTAd z^fvshc~_zk`oo?3WHuUMoDmb4wXxtMY*2O!(&1R3ou*ywPW3akJY2*^rfc8Ay0-lh zoT$vW6AEOQw%oiv!#X$obCBPqpL?kkJ7FV>o9;o!XcMw!u|#rIxpztCaki)oiBrG! zhDQhRZx8TY+f<H8M{z((o=GmzfiP<`RfC}UwA9*kZ07JyH?+tnk=`HX!3zckXcGKF z{|ooujo(w?y|E{8yNAap=z>_uKIO3f$_?_fmx%ZEw*TZ*!-|MWJ@zPwpB4236&5K= zL`2^8X{+IE^T5pts^O@M>llATeP?vIP<J(Cb-2#4MJV7U(=Gyf5B~3^E7CeVEayCc zCuXfihr~+-VrV*#UTAbtINcniX>dAn-0RvI&!O<RJ*TvqDpZrNvDB*BKqKb=0i;R? z08CNnw)>qfIzjuM>5bj_2B(tq?NMQN^O<E$<K9TpF(7yU9iJ^fl#pvoT7M;sfaAS( zqjR=q9Ie_qz;wx17|AH4*%`)vf6JuT`*3%arBP!!*5c(=u|8J?i6#|(d9#(~1vhYa zu}1}DNn%xWJe1fi=H8Fz$i)LSeTn6C>Xnf;YuyPu`aR(izz}h`d0)?rX0yrsgbT;R z1=G2iMwg0;3vP$q-u>B%l(UE=)zYs%6ZuL~M7+*lgMr$OJNXiAzK@!<r9jP=*vKPL zsP!ADJh8ppSF70tmOw==7HX}}X7uOn&HdT%V!umLUjj=GA*XdKZTlrIjk0ui1Th03 zO{^|6)%Q1NZW>DTnzbZq6-Kc@a(udOAvS>}2J8JXA>y(2Iv3b$NJg8VeT(agWIwUR zdIuPTf^nU!U2mYBz?NZAir?LI!-L(>x1#S=8ykVgQlGrQC0g=*RNAv=i3MnglD5$Q zdS6>5apL1fobj+`gQtcz!*L$p-o4hP1=`0*ju<<Bhhj0?t3!`1UP{Lj0*X8ZN{4~@ zil^T{ikIf;(0b0Y$Saavj+72b@6gg5hxC7mb*-LmEXysN`m&teC4|VYW)}P0Zq{42 zU)y9kub7vcUkrhVIazwDqR^lNTeRBQM#QsKSAKRxSZjf-F=p7o^&5Pao3~-}(I+xa z#Ws39KgL^R&+5!tFrWkR3h{Mx3_lrSXdkiz`_7c9a0sDHYM7l7Yt=+Xx2S0}YuOLm zaZjG#7fuJ)FDb1CZ=2*BW)W*Pe%^RoDSpT0P)>K}Sqp1V>l0A1nyJ{Vfy|qYZH`AI zyBI<)VpCd(oQCxEy5ozo_U~Y?-ZzJbJPq#9`=1&+YD_z1n`O3@+w74})86FT!zQrT z$M(%v$n|^;Te2L`kxc}nS`cjkTgYm`BkQSJwwTJ+a$)x4$BG9&a6j(XpEm#KFbFqm ze(=(%JFKS7P3W$I#tZW8!Yg%J%%*KU#WhmANW*1BWh83MdgtaDV@rHxr{QPwG#@T8 z#Cy&K8O0fLanl<f&ZJNZet%hb^Kf-bhsSn;kic#nwiKdqRc#6H{>Q!Ot~PXlzu9Fk z+@N=w_45vkTTApznO^P2TBHA2Jj9h{c<*%0iBGFyA)9#H<Dp;{zji}+GpFl;!Y!_H z=pN01cYn%eC2>>s-~&a-mi?futks<3r+ikz{p1>|ON+NOAET+5E0)5)<z=c%`lBJw zb)~dGX=g|*8XAc2cE;bw9~1=g41CpF?Dmk~!@6oDR2%QF7oD<;gy>prh1g~-{n*hj zf((9F!2R7Dn4+n|G`{yk1{Ie*6`sFvR9vkt?FB^1Q>y!Q<z*%Z`_9<=n0Ey)(xg6L z^`xQ3R}oy+v7M=PFpo9Z(bY8Gp%L|mT21!{ok*?kRwY>u_$J=m$DEEeX<eiTi)<S* z7#E-YxjPbvy-i>-{`Oz(bS#9iECt0ibF$OdK?li^gI|1E>kbR6g{%eR&?PFEi)#CT z;6u@GfhZz@42Y4VMMH4vzUOC<f-_HvJ+Iq>)^mqd`r{eWFuH)x?a?y3oEn%;=Eq`G zr6kZ}*{pUlk@1uZQG@kUfErsoj9Z{w_NN1-)s`Y+Cs0Kc4=9}PbYNi;0r%@tkq9EK zp)>&oi5N<UsTGLcOsVczUd)AUp6A2ub3<kX4R5g&^!+lc0}<Fc$~NehOzXVOS>s*j zWk(#?04H6r*m@`2_Vm09j6O}jOT|@@jr`*%p-k_<G7oB7X<Yq=;fE5A=Yu~B6iA3X zd-pZ=+uLg7=5F-!EjERX(SI=2OKDl*x4Vm4eq<Fm)&jNP8GS{i1JVQZF@{Ug-e|r1 zqFK;E8tj2L(f4)Cl8K58N#;RJ=!Tl(6>ri<i>Xwmo89L;8)+X;aHna>-GyPqUs=H3 zPRvTlmntIETryUe&VKDnW-?57H(02%lN(z{t+gse7CiU8Hq2QD3c`~3trY4U?!ucA zZ&DZw>lEp9I_ZWTy5~L3ryG6kO|>T9Ak)Ck9lqf2oGCp>FMQllnLQuSh3!elEH-ZN z!pHu!eo=+$XUTZ^#*-@STADhZZr`EGf1q0O3>9lJjxP+xLdAX~jiB79L%Xf#m4*`$ z#vg=OUeU=M?DP|i*Zct|v8iI-L`yYM69tdq)A_jfRw3>{=jTRoQup<<p5`6jhwtk^ z+T)^3@+bq^=eIL3=z2*kr`(Gs1B>r#Ij0#0AF4TCcw+JUyx~R@kg1chH~gqJGJJE> zk;*ay35BJY=?9mO<92^;2N4V|T=%YlQ7Az3qG@REm&~6LQkYb(eS4=tsS>c<zm5bi z)8IaN|K}n>hiyJb@$<ubkG_J$TLj~=HT2GOJUa}7^JuAQZ$0Y01g0raMELqGV{1?Y zx=R$ba^Z&%Tn2WWUUZL4k?(r6*?{e`h}-IqmAX8FTKO5MYPm_FN9Z~%=ExE6%#JVK zt^k#&V~s9$v-J)I3-t~Ib=)EbLAhG>_EDYRv3Sl7kW{&Zj~8pK(iR$=h7CH%0J3I5 zEVM{ID>#|cX7xLqi6RijD3K$U07(nk6mCNfh$rbBkkwplkn(?p4W~TxHTbbr9e*Iw z+|g*0$qT4HHl{5k=5u|&?0-or<aPSpP!#6}e5IB{o_v<UU?Q6l(006fGZWSg&r4$2 z1!K9X_l)f6elBbuWPjyjXV71$<O9<II!cl_#>b=>Vdi<^tCFDZvEHhCg_c~}sM3=- z_RB`CA-9a=ywV$0vJ;QlF(dS6X=fcjC9glux_>sz!JsXJ#A^I{?-Y_rKixYX8`-6z zg+)3vmeSxqv2;yu7^X!(ceAlsU1yx{+?PS(z@ZWdU*x>B#w&2rx1`OevX+L{=HOX8 z?phkUC5do$it`?fv9r#$l%$nSkVl&RWL3)gsu+HD2fn!WZe?)^o@&ERhn9>fT)wky z(aBe=j+7=EZLdILiHP6~j@x-3L3GnV=Y(A(tl{0=e6zT=78SqR6<3)TlfVxyoX_=T z6`7m7MI&o6J|#3x9pKvvi9MigN#Q+;)5_AsNZ@R&(@5o8J6Q9l!bpF*y-U1z;A&+- z-Ia^tEnB`8X)n912>wPheGq5S5DtI(!JC=NQ8w<HK@I0-1t8U~iZo9bf3y;R{YpTx zDLmmMQ<CK-T3tlA`KSL$o9E<>xK;!(7c1r12%c~X#5#9%StgcZW;M|3=_5cUc=JMh zsJ9^&lxC$Rx;KK8&Lk%5=H3|vF1oTf!r!9Tj6%`4_=d(;J+<3|es#zPR#_bL-O*Yv zBAZ<}B^!7Rzuj9-Rrl_{3K<NN1-b9+iL@bMlE9*Yrb~2igIcV8Qn>7lBx0#!HIP(~ z+h5@qXXy43Od++?zyTu~uZ87vWa)OsvRI1ih^OgA?1M%=aoMh;YrOSZa3xjq+yI`d za3q-eWAfrj52y=>rdExE(^dsF;<}tTdKloQuPEbAWv7BZT-tvpn>Fn3#fko)+X&_% zVt^7<<CD9g)rZ)BK&%<C{$>%v*L!9J8|GeH6OaU%hJ$qLZEz!$o1MQ=FAe8_b)BAz zlHJp5SX!=KPUSl?Hig>NHwB;JuHuWZPp``q27n*$iu1Bn=Xk@|8oSA-l|s9I7u;FX zE;Qe9eAJm=6lFT)^KUD)ZwtF*{_P<;MR!PLv)0*O$kKMfUOmauc0jiPCG)R_3FDT^ zpRxZeN;M7VG&!$=g0>z&5ch*v`3UP1<E8zJi26pPkyPce4_7z13I>O(GC%Dp-9yW4 zc1T?KItqq(-F_*>e>{D_ygF5_ehl=9X0tO>psj@8Mq-JRIwFP6YB9Eat<+g$a`WW$ zU8_`7x{9DIYhNb1$og_{wXln5maQG~h5Gc+h6a;QU2(QvFIVBZ8dvCzXM@l*xTg&u z>z4fFYkvelOo!pF4z14)6WE_PMVOn64tM}=UvspBq6u=k>C2NFVO)ARt_oh`*WYMd zil!2OIc@ni<nkA>BdP({Yn2C1oteJ*9xUR<7;z&xb7Nel7Q`g)wYKpwfm+~ezISBR z8lO5^+B>v`oh~)X0LD`$;z&1ocd|7#4&Q@+%8&Zk9I};&F=T@~2Rbjc2{D|h6}6OU zhrrKu^4rP)+CMhf0io6?!PIKH2JBB}@)CX<v3_mF0)XA$7ZmP#Y?_0Bp7cL&A-Vyu zL2~O}De~{72Y+)yz}7O(yWXV}e4VRQSGzuxDu^z3)_H`xKK-I)AQ^toqdVzfro7zV zT&=H&8@PQqxgT_A+MfDq)pGvKwLe8)8xF&TXsDjnQCfk{(JC4wRyd&6bd+rv>7;=D zNf#!OVpokNqjb!0PY5dw<Q!#7LPf58sGdJ@!%t$@I;8aRBa@>M>7)_URIn<J=3B!I z1o&yy(ur>x=T5<!=&IA6d`>B^i7FT)**-dh*J}i%5s_i>>05O9_7chsB<Xa*Oeyfj za~MOu;>|a%iTGTvCp30_y`ThN9jS}+=Uw^C?>2Xi|CU%yieL`L{$tkvO<`CN>dfc4 zEDCete(+`v#7gT4VZ}33Il6y0+46m&SiPMq;(Ni<66;A4Uzb=Yq|+VEQoYo^z&*Q> z4{H^Te9D2}f$r(EK>I@ZL+LETlBT}EcbEe@OHO`9)rL|XlA3)1i#e-sxoLi(NP&8b zUo4b$46yh@^?cR)7im>6VU8y!L$#!7l6`vD*6jJ;u@-ADX2A5hUmRCL=_%uQv!ixd z2}~D6H0)r#+#GX1UYIrNcPhAFk7Z2Tb9tSld4t09mS2r<;-FgM=PdnR=$ZjMkJ<nr zdf^$V>mm|I^zgP{+w>Z%JCgR<vl&)={07?8{JIrb4bx<PA?tH|kZ}A3r~N~1_JB&U z#35$U3y)rZ#y7R}BHbde+{}kcmRLxUF#mToE(7qnf+vPqaQ{C<IS;`(Gn^(nwc43H zilV2VL=%H@at5}k+E4~sMOj)R-<l0>CIYlYv7v8bK!H|JYAR0s&M7OCUfy2)$!fR! z7wXve(&*EwFWZ^9I6<tt+w5PfrBj;bs<^jU{VM#B*6^>YQ;XxinCx)y{r=i4o!!WR zyRfZt5!@9TatP#~`}S`H2z<I`y$9fEd30AybBsyB^A>!E(`PTfm7=FVJUoPf;WPR* zBp+>sus44p(Ox%EbZRY@xz;9O^Y?ow=Cs`ZqEFPqqIIq?lFiho$qpqNnQ%Ap**I`@ zr?A^BaiZHz?p6g87(d?AMEWzl68v%J(g)-7)iMXL45ZY?G;_=&fv6^EYmDM?_V_O; zvbl<#9$k4O#eG!E^m@y5C?(AwP%PU$KPpwJkU;F*JSHwa4YcyLl$^f@ou9Hb{PuCx zJeD41)s)GLc7gk~BV6St@LRcQMp@2jO%&N6s;i=&TSc4~TB)rUkZFa-@;h?953@hC ztVrf&3%JvFT{EE=<gO$%hHfrP$t8FYQ#Ya(#60hjSIuK<hx&lp+^Zps+zor-wI9hW zidm&9OJ;>VlueAO#4htv+*?f0>Tvdee{BtCADK{RX{c(s+~o$n0(#&e1XyhZiJK*h zy(*8;9CNGR7<RAUJt;VD|Li!~PCUkFi?YCi^9T$54zf2`C|IEUTftyDw;}N!>h8ez zbPQ7ctz`6{m5hGtO6e`o<m%-mi`RYDN%CF4VbuqZF*xKMY&$oewetF@vr3RXl)frU z?b}$fQsT}pL+*hS=%W5uJq~xZ$FKL2=nOM}c+Q@-%*&E|_J8l8ndKK8FOytgzr&%B z?Nu?wMLhQ1Q)ePhZV%yqSbOWZsJiWc91xI@P^42(P*PfY6af(sq#NmOhK8X#M5G&} zySoJGQktO~hVGi-cewZJbHDewpZnMEA6|z&=j>Ut_u6Z({a$OmkA<}}U~(FkMUjHG z9I*fs0Tz8;q6kS>@t(ATPVSFr<9vHv>~xRij7W5{`m_1KgcO-Q$qF`<_?Keol>kSR zHC^pEFbLvn`nk+82)d2>YJyqPFt)JD&p9jt5%02H?};`fR^9-c1AYr&F6i;!h$h<1 zrSi4N&`9kMpe6N>o9|_2Xz?=Vuu&wM8l~kTS)O-8*=k_W-d!~IEUnQr#^l)NxBkPC z8k+sCNoVvRx;@@y$n|@%v6aq8r-I&(HId0|3l@HPeh&q`Q#cNwexEi(-B?Tn7$`<+ zE?4ic#1mf)f%X*Be;y^c>weH_@?niUw2CY5_;=?clCCYTC6cal?N6hwq}MsEt|YJf z1RTiNPsfWJ5ejr?I*zr#{W;`SZJZUk5LaSZSdST965~vezVISev?jl2)3kWAfhwYY zBJC@2-NIRzd8nOetLD>=ZZ=1)AC~;zP`H%E1&7xu>7Bt&Fy(rE^f&je0$XM<mDhqG z*WG;kL=BF7sz2KS(AHN=p~Pa#aQ@_jxS)imD)xw>)LI<1%D&z1oAfQfKg3*qwlOkQ zTJoX@)uakprG3yrr;UvL73TMgk$IeKvrAg-&{EA>jqPfZT`(z!7MjUGn(%d;`!B2H zN!1P2y@Z$IOJ>bl$06RD1cClYIJz}f#~-z|Sl!MB!7X*_MV=zXS#3TSl8=_PoHn`X z-bopcX{h)+cPcx{T+pC)XQv=q?6fL~EOm$ZA?G)O>hk&i#VK;Qwnb0M0-l1+l+^gE z@*bNIT5mjU965fRzBKRBd!(~d;tcn{KExDF!km?JlX_)Uz)SULHIQrSTm}~NC74sW zPH9QTD}eB*F{ZIUBg=TMZ-!Hr7NoC*0hNJ9MANmxbyWyg*@}W&O%!C*xmZ^mTYkLP zeF6eb(LFY^C;dw_*K76o?6t-b*5sQMi)yU2!wOyzJ#LX6f7TeV$sfG;V)JO$)207y zvD-Ll+uWjzQTZvjk7+SScpRI00_#>;c*?c95dL2LZ&-wuuz2!TWp=##CwPwyzOs54 zyM$jRGn=BUJi2Utv`6GwvO5bokZaLt>J556&zu3Ae)Ae6{i{>VzaP49^)OE|Z*X;9 zC~M{PB;vTGxTF%4tx`;7rOK8Q+_Y@+GJUQKbEQGFQJ&Li-9ngZlWGKE5=HtNp9<uh zyet%SXSwTHod)sDX5v)c?Is_;PM)(S21w4kA5)Xii1X@UGXzmH$eqq_cb=t8ajPwN zMv&$;)jGFO$FP3V!?@2_L|P_qIck|$q*==#H=sIs<YH&=Ik+Q9DgZ#fjqWFEHzQO? zd3U=(C3PQ16o7XK>$#p&A2v%THFp&@nty%$rs?F6tag$UQCF?lnS_)=Ki=P!Im^0o zb$#b%mPA-iqOu_!pf4uU;Cj}YWIdn|x-F-AK*RcmrZf|!xfYLK6Xr2{(juBG9j0kb zt=0yAIg_ujYuT$<mXJi)n2QvC7+o)CU&~<v{l}N{Mkd#F)+Mw!?ITyX(|2wKTmWF1 zQK{j<QojYbTzCsc=q>!%B^gO8^POXY$Xu=1OTT^xmn76jn-KL`or*^HwnN?UdHR-0 z^ZP393sSgLd`!Mw%rB|Y>RCIu^b{jWrxb@%l#>mH*UoMNitmGGhoU>WhtT<RY+I<N zOUo}mJ->C^-BB7S1rjH*7ax8)8i+v&wQ>KnlBkJh;*Rf!=c}nuFK?X~siJwjV_Ijv zFKiNF>x5_3^5}}&R@J^YssFmV3jkV(FUS!abLy?OFgC-j0!phv-GOC&+DSj*B|5Af zJXv07$=b`5t!fqUtO~~xfuO#=TWmN$6P8YQMP+a+`3C{;LPfa)szC4JU;hTy4<29w zh%f1PTn3qDpx!DRv8<!A)|roOx9kMZZN1fQeI_}E#)*o5EUTa0lKj+LORz@=NKxpH zjR`xbGQ57s=W^w910TjYek9^_c7hTLU8Gm_L3plzd1$dW-^f<3*EIt9Df3`H_u<Uq zm+QPP^8hb6wAY><o%<?Q>Na~6v6OsJ=l80sjN%49AU0Y$_9B|BwjO^f4SYmTl!XV` zOUo!X;?Aj1kK3EIpY5y<h%p2#+0$Iaaac3F=sNt&YOaMYD8=LLIT|zllLy8_@xOLf zm`igXb%x_ezPZ_K9a|IBM4~g|Tdr@3-6KqcVzm=c^G)rFhJCj^2&loyU=lle`@~du ztpCk(Q3|Ip-mq~E#tHFIDkb@rFK^||lIO=5KZ`oMC$6+tj=tLaklK<*yNx-=0C|tT zrULT+DP5ZVKmjIO*RufGteRG?vum+GPi=b4RudoJX~rGy`Kc9^L1<*^XYN4#O3q~) z(ndIdotkMLcwNl3=QC9GgW~-g?J1(qPm*JKlsI~Gmzic41E0P&5wTefBW(nrQ67CF z+azE6y`u@-2x32rI@jG;H6R<2)o;~(w^-X^-`drka@^KIHPYI?<kt$56g}+1CpTy# z;pK;sS|L~Wlo8MQ2Fn!Qwj(8>u3z8YGrHI{5#_8PT&!A+p^wvrf^AZN<3oU&(0nHV z^aG<6yB_&pTNi!?pdU})?6)v%--I%QL>|7RW<q-gx>og{)&(uvqBxLn^)mib!)Qs7 zu|}dS0`2Vo!koNNJKeKGON$czYFc!`Pe+0a=7}uuyZ{B_La;_MIu1?EGmzj_sP)4C zG^1!H5x@PIyMvVXI|;zb(#^51p-27GF9!MDSy_zg#y8mjY$%GBCFg}p86m5IjkcNl ztmpKf0zC)WKltNE0~c@k0Z@Q^%kc$o(7%2&?PKKm(6@PWgp4WKNR*5ucLH_gmiVKb zD9{<8-3`IPcLG4#?|8^4f1T*Od;THLg76yAZ*kt<i4#;+`zE&(5GRn11^BZvI`Ibi z$`sSi<DZ@y)(FMDKMnzK5fy=?b0yxO)rUj-&mZfJAqIE}56ztX-x~vxV2P{$|0K>z z48*GEG^uuuQ7WFM1n}vGSt2=gr51nELHLe0biBzV<M<O6Z@PX*+#=~f$XB+0b;<va zY5=Aq3P3QXbpPde205VKz~OXrJhr~>z##1=;l%wJ?Ivf`=b%>S>tqe--<IazVtK!H zD+IVHkXBk*?DstYFe&F1anSgg{O@=FX*m!CZt_e$ndbSu=YpI8f%%(uAD#vNo5cTS z_U^Qg=FP)&B+3j&Vgac?U)Oaf0Q)Cp+21XHzU0pZe*K+Dv)6GN62EW4=Rg4_xhpjL z-!1E33jWXUZ3{<2GvTF3{&VUFoOD3!_WMY@{|?Ijhmea~7{E>Zo|;L&e+Z4;o!Iqf z47CBjoBrb&;NL~kJ1q_KuH^fDDDbwC@j`K^n-|)l_&rL$Pn3Tq=`+Zg;cndapPWVh z?t4+~5zrH}X(h|=p8kCy{!34R<%r_&4Vpbm^ZO?6TpB=SUM$)w{cidDW5__ygY16> z7KDNfjQ;(y#mE1*fJm5r@knT!_;f2+e^yS^UCX;B`+CzqO@F`8l?5c6;xxkk^U=dV z;IW{}(kX@iTS9M}cfez>W?$?)`YltN{oPuq&oKUf9Q;4M{_`Tr8|b^IjJO`>&j%S| zKu;!7210*Y{(iys6fl@<t5zH0(ZA>B0vN`b`y@tx8vdHB-?w}Q*#J6%i~B70FV*^Z zr!^nXxN4(+&*R^9ghu-AF@mC^ufOl1@d0Lp^0gC1qs(vB{<qorm%RAGcaKqDdpG|v zfEd(w%O(=uru|=}`z_{MIbe~{ZAR$V{#hm8{eWS5+1~g5Ps@KU8ZT7s$G~GcUxfyD zpZ?Y$3|wHC+A}&i{yu_#wJiD~p_x#BivROV4&K};@|!(JE3V&j`CGKVqz0MXEiUz^ zYIwiz0T}=@z|XNGW_9m()88NaFG{iW8Tf5BbqZ6@{)c)6i2_Q|Ijyqu;&<PmDPZAJ zo`p@L{BHUCw}GD010p@z*R&%0eG@1MuqC+c93_9BuYYU#`}&geS||B4jsNB1@5}gZ z>-~?l{(D~jdfwYu(hKFJ^4nm|p9%dg08HAF2VObkuQ~cR(f--Elz2C2&2CeFnJr^! zpeIiBq4hsK{rv(U{skNjbBLY!pU+YwfSz(!x~u<}YT4YW)&X+cUm%GMu()S%?i>H< z=f7Og7~Gku1AfQl<Qx0bfZl`SV*&^q?^c79_G6ZKOjW*i>?#|BgTWC47sk!$Irp7M ztee6E=5RTU>CE`Z0>dywxgVJvl~lVI)Y{tGlyD9X&gm9?H=17=x4N8NNbBluIGC*d zsOd4oHzhyV(P*pI9WcP-kt~!^+?34qx^8cOg}?Upz;tWME#P7c;_EsVEy%|qXhq1% z;Qj9P-SkX_&tm)!z1z4mMo5?FEZsdo4S*uWs34u700Rn^sdBO^`!kQk)i1)9b}r|| z$?}DEn8A<G3=>lQK>b6}h6ubQ!uqt6Jw*j!bH#PZf+c?4cZ&%4{V$BhbaN67aX0YQ ziy_HJx0szPwM-6oXP?B>p|p9m_m6w20-8N*l<r`HCM5)RJ>V68Wjf7~roO(c+y#+T zIiAmZywGfjzHvcV_r@`0F{eQlS}7OnSs%z%_SuL7^1gyT*}?s-@c(61bkYK=;uD11 zgijr9a}F0i{X`h59vWiRML%O85UZ<{G7$AF<-v}!3f6l6V>vYv>D`;tIxZ_A<)o8n zO}=0KK#a8`J!Yi1Lbs5;!fgSwcuY1Y-0B(HnB4>?z~>~nerp}7J6$L$WBRkM$oS!q zh%pL2?I_60G3<6lfL_w)0(qhz2)IdM66%!B)mgeW0I?Ctcp9s8?W}>Q6-eR@8fQa~ zhJPg*Bf&?`0xpuLNuGSnKQqN*2K&*a0vt-;j>*`<&TB(?-Y1oeo?x8CmHUdskP(K~ zvur%Td@FuuJy*2Xc>d*K{(S(DFa<e*o9@X?svx`iV~rG4z5Dg{@EK$;4A)#_B}`*) z0rI>xt^RqLgCDsbM*GW{nr4T|(vR{1r=}!wy3b`<w5k=zYDvwDDzVoGpGvg?wVf<} zo?x+lhuD?YG+sisn=9-W+Rx3?#nl~@k+1F(rgNX}-ITje9CUyT;}6|2L0%t<QL6NN z`h!!V=a|!}1>K*Q8ksNdtO7XYQ_0M@<l{3`X1~Z(Ib`*ioy$=HF*0nEFr6^{Z|K|$ zF&WL|@moCy8%rG$@=cn=e74&@O<$O!EVS4amw$v4lJbN@K`6q2l{*U#!2U0*^3VRZ zc!>Hv3&}%l<>kJB^ZT>yR`W?~$)TdHrz&SBM~Ui>1G2AGG`s@JZj_@e7(av0u<%(O zBh#8_6gG-t_s9A1QMj6Z70~}9hOs8{d=cE7JE+-U7RY4^&n|z%@|hu;ITQ)Sp}Znx ztu}fCCk>6W346qRVxQYEy@;44Ud+zeIC(mkk}0EE_-#+HPU*`l&TT9C+OM5q59g+( zZO}IBLwR~*3MU8I6%IRmfXLFfA%5B;R8;lO&w)$`A(8M12it+x(PK?miNYXazjU4| zaT{Sw>)iv`K?j5R##x^Kv94b-;HRQ)LQ3bpwCfY~4D3T5VQAB<iFb)#^rReflg?(P zuJ%6UK{13AnISiSU`gkG!c)wKJoDJ*MlHQ0OW^cU9XLJfqizEB>nlrtV4YfELo6iG z6k{Y@$xA%sInhWglE-Ve9kv$c^g;APSwH&47a(dS?9EPX=5)Ff@vF@nBXFZu_7aW# zO~-fY6~T(^q*_EYvzU6%T;usOkD1k@{ytsZD|R`9mG0f(yCZ=_lSdcT|8Q>n=uno# zybvq13-bB%AU?OfiVOk<N9o@9C;pw`1pRe3i0eLZG&PIaV^X2?d__FpSKn~dBYu*M zG)fBqvGI@2K&0{rfRYAWFGdemoDAO$qYt@Q6WA<y9G!?Av*lv}sR8taL6fhat@uDY zy@*^Cv}^)9QLc_+%<T?()Pkvm3cg84z5SK*-l&M%*KF8+Iy~vtl+rj1BZhyx|BL!H z1)VF`u%6X%7g}w@o<BcvBFf3M2pf6f*5h6R$@Q=oGP7E>0irMMug|omfrPfU3i#=y zJY!OvXqjVAi1rPvfI|2z@Tug@7lZ!f_26p{#IlH;qW%lf)xku*Q~h&aW43=p-ZQ#q z424P_F@!NlMt8<3zHoqaVYotEC-jF!oYPtBDtTX49-!Me7djEoI$ka!mPH@ec?xG; zlT+by<Xv2HY2zb4?qeMkAxW(PafGP2=%*M~D(<oNLe^mbWM!u0*>5}@)RZonWP8*a z10C!JaWYiJ{CajQzB;J8AW8EMuq(cu9sd7g>wb#Q<$XW{T4+26^^I-Q-N#Lf-Tax^ zYaHJ9?wW{jPV)v)XB6i^{!(K|=So7)gtSux*HX#^Ly}3+D?n<uHq<E2MaRXdeOL1F zvWE9Lv_Flwgv54z?HlIWqEWy6bmdOzea|>#x(!UvC!72anq<mp(JJ1>4Hv%4xJgJU zOhK%$y->=|J?qukGZ(16ZDjJCX$KNu5?>W{oz}+AS34Te6v7}S^IwHKTB1)i@cPi! zjCcmPHW^IipT(?`nb+Iyo~Ky4Bza+<v~;w_0pW!nU^`Znn3$TJ5vKhl5lk!JqKIrd zSJY$xp)0mOt(nWkJR{cpvQX!uK_&DH(~>vUQyQN|U2!xLy?Q<d!0r>k4LHekcN@1= zgPT*qGisF=OGW))z1=Pk4n!e!IELr$6{G>989(?TXG2p4tpne#RAG)`wayHC<Vk;t zzI^GA=g}HV9d9_K^-n$|m%kypt&_pc%frneU~Ye29@5b);K9tDdO#Ogx?U9ZM2D3N zs#7U%YYHv$ao7@3sId~Eg=_^Pbp<x?JQmeb-RSwET=12RR%RQunR?_9NJETUS|Lta zS>1<NB@Siwe~dBBnbeLsf4>_0p_uXdaaQ^MK{@JGtxhEO({B2tE!4x6_otB|eYrUA zVHY;UOYB|t&2#ENIS9?quWYl@0F|0(-`I{msvcynd(vvwaf0|Z`<3`}^wHOIumsDu zbV}s7vGBkTNoRL?e=hdTMma*a1IgFXTd=4F#7q5o#UlUZu3*zH_2%to+3Nn8sf7ub zTMI=o?fS$d2rZ~`%#6}}tS4P-TpG__+)6GNRee_)kAV8E(Yx!AuuW)Bo#(mwv_8i} zRvL-&*dtK4EHh<bU~R<Y3P+7vA-L9b;u5t(C(z)Q73}W$iqr1<qXaJdY~27jm$AIt zi_aUU-BnagKVr7=zKQ}qiwrA}{yC63!rDW~F7Aqp=sJ+fqE09RsEfV^I$`*|Xgkz7 z`bT;T?&}F)Rp3Y6K?4673HZw${)dC?yodVT7fDF>%5F>OM1ZaZ@O<jOdWf{Du$K9F zwwlTEIUR;P%$Q)^fhk#>G}yi5xTgmS?efA+#kOh$_yd(O{S-BRU3;8A&c*lVz>n#I zIpR`q>(H$8#qc+xbX!XkmK#Nw(*YTkTzq9;ST!56L?Oq%!UeEx9nK|HNl3(g{8N1V z-72vkL&N+s6@JEpZ#B(>riNBW7J*m?T{HiLol)ZlidXL9WHn^^MZZ2$R9~1uoYMm2 zOT{G>wnxual&0W6q@fT&sC5plF%TgfTqc?szTEDk$dAJsVL7>nI_YqW5Z&U{E0Ip9 zf1hsX2K8)o2Qm*nOLmA5sERk{jEn5Y@DEDFGh@W%bDtgb1dG%W*?xc?8E_JAUOOVD z5~yc`msa9><OtKF03XfbteqNy*o|q`GG4P2GjO==dc>ssK%oCJw4BrR=+<B=E;M|E zbnMJn<+a;gdJVe=*2Yylh;r9WI*t!}LgVsGT;1LZq9YmmE*7#N#gZBQ>UbZ3piS*~ z9QSTlpbRkQP&&sjYvb3P@>*R9tRw=P1m5_OC(4&>VQV}gEEff#!PQnouMnEejtVml zN=rl~2ZNJ@%3AYX)Ms>pIGv&7{nc2NI!k@j`4ZP;6&l4*m7?k0Xc1vmlI)$dPP<D8 z6E~|HIZQvAmz(uEa^ckG3{836T4j?S0+YB;a(lS^;|tC$`8V6rdv%!yCCv=qZo1*C zOLR;-pdTFm1Hul<CtnGoTeM#^qeIMVsACu0CL<s2=<DRa_kCR<ITzv!nKQE{_ogyD zY?toh72!VZ3?c1Ew%1(ox-N*ke2V|@PAh}l!aeB!enR`*g28*Il{o!<*xT}T1?oB< zNM@Md@tE`OM9-w*syytJW%(*lkZl)1(;sM%vHPA_PvsrJ2Wi8q)f{=&_r+zBd559C zBHNw^@AZJvfgcD<YQw;c<qptCX(yS;euob>il}|NDKd1+!kzTM_l${G38}$mi;;ft zthI!-kDXGY_>JyEtI*eFUs-e-(+pk0))>u*Uc`%O4<8f&M}d$wG7{E$pvvA}m8vnP z!;Jm%b@e>T(oZwlFxVdNI3vOL)m~0w!*N+R)Y#I`7+D48C)Ny3pPfS1K5L7%5biLX z9Gy_dt$AA!_uXOeww$=cePtEJDsakCgU}&H(CsJ8k<I&8K=?)9o=&93N){RX8qo#I zUv2UXJLunASgi>0@IplZ$+Mp=$+CD_YK(KoVGuxz#S^DDw|Q<_e8C~B!OtM6VO`HI z)3(33Ca7D9jNze<ENq$?wt&~<B$N0Pv}DRWCGL1fOa!KH9?=IeUD_Luku;R{EM*xR zMyj{p<`V7lEb5VoI7h`@w5MFVvv9U(7^~!Jqhu!T2Euk*Z-G}Lol(Tun|WAULnx}b z7H(EPRkrTL7!6;m&73D&4JX165YYN?e9Zo}YS=j$w$oz}#C~Idrf~h#Hnw|8IwmV4 zq5Eh>fPp;p8d!mS`JAzaH$Z|k4y4StCj{;{6L4zvTnwv8cLIzx!e9F-0Bgj*GQs_( zUHHYvjKbs_xX)6g<1$Qw(Es$6WNt}qB+Rx#>-@A>gUosknI%nKjx7z^@7|=R;<qvY zaD9ASYQJZGqkFS4l&bxOK>-hKJHitldvAhqM$S$M*QuC%OE6%kf$IEPtX1u3oY!2s zJO?b*>tzL=Q@(ULM$Vsh$SbGs^3g+kNY@Zr*@_RAQr@OFK6&-JFWstZa=X4mL6H|b z?EoN6od23^UH!;85%fKf`U0s_!$->TD>oH#Rol_N8#W0v|5HA$9a$>=*IOUbl)1As zt$^02_@n@S74g6=3OG;vpZ?6jEQa5GWd2KXaQNa0kj##TvauqM97F@r{IZTC{8d=? zQ^t0oXFrN-q?r*yKGUX$IelrFUnO{As6f|Wqh3>&6VDs>J9($(w<~U`0z*s7mG4sq zo78n=Ruj6J2!!d3Yz9$?n<o{^N3B^&Hmzz=vW^*f!WI-4ok@}rq22M6RvRZArbFLA zS@Ikk1z}rf`xUb(RtGIK2@K<!ady8JCG0CQ+aXp375^VfBDZEnx=;A?+_b_i;i=72 zH{ZkUrxQKa_B`fTM|0b3^Y1Ymzzu#5u!?)x7rr%~WlkR_BUhew^m#_l%(3fN*Q_8> z?NE8p3?xZAY;ubdvCf$hkMj2w7&^&p+?Wxn!nnwF*UOvnN61Wz_Jp;B9SJCx$lzHG z@U=ZkbDe7QlD$d|74QT>a`40L4=$Uk;PzelI{E4E999Rp^ysxI9$QDPC_~p=^yOMa zv;gO*-LzO(^#M>YLi!p%;VuFH1)cgktGQA~pH~m?P)3bPYbwL~VZj}V0vqHW!pgD5 z^QP<A`ATO_3Ig#L+m6Vp@9?N9<-}#K(nukjBTFnRG8QIZ>)p;<z736I0JQOwsYK@` z?CBp7Jv{sD=~C|Bh(`Fk+98OO4?e0eN1tpf+3K-smZk+I-r<!~-^ZPKH3SirvNJND zcikcqJZT_4R>n$TeXHD$xX3d`A+Ofv#vfgcUQxgD*ZKP6qI=5T;)^?XPR!Tabg#RW z9iHy_CQ{9EVEh*pyw=jO(N|~M&)vWM6GWJwl&2a#R?%;eP`bBKj(S+DlDCLmyOWWU z=#;!EC~RjW!(dK)dOXv~1s<LUw=s{7^K#^T3GYXV5GpzdcI8z?*+<7JR*JUk`aWwe zHt^IN;{$L8L|yRS`Bzlzzkwlb$ItMoP&W+#0^N~UEpv0>RcstJViwsI{st8xwiz*G zChnta=1%4+H><^&(*~X|^+8!4st<DdrsW_{o?S{Ahp){dDnoM2tCcP^vrwM<l{k%6 z^XM(ON^Oi|-4q{bt#;w4I$xO=P1GE{?rj>%6!)^StiNW-BWf#iJ(CVfftH^?h)#L_ zT5!5TFX*WZ#X@DgZLeneJZQQ`!C%l4d#`dyoZNI+xO%<hCscZ67{~%bWlw5;rYa!& zOSNyxtnOUsTc+Wcw`_c&L`;)vo&vV>18j=0`DL>;Sb{L@l_U|f0=7x<H`xt!vZR?u z>D2jB%T7$EFAvSzS*QDhSL2jAq$xfP^S@kBoo3cTiy}OX$?B~k6nzGM&lJmkA?hVA z67{}0T4G)LgbKU1JKfr#qH9I0AhV?W2@q(VX07{a>_^Uet?lV;?xO1HmIJNbPsBqn zn(8!jDI#hpJk>`MP_h;rQ{C2+QRT7L5z;JPCzuMI^L$9>v2CHcQUJWbs|a|P$;a1L zfpN$dw;O3+<Mwt78jC|ogGO8yOtX~NRI1S-bSEkoW$7=B)j)+vyEGWso2X<P(;x7t zgukj(a*wE;j4k9M?N)hB=wavFR|Nq{1F?^vk82-<u9SjWCf6YL4Q__$<&6f}dv;?_ zZgLbu-FM8P9`%;UmadtQ8ScV7>W7NujoW7$l99-ryb`r;ux`B6iE}G|A)~gZ)l|nI zloEXKlRZ~@RYwSrf5%rMhr3q@QUA5jd4#Bfza9LkX$dirUFc3QPI;2DP;;E8SR~MP zLIATu*aiiY@2TMlGVDxwX!c(wMY`cL%yWk)RE%|&Nod<vYwP4G???ky_RjeTbBlrg zjv@XRTYE5o;fIaPFQvupdJ^qH?i(RvCYnF)5MKms(kX~xGh=?bdy@;V)(43`aBX~t z3qG8C@m49PANm&2=rJ6`8&vo}YF@yMFHu;Qz-6=aP)o^prplts00{XTH+OWge9;9C zI2HJ5Pu@U#iPEdxj&iU@`kR2tYM<=1lTA5Mb)FaIrBs_v3mFPWo`ltz6Id%hf?4kK zgmWvG$9~2%R~I1^Sm`eniL|Yp1s7xZJn_@P_*@gT-r(^Apb>*NVvWpneJ7m<{Gmnd zgAV2UwTWG>q?QyaDg$Euo5F4AOpWaKlF&(W&x;yHSg&-A1EDq|Ta(o_?(+NhMzvJ1 zO$;n97C^mUUR0~CUv}4oiFMdeHXA6b4AZ_I>OeamVIxl1echf$bWYr9bQZG?FH=(o zr!F(M8eZbBuu|?N==9$P+g}6ON=h?4g^Pt`bn`^cI%2c1*q~64NOrFit2HX|gHA3@ zIa%`Y_bUp<--|T(U2kOT3s{q$#CDw4QA$Ba(ax;}ibzJz`oz74=G!Q`@!2A+lAZ?> zpsBJ#*OF1Hj0V!?r%jRJ9U;5Fs&-!~46ucSY#gI>+Pi5@E~N47s!UFFP^j%qoW`PY zF^|C4vg^tXTlF;?ORQx(?A*VHt>rA-WnUq*U{j$~*KUWUh1(@p_AJIYBRb>y$elLU zTaosVB<B%ct9hQ#(OgMZl@jeZ7%zgeQ02MS2nJ$)0;jl%mfG<PK`v|j_RENushR|{ zbsInN@XPpYv|1n?s*+z`E$Tn|3U^#WH-%azUY5~@Hnd4do6|oE3cCZCx|;he{}oUF z?ezZp$;{aZ^*fNEhHWCFAYSu1#1IRue%D4nCCizKuEOK69?Nc<mapmy<vp^~W-TsY z%2Rx`PPqx;+H4~OYMwKju}4uM?*q9etR=x2MOXQ@r-C{wId4iT-ZI_j1wZ@r`UF}K z8*)!#up32R7fVV(puTbOj1|=Ji!gTV9sa1Dp_Fxw?!)VMESlkjC5TKJRT34s!gm7G ztRo@_gSw)*y5W;;1+e|R%ABHzDWh^tXz*Mp(ZlN+=L`$)f-C7c8a6qno_D}zr(zbi z<(d}GaT&z!;ke*Ha64siaiP#0tp31!R_kfq1L|LuEeQd@9Q`c+SU!$&dQv*tq&}H> z5CU#QQ$Cwwa^Xg>%+dxv#Yrnu)v{sZDD=8b^hYKdoZt3#PGY~!wIe@F=N?QQ`M|Pp z+%xanDwSW*oLsoG@M@wHsJ(GcxM+eYCN!F4sU0o3T^g*vB7Dq^L&7Kfso9L@y%T!Z z>nPp@{7!Gyg2VPo!w+;nxoWCP{Z?4PAF933A8Xy#0Ss)aAPw#d$M(7Lx+IAg0D`^R z`K$}=)lsDZR%%)Baa(qO5Pa9V=v?<lq=5!VpNtjGUgD9hyF!F5d<F6`cke93wc<C4 z*xgg7!2JPV?JVh~AF3}s4ey!%Am+HXk@zd0r1?W}?n62u=24)?D*L=_U;5f~jI(X1 znr=42J9xVni3;a)UrU;fk^3~ioRjAm$rg@AIQt5XY37BUGwcGuxL>3Em%#4d?b>iS z$QeJqe{zov>O>t~l%UX01{R^v(yzICudnnLR;ovaS~X}Ub8}^0dc{b#e1pzd#H`|t zw*5YscH2jTh08aIhKo+QH0EY3+>UzXgUeDy@h?q%0f86+g|>`kzV}cO{NQjNi$eE? z#aVCC4^;hQ%??=;`Z9+o01T@ulW-_g9nWn?;z0jg%TeQuP1z}>t0|^Mxx6AQB-Vdi z{^(nWJje5M=$Y$lI1vj}4Q=ySy>s{p=)PmSWw%$?+4&AW{yo<l6_NL8y7n@sq`zA9 zX07j!6U9EMIaOIG;?__tk|VM_#;)u$3pdu1SYe@SKO!X>Y5CO$W^}JD^V@d^vMLMF zuFKen$q_+tL6=VBmPEVt4vvfXhOb?Sz{Le=|64S&(JRjIxzNWdbJl4yoa1WqlYVQo zE-llDFj9GpqWmbU`3AZ&E}ab4_2%7MfQy#o_0?`B-#4SR+SyrieYo2>6$$Ziy!a82 z{plktR&zD+#uC2CMlh?#D?s!Pb8d*Pdw8p;m09k5&^n*o_-U9onpAE4E?cf`e6G3R zdiOJTLFCC_?*_@fDqB?#a-nWMAsZ%JY2B6(GxX_-I=idc({5e!YFj~iG&`*GifdG6 z<aEL)P<zyH;v5G4=!Lc053a2}O{3rM8z=vwu`;$W?h$+zm;X`+gyt&XAy*tngU;X8 zoh)gYJqC5z5gXE(jNh4BKfZ6AXz4_>h>H34(Op2NH)7T3ujupNVmgA<C`+%s=5P9S z^JnF>>I=yC_?4u34qC#SLdZl44Vjqrbi>uu^p8Ja=~~QpKdfO%e=-~^_k{H+Yh7Hi zvh9Vv6h+rnV6Du{^3UBLl-k|>u+|ed81FZpi^W}S$)i4)kY6v=_xB+0$|GOx%z3p; z&WroTE8yAyBOyX*F0t%*N2k_bRTN4+-x$^9<0<iL)eLy~IXnLYrgy5M!lsNlC@0p8 zpsYDGVLcEzvic7R3rJ~%O}kbX*CZ&>M*24wfXWZ!ppmvCqrG$%wOvH`=n>pOsLYdP zj;X>jdo5?W2Jp!2%#sb=Hy4>m`^_;%)FoSz&5(skYfCe=k0qp4X+^tOx%wAT1YcRD z^iOOv2|$E0U>5>E#(FtbB!^$y51i$Ca_8@88wW3IlY<SudpJuG+LyzDDksICu8A6l z^`X3db)~W9npQz=jumIELWt$UXI2d1x>k*B5-%w10YGTN^`reKAlExVXK=oa`Q8d$ zYaaHVpSPAx+{HVz%>@M4nkGxSd7i*-8gIMZ+(IXLcOYJ*)L9_*a}{kdSSdv&x26pq zS!G+1cJ2(6qe4eNO$lc*5-ni68-?Wd)V!&XXavVS_EfXXu|xavV)P-sfa^)#Re8^3 zsu;>(WKxav74Gdk%WWZhn)&U5LqB$50+7Ys^W6|CeOf}akUOOP#U~p-X?~$jD^TF@ z`NvGu?XEk#NP19sByaEqY<2I&BiXrR&M1(<UE)*4?nkRX2@HQzs?TklcSi&hi!#Id zhAs;?Abs*%K9<v6;w}L4CQeoNEAM~VC$f~Q{fYSU{>Lj-99M$6RCI0M*epXn=C?|k zE{*ScG;Jo8U^7ZE2=`!jFr~r=0l!u;#xu}~w?&|3z6RWq&VmfNTldI9($!=_mIVaH zU5+@FNaVed`$8XKy$xUJqx|)4FwSTu3GHN1<5m*A^MUv;vu#BWjYkC-dE;9}Yo5@t zNc5ip64(1NJ(fkEDv2zgsFY~(dQbr<UA|iWD>L^@RAe%M0K@L*0NXd!_9E|XTQFe2 zhQu?<72-$_shWpR)Zb6vtSo|Hu0{?j?T$cor_pt2zWJ6IXYs{g3DHQTeJBp`EY27j z?^QCJia5A^uD>#YW`pIUebkb6zNRi*Zn=`CUw;Wo*w6=18^qhq%_v7tq<KCK!kLrT z7uDBaZhb8Ap{`D!7p?im8)?C*PCeXwgEL3!iG~X9sr>uZowPVK+pkMU9^I)6$yQJq z-7p+2NLff5j(M|r1(h)}U~X&31uDj`kUCOpAt~(X)!}HP_n6a@s?%sfJ*)hF0`5qG z03!B`Q5eF+OY-!>87<#*PM#w@X{~R;u|3>Y=h_yfs$r4p%8#|~d`IL(cHz1Bv5`*N zN8-bBJ#pUSc?1Pw7Z032KdKrnfcyT~_xSs;$b$n?kr@pw*~D7I?uYq`vc<QAAC~>t zOlQy8!ZogcMk5&RNobVZirjuncCc3IFed=YR}>4a`wOV74u`34uF91cw3+niG5zcT z3)XRot9SlC!m;0Zy$@(X9X=HbX{ClkH>X$+emz_jN%<uiNN;Y(kt6%0lA(rkqBsZi z9PxTqCn(ZH1h8GQsV&dN#w@EJI)N=GFY<v{Qj`;u6K}ILzDR_v*3CC99`<w%i0LNC zOZ&<)5~Q@ocv!Dl=P<s?HoEql_?JQB+iq7%b;YkAiVINH(EJWtS#Qnz9=|vUXpom( zVU8pjX-%mrvRuM#bk1l)sVaO{X#T)NHKt>k{8}WS!BC{~oXLBWP_dwxDBpaHgD_wF z)G3TxD8~}p<Ut<rLY8J%Pig}<8KyBbAS2qLUFbxLsl-ORI$isc1baJ>EL6b0eccY; zWjUH=nx$cSbAW<kK3Y+I^{V?egJtEJ=_8mvYANO9z?o3Xm+5tqM=Nh!hQ2wr0E`s- z0uJx#J(}h5b1&L`B^m~FetmOQb1bGb?c>u2!6+g=bkYYtw_*Se4y%->rpUWw_ZmX^ zhUDhQ2^}Mfwxp#ER`18y<qMc{PAIWZk2R-qmB3qGSYeOEyCkD=EsJDXK2^QN+aK+b zzQ2L@_U2|EsD|PPokD`@dv!i+f|U;3TJ7-ou$)f#(?`EaGeU0dJ4nwPO_1wC_m{`F zr_B9j<Ad!Le{!U(`E~q2>|k1Q&pT_^;?XQ}BhBAydKMNbI}^dVu^b0rrJ_mPIot%8 zZ{OVQaZ4Z8=>J#Jg1}vbx2Dz^;B)Bm*h7fs?lK=Iy(>NyF242VD%4S5&NE*sdsl;P zH5RT~*KXVH0q}(6U-rJ7Zh!|pG+eA9I`ro>mNsp;SV(dE!YWG;_-dB{Hva2jd5Oof zx}T{pJ?hnN-dDW+rBtlDYoG_JB`rjWO~dLh#YQz6R?J@dG+nj(`5|UwRX)9ClE<oA zCm$Yrf&M-zKu2<q?&N%1vA+(-dJbHmo@C9n_Q|EmbebkjIRbxzCKYkMpj8Pkv5NWV zz3W3h3b5(%Vw>!WhKd`?X-1eyuh#adu_r%qqh+<Jr_m%7MP<8qEpf+0FpZc`5#b?P z%`-m*s6%o|I~5^PUbuZ|wHgmL87jS~a!|O0GrVFhyRu2}@<(t=dzB8vy}_Z^QW4Ci zYYVX2rwYgdp5<vkDvLy7UUq4z0}xxTgvYV!a6=cQi#M*H`?iiJQYyDhEwJS`uBL9U z&#EMr99A}-ByDNB%ZZH}Fc!==ma?bYb5)HQQ$o6(q~$|BiIJ}|8Wx8pK$E_LyKEBU zXdcS|1OqE)SoFEhYgj~2@(0wdjt>1qui6u#Sezbp5QQA^Buq#LQyIEub}p(wPik%! z4vpZl%WMGgvZqtCfdsT{%*9Gr^JT;$FYP5FJ?aW2zAIZ#I73T~52&IMp$M`h_aSwb zH8ytrw+eW;%CpzF%|@>#&f=^r3k%oWTvG6$2HnaMX(Cr;>7EAik|A+0Ju;SfSrLm% z`U96Adq0a@M5F>!H!E8W+~~q@nhd;}g8+Ot{|?_(Y>Xxs{U7*l5vCt6@|Bu(tGV0h zINCW@-QtAe8!a1AX&lCOJ;F@VxAp?<THLeBjX4F3Sk{UkNw%6F)vrQ(DK&{3Wkm5) z`OX=lRZg=qIdt<_Kp|5^BdV%2kys-Orw$s#oYBT5U#Was*e?%tLJDnxrHtuc!lbI) z9{W7$)ki_Tyos=%nd02boNrA820w-#%kQt28_#)eu+BV9bMj9z>7<Bw)?FjZHPPpo zp~q-3eHZ|+qa(AYi|OBiMM20oh3Xjk48xjSZ}exzdc(-~;@LBy<Bjl|EgqQ`t-AJA zfK6mPD87cqL<1!1(2s__nU1=db{4O?lK0%2wfYmHg_qtp#e#UYhD0OZ;z@#FrvK!r zE?hGkK{MR|%bV^}ETxa4ER?{wid57Vo7iQnd1E5;l_u2!0K0(j1+2QHo$Q=sqSf(A z*OB+d%{sZbSAYiism1MwYO?@vWo^~*<cb-1ZnA!}tYdp-9EA*<1(`pc8sJ{~@_2e% zVDi`$xtP5#a#MonVp$iVk}_Z$5Q6~d1-fYVJ_%E`-85RSecjKYp|@Q`eFFD2Yc|+# zS1?-YHzSO&^ntm3L&7!@$=4|M3v;%3uqlQv4FrtGQ5rJx>FOXAk3Tw{-fHsxSV~yB z$JX6DgLBQLd}5=3e$!Z6`lL89mXjJ)(OOWE$3n<tCNk&yb%nJXRuOPC(fZgA)3mPT zDQVO14k?LUcxHH16k`+?{yRD1_@4MsG`m7<;@T^Q#o4yEC{a;Beqm3YN}5g5%|1*L zfoS#7xxMD?ylrQRlDOSEfw(R=F(B~v^BKafUF9Kpmw2aduH|k{m-iSVPecYsOKV<Q zmbCE~`=2D1*nTW}kP?z<Smd(0f|CDGZ9r1(R!@23W$D^Y71fycEoCbLF++r<%QEIG zDIpO5dN8jY5lT^GeTYtcv~jU=XiSN>E||6PAQj?_7$=}ZY@AB4C9l^lo~qoxw=d#+ zn|&V@u>sFrYP+@;h8$dC%f`7QR*!3iB|bfmH}|bK|CR83ea`plf@^f*$36ed5S_<_ z0-2p7*M)hHo)xroPMQXjN!|V+YR$9lWD|FfGGUYa8Xr7ZTGIJg@-7v4Dk>IxPbhJT z?O8|x5?l#zqu)T=>5RuR&r}jP+^RJj9ibE|uln$B!G#T_GBLE0pltqAnD>HAk!o_B zT%G!5CP$UiJ-Vp|qU76FmR|+^kHhVY&(GCu_Qr7KE1W+VM*$HIMsQN(FBiyN3T2m2 z1&iX0Kh0yJ7ju$`SGTqKbmlpV9EOUapowob<l5zXNQ8)!lmbWWz0muodBz3pJhaOD zhYQ0=mZ9XlvF_aK_Aa@qxQ(R8jiNvL5@iDViZ#kS&*HGmWca{e-FD``0901(`o@Fx zCeCT-;yZRg%!HYERs-Cpqp4++HOolo3-b)HtdU>htu&EdGx|bXpBhSm37vJC(gc{2 zs;xr#4kdvC&gga_7*vdcXoT*;?d&v19@1QQ{eZn*_QP}u^P1LG1$LVsP4lT@KFQR= zp1}{`b{VndS3YTEgdzPsh;W%EEw{C6#xh=W5p5CXsV!!K0ti9AR<)m2r^k+o)fw+E zGxt>><}He4Oxh_HIKCf*DYd#JI+rlB&#}K2UD{o38|w`<+>t%W2JJ~7?S4J8Vy*gk z(4x)<LHtlut9x+$UA9{qi}0q{xfL0us#?aTJ*Djj8gJKG;WYUxsE%-L-0E=3&*>H+ zWb944)|62-2ori$hZ$)<%M!_K8IGs?ZWb-a+ZDO)(L>{0pfdllJB7E0pJhAukiBL- z)(BY9i;C0_iE{A@I=tl5m@%hV=9yGO%3$+4@^GQt&=9WDYuS8P?H(N2uq}baJEGQs znu@*n0(nbT=tKEU*Pwur%N1AeTJAIW+z<|w>w^<KR=>!+!OCIH&OfZ*R-v5$arte| zS*_xR7}osA#=IFIQ{!T-wtTXnF%Iw<aF%zJKZreHA&)y%4s*F9xB)hWgT{ki9wgd| z+hWe6%^J@a;EiO#2C%j@GtEnL5AlH?Tk^&O!32{ufi_vkb@WSkRs{p!dPiP#H>oW! zR{>@Gd<s*H0ZP&jQ(hLr8X}DO24AvNdK@`LyoeB?f{3%OUj}aIT=Rd2Mb?)k5@*CO zpH3bu!ULau{sej@TwdOViP#<_3c%cN{px!UbzcMwdX7C0f!XSIp{9uxiQe-?VZaH) znTH%92qp<(GrA>o5#{aIcrcRk$e7%gTRHC;wh5+3+$9ZP!&Ba8c1q>qsIrYxvK%s? z+~us7Rg&eQo!E7HIVKB)h3Y1Q6c;=A)}lh(YR9wBM8UikbKm$OPmL!WIJApnEGMDJ z)#?f#+9ixXdK?*GX3Mn<e0io|Mt5U*wf1>u%6?W&_ot6q7-)m+_@ZrTluWiESIPH` z0f55jV=W$6C;L_NFn^AsF%Sjb(7B_^{!I?S`&440YTJh|mO{`~*;BJpimCu9q@_*i zh%r1kdEy77WA<(U8E?8e#v3PuCh3|C7)~k5^7&^RH)9apG}z)oH+`Bldh0?VcN&8Z zp~v(7v51SP*2fNiS0Hkgo!^ndrD?-9CY(i8hg_>=BT#@3Ew1C<H})H@4K<A8)s}s> z!KN7%-bxV*l(icY$6aibktqWlLtXakYOHk<@pG{XEa{V|y2#<L7B5JUU!;ZPF)2#p zNMe<FIqKY1{K?Cmy8!u5O6(UtH<`-Aj!jmdhSF`Fcv;AIsyu5Z9l;+XkbU5n9}X$L z$Y|AzPR&-V1aS`WOay1Jrt-2-GH;F2*A~81E-cdq?|dKx>r2(2ANRv%;^MECI_r;B z1|$e;s`->HGM4q&0vq?-&Vj?jP(Y&jxXpJubgsS)mT7Ucai*Mi%#?IC8R0Ee*F~94 z=gO>)qaLksqn{j#3{(|DEevLV>KEE1e8^$T@j^&uP^&j7Jx`h@>U^|NwOa-ntL8=B z+nLN4FrQpO)w}rwP?(%tkc@-^&(kVUGJ6O5kDy#7p^<;HJU7iL{zWF`65FNdC{sNq zblVVqEkh(McPXi*Oc&8?{$|##bFb?{Lc&b(IlT@pVoVK{=+`)z8Z}BfkeGrtD1kAA zBKxvjEi!=-2VKoleXy_lYU!S^7Y3uDQs{oG?qM;!(>6+W81t_%5O<KJ&*IJI!o8!- zF7~MVwWm)vj^s=yI-JCv$0qy?UwLfb)=5-R3&X)(!|eJWHlM@dUz&e+?I}Q(auCv* zZklcn)5j!x2`=+UVp2uMzqG$OyciYr-k`SUdAT8-3UPtkY6`f(*WmZ}TZ??#ZK0-n zOt$Bp*Rr$b#|Y@*SMMk78+gKyEgPHU7Zj6n$Ig8lvTNTr_74y@yTs3G-pYJdyLq(w z^|>wZaxX3h0R*PgU5@|GBNgJ=b;3_=w47@!Yz<hImPsm~dC1I3_X<6pb^7cH+e8dZ zkQ6u?zS>>!NQ7Nd_j9zS%Ev+`rL;qcp^UH#Z10j!0DIcfvWV5C^X(<nvHBf2xEEVx zOy?xcQjhcgW5;O}iAr{*0nHOIarJ<^P<p&R78uBq?A{`p28g*U@LO^taV(yMzfl-} z-vsrVjyOW*<}#O4zjDmCJPL1Xa0soJJ*)B(_lA<`T!ng88ti3;JI~=i*S)q)ecP8J z<SR|~Qxl;5`O#-{qgAZuLK%4KTw=ipV%ve$%!1KT9PR-*vd?Cpls+g&JJBPsCndZ) zCQ}G_ekjrON3&=<gEr31-};~4^UkN!Fl$;vEzx2h4QdrMIdKoiX?au2`4V|@H?WS+ zw9|Oq7h^c&lh!T#i;QxO<B!P;QE#kl>>j_RG(MJ^C!kzq<Qc2EQ8eBOZ~W^)pK$){ zY)u|Q)~%&Ps(imuv|3f7kqD9zqYOm-#iY{lAu6hTBa2&tR17`r9-(`8LI37kR%)_O z-sN6YjcpDq0PJz83PLma#$elygT3y)U&Fn^R^QbpoX%beWDDJtxIUP{T%lZ<*LXv5 znMU!3JGP^*Suf#D&ZanFdSL;QrFAR7816=PplmGXLL%XRs(Exx3b*O7y}it8xb;-f zy1sr7+3u!tStu{swjI!a;OzS8pqlkqk|y)c*#115A~>16{dv#D0UmUr0}y;!>%1gp zg}p6=oIc$${t)qT;78B9p>==%hNMv)Dco^Gb#z#~Ut#Quv}Fi?#i4$En{8|rYa73L z;)ssDWw&78G8>cu5+Nc)=6s`;ODME*aFfOG?oO|lPUFt0>!g<9#QP#Hx8cU<(HA$m zA|N$o7!NUg?Kln>w~58^si1m1RI)pM={VQtqaaP^hIKZ6`@BdS2mkx!8^(9W<0+dO zVU^t?kTemXQU>|Dtwx@QucRQAk8VrjbDrlT5p4>|%)>9Erh?1MjH$0t-DFr)e3KSS zoOZ^t#lQM#0B^KRj(ckBPC#A*5O##e6gCf>67MwuybHG(oJYg0ykY$-c{8O10=ao; zRfP8@Ss}|~Xn+|{fs~r}ywNnKc;R|tA}=M@&NIk}v5b5D9zZlXmiGY&bM;Dw8|yn- zKfi#KBH;p4924^#R4Gp7%d;bOv{@I?RSkgTCs}2Q!YVb2X3EJ@VNkkJ9ppxkF7pI_ z48$ZkJg@Bd3fTSj3rMUjBZR&4?xAB08IEVx>KFD|vFGiACoQI!szoSujj-2-nQxOu z=d>Sho^K4^{}#|d)^%BH;>}#)tUL)a^y%|r+R4x-PDG3spVd@Zf0RTIOElKxUTB6@ z41>cA<(SqY6?b;W3PO?yOKY!mF$GTg#q3nI5*J!<YB%0Bh@6O{Ou+UFVr=ok*(@}^ zyG<N|Sk8%A=IBRT)acy6(slIar6H@mQSCuL%FrFF8F`3C>dE-eTXO+i1AICoL)b^$ zV;7!<h*a+t9`T5D&CSzrtTUGIF?ZS#c?!H7Bsw3mDDS>J$+UX?BMDXiqWzwGRe@wJ z0kuop5v3-1K@!*+6XgUyJP=1%X8Y?G?>U$&lhIMv2Q7NIguC_Cbd{&+7O;>%`VA8{ zF@prRb?_KvEe|BZ`*9zhS9DbcybOl}#pweq3FCv#$OrQN<$e-P_MI^jK7_a4;W~-O zZETH^y%N>;<9E9c?-S2PpR9Q0%7{9I=@ro$_8k;hXj^Hhxs|$X?<lznUy2i7k=s(x z@&OQ#@*M;OO9fj@c0lm@viix3>V{lnYuJ3wjP$7z*2PL)<R@KN0tp%j_pufD_cc|p z$=mT0B22qE1TO?&?A-B6#o}|@Hmfi^Xf4gFdgP!ZRPd2g8`7f)k?wNXtM#v#ks|l8 zQy~-^A5$SI8y~eHHUVXiK4c}L3}s9RA`-=}x_fa*n-B+Aq2;=M6tlr~5tNiPO}CuV z&Tw!Tnl`Y+emrd7X!U~S)%&RCVw&63rHWEsf&wJr9k9~ACHLD;I6<FgA(y62n|ZzS zQF)k^Xb<Q=_)7u5D{a`yC4H|nvartajTSq@dlD72PJ=HTVtU3FQzLtXMZ|c8Fw0rq zT%|)nnSyU*mFoJ<fi&&S9L&Pj`4<VSINQ$CrccOy%u*YkzujHQ9qK=f-jHE7snf+} zwAxkRCHam8oI66nED&Dekusk<LV@7MyUd#2?2<O2;#uF(F**-bVKBh0V7Fu>=tcxC z-qU5dt#f77ZeZMCgX{YZ=WUY-nSMc~cf4fUrWX`^hEa!Kw4?{;uHN(1@|UkW#g#QZ zjD&K<o}<n|J%6PuOOnnMzK(a;mkLxxnEK+4yR~)8&WSe@Wucaw*``fq`F7b`$@BE^ zOR4O$wC;(R?oq{gj*8Cp8e>%m5`O``y(>(l-EXG3+PhugbFtA4zgVcxV@o{@=n>rB z7NyELtRw#+y^?^(_KQYqajRQ3aS^$PF(Na<)IhZaE=~C=dxq`#E{A%pi)nsqmrUMP z+C=YJZ`#T*KVTORZ#s_IN=2FwM~IGDhAl(ybDgH!Rd_8GhC5>MnAq>#b;9T@<I&}) zVCeB>(mpjxD<t@lhQ3&<{QDcdR<l&LO9p_(8i^8dhUxaW9J8&!AxNEZMav;&d%q{B zC7kvQz5M+3BQGTg;(Ugbo%(PL4~VyoILhEyV?JX7^HOWoHE`io=)Y!uyK~s|44UYC zwh3!F)n8rv<~`R6RZ150_*u8pB`EBCVQLF;dkB%Fk-saa-Eu5!6>Mr!qtcv537!gS z9YHYTOdT5R$9Ly+z<F92D^N{XjAfx%TxRX$$TE5N*n-3Qt&Wez3VJ%{MERQD+K*Xo zg~whD{Bs;82B2Ks?0OOOR1oA13VU?h<WOfs`@zUJU;hmqQJ+a_b3s7UuSsBZyf;UP zd*|90-PdgMkeTzuiFy=j;A{ak@;OE?$(?9=(s}nm0#i@~j*L5E4PNU){3t3_<R<go z*f+6p+J^XuPqX)5`ZEA}-Z47=Nr(0xazDyU2-5?VpLAE;5f74nSYr4f6-8f%#~aTF z%8;EU*ve49J;Y~ea9?ZSG8Iy;+2yB~9GAIGO%s=74xdACVX}|6%B$=+BN%UA-*Fo& zeI7q0J1Uk=F8}@&sNM;7*UWzVd9D64DlI>-OFi_!`~R5w%BZTppj{ei0cq*(4#`7E zw;<9j9nyVhL>dWckVd-u&`5`rfP{2+pSTDA?|tw6#9C+ZVekE$nLYD7&&+PWUWY*i z+hBWmbS;9=uwsO*=;iF%6Li7>y+P!(k)eh!8MP)qzme$rka|~!QH$v#39hZ~zrx&y z-NtafYmsH(O_0*WT<VU%eaV^sF&F#>`fNp(>R95i%STHzq#DpIXBa-h>qS-YQuH15 zEnxHcmi6S=hjqf5C$j>e(-atzzzh}CJSn2EKweaQ_usBYV&im@z_$x~H7s@Slp9ls z2Dl?#*x9i$N^De~dtP@Y7U-l9g%LA}r;~tDN5p+yeo3=A+pB}@OjHlVaiz!PvQKt2 zj&OwZNhKRDmRW0zXBzTQJbKz&&#JdL;TlQgil|&9-791&C*D&0v3ti`2RiYcG97T{ zzLrLZw@rq?8&KjZ^_PU_nkO1KC%*!GXyH!Rc^qAO90ki!af6ND(c!tgfj=f9LyvUC zZcI<UC5S^McSGHM>iL=aTcdVQSb2uGc<W>N56!V<Mfaqr?GJx4dril_kdGmod@0DD zf3Y&GVrhm9fG9veV*h602~9Kyk@7SEin-@S{+%GQgN6i^t18U}7z>3fdXYMtl~>XS zqKiGbH6Uv11A`Z>YrxI=5M)`{1@xDZtkbK7i152S_?b$^3Tz=pVdhC)XZP+Ue7*+b zGx~`~r<Jtd4=Ow7EZpC<J&!F|^+ptXIr5?vYuX<1PdfY@9ZCHEWW%afY1NkD&%NTD z{n9*~XE|sxQshs71)0DHVH=Y#`kKBJ11rrAIYkv2^!M(vjv1``!Opb~LhT`|d_UP% zJo1><Iag`43gwcBuKZNsq-Pk%6ufogpBfm&|6-=<*X2fKJH4;Rl(+zTr}0c&|C{M- z0#Br~+{B%ik(5(QEcqDj_dmET#zjBA=l77O9#GR)NMkJ}s~)U;`(EE6n%K2^`&(_p zQukjs9#nGpQ#b#CbJT>7V^?@npiU@?kuqgeB(Iy>8`VlB6X2(sz9+oZjNihBq-3~E zUc_f0Y(iO7plUsn4{Y$+H-8U@!(&~{bC|2I$)7}~H(Ce2jb@$E)=Ogf^h@+<EP4;# zi+z1%Ih97w{A_Yut@L~9w{^Nihq#GOVhN+{BLpM2^Zkss@7hOiXH-8o2t(XHz&lTi zj^J~=Gx-fg^~D>0JnQ189)rYuO_Wn$GE|a&3=`x5EP)*X#|<1Li2$qTuG-ME35Ax) zpJ7tF(Xc}FNlsp3sY}JFFcExF6Dd*CM!U$5%O>ryO)u9BS9Z=FYz_sIZ@LNI3q1O% zAN7i1JPn4kZd^p2^rr>?bHO)L?BXz#Y(^r?DbRxFVE3HJFa{T=S4kLYjZ|>o5%)7@ zIryY;w2*bT?l;(DcAk6>{#U+TJY2mEf(|q*s2Iw=-S;q=OdR)1gBNO2D$9;XZ)sfN zNoUAB+gfe0(oMb9ME2v`B}eB%g4TeY%;VqQ+EfSr-~UF`D|NQk^`WM#q{9Sx^SB|N zYH&!K{&z!B>ZmoA)I#d*grT=p!<R)KC3Y=_j)7=Oej`UbF=-_8=kXaI2yy!#A!lI5 zJB_LLEi1JFUIN=MKcDCav^(f~e&BY<ZXOqP7~QAl;MLB^orX-T1dSW+OgyrXmUX7u zu+_2E!l||0LSKi;%HiA8>Md)s2Qf62{EP6ApBKYhQ`xCL(9MU%6F)eDw0D!aXzKo1 z<Wy}u@P$0~KN#1tul<Y2P}t-J6zk4dMBE2vEsuUCRb?rM_sT)eEc9;uIcjAGKwBo$ zZy_i_IAnVl7RH9EC7uWwS3iu`zT2kyI-g0+5s3P=FMA9-on-9YrXyXu%T7i9Z<Bl< z?Q@sDYbnYP6};*($XEVB*rwVMYN#a2o6h2l`M9Q+x$)!uNjDa~XZR*kWaavKCAHWZ zx(i=eBKsD?zFuAYO1bKf!7%<}?e(V8MPNRdX{o!uHjeM-I`8aw&*pu93?~&(9Ci*K zgM(y4mycu5q2dDi75<^AYV(BQrA;-?W0AFl&1M1Clzss8?-g?%=#%3)BuoY&!mJy3 z5mUiiz&}R<+P|oy`{n*TW8@9M=Ij4zur|}42MBaH1bC0H!;%anr|@C0N&Sd?@vJC< zqd=+@i#v3$VT&s-WULE>LT<rW3#SPReFWe5kDM6@)|Kd{;3gw8xX$-4M|8U&^3{iM z%yG9XLQTvVt#OV4Y<Pu+O^Qt%#W8%bCvM^c-n8m{cI@3R=G7FP(0$)oqlSs3keKpR zcr3*2gl$05+o}YZv4@??uKesH;h*Y8r0%rE2R88xMeNu0JpXxnxu*eIVx1>wkH@`X ziKmc<ivP<j8J(K{-w>QNICzPB=KoU#MrE?bfXC%Bqo+i^hryj9kU`@7IGlXRgC70C znu9|K>x4u@^vGwUK255gILq&tuZ-mjel{i6Ot{iQ%fmfZbS%6*X_y)p+x_!qE$=Yu z?*C?0w<EpX&4(Ewi>AZ+1jF#PjyK6hw|)uhg#CpuOSaZIb9mT_5S)Rk3L~bd|L40r zfGIoo&dNr<D9xqEtfb>ZC1szJVp>O`|F`th=cV6u{Ok&Y!gBPE_VoYOoHkxy_EFRE zbUO~JI7R8!J(R6fmM{veE1GB8b+z}3aXP4db*U^pe_816vuSM-erReurCn1+viBj> zO4(qLvZ*%kUt}GAK72o%nInuEsWj*CAB;7i*0G+p5coy@hv*FC1B))~eN6)Qi9ELF z_`yB=VdL4F9-y=W>ma#k0^RQ=RrFeaiv}ECm%s-;z^6=vPn7(xl%Y@J*%N|D+Pu%# zM#$*AxyWl4<<Y%5=Ns3J%@#NWxC5&^tLPTkxjV9rhf)?TUg9J=9&xZtq{RpY8dpS| z=Z-s1f!oI|LyWms(B))Vg3CYtt}${__aeI-F57x$ZQ_t~fgHYH_DoHHK9DA7j^~RL z9`)fV+N$mmPQ$}sxwH$}c@78D@sqtI5aA1L{~n$h0xo2Dk6RLENDcnhyCE(={(z)^ z`=K`qyc-_s9^+{d&2dK^RQFEt`ufJxAtRR&zKN-1p_Ts?z`i(x$IGZYiS1dC!XoZy zn7hgZJQ6l|Ga`t-H-#+`(m819e2-APWn@s1`TSDLYn1DW*cyw~kk;W<y)KlK#gIis zCr#U{Kr!a0<ocQS=~d^|<hYn6TH&VHqR-mX>R$EUn*V9r-Ku-M^S4@mHyfN)0cmU6 zq5@XCqRAEghzrUf<-PK#EOxbF??3i!`yp}ZEn4%_8w_hhvC^4&Pak*0f=R(n;NM_= zu;u;Y0(IpJ=klF|Di9`Cp;Ro??Q7q@Mj<Gg#0h5p#JvLan<KcLlid1syFSYDo^G_T z&|{?b3LV&NH2I*CT&ba$?BN@@zkH?}9L$?vgOphHA}m<ChsNwB&kItNYO)uk$d3jG z6}4AxyQRX6$E%-m*MbYM=NJ8CNu>PJ`i;qj?UuS9RQCZZtGjVCM&#YU5@3hYY-&fL zq`S_LOZvZbx#@i{vmFXBFN=ugx#bp~t7~xWgU$W$Irn0mnavR%9-b3q@Y}Rt|2|$0 ztvrww(i1h4=d8{W-?PE<m{k_qfZ~d%DIU9tJakLl3_D*S0JBgHeyD@gXp_kU4-d)Z zf(iUdA;rHK4%Cv5Pt$72(*#~f?#5}rS1u}9;NVMvf{lcR_-|9LRnk4A(~F7;#`j15 zic;Kvm13!u%l%lW#(bg#93z7+yN{%~Wsqu2#K`gt>#)Ew^39R{-tjks#Tx|)#BvnB zVt39CI<cz)kOnqfwx;5cCy8^&*TA(8dm97@=!huKyQ-;@YKD?S5XWt=(`WM9*~lSP z*dPB`ihSw4rB>$L{P#&LEcRD>T+d(FuK+tB5hqfVuRn8o*!pbImq2Xl9d#MC48Ddk zjaNtVqg~Gj*Qc8Q`xgIp(bI&4XbCW-m+<_!_{!!fBPwmleLpD`L}pE~u{iO86qn;= zZ(!d$#`sn5NZ*xL=~41-*!Va0DBPUt!A4*J!5?JzEeH6=Z~|}s)M1Xx(h`DU!$_e! zeO5<s1Cj@}79+xK=k-;1K71$xK$RV*Dr{ii#R3ZzULULgTg~Cir^Fo~!+ePsw*-Zo zlozys^h*0KFWR|PH+j#o@<*M-UuvYG;CLXZA;$ll`m-q+iM`}OyB3_*GlxOxY`oMK zDShZ9ro+LBAbjsqA~W1~B`?JIMZ@1P)$Q$RScLngA9O)FE4VB%mo43g%{DJK!}`q= zgJfUBUiOUY0m#8!F)xIm9`*n00>(m45Wz>}cOfc;vld|9$FXM@Xe*xfEqKEHs|07q zfJ@LA{|TA^C2oZ%_<`%%J-JZgwq9e!hJ<0S08Q7RVh=fS3|ga(qGQMSAoyZEz<rBu z!f+q>AM4pz4t;iz5U8m4IpG`F-Y!7R*OsvNU@`~&jaD%$OUcMoCK}c0`gMpa%r%8t z5G#<!O)j!AjY!8fQBSIEV0>Y8j~BC7@tkcA2Z~FDf^KH9mEhWgUH?$JCH)D5O4Rte z*Us{O*j!uK=3GbTI|#pv>t|Tjrl}#&5mI-PzTdNt_g%hal1=;`Ghv20JZy$q9{7pR z$0}mRR37MepCly@gwyK@3<p<Avxd}*r_Bmr8Mw7yV405n`5$+NZzaS53;uc3_aUgM zB{o3;$R$1M2O%MRG05nOODrJ=W5`0H5+2*;p=jq65b=~K^t$Wwi3E~YyQROvqiHJo zUn6ITa=TCSaBFx6TmG{n)DlbyQK?m{x(D<WbGZGxOdIrP;G4z!IwV&~;kqn6Vxiby zqoW~jy!@bpWTn?5n{9thVs3R^I+G_4MAJl-fIBgtvX$MnuvXCt>;#Og*jw1NsWnTh zm7J1%oa;r^HK^%+feN?!VG=7&)Bis>y?u7^kUN8==s#{=XB@}WKGT1X9V*D#BnqrZ zg_do@sAPPooI4MGets_%l*7i)la5dNIYy-YjU}nBoIP3W@vNQrl*_+e{07s@@PfB2 zjru{&(51(tDYU$oypOUY+=U>d#=_^N5^5|%`)^H~VRL~^Eeb6VVFdF%JlM#C%H?Yf zmG3&<A{K+W7iiDRq_E|*{Qs9J{coAx%Mt7EM}HSOczl(&LwAd63%Lng?K$MMVSZ06 zay=*FQ&x-pt~c++r@kJ$U2bF@>mTw~)m9w5<70+f4xh*qE~)~k57B_s1VT_pC>gYu zg9jWl*k@|fnv0;|mJwZ>yYy&zRP$^U_`IYL_;KF7HezWtS^96$v5MSD`E)pg7#2@1 zV!(6nKYF`hTBTj>WQKQk*nI9681RKh$=>z!u3N);>Ax)2+NCi>6DlP|xkV{_E<OWC zVAVTs;gV21xPG+i5DZ(*<+S8*ueC)BX_YmDaVr#C@71|&%6t>3y!tFqX3R91QTgXw z)(lKC3T$CeNs|kDbKP+)7VL{S?8&v4)1q90Ej-BPQexe;&lRlxGl&0&l`3^dV;vh! ze3I9xBm;;N5`&X;{4F6n9UYJQYu6L-S98ed*N!v56JJT(yUC7Qsx;FCaI}CkWvB3> zlbieU+^5kkZF<6PbOal()hhkjG0GMjT&pIBvzp7Sg|S}KgICwcD;n*bOBWO?@Sa$) z{$wUKx-PWtLN$t|hJsgq=cgN1yTUJw(w{va>1@H>e=YTj`L5l-Ped(=B&qhVISFaW z=lF0^UJ6Q(ti`XlNK{r<otwjNJ*m2%?h~mPcREEvTw-NycG$tMKO?@1$mz`k)Ljhw z+FVVYCv2J;vwwc=1kuXwiTJf(svt)S37N@PLES*q2p*bMvV6WN2C|%EQHCP3MCIs1 zF1{y`PWD<|ll0_&#mwL0rrflBqK*iwk|L#cCiwlhr5Q;_U<gFIpEHkxnI5_PrD#y8 z7dsU5u$%G;g9oX8>6L;-IbZKJWfYc+@6?i}=U>EKP2<lM>&`j#yY+MJZpihT0VXGg z;<R0&5=1d`#Dg@hb}!3Tj&=2KckYreBVW}A9lRKMt-y4MYLwl&1GUj=rGTw0#-?JM z5hvt;>#l(}U4Y^*;wdVUnZMDGz7`rHQPcUVXNz-M|G3lH<+@V!m37m<n8|;EJ*%kn z7t-W(cfsesX`e#K6lhY(?v$mQdhts#RWi)x(JJZ{cR&qC_S)6=lPmV8JyYf)xt(ga z>!o$_6gFLD{ihMlHpZ)opcEen<@Mv!<)b0BJ((JXPGWB$IqE2TykFA6>0SBL%lLI9 zd@|(Tc3q4<AXpM;fCr!am3(Un75DyWb9R?L5M97Knk~H{8VNrjLZ7`{2%_A=!#h7L z1Fg){6dzZm5^A;t0QLOE`QVBSYLfMucb7q2O*_7V%x9`_Ek>$$8}lo9^xSx|mkR6m z_@A_L)B5=y21kYO-8sxO_u<dY#z_g=h{%R+_OZ$VK2`fp*FT_>l0mhMlwXaR7g(ZE z(Ro2P3eXQDfllyfzF5UCS1Au9g;+nwL8_!3EuHrF<dB_{UnyxcCGf_(La8`tGbHK+ z6ndMNyp5(5q`^#}v-=kx9=mbTou6_Ece)<fDLKzLS0^amr-XEoM=E@YsL_v_x!f#0 zq8m7#uFt$a{=h8rt;<LfMe=^z;XrMdw!Xrkt`_@{vZcEnOwJJddN-)mAouJs?S09+ zqr=&*z!MR@aT~zV^swd1VI5nQYBaRs#x%`E=nQ3|B+H0w`Rt7y__~lcZRRTV;PN(> zkvz&5_xO)uro=niA%P3X3~xSY&ce^Tn*AhGkEr?V!VhBo%ew<#oy~Z=DP#gZ$DXgQ zLcLV-tJUt)n8eS|otR7F-uO!cY+E1X$we*$$poRYoKk$q?Ms%%R*Sj)xTUmGCo3!u zbOJrLpHA>N20uUbj<2x|u9Y15q~J5ro7!O>Jl1Qu)ggrS+@Qb%81u#&w%AikHpGtO z-1DVnf)7XQBvcqB*=4hq8sEs`_41b9BA>Xc*`C<YFFo*~4V5!LHQJ%V(6&N2KN*Nt z;pT7edhiv9Hc`o#fqnfs!`sc<r0xe9vDWhJ%lv(hV{GBUJH!1ru|RDIjDwzkW?`(2 zh4MZA+ILw2x;`{Q(odwEU{wtCRPExT+I#`dG}DvR2(M7JkEcwN*MG?~*pX9b-csN_ z#~2aKl@LS;ENt8OcRRxygp2@?=^N$w>B!;OHSgmo;C4vV%INQgy;WBQ9o#1`&I)Oy zWzy*x+0)tX*f5V%mBdNLSO*C$?V+ya!&$!e$aj!HaM7!(`|n`r-Y2CzuDD85KAG7f zb+SF;2W(p)*u_vxop1H{oV(a|Y+;|VR3mRbAOn^Tqrgm^Dku(EtX$#JkQDnc@7&;` zOf*LCUh2`Wg|!Y9TP#`e$+$u!oi3AKpS&qjYhW|TI1$5Dj93ob9?x)p<ou&nZZ*MN zW1?exA2}hVl3I%1+C=3`%<hvAaIRkSV_97p07AsD<%`Q&NMJ$y(*)CbxM`7+2Ache zP(qv^+P;u!<W33lhDfuW<`Ke1D{5L$P|Jk}6=H=wVzOAIs~HusQ|@edb4P{(`5 zxH?$%UX@iCV3o!Ud%v~!sA33LtW9GQfZ9S}@8hbk#V~Bj#V5Xa2UdS1`xH!jL!m!( ziv4T;cgz@yI{8-&?hPVD^4&{qP^3&C<?&m>O@jx7h~7Ne{vqmUY?sOBd~z!RwI3wT z&~h`pVESB7X;<39opik&IF0l9L@LF38=eIqgnoaq0$hf!#G&#fykYPN+DKA725;JD zgBUsrLm1RkWwjb5{-Qs7Ym`F=6`|EasP2ntRDjeui~DHV?WIqI%qG6&xms?UTsufy zf+ce7N1#gC#;Gk^YZ!w2+s@HoDzuyFo|&eK)I{N^rf#|i6P1j6RiA}6g{bA(I^7$U z3h2WX$@YTy6kQcX!%BXr*pi7mKv&QLVfFGuTa?vfYrNK|=);+vZ;>DO@2)ew33ktl zSM~NYFPmCkGhXWA+O7m5>A1Sb$6SY?co-@Io}BNOP%jVnAACGdfc5sDd>!tupnEK! zy~&*PFHKJ)Y1DUY0Br^hCU(i!4Bme}sid@CkiD!7%_7(hqp=Gruhx&^&Ammtt^L_> zGdfb6PW}UUs^GBwz~#c`@#q<7kn3{U8oAO><0|o-lw!2%af2Unvc>MD-ge6A%Ki4R z3Sp$vTXGL^Is8=l^6i<A_XTOBK!`CF|4c&r&rcmByH5A57w+RuBoz^K6(1>Crk+Eu zoCpai#bMpW-nK57bvNJgzq@ukqK8gi>Jw_%bKbm2DMA4;f4*P=$#ar&$7SK2GwaSd z=Lc%fB|#W{A2lA8enHs4b8WfdJa%h(`gz!Va{o3@so{O<zyZ~HX)<I88HIoxIa4)< z`+XoY1!#pe3$)G%ga#4_eQQt&L3dLgtjAL>5m|1E!}=LOw3_rwhfNQ#Mn)p$c}`@1 zlj_oPdaG0jcz;q5+L2zqf!njC>>KbjFi;MoX3Xa{dhwNxNNfy>te|t~1({vkzI7zy z#^{9m4EN2gU}CU3b#+K6(nmpZoRp>`ZyGdL;b6&!IIoIF@zx$H%`OSDik3jOeb2In zYHAhFh2DpOC=iY#00cT$sWLQO3U)@OnC_-OWi|bg;P~_jM9t8!e#(}Rg4u=EC3y4h zK+LLSXy^WDr~De1p<6juTC*1-FTmEir5(ZR0@Fal&LdLP-Y!_R#}a#f>iCwMi+EM< z67`MmW$bFMVAwL5u;DbKYsQ}FbJ{`Z8kH+nFF#4GUm(bE>1l`o>T}LIV5qL`>QPW; z|5YyfuF?qpm^w-9{oyBkWCPKcWp7YzHtJ^vr%X301HwRYQfK)pdr=co-erhj=@W!V zAU16IvG7~(f+?QxWhml#=nVC1VtF7L<X{Mq1hWuCq<dI0cuSq17mC4xCm@&I40G$L zg_J%A;e%h=&XjEgPc;k~qsqyugMqP@pbLbM@PS~c^9q$6I>w-rSRWZ+*$S_>r2PWz z0J#L;X=*5nZQT{awhWugFRxPnj$Y@5spiefdNnCyM!ugA)39H9;P+B_B~MGtHsQsS z`iqskn=WLAeE%UCq)ax~mQX~m!#T1Mf7AG2%<XTZai<hyU&OqqQcQ6nTn|@kWYU>} zA~<9&Sv|*6&?yjGaVDf$6`pHt_iAw+t~Z<sr`*I8sbpbdPpi&UeC+-p?+7b7^LBVT z8%b5Gr?2^zJr(>h8>Wqv$-}gDc`D|TD&}s5p>NISic#2QM!cTMDDlmQjL3wRewgpP z!8)tR{Af|YYbLa9r!XUsEAz6Y5IB-{;?Jm*Jm97mZ-|6_1m9}<TPqCP$kX4jEM?(5 z5np9`>yZ@o_dszSXJz-o?izhq*qoz)Vw0BJ+p_zgVSHN?OU7%{*wsB@_I)kjT8((P zq^#abo%wFvy@NsR@3%5Uis_~8>kwtI8HOz}<RtCS<%h2HZSWf-(Wot>{E7<-ZH8ao zuutdbysQsC!WWx~XHc>2UvIbv4Rk?y8jis7*}VIm^BN!mUU91U-N*D-COM&u`SdHn z@lMdr>vy0X*UbpDc0+`g;wzJ+@r+sdpWg);^7o_(GMx#ysC9nmi1c~;^S&_R{+Gs| zj<`pqk3Lu)^{yg%{_KBIF~0DXo$B~<8Yy-0odk_rr>ktAQw7ZGv-hjYP4il;2|{yb z!``pvd$J5ryP#N&d5z@H1WG@nf$zKfdjF*Z)O9h>3BhxTs8Sz#4=>3JL9tNm(oQ9f zC!OXiUN~0r99Gx^KK?^Tj08KS#DEB_o=xxspop76$%Pe<e9|aDnx=p!Uk})2d?@6v zJDDv};~poZfVJI)6<d{($g)O%Bn)AIg|d!80>3^cj3#zZnS4qVK@&Lcw<uaH*HZpj zW_Yo4Cx^(F?~iPEvueqk_S<E@<8n7eLol!_6K|}yANX^$$?!U$IxuEm*A@1#h~euy z;HZz&(bjPXt=KEge8Fhb#n>gD!d-fe*1#b;-#t%(tFy~|7hoDFs(SQ?+AB%eag7y7 zDYa>xs(3n{tDhTG->6BRW%JgwZ{bLBDgI9_SuG^JIq>(aoSWt7c7<PhMfQkh{3PW3 z7h)3#JkcUXTOQQ6srbh|La6}#ERD-47n@b0-R<PP-GX`J$ER1vLU_mXn)0%bZkxlV zc4F}7S8Hy}0{ME;?t-asQreCe!z_zGbqHVI%#}CTr+;f@^{p=|^T%}Jc6#Lk4k#M( zwVCAe^}T<6-(TeR5PCV3zZ>*kE2wnhQ3g%sd&(zjZc{~;t|PJA88P>?qZVFS!`?L) z=L1B~Bh&AaQ9%;Kw!k55JDMs}fXTr_Y>~QW*fDIU?3&^+w^AQu9N(+nu#z6S0HD^X z`k>g}Qp4GHI(kS#Sh_Ahm5?&ky#${IHBt+W+xgwTBU1v~ndUvf)7{BdxNZpac+Jnz z@$QKHa=`K}gTva;T8msgCHz)r%ojw27ZT~|B`8C>{i4>^1Hnv*u@Y5Y-|xx}oqCs) zaCT2#j=WAVzygaVeR>=U$FrWXq1cYfyB*UvsyJt3u?v=kGrFAI&E>R#=u_dCr}Ov3 z#BWhs@)p_YcYl^e#|Yr|&^zUqWF5AL8GrREw3<o2Sx#5#{RumUK<$~|h)kxQYxsJz zI=-(-JNsn5F-|tAwZNjPZ4tjvW~8cHt&!WNV-kfT@KmqFKrvlcj?A5)6Ht){epr;s z1YT|{nGd&@z8VL5r)=ldfZyjj^BVQ5qz4<Hjr=Jb_?Lkr{L8>i#DVAZZ(!)3*+p!^ zzQs~UQAN`ql+tVK>~--6V-wJ6xy1a)6D0q&85b0Xln_a;`7r*d)e`Z-CZDP4l66Pv zGXfPRw>UP`*>UOK-SvF$%Y;<FR3xy?^&h-Z$oQES`KV-1_^}_cZ=bs-q=*SYT3XXs zibw!E9DQ;hp0NGjx@kcRPk0$RR)r5WFn_99Is*}uj<Ju_>s}$)N?jm^g!crO6j>bD zOQd{KCR%X`KG5qP55v#&M<6J1YVSs6T1$9=_w|C_Br#L>%iGWHZQP^5C4nC{szv8j zl-i=Mlzwn+T|=P$^<$TKX!3?A#h<Uz?U)sUV1`K>eRdK%O~O(h*tSJqCeg0-D1u%o z6H&=My|PZ;xOFWh-t8(yqIYNcc3P=trT5)U@4@$u7AFW@$akeLO)z7*;B~{%bpVdF zy|9y%vbwZE3YVs14>Iey#obsZ>z|N%Qqmc<oSU|5GJ$s)7QOTHA55r}XgozXoAziK z^%-RTzKvPLBJ+uT5Aq^wdt*#a_H_H5bVWl+HxHvV;D)&Tmt$EUYgzlIHO+a+obc$> z(Xhi3!_p2@;kQ$axt$Z?<qD}e%f9QWqPu*u$MT`AK}{G5(v0Y>pW(+z!{|Gn=Q1$k zc5SboQoDSnDet@xvbr1>RrX4hAG5mVtn}3dUu>0`|5%8O<+)}bB=>vOr;&}KA%mxp zAIXFSOG$q!YMPZ};w^W)$N533L(#`B=Q{csyxzWM;5TR<w&gHUwJ6M|5a07US3mBD z7m&wWu^41BLa%W1fDTI_l)g$dK{l}4`z@+4N13Z~_7%bK7r}TJ{NHGe)T!=&lgPWQ znD<^?Drbt+apb_hC~Nai&JwLFL*AS9ec#g8VL@xkTP%jo#(TlbP-`fR#_6?ruy5Q| z&}^v+=OE@fJ2m(3{I`M;IQzYyY7x~iPDeeT#u8dpzYo%^FQN&g+VBv|v;kof^xi~{ zTzj;x-#F~FAAJ89nniBSM!gB^@q7jVnKy7HI$Yw7?rlnC`v3;e-7KNY0RQiZod>=? ziiNXdUz&}#`jD9YeH6t1>IT|IN=K2C+ooPD|KC{vId`1`Bm<k9yaYUPsV?VtL-BD^ zS-?o(s#VXgH&g!@A46cdfl4`c4uw3>Ro&-BUjYs}RUs&)_WHnpI*A;P{<T=(Jzqp) zw|UL5-As(|jT*vcSf|_5l$_mZn$_>4*5bw73#5NuF!6y8<6VhjwY}rnSfKyUX6=eP zw4y-PL8bE18x?sYtzCs$%u_!>sx&=QKlRHLd#w4n_};rW-!xOkad#vke<=zC*rNB2 zTde2WCFZYn^$&PEmYd2-w8wkx76E_T-ftDSL6M3DH-lAJ;*<}}D|$){9HDoV2k~$K zN9ijX5ts(T`QW4XV3%$Q@E_<<RwU+1G`s{rcyuL!^&W-HDWNhOwu?978yS2%IG45u z0dD9;h2`&kcy&bI5ebi3jMvpP6DYHycYOLP^?t-giUVuznwZlkq2lbL1dD#)p+M1m z65`l%J;P>vG^~dv@NIq7ivgAJ==j0?k(GT;yWvE~Uo~qehS&wcvDK7N3GHCFh6FEt z#W~_c{~GAe6Q++qb^@Y)T5+J*Nav>r?cWw!Dl3MVw7a5t(CLzivl3(5HiP`E&^%mt z^7pEz#UbePAEaK~>^5pag@-VTlVG6&t$2XuymZ2SD%Mnu(>AdmVE4bTxR@O0h1{g1 z0h(?Z<A<#x6_wmDD{5n4S#Ho?B*uBe?xr&*mx%boF-?J=6G;1w^Hk;R%E(vdurK#H zY`g&>SfpaB?&A?`t^5EaiIl?8i&>59#I0f8_b(shW9j+SGI01SOxns+U;k#(nPn&U z!kxX`>EVEGmYQ}35SA~X#M4*6)kk}#+Z0P*_rBe()ZlB`SOd_12jf;RwYzxzB%JCb z&{R_6M4_HKEG6Mm);-~R;*mUX3oYmZ_e&=u)pjPwrxT0F2PiPPNY8~F4tSru;h-q< z1Tdz}ur&_$8W;WMC$iQ&iwvrdfeJZEk&~b=wE66J$dg9((<)Ja^>0=7e!#ETK3K~- zr#n}Ge*3;69M&WF3`H|<IE%H|L}p9X;f>i?Xdn&z;2@!)k>mO}YKUtero4vAnF!Z2 zfNiLw|1L6mBiaLFt!<ml-Z&tY84lH0Di}VfBMl=r_O4R{grJ>m+ZxhYz;CZ3f#A)2 z3P3-Ilz?hdhi>f|#o?8`HbX$}+s9MKh0|VNc%HEwu9wX)cyjYxMyRp%PE!9p0@Gf^ zwJ6n#<zgJ`jl1vACcY${_LiJeoxjsozPIp7ZaEX7*|EP(#xg65qgW6Pwcj0!Q0GBz z=OUdhjA~3Ic~VO|Xh?Thn4r*23DNxVdQ@WZ)>Dzzc}~N4c~0l%mxdr&1-9*MJf%T_ z6J;(Y!;Zdcgh610s$Bc_NU~y$x~J}hHxFS_?3R}Vb7(mRlZa5ofwOYe_(`Mio2(Ai z9|Y6>J;5KPUGH|=)lN>6la4wPBn_FZgit&B@OG0g615o{7QDrJS1XL}7|Z75eakGb zB5nC~te#HKU4UgLE5M#<%TJ9Rsw0l;XgpiI2_)^^7(?q^e^l1O+u?I}ReR!3VKi00 zTdUp`ob0XCYcU?+JgxP{cH(3K7qHNE#?%+9{(PM!n_cNucLK;Z2^gPLFnXw>0835D zrdzjrFyE1j31Qpj63!zK3WlP1<SFr8L5O~8c}FD^AC9aCbycNs<uYqBFwT@u-iM=8 zYsS<TW~nnJI5UU0oc(;s_;m(kmMAqvS#PyA2HpDriO;(&za|4$$H!Q-BAjo=u4&Ec z{(#j7;Cy5gqf`1mz(=r*T1F+ggg@CS{^?aLI9T?nr3SYubR~h`p_k2W(eis*jrrQW zrN3UF0BQ0`Ii&33_UW{fXcmzK8GJbwMyC8>J$Q_xyD-HiX42Anq<$hO5h~A&c%pFl zFe-L=i>>H!#In1S5P5l&1lqo7k#waEp6NS~C>CKztSKsF@Xq<z?*ZjC<pbHM9TP}X zN^%Q&9E$0GoQN7iljxC=>yhFT%3mji;m=J>%~ZH&6SF0%tn`mj@wN&586JDW0?j>W zG@uA*m`&2Sc0DuJ7_ACZr<ru-rMM0_zOEW5C1Y*~WOU&{XA~qJe`=Gr3d+VDUYl<A zUAdkG95Ra|9ds7`B>3&ieI8Z%_FJmPG$IQX2R0PZf|e)V5!}Yx$6ShUbpP7zzkhbM z49=Q;rh!{~drG4$v0;{>OOIjrz0#Y#ea|i;LKO?eGF6C!ZhUr)7eF<-C;z+lFsI=I z=P*)rFIW+afzKuR4jM9?Bx9|Hdr)B;4^uVzBl`u8K7;-wlVe<)Pbfgf3}DmNQlKsO zgBT|yoQ2^7_urqb5U-U}m1OcbRtG*mn~Bmd)=8!LhknP?X|)O9262-bP~;5NsW-cP zxj;3V4_<}PB+S+DjXSP($r8-z!)2!OMKa>akEgiGP=6tvz?Kt9X?L0&*{7})N$ee= zpcqkr8rO6u{053+SAjX-AZw`rko=e#5~5++#mcltBngSJWZ#vmC9y{)D5a?7I(eE> zJ27v4%hN>aj|9&tGZb}4U9DAiQ{Q}TNBu62=eg-F6>4CnlZic_@bNZjAo_Rc=hHjO z;fS3F)rq>=46x2kGI=!iK9d=sLBfNVH=+aY8`D>F^z`bWDn5KiK7LiQ>00jz^10BW zTM<P^1+PtlX*k^SL5nA@Fnv6(EHn#y|GYIP1+i;&o&t3;XU^CbUAW%AZvB;!!G}n* z#XdVr(Xx|+pBIu~kDTSckAL13DKb5E=e&NS9D6n;%LrM;({so!!5Iycesm1KA2S1G zd(8(bj4bRAEme0#6{|Mox*42ccf8^)-?+VUoVE|=fRjO+XEmU+*dvBiD%K5!=$3VU zn<=G>;r6mPr@mU-0{;TOZo`y^5ll^_&Y-c)dhi(MK2cTqb)>OPg;&DlOo``#eWF(+ zj!S!yCaXr%9vf#o68BqsiT3)?e(E1r$;wXrvE=x~Q<OC2&e&DE?GmmF{yqARQik&= zTV^jB8H~nf(%i$+=o>jz$5fD2_COpu=s@1Pg*lBP?&Cl1J|yg~9{=_aUe=zWg@oh& zyBMfMulSE0vL#ENUMmp4A)>B;rtMEl(I6zdiQD8WM4Go$+E+9>nJFKgX0W_enw#38 zt$Q>UtpIPy#4~>u%RO^w9lC?4y6h0jqXC8AJAyx{yVm=-qqvX#hxPwsf6MWi)XcA= z6n>Dw2^!|ls4th~fh*83_ugAmh5{!TTszQG+ISW9!yif~Y?ZkE#?Xu)Glu&G0}7hQ zu7592??M2x<86sj&gFMCr)5NY%id1^zr0IV5h#bLZYjY`mfs9a4(C`{Eh8J}Do3`L z+xGPC`>jUzZ?%hM3SPWL<L6W%ZqpzJuqAWRAjUN}xtQP(OnM?6yxuxB?2H--)co;; zI_Li`$K9iPK4!}b`n~D#%@HS=)z#%9vQ|t#TI2GMw~A4uML!Ka$l0j_z^T<osiVY+ z-fPFNe$p@T2f{07{MppwugMYpEB#%wIe3D4x?{00(A}f5xuTl<9+Tf~iju-B{}-_p z8R{~^Mo8q;(@?=$V^z<Qg4K@Nw$%cXB1f$ZKC|0ysW;hVllSVB_Z;_qI^<7kih~MX zz1aKqshF}z?Z(?IA>NDW{1;Ruw=wjJ0QB;W_~;8Evcv5TS-&)aSH(r%0g(4JFtaJ# zGF~oJ&ml*{<f7=E>9q3RJnq99ETXfqxzkT{imQv7<xd-O#wJMg0ShF{>lw49sT1sL z!GG2mdbQ~8Q+p-7+v0)Ez^BYln!PzpGTA@5Pi2sX85b4zh`Ux4;56k5Fs@}?O|!}v zW88qUj0L#-f#;E$lhnz#Q&k%^UFo&)5qxPOh!e5ztADdKWnp8aY}(}Bq+R!6@z?e0 z1!XRQwfh0xvnTke{=7-FU3t1CZXR}bVQ)-YWo)ct`PKQCf~h?4DW*H_AZ$bqcM<PV zD;V(zD=bX!)iqdCV5j?EqF+kh^$T8#3%s7zLVZ})psjL(NB5lnqPy(WDJi?TO#-q~ zdqmV;>%g~)dTl)JV2O*Pct6Sl>F`mX(X1gX4Dtl#lVbRcl=$6}D03(xf>!a~BGEvq z4P3$ShCrm+@t-0rEIN1uz(fzkfg#}<>4=Bs`oN=j6#KJ0rhlJXL9Epi*{82SP5ouD z60!jE8gfvf6p<t*M<I*|4y_Vk%LAXw2RPrM_Ys!F@YX=d@9t{N!17<o+#Br0mb)nJ zmfrlCd)F4-Z{m@9`+!+xj3O-4l*6wQ^aInGC%;X%@?D`$Fe2pf9?yFE=bd2Q@TdgC zuVK&F#Jxg6|FOH3J30>hIWJ+7in9`|<}LCJUZYBFl7czbgR^eq+%%F*@oKy8{*@oP z-saki>PWpyG@BA8M82(2kzB(O%E$X_NayPvV8d$4)Ts8JkD)Y#R%sEf+2u)Q$CIUH z13t=LrHn&<zDVw+ETpHm@KUqwym=~bfe>5GXhAmcH6)Cj1v0J@OSb}C<^Ds{s|s(~ zvv{yR(ou?i0Sj~58#-B=K5w*hPRVn<pb_0u!MO5gDdA?!M+YEf?r2f^D571*GRlZ; zd60B8z6{97|CnmQ7c_vC8J`f}RS-&nZP4!3syW@^ejkB^b4_(7%l3e($owlwT0=gN z#e08IYP+}o<dv7wr-zM=_vk+~KgmW&U_MQ)yQ~)~Y#{pyB&JPC64M%Rmff4|{Kk)u ztJqf4%rGf;mZF4>??*LlF+e6}`146*FCQ`%=o<t2^0pGV-_%=ccl>Nrq0EaX;WO|g zE8Qto$ue+yKkN}5=E-VYg?{{&dfNV`_uhpf4rhPE&pWv5Q^g~$7r(1-p@q1jZw;!+ z4_vUk!UR%h=gZ19$yVHMkja(#<zU6xdFBi3{YzfHd<{UwQ=KSePDtpNdpGXcx*Yj} z@4(dQcTh~aOR|kYSILF5@>lI3lQV%gMgj$I;d(FG{}9+peldBKr8Zz=BoxQag!3<_ zxi)hA%T1dW7GKGLD@nfKA8Ax(w78~8xxRKhVRMAJa(D<`0^p0B)HCE!IOK|Fi3k*e zy<X#aRytPkiW2mZ5OPs{5t3LP(R&s~4dzed!y~n@R9|WAjA_1xM0PLrI-DTb)GUNK z!7QJo2D9XW9iILBE`=cO45)gtLGrr$4ucWoW;v+;UmmZ2CCS`q_lSAS-V)5Y@om*b z0hSz^;Fa>LbSiXa_9b@u7v8rCzl2S5I0;et^WoEnnNWLuB!INo_CADT-aq7(hOX5I zZx*O;N9YCpSTj{}F3}#KV>6oRN2D%I6#Zz|%bbr@fmn(FZx8IT23v<)u$m4h&lsI- zR_w{QY)hyY=J8bbhx;$vUBXdJ7fyg*s>4W%d}*JDn5y34oRCg;=NxKUvmrkY*0sl@ zs(5f4OE-1(djelE=6rtxyB3<&J1yR5?k(ZjB<4coOy-7X$pUCA!;;9E`mc&}EH%Cu z@DKDW0YIfZ)y70@tx-*vQm8>;PJrP|#5g1e9WLTtGy$@tvk-hG`xf+LU+-|^GtGGW zx^k+lYSrjYt)ga#z-v9cwBkngIWbwj2ea3V`B$mV%qicBDFj>JzFL_?r9MHqnNxTr zB%7bxp#-@k7j8#CiQQwHoeh+Sr`_=;NRLw>b%0+-maAliY(TtJi+`lwq2G?xl1f}5 z`{9@H1kw)8$oSk+-<>J{QjU;{!<e?c<(~Iq#}&Vf$I%+|7T1S0lxcgCy=H#*DJT3T z7+pG#Ar0zezfkd99*|{G<g->}Hw3+QpK<i>6gOt->X6%9N5S6jolyk5+sG$tQV3|4 zOf0RykFvjQZS|~WH6dyq4D{Z3Vy!i&Zuny>RSh9WsU~{XXd&Ls{idu5Lkv{*&iLS+ z@?qw%Z)(wXm=S~aLsP%-&iyD!(2Oj5$!2{#Y<xUWyJn0zo44vL_Dfj2oaVe*WOM2n zptSa)r!*+=S)OaIxddmE`^^EXN;dZ-fZAt1S(B>$qnpJB=vk&^E`BeIC@E4X)t``t zfU8ba2>K`39Y_5~EcZv>Z~jJ2PjIz)I#XE#GPg-<uTn^%LpC8-u$6Rp{W&2PpM)p* zK#rZBMf2v(2Vmh*YO~JQE;FJc;McLCVtUzS(Ri+t!M>H7Yy^tw#NfyuSQz^=xtPv@ zQJnCqvzTJKD@5~qN;MqjlPTA4_Y<UkFQ4DNAwA!f0@%qlB%98~JS_ylIPcPnoXi=E zwzqx<a#uiak-n|Btgio@GWghuilJIIQ}Pb&`c(GN^u^73iUICUdymjX<L`Jl(eK3R zdl5li@O=cS?7$hr^N$B(j0~zUL(ajsG~atJ0^MVDg?k*ys~e{Pn9GHqA|OG&vzC-G zJR`{xkkI?-?YCF#J~aV6$m38N5;FjO<?UNX03o+tl@1D9YawWkfCJHZ>!M63-=-5} zq;mHAf>qbMnmdWAmtb1mNo3wp!4tj)X%cMZ&(xveK1N1Vu?6%XF|_j!OWi@3CwN{h zdk%CqlezO3B!~X4M(cSeAwzD&&8Y{?Ri8-Ez%IR@Ds!v=sDi^x*z5l9v5zgZcIvxD zXCFa-bg5Wxf?<Axzz+Ce(|2p`x4mJ*cB-hJfp!(DIX7><Gaw<itH%oEhh4G0KX_}) zkxw3^rF4}}`-zJB^rj`>Y16aRX|wvZ{`QZlNYln|Ns9bSD!+=PBW#@j=z9~lBwVg7 znOL9MukKf>I*Mhgx_|bbU;@F1YH%p`FlONw9bA*m_@AvV9Gqx&ecI$W|7I8(DhwZB z9J+4I|1z}o6e}pO=IkO&eCTt~u58YgD}7-eaxa%g+)>o#BAhi}xyQ<UdD!$U!5&Vz zzv=28#^VEz5e-O3@M!OeKj*&=*HKc;=Rd~%3SmfPbI5Ve@<(zASvd2Y!?RpTvSgs< z>_#FeaVF2`LmU+46#=6%*T~t?YqF%JC!+A@QtxZsyW40gH%#pbJW1DdRf=CG&J)R! zGVD`%AR5`VZ|p9r_li@*rjGCUzf7#Wz~I^(R04cOq$HL`%G95LQO`lVs7l**Lnd~u zsj;=oPdf2hQZ0c!uV`2M4!U~4U3!oJxzf<5>x_ovt=^K_70jY2>x(}Z+{zEL1@j-D zGPI1{p?s5Gymm2ziMK^_%*H&(k73?^N*A^a`IN|hWM8n~E#XHNtjh4@8v;;;lQgX` z_8$|2KRz^4tQZx~NB`{4c{f}oX3c;Hlm9sHenXwQ((o}hKQya3)Q(>So)!SE{Oev4 zOj|MsazLV_k;u6|60M%m$bZub`*W_uyM#jxxut`%Q;sL;hY3Wf(e%DrJ#DzPh>+7& zqGjmmiG#}U!aPI+KL}0>sgO*;<u(I7!9gel(+KZo84`oz>5<z#=BYG`0{{i$MH=)= znAI-}s3OL^le^Gq5OEX)$Knnp2{!$=(+Qb1dWXERPzq)CtxPQ>+{C>@%u>+YImESo z_u36lhF$J|?a~!Plv+!E_^AIdW2S^Iwsf)i(}xmih+W~g?89lY#@;o^mbZ}_^x}3a zgQ=Nddc4tgkR+n{>w!h#q!GPIpCvKZ;`A$oO`yt<$8>W~3^{KHT(&oQZ<l9ZwC@N2 zb`vaP8N?w>nlG3qzj-IBFAB3B30-e(pFe6=nUD_?ARB24Gb|aj1xZ#v7zTs^WS;vB z2xYkC-bL}R6nsI&wrw#*Q7)82scrAT!Ko}7-z|&I$;7pgPG)GIdmH-&AklLbM8^6u z;o?C{)@bd$WQfIi%f&xg<#(hz9`F(IXGIM|U@N5dX7QPhMe<-&QeOA<U_MaJo5LQE zef=LLgl-e=-V8%}?Fh4e0#2_q!;u6$36yrECp=!X#)OYsC&IKm`M*V!)4UG(l{9X( zOVwEgskoW&LVGiBOLXi_9~!v3lc>!*Qs>Ya1NPKUOYCE67|fP$zP&g&Of^6|5lNtu z`Izkv%N3=^OR;B%PlnV+eimL<2nSV9Q21yt{B{{*IZ6kxD3Hjd@0K&kTBGW^7S5g+ zN+J@iYP8|`5F@#}<_*6t60o*<>I(UsyEj(TqEuyr?(m1S(P2wYDQC3&kxQ#B3*7mX zKNPEk^f(yw)%x*x=d%Ku$2E4qI+_4K_D8_i45bn#O0tqCP+nzojvx&#o^G!i#dNTW zs!(#q&LHWuI+7o;MPxY&4`U#`nDXxJwIjeZ7V*TaQMF-%H2ZuRE96*I2K7#Lir6&) z*9_3|B_6R0jjeTk&cj+6{(F&gh<K<3zK*LY67r)`EP6avJcx?Y{%yI2@e-QJ?T$C* z-k!N7xVTyaorHQ&Lu_})d@9>L)>+IEVDs4u`4Ntn6zj~NUOv@<1aX^)HS7Ph5J8r+ zs5id<6c2E3lk;W;5Ss3W84#99gq&!kbESemKcY>6G=Hc6_^r_FxL(K*aHj5QKGtDh zW~<frB^a@b1P;D`aTn}n$Pb%0Y~bt=Qh!l)ouJLi9en%EE<J-Q1?9S0BC43+Sn}PV zC`B%ZK7mo%LAkpI;oyZd(XNiQy26N!G9Zipg^|mDDeFwbiYY4cW9O;h=hPd;&?v96 zhR47J*jRH<jSPQK$9?>gPR(CQ|D+2-Ua60<PaagChUq=HLsB&WZ$r|t-qHBc;6$eP zr6Jyb{LfA|zXo%draV~v-`<e+)L;8}QQWCPJn(|7hUKVuCNCS#<DkbO`veA<Kj9%E z<5~_p3(e~x>4nVSk3j^?aEX?lu--8l_e(_<Y+E7cweNxze;@J)c}~#xlF|<8Cng{Y ze-4YS6xZcRtVeCpJ_X3tBF4E5Vc#5Y`aRqWrRdjujAI*xYm9c@@eCcbH~VVSsk~xG z_QN5acBO<fq01lF=jq*D=e@tcuy&_d^Ttp2fS3<Itf%xNQR!8Si9vYDvDk`$LJO?Y zw<&$NlN{eMo#*Z%rU|CuiRKAI0}%0>t_O+cZ4PSYSoyosCU=$#%A7{Qt*^b3j^hCX zY7D}%m}wPb2LTk*;~>{chJ1Lfl*ooTa*9X0NqJ0HvB5>VW#U8$)`y%SIOja6FI?#0 zChwl9`S2>gmddv{>8|%3a}I<9eI4{%isJ)sF%~-fW3l&LDW-Xq0Mwy4`(SG^niif^ z8i_{FOw+gzkmm3d(Wft`s65}E-dgb7A^Zv?_;%)zB5wln2rmlW_VcJFnH$l#hL6Xd z^eWq!-1see_e;PD599CDf*tHC^mLAwa{J;E6*5&pl>he~q-XmN#f!qxD|^krpkhk0 z-Gor{Q?X_1Ane<WLU%;}6O-^(MZgct(?HMxI7S05cf3Dizvx^@+;VhX|K{Wk^V`<& zeQ|rB>m*7bP-QBOCQH8N4a>gQV_Q7IQ+hB(y7z#~0cL9_>4Q>4VA{$Z65~X*F!w*H zx^OLyz@+a7BzWA6`X{l%%UP2z>Nf<1I3cg?pQH2^h+EUhMLr0JK~&(9U1g8`KnC-S z7l*bU{F5B#)k_m9`+G5(L337<V{VlJsPqVb`$zs0wtmN?9&C-_b`I@1Gbhgxakg~$ z&p>;!j6OVr1z{a3g&-c>0KczG(#%mad6?)?%(2g!<YX;U-1ttqHJ-<neV4!J!c3jk z134J3*`yGh>2T!XNhyRl%{{5wm`rf@^lTWZ{BAV@3IPFf=0rioLHSuAm#GU_OUUUr zBMj<~rW$V;QIYBJo|VgLe=xo9(2!5BH7IF@k^^yqZ_(vZsX|<WrqjoGZ}kyme&CQ8 zI{6BUBe@)G()RI3WRruInxDT=K3R{D*NK$wCx&(ASRd))?t_Hm=CdX4ogUjla<_JQ z&oSv@w=F>9-vrOp9RF;b{g@vcP?<5ENMATX509FfI?;W1<&8zAAAa2yZWm51932hL z*?=yWUz4Gx9(WLqO0w{;A67!&)*}9C{KHaA0*8~Z*jk_LeaaMCQygrGR^fQpevwl; z$2td&LBjMP%Ms_nP8PSkvc*Q)&(r@=Vl~2ip|ACkmi2lkJb0A>KoktyCB>4MXv8Xl zX`No2bnJAepx>gpkh;K5af{~Ij8?_X4`iwvc4lbk9fC(eiR5u;?KIar8zyl@2mJW+ z9#0o|u-m8#x5Y8)H7M|jToF*Jv89mo5R)GX9sCq>$NYmhy(LoYqaDr5+_RRGBxQ{* zhP}zWMKfisnqaomak5`%3LTD8d$Q$he|T>3987f9VI!46GZkUZXg4b<WqY$KnMK}v z?RCwFflWHD4YY895L|x<oc7bUmS7xrQO%%Z=zPS@V0BURJZvEgfrz!XSOVS|>TNX_ zZf_M+2yo-3$eW4dp(8)Kt^O$78#}MVO0=P4HKeN<&#+1#;6l$$6EgoN1#K{Gd~ef& z7a<XXHL&9<QnXbvOX%XokP)=uOh`qgyn!bLz6rY4v%F~dIFJw^jhmV7zc@kO-^p=9 z=|ZmC|DSR&3)n*<M~0}1xNUYH1<-6Ig`%=}1ng_vv(!D;U9nigBC$}O|JuToR9x%M zs~LYo>1$1vp)oP=20SO&$-bhb6D)A&ZrwbZU<~Ju0y#6qE}$(O-pW?vHN><IXWm9D zAJ{#$7pthW;HsN$pPlM@hdXl%AkJT9&F?=Ui3F#c3K(Efv}>WxC}~u0!Lp5N!h88$ zqYQhAol;|U&;LW!S%yXZMsHra8${ZmyQE<T5Rj5iX^>PJ0TBkIq`Re*l<sB-=?3W- zy1QX&$KQYV+TEA)YUY~xKF^$UpU-`t&%fzh0&VW<w3`%5f%IPn-+Uyp-OV?Ta}b+# zu6i#eCn$GQbEb=R$G9C_M~td&&5$}F5MDm@1c$orK*iZMpjDP*v%I)DdS)OyUe;VE zrtlV`9U4D*TRnji(DLJ>=@-<dW%a&P|FZF+C|Q*8j&;RPy3qV!O2Jui@D<PgajW24 z+obg6Mzm{$K@kX=-8b#APN@4@4S%;yvVi%4M03@QR;0gSWYV%@ilm*C^jN=Qm9wI) zlLajMGuaL^`hADHz(OFgb~h0n>X{w$5E0CitpD;-EGiHQCovcy7>ueUeCBy3Z~B$p zyM?p4)U3^{&a%YuWeX1%%~6#S2kZ0DI1v`K8Gnn3FwGeg896CI*Q3{dRJT9lUeXiY zErE&1CuyYd>{XUz2xpg3e68GauDFh|QHwo4^RkZ8;0Op0{B>Qh%&;<W^x59uCa9ZO z+E=}`<Wa=u9d_m8XJ`S*Q3&3^Pq(Sr-N$<yPai`Gkwd?hhSeRM2@47<Zesn{Qtx(K zS}L}lm=6WUR^U6Q1cp_7=_#Vpb%jpDg5;z~W`DXoSn{m_SzhrL2Kzr#jHO$JqWGO; zwj5uDD+|!}hzA?5i)W-&l`l%!+=;nhazsK;Tp~MfP>^DYypkzCAYFCO{nAGH7ng+o zMtec`2(72IRdpz1*(i6S?DL@i9{74|!-|>ZuqUElqEZYr*tmMw;@EkgQyP*G*U$(2 zb?r>}>VdoD|3-?BZ*9ra@eckC8tpx%y5*X$Tuvu*_Nr4HWil#YI3`3qqZC1i3H@_} zX+5s=mj7>S$v(;I(b1;GKjirtt_0zKclHVRBXa0w>K5Xx^a<r!>w6#wrA%6#Uu8I5 z5HkBm(z{kS1{*ILT^SsdPU%=+#?g>#VToENZV0(<o7{h=Sr4RDBypw*bsN?2vcUM# z;(|d-IAJDBJ_$o$>AC`l^j1uqgaI(h(KMY%%0V6u?Z6jJUQJ8!tQaw)uPcY86O^ED zM|JeNiSX=CFQ|bEUU((HiFdx~xekn?%Cs-+RW@2B>p8oq-@%vjmEY56sO^Rx{aG%F zR-tDJP!9;PpakJ`7GT(MfmI)O!w>CkQKH*%8Hkua@1g=X-70GB=;#vvLYHomLCFak zMK#P2ipKDRdKtvU{G-(Qu}GNXMFwh0;q|-WE)TOJMKDY?uq6O?53ao#@UKNNQD3`7 zVfn`Q%9vrkS|5HuXAk!U@)oJ{Kk`B^P!<_%!hO*Uy=X#v6)3>4C_6PpSlLSanG)W< zPPJ-)cS5TB?x=W0y$ZKd?PB{uo2hmo;)ytjcvr9^b*E+TGl%Z;v?cGg`;&J@I70{! z(#vty$N>yro|nUFN`nYlNbsV~kg3mirpr_YOX>4|@tQA2g<QD5{xYIbuYS7o@R+xI zd7V_~&#$}|zC4)l3WRm&Mansd7v3y}->nx?f8)LU*-r{PT8V!w%35kI{NnAdL}5hf z>52<D7X+a`QpvK_gU>$$VTj~SA@nEou`yZ$2JcZ=`mFDf1$*tqRf4-;WAuxye#&Rn zN2Af}&MMg|kyN8RB-=wljj$I^qOj*}(qUp7Bk#8Ei;3)W(Xt7wx2m?-FwX6uGi%A( zSFgBO+^j4pi%_f5BD{IX!!as}EH7<l$ptCXtiPs#)u3IW&&H3enx9$yztik^G<6)p z-7S6vLt-a1-vkqM3fd+^n()uD?0Vhu8q`by>LV`EN2E*%%#eEx7Kw{~%67}+d`0zQ znv1Lyl6|q_c=z?oJp2FC3I2~u_P+_o|8Dp02>un&#Z429@TSxJ)w#uSCi3A2f))mj z%Qq?9H?fk(d-y=qXng}nXwXfxEAR;zZUN+)ss89k&(WMTtSed+&r>!KqMPu(O+eRG zzBaW|`XlfTzZUHzt<|&7Hh=qO3WB^1h6QW--_>ZL_{g2Y%9}zXT&iId1O2v!9~)cv z<SkfekD$-Xbono;LZ#jKfc?<Jl7~e;xgnAVXSeau?(yoW*PtRsJp4jtg9AD|R#J@$ z6N6bA8D@MaH$zG>uN7z}HM7+W%1J*0C%)TbWG1%sae5`7ntlg*q?^tm-Oe7-7Tkb8 z9h~;aqXdi*r>Sw3iVD+!uVrw|(l>oF)BC5ZzHz@9R(OH7${MdUuvU#G?a~r(w75}f z&yu(I8}r&{4E~d|Ss+!D>@6Exg1nmzu+FL>hmO0f^Jj8L$)_VF%Poo8UvGG3HISdL ztIn_PNKdLT!!GUW9ZtJ@(*lc(zl;-k-QVgqBnsZn>L)}+Tq3Cc?gOR*IlESIm6Ie2 z%kN!D3mO>YMKp$}w{ZtL4A#1NbXZVs0<Fsy!!C$LZDYWNy3x0m?Y6wNo8W6JxyLM> z5s{L5JLKJg3EL@Wv(|iS3}aH7pP1in(5SQq)&eQ=smLGrrF`jb+z-~j(1RJfP-<yg zzWZsWLf%XquFmS{deK#O@7_1vC`Z!Kodwm(KU(j%_G;l@I}0S;Pb9|F%M`69TMnAn z&7lD@-vpA2ld~u|T;B2ms+jnOcy)eCXjb|XqT}KIb4mlV$yzvzQE)#|5&uI^*9T{! zAagf^NlA>aYc;&`(S*q=Z{7~@GZ{fIgnOUY(|tc8^B<il#jU?2LWm&9r(w&FGnR|> z@aMQA6z<y#c%*FATUh8U9$~tPua?IyUAx<ucZig$9v;B1Ln21d6EkHmdBIXMnYeB6 zo&IGD5t6K!V()7H#T{l8QX^Z2?WhwHxBO#ZXD}COFWoXV5v@K9_qI!IJ9DKgT))|a zGLei>o1Y{br(6zwYFhJLJc3*#*=>hsgNGV(L48Qb_IU!B1zI7hdMYV2EbVQZ=yy8I z?Jy$-1^9s;<d?3^+2c|=m2zA(wW;SUV92vlkIU2WESsZobC&nJS=$G{Nro$oOc=bz z-sIjpIpwM2)}kyn3NY&H0h$%H+>U+J`rcmIp&v3Se|@ELS2B1oBvl+<eKyLgibhP; z{I)hqP+Qpt=iV}=_~tsN`~tyVk?ahy-u&JkfD6YWkd?@)r*J>mwu4biK+`ecntE;r zw9VbWy68duydzPi*5)mda`G~Z_vdv<g_Xx<)&WSjIWo~3c~?RIc;YgYb@bFhvm>xz zJ^yLu8^ZxCB>Wpn(7Y~#Vf(;8UpM~<XVy+7bYON$71m~V`Am-}`2G#K7x0)3ySM{S z|8$-jF+=Y1t^KUm9oSP*e)JT535fO<3Hj6#a=|oz>-6;>h#Wz@{%_9Owsb%6Y|{C( zZ+-@Khs58|Oww-=9rfrt#%J%G8@}{<(a`>J_6`?638kwO^V@yfnyfmElg8UW)zn)q z^|~3<y3TgYg%bK2u)KDNnlDy*5Ma=y-<$9YZ~wdFFy!HK8-FkqSVUxh+5|wQqvCJ3 zaoMz^WVgY4ho}o4?o`fZ1r@3e^IZ)Bf4mSvFqt(qr~KLVfsk>hxoc~EX@S?)K%sAM zq+G*BH}CU?=&tanRlcjDu<=}nr+g6RWNel(oyI>HFHXi1ThzRIA)i)XCF6HK0Zy)+ zr}7P8QkNx`eri>ZX9CHPzWBk<Ys^pmEb%79vF1p`pNdotB4_MPS@78(^%v;9SE)%} z3*g15cQ#o>Qf`PY^m~=-xdtn;V2D&Y(=tyDm25L!9I%ZH9%+U%4!fD$Fbd1h`ClbX zG95clq$<<Sag>cNNa71835ii#LOHauC2oikn{?m5oz7XWPI&*q^xf>T!>dvgl5xHT z$X3dMphG`-1=D~XzYf@<CAJp1J_Wp%UoI-mJO={lrT&tiqrmx{;5cw9lQ-fewFQ&$ zfk79G`tRf~3Evvy!efk{&)E7`ir+;96YUFXZglz&pb^Xa<{MERK&?9B8_M*NA08gF z7--{mT#Rp9ZQ4?x52_xz835~X_t6@E_uFING(OG*K?Qt{dy988K_VADgOgewUWkit z!_OyBgS0*r0j>&-*$O_t6Y&wyXMKj`x9Z4D#|d7Q(p<j5D0*4<2E};DW(d+!vyakX zR?7b;<$E7yd<v$jA$#RsGaZ9>sLEW7Q*^&WgNH$mr)w)uo0&L%NJ)0t#;PcWaS!Q$ zPLG>?wApZhwFk?kscMp=oT_JvQ8C~$0P-`F7;BZf84L0~A{h1!W4$ESe~Z;0pZ{J( z+B3ZVOKZ9=##U<g{1VJ%(Jx2seYL=(+Ha!FTSwDU_yPvrQ{<<IgLujS_xhiPRa|+& z0~_zHW|dU86XW^;#c;CsFlc{0U*U4K1|_6zaqryY34cy_V|e4&73I?|EKDzF_CNSY zWJE9QM;tMgph3$x=B}uaoXlwC2=2()--7z~mHfocBT;NcV^DMIDAbeS(IvS2(UwG4 zBU_<hlGks}Q@cjjxzv=SDw!uy=bbf%X3HY8(bbrlkwk?!ug*#-mighMC^YG;falpT z2u^Wv`#EGOkBDS7O?~(mNdMCckCW(0ime7!LJi->ZL3HQz;eMSH%XAFf27I6=J9m5 zp5w!!B+pJ~TkZ4yn-0V-yO3clqhx9I`SKZ(wRaa$<??84;tK5QvxGT#GRVJ;x~{Bx zorZf3PQFRoonmx?HI^?`kBn$OS58oq$zIWo+#ad4mXH}iBHw^`%<t-SU8GIlZP>Pm z;ulEm-iEu#;ON=le~>!I@+W@}8)&A=sz!eH^HuBvUG)nm52!-IOAJ7W3tZb>oGhEi zZ!R`tyVBJII5A?CDUV0{9yf=tl;*MuvFEnmjWOygmXQ=Yg(46E1D3w^Q<(Irp<6R@ zzAj08MNP@uLlgs!2&TTm7PqsC6b18-IK0x^PoG{3S&|V}6)01vCg3=yImWH=Y3Kz2 zoxv>G=ifAQ2F!JAFFJRm+wapFk>Vb=-&SJ0;zrNXmJO0C$A<f9koVR}RCwz=pT5uU z86uTD%OXeA`W7T$B+>|KP>Z~y#VLQ>{v3pNY<Td=U!il;J+iuv`rA6*`K9|;x)pE< zH7~J61f(CiGmhN8Q=0g(`Bli<7bUP>?{uH36w7=eOj~O_OVZJ_RMQ*ktlv-J-Ba21 zW^|1A?%};9=1183i4J_q*cK^a*zE+okWi^{S?r{?!!OoTzgqQPqkTum`UihNvp7jJ z+cfVZ&VnsMX&QLwhqA5bd097JsIp4Vw&}bSKl2y=xo{Sn-^_&=Y6{MN;ZOU)$2<ZZ zm!k134xljzPnV~U3+h0ou&=-L)r9+u%kf+%o*~AP_?DQchu#5%CQ#BzX!Rc^_lea- zX6k~RD%&iWgnp=mx0zL5mS}?{Z~Qvp{J&6vkpHx~;@?E}^lwWrQ$f1x5!O?mlXE)H zrQ|1G|4@pPg~_i2mwkZUe6A}sFG-|*&d23_NH>VPJT*oPG!6r6{>d1z=%Se)VF&cB z##d)&AVf+)6<=!8o^#iU)I$D+$st{m>Xn2-Auy+00-5IDp_*E`_5z1k$>tq(GoUQ` zSWi6?kp8NB6qyZ;>(STJZjn|s?t7hZF%edQv%S%}75JMT;YOvwobkM)8KgH+hL3Ds z2SbozFVy`;6Z*<P<P97<-l^dlBWcVStr0H`8r7BybM`OJTF-8Wt{>=j%3SV7QW?a` z@91TJ`2g7SJhs9=Xcnt5uE0oOF*L}y%udBtLQ~2g!1-{C`@O+TGZpF<3Ucs6p-GL& z^HKRKfO$DwxwZ2#;$u*9Kv#a{gFA%tb+=8YG(kEp4l<svdmm2w>G2LC^u4I;X@QQ; zi1^BH>Dzf_9?-?#r<bl9lDrOZ=-po<2T6<k(I*~*<`X&P*H50Iu`v5Jdb+Q#=hj+| zpk+vaf<HnJM1gM&27oD9v%K8akAR4~^Bb3I$ubOA{4gZN&S{$XFFMC?d)3OBo4dXj zQ8t(DH}2&APgerad5mhA-M`L^RI*iBL7UDV)u)R)$eJ!@LPC&!OrKKw)03Qu?9|0K zdI~Uk_qnr(Qw*pxK;7_(zl@fYC->kSDAMYLPv5WpW0-C1p!01Bw)WPPlyjJZJctqN z6n3%?aMu`Da}*s(psB3-j_jW>W20*VR%Gpc8-T!SVzw`8??R73sA3j2AnVF;eYUCB z<<%>bdNNr_D)mC;X*<Re9i!$a=H+}ZOM$taDv^^Up=OgUY?Eis^K2NroV@U*RD(AE zG;Ksm5i$<xVNAU4{(hXEM)&990J8hqC1$U9xR?t>=v?{DM;Agn{qtSIvppn7Necbz zPYr~Aexa}vQ(P+&w*H;k;YqQJw^KjFs#Wu??WvfDn&jU1h4NPlwGX5`%j6Us>XZio zskUiaSgaiv+vw|`2sZzPPe7SKY(#CN(=sh5bM=>7c6?UH8$fDb>^SiHtx^uMO5KP> zo{cvRFccqG{hZ72T2}FKfFjER-cI0Vviq^=9qtWtqWW?`LXFVeU|Mr^gC9~r{{;}a z^2E5*<ih0gD|}PCb9*&Y$@Az8X$O<Pe-D)%GU>aybKf;Tzt_OM*Vv=qW09)KM$XGa zW6;Ll#R2RH_wR!%c0Q6e(~>^BUxISNM`~H)9NF}^o_5mqnI#B@0o}c~n@I|EFQf!r zRPMME_dwZrg{clM+0=A%UoQ_qrrBKjh)xWpg13F+<k${ZD=p4RdQ7soQ~!>A=zqt4 zASWN+dC6yvA;CZ^&Y}EpqdM3_)N^H{<-a^JoK76Gj_v3P^;XV_F3ayZAbY9P)@MV7 z>)4}Mh7Z^v&_~QLobfMWh96;ld$48yb&}GD9$`JE(UyZ;7a48tVl_rxoZnxj-=rgT zqpnTn#RZ{BE;sShV;}g#pJ$7;`{_9tRh9H3`Sy?lTmkXLOP#M!wAMjK0+HNi?vO<z zc+v$;DUHtB=kWwWP+I(6uuyq&%VqG-hv~DJLSyf$!$>_2ODKT;jX5VwNzIr;+8p{f z7~iAFga2n7=NoU(nyGW(<ITb*i1@dZo>@{hG{D<P;8pXv{2hO(-UXVk-5cqZ#@*-J zO0YMXNGTxw^_(D$*r|v3M9`r>+piBjTn*2=Qzv%l>JdLA=~yxPxl;@vW|fe7j`89h zzMN?}73%RIQp!QwX$75t^`yHFXtl<N?Y}9RA<E{C23oBR>~tSsx#+)IN6L^%5gv1) zX+ig6jwra)T>ZXh9=zMJJA54qvDY(<_K4^j9j(-!G_E)3gdn=gm?TT!(Oa2)UP~^& zR1-AkAyp!0Nwq|4)6VD16YpLm?c}<cPX%~9u^QP-Lp+m)t6r}TOjn3AV@Lu8Nm)2o z>g^2z?ly7<k8}*M4$JJWzNbs*;fr5-ft`pEl?j0#3myhKac2WWGgn0WBlsTUt^hZ| z{x8r7S)E8>M!O3EuM0SY@h3w$k@pcg+Vs@1C7sau2LC)_K;e<n*x(r0BmPNC;r)m^ z?2kiQ$8M4M@He;kKwghCy-&zVJgx#rN#C1X*T+fKXB6g@EE)!Wq_SiKfH6|3Ih({E zA&+v3-W+f60j^o2zQ54s+>wG$!B6<?F?Y#gL57J=%mJ0I$lRdq&kuX7o7$5yi_Li* z+Z~bw_)*4)>^MTL+2sIu$9SsDr(xBqxt*V*fO*;kh+FA?X`_(MSdlc@QZAR0Vut7= z%@C0guJMm~U?cMRsL0<V%#^U^l0PoQbG)>CKjNP@(?3SRYv`%(zKR}snIxCEf*U1y z&mn&2=~9=YKKT-EU=R#pw-&NF*34Y@<@U;b{)Fb75so-IZt<A*+Go3*YjYywBD2r_ zdpY|<MCC|;4}j!aMa~aDTA!fWtClXUP_rv)VUYbj1ur?`V$X>kHuAL`iKg-GFP(*3 z29h@>?UFVG<X(q178@suI_y3e6(3NCos(3IuyjFgM{<LlqdqTXkn2q2^t0Y@$BJB- zJj3mGHv-G0-EV3PGiHo@=KJn-u!$KRmM<<Pt;Ixkzk7&3)WHE&+bsGP>x1IW`{xO# zzs&qgYoy*+t+4Fc454&^e*t&zy8JyeIhzmK)nlB+YihQ0bef{gnw@zsCdb%Wu>Xbd zCDCcA@7G=k%}5E8lXQ0kTXi1M)wx;{)?89?6Gvl%=s*URh)?i`c-@au%Itb{$L_th zCuwKWB(K%RMPA|*zI!hvhMdTeCMdd*ti{xBcJ2x*K1HWW;`1)a3w{8nBir&6+wXO( zb&X4GL#2iTcP%2LK{CdTrjAWnr?1L^7=zmqjR8-X`xDvwr{#aXJsFslMemU}dNK?O zM9P?5yn1|cb9Y_|3B>0_gRijkBKb;&$I)5UlA}A&afd{4{>Jw=;19IIb7gPrZuE6t z<LIaPwtWb!o_S&<RoJ2eaPp8_2bg`eU$U;$ToSgQopcNkI~6b0m1x@Csok1+-}^V~ z?b<+SM5_NTj<URvN;<b}3kh}S%q&Kzl0oNgR5%y`uZDi7EEiWPEL{w1bJpNnmN^~8 z9W*4+s}dA$zfjtJ2%J)_caEMaCuYv;7Sq{FeCxuG4BsLTG@!U6n)vxpHxijqfxaW; zk%l>ew1D;5J+L99*$xP9<jWR&TnAKnZGh$#4@!#q4|Z(BzHdjpKbh(4%{;=HRuBm~ zos@$4uiAbr3JvPJsJ-b2EO{#;L++~4w>PgeGLovR7+u#q=~kO03al-w3;@leVpQ;l zJI`>MT8kn5Y)JV0Ee6ITwx=a2>&0-@oK@ued3sh{N+A$EbDk<BN_bQlae*X&Qx6v0 zxmmI#(qV9{?{?93Z}IscK%KXCr57;al#p8ivaL0A=p>h2Ln^(dfI7RAhkU*HOh^|P z-$07D7@m#8r;TTl3JxONU&)Za1x+nkKhIZFr#E(df-c?t^>iM)UIF)B=Xb07jB>5D zMl+8@BMSj<;wO^@S-~RJgQapKREUJbTlFTob6HFe`f}^1C?*n1G`y#<9yE~iDzWPD zEjkfRb7OL7$faFUR$J>IG6VpKkC;-V*zqo5CKNlV?#kK@>NutndarUwC)@VW*F!;r zgvF>n6(1%JDZ;bAus75W^D6s!^7nYN6p4o>oH3#52%s<>zpwjG07GOwq-`RO<T+y} zRlkp;oJ=(a<?m3bak6>H>N}g`g17|sgr#F%Jxb#=@@79=6yM#2?b)`gS15WcU`|rW z`4L!!YSmGu>!JM@JoKx+48qZ$52N&tU$W(`!PZO`x393OO3o*}CWW28$v$Vsj7DQ1 z=f-;#Pd-_nvPaCQ$>u|T5G1ml1`ozvZMh;1795MlD-K7K#StP@iLMvRvZhq;;qTw7 zO5Gr46z1vqA)lUdX0eSnwmu<izr`oB$MoqkwM0n&GYU+r)7vW%ePz%AZKoW{<>`L_ zk#llh>Cw?>F*KzOEn_&12)$;+k3J}Sjnt5>Egqe5!P2dlF%qb1hZ@7<@54C*2<E=Z ze`dNDl7r^@)y79NX>xVPfmzHE%(KStF20OH(ek*G2YI4crM!*qMg6<eEDR2!cgaiw z0g9K9EWOU0<$eZNp>J_pzq*mHjpYjue={VaP0xUlzJl?EBNNh1{?kS*O$$c(=S+p^ zIN%$FWC<;D*Wq=E%D7(~=Biz^S3K#o$z8u_4JE?(g78nZQFCXowSFjXZVZtbGUxQ1 z_nGng^q5x84`(U97TSIFhy<6Pw^I3!DgR7;=n_8gU%yv3HDaii@HgGp+nV@U>E^%S zT9$LrTM}3G=jX~&JAkd_eH=2vTSJ-<W`SBex(|K|_JFcX!Zi8VBs6vEi4!E1$V%iK zV_9N4cPyd3Svej`qSI_ZXvT<c-z!IRosbrh(kvVZ91!~LE{Q54pAe^MAc>w`wGe*2 zBEYdYQ@+{HlCkGADyevd2^Yr~IW0vQ%h4Q&<<TE)-vTA6-C%&LO+P{E#vP-XyvpQ< zt(+9j5ANZ|AjS5?E0<$Qr=7kRObYC|UKN^~&JVatJb(N65d*Td4O`DqL0fMcyOCo_ zTv6pdO$`O_GE90+6v>t+Nb=8JY+%Ko6iq_4xR(HXGiL=;QFZ8_9<d4S=fWEZ`a#^F z&2q2l)1A<u=5n?Mlk!}M;%Vorq_WSNP>oy}wjJ8ov=~hRh9BQga)b_P@*ln}90Rc9 z%vt$j+^Itrkja5-P3R(dRNu)i03$8W5GU9b)Gj~<v7>w=A25Xb&9dHOmU_ezA{}+x zT3(@1D0-R6)+6C-M>qM}^uBdD-)Vls{}}1Y%9MDp^y#>v_F~Qi@Q8+8nqq9uAvEs| zj(SAQ*pAHA;4UY7C02IkUlEJ<80qALFd;m9(T`h44yYRWTltO?Y4aD-`|!+6#-o=u zlurlKbY!xxrf0ce<f>m#;UT_1waJEZ@5WU}Uu>|@J<Q?!iv`lIecJ<mLQ1{wg#s{& zI`3LC^By67{)HBZwyngKO%zux{yzd&h6>O+>F4<J@)Xmy7o=UR9{#77rgKwmwl72p z@_<kBF#l+Q33D*>;g9uO7o85`24GP;?r^>7VZxXCHj4sQ&Mx=N$3$kflXd{D;(IYX z<KX|)4?AflDXP_HgaU}i_S+0X9S8Nh1dr6t?dsW9+s|}Eek8={WO*JmBr!Wb`$u3d zM0oQY)lEkxFH4QcaB3JaGE=^rAQ<@}lElW$=JGo(!*q$tCj*Ruv^)5R^AdjI2XO{x zA%e)%ax{$A2g>@0*Ip^VG(G_}?4zHzo4!PJv{(T6^CpJneQSq`D!@K1&@R^<|BH93 z97fM^&`rh5(ZKxf{0K<Po%av{imG6Dxl&Vpdsk_}O}(fQ0)cb+DHjozu85x1A?5t? zX5VXEnHk|zv~mb8A^uMm0QVrUh{z65Hn?+TdK8MYH5!5{6{R60$zUH&<|=~K(ubDs zTJ5}DR5V>+?-V-pn+nh?4TD@9OC0#o-Ln0Q?vpqZnT(W>0f&;spZKQ&e5f3`k!3#6 zPOIs(-?m*X(P&V)#qS0OLpkF)o}!z#>|1eqyXV@#o?mBQL)nUM!<$m#U$J^S+;24@ z>2+_4#vziV1#ahxD8wmmiw>!?*`iW6R7($E|6-8$ZCM$pgoBJpXR{HzNR(GxNO1Yq zdGdGL@&md6i7jPK#Z(g{YCP9<5A8+k<%7{k(4PCogxu?@^XZOZp)+JELX4nIuh){q zd#NYJ{Wva3dmZsnupGK|*(Kc+_7RyG<CnLy^{KeD%ZY?$^1%5UrZ7U`m?8l?8<OOk z8^B~QzR@K{fSQt_;;UNVx&ZUesSuz){L(0&1oHJAXHV=!Uq6y*0GV=Jh%-WCGU!fZ z<G7deg*&%hsGU*b{xAm2F!@xgc1=w0)9L(H7Rgc&o}Vh^{gtF%1z6mpB0Be`fj(r| zLGU+@_isWWOmVp$@Yo1}&C6qNoZiS+VygYRwa_TW@p23q#p9#Nld2%Eup=UdF;`8j zCJFUuDXT?f@w@D_Hbw^Sfp5GfEzv7|zXbU7l!$)C0szFz<lVkI8Uv5B?bLI8V)JX~ z`r%N_-@{Cy!<R}8Q<>C^3CreqOD_855SHFneJu|Au}Lo78J_@ngf$e6oxz%0xb>2+ z5MC1sMkPjobsy8VR{KHc+u=yTJ+MIDtL`g&FfGO-KE~f#AzYXeX?ll6O8tp|tZJ@c zYlm*B=04ze1eKq(ti@fYc_Au(^Y>K6fe@7qzi!k&F!Jnwz{m}u_c%w+QBZ<aPhZ9c zWFa9T{onP;K2$8Uyxp6>%a^!)Z1=v&U4O;C&#A?yvEq`<c>DK-ziIwG{x3DSiuJ=Y zDSeqhi}O8T^@Vrn9^~@IuRwb%BHcb`#<z6l-5TwAfs{d+X8A7UPLf!8!}wQ+FMTrJ zE<cOhUYze!EU_P?kSh>wVOq)BLK*gZ{W>|bjMudR>)g}+@;j9nF!`H3y?kDg_KwO8 zDwUm7lxU6mAHCb{f#+pj{ZOMdp>wQ)cq?Fbr>q%U%uPIbwPke=J}QgajyWhRG)sXE zPo84?wMsqSVH33}F9WeXZTU|<)w}GNkh$R<^Ku%osV8j#1k4vYcFhlpizIjre<G5e zD^b&C-RvE_tFu8^4cHjtV+-rKTI<>qcMk9YC=ufLR3(clM1OyWi?WcV>k<_T_Dh?p z9hv{CJR3QV6IGpA3jMI6T8%b#D@a7~B`1A&{ysW<kUAK!b>IHPz<^n8eJi)oM`!0~ z3$3?1^wZl+=9(W1gn0HnTscdijj!qT$C4zUn?)7a!KwrrSl)S{O7nZkd-TD1F;wtf zF(l8;;wL?kpg_0yXP-~zAAm<D)}roGElduRc;u70WTN~<#DX_kZD=%;2#KfWTUAoJ z+*M1>k}402b%jYxGDj~XjOgYJ%AUeuRr3KwU#mUo>;RYe-XCWV7x~_Hw=cqW73{$z z-?>6T#PkRqM*q*>jvqoZ^}m*|l79g16=2pA*zg!NHK&4aihB*2`Z3q@Gknx{3$wNS zbG}m`peJ`AW>CZY&Vo6=0*Zn5OY;UZKVGepxJ}`)zLM((urN>FaUbzI=s>!j4&Wh# zo(5oG3t!#P2Nm(<3_z8Ch>`I^y4O5?RY1RRnY!r37!g$=Q8eu~LhXK3ChnZR%I9Fl zc_vY?R34gHBeDSw20&oGo&vD43n?4QRTzPq72#{pm&Y8`=g;tvV+ZuX*Ay6;To%u$ z{>eI3>|#7Z!CfG1G_x*~md|CG1Z}VUY)e!+PXJ|6RA1r1b>_d$9wrH&IZUPE3ch79 zdSC|~p|E;XV<>%$@&`N*l1$+*S==pc{Ec??SzhYR)WpZ|y{UV(o-0DW(NbRw51OD> zOKY(of;i6<sT_>4JHH$V=j=88S2b^<#9t?2GktkMwc-j<Z+OPz>Q7=hR4P+8l0>hV zcEB9Iy>UhNV#KnpaUpn-&jF!B#CM^x`6nWX)2biPyJp7gZ)UFch9V!9iE@}TqC&u9 zP}%L8UTI=pDwt2ZXS+bEFVHFo?QtHSz_?JZ9Gb~UgT*FSw@;wQ9(c`zIM;i`ymHQw zh|(PD>kxF$;5dDUZo57{(Kf`RsYA<v0HD2I6CWW;0U%{qxGdA$Pnw|5SyZ5k;|G&7 z;e$bX@qwM<uMZjAU>bWterGKK<R{kiVr0Kn_d+A~hxdH)qE-;5P>{j$kG-xHGbtj% z{cql=8iN?=y!Kf$vU}XeCXLk#pJu5rQg{c8*q~Z5!US>YCFzE?{YkI;L>1U3@~85v zZRJG*^rjh+6Q1nk$e*{`&Zb70nS&{OoHU>VEq$3iYNs;)=!p-F-SPgUuZZngLir4I zfrj*myb4t1Z?cL4RSh8a);m{jJ=A};LVpd|^Ucc(uEnVU<*`Jeno6mF9AK7+&xPp+ zpwcNw1!#bcArYSv{=SkKmge7}%INgim`c_HlSsK5y0%O<*)15DP#Dey`$S2tKA2Vo z1-wjalp8WJd_^1n5o7}!L4Q>RwwuT?;9(o&QG{I)91H3^e%<S|HY;2unfZhwmyCXP z0Yr9in!sk&9GJp0;dCSSp6)sv?}gcn14;Hv7ES?X;lsC#y)rk?CZ&JX1Dav`k6_Q> zrtPD%+t3ie(Jf~|>@wiJ;P?q>4`U#3JPZ1Ch_G$VyldCHOTiHtBg#6^x~#%D=<hUm zJ7aq*Sgz2^fwo%0yt(j5F@HgAisUles1J@hx^E-DpBOgiqJvQk-m5x`<au%T&-bF9 zAS|Pi`TsXY{$CXFyRMGUwsg#SHSY@@g&3#L8$~gi|HYaEuN0kMdRK_L?iaWTS5Q+2 z^yBp^$Mx^zlU&Z&jOM&|BO-{c>N7&ApZ|#<Co6usmr^o4xhsz&4?`^(ta|P!oeeN* z+6mS2oKuuUX49TheP@`JHU~Gi&y{74BQuyjY5N67N|Da;1(zBIREg~DuouMHhr2z@ zOMZal1M(ZiOQn3w^;6~?;huO4xSd7>Xz9XmEFVL}`4HD@smoUevC)^9%z+EfQVmX~ zq!PLOuxWzs6GrYX%!{r|3$3@b1sXvInYkZsMEydd2hRe;n3eKvg+4?UxDR|1RV@tn z7$G21*>JsAWSGc5hg4z=*i1u@Y~Xh|_1WQh!x@CD<wnklA2Vj;w8hGV%Z)ToCo*Vm zaEXu;Zs#V258H$4PX|owzuo3fMLKQ8f?RkP4Bnya6?1WtM=!o4o$5YBe61HTO9-^P zdm8r^*;pM#06^3`1z#ZlESjrkp)m~x*V9N>#k3RqrR(aHzoY0Hs4qE0@cF4KE_{z| zM@SLt4SsYgxwHcl<MCZPc<v1RMZBv{kRWz6Gnfc4!{2G)d3Q9SRe~71<!5%&LRmi5 z;tVA86MNzX31^Yj&V22_u1<Z)lEFQ?66YM~P!riwm9JlL$MZwOGHo*=;~M8CZp5V$ zj!Q^2e8USb>G#g%+X(c<o%vq_dI|{6KaiR`T-_<wR7nBF5y~Ns)#p=L7rVL=B<Z*O zyKA^5Gv>Fm6PFaOZ#en+PQyd2;B1V^dyY@#q+HbPw7#QOCQ2+~^(Q`MD1{X$C#mF8 z4=W2sAKbhTNWiiPYk3I!u12^w0nTz!;V{iijd+6e*qcBJ!cF}IAZGL#0+BpX@bS`E zc{&MW0MbK7U9qF_OzI^x2tdpv&ReQe7+<bgn01m`AMXiPrIpbFI`>J<aK7R>>W+RF zRI2Cz`fa}ZBkDMEcY>FMx>Y{7n`vMtsOLAU?;|WmYyC|1*;-VT)Q^GkR|J1E%00Br zcSbX=1TlQvdcC0^W^RLPZ;H+Jh9w~#O>zyM`W@}^bx)3)2Cog&&g~wu^AVErOUV}e zC#eFH!0LzaV0YZ}W~%CDI-eTL8+2{3F<Jvw-|pi*-zAJ76od#Py2+<VZ3zI*yo>{+ zPIMm5wEj2_**^N)Lih6ogr0ov0J*n(*f}?^2CE%x<uN{KH@0Rls(rNM7mO2ZB}I^) zM{CM7;H`N1VWWcAokSVpjrj#Omb30!4QDPddW2Xg&FkLR+IA{03A0~-j!kjaV`uLG zkXohh><VNldV~6kf{DjddC`%>ZyPCD$cEU521kcg7PD-a1?5C&XiAT>k$WDe9TU*@ zvpeYP$D5OZGo*<*>g8RI-?;Q%?4Ivg%N~6kQsTRg6yrtYtC@m;?`Wo*0Y<?AkG2R_ zqd>dAD%W(m)Fdl@bSB%>$&rRpup=hAXEQMGtrVT^7(maglh8ZVe%7PCH7CXS)SVq7 zskC$x=|%_up)O;;35Slhc=TxEXAVe|+f!6n-*G$hmFlqe(TlUfI}Q7-0g($3<S1vf zSAkr_M?vSMF9(1xr61S43=x4|uDgUyo8n$+$w7k9hu_b(idqMd6OzO#ePO-V4^KHj zoBn8C%?N%LjUSpzR^`#To%(Z|zQc20PHxg;674vpN-F0}pU#*2MS5e!G1hNxv7i>D zUmhij?yxqW#C<}DOEK><AyE#~G<uEOzZiU3wvA6*=5L{P@Ll=p;nS@K7FI;vO1ANw zhW}RP9^@_k;1{x|lfI;k)=PO`F6}E2wG24FtyH}s87iqS3-Ei<-&6v24#O!YV{!); zz5!uymnDWPX!v`uH5~{!1Pwnz26iU=X@~`{-+5OfT|36WU0B{9xvl|8Oze3`8gXnx zbUaLQTxQa@&*Op5gOEgcnE3lg%}nz6_RDWS_BX2wtQ_Pn_HD8fihhZH*#oTm@!sHb z8PNOX1(7*XNsHV4s7S)%q(#a(QC?uD%JX;-KVsBV@Ji)uEbpFe7t-Z6Qs}T(bz(z+ z^x59<vpGFat5Qb575&*<JhN%T2FH+oLPbI3S44ej9VQba&dJlprH4+h)*m0*NvP*( z>+rk@R`iGuwbeOOh9jiv5;Zrj2ZeGU*T*V;oz1b}Ox{(fXzwPJ_cUtyGUyC{(V%UB zy=DfkvLPvo$5pO)bvAPDaNfVh$N$cph{^lf`#tq9ZVRac*)zSg!)oKLmwR(yNL+TR zf#kcys=ZH?#=e46ksK_^u5EeAGB_;h04*)CjV%dx0%B~^2r#m=>+9u?<r9*<VEp~k z?`ZJca&YNo#<NL)x>LT2SHGl;XGm&D!G|xHqV|67hO$lHYB_RJOP7IiPeQJlDyOMc z8Zf4j*_^239KfEJ_BM<01%dshs{;v7UIl?OTh*_DiDA>eRv<6+limGRjRYoEkt7q6 zf}7!&2W??uUlp}?A3Qd%#UHRP((CMG{V5le($*w>3QM=21ge2yXJ6|HOrSaFpMqyE zx;5t5kSUB4FBsgadpI(PY(^)*HJUQuNyi^dL;^;Yd??c(iM5*EOuL2>Pu~xy;+geN zq!tuEk=Xz0@;$}QcBu!M*q8yQ{nO{MqRS%w8xxn@RuI8cZy&*H0$IG*PJ>Z%hf`9> zk*)LI_7BTwj6%rey@tM^NJ=?_dcNLoaP&>ifn>pz1ntH#2L@IUXI_%@8|sx82wx@u zBh@4;c6~RN>=xXbYcqO2{PG^!(660)IpL$kxi9-`asV`F{<*S-IHhI6V5qEeqV(en zZlk=H>G=ESl<y$tj+~?PD?qCJ9rH)2cKps31RqJ?YJtX~k1z}l#)@oT;u(<LXoQqa zcm?KA1^|ZL`Y2iAsE1z4uT{M`$P>Gb14qV$4VAOJN292N&0C*pXFRMGdr;}9r3or* z@K{=yD!B1%A<q-G5^FH2K!0e3;8`x6Mgc~D%@qARjE(&*NV5O@#ofNhoBCaqIdnb* zcpE1h^;J_50g3Tfknk6z4(Dym8~vOv7kX;9@z8yf7%>daZJgv%Dls9jYSx?r!$$ho z?#p6)Et1zUiEjW@lk}=C?EotHX}`Nz0OxhMXT`=1xXsNPwS?~@?atn?5Bm<|;Ptu- z+t&)hsoaPMgP%a#WmYzi=PjeqI|c<vaP2}23zP8*F*J|0=jC>)fX@8ejz;q^>MxL= zx<;VwI|{+i5-FDjM|+DafEz;*(=6?^Z}J8qz|UAxS<hHs7G2~6J5`NQ7yUz{^c-Gu zXKwqj0r_UGXSjD2N5k-5fA$85u-|*NF~O-Q9|S6@uiees<+Ne5*5TT{5p{>1!EZ{w zm~Z6s&ubwb>}YA~U|C)nFERSd<1=@d5<iKJ*3n*USgRxf{L!3O&kV>ErbW5Q0gg*S za*x-zJvV=&msxSu-GGtOqQF=Nz*fR@u&uM>hta4}L4Qqu>1{<TPCZIr-sa^lEK#1k zjOWa3d@PKzxxViZMN;?hV)wxH${lzx{4f`3u#60X1z&i-8V6$r5jfX%_D{Xm1P&xv zVAKa;{@+`!i-;o#i004Wy}TH+IvR!^KvdR%z2qpuSFIJ4joUF0_hB_0NCSc`@DQHr z-Cr8T1+veY&#VkRKUV)!qK|v3)aV?zU**Ds;`|f;y<hl4(N#1>lm{#EE~IWusrMzB z8&GUFeKdL2MQ>zRZl%kv(g6`S(C?Y8Z%xEYl3~Q-v)Y-*P(GSC;`T9~IHy{B>E=iN zkvubeTa%wj(oHcXUe|uwzqs2<Yng;|<L_@$hy&Uz_Hc}OwNX3e)1eo(YGcuw&Qw9# z*>p|yfAb6HtV8Qcc5JVB&E<nILa~9fkpr7x68wFyYPHB@q{e&V!Mg7xtzLVUDO4Q7 z{u^WmbC-B}o>Np0sC}>8wd{t1ibL1kHwA`4+>u8siv-%u`T#G3)0Gff>U^Wg5Y<{H z*jyO8`@wJ1O1osF)q)g)mbZaIR=Jna<LFGv&{C->Fs$dd{nHGQyv}K6YmgmWJ*A3* z82yJ1!M>lRz}mx%?=%)N!z8zPB7fC!SV%*Q<4MHB9I}tgy9<8!Tsh-q#K)|8<3tvf z=N(GVu<ExUFA87|32E5FKEdHE_2g?$$Htm}jZB<;|K-cZ{Vc|$=b#P&*)h-cEdjMW z+#N0_*$lq%CY2TM#;Ta^wlN;!Qh!|rB!k=j8ekrPH*r~foB$U(1tg?dt}2FdCUyU8 z(#(|M#t^tQMV^H=?CTJP53|OK^U*vO;(ti#<7yfSCD3#-Vt(u<NK67DOuUt^9{UQM zkKEQaa0r~&T?;>EWcJE6a1BVfKc^AL_;AC|9%TkPbHd0o_}d6O`vJkmw`W(sfbF4u zi*-$aINdj4<HxvPnNv-Aysx`zzsWs$_ox}pk1!GlfMxCc$@EU?bYiQp^_|?%pg+y~ zqs^?r&ebofrRvi7VRbjSI*j3kpX&nfgDm@s@Ry>z>(jvd>XP2M^5B>Ey)PhE#7xOI zStRi$&)^@=70*fA1?(lY<|-|f4LjwAu=sFwuV*N}&7I#MlfL7~kVUGhOpWx5a)!}) zXL3tn%D&`wnlCA+|8$>TNypQso}{6~ceQ^NKWtw<61P24@aaR=Vl38oh;e5(=}M~^ z{ty_OyO^yj@y;C{S2S3C+OSRY$L?+f>DZ6Fb1gQ<!U>Dre?e$vC3szD_l`|9PxxIX zvy2!R?*ar$BcBKeyLoI5y1W6HB0sz6tr-e$`8Ih>oN4DRvAM&xX&S7uAAB(yZVU7{ zQ(VT@wSrc@B?fTHnb?x}Yenvaqto^r%UC3QTnTvMwgyV{aIMn-hVEgNIv$uLl3GV( z&{)2Bbe@@JL<V5h=PKTEA9#Ljmb!D`Z*@wJJ|d_hJO^5QH-kU6k>Pk<>Ymc|r{(r% zR+-OV?C$1r5kPJ1f<dt^)1dQGGshbGA;8Gd3pF6pyDX0#-`Xcv_|LPVo0?vwSlHe5 zWVc<nGkvl|RIoNeu;;R+#a&xSIX(XAfR>=evAa>DZJL^Sy+SM_;M2MhUc}B$<Z3Za zm5&HB3vuuD$vNdYBD8sy4wcug@Fys)K;Wkm4|^WdUy-_USt#BmmWg&ax%<f*E^HSp zk1#Oq0_g5;AVSBES*YjTxvA}L{G*Skc#%`<We)-Q!`@(CQ!+>HEJiNrHy3!@3Ji+A zfoAsA<7N58S#qIyXOUoL=e}L(nh!H^1Bd&%w&RNo4FBF@0u?6uE}chiv;=p)9(3AN znDnwPYMf8tcXYP1Z$5HwZ4O($WK$nb+XCb<4FaBlkf)E-%P;~f;L$XlT^bOL!R{v! zDbD{b7=h@bd6?)}*Ip|QcA}mQY-<nJdQJUa$ez=b^i&UzkPo&Hbii>!j8wnmZ1co@ zg-gJBxT70I@c9bPq}5k}(<UBrtPlC!Wpw;#{;!M&75n6C1hFnj`kD5^vL$TeVI2<! z*BOY*-bZO$69p3*9q%VrhwjKzM-&%FwH#7|iC1;C5=W7tI?&GP>#<fC)orecy+T~Z z?L6L}oY_NUGyimF+tyHGn(EYpXMkNhJS+Er)Wc_{JoQj+wgPZe<7Sq(G%&Sqz>zKA zfBS{E|Hf{qM_p4bCVGyr^~6l0?34MB5j%jfd_hSR!Vu?~-|_`j%mYPNu=E3T3eN@$ zJB@#4!E!q=d|!G$S<w<blEU&1%ko*6G!JYZeDJV6pM!W+FL7x$R1HJqLx!2adRk_` z_1Mc%?t&(TiqRkI+0n44lOS)nx?UG&9vMdF=@$}u305POyFj@sD`A?wVDc-;>zad- z_`i-FGAf<l1oMrum9~TDb{dd?F!WlzI~wdc>&cblU@Q~IKY{u(t{Z!MV%6u414FS2 zXES=zjjeTT$hVJfaM%Nt;E)mGmE{j7e1+u`+Fh9;oZ$ter==+aZ+#muB8%)auK`{4 zWe$kp%TyukWq5b5Dc#e_;0?6EuQXO^1s>ps=^Y_@LZBHl+$UW=`aSGT4bHX_yE0^7 zL=Va*!OVS2(H}0?5R%NDjD`Ozw~%Xv%Wr72=<;r2_Uw0P<*$i{w37(NS`3xmw~vy$ zTdsk|B(w-`)PikzVOv|j-+nLUWS5*2b>7#X=*CHLO_;95euTWu@N2ryKiGR+NxP<s zso;)=Gt;kY>5#AT3clc}(9MwJaMb#!z2b-`5IB52(!RCdI)i@{tC|{6mZtJ{Z+;|X z2vPy?593W}WO_Fr@Ok#$h!KB(C|AnmQa0oa@P4CF%B9dOrzh9dGpWte*dzbQT_n`{ zFsPPk_=RCkYbOf;U}^f34Htm)wLED<3%8atA^WA=RQQgQZ=Ri9;c^Yb;5bUSUe*Q) zz%RTt`6GL`CZKuaIFF1A<4(Bk;Lr8<e}gKtJCMZAplp~H_bN7T@;iUKE#CIR6IcFU zI!Ob)`_#a%UdCbG{#$sy7?poI8uJfVU0c3Bsod$XnHnp?5;lRf{qfbt#0dxpx#2hn z-&Y(8Dr9iwXBD{ABo{#HqDlL8%uNyiO`?2B*RzX0Y@3zCrQ+*wah{mu2Xh$ho8?c# zjOTMRqEOnDK^UFqo881hu$MF4kgHPUvjL|0XFCd5&YXz>rF%H*7IUZeFpAp<RG|De zV!{u0CubPhC~epOHW#O*|E_|k#gYfHS0ki-kz!ek9{xetsQ+P#A+=t;f~8^$teC{N zee^)5b;xWZW1!(++o6$uM_Uz#W>me)c6exV0o?|W<L}dItXGHpY-WxF^Bbm?uQ?dv zR7c8kMd)&5>-66S=nK7uj05%@-@E;pL`Kq52YkUBH90xUofz242<>ILTk$G`5#FuZ zxt?=VNPUO7C7(%sjL_End(7LqQ8Z5lK&EAZdfoQJQ#T3KdxAwy@2Gnt>yh?F^EUEN z{sN~5S@3h-a;kP5VkJM#(jpd^rmkFgA-&+@wI0Tpu_R$~e^BVO4w-d>Yiz0@%@aXD z2&yRt^j8z_TEI+6uJ|ltKRY{O=ej*d)sGA~azvWg|KB}W{y+Di*Pe){5MAsF`}%=f zy&ezt!;rb_;JlRc^Ytj_K8x@-Bj@U(Vz^y4mPXx%y#jHmgoBe(Z8hC~V|@w!f68Wv z(gFie$(Jy<%s&UPHn1AWB9XyQH5Jvf(I(aj>I9M+8eg8P++h;q=lwr^+PH%yT!kJ2 zMUhc*9M!|E1=lkm;!{<?8TMTvaC?8=UPNV8k8b6~{+$^g7f(Lvv>xVBf94oiMV3A9 zl}?JF`0P)~q8q!B%2$`qH>G~Is&~a*6r-BkF?Gq~N2L$)dm4KfyqK$bif9M(rL|3n zhJjtU(gNmfak$5aK=(#6R|RGG?bb%@ty1tjx<JwKiX~CU%^BaDO|^`sUq7V1yW<oO zH!Oh2@$ttLC+Fl*S(9yifj@z~SGX&O#3fLs3NQzmWb$cx>G?3a=wb3#(?3CsLN^1N zZMq_gj1alSH%`V)h_lewcW})-Q`8>rFN2AKEQxEqc_qQ9j=4`WF7g=K7+er0=`q%O z$^nq{7u0ACP;3!;$X9R<nqA4?aq+t}AbrkC$%pK}vP01r)r<)3@tI-4eoDtW3TmE3 zNI3bU-k<CktL@qED7A(Cnc25*F9fHsnmF@bNH^qR$W4QY^&b)<cw@ka=G6Ef*td_b znuN=zsH;|JSQQ$1UvE~w3-;6RKlnU>v{#W~A;p@%9%C7iE|q>fK7aZ8amdk7Q?V$U zU}=Qm{OkIDlItP*+2Bx;UGF0R!{mcY5LUf!96vmO@oDOiJD4hn+ze||VJM;@yLX>% z0>iV3UPb+oGE}?p8-pNdsJ|k7+mhYUONY_I8Ftgx9UInQ|A3=ulL*oS_7Zv6IG#{W z$~;g|9H|FS=WJlSOmgcsr7KT*l7}{uzI#PxmxX7vlp6c}*Bay8#5;ruGAF+{Qk;%* z+~mjXt&SzE4#_rt$)Wa%sDIv6>^Ir&S%nIcn*PaPB*v3i)@~oBB-rYsU*R1X5SN`e z%F^c0?xQ*fH54jrk;*$g8G}MZ{<w?>VbCR9Ujfp|aL~*yw!v78tc>N8s);YKAI3vZ zn0*sNiirCqA%GF%!VE`hcFn9GQI4B-SmP*xz`$jpjMN!X1A8(TLpel&!TkukdJl9` z@Y|66u_#a1^_%IUH^@(v`|QjQh5`7#2FYgJ@_kq4WCl{~Zf%prX$~Z*p$vnrU&dBb zzWcs831h3KX?jZO1&0Av>B9qeVFjT92<30#n5nUTSnR0Ic~l{*@Y1RG=afcBFULrK z2kYYcZ%7BS3i%MYU4_82=%pJGX!6zP`eUlGeqoNWhSO<OJ#@s|7}uchf77}>`Xx&V zG6qPU`8Aw&cu8m8Ze!{Sc*qk)T<IQ%`kV#)FTVczt?57h8-@iOC@In{N=cVA3_(DY z5~RC9Y9L*sK`9AIDJf}CV&v%Vl<saA!p7p>=lovZ<G#*cu0LQqw&T4$U#~|#$(}IR zwjn)PEeA<3h@L(Ft1eSDRLv<~esKAu8q+vn0BQ^#QY}Kp&Wawhl5y9Erazo^3s=++ zQsMvL{?cq&ZB2a7Xs$7=&-&0T3jN4H{#ua4T3(<KAnLn?==;c0J*xu#(;qnH6hm+L zg`nxqQ`a1}8SvM`?<e}1f&BSoIGzmGo;kRFZ+*i&J8T4ci3EHXL`&A<A`b54(Oq{l z>GW}t$2J)D7@Sk%wCxQsEK&~D!b|<XkuuN!Amvp_1Nq|`Z&#cA90VS7-b#MV)>)Lg zS>{?s6>axXNkX>}nOB=BbIs(IZS&&Ac7NjHifFbJPS7y-H>i03N5-zZ*jTuaap?Hb z+U%%gPS+YzpZ)zG;cw`<2@HxL@v-&8Kj$)U{N4B!3qA(p58H~&V3M)j#Utf;`IK7I ztcaDJ@f4KCn4Nkxdk<$#s<NB<Z;0Ysk*<~)y-8P;A=DPSQ}dO?J!Kn5FOq7O*5Gxf zJ5>kT8MXU}*C5~}JNOm%dvMrpjWNcXwNZrX`R2P#E@%4m=sB$qZYTyRN2(>M#f?9F z2Ny@ySMyGZJSZAjp90ogxA4AmGf1vCY*Ay_16b)~%2D9x*XSH*4G<ZX$7%PBmARSL z8p$@4HYg=F0fED5C$mPkoGrWI&=o{BH;^yCETnPR`q_Q(DzhKu{51z1GcV_rN#9CL zkP(IFC}RC^TXNc~6ZfU*!^>X;bJqvA^!Plj%=Oc_rACWZodau1Ztv&~YJ{V|K(e5i zhCqD%UOVT^z|Zl?afdp?db+R&bKpz32!p$CUUc^k*)i_YO$4-I^ES%f%W?Im48-%d z;HyLJe@c+ef44xm51|7UJ|*7AH|k+qNv@k!l<iVSL;0U|pRu_XKjV8++P+RxKiM>i zAZK^$(AHj4{_$yxoykr=*b6dh&<5~10i-07!4ZF2w5bUgi8XZeXbIB~^h%yfZ!*iu zZc$4keqbC4y_2^&$LZqIf|U)tTy?R0A9fYpw>Q}R!83_uL<ba~a9r*<2}?ulmYLi< zT}B$E&}7}Z;C`!fo``T(>zfLyZ)k}(TN!Mm6W3v5o=9mxJ-K#_oRZA(a-L=TJ3xK7 zSW#|oSGH;%J(0r|FnG_XY}Ja9@Pq1W5*fZ%6*9RqDL|*|PNHq`d!cC(;RV$eTzw_B zjUTv66To9KdCWrOvNsf|vm3y0ofU1Y0CS6qz1s#kjsN}I``TYE6*(sZme7NST!|*j z4fu%r{sF<7PLL$W2P&P;lG9{=zpY2~aK2wlH6{mUNUR;nzV|0KdzgL=f1*!o{g-2* z(c|V&%$LAJ(K`H>-4sCFO8$NwC@EGrupXbRI3hL37t^FZmNQPe%r>nBsON^Mm+(#h z9iPx0E&M4WW12h*ml{^BOd*YGOUl~@diPsCUb)-LE1<l;cVl53cZ~0m3^oa7)~pQB z+$zNAzV$})@X|9aBUH}GVb95Pi6hnME{JochPT1^RFfIENw7oZU~QmyRH&--^LJ;F zS3xYKuOPvjoN$)u2B}<vZxj@FHB*$)gR{6xHqB`J(Q`&a*V_Y%q#oU-v2<a(C8OsT z4JvO;K^1`dOz`4J5bb6V>nuV{AYHI}?iAs}4!=Kint2WX)DJzhnz{N8JWWvn9fYo_ zH$JOI<;aBKA&ASq5m{rB`+qjM%#uYAzfF5Wz_;{HJK`4zX`X<N8M@dD!FxxK=Z|#q zj>*g_oiIxk_98g?v!p70CE{|lW1wi3ByBl2-dz8L9|Cl~FA^<__n~|6$52gQ8iPVQ zF^vgIRNEpk6jx*i)}#1Ww<K0^2Vi$J!M4D^OB%>Vqmvb55LU>Lc<LM?_MfgHkNZD^ z4Z_O)ge$*h8@olMuEJHO_KWL%zl#Ehp&R+DJBmyU-aK)xv3wm}opwpU4Bmg4I5@+A z7xO&jTiImr!^RjYoT@JyZ9a3ji2sPc0hSxg3bM8EEe#xX@KR*+d3DW#lA3kzc3T>} ziQqEb(a=8G^wCO$GKZ;;-WxSF>?Lg$Ir}H%UkZj?i2u)vl5#M*W|{GiRp!@>?TYoJ zY>TNLiFPNkF5DI=zWkm;{bGP;jeQbR5NffJf!a?~oAqh#*~mkh73|7Mc=}E|ny5&G z*PW_~%u|Z8B9Usm9M+7^39+?r#t+{mFk0CRaPM{9i-Mnm=rj<q;J>W(3cC1wxkkGz z+O$`rZ<=RJIlm*&z3SWM)J;Uj;QcPZZ3=dcG5f5?BakA^$~tjng(w#N2k`LbYj)xK z<MHO7M=GqvLsjz7AN8sUz;B%^k^$u0_&(!KL`?&#JH%TkyHJm<i=>nou(1kv>R^eB zCFWL%_+Wmu)Cn~>lIymfTCXKfQOW?0qd}8zT_Jw?%}sYPot-J2%EGgy@_&kVhGj!X zpoN;GHFl^)@ByU$1QD-RbZn;g57FaP-TZ|s8)syD;H%ALr=g6;aN^bT%H19vN=gqu z+b5M9B;aS^uf;9rH9<PSc@?sV(4Ac)0J%<sHZ6t)e3?@pk?ot9!G1xywO(G#+|jz% z5Yc+3*}XE9o%}dsgzBB`Le?4FoYLBgic)}s4pFq(ykUn!udZ6chpc8)<n;v)Kk(Tx z-fx)VRl)p>$6*Q+VP~`-u*4bLIRM9bH!3jtrVt0UT)LSxWneRP>l*L+w@ej?jSf8M z!yBqOpK515*tPboxZ-lW_HgH_-w4Z=f14n;katJ~p7$N(HW_$m@#AXz(!7R=(#ewg z>sP|)_X^Q&ouU7FGRmApsjEXDu?AhwX-mv%4;Pv<M6+;=c0y(-lQF+?C*FGXhKemJ zqd$hZtlne~U7LdMJ6=@kRTT?e@x?+~ukQA{Qmtanow37oRrkD`^<nkg4wS?s_12CU zButwG(U|WwdOLE=Tih}t=c{FKp``dSne#<wPac@Q$s6wNR{4n&Z~uf<z-nyF=AM8q z-*Cr~E5E(&@lU-tg7o$(_vi+?!O!YeLi;ljbjQz)XxR$`WM9mI@`u(jfx*t<@4d)y z+(LFPzBXAl=L|A{D~pobM(^FgjP;IdyV;Lg&YV(_jlT=oG>O^S8NTeWfa}`9B4Y4I zz8^219vj*Hu~kNT$b4Qjvl|Pc0)N+6@y$Ogq3@qOAajr5*+N07O`J3ftTJOODW17X zSvX&9P%#YuJW^L47QYs!^OQqWa4O=ERD4p&dr+MrbU09syVTH~8Yka&l@+J2VIY`8 zwPq6BbswGAp?G!lIkH&)zLYqu;v00UHxesgsOmFgeqc1vB<so&Aw$4l1L>-uc1`rF z&|rAFT)H1DaARmVZL^R}R;bfz`SQG+T*3pH4Z@}Ph|_-Zp7GuF6eltmQd*jxIq37K z*zWOU&}|qwNilcj3>{tz0oA00n$Ig(jV%&e@-cm9e~*j6EZ)sr{tPDw9~}o`ZE%iA z0+`S+9^86Ri3R-FqkgV{)?`Z=HxyUOb%)-9<bOrm|Aw2Z+W!c*>$G@TP-L0S#y)$C z^K=fLYo*0~IPe!tpQ=(EvS``#Sd`(`&PlyV^-&CX1thu-kN0|HjiJoj>$%B$GxfZ8 zlH`Cv@*$RhGdCHBu22PxOh_LzPLEocX2HbX(SMvEYnuLOBD8(9hl%2YmJ_+^Z7MrI zs_D`cuZ{d{58H#f$G(gF{?eXgtr}k5Y!g1Bhq){LeblIM9%0e&wX6xdOMq_UEY)d* zzL$vaVS0WNtRi&?2LSs_NYRXTUPlp_bO-<1QP+WJJhoyk)mxZJ09%u*h?kg*+*ux+ zhXwpyb_<u*+qupw3sMz3m%%0CGKjH#TL?$r)SM5Vf5d6HUjTl5xxU#jV8Q-f;S#%a zP_^UUJ>B&$3(yb0jaJI}8P#L}wI$v_6MEdZMK?Flk@lVLft_g|pDAbtF;g`fjEI;G zw#pf)gf{J!0$_lKhZBV9Bd#bGl4ALGv5yHqJ2a^W<rx>Ir>j3arn?z4@&dfmuLsV& zW8w&PMV*UmNpt1r{1)tGaX&SkeNFpkejU>u6;#3B%2y|t^_ZeRr7#AE=_LlUcWpo4 zl)YOe_u1yO1eIE!l!EeH=i*8ExpM}4`K0E~OO-Ia;S4UmRly?KUkb&1Iff7W*=VI7 zeQWFLJ$}QLpBH3Z|MPkRn(jm;zj84gVhhYnngq`Z`=NrMVIv!UG-oHq(2pyHn((GW zAw>i+hSXQmDuejsF41X3M?$STaN+CE2DuMdx?FC=hi?Pfxk>as@0TmjLqI(ZX4S%i z*uh9%8m)@i>U$r9jcBZF!W?z|t@tR1<PZa)HJny<Gq&dduIh>&g)0}XF4~1GWS#=? zd<{r2|8?snv)D8FugSj??Y_T69didHpzr9M<^Z4~<Z|0bHLi*$`z2J2;J9q;+a?`r z!k^yu_`?8NxGZ-(A+NsH#j6V=uXBa=&qp5l^bUG#V~+i@Eo7yH8L4;cQ%m#-bfgKX zTd@Js(HlFn3W(a!L1HcybLZs<kVZz*dC^OPk+Xwt*Y2kv0_o9k4))`|(bFGHLTXyw z0T|^a6BS=(zVrOPj)t)W3ZL3of6^c3$@3}0sUbBcUkHv_$%Bg--jcLwJzyR9ZVbLF zu54Hm#eKAy0RVV@3Cn!8H+gSw88<e^z~#2!RK(!{f*aS$sN+%>Lctz#>fOFyPB2p} zvV3#Hdsl)<9^;$=uM6CE4p}fC<=2x%$t|W>J1!^AK1l4TR6;4-WAepP*Rfpu)mua> zb$#gjdt)tEUvK>czAEc$6mmhEW`#k1ZmzpZy%^;CX=$T{3zq{qW&l}?v+Uk#Ei`A6 zNj&<ZXaX~fz>O{=A$<y8c%-8;>k;&<@_hBWRCz`qKV;3pc>h;yb->~rpsC<|k7WRh zN(MBxSs%QMdn^cHAq99=aeh9Q{Y<zmXj~NQMPN*E{D4fXmkq-dkkHwRpQlI-ObV6l zfo#rfei;5nUR7}TrN}x(*l41D(B1Dbfz$Y~EPLzG=g)!b`)!Io3Fw8-;_u?^%=4?? z<Ksau+oR8cHZx=3;4hBI#E_=Y^bN0Pnxwu;@ZXOMU@XQ8!GAHp%JuNOzu`~72$?IB z_p+|4+SZONXcVMBM2vbe(S#~!0YmDf2AcwDy<$%Qi^hSKdlBq+D4H3>=VCP3&bc!4 zKnjEH-grEgF^gpT+&&6@xQGQ}Ef@V$?EhcdF9Tn`m;D15QK3%ArDY=@#K%&MYOFu> zx8<$#T~8DPTcvX_K0OEgTI)EIOot^iiwv}SZuM$))Jjgo@QW=qO-F#QGo3g@bnzSq z^&gzxwt+Ol_3`yuVrL^?a;D}u{3~3d`0I?m$aKoWjO2?3RgTMxM|!OtQ8pZitGVIu z81=^Q?kM3@O~>`wNT3>O`&%$;%MWOb!2DK0s>#Rd^Kof=q+mm4zdPm~^JK3WRYQ5c z{IQnJ1AV_0u6)&%DWBsBG04XFhmKPHu14kGYu3E^RLtoz-w}iTE(C>B7#AhZcrU-I zD<4>MuxZ5maX{@?dem5=@aIcA$)_dxII7EBMigfS6|Nk%8aDh^@<2yx`PRli8T`pm zW-a|L(Yy=DrmP4GrcN$mI4CTVrrp(0>E7jHCI2Vx_*}AgZw%q<Bl&=br*90GZB0i~ zp{X!&j``WTH6C(xfgF#*{qqeu&Vz0V@jsQ;9`b*mFA)$qP~b_;VhYJWw=i324x}2K z-m<J=w`FbNM9L{q#=(0XeS1pVrvg{ZNidb$o0dU8XNgz-p4Z8O-+tq_Z-wQN*>2Yu z=?hezA0dP}w91U94XAX`=79hr2d^%YXXZKkJUzJdgbri`^rDs=R`M^*>d4g2&4XK> zRUU!9MX;$-i08!{^4CCb8??n}bdKBiY>Rcnx3YE-d3r}dQ38((HA5b=EW9?hoEm?K zFagIJ@#qX>ImNbYlyr|y+XvJ4hRBg>;&9Nt^(vNYZLpI>d}QMc`lTp}xZLgmh_5g> z{f52#Hi!n2^o=aUNZ+%Qs*Of!UlXTJD$|yfD?I>+k~y_)45h+2$0Kx`V{uM1UIVxf zr4_NoanJztXhf?=i-6r#`9?{g(00dP8?))VGyAr?GwP=-#$8}#t}P}^>SEyN`IT0` zKa8?<5S97sNAd6BL8$lp-jFC<DQ$G2To^!<szeUH|Au(4>H=w@9fNj@5%|GKJsFJP zwQX&Xb9;p+&djQqM~8L6aqwBZny{%mQZF9(Jpz$ym@EHrI+_USiN;90Z2gBO4|@|? z-^7t(4$3bj<uxDZK|mO+io|Q~9pYf|M6P*&`o3P;Ntjf@Sa0MQajfRqD`LWfVmH-c z%KqJ^YQ{9qEAv(WZ$7K0dWTdYt0s3o3V98Gtw}E@*bY2t;<_Em^RhMur&TVo7n{*K z9JExB3Eltr>IV!GM?W6`GQ9r1%Mh60yS+>z#=>tmSqxmD7y#x0zn{>GQGt|lKH2^X z3Fa`u_?(H8t-U^=>s{D~2!zVzOM8wqesq%2F0tY-x5#j$AX~F(axZ4mbVasLW)V8{ z3MV-H*$>N>5PZfXMvs4co88lC@_h>+QlZ-hMy8PNLP^XX+?l{htle+Xz#RdZXj9n* zITXxx4gZ`GW-v{0nWb*%fYd>wK%M&}zFiPJX3fSix56}x(JEosS<~T5I{~X`<A-<h z1;s+w*ZA5$p9~0GW;v%RjRUvC0>XBbAj!qp)09yND}!guB(J=eGgHeQ>r_e9(|k9x zzZteHhY?U&zrmh7R)ubhq{iSM$k)pE6P+8w3`HRwr|$>D+bMJ~(uP=tG!N|kD00WH zmnIHKh##&xKyhXrfCfSOc9BFTAf0aabX<Vv%8*zm<Np}@{s#cB_&$9^Il17Tm$N*0 zSB10<V|jV^eM^jm_C>1e8BC?t3D_a*-lM4LmYw%1tCX6e<xfaWN!I+UM?u-#`1H@d z0tC6N0rQU!2(Fr}IcdS}A{eRXa_0mw6GTEP?~gkC<YjZ_^m#CjAfR#lJh+9!pz_}f zQ+kMM6oZ~oSBqEiBa?sC%ESk65kQTusCpy$W!s)=U^#K5G}hY%7}+be+$4y6@RTYD zP#zTabrV+6i3{STB(VE4zVXVJ-_fN12RT2a8Es=p>;T!|z|aVoq^6QV`SuH*5NUq+ z{V&_@>cL7ziznYPOO^b+BJ17a!R~L&g;2D@0sZHAqX}W6_$j3+ZiinXjVJEAKU%T! zB01jDm(?Nj`wNkoXnX*7C=DlAF^-47?wcCy=2nk8f5PM&Nlau8fO;~mavFTD!^Qk0 zZ+?N6O8T<htch#ogg+_ba(jprqHCvSk3p={*lxOOs5|+t5&=ZT;il3rF%1m2s$E$J z`ZYMDYlC*HAGvJvzgf3X!zng*@AOadS(dL82?vHg&-ZM#76~%@slkRGna*NY4H%1^ z3hE^j#>Ac)qLy%ky5mQTP6_EA6N!Vd=?tXI7MwJK$d%(%(!fn4|2O4VS<-+73@^CZ z?zMru{_hAi444q0`q>HRwDg=e|D|<p<&|5kL4A7;pq|V(&>U+z7}=ZR#JO>JAU+U` z=xuNcHg0~g%7@Q7KzlV!9jKD<8s}8;<v8w=W}Z;KERh3CZOeu-vBLn=@*87{TIz{3 ztTa#*W=t3Ad!jKlb|iV$Y8t%cA;0PC&NXHk@Bb{m-w-{Z2!C5cz?=%OBR=rSo<bzC z4;I~2T7}(krTToQ!;`W?y-d7oP63vM2lg^Wz4vdyx$SSV5~eW<v4_YObApqRu44ic zqc{um)pgXl{8gIBJLYb({4TDd-`Epykz<c_(^@n&$}`XT5j}jr+g}CX`S4vqx2WG! z$cA$0f?VJi7o^G+W?*Wj7Jb5DKfdU-Bo>ZR5?}eqt5YDiv07f+i6(Kqq51R0(y@O7 z|LAE(G?};6Nl{lpcE&+^RU+QU^>b0Rr{T5%d*9NFBzUoV+9myXiS>I6?siII7Q#m? zaB^hoy1zMsF*>=*IO@dI<Z{14yeAk2z@%I-zi%%+&`ePZ7-}w9eEyf7!8}^^uN~+g zGF}?fG(no<{UG_!v3$-X1v1;&ql6At3nBk3Kw#4te?-IQv3?YiKJ9M;S;1QulhznV zhG(>REk{m04H%NzO{mf04`IT2SQhW|X^g|cbH|0xC1PC@V65jQ;;iAjzR4#k{Z;&M zGsAy)79Qn9XCU}z<eUqZ&^dl!OIPPP2M|x~Ou+CzO1yLh_NuD*?I3!*krKspgfTC? z;pa=3(x~yj&HFxX$m|k&v|p#A^ZAm$LEq!)soN2AvT_%3g1KSJ_KEyXD%N==Mc{UB zw564fH!!k?UM9}7_&>a&d+|-^F+b$|+wL|)fTxu|x;@ffGx0T%=^t5xp3GZa|N1(9 z$OjNRqLPXNBQ&+}8U%o=j6*{@=ydh((Wm}G-+=fv=kNg`<~ZQshEygPE0SfGxnI+9 z<!>uj<IMjrmG_@#d80p79_u?!xIbky0cD>Ga(%hL(*Q&2+E-wkG_GeCO-$F$+9O2H zN01^0`G`8NyX<JHM+LM=uW7vhCe%5+{*ctYDlCVGNY(yNa_vJQMbh-}MvKJ$=<KuE z^O!z6zcF6QdK|b2Undzp(Fody6nBpMU4O6EC4ida)hk@*5mxBZb1oV)rJ}Q5j_ro> z)`v6>RkM~k%UCT_HvJm>IaTwvD_WTALaH|;!44-rnLB1tvgG!&C5*&~E8mAHlxQxu z<6ju={IAN7@`{(k$~lF&Qf?{++}1g`a`Rr|Gf$vRlv}r-s0rIp;0OJU=p4y_4=rS7 z51q=2ulA=zetkknyNyd1JH=jU6AP*7D8d7->UDQAI8=?euE4L`tkWUrY1|fEs!8%8 zET1K<o~<5UZHw{}F1_(ji_?-fHevI+7<}C8h?1`=c=7gFe>$*5!Icj2ddvY9KxRyM zJ3ga3{&n_Bv-!+)1?OEUn~@5+5XGRdVqL@eq)_b0PG-Urx(l;cemGwj!T0d%uFl9S zF|phAP=()PBkcU|^XzF5T{j5hcKg@Oo*xXaxkHkMi#-Pa%6XPK&tn8+0QH-?emzz8 zIPn3u$Q)4yen@trn8fM#EASVg{Pjla$yCPv5qs;6u-4c*?VFkN6=Ld1kihA|pN@6h zr73%~uaP|V{&bLHGi4Nq)1Kax^=P2_nm3;>C;=)8kRG|Ds15OE!hdUocFmmOj{EOL zn(KWb|7JL+dZ0PqH1bA2?4tY(4pZcG*nQ;>A|PXz`2Z`VHIo)^Eo_7;oajvR7*`I4 z%=x9aYyG%*BSDU8GRJamf>IUCk?J$Wn~$Rc5H$tqpux=2>`gQU_aHVFOzOgTq}F_? zYUoRHBMgn=kWmZz8Qw7Woo0u2$y;86jzTYq&l?>^eBNc1B12t%)L6}yA$>~a=hQ+* zpDIQ4l)e>pCK#Rajtyh8AK6%Iq_px$WnE0g%&+<p=@M^>aWebIrW2XC4-c#BUhHFX z4z(?ES-!uE5kj+9mRJ{>lj^k-{m`v>5vFM*pB`~jWvdU2oqa>>z-93goDw%miluq^ zhMwYaAnr?}OfC%NThX_?`JscCHH1N2v#2kWq*WmbEJ%984MK$eA9PxzPjckCn|K?= z%W^41kb8a+s|<snBmx1TiL9$lz!3S`6%S!ZNdcV*U>I2MuV~?So8b^evVSP!s=I=a z`5lUM>?4Ac!pxqd60ts<f3o(zerFt4aGe}}$i7Q`A2rcIO%_awG-|`o?eKmfkH&=9 zVT2;PC#z>uJ6mrs0Yl{kPTDd<x+{i(A~Z9e<uZ1@4)6W)lnaxdg6R~{f1Bb16$@jd zXTe~*VpGsZ+CKmsl8CeW-@2{EJJ-IY3LY@i%<&o89TrKnTR&^N`XfVkA4F<Nxix(r z8ZPcZNXpgFB$Qu;D^-GCaDOo76K_{&!%eD?**nkLOlXlRxmpn7YP6q(aX>pUxMY7f zeJ1uB8>&xFfr3%COzL$P6TjcWrM>UU0+%O3<f1+Pn<J>(ee-U{Q%@s;bVw3*J|!?T z;~U`pzhf2*I#?19A$2BhB!6wsO(9O&#&U{q%FP^{GQTT{W8iMF><lzc^lEtiS%xvI zf=1!!+yc$$-TE@*g)W6#nh&}q%`ulX{eQavO6>4qc<}5ltNssyXmkpEz@TTbz!zn^ z&SLjZ`<ETUy>aMYv&)W#!S-Q>bwAY}_=e-fE<?-TH(%x1Sz}do2EA#fAo2GKwT;vC zkc|?hI`~(fc}7=Jj-k2N!0#dFzrjPs$x`g8kKTU5A;D5MFkPJ0k>2;V1)Sn5ZjoD9 zVETf4l>mFflr&!4l`b2#7{;reE4VM<yZW`ik$GEJ1@<&H1*C0Nhzp{<-s;u+myArO z4v~vcqQ2o@rfX{9xGb0`l)O**=q=z}gKWO>S@QvPk<eh9IIlZClK1{&G?-W~ZZwm? zJhW&Us7IG5FlgjRmVUkCrO%#!z!PhLnNx5%GJ2u8@r;>cA+4`=p2wo<#zc<Dk!kF2 z0l(();=B3~jTG*(zEHJ$q$6^il8zYg+U6&f9#czk)Keph%V&KY;OAJCltCRKbXk+# zm=>(IjR_AWDtcih3rsyFHQcP6td<97#Zy$qvzazhVdma7u3u-3H716I6?A1&kYFCx ze)ICp2<L|cO_=wW+m0FmuAeDU=>|!4;x2JaAJq^ZxS6D><hd3fnq>_QRlmaSvb&oX zoeZz%hgg>F50U%q$*bOFaPV{IpUcO2a!`@*H%;MU5li-f_YAH-18+zsBZ?>vC+8kJ zm?=7c{<~Yc?LNRf;(?cgu3Oi^Z*d^yJ&1H6>$1NEBc|N>IX~XUx^Q1(ixUM_pK2pg z>|U2>9U=BvMvU7HOx6mq_zh{=PIs_8oUz<{So)y@;@*XADP(Z}M#d<oam8vt>6*Lv z?yQ;T`v7oYeU!a((E-AueM~hJkT}W$7tXT(Ro2J9G-~4k6YI8Gp!8_pZW-9=+S(K6 zRyz!1h!=-L%<Pc&_vT5PSEt-uqvSsG6HuOQ`$Bs{)pJr!$5s}uTh<j2#?_)diuRyJ zF6i}emzSS2_Gi@|#t&KTQhIT{XrOo)Cf$o?0@|+C+3|nR9i0Y^r=5(OA{IW-TyUy1 zCSV5i_xuT&*0)oWT-BN>ZiZqcOK9<yE>8rRLY4fq4N_Zs0}1C-vH@{LK$~u|t4{-} zA$TEfD0_A0`VVA}gyMk3g>N1_E^70dc*7^M)9!th+2b{$nMXY-^TslZ`ln{ow#=x! z1-s=TluUs3QxNe=Z%^-4dLBPpkn~5`)m^YW%?f@^*kPpJ^ii0!0y-ifR4Bi+2hjAK zbgl>>YAr7<i3YIW2idlaFdxrLJ#;(}w2=oy5L?IzROdRyZG$g|nY3HZo>kd^Ha_jH zx4%mLBS0D756uQw_GZjkZwMnd8GQ(4jV6y4haym+4g+Z&ziu#F-=$3{;wE`PvG{#E zsJ&gSg=zctnA;k1UpYa1>`^43L$mTw!Kx?81S5@rJ^X-dM*ntPX01_=cme6&*i*dt z8^I4rvb=I{?}xK61gXUC)(x#Uvi*<OS4seLyJsFIk26**IIfMITOw2VG5`16()ypd zRsSajrz(JN#cwhX+}}{t0^IRwdL6+sU^hXr{9?4nHL~Tg&RQ+quU49`5AW{YXDyS6 zAGVj-HxjEV@&z%<Y2Ta~0`oQL09OO~kNR2ObV_zTzM2?=vQj|P^`$iM*9Iv8PNVik zSKE>`{z%*4%Ip*1+v2as0&8a7_>TVYvyuK5I5zR&*>&2K;mLPLPI%yaThg;IiNr8f zo1Xg?#k$09n{UXyN@5K2N-+k{3*&n>bo?SREQ3IlzkG}WzziT{H#3|M7Ff88pPPdI z@bZ{w)I?+Nnc(^j59&AgiH`2r7~pUe9bfIDTA02WhPzFam|vnPL#(`#IJW`x0)?#} z$rQs)tP7E|C-eiLJ!{o4073`F^-AdHH0LD3_$a!%!rT<_p~~;!*@>f{YwPA<9oZFC zjh@qHBl(`^ChqtB>tvv2Bo{)6E%6Cz<=~cTg*GuXcztud8z5e_C!yD!f;9vaVfBO5 z<M~w;Hn-iipJ`PI#QDAf(%TvnuI!IDuI`QUfUsC8zcc)@2eE4p#n(@UfXTsX*EIx{ zf7j1sjgOse;;!QytG@<Q8PIA^ZSO--Jc+6*TzntXHjZLnR?K#j$a556B&q5Ccv+-> z`bF=L)(F9hF>Y}<KPZUhE*EO;a17*i9#i_G|FJYMVrSLveiuEAzT&>`DRa=(ddod= zL93^Fn%p^b)^|?p05n?2^aHGPIJLoPkf}V1F(q2c2Rm4*N>Hn)E-B>XhwS|&y@6uC zmh{S{CSqT}BzY$eE1(v#2%yzH7=`kgP}>r}E<w9;zXR6u9uQXnU*@Z=-kJt{p%Lhq zUJt`#YtB8P3af*SDB#Efgz{16EEM+Dk;YC~oLi_#bl~HFbDDznRoP+Ngn4=%xa_!D zyvgOtRW}7mqeq|;v_gJ9f4!eHt2)*FTLrrr1s3FwBE)USHMNg#rYw;Zn&C(A-5Cwt zdZr>bUhe|I8|-I15^gyOx4fFrE#RaqsSWuz#A5ytCx@82H5lQFigiRv!==%NiPr-9 zk!BqI^}{jhvkCh3Je*=F1n1ibX7*FLEK(&#Qi(T3Fh`xic|SgPhYdTICcPa4>i~H4 zvd4`jdz)4CyWs-Pl~LB10^<v_zF#gXrPS4nEQAgQ`#glP1m>i?$Ir?(!i<j#qo5e4 zCR(PiIO@*c$SD#pT}Yu=5Ru7-5GPzWGWe0WBMKkjb7k{*a%U3xLg?+6NA9tb&hQUU z|Dlm^=q%5GD<kQ3a&aDo>yEyLE4?yM8(mT}IKeJ7araUlZ)d4ZivfCa|3gr&DGzf9 zdSSk+3rSiK^Phcn_G;kipZK;hM33(kYJk6KgLOrANx`zBx_#`rG>Ta(98g_G+>7r` z<y+tsSn`e6;9olS2}tw`&fCEEQG!PDz)+zvAspr*OuqZP|Kjj`zCDA|k*>fql(b%B zk##!eD~qM7y3cK+%ew_LxpO1MPx_;Ufe5${Eb&=_=iY#v*1{o5P=H~UEB}g9N9YEF zP4AJn`v1y{@X5PM>-`-R?4+7SJAb{QxvZ%C@lHA71;-5F_RUbZ;=>UR(Rpf$Gmi-< z(|z!O)T((f=DMh>?Dy5+aCB{mD=`*;0AXk<ilD30bfB>hJ3`sDZUp!n^F=@mjrV_C z9yCT6*Hk!7Z^(S4@HjtKdL4hm@Qn2Tl3wNiM|zz(2wQd*1@$Q2dcj&-)c$14APUW! zq)zN&;3(_c*3rG_RSJdN)iSI)I3$B~-V=KajXLwP50iXgC(E|#!&<o|l&ywMLCAim zu%#U|ANX=M=Kk@pB!f=6>#p?*DNRPygVen**NxMVGCQFxQgFqpE;XOfd?D;h6reBV zb3xZR>!l>+oW+Fp4Ymbv>>omRp3s2kG?IF9F+bvR0#jV!qdFvohqCZDXFFdN2ES3P zPckRxx!Xr+KcSv1sRpPRQgKZWOyWvK-PlW~YR7FJvX6kN4dnkDjmF@$r~vmClZ2O3 z9?fq;18OAD4(uwsYBukpB})4n8rexhjIX_f`5|5?kyq2Wu8A!h96fgEiBNziz(Brr zXWqO&B=@Vtnrpm;pBnPmh;amwP3R&K^zTQdN}i4F52tZ~=d+loU?GTHG113#M%2Vw z)$^P3y~L#8Jj?%PBGu&73^OE{GY^g4r{ShE^2+9BNi?Bvf9fH3XrN?PnPfhIdu%p_ z9Jol=U&~_asmsJbjN%qllx&{&y#XeJTRxP&cPYWw@_-$GgqI&cXM7qxnQFC;n@8R> zOplyRiDL%}^(4NwDF{m7^RMG(9Xt7O)H_U<3ql(CAtVCI9C^>ze6RV}vFVLzI@eyT zx;kArA?2B}X8(|KQkjkU?jZx-Qas<Z=(V34ZIIsIP>GI1clnT^c<3@=+jgJbJg!L` zZi_lCibzYIbL84i6yezwg!|vrbDvpIFnp!)0S*_qKM4A?cyhOne&1`-)Js(jHVwpk z(TEwCkS9J!3&yrsRq{hNZ#o6Q(>A79@Bj)eEPbV=$DG!dVzGL5=Z5MMJ(S*(=KE*9 zN9M+*biG<-h_8}LoL#R?-!3~uR&txyW=h9DPsGJHUHUr^cO~i`&S_yVYSoI~<t77& zl6t-=S=aN_j(mB|6eN;ZeTu5!w32SJH?iOt?oY^5XcqZIY<-Jv`qUE~mmN&FXZm3M zY&UtjPSvrBkpbnQwKcF4mQ6j`cV(|<SkX^@N4SuWy=yj+ygzwH+50j@ag(_RVUM-O z@#U+t$Cr^rc0e)lfcge50@(%3rw^o&9rtI5^~>{iq5*j@vCR9)pLzq<y~z7u1s%Tw z94;3=K5?{_Kc?XQ0rsctw2gDtqCi+EK;5aRw1WT!U2>EW0_s`6(Eo$$G^lAXy-zv@ z!)c-iWg<dB*oSZ!ez_tLvESihO%f2o6Gml-qN1EMjKehw4LTOU%A!~#o)9(|bEb(E za}b?d9AlrgmiDXvzV|+n+!``=%IB4p5d<-{BqP&btodI`<w>S}VEWF7QEZv}W>2pD zJQ@6t@9>tk%|Hs4_43Qp{uEOQwzMN^>1r)%9n7Da-{~p=<kB*{hRL$avg|e8E<w)Q zU^X(YE?lFkzXc(7#@0t*&KJ|qsv|*%6LPJ-NrZbBFw1Ph+7SMqnI&Ug=QV$K&h!2Y zI3!)&?#UFLtPTib@Zg_m@wd;xE=cylHM52Eji=)O@%!#C79KeL|2=Py*g)3ro@vRO z51^EZ9MVVe>3lRm_Tm;R{ZGM^M<v7|?)KjLqXHn5y6bTLLx$%o)dka!Ye%QEwWv@y z)qeO<2N}2s4Xyu^b*&45LbAZwO)P0Dxra3BtezA|71yk_cW26hgFI6;OLs(>ji8jw zwhpIj|Gpw)qL$=1ISfnaE-#<MDq^FYFLLgrM{@cP{z29yh9i~k<0s!CUIDFd3u#|L zRgTm&5Qdw+5V2ajse;xe07!o%s&d^!e(gX?SI*%kVrR%by&>f6u)tf~upk8en9xBS z9SpzG^m{VAEn+|6G((vX$GVz+DAt$9VXK|`vMFog#%-n%Z+iQ>Z~D(_itzn%6^Q2( zm2C)!;hSeb>uoD(?MA!dagJ1w-ZahWb9-{0C8)5r(EZ4BuAS5<$NrV`#%{o^R@FW< zgZ9Ub4BYG(F+zfx)sh!M--IQRL=Fcxy*w(#u=CCy8SLg^Zy?pCp!OeJmSMDqGe=48 zicA>2IHRDM(X?bqMedboL>{XD9|q_BU9uPcU<p$+@k{eQ;-=DXx}g26Fa}IztY`E7 zgkkTSAaed{&e41#cLt1h71SVGa_Ug!YMqJTDHh(a>yD;{EpPBlf~#iqui$s*vgn!B zG{$JAwMpN)UM0kfQY4o^LtczDOA1%OnxVWjx5Mn4{t!JPhk4TCC^>ORH3k?-<jS4@ zk`;<vziHm^MmAw5w3|VN3I`3IQL$RV19*x6NvJLOExGN{o1v`_By%LKg&3NRnNGlM zna+OqLFxMx6K{b}rE`oy*j&F{#3ggmmUS*;G?tKayWUhgj8`N{D%()>;R!lbe}JhC zuA%H}>Dk$bI@jIrR-grZ=TyPCD*__b9!1FGEv-KLoC>ZJQ3xe7a`$$cpgQWjL*%<r zLo4kherACJ%?dU)ZnnVkbgdnuIb>Yc^h3gn*<6tl-&2|Qx;(}<LB@r3K2dMAR9Qza zWq!#m>%A3MtEaXC<OK6&9?&%KNHG2kF3RiuIg@?*r`P5~Bx*t`x;>*08x}=bW0VrW z9{UH^^P>03jA&G<O_&O?(N&gO-~-={)+nH~%>!n3veZM?R*THTr?RTfKj2X|Ej+Kv z6`t0$fu)EX^e2MGnU@}5sqVQNS2PV)=1{+HE2;hFVje5$18mpmTuO^3@#x8Me-8nc zAj>B6VuYR?F)2M}Kl%L4o)oopM`kF`K<k4#z4yUJBknqJ{2zK-^_31&q>gSs|MB%0 zqra9*Otkl%H0KsB)Bk7{B#ppt?s5CFah%8i{;u#!PV9y;DDZub@RvaGGhIl4CR03G z4J-cy+d>Ej`)oOI<E_z;`vuFE_9w7A-%{^K{(Vcy7(u@&KDfHP2!1`{#?wWwPX5z6 z_<*TPlptCRe<6K<aQlKfKifMOw_^R_bd{}SV5~OLK^@4EDHF@fZCxY+<uI{bW7z%b zG|p6tk>(N@-zO1cw~0U~G948|JKjK$(Gqff4Lk|Yx)NOp=E23fIl(w)qGvp-rvI2^ zI5ammm`#I4nlDRbx3aKK=B|9DX>x)L1LjQOsXv5kK-aGWg!!M2B@rlc&75Oj4(xNp zMlf_tA0H|~2b95`8C26J?JwmS5JCNf^mz0W^5STIam*vlG#SeyS&<qwpctG2pd-IN zd?9?CmqbymkueGxM*Ha2V_cYmA(Vr_Z_x2G30g3=9zc9#YfnT=K6<Cc_C8qh>RxE< zErEbq=D9b)s)C@<YK~lx#=P@i%XuB5Z`+}99`CQ%zPgPhpp4Qs$3;E#{r&M<-Deyl z{u<rSXaso{y-WJYJm|jwj?BcaH$Pq@T*vr*(=g``BxCn)6(l&h^Hd0h_XH&Q2@U7) z{9)5b-GEO23vu}08EvxuKe8<5$LgJ2E?j)GD`7y|hkB)l<+f#Nqah~|EC)-PAR|lA z;+kUNtL(u=atiC%TyW=J#FGK~`jzB?|Mjk*QE)7p!7rQnRLM;Y+g%&K@c^kylQT43 z(vD8P-@MMt9$qoovnw6<(eOsf8X|+8d*`E^KE*j27d`tL%^v^^9Wmnq1b(Sd=+j=@ zL}y=S8LCDIrTlrJckmJtX6NCg(P82<<YR44Vho<`zWkENE6cC-D<rW<^{IR&GO8N* zot`9x#F%WT$(nwsNAf(`D;xYBS9gq~9jrU-xlpBG%jKSt7UOvcG?%)~KAx&SXfP$) zAAP{C$E2Fg-8|j=sP7cHyZmgyT}w0vyRJs`Y0HCZl?pM7BHQpL@8{93-_3vh(6s(J z?<CHw=hRABgGna*YT6QiQv*!}ZaevpF5v?a7qY@Wcd_6AR1TS~l^+*XCZa=Eb^Gx# zjn?f9&<Fj~rf=^)w1-n`^cC=(we3F#)3v}vOOzWL{SodAn-)ZW$-c@1(6sas+KK&A z<B+_X?g$4zPurDW49CL>hX3*ITA{P)&&N-Ghzi0^>j??ZONHz@e~ZwVbtf}DqmH+I zGYwTxEHA;EuL|@k@BJNULNNs5G|{Nw{RzZi!AELKC>nL3nuw`u&kN7KJF$cjD5y?S zz6%?Ko#-^%zHQ3lW)Y`R4pj2NCbvHAkiCJlKNPP8%@B`(u9*x6OLtw4$x2ihz;5Dx zCTq?g-yJuXt>6;8Lc|j`NpPxrDH1pROORXs`&6$zFAhoS^Pm<6hijw=Pd8dVc3GNA zu`e=Pz7%%pnwUDE7)V>J+QqsJ#{zH-B)`Px&%&ka2HzK{;?{>G(42F8FEIK<#(4@t z`^DHTLKgB3)C4N>1FPo(>k}B}Zzq5)&bg9Bavq8(;7ot_&7)-o?!e%?^2`%#9Z2fy zHiPQ-$N9t^FH8x!LKeEHq^e35DeIMhO2cwie02?{Zzur^GqDPK-PArokhwFm+jb4~ zXz*6wL=SJ!5C$SA;fuF06b#pQ3wwe?w6T@(=6iJkkkNYA(ZE$(^efGi#gUuToPZa+ zN=aag!{yqQH)=LjiNqYS<AIy60?`3Z8Qy9KD6CD1EidNp&Jz2tKG0)nn)(#BFWcM3 z4#maAFqMBG^3Sm}gKQ#}eJ4kK%Fw?eDBIl@S>_B|dS-F@w`I=_wc@S21WjAko>{i7 zY6+v(t91SRNL+ueAB$_YkQju#8t>=BpO||x60p>rr>J|fdU5vZ6<Y9vG~B-D+Hv2k zWrRbWNAQu|dh)+y4qM>`QBCxy#r>ly)e0prW|x(&zoo=#mVG)dS_@~U0K%6BOlgX8 zunw7O2j@xqm<`V|@YWyQltw(>8j71RPyOj9(*M?cJL3hur;Sy6bB}p4F4>wA_wXlU z-?s;+CNL3AA>~8M1kSIk_8p%EHxnn?r*v|J7YFmKqlKb@4u)oOc^B=XLAOp^Etlgs zZa8mrD;Bvjh20blbOtdSw@!nfHFqy}Y94qa<Kh1Cq9L||+X?~dxbkUnWpiGBTW>bE zHII$pnz`*CavkNk<8tqXiLVdnD%+hiw%2cfWEAcHNEeRYgJR8fk7g(c=r+J3(AHOX zY8bbb;I~&FPh5V3;%TX_7F%_TT^j<2qX-_pDZ+@_hEI~q#Qm|q%$D%|PW!9?4SkBT z%Yi22SnEejZfWr4A5+3r_AuWM{``14ZL@oB4sKTv31noGD;zN=a>2A-vP?p+?ud5W zqICt)wRa%h$L*Njx&I6>_i4W+AS9HNtOfHfE5;=grp0vZEJN(u_)Vl#{T@D7vvhuq zmYRl*!mzVjY{#9jr2~VEb;`Su1Vx)(2*9Y-^v}^W^833wix#2ZD(VJoW75R){b_ue z`pW;@(B_W5xyfTTm>TfG49bH9bZ!#SRT0{xrcOJp_mxVBKv6&+$VK$O6afbO|9Ecl z3IX-5X&Lc2ax11D0cB^c@SX7xv`3GAV2iQO_V1Ja5bEonLn{}YiPQm$_@&(~GJ*K8 zv5_=QHVDZMOB_&K=i<<HHGZUJ7%mQmd;1}7K6sqm(e-Wg(~BRpS{KT#js5-c=>3P^ zSEaH?{`b8@e+r~%)36XaMb1qx_2J+8d5UtbxxLGuhsl&>R32UN+-5;*>AXLbmO4yu zhkq89KQLz6EYB{ru@4+K$jiiWapHld?<D*@FDtH0o{D{`i91sMr`8NjH2*F>myfM| z83@Sw@afCaT~1B+!Jo{PUv@HEHd=NqH_>Ujeig8!(<8B&2B~@%waA4Y$+J{rOH1YA zt~v_)sWvV;oa3@AO*ytWAwygt4q2hU%2H~ops;k#<QF4~Z85W4xB1R1-dY_@sB$)S zdKSV<Z1;c;S0B?ir8H{y)TVJ45*`~@h)O!JOP~oYb|z|wpC~@>b8B@SOueHnK2-2f z!5amJVr2aGQ<{z5&`oBgGoYPyY%rK8gG@j<7}u$;kePb`C{ms}UO+gW9Td&{18ExR z0%9>Cvc4&;BKSs?3>^9Vyn}c3R!;YM_VAh}cy|fAA$vdezl?i>resOLv$Nfb2`aH) zHJ9*(pbK2C{QZ1@a>tuwTtXS{j}ipW;Xp#7GlYbRAW3zWX?c|I??Q5Yy_7_B?CHP@ zQdlDC=>2G7IL-V1wy)=`aOjfzQ%Z?>?P*RmZasUFP~nNj#MQaG)f$^8mENc?x}RRF zO12sE<&(%_cshRvlk%Rt(1EnNDuV9VxVqM}evN7OyRZ*KKV>p{P!mw?ZEE$<aXGiw z9a&rqwQ*~G5|6?y>Qtm-!&z|#-}h+vAtPT?b0e)6A^pkXrI)D}bd@dHtkhK;ESSB3 z)RsQo3Ba?bhH4qT881A0g3#UOpzo$C%N}=wkB78Qf|SVpn%Nzn%YIaFIx{=vu|iU6 z<Ntbe&NOE0M`;zl&J9!t<1M!qje9<>=YQ8lpSLAAic_QR$=4BV^x2?ft=#W87_CeA zqVXS~euTZDq$Diii8ne23VQwfb=jyQ9kZ};0p8YE=*^M!Fh!SUr_oKdN^56BhvcN9 zAZH5T1w+?N=m)iywBIrwXrT#B^Gth!Pwbz2I>ZPJi2o^1P)^&2>CMg8>QPpD-j*@A z&RfwZG$dPJdcYF5=)72XOEEIR^sl=2fmKtgv26A1wBS?!=kqWvgTWU)^~$Q7ppU7{ z8e7*qE?GA89;^TpkC|h<?=Wsnyd%?f|Ati0PVfM*>hHNfOtx}aFINtC{n<lY*$>}p z8zX7d_x3!FYqs;=Ikar$J`^B8dm5J)2@?=*VzmiHv5S{D3MSe<v++-iEM~<`fX?9; zNu(1GnZ5rD+u-2Pp-|cU5Orb)z~ff7_sLpojz;un(DP3EmdAvnyD@%&<OT}I=0c3m zGDKj-S}D)0gDnpt(jFC97*j-vEB$`s0qdn}2)28aKr9jA!F;UrkJIWb+?(Y6J5EMt z_if;tI0u0tPe$f4yjbr9FCZpzliYHP)5`)!!<LV=2M%u7))#*G6;zW=Ic)L~XDpo2 z43{%Q>sX9s(sxG%R2x%vZcKIa3@^zc-2i>Frs(Y2VcNiOyxb|6dAIvnx=n#MI>04Z zc)fT%F;{yL={_2VS^9ppk^jL~^=;y#sPbTw|FTY30wg_MaZJoX`&c$x(yBLx7#O5u zb9cLQtovL1ZnAacg5@RlEGkVZ<9xSF@E_lB3l+xT5;?4Q(-SmdsaLky7InlRO!A|= zdGeOh=i$4F<p@G+h;%64Z%yc|X@@xzVoc|mn;xBPez6ho&oF~q5Wjhxc}y#p<8ozI zn>!OvK9EjE7e6keDxfm{C@n?*_)^Q)_qhOHCq<qlMjZfC?8_diApqa5DRmpu&4^dW z?5PgjPPpear9D;p-1m@n2k`$?K6GL}{~y-Z_rZ@j+DYH4>3u2Y!@nk*QS7(9tNrq7 zW8$ty4lbSX^7rpLZ*kMVl>zflIYVV6I)AlNjR{fVA}HVeAFIIqwHpPmhgpEVaHX5$ zE2^ws$=_@Ir}W=veZ%W*mcy`Rl^uq7yQlARmI-53bDkSo9bPFL`jsi;h-CUTT<2bA z9fxFNJYx|rC`VYi3%CoYx_6tYdJII!B40h_E6#krz|tSjcx-5-H1K9*kA>qEdCQpd zC0e|t?!2;s>6L!$*M%lC9>7Aj-`lsbWC}tzxRrg_Rt(E7mt_TY*d$vFi>6%I@hOcH z^{m(T?3*~hPn4wzUkT1uKPrzNLHSYa<`acVFH*=PUC%Z0-dQ^}oyjq|f}7;4O~5$R ztfQ?K-xsOQ)SP54zfIqcta~f{MT}&Wdlq-#eqH*0e9L9~`Ae9BT&`fXsA)=*8!Us{ z)j%1}BIBWU7Sz_tO`^uT>tE2~Kyw%@2nt2EZPyx6F^B(teZ2K_%4TeWun8h#qDA7a z@}9DVMqb`(x8?!lN2pa)ohJdfSvaeK!Sd|k`E5Z}6V`SUK+5>tMM6~m*gE|zY{WI8 zQ000xoR@H-$jJ{)OSAG>i`?W%WMXFY=2{N**N-uOKnz=Ci5YE6I&CxlO?+0XjlpFS zCN(<l7Sj_Tfi>#OI(t4#s8od94ulV;r3|li0m!ON;klZW@-0PN;u;W~ecQaA3e4O< zw}&zR^1UuJ6lOvNboFCkmQ>7)78vbNpM$29>C5d!xe;R)HrKM+DwwagU#q)gGmUHD zVfWqhJ)Ogr@Iu40`%j-LhPzf@ts;e$(i}PX8A_W{L(TcqUvGEl^Bf4iHU45o^2;F* z3a7Xlsev9B;I}v=ss+=>t&d77(TTuE?oi}|pO{nq&*1l5D_%7byPmW~HQFEi>1h86 zn|#9XR}}=7*!tKw>k{M==>0Wy7YyBA+GUEwJ^Y@K(vuq7d<Hc&3uug4@%&7=@>O7} zha(|)TT01GPUpkv^piMDt|HBdcW+008K!Ko2IfoJ`?iKn1iJeCd@DajQhvKuO6|E! zy*5RiS3qOm<`2vTD$!b;fiebbFs~jnBB{7MF%+cfY}+;m;@rv`c6O17%gRiW1a#kl zjG2f~n<>HaZL3P%id-Cq&i`JJlalHRq`XJGW*zWtW9#>dw<NSWX5jv*a_g_ht*D_0 z`WDWLD`BoU^jrm>FEp?F&wff1t7hExE$G2pOT}kqub8dMb<wO1Ht~UI_wr9vIooX_ zFH3HsM*<QjXx`n1#_l}q7J&rs(RbG=q;F)3lyr4f=sa?&({IUFmtqc?eswIwctpQa z+)||J6Pr#$fy!K;2HZZHg$Z!r6OpC-Dql3;sl_SsBzK-yNNAG(aLT@uqBPzdCgJ-g zvgJK~#XZL~L-f2^*JiEi|BtA*42!~lzJ{fwK|w(pln^AOQ<hL#8k80#rMp=g6r@8M zq@-J#rKG!KVd?H#maS*~{_g+vyx)DZJD-_3=ggd`7a4@(><;<9^m>0OyP1j~x%lCF zG=bLfMGNXJ3YBM_#O{31flalJ+AR*=><ql|_XZB;uTe!s^Ij;b8}MNE7#s(p;uvW) ze|@5Y-JN{DKX$9!$;V^#h54?rhw*h^jp}bmsC<AJoT~Hi=?-x$)felbOwhS~SoLdS zBrfacF6kW>uWV*qr3J$GOEmn5cemxI3BRMwpTWx~K{^}vfQEURMZfj*e9hA~*(-A& z68yOKu+PJgDQigvPigY#P3Cp=Q7v}H{;x#Q?oFtR1UdvN^wVc=v}W)I1q?M*OLbf3 zN&~Y482?3(@4HRjTh}IGY;EW;O!ep#@2pSvDuDN&iw=eDYJY%5KbYwnrm?e~rm5YZ ziZOx2+i@d@nU9GCVJ?Q$_J~_}gLdyK>3nl-FK!>vz;>|T&HUkfGFh!3OS%Jm1JWDF zubI!OMl^jj)-x2YqNseo9WKS6!jq0qHR<#=6ecR*nubxev91{9J{&+Tm8<dJ!rJB; z_~#g`0&<q6QkT56FVkMFtfCckaA^O}|H3&``9I2>!HPR?H6qjpZ_Do|D$lBI^#{Dc z=Y9oz5iSrQTmztbW5l(6mBJgQ0eODjX6TmizEG)RqQd5!T_fSqtnStN>W9iWxz+^> zqJ1OtcS#(q5ot{_D64JUt%*@;QFLc;Vlg}~oVLJV>vZ3En)9h={`JQsem2b3N}dpK zLo4fvy1(94-1kMjn!qiwS<~0@+Eo5in!eAi1Vjy1{k_};dIY|bRJ=O~G@^`BtO8i) zZ3k5Swb6md_$(am<oyhaUP-puVzftCy+aJEYG%O>{JPp@vA&#=`BrC`&x(WrzQ1H@ z-7Y5nnx<CUB8^48t&6Z6k2gFLNW1g=R1ZB^P#kG}4<AEZVNCpr8>WefhOEtP|9Yod z(veUZ!f9e6RFv&`$q)49)w|#De`#6HnUTZUi=;Y9>tx^x_nx(Sz#ajE)K1VJJbYK1 z#$Dzd6CyTy62|Yh>TT{5dP`~XMC5{lS=}Z)F{<&~wi)n@7u880a=F&9fGTWg+pL&) z_)FQm6mPTE)2ATIR&vBxz5%=Qg_9sS0~$gM;TLmvrlLA57Jc!^prXRlY<K0+USqRV zsCUkV{pblu5?vY;NX(y?8zO08m_KIN{;pV_(_{UdUzHybdR>s0m-j1(sNz^`v3t7& z!06(S(bfxKv1S8*hJ-$}T3a5^QPps{4|TnZME%+mOeA4^P}bF0m7sQfX<ae93P&X` zv~~Sj{PjI{N;500lQ*u@N_-};PsU*yW_LuiyxnqC$(gwRNR{xT7<Gz!xWtGHy_`~v zh!(%9osZ!HB>3Z^ZhT5u4~I1@rw;n4VtZ<RabGa-%<v;geO9Xf&2mgyhfS!yYuJS{ zo4+4@UOt5*_`0!1E|=Lop?U&9BEj|JP<Oa|*o*5m!+S$oZ5J9J&4^4!-PuVBDXioy z;{5f(a*LVqMZ+WG4r+Y*PySa<gLhA-Z+mI_zl2n;Qsr|+0D|RGRDdTkiJwC3bL_=* zpW)|lirm|u{SmL+F$p7oChe0V=Q=8mdYWyUxGCOP75y6@d)J_S?MGTLHdwI=lm$FR z9^q6ZI6-rG@LWE9zWA-METgmf$nA{j#5^BNtRmjpb@~J6QQuJ`XGma?LT|oO9P9zk zQGB3(-`Ja)V;(=H{@46uwP7s^>AlqX7aD12@~!jnij19#2l_!$Z_0t8lfzF39ezo9 zjoQ7)md?S@KScBa@3w<3XiReKXBt63h6rdBZ|*pC|GVOwa)%s6PK<YlFuAh3cTsZk z%Lh&@ti=0+7kg&FV);LMn8oNJigiWdzQ@(LXHT5gC+E%k0&ky|ze3#4UG~y->9`7l zLow5^)#yHcrzMfFqdV+K75ihiHdKjn%G+n?cI<Y2XDQ+1*HeRYin%ATYDOD%kTA&_ zwg;DSfa%;n&h`FLIK;2!<sSdA?&zjG1m5Bd{kHdi>uT}71Yfjl^O0^9yhgo#^iO22 zf9k49d%5`Cm!Zth_^i%sriwX*9AvYWynGGU>Shh9i}zc@lgZ~6T5MfzhwIZ#+x#C@ z(akO({4deHolX7u#a@{y^X*Eoq)zit(U;+fgZD*u^~kvb`M7xvl`ncSk;_MmvD*-e z=3@ki_P%e63D|uGAm|e{#fGia?&EGqi#t05TlO?wqbh37yBnVu!<^~}BQ17l(zf_y zZ)LC?!WcvsFxfe=yfz3WEBKu{81Fh>Bwr4)I`yTb&?=OCd*DX09X$Q_@!@YL9q*^z zXIN&2H5S?S;CB2oF)eN8vw%cCFDkQx8;_N;8BH2#&rXrD9Xq{3kEF6G+Gdv#MuYPc zi7^nz7YUy__fDJFXoUzftkvV4lI=0kuP8c;|343(qcQXT|3+mk8iV>(^`3~bPmWLS zJ_L5WhFBcp^WOKynZ?f<{)Kyd5fS<2Ua#2H+8`||^@NZs<E!Tu{tY*+Do37%5ANjx zq?g{r)A(8bZ+mCL{@ir+c$KU%klACPj|eya0Z}SlRRgMs?yKSBq88zA77@ZOr9B37 zPfy20S1lw+i19>4s)f+eJ&ng@1PjZ?*7IH3r@?+R!x(d28}N8X6wJ@8cx8L4J)L#r z&3xt6(|e{{&ANaEr%PmOKpjqMwSgAe{AFvM)h~g?N>McI-i>kilytGng!J8ge<eQK z(`0+`Gu2luj7ogawREBvqPsV7M91O~hp90OuE$HAS(*w0`aFfdk688qJoQ%GC<B0C z&u3AlO{d+Fly05^tM{?Js6FGEdGLcH^in;wr8qc;&$Llz#{*X-5n}#0lY$CvkoS8O zTXb-#zA$AIRvN`${;Jy8mNd70$L;hm7eJimpoq3lT{wKAU+(_=m+)FE556T&|84LK z%*4T{qiVKVo!rpy#fbP95n<HTh4qBN-z5aP4GDU9^cPwq1#W?AG|GSgZibxr)C+_Z z&Of5GRi#^(0-g->8v@ZLFvUyY?kHY*t8;;USkaq>T>SMMF^q|(TObSqSnR|bX_8Iw zl=I#%&^(Q4?eCjKQ$JQs&0*<`{P3W^U?QTC6YzNe5`L>V{XwD16}hE!5eJBP_$lU2 z_?f6ZQ;K>^eNkGw_%vM__z4BqK0dE#&(&F%69?u^MJ7j5R26c`yzd<f%|(w%lJ~Bn zp@*ECf!;itj8A*I(gVAZz(F)qjjKV6XLUvY22+IoVDycM(`{SD(dSLj9~fHW79ieX zcn|DmgS+;DOdo`wntFSQMY)W6MC1@+ei_|`OgV4$*U5M$9+cHUgyJy7U-a6#M_$gd z4-~7WD-(y>r}^O{O&ZxN()}x)cPDzg!-xpO{NeD{P)x_RA+9iYfZguFB}F3S;@e|^ zCEK!#-?x>u0AxF84w3ZI2WB{UyiZ}ezjDPq^jET_4CH=j<vgA^Zuj;C{5&E1sL!G0 znat@xy|Y#8Ek6ocTmw9Z=d!Te2h=`^oFh`U{0OxZ4KcyDzTW|+0ed-MZT!1{g={)= zBxw19dXVW$+yT1#PBp%xV?(&P{~K<VDBd+6oNOds%gu7=^2g=OMBAL*%!#dzasq-} z>4(a@BHh}^&9`K4@3BA2Oki9BM$0KMi`Euc$i1l~?E)13UB{o9OyJYu-Lwhc&WO+$ z77clq`9ZO_;AumdB((m2pJj;Lt|n~c$|FbRgFu-wSgS0U>4YQ%_WJ+r_C%Rr9=(L; z5@JEs2CGuyvjd^X&c&;=)9lgWXei|r;&NvmH~Qy^h^6z9Z0$2xalBa#XHheOJup=u zj#)|Jz5KJveDVLcna#S@?kb3<6M7W|G3c}BS%`b|B$}`|9N~%_13lM}$NKT!rWZdY z$U?zlEO|k=%JMZj6vi|PRKG-z!K&>N$Gz(Y|8jYw@Xr7eV16phkyc<1V&CXnPkwP` zJ%pE#OPbj_vMMneKUQ@%yGdzt?`n8y2rv|$8#nlVXj65Q{oOVKapHOke<Kl{_{Up! zyc)o4Z9PScN@NYF4Kfa?gN`mXKH6hb{&*t$O(pxSI{C-^E1vExs7kxP*YFTI)q&qz zfA9cMs@#9o^ZRVpeaHMpTSRucHvduf<<5udp_T)q?H)t#v!E28y41fr;LBB4$0>CO z`OO8Q5i`Ho;dD;A*?`Z#_AXVP7!Tz#%D!U#Se!P&eo+VJDuHwBAuhhJ@eneOv6e_1 zJ|}1=?VM5mRZ=%(dl|6b+}9=;&RUlAL0!X;kGcuLuBE)Cn%EyV2*X?Hg7OV3Qk(~D zl!UJPMR!uMeT~=PWdLyO_0N>yUqH%ta3SAzuDj06D@mvii|Uj@{Aso=pm_Iwp#n;} z_tN64z;t88RYTuIq5_3JP*Xh)sfAh&(Ea5+$$K#Z+c~}-b2VQL^sb5GP&SmEUx!QG z?HZ|4Ty{|V0~@tK9D%$|;4mE4lE`X>OlCL{bY#rHF^RGx?Y9&qRfpmxb(`WMByAFM z;Rfa<%=MK~Ck@Ub%Z~qjy$**-xjJ4dTtit}a66Mkc)0ec?D1DSx?Gegn*4Q9hCt2n z(T5Ow`B22=!bQESFFmZ2Rk>_0G4s&^V%B;@Z=O)aG9~kBm93<eN4MqgM4IVl+BjFq zeF0)Za6fG}@qx$q50e+vp225A-`Iwt@i^r^y+{*5#<c?<PULD<`_|L1j>5+6&QT2Q zz=6MTubyLv6FCb1zHr<s4!rB%gwcgazZcurOK$h}BIVI%2H04-fpziz*N!;KsX|i# zUb5Ep95QOK@vO>|Z>J&fIDm#yqpyi&Z}FQo!78gF#X^mRG-t6`#cp)VTj=zW=xM6O z5JhSEzEL7a3;XuoxS8h2kvxbYJngq`;ziX;d-H59%hx%VpCnA88Zf6D!%^PmFXetM zhz@w%U~#3Nvkf1o)MR18%FO2LswL-E<HnKHonYeAfcVF`fcJFz$)cGH&D;nG54Z)M zXTSDGL%T?gy`>JqqwMW`Njq&Drnz)}<p`r(tDS}fmV=X6Dp@aDY(R#(ef0r*)rX!^ zW;qqEua>toN*;Z+Uf_!q;`MY5`!kaL%I)KCUVyzb)BkNRr=|T{^lSG=l^0oK_KE@? zUJ>iA^!y-0@_%0ldD(9;vw%Y!@w`BVodh_F9pTYP<aq2@=CYxx#*H3%@`@2({;lW% zxv3EWD?8q`-cJS3U<Cim(RJNY=S63=gFM21$PW=T(E{R0l>^i;pw&q1ZQxJv6aBux zg+9*{f_{v;9tx`_@Bf7CO=A9t4&8&G7q8HLl5RT`QZcN90VS*z>8k2LM7A;FrWu^Y zpTcUZD>EKz`f3j-!HgZfYH^}6yp(RV4c^SYCrjSWp1xejK$hX|JWnXJef;wC#PHC> zhr``Zza+?2+ufD7ag%e@2y+M73BOw;Z!uUQnfyLr=jqHvKnzA>OroOdp2!t4gg1O# zp|*wCH0crX7}|0y>mc)=B#NA*YHP8+GpKu)n4NBZi}qP_J41FZDf!=wjr#qBXZy|j z<<{r!b)IQG*)n3p{dN61ebzd}9Zk~58DX)|FVvi+)zlgT1T`hDAJof+$VvP@4k%IX z7m8nJmR;hwNUuwEO3ijcMI8W?Chg*SXBlyYl+{^3-Xn@BR&0REK99b#)PG^E4G2&c zYwRop!^16#ER{-kh1p8!1|bQ|q3Fd3XjK$c+SIdMTBD8%LAd!wd6>k8L$V}@B&Swx zk8B4&_c@K*LeRD8?*Y2xmX^j&ufq|@fh4ObSZ=dC1!sx)lA14hvgq+zTdY0PAhh3S zy7H^4_5yn%Vo_*ltwDbC!oP?QpyK(UsRAOR#2t(Gat8V~z8j#t3$<g#4{Pm&&RQnk zat;Qzd$&j~hcv8m+d2g!{stW6FgrfxeOf3o7nmjuO79ac<1Hos<)2`?sm$Chd)s!| z@8`FZEwO2r3>EpN=#$oYDz+qki$7#sqy9PXZvps0Jd%k=)`xGVNKZW3tDoGC?Iat| zn|vE9#87+M5?|T=XQ8U0lKqWqB4Xd22i?c}@@&4U{lM(7IW*;X&u?T`=$pgGr%dAK z9oDMzbz>Apl@-Y+p+dqQ<Mj^6lBYybHB~Ej^7Qsd%Oj0H5g-ssgwUqzsVpTA?)C55 z+~EiD6UW@jRoQ;Nuf*#N0$9>TfEz@x^BHlKZ5JtfAKe!;=;0H1)~7aXib*Cp?2Ik{ zOewBrQTt`~Jrn*X86)RdxlpY+%hlzPg3#*8i@KWUzUvpFvWMz(LTCGKImRASoU)Tx zP!KyMWMIJKyy*Uvoz!k5oBT(DQzu+czj_tdD=0XU#lUZS<7S8R(Q4#;IZYqm;N`BQ z+3;`v+#$Z`56ZOC(=#y%B*Kc>Ya1V1j^-Uz!tPxlKgHxveYQ!*qFJpZHqVii6qJQR zanvEU)BDWn*P!69xl)#?V39k$MYqB$PQ{@be81m*Ya?C&?u3C{A4RsmqK-X^3ADW? z(DIM!RV~*-2R;V)r60{FqC5b(GI*ki$E$8<1pBX?7<mP)w+KXkl0%vm<CI&W2`&PD zr=Ja)%%evrEXSQlyvL#Ek-kbl4pXxIus)1int7jmp%Fb$g?S8p9U2}cTLY2ACP<KD zrjC1+zwQZ_WP6cES&VMPFJ(<IDYm2}Mk=Z$e}LS{B_`=K9(IV{@`{rkf^9`M3ZlAS zg1i73Y?68TksYr{<03w1x9t5nos|2@n+6e!d8*7S1;=eUi;dpdDg`K2V@(`^<5RZB z(j1ehm@A0|{UhhIozi|7eN5%Q+{XDJi@fI##}oCE?|Sqa_q!0bf{dce#R1JEy*~fq zeQ1t4ADCVqpR<hmuTq(LbGm$~RpL0W)G;mfh_=}D*gKDCU6Wt?ZzozViS4MdKlIIn zaRYlHhi2bwfIRsje!P0len{drh1)0f?BJJUc@Jk+3&g!qRn)bpfqOGLpqRqSo!u3A zCeU2B>+aK)hSN0||LTlWbHJZ!{QHDm>mNIAN(Ivx+2wn)Bg{~0?ZrYr=37vI#Cx01 z)-rnxM$nxAp+}R+uN{E5*@;-Ct6Oo7FZsyN*!0SE2X8W@Tz97uoJO`LItwV9d&Xam zKeZ)ylH+RT#Crb}d`hx^6aQR#?>h?@;n5ku*y|m~BOmG$k;qQ3*u+}OV%ug(-&_u4 z6+kd*rv6OH2S1E=#vhBTU9tVH6LCE<j3H6L#6_Ih$$5L)SKJ5fR(_Xc2jrg@A1^yP z^~F&$rY26FwTnDb#MaZ>!~BoN_Yi0qfYVd<LJCz(0oBS7E`5=!-mZb{kI7|6p3)ZG zl5sF_7UZ5BZHAfTq9FsWkb*7N7nHc(jFh9GS}mPMSD*CD`A_1{CK^g2aIcpEqAv`| zSo2&Sx<i1053diXA$;|_-1CZg%N}LV%^H0>*4hGCBSCw4iC~?zc15yMBl+wez9Uh` zluSpUY;nL1WezuY4)q`qZ5289&fs=JQLgE5)-1K^WBDBry+k=peq-I=L@Z~3DZ>nI zCG<dDc>@^SY`SCM3$Opnuw}#^uUQod!F|<b7o}4v{6Lc$Ky>no=Z!oWV8d#uj_2Lc zif~#MJ2`2qkOo)&3ST}2+PDF0UQY!+k;{**2;Fup$e*+l#3jL!&$D^)3D^Ben%Qzl z{<D~x+93Ya!nkpRPYaM5KZ!eVE!hC0d|&X-2uIYXgUBlbR#FWhfLnB0ShjCQjpx63 zQ?>z)sROmCE-LhIY4W4heD4JgW~&n1not~FiY%_#<u@QkGKrkUyMhA8*Tu_s(IUO% z>tN&OHZq6U&QWn=fG7B7lBE1~Flw2u?efA25W8ABUm93q{4spF`>S5J!8ZQzg45dI zP{$8|2+HP7%>}hX?=BNAi%{|Ac)wXD@QDaoMF@MD^!MXd1stpZnnFRqy+4_*;2DSD z8r}00u2i0D2S0Lut+k&*@)RsI5?+T%%-vj6w4NGo{IN5TTABLqlaGstj_UdRlv26T zp{rCSg45Kj0z=!NFG}-?4Lh`(x28S>`+*uGl1$nw#6mIa1hFwO+>JUEDcS?cC=^xS zKKT{UjphY9m3f~dG>AkrD=pV$2nXw%3aFQMHnkuQ<As~=W3{F+VzD-%PDw;5LGZPP z;|Jr*Mp-!!5+!>1j5P&j3Y1;;I{Kr#ctM_FbGQ&}CGH)6?2qUy7aHMrdY=*qZLiq+ z7<sEWxNm)5+6MHo=v%p5oJkUT6AK*$@w2wYeT}vL79H*meg0-;h1`tX#~(4@;m1w5 z+Rj6;?*Ui9^IC{L{a|Y?t@HUar*fn|ODCFN(__qfZ)t4?gkd3MnAPcfCQiZHIY7ii zwD7eksiDtuwzy`X(S`LnN>QYmZ;Wmsq2kycb{bg}uQtH%TA}p%J-OsZ_D)wtUW!&W zf_=*o^z7Uo%(@}<;%t1jCNN|a9qIzLp)*f+0yn?c`GPF-o5b?omDfz$Ojt-XW>TAE zntMCG@14~ybj<$VZ8G%B_TMR{@&I(%Q&}#6-fu3L(J3Qa0YvVN!amtaT=GKB5PBn+ z-Gcgm858TjjLA}lD>R@*W`8%JOiYR|zFPCQ+Q@E(oU5LNlpzb=?M+f%@Vs{>7?|-4 zoV{8!DAaNNcx~tJ;%nI_Bari^`;zzA4!@ckK>$(vwAPb|U-;c4wPTU#<r4sf97g}t z8^ApJYh}$v$z3Cm4()&{xT4EMzgtB>ci|i(#7>)8HtMUr{Z@|4Ja_w4;`>40MbJ$* z<cF1&XnW*Q<$gzC&pi5A$N~N30~|p3<2|;7eM{R!<&=!ff{klS87HGJbf-mEm-@Qy zNVb__u9JM0$oh95yPo*jD|z9+Ix=B14dJ-gI`Y25OKAYs5r4VIV~x80?W36zYD$Yp zhAo2q1A>f#LJwZTeIpqe;5va#v0km5fzKwYJfBOlaQUZQE`BaO-wRXki4~2f>9CF7 z7F`#e6VGZazJ0_iLR>CAZnG)mws9PEZ2(fbeW|b1UPmq6rmE`0OCGE;1X)-^$Zf59 z&MdjEyx-ea2^B4nm4Ud564?BlQUA*FC5QC!WHRc_Zn9BWS-Y+GE<f9Mx><ncna>7X z3NicE=W=PN8!i>&3KCEg_Icz@or<aAJ(_xcP`Bl6@-gb00ov7P>tt>*DodhLtk-HK zp+Px#9xI)t!Fz%C8%Nt=TVz++4S}~b*NKnpGCnF{@#WmuSGhIj`k$K62-vsZUyYt} zFnuy$EQg}P?^X#n)4tZW00Ou4O!C;_au6d!&-V9($6Z+FT<h{-lIIt4#O>Ch_rf>5 zVVFE@x|V@Rc^HjTi=NC*8%|l3KU_VK1qRk6q`Z4X|ER=EN?(Dr&0tIGs_UD{nWiM) zmTCL@<=jQ?W+)F$x<Pzur?QB{`*{mfzt%W+DcDQMN+2l~CN5uk5<J6V`1>y7&m)S- zXS2H4Z%c%lZ~A1tJ?mquo=JJ=46kHs-+x@HJ*RfdESHdSeXBs*d?>TW9Tom|zt}tz z*RtdPya0?Jv7Q-o12=y~(V+Cvnl!Kf@+zF1rF?^o><_tF?q)qbemZM20$m1j2l{zR zno2a<zI2GKd{yM)spaAe<V~+YV~%V6@cfOcX?7fMS-i8}%Kd@1C$^{i$wF0s!Dk3Z z<AIQD7!$QnDal(p<)s9y^VNrGR5;&5uTT#?%2aY+X(a0_&L=2%K=+h($5v>DMJqb4 zgKJ@+dGpFK+_yX;s1?M(F2J5>S+VQ$6_aNQS;D3?S`a$}=gYPlra73SGbwZM8{gAV zaZuVj9oKyB!#=LuNl>^|gI~R5at=BH{(ax;^6(l;i;DLZPIfo0z2qA=bS;u~&FK4j zE~~{00gLM9OIdBYXFuA}>3&bR&k1QokxaczS0S8hXQ#yd6rFTG*m`p+wdwrpwYX&o zJ!C^9#nh_3P1b77-6O4Cc^?^>8_4%zz5Po@Uzj%61#KYNg+r)W0{Vv=uDdzcIOkXZ zabfEc+Wi668woXv+i{jJ+jzerRtvgS_O`G~WPZzd=6sCHhWJdS1k~$m)#E=%!m(t; zH4;63_W0y}km*Py;$e7=z-LtJ*)d1*z{XVfhv6OSKhOZS!VtR03FG)q;L7O<*#{GH z!h4a8t+E2=2!AxqnKbJUvdW@PAfiS2w{+&VWWC?*6y0`K70(rWK9uhVO!uMmdB$?N zekJo&$(y3YO?Lv0Jebf}=KRc6x7}RDuMHn_Q^j20XbbJX;$^!yIDAuA4WSRrQ_~cH z&v9I;n#l<WFSL3Yu*N=_@V07n=C-|^D(JhJi8H3AS}_1*q6&=NPrmr-kTRK$W{OC8 zulWa$bz_=PlHkulL60Z3jH3>8yT=4`T2y-y68l$#v~FUWT_}Y_eRTLH%tSNRnZT`! zv^hpv>eE`5rb1dKfp(0%52;Ofb^c4KAF@`=TBy9l=X3*^dQ;<W&zy!z1$m8~{crk7 zUDfIhYi8!|nT~)aZ`N({UqqUEZ-GON9tN?Pil7$%wWJLs6fMhKQrUvOTZS=XpoXTd zqU~`2Q=oY(82Lnm?#)XCDPW=g>)-Cbs~%#t!D@l0L<~*YNtlLXL2+=yax3l!NCb^J zygLSvyCRpwS!PmwaYaXp>n{9gQV0})4pH$sJ$+w&`bO^Ml%K-D7n$XV!G_hpE7<@{ zcroXR^9|Ykro!AW4aeUDh2EPmclhSyh<@z!Gs6?zdxMkrr|rzc+*$^csYsrJN2~dD ztu%1*4Su$)XDC`MkLJ$8zrm|gRmYu5ZcD}N?s^#G_EGqHKPNIp)Qa4QRrf|!nRJC0 zjUPh*>l7`wmt?Y<6qD7#LAzI)_vzY`K4beAIy>`irV7s}CN;Ym*s)gk?~W|5QZ0S} zZ!qg5@ol^#bZ?q-FPG?jPJv7ZJ?*}YDwjQ!q7MUs1DmQcZtTkTUc%_vYjCnoue7g* zBX+Bu*Y9<dc`e02Ny8hterYe&{5a>$1FykNHoI%&K!>*-GryE)kNbh&o~lHL!+fEm zvz4bT;YFxKpiqtfoCGQQ`QbTDyjrm>DTUvVYZlf?`ULx0wY4)GiU`1--ja{h2;))Y z`oP6^RZO15N?`iL;-&3s#e2RFURpI96q6+A-9o+{E2W8<WS%5ekUz44b)(|K*=>e_ z#rCM3Vr64v)REvBc7%+JY!|`)OWvV|aCz{I%b$>LlL0SBLsx;WjU~4|az_t0GuImP z$Oq%ZA1JVBwL+VNoF|2Z;#cA|U-6y0jhjPQ%R<v)cAV~vkn(h#(kD*H{*&chA9JTJ z9`r|dUi({w8`*j9Sm4tUCG2r+F7>ks#<;UmWa$l%jJO(MfEe$-2Qnx)AnGqB`T-~W zjUZiaW2StjtwjVS!%k;9kmF1#ZF~0Gc&qxh=QwJSa|>idOwhmWy;Ln{+q{GTnr%#o z2h(QL|I4yW8EHSzA}H$m3qY-l(z^=C&eB<@JaY!Ye(9OiZ)>Kr(-U=ZC~rFGQ+Qc= z;C9X{Pilk{JaRl}{vcZYlUS9Mz#Wm&0b}e{z}XX(ZdZOrCTfl(MRG1pZ4oK?+@RgH zzb73NDaKsQz2iZh+lZvRz?JNJdPYc7k~@&8P%;wE;$_0c>TR3j=kXo<=Gvo?Y35fj zIKWP{aW5mthu59%7U6dZi(&HUncSHz*hwkz#qaDSa_qdSY}4+p#DF{f?hXgPGJ4n1 z#;DR4wf+Jak@1)9D)UKz!v&#D@ko7@5fib;QkHWWmdf3PIN-wdxWSKC+7w#9LNJkY z`@Wgz6eXJbA{oUy>VD{1J(>>EodJ4SFdg_0YtbfKcGa!Jy(C~tEgNO(r#{zMh*|Hg zoUJOBFD|@L*Bb(kBMe4NIU`Z_SWNbuuybrjiqim=vPs|1ZS^u8CWtCk!86aiW!HT7 zUKOSnaCP^x<tb}21%BqsLT3MR$q6|26>qXDXO>20HgqZ{8k}7mn+1porQJiB^9(S6 z2UDRH{;Sa8%7ivKa}~X%qE-Db^JC7ukl{70lFNhPeZ<{kke}L;*`GHQtO&pPC?90W z&df_8zJ)!!&<xJR#8^G*BXTCf{Dl0?LkWr_;N)H%Un7e8rn{dh@Q?WxpEV^tcZ15l zl>CW%9VQKd>`|p|1ew5BB!5t!07IQq^JZP$^SQ^=h>Jx1c`_s53M%sU@mk*FZ#N~% zGHvrz7?b!%laY*G+4id$*wa<@B&U#owCekjO~_7R{X=DuUAqZsPZQoc`?SaCh>~i| z0H#n++V{Am1To^fW8ML~W5Tll9#QaZ1s%0Zk>Pe}5>xVbv9qSv^4Pw|cebr-FE#N* zS@Y7@O^~faV{cliQe%u~ti#&jQ}#C{7PT5O(9qeFXG#!>Ed{QV@?Pyr2*;>$<p9H+ z<{F)!_4;x7y49VLOiyCWn4NdTMWq-{#v6!je9-Mg&@Mh!NtU0e@hbk6D$Ce-ee?@U zLm51120)ZT;=tqNK;ZWo9$KtCp2)Fm{2`W~Z&c7&rj0elB!G7giV2XjprBLFu>v_; zcQmqh>uCgtsraf?aj5*o{l0^$k_=xcuF!}k+io?v@+f?R!Mqc<NjyB_qW2P7n~=1f zkl<9V^C625b$M>Ou_o}~uuD;vM~sxM%M2*ZilyFo@<Gl)GB@%qI&$lD%Gs0uKkzH| zKk!?nA_Es>h^@&Xte2qXBbgXI$G1MV?P6x-Zum<7anS529P8X{)y4dom-#34fS=mU zbWT}QVklCeFmoU`@ZAsRXQ+y2zd7~M4L&xRvVUuC2T(n8#MlMR>%UB*OpUkxQ7#a$ zM+owpprJK_4&{5(-_P_9O(Yp*hW@KomeAh;?A#*gb#E*odt|{@@jS7yYh2?WR6VR@ z4B*y8s4kQ}C9(xM|A13x#F=r$_ad*C*bTB$5FEsaI6t}^eWk<7!eT1zjdaZNGivIh zem^moT%RX*5BE_mx<{pKw`nj<l<+opMFD4~lw~I7F4DMuD9>h3|M96>6uk!Ov$w+= zGaK?8Jl>Fot?9H7cAOJ*;tIVW*>`hfd<uTClTB`YfY?KsWNnRHR~_CYx1)!=GS1>9 z<#DfJ9{}7K04Z*>7r1te-(Gws9&Vh)qj0!T$_w$o{>}6*3-?+#0KFn3qeJRt+AEj` zQ}@T9Ip>EBB_Wv;{nmxbD%@=4f&Su7vjM)4b`vLq==yludt<K%l4Ca%KppgITsG&2 zTfD78{-_4NveC~2|BVhq^c@O~V+$Ti3UXY9c^cFm$1xicI6yeJH|_J`1sQW`oKB?k zK?7d6SRE8x&05|sB5so%t+V$z9w8XrQ~(AoRvi(9=w~bh@dKCFzsa}P6d_@+So87^ z{d5@{Z-_6n6x#$JeytEwx<w;)2uihE+Si~YNz=f~V7u+jtL0tgRmcCe`+@%@6U&Uw z2VBkk5w3~7`Lj*?u=3X6kRRCGzWW*lg&e)*I7pklUs1KSk4wIJZ}fl>-s-Gazf{!t zm1YfuxmMeSRv*UxSGtHZR3@b-)hB~0kXYfL!Dz(&K;Y$3uAb#oE?oJmZ#%uVL$r|8 zr+RuVIG=LVM2rp08hZg&UiKBuM4ibq_O&|B8x0bCqb^dIJZ1jNU9MP{GqVhKR(_wG zK!Zb1*?7X*XRUmoo$lD~(yRXZ(b8R{>|#IAlNa8AT0-FSi1+UTm#1*E4JS`<sg8gZ z=vovtY2QD#&>qdwpx47M4^=!j3Y!Oh`+<KRJoc6#*oHZ;N#?%gBpoM3v%<KPUf0x& zChOY~=(cR`Xs3TAH_gC}s&s}}1I%jeAw3RV;{bgC3#m!>E%kwu>}OiPVTzDnr+aA# zyigE-h>Ie~M#KJ-E>7S(Xwp@9kvynPap-4Laj981#bo_@+6!`$*0;W<%d)*s-CZaq zne!gsvOJnxFy7R4z5;J7uwaett~Q%HoT+_2>%L>Rzgj{WhULo@`!7vTi>me9{_!(S zKzG2xJJFS**^PGCv_n~*+R-xD_ajbJgDoG(!ple8=Hd!i`SW#O)(6<@JOv-g$ZVzf zo)zJ`FPt*x?F9!Z7*@yZ^XR@e{Ygk*gL}uM>kP+r7tNY_PiWJLu2p0#14>hcZ%p|1 zYiQc7AD=R&wbVJ`5$;1$b@04mV=#+8wku_}n2{*&?oa8GXwga_hT~K{-S=?pe7!-2 z!$i-+_XCC7vJv`?j5#k7LVsF$%iFcy#zaq8%_No=SX5Z7yrnNX7@K_{#w4K?c<)-6 z{3<h<uSy)#Kn1aAf*-xcmiVqZn-;W-8+q(PM*ijRk23Qf21P}(Unt$dCL3=B*+<lA zQ#53m(asiXq~(CDH<-;K0b%kx-wUBE>m~##upBs#sz85B05l~h1v>pn83NGv<Ot>3 zY6jHDv~RoF1`K9&oC4F3dRl)@yFzf0&L><(MPFgr%Nl(5^@$D5&^qR`Wb4Q_>D9C9 zjrwXhn%YVzMcS!hK}u4@gx3jj$H-_0`2nEpw0oHbn$?<dn)%eIb|A|CEc$lmu70~( zrOtuln`1(&UX${UfTTU9AA{@yM9?#+m;j!=vAb_d=z;^+X|i5YflT1aeeb;4A5_Cl z)5=5*3JPHWQXG@mh(1%?rzOP|GIO{E|8->~7|ph&$?{eTy~kI%V)rQ04zCjcZn(lO z_MV=ret6<*?3*Dawm%&Z8wGqQbmg>g=<H*si^jfU*7E9SBUjR=x;6f!)DS4JVJcoB zd*Z(VUnRu$?GXwnk@U?@+r#KxSYrsrXAiKKU$9)UVMk)RGES_M$CDH7h<uVj3wpWj zV}07TJonyJ1Sa!IHLZG+5$q$ZS@5}<9~bv3D-L7Wyss2?^Nz_>Cve=OWG5B_hQM)G zZ|R1lLEu$3U&2$kUcx^(kvQYi`_z*u@S%HkPRU3%@>U%WdMvJbPA}e;60}P*=~fy- zcUFyiJ*0T>;!<sI&kU{vL1(CHAO~))ymPQRE;X`OG#K4V8l+myr3oC5;{@{{y)=^# zVyBmrm)BDA=Yy(2Xpgjwf%o^?zNFr=N#aChe@bNLZdrdvf~Y7aE6Alp{k*K-pB>VS zPW%TIe}(-A6%7xFBlm+8=G4K3zZ!TL#L~|lBwD(Z7z#d*(sD0K>9BC(&elZry%t|Q zn5<#VPRLP^e=tqJe@Mk>T0i5Q3fgeGnQ-!$NBOg=7^3T4zxdd~*5B2!%{~4ZPRev# zrf70e>n?e^&~XTC13=iATXS1&%8X~MI&aa@EFP<$ouh)(1ITcN+U!pDmU0VXH+$Y? zrOrw3qs=m=)O<lMiMulw=Q{!y{X_J^@f=kW@y8}8N)SMo*RM3G7;+d7*seDkIEp?# zx;V=!nTU@ARu#acfJ-H=w&md>s9IJB#KerL=DP;cd^{{VI&=evOF*<-L4AL~JgO4q zqRzNxW`wN2jjS4&2hO9H@4jp~h1~+x@S1q>o_l_y!p`8kJ4&3J_FIp>?9~{#4p@!h zy*}J=nZFEZ@=A*ycQ|TniidR8z^r8iD?KYSLxRTnuuk@__Ggl|YacruEyqy0Q0)E_ zD^xhx?qsh;7icEqbx*u{4T6EqzFYX71tIXmHH{%ZCV6byys+|hl>L32>r`Xhfgv>L zP*)WdRwh6$F!aToHdhTPJ7YUj_bm&kaKs#j33di0=`;O*ouDFMMq_L56UpzHP1=;y zqTUnl1*4udJ)Ce8?;K%ekBl-suECIMg;TBc#Gqm#$MMT--0QX9>z!tRp!IsqY>Bxt zYAXq^NTO4wRIb1cT%m6;BWDqj_cOg@sS}V+VA@&lx%!D0Ss^kmNoR#2-^C~-2<3o> z;Sh%k@Rdit(3ZYIT;2(u4feiZKV($F*8R(O_ccuS<u?6gk(frSaA8@?o}Al5U1w0T zBA)1~j#nXO=Dx#X<yLoX*#37Dh_3Kn(QI@M?j;++WcGljD(C+-cRyMFDI3%_Q9-wi z+RC_#9)%+vI)8@<744ywbiFgVX5_7$#bu=142~w6))(_aYwHIhPiVr;u>tI|l)J?T z)2qZbFgjEPr9S#2xy__l1yY=T(&b4cibGOTK|<Q4YSh>_FZDGa&k>r%B!e&Q@|*4D zSY5{D?y*7@Fx;$2KyoLtyP(K7JQ;{UZQx#?pPDJf{Q=kl+O6Dt4&YXj(%AP0^fX>4 z@P>B{$PTd%8rD3G>3?~W#R+nKDt&_H7!`j)F_|rl1miYHoYY_J9Vb{v5bTf0$}nkQ zXY}_2=9y7V7r7@Y<za~-rZ)uOC#*MKNzXIUiwKTsWc3(G-eGz<_oxIJyZn!E!Q}O0 zL{sxEh_E=ssKhIw^(`gVm{+0hMH+-R==+A{<yOd!+zmJ~bW##~Pp-*n#`yOyN$uV5 znUNzH7A<c3OWyr6uI7B~me;*|Qk2@0^=}&gk}M#z5Zb>2Tud)NW@V0I6lPLv3ALS@ z#wIDAB<Z_UElViwHNm6FdSQDEEUaA7-ubBm=TyILhu%hS-@jUooTe2!P9)U>JE()? zsnCC@s>Cm6;~8C9Sd9WGpXYtqo~N5g#GuJ7KRnRJTRVJ;$u*iu66e#W@|vE+=FwAv ztsvz7V@{)};a%dYz_-O?eSuh`;nulP)(54xAV1qRDqJBw=ickiTN0XCXfEYsiJ3cS zGz3?AL6?to<%VW5IVcB*&?dwADOmS0q;AF`F(Cmj_nFVd5Z%tG`hab7JGBZ-)M-Uk z#7SAd+F5Uf3#;QZ1K5p-EsvV_-b7u(?osgvN9(*A!J7K|`fw)k_~Si6{;?Tit?n^= zn)5C@fv=+_w72)KB!@BDzB0yZVw?!TykM|FV%Ps)7Tt{bFN?O?5=VYk>U|I;t67wC zQQ;`nry#E2+6ad~I{)&~i8#BqePa@?H$`-Jnbyh7<N7->lm91Pwz)e_2I1CP(K`(c z@wSj*enUPcYZTE4qF6w;i`0KJ2HZc<e;_QHC8WXF`6;5$)@+zpP;o8S%#7E!D%!%T z-s!3M-fuR6;(^F6E(NOUa#p%OX<E=-{K}+h!llcbX<7j%OJVVfT?O$fl;NW#G3Ya- z165D;;zzCfkaMK{hAiz~)eI<sIM#r*@ph#Oi9X)Jw*i<CS<5gPIaThSLDA{VBS2%l z_ko5wdh;(P4B>1eQyvS$`EaLqhQ|<jB+yeoX4F8>B(-8i0~O?Z!7dE0*X70cfRG&B zp!tG>#@Vm}a0vG0`g2Lz(Z`m;&x|NG>$ZPN9I@i<SpYBHOg_c4D$*m;==JvazWr6C zMcoN*z#lT_$x}?~+;{K@rp+dczbaF1DI^j9?n1VkHsU=ZbrMH$P#+JO@P>G-3=igO zh6dgR(>+Q17;D)ji1V{z?W63-`3T$TixB*An98`s=hE4-Iun(@8wPiB-e#L3XV9;8 z=W$%xy!<8^^UKTwb4VQkJHZ5Sr<mxH`f|%pq^whUI88l`NnbyWCfaT&&~mzPF5fPU zYv~4ur2RJlbvaWrK{cOXHm{#bx}u_rIi)p?>#<&_0FoSaKv;50a@(WKuK*2RLWCmk zO|Ke(5-W&>U3AsNC(@~W&g&68^^a&eX%uXW+%6S@uCFsde?dCkAqM`v@aV0(FV-^k zdvRIcM6e}Xom%0^0faWUMGi*PRUs=H2T)<U!c`g^OTS?<QFQ2lTih!1p1Z>V{$mxH zI4rC&A*Zu!MLaJ^$xb#WE9UK?f~iLu+cdiaJkB0pN*AVyN7MQ)Sh~*rJteWR5^|lz zcSRR$xl2E0V&d?EEWfYnXfGI-KhQ`b?D6kHqLUW-1M)X0C{>4g(lu0Ju0<vfjPW-r z)~P${Lng#KQl#MfH^I418*S&=cXAB*A#h<0JW(WP%i2YVpL<BJ`1s?<g(T4&kY1TC z@0sEPISC^T?2i=ec)RPQu>`I%kQ7I9ijFN3XU#+F651qm=yk6?#l1deqS|WtvTd0a z2Y&h$Gjq{RGwF?XCbWrQx}L;5>&Cdoijg31Cf|jf>5~ddSGJUga%qL<PqKv7bpp^T z0#5hN-=aPI1d@YH9}?QA6GD6(;v2YRurn^IKqm%1@Q&(6ZXb=O+oGnc?>0A$C8R*< zJ3<GcPWzP;eFB_n*cs+j+=fMane|lyK=$!m*(PHQft*E_N%Nm%sq*kY$x^(8=UE6L z8Dl(M&9h>JY&0nN0=fP>`URiVgx|bduK4rAm(<_?I`f*`OwpB~CRN{tK7XX3G?IJ% zPY0?%+Af4ddy)s@Z#R*OQ9)6AWoeSbnPMeBBUsNTKH9VQS+;hHA0z!beam8ye`+IM zrTt_%%JDb++!gYN#y8&|^sBWM;<hV#IZ8d2La&eiUeG|n1eTCB`RPZ5UGzC#y3@lB zUgaWztJ3w`3$xnJcH<jN^I_4uRnG^2{{DNpxs$8(nbZGW3~%64#CO-5R}7comhBJm z?e#C)ZO3lzV}10TK>-CxLGzs~$4G<6e~MF6Gn$Ocmp<^e=~Mv@6nzj|1(wzd#^*{W zv?yLa(~EDjU_Ly5nb??IN(r3%!O#WeAKy7IyAi&!-iEwR9WoJKxm<)72qM~t7gpS* zY#%zo%JYt#N8p~S5)lJVKC_Di-R`9&vtW1WlZ&e!fBE?qXiRE^u*gX4wOGqZ<)6#p z5AWr3a(-CCX8)QEh7W93a7<hWB3XSNmkrrE+5<G!vOkywLY^#>MB%kffwsI{MTb{J zRb3w56!CecG65kFFQUMsGtj%WgPYjd-s=Ae4AdD9mz&1`_NZVM1<{i`x@REAZ5p6G z^2-sx2>olJ;Reu31nlj-q1_j-N6Sm<yi=gaBq3<ksXz9^jR_(%_r-``+wv5lzkw%) zCx&=UOh+eBP{NU80Q(}E`@^3G|BG83?Y+FOw5-N>&>-nOWysO^b-^L5^=v)M#4{Ar z_D8fHLUj}8bh=NnvBvtQnYH}<BUdcOT7Sqcljc((_ID%q#YZ#$*ni;sNU__Q?|ag2 zEd?Nb4mW+Y<WF-u=0R3QcG-+Q8@&*3PJTQ28w=}4+YB?-8+J?>WBK0pw#@5TwK$SO z;j*7Q=4vBLpTMn@>VX$K{z{*!r=;#St!17=+2T9>zADM+;E9q>tgEYVg$rFMQ%Pu^ zNXL6}pKVOQ?OWZtf4K=HDB_!OT6>A-OuZwtQIeTCm$J+$H~tSrd;iA_f0x9`*bsGG zBeT6WVx?hXwP_-2vJx9;$BVjKZY&$l1)3vCzCmy3F7i913kPfPs}iEe?MC*$Vo=pL zz(f$Q+R4p#y`$}Ix6|qR2AYJSglos;edeLT(Vp~!5jHv|hP$%~vkVhunHkU)UZ~}% z2FMmBsBUbWg%ET2sf(lK_`hnL81i2=o<1OsXeFhN(Qf_OU;i)^M0UOQjJPccwH`t# za&P`|`$4&x!3BeP$tyl}+Gxj|zO>eG*bB`V83p3KcY>X&1G(<rpj2Q`X;SPid+9%b zvux&a0?gR3TY=2Cz3a)Vf(&=@);DO3t)Q$cf3tAB@yGHr5{`~&=WgV3Nc%$`Nniuv zJ9A18Q-fV}F31$8YV}A|1I|D+1lY72mpNEtC~HMSPFv539j+l`P2*ORnOzQ=*sH=N zv=@zMm70-mTTPyJy>=b<-zzr+rxJF?7nL>W9x`<nSG)$|5ng=cM^9Y3Y1;%E5-wNE znqd~hS%&iMdo70R6e+gBmeGOaoaeW3Cd>x$j=ZB7!YOIA)2?FlzA_@~_U#6)`-?Ei zvn9dpUYG~5s>&)Xp(}iVXBq*Il~gtjvbZ}PEoq8Ln-^QicyU?_HLFOR3&h~>8aTz3 zyPfWrq|uH&;X<da>VGfC@=D{jRs=m?+YdoB+(gC#!he9S-B0@IEbWmcZo;PaNa9Bg zn~V2mfTJl1EmtT2gYSXtjOk`C^5mOM1GiqMUy2jhxXZNZbTfQsbCpN$S@g6C;&7A^ zqd$S)XcJ8jVpz;KXM?Y>NR>ZQObU^cwd(#u{L93xn%K)9y5HbZOn!(|mJyU1vMjqs z1?K3GGpNW*&eW_tJMi2k6!>ftn6kjiBN-G_Smwyk<+7t&Y_Qep+bK**^CmC@Of+N| z@e+eS)t5M%Fe7F$xyM@x0HBHH3zRwO*Ac%jXWZ9_&$}OfivKcUf^BI~oGO`dP=<$d zi=h+bt5b-j4z``KeZ8z@yjssq(}`nXsOs2bQp{C%W11`aE4%v`ch<K1Cs}|FU#OJH zLs?gGaKrH;L=vYJ)2s%1=bZWLRhMip6qO@4FFQgY$a4MU5neH6Z%3iA^z67a>fC1j z^9ehjfK8GE!F@KdE7Erp#f)$;&)24CmQNVXjSUa$MP5O3;S3Ab3DtMU-<z*hdn;e& zzz^t_XEk_Yzt6MKKzZp`A4RqyWS<|F$}>}&i!*MhZ5j}=9wVmpdbjY=@>=Hgh;LDX zSR!Bf5!vk&ha~4U_j_L>8$rKT(e{vZ<~ex8tYwG)+(0m^{n{;vpH1tl-!$l$W$oyV z@-owHTLZ%~Pkd0Z<eaUJTv}OyS%!;P_N?YGl=D&FD-qrJ%>Ep1GhWZbUuU=n0Vw|H zBF~+ZI@d2Se_SESgGhjc{%Q8{Y?9W!=XtuBYIfPmc6zDeU=#BvTNQa<-xJD-;Zz1? zZFH<%ayuL0eP8%Y9=30anwhbYBduFq#Li9{zG*^enSHO|u6}nV#pL+}9A%F-%BN_v zdWMya`|W70WHjM^?$6-9QwHu?-}S&PLUUZgwN~w}^Rce8S7A~HyJr3>Jh`Qgv#3Ez zYUkNfR$}4lNnY`Nue4J+a&};xczm0kx3^UzsWtg|j$f42`P#)D*9xh3FSBrfC5G3n z^ojeRL8)gJ`{Y#8t~hIHQ?wnT|7aFpWq@O1%H5}>YBkRF<{RJz`fbZirRU|$VcDUg z$L>`vR+rai`XUBuk5s{3Kr-1rmItC$*%lV!9wyM7n?~^bhGaUi-W~^uOXA75_|2S9 z!5;F8<mX9yb4le29wa$ljF@wjMNL0ILG(|#e}D1)ZxFOrL5y;iv~dwJvTK&*u!NY| zh5SL)+@#Sl*dz7s?nf|Wr{K3i1T7Xr&rPdQPD0b0l)opa2qH(o?(H_iG^6!pRiAhl z>eqgi(SN$qX<`srHWtH+IEYt~G10G@*KS|XdzOecGuumzHQ*0pdUoNXfsK|~R$3TY za=gdey0cf)82VP}O>TiU#xR2ZlE|JiZywh2p?EOS68MkxJPZVDk;*)Q6nT1KT)}Jh zlrZCP!~3tFf@PoAsA1PilYIT7oT&YLfhHTKl!6={;t|c^`QQm))1+Zwgq|(NNuKB% zpx_cscXGIC0$(JI_NAp7;~`OU1p5p50*K^4W4$kS>T&~?<OSSB8e?__jSB9GkCaX2 z!HnLB_prNYCW|@Rc|u4tBJRzl=0TjR4Gt%~J;GX&@6IDdLWA**k|EWvkd?T!))(({ zd@llEWE9}5bFA+1Y#Q%6`jl4b)4_+X4C6R^iez4LZ^UXhmd2$x+FvIT&8^<Vo_=fX z4Q;+u^`WMk0RK-~Q2$R_C`#{iCWsKXMs(+Z#+x13Ib6Q)JjM3)J5doj3L(y&4;Qlz zTNvbJ2DR$oD8A>E!kUP_A{l`M%Qi=alhd7ne^K3GwK?DEW{b<+?<Kd2MS$i2^K5p& z<EZZ#zRktYR($`@B=y+YZ3ZicsA;%oE(jU=F8NaQmTa)knh!=_Rsc*DF{hYhAUESx z4{tmI@&&8xD!Y+e;w8w-Opk-0z~v=(U}v-%YU7ayQ{WW!G|u#hP&5k6CyIqUc0Pd^ zNqZ-ZLUkn}lPE=>YsA5_wS$^^qE#p#F~L;aediCil$o7(8oo`Ab3Cp%vlcpucO6VF zu{XpfLQJMF^VO6sDJofwZSN42`5|q2P-}FPxUe!=`;G+Z$Oz?X@lc~u?hSzFg((?> zbECZ)ZzZZMr)@>|(IlyxvW(8xbXK9U)g;4mBBx~|`s=jOuqStQq$!05*&imA$6kF1 zV5O%2|LA(luqwOeeOMZlM!LI0QjkWvTUxps5dqnNq;yMncQ;6PmxPpbhcxW{UYq-Q z{?Gk>_<h=k9=fl6t+nQynRCvW)yhBs8TUA_yL(LfHhXH7CMNV0>0Grt$WJ{H3#9{3 zCXB9&^`~93eHdmg=g>KKmV0K~$;OAlcVO*B-8pY0*+X}X&zC3g=6pH=Bpu=td)Fv? z1s?N`rz9gi>}Tv!<0Q`RPChqt^x(XMR!f!=+Vl)TKAGU{FIr)=o?2IbjWA-(WU6NB zgv}yIKJfDcbKXLrQrDJdc-s)Dmy}eagxs25Dp%V7x(8@R0&as$>qB-uP_uI_Lxl(x zKSbWj1<CIX4kZv)Mq*Y$oNH+4pyb4S#WD>~iMCe4x@NZ0!x}G5CO*3Xb!r36pR_*& z#YT-m8D5W$A_JfVauKE5yQG8~;~Dp2&D!s?#RiIVvC^Agiu~MgW_^=b<KPpvP{i;W zSD$LyuIDw{$zAC4c6(+Uk^BTlR_;2Bi5ymIIv(HtVNBSXARa&Y=k$*Mb9zU(vYI`2 z=x{);MVxxc#C8J46dI)!Oy{(?e0pxhq%8tA5{>(3i7WAb+<1RB$WIA^c~3hF3Qts? z7~jauj4WL6J?hk#fps33hn|${g%@NwX|k;ifAS9})jjrI#mtgn%|pQT<>9l(YZ5)@ zocnaq35rP)`R+GBVP{cZByL3a+wEV0HEm1Y7~=l49A~*c^O1fmY8rI=$oEd7qMwmq zeka1ag|q1D$?C3nB?s-&Uh!j2I#rTghdb&&_@%+P#I*~)yYs(?gpYnmhE13`T|e5) zT+j%d{Uj-SE~6rHzW=lqYkCml>+lk`rCs22??c<ynO1}j5m$BCmbH2x?2F|d_F)*@ zFAj0UAU2<Bb<w8Gd9y&1*A@P)x`7%xk#D`-B1KFsN$G@~8hFPx>P7#e6ESD96j577 zx$S;+X3+-^hSh-XG*0bJw}zA><KL|bi~Wg5S_JP_?Q%n0GlaR`iyDE8^*tZO6aDMF z@=~L%3YrVXyoGF+CPZFMI8o#+H6A-{i)^j1=id~&Le1j-jxfGd;++&wW?*0R*+vl= z8I<q%sXd`mEwsm^Q`?-j^T&w6&xdLp+)@9>&U(QK&2R}SwXV8oy`KQef%cEALw_pg zK&22Z2!HF(vZ;AUi&3^~fZ(Mg)$65gC*GFV+>oo?wKRw`x(@b@2sY6&;Urj45W5rT zae&p7pTP_vfz@aPU;I-3q$0zB%Py$b#!(AsPPlEC<<-;~$1<oZJ71uj@&?L#s6SmF z;uKOJo#f}g8kzYIG#)%3E?OBBt8h!36+$O{50_v58zw`Sf4XHOK3AHBc;1h2?2`8O zupSe^<9(9W-%d>qie+>w%nh@>^ZEcA^+FnhxMyt+tFz+6lPmFiJHb_YvX?3<j=y@( zERQE(R5k818;s#y)@F7F;VdN#@QuWbUf@-V{6NH=Pw&y-XD7Xnoh|widx*M+LFu>B z&~>VdMexL2w1_T}>GAu_GT+;zB|u(O1OdfLZkqEk=hGLOEHP2XZ%c=8-Lr<u$B+UN zq#^UuYVjw$OhQn+d%;${vQv*Nnx@;SZ+wko0d;!rVR;|n<XHaaCiuT-6j{a>9U`Q> zh3lwR%CG`3B?Wv@1j)>huG6|$X%PXbKaFLOU~iv84?>`<!wxErYpL)4GL+dh2D;_5 z;EOLc+As2<$+8rizD=l18IH&(IV;)zJXj)ar~*vZyF*qR-}Se<9t+w~mQU_gpe+Xo zQS8ZRUkhP0L_^wAzNCfqD5JLq6^?xDgPDJfJnrJWefHAWX&+Jdz@(k(3A;YvuK<06 z@KH(vV6?+TwAbmya@ubWkImuo4qkEE6O|>(@+oU+?t>qIo|8Y7k+emHOTX7mE=3kR zUmk#DCacbuZcA%D5SWVinsR^dkfS5_A<Xs7ixlD8@mi_e_b{_^K(&FUe=rt4^*KKB z;I33eh0V?(@5xZw?i0bri216`Md+?l5E3Q}Xp`Mc+q+Doj9r*Z;U0Znfrxy$LMuBq zX3}DyUi*3<pi%OH*XV;l*@vs7Iw4ozqhB$R)>`4-LfcryMgw2}8h(U#<$l(N{GfU+ zAK2Y@XMcdSD!y>2xeFuDm^#YbJvzpKCq~exDft_Zo_ezF;-8MY63m*V?{6H&gf>pX zgX>7$eW5*y3KUZfm#eR}IUmR~NCoHBr}T>zQXcOoN5qG8E0<@<uhVvgmNkXbjFe9q zV_G+3k|vX4f3|T%`}$nj6om=jYcCAs7)Cs{1+EBZNAw#nfdtX54gOyK&cwj#?O}vL zOBgEyg<Z;s76N*n>Eh`_I@yu%88-%)uNGnXPP;#%zqMgRm$57~@&yweYIoQ%HLly4 zrZoK`X~wmDtxj}@d`l{@PL%dFEzf60RFu3Fd(xPxNeIQtY(Ly`YL>FS6#-C~P52sK z_PTc8TnuD#b<e6_vK(n=3wyx=MF<zHNgPZPls}NB#RvdLC)?tEf!>%|$@=_}s>$Rr zk>58ZvuhyYRov+whTw-)dqxQnL9IX0S%!BE$ToQi+OYT7GNN#lPcPO>X;0dY9XtxO zE5L6DL6#P6vqxSAzO0(xjgS&HH!MZh5Ml3ExjIoUFm7D2CqLv-fp+SJSjAR*nkV8n zGa_$3AQj&^UEU(hY~5d9etWckL~<i@pNAxlf)|F@u1K{Y<>2M}2{gnefj>Hrcfq!d z8A;_Jm|&#nZ)1lt9;SyNkp4Ndn(Q)!1I4NmBXlwfYZ41~K9&cbD&!}Ewg}aXMGN)B zaJ*79xP3hCa>9!CLA#h?f1j}C#lZqpihj3u$={{E^7Sy#WripF&&b~ShNWws{vwTL z447v82yFfa9^+$6hG$)yU{2%GvB|UEQC8wv?|AN3wC?W4p_I@I8%w-k)0DV{=JUz^ zSU)|fqQF18<pgzp&8dLzPs`por4)XlU~}xrpenWKZ(<)-(53?*cC5V~5SCCxIR+Ix zrKFx6P1_~60ofKMHqkmNq%08iEX2%Y4z46E*nnH}VdIP|>m9ejMsbx?yEw$_{>;_? z^1JMd%W%`{&fh&6P8f&6nw|~ml}|om*ps7`ee3eh!fxieJV#Zp3J(a*M^czv{yrrY zy&JNSGhY+#C~vzmfctb9R2z54c66@o$W%G~^T6+wrqer=J&R_VthFHcI&@qg(Z`Z4 zN?x8uQKE;R9M(qzY(Loe>M{%eD02>KmoO+RepZ`*dN&rdb=JrU_L38Q$7q@0+hOj_ z0e*|J#{o-(JqdDPWWG9Sq8m)V{cH8jwP0wzyAEbU#8UE;Ww6G%^b?&8P+jBJ%)az? z5F=k>Rxsz)^kpaIeA(-rcn!&95tJDxa!xGh8;S40q3r6=y$^pUJJDaD2T@uZ60#gc zy*Pbk?F{j@O7QL1S*JeB?s|A@n!2;qqcvo&(mZwnF`k2QVVsR+*E3RmV%Qgk#XjW) zFa6Uxw+Ky@XupEZPb<~o(jmrbIh}!Hoo&$#;F}wnHlDC!P?km3Nz=hM<`=SdCQMCF z*Vv%|bwTY{l5dUt9uFpiSrI$MQvOhM_=qD?rG=lCN0F0~l(YetE|{l`HM8wgB@JET z@YP3BwkBzxADyG*HFNAJ#=^-?ncM<(S`}69rvDtJf26b4a-yws1r<2rVTiQh`+W)U z+8-RmvW>X@NKM$yXHR+YSMYv}Pu;0pZ4UEvCfyv!{U(n+^o7su;$qUC0!SDvpN~`F zQ{bU=8-~^|wr(%NE7Vg7{mYHHK2xj8wTqmZsPFyY;9c&^HjA~{iZ)WqYcN}AevR2` ziH%<X*VF#ZlYwcTw>3SjqWE90D9n5j6d5@ZP;FU38`lk{Krx}`YLd(4kn7bGpxIQm zuTk^#pA;tSWaiU&vFM#l4urV?u?6Y4iA>5Se_L$izV=lqnY#Xrgk8em3O4OrVP+pF z<il*8&sDldJJ%8wFHd~}$G;I0j_*5U8o}&^>@=nvjE?}J|G<3U%M-$8l)X^CHf^hi zwb&F29wFQn1!^ptrdHC2Qtskr)qd~g4?eob8MJuEme&TR+f^yVW}7!?m$IS{S9@rJ zmVn!B1k~VQRTIz6G>PQElClmo8mHeS%UN1&9c^%$3msoBTW<1gFW2fi(n;MBxkad& z4tg`UxB8j4_}U(-G4#tyh6tl9IWP5h6^CRm&f-nYOl#DWfrgYyq;~XS-7No9n}&27 zHrlL-M*#v*&yY1h=)OGJ$RuNgTN73#FNHnm3kefWqCR{K<jNkAQ;r{CiiU7&oc`Xe zBmd}{TOZL`QCW3|L{txG<RgAPE|k7nT}gWaaYTAP1;O^c!KIrGZt^mmdzC~%MjCBc z7uh)vRu!EG8z3}XC6&IK2e<o--gjn^uYCiHbRk<UWWL|Cfp-h=tv5sSL#D2ByVHeg z!Z~fnO-N1s@960VGvpc5uzr_HYwvsgCX9`aPW84Zio{1ngn81Nq^74D#knzJ-OXG~ zS+)2_W*hSyqxEG3ab+c}U3?DhEymNlvL$)+_WwaF)#4nm5##X7T8-!Hy~Bt2{qZ<D z|G!PT+nlso$82P{NzU*4aDB1`d5Yxqkn~0}2?)UzK%_P!waaT){$$;$zVeW1|1LI5 zu9Y}s>B*hnC0=$g9)+*tm5b-6NeHLmTNncsSS0O5I+2yj6dF3S;+yE3s5qom5n=5~ zM+(yP0M`_~6=JF^btgpbho|Frae4a&Nh63+EMvG>e6ke7^lO5$N>*pVKozwbsG`QV ziCz4@6cQlq5I0O)ea%$3Tndub>_P*YuMr`;#gJ4#^(J_?j`u>r@=oB3%cQRbe<lKe z!W2-3%-jShOBN!&Lk`TzLhG^~+wvqG3g1Nt3li4aef9~Hv%v>4nv=Dz<YDfk*3)DX z0nkzu*G<OM^q%k7G^jhY1pW8ti3fSA^xJwE{;|i0HY}<v#xd_x9hI&K!MEU@1Yfc* zL-;j5w$}XDU18gmf=rD)my4B0N_U8-0~iF4H@i*L!2MTnK+q196)}!6-Y&4`LF(^1 znM~)w-SZ+=?7G@#K*_@2SIhL!XdXciQu~T^@qrvT(FdaN5r8X%Ux?*}2P44y=z$Bp z1wU0hf(H=qY@hHLjVkJ9g>GL6b>4*(e9G5Syb=2<7R3n^!?u@C@nxkf3P|)zQ_@z? z35tW8{&OaV{z(dzaaSC2Lx~+)D$8$5IG2~Vb}Sl~ZO(af&vLy`+qN5opWyhS7=a<| z0c`zG2n3C_f~^$_21G5d(3xk<QxZj--Du!IXdo?KFicWbMRSYEGsAuen&vd6r1efi zefY~TG?+tT=OEF2$ylT;+%$Zx|Kzv~FukpT0|5KJiMoEz3AzG{naQbj`qyTcYRibJ zn|NXCG*?s6Bq+yKCI!*N$|}+I?b2nbXMTN|ZSbJiT-~PsrX+4uS4(2%jGc!me~?&c zZQ(L)G3l}rA9-85LqPu+Rl0a(`Q2FTY2pGhO^6<Ojg)ZvCs>*+jNlX-X;Jl*+p)v7 zh4(KHAPpxnqn{AD=S)Rl88<~5;%5f3w_GdLcZR=OPRw2w-oebm0|J|){PBa+()D?C z1mkqi6Qu)b+-)Na4+B|4;oAd6+^u14ft~IUm|~l@Q|q;n-<ICY&TsLTBJ|!|EC<QC zT#b;LU0+O6Ii0L8h>AK+pF6%@y}UlRH<OlX+u1cjFYnL+2VWS2M;Bs7X|hsnqrq?C zJkNBDm=nt`fuPIiTWxEme;wDzr`6NBt)MsbrM+#L4G=Gl^iLNA;~?RRlgtbp?Cyt| zs<x0|0E#ZKLH=qgPr3NjHRl8UnVY=e4-bvk6Fw97B8L!YUj@i<7wCXQa;Y(+6NjDx z%8#r%&qdLH&?5F9w2(rvT66kl$oHq(Pm$4ln`q^Jx!Egqw&ip<-KG()bYlgv;r?U$ z@b*hyc7iY^pBls02IIx4ufAd0uz<)Jfc|OGL{53G|p-m!yA<m(KSPs+L$X)J*> z-1*sGGR`UI8f7G+;~x?hRX;+AEBsTLs^0PGRqV0?q6RVa@&=s}Z3W4CaY&hg;tBZk zQ^Rx(u38_8^P-w*=dl`7VnE5ama6+kPuDLxA+?!tGn@H$V#IldbF8}Bm)8(xv2`{B zZCR!SS&61A><OfwnOgQ*jz5HsA&u)gwn6DI?Z?Sj*in`RDHj@o%C$>}`TA=8!la;i zM|SF0kRqzXhQt`eXbT6RN)nH#YV_;&2EW@8PBr62@lHxYZ2s%2tMiG$$SId6b9-~f z%pa7B=qnWU*Yf?BC#_%*?eJId>4UtSC*xwLcL_-V1|GU3DO1to&1(=bq@MozAc>cu ze1I2HeP^@^j_PR?F}phkIX;Nk0*X)l;GV9Td-8D#t1?JEc;S9+Zwd4UcV&r3^2h|< ztv9Pa;F;3_8pME(apZpzBi*I$;3~V|fOMbZNTe%5n6mBmW75<8<}ANgj}m$CZGu6` zmg0Xz#5rIdn^S)tV56{xUuB3@JDUKSDlN!l$FKEYbXIk&{H3kB54B*+n<qy=AQY-$ zi^emrTUm#VTM7OwZ<9kp;Ewor-crh+yHM8|&>=rw38+*uH3`>w%z+m2erFk9vZsQU z9+&6I8^D|JOZ~vR+CT0RO*_7XTs;nGIJIJj{4Y@iM6%GrN06xM`{&@h-c3&Qa2ywf zZM)sSP%|`QeZ0}hcsF=Ud@X8A$D2OthnBYC?AtIoUnl1+;t``anq}S!XSTPy6;bF! zP@(K&qQBAFfu46E_7Vih_Tzj{JZ@dI4eHJ@S;P^kDhU2|>Z##e$csv#HE4xm>w>+% zc!9!+wZKc$^=xN3{W{;}9~M$7*awp?S#DKR6FN<DK0yDv>@jTM*7Az{N{9I^bED;X zdf5{*-S?O5HfeoJo%JAlGO>CmTbpv`4q~61ZSXf)(O?*2m{I_#U(Cfm=dRFs5~zMu zVb8TpH)~HsZa%y7XbS)0`yY>-{KlC-*!IpfH>GNLau6jcw)3BUI?W=4-yF)j-G`B6 zq=pdqSdAEoaX*#=tXnO3H}j^`o~#4YQdE1vXHL88_;dYIpGUic^B#z=Fwr0W?^PE; z&9QZ%zaD9ubMPNs=dTbFsI&2VNmgg8RP1efwYHO4gfS1%fjIsl7m)lLEG81VyvYHn zK3}^g|J8+bCGdR!(n|;Tm>l{f^#Z6#QNJjmwTAA~KiegV_gtsiOM|ad_Gpq6(B_5_ zI?{VI>Ued;RCm;ge~QJ}Z<+<CW7Fj7{8}ywr6;M?<Y4r@aogY9)OT%{>e7gxru@&q z<^92h+oxyTkL|;;mBQKmAAsCRJNzNjI?QIIApZR^i{AW$gpGy*W5bi9g;rS`#Plm4 z*?Po;uuCYl{#y#Cs;cYLAO$1@X_-Sv8Ke>1NpJ(%yPZ7Nfml9uMu2$1Uh)>ikan`E z9TCXfJzsV;)+9+PwK+h^zOG5E_~+n3U+A?Yl&0NOQ%AA#ksOK85u{JFx<7P3-9I6@ zLc&+pYv_oPD?KRS?Rjg$PnWvCr^UCj;;)R8?N%R_|AmA8^F-6LW>$HbAm!Vdl78#Q z2PcOPv#OhPq@|pNY3`hQ5uaZsPf=H$P|9TFOE{E4?nC>W3W9}81tDEB9TVF<86UDo zVsSTs-{S*BvVl~#z?pWXF8jy&t0a>ezQc)temGps*7u0ez4%s|Z}YFcz<J(_6$aqS zV7L45j+a(tO>R@$i6#e<JIN!Mo?n=GPU-7n#2gW42HU!Fv<PkKcBWw6X5a~j^R4fg zO}?k9iz!BjHblK{Lk0O~H31=ANltxFJ;lllCYiv82RJ<V9;lFMF{y^D{#<%Mj~Mh# zb#8~;;cwMlQsfT-ho#3?PkG~u{OMJ9^p@&2c3?CDJMdU>eYuDV?%RA9k!(Z%xo;BR z%SGHetPi6m@0qL5Ny`W<sGwTqf0FuVck^r?q0jbVXHAjyEg|nFO^G*I+hJ-mjh2nU z*sUux%ypNvu?RD0^<YSok>&nRa1N;9a^CHcLxDjn|E-X+MRxy%HV0`#kjMc%^AGL5 z5B48+>!u%T?;G_6Y(4+cpgtQSwCC&@Ie%jQ;(=wZwK*2}>EMn3)MCy@nKSaRMCW(_ zw_oB-UM2!e)RvzH15t`}gAy?esg5`6r~mz52Hj^5xt(t*8n+JTJc@vaw%;*{1j)M> z6(8ueA;_pzC>FqFx)gtRWsrriQ544Ae<LHFkIeH%aSm%d1uj;jCglV;I*`-3PTXlt z+X{pKuEf87AP2mAgs3camKQIX<pa4_GbC=ZcieaNQ|Zz>AaT7x7YVcf-(l!`^?Vov zXYf#IamsA^2631bQSo#@JtzB^6`rG(Md`z=*yG_6uuJ*4G+Auuz7#AWmO<N~LaO-- zdSLL8_9>M<BvC$z!6D|_>c80hABcv2yvKm0jRHuex?|AM99TIA*g^ZB0D=74P#$P? zfcm26!8ruankv6y_&U=l(l4V4cnTD0M_9RTAy`GDLEPN5=>`<1!8bH$3EAk_ukzB{ zCuO?nGlX)MBo~_Gq^PlI3pRkEX3DUBkbB!(pjD<3xS!3cbAPdtQe_s)>Q|GFDhR>L z=lM|=lvP$6kKPVDDuS5no<$TmWtA^4xcMa3MWnKNu_iUT?143_2iEKtO&0Z{U4(BW zO-o|Kzf7O!+4QmH^^gIEwUb6*2zJI}vq`XZ5r1T^9h)|V@TJSRFv_vXy4>eN|7E3# zM63VK0_ZzeyS%mX5kEq<3Nj<};f*w}e?)_TO2LFMq};l-1hA51z-EMbV=cXcIye~M ztQfpt==(WDp9$zvY5A#@hI`Eq^qh4hVFey53EC>!#ovf$<zY<<!Yy5ksQBHOKn9Ta z2Hjr(>x*Y3|NCF-i}$>~U-AKs&xD7T)GsfUC~mE*Uo?pcc3MjxhD>qYv{rw`3<wV# ziF*(;ASaJaVy>ApOqa@W97Kd=?-x}9txdTmP2V>=RD6Zht{ejaT^Ui!6Y!JCp+DJ& zaETcDlYM}-8IO{6b{ZP*zh|J=tEsKo`ZmX7{xm+V)_9~<tnJV`%TOz^X_9?P&x;O{ zlEM$ZU{g9(xj^1)RPlEBEb*Cb<r*Qa>Em`skX}m*W&<RfeBwpI*%SFYIDr}=z}92> z7S#Cudu&ksasr?2-w;sVHWglKskY=%Su4O|LcD8q@U@#52qPkJ5S>(=w#7%w`(`8F z?5}_2C^go!NvZhjpBeNsVs&~*(s}3uI0yuPm?<k-+m-wV{yP$|R`^jab&XJfSpha7 zoj7XKNqXN>PT11ILQYC5$h+>ELZ?1g?aU^$I@!gEFm`G=&%K#67E_v2KntS4Dd0QA z0>S#{;B!~^_g#xLajacFW%l-eew41SH#4(^cN=1`^pgJ{GTeFr4|=+Bx>pugg%v=0 zUv)7cj8YcB*<o}Q`tegCFvQ>5T!z3PoPYJhvD^1P*fAW7>sYlp&6I|njSx$bwt{be zfzeTrwK5PL%&~5U0h-??bP7OU_UmI(z{n`rftT8q%-dUw^Bwo!a`p6VIr>#CA*^Sl zJfEk{c-FURT9fC#Z?9JQ=+56{S3}bm*4x*|h=nJiMr3ry#HjVr4xrSpg<xqTd`}h$ z0DWruB6vV#74imU@9P?Rk2*K300~+x@qhh&kf^LQG(4+-VS-U?_|g9-@$$o5Sl%17 z37>K1F@<W2OebLEXT9P2xR)XxevmW%;|^uszWE*V%P-kZkuS-Nc^+>Xz~v$P*sx1> z(7vcVEDqQf8T?G4HmLiBD!c+;s0XAoKBdLPFut2{my>h(uJGHfsBEO7VVUoHUrMnB z_TXO4a-PH8Yeqe$cS$J@a<asP?Ro^PKMko=rje~OV2)4qe%(K09*Ypbv?oP4@J&Vd zuA+(V{0kjvUp}K_(+s{SLZdV~v#LT)+zk^#1hIGdgd$oL1hsfe3+cYON<Yylp<s(E zP6ZO3x_ZbeXhOj>_L?U=Df7YE!7wEy1h5|9{_#e^0%n_M6)6Kc)V4|FrdFX78!n*1 zxZ(Tn;ICSED0^<Qii!#L%MByp_Q6Al{2!wX!{v()LYMQ=2lqU;<MP!^w6bklBH~x6 ztys?-Fd0JO-KsRj!cLSiX_g_ktq&qVW8YDMR`Sg%#YdpWBg@kHUvYoab3a8=5j)IS zApD|fno<w~-kuqKKdLmnW67#F#zf8rA;K$8puwzIj%bUM<WpCF6^ESCOaxQQzdFny zpj<F((s+f*(j4&fzY9VQ=8mckA8IhLKm%LL3zhFpIJ8+a)6=xHw2}>Wi&RFmd@+jE zJU0jBB{X?*q-n$k<RvYoH0DJq4n|ZdKvXFH(CL+sEXg*79M27#!qj9Bk!Z97pM#bC z0sYC8a1ZicNVje1zYZAIKjeGW-@s^JI&uZP#W>yqxKQK9J*r|<lh?h@;W)PFGL~q$ z&h1rITngwvEYReqlT*nJO;Tc80)wTcQi6b8xeRmZoR_Qsv-07tsIE{i<`Tks1p$Z4 zD<~<D7kHZ-Kj06NxoOv1j8B)DaoIjzwb+x2=JG46Sd$xfNl}0*$h@J*%q@cRohGNi zTfU{iJ*T2kFa{=rHMD1889cCh4Egc$1T;B$P4ON0-WQ1oJpY3)lPKWpRrPx*VVAsQ zXk(1&trcGC-ihx1fV=pC9zXLb2ps*x-OnvX>T}cxIty9hUY|<<Z!Smke}fM)BK zM9_=)jZVjdwU>Z?bSVX}dXs0X&%!*$g<3s1)at+G$-HwyS4P?U``1lYNhwh=wQ+DX zzUPJ!h7?_8{}*b}J|dxkLn{d~XN!iQo5mMQ$%jr<$JnWjqU5jI$nkHX{QXK%)zyuN z5r`gm7XzN+_G>O!&q~U=|Mw<9N=fiftB%D|cXp(T){t53NaQIE&Zr_vl<PHvg<5xf z<aIY?S<nrrxFOZ1>Fd?+_@DwghyI^SlIc7w-HZ=q!YOp8nz|{#xE5m)pcUe??P`^0 z!wukT+>8;ppe%VGG_eR1qg-N!`Wj)VuR)TH`HU<JWNoXcuq`^h$a_vz*e11GS<`y? z4$by1PR$y!Az^+c4mPDPW2R9Z&D`GFSWJXHM@QnY0(BGBH;2l<g&2*YT$$UdR-NN1 zfB}sjx;Fm{WmM__=n?on!fxM%Y4>&=M)rkia=w;6pcP)jh?GOOr3i{dus#|z$L?pN z#Ft;tCQ|x3%O14P!F6T!c0f)K9MF9RX#{o%m}0`C)=Kn$yoLK9EldPg7N7kRi;<u2 zDv|adqpy{3ttv_q>d<1RC~`*%Ws+}6g!odnYL#U!{lde++F|)JW?Iw8d$z<;u3pp) zkn^Y;5TH(G!2Z-)Bn(6alJ#3h&HuA~3Tyy#NKrI^*?E96w#2L^PK6FZhHdsiv*uUK zl#)O)4I_%>pxC?lw@GGPcV_*&ftGa5y*1~5j$lp|K%WBd9n$d2#S+3A9$_Lj$T#jS z0VfcX@_+ct9G_jBNo$p^^v&XRFgvg<B*k+jQq%6mdhWz|xpDd8CJiO~*P3pZ3w%;C z4>Iv9Yq8Rze^9M_e)zeYJdi`|AOqvRk#-<scsH~2`q}@zMV8e+2&OGSlQo24YTgI2 z+5W>Mrl7UN<(H}JExAG?k0sqRg%(!lYi>bxu%9mvl72g|z91{!Dw)2mT=utfgM5~6 zHUb$4px>mo>mLn35ljGzU{~06yK9ZePO)0i8}BC+RK)=YmZPED3Yz!zRRY>98HZ{J znVLG~tihG?qNfM4H_9^ZW^Q+8#c4mXW2b}(kL6{!I%1|;#j9`c69B7WYU0m1*ek|c zhNw`H{doNSL=Gri7s0@{4sO~O{|m{cpig38tNa@%;pW^Y{num97oL{(K*pS<Nfa>_ z19jZFf4C=R_Uv2h@=`!*r`$Kmz`SIRBO(Don3fp|!38(&#Lk{>W7+qp5Sdw#IA4q3 zoTkXHF#!8uBipuetgggd4<F9d3@jW<mb7xmIx)T8dk;Kuk0l9ouVI(4sG}2$j<UC9 zR<yp_CflLFFW%5cdZru~Zq+82@9rDE`cm`va=z}q8&KzCp~-9Ra0Pk4pw6Y%MO97k z2&K*<?lk{Tnz0pGRMOLTYz#cuf!5~qxw7?7F5vYI3aZ}>1O8l~TAj$Ml{dt8@v9`4 zkG=^tp9HN`YG@Hok2FzI4vr~84vNh4QRak@cE#gj`sV|t3&SN+Z;WQB%jM>l{apvS z?Jev9`l$$sA0qyw$7VpaQo`~D2&8Ts0nc;4CcbpRD1iL>z3AmWzbluz2GkuD#m}yJ zjXR<2y_OMm^oMuj#+lr3)Z}F&HT#Q4n`;}t$awBdlVe(4@CxfcAkjBrh>c?9&D;Gw zBK;L;_nRKj<Pdxe!bG7``vq1OHwD7cSRp?k9DTnq4fu)wWM6(cP~V}dZxZj@Um_ME z$8SoBCBw~3x%U_!QZTo+uw@bsv$3u^N~F@rM~Lych31^)0KN5M^q`=Q+x69S-J=i- zjI1wpHC5uli?Gj?Va7d1FpD$yjorvrT8vylqSd0nwwb7gUt<IC6paL+;pGdpUPlRd zw-h-*zJHTc%t`@c&@R)iqh<#X_ZH0K;j2vbBPY=T9+}~h=Q7rfAK!4c$lxBXdH#x( z61}l|EGX~1ae;JcI6=w_uA<-wVV<J0YeW78eyGpj_loE1hKf^4Bs|^Fe%yp$?xM)V z;;!J=3-wO-{J%B1vRB70uoi@kAz6pGhTL5oYRiFAn2M6gn$V6mflKx|5%sR}h)K5p zyg`mmc$)T}V78U8PrS${4%=5=N)qo-!>NpUfDrHX44IHwVxhm67blRkYH*|Mo#C_L z#?jWxJHipRD;~tl$Mkwgi@)Rftt61L5fP3qUB0&@PXYzcIS+}FW6(#k>fi?vIvCQ$ zYlr_EJv5x+Ui2sWz1tlGX@NcybY{5QtqsTuBT+Sf2)i|Id&~4=gy*FRhk5Me8`NGw zr~KJk>ay$;;!r(s5TsKFq9wDN#g^I!#SUI5cBEhYxt-8~flN7ZEj+wdJWVf6eOJ`u zz~&&lfplSdnNb@RYv;}KXk;R$ASb{kS*iqte|5vQpOuMYeftT4F;R-Jo`^JAjrc%3 z-Mz|%ci09OCn}XhOPS7Mj*}ISZ_|=q;vlW?;2aTs9mAm8NgLyfPR^rwnR*O@Pq;ok zg3#~+TrbQ=tSSKGVN_TFWdiWqp3fyi4HWnLB&AWT9FS+=rzckOG`fF8W{`@xVFd*y zVfVY;Y`&@JtR$3^Fpw}rChNV-;nZ$KF}}ko!gPNzc(%}j6M7E}ctO{NIxiTg3sz!u z$(xw}0w*l|EuB2;JW7D=?vDlR2_KxC7H4e@?Yrftb5?$%5SVPNs7^KKH~tXtEL`X8 zaJNXxhf`(8?~4drjC@~Su7)-0+{#akB3kIg!i{=MT+p+zDNWe1N=_PwC?Owntk03+ zv!{V<J0WPm5<~c!q?r~lVMx`wgs87%fWdTB3EQAVHtp|oIB}$e20pDCa--vtQkO?o znFCy$_ae*%LgP&|*pq2L_M)vG$bDhSM^86^a1y|wmGNsD=%;^T;J<>g&B$lt?Y0Iw zDIorHtkq4<LyE?~Hv$qy*nZs^mB(8IA%IH?^w!Yy(1tS(u*do&b8fE<fKumnntqDs zd~GI~>#L)1M*y5+%0VNh3b4AV<p%#5$0k{b$<LhC5%qJ+H>5wB9Oo6^D04%;<<J4z zviV(0M2$rc-kt5G)*_;8SGu*ez%B!>k=$@R8}&m%B?XdpB@>cnDFaW5R{ZcbHVsOd zP1(4_TLWV<w#=#(f4i{Qz*@Ss-WTj0ct%`E@Jd8J3-(HhYkUqd35N}XOINApGxO!K zu`M{gUn8+6WMyU5s>S(>wTjzj%T+aPU@CPrhh>xBj!r~#`0sNoJ6~`yA7tJ6;+_L+ zQJ4n|-r222S9%bbg`Z!N7np3^2V{^B2b$Pw`AUoBYL!Rs#kv9#4|El@U-zvtfxh|6 z{>FzrQ7A33poY*G+4z$juJRbvgff_M1o=fc8Dro2f6^1G2#qIJp4g2mzqZ;-M*Gq5 z30wB2%jHhwTpHEV-DYC>XfoSB8VbUnbz9lUNj<=GtDhYWE3`hw2U;8?il}frQtJR* zl(v=t7c^x=g+l+{>m2GV{y|qZEI^Q+TxGvfBjIDvAhpA;L)lsOfz_7JJ}x~O8J~E9 zFgUtyDjDuX(<Rc7!jw5dF}elmgnf*Nj76HejQLZER&-7N?FO;`3`eSo#cik|XO&Kk zXYS+eh@4K{=4YJ_i*h^Jp=grzxQHQwAxr6QxWZp=(=+?bEM=2>Kkz$2zE2m8!?<i2 zU*&IzA^D;*(QZM~%~~)BcdabIwt%Y!f{Kr808fv)w+XZ-GjB2Vo(7ox)hgOwW#bvn zBP48RuF4s$1XfjuII8Ev-VSpba2wOtmnUD&`maUYI|0JV<752X=H}H~0FEjz>KdP? zBR!*lUpq8aYVTpssRrg+5F3dn=Zwdz&DSS3{+iZIYAh{1teAYM(9=w6bk*fQiZ!|d zH!qo@D;~y6n>81ann!7R_aI6Ljwu6;<jWULCyzl<?|ITS=)5@SwIJOt9Le@uB$drC z(m|toHBuI%_p*s)ADEZjEo550Z~1cv9#|C?;Qpc9A*Rv%9YBS5<1CQ99xQkMhA=ih zniff`f`S#pk)}MAFZ$aJJ3Gqk#IZ8R$}G*j-|%Gsm^*BX!%}nq3}SzbclWrAz-ig# zqSMGI$ysAc`--X2waDUKMiYNqO;*Rs*oYINtv+KQ6*HH*iP(GkUUP99LBo{UO4{Q{ zYbM3Rh3Jd?P1v~=2gKmLFBt<45CiDCe?E_NbEqI60fs^I0n@cS4^(4G8bNurkxPF^ zdd&yB%o}{8SJ~=^k9((H3UUz%VIsaV5=g-S5~L~BfM;%#TUvY>GWoK|6&7S7j{m!1 zGVC6|4r0pGgqsp#0MrQx>JqE|MIed+PmSCZm)E<+Ow^h*2t^1~PdFB0S<q|FSdJ?? zzxHhgtNeL0Du@$u?i|PkUQzsb1zXxlK4;Vf5LHm;6YlF?K2-sX+k=J!IjY~4)CR{e zr{AF5Ufo1o?DJikU;F3RFwz>BY|5F!!k`$+@b0Sc@bP)SDO}g54#sY;dMloS-JU^H zcW)`@j<;!f8qZn1=<*>wWqKnZcY9E(K2Kk~EdxgpWi7|%c9rmlrTuCQBiAl#jIsro zqO)=tag!e%z>yzPG2sKv6Vb+6n^J{1Z~0OWS~UAf-uu`we#AqPb%783%iZ*~cR0T3 z7k9A+qu~<yl!^e^vVx5kTc!1_D|fe6KsIxUB$;3%)+Eg9zJ6@AuJkgdCd)iBgB-x= z`zwZw#eb83H?;R{IA6}hJ0yUYQ-GuxGHwWPH=11$Qwf>A8~)C(MY>)LO<@T)K&nUn z39zpL1<e1IGtg!wu<XDx-&j!N>)i2?Z{!%kwgzf5#Nrc+*j$EK*IxpaJB>toLF0c4 zg+)N2P`Uuj8U^@P<GInYAki2pXyD8$+-QFoWf$qa>bJc3N_*tN$eTG#3(WYg#)Ou* zBzNe0EQ9+1yXY`sEk+?qPb%L>QH^~-q)8%OKYi;Ixn-Bg6oqJvhRsX&PD0MyTt=~I zQ+T^KcRL`LNT%K;RP80EU%^~+Xq49N>)y{{4Mv%Dkgu-a+j#6VyoGMG)6Uz}H?gt( zaMxbYa95xfGQf$(PPSoSO-9oNxYfzU@A`(12OV50DChI<xGYAOvcYZc4ocXLd?~C? z>_IO>4YejGO)p2Nx~=g0rbG1|@OfB)HzKlvDqZcsOc1){7*AB>y%NY!?R(dP^EZnw zAE$YX_WO+h8tt1>8aYKZ2ym{Q%+1)+qI?DLb&edcOJJm*>k2Qp(9_*8i6=JSZ7%|> zpq0sMep7h2t*ZL?e<g2w|0HiZDM0&Upb(i_=jQf8o6RSD1G{Yy%+n^mUJ@{xNphuS z6yv4zk)KV=qj?quN$mw``&puhm^1N7P-m$pI2g+icj&5?@0o=j56u4!u+W51p04pP z3C!58>(-@Kh%o5kQtk`ro<*nSo?ZDn{&cfOBieM_mMiKGB2CJbvQY#MvLiOkbaKgu z1gF3yYi#TiKHXj7kXMZ!pN8YVn{VbHpR;b&{UILl!<Cn3bQIpqkiwbg53QA4&@7bp z$a8C3D7hNq1jkjH?<h0VCU}le4k=tDzZOD!GY(U&5Y8X9nMqZEJ;{bT=fd2ojQZBT zN5qEvv)T*|SWL$9B--N2M!r#9Pwac!cN&#@6?JpAkW+(nMaN@oV<<Xs{Din%+#4g` z7uD4PH+m?c#QE~VdTL8{byPr;Xm$70S>{fIVfr&K+Nh9uj3n{)*y4j#+*q$uZGaHS zQ0zvr3SL_+6VXwZUjj>;#KCwOowFePFD85r4JqWXev%b*2IA){6VOI&(_~dsOl!t8 zjv>EmWc?(23RGu>`S{p2iosx^6Ac&H_V%B*Qyfii@pY?ohDp^38fnY1SU}>j80@<R z6rR!Dl0AfANE=`*)m&NiWQ%>642aOcvpg_FOn!isZ(9B0E;Z99DKyiEb!6T?9#!si zr<eJQod4@KCV#+{hgeAMa7Vf#w#3;5l~QLRSc=E_28E?6y~X@XW=sgTOPa)Ppev9u zsUXl`M}wrnz#3gUzY*_8$STP6JEwoWp0bFgl;T)$C)d$@CrX}{_1maG=8scpl9d@X zq2>;h{8Urx&;5#i&QVUn)~65q?LPa6yhL%K1ZkvWZl*JwdCtdh+tanWa=E$>vaxdF zt&jdqRUSGIO4r2yZOJjN0h;2KgW=u2%Zi?=h8|@``*Y<sG7%c^GmgZCpr?}_$4!Dy z$!Hz?K8HdXCQXD!zFsDds^$B4cIcvlZo=ZxRyRMpqsDQZWUcJr>-<gsnSuWQvPTnW zF%wk#K@*nfT{6?B0N}Fv+Y#gwr;-=kB%fsc!r0pMU;Jv1ke!I@bwQo5*)j>Za}ACs z9&DM~pBekBsmxA}KYoFcXdl!$k1?M*INON}%!t8i2%}UX0YV`GIeFC~Q(?XE=yy-f zcLHAybCYX6_UUT2rsGJYm%JTGRvQ~;&7CNmt?18%qsLo)EpAVM@%GElz^xMOGR)m8 z3*yx_|7v}KH0NH5_5)n`p=5e;_G3K~rUW?&w@BH5r(%iM`e!>fw>x!?GD=CG3w+o! zzuwf^EurX`_kDL|68AOI?cs2DlpG~-NcnIZ$bL+Szh*p0NEnx`dzm(BCp$V6pEzbS z6qv3jd(4C@y7VXB8W=@5YStZl5m&c{46xa5@S?!{p;1Xu$0e4T&m^y4TZTc88j^eu zPjbi60_G%y2IZ*2wmH0Lf3-VO9H7By*h#9?KwQ#$*c9rL#)ysU_fC!v<f}?iNaGdP zAZ#Lx@W^3XW#j*qL!UF>_fVb@LRS>;yR(zKtkNq*BWr$oX(%1)i87`5S@^fO0-wmn zW^;?Ef;3LIB+fP>{gGR5yTfbhuNB%>EB$YJn%q2~l|7k-&g}yKWggN0vVOTunCZUd zENefgaD*SEIx;#x#LmbRfHT`qUqqL*tU@fjEce@GaWLPDieV;ve2*2;PXC5ZEUu^+ z_=i?LZ&WYC=<W*fad>R76~k%2|Eh+;(@^nT%wI5H?BJ^9dVq{KpN@^PwO0K+sTs+5 zbvb-|)9Sd^e&c<bc6d5G+8#P2{0fG3sWnn%JK$%kV^gy~UuHBR9Q!sVb(ZYheXl=! zsH-b-!PbD2O#&@;x0B7Kq0qzYlpiP3Gt=D0V;&cmGcqmI`+4aV<rRjw8@g>5CpLoT z7qE8B#p2xj2@hr?;i2^w@9k@~?|vZGHPF2&^kkK@Dw8LtjTHaHWmKXUMtB<XvA@0U zUB338M=m;(`B|Jv9%{HDPFYjIW4!i~q{iP3$Uv^wh+HPkqW;A(bUWxh&WaM;67t^8 zUVPft$MEozID>MtI?O34DL$kYwx~!c(NYwLI8fE4`DPcyfhp?}H(714TmWUB9}$CT zHgp6uE&Y3Qpr-bJ@z=8^#c3AmiEvuEWncvwV$Y^kMhBVszyF*;VW5@y`o#(CLT^?> z|9V~&w9$phCQ4KPsxV94HGVFXT$*!p-0)tf`f^7#J>hiH1(^tZKYBK#qOnzdp25=W zGLb~SC!&YCIB}`%t8*e&n+rU+4mvBsA=O~7v+^d0%EPBiP4H$O>6cjCw7BrA#bUFP zwh_kq*-WQWPRaGw<(QI-ApG5S;Oe{EArS#AmkYTe4Sad;c7B8&8gdJU%g{1W9;J82 z$ZP_xFYn#Q3Z)$ND6{g>9Qt$ZH`Bv!nP0m9$u3;`Su@s(g;(t0Qe)}Tc86@W2+@(l z8PqINh}%=eUctgP8;W`P_b@HMmi?X^NElH$)q6dfvyRj9-Yi@Iu>Jdjpb?mx3-6}$ zh5vh4F^qvV(Io2>5}~XyYM#a4^)xlq!D1;UqkAbSa^Y#x2wyFnl>kL)8#46IPsNvb z30`g_cfr=8<lwdR?Ps;t|5F4^x<d<jp<i>oM8lBP0_qKku-N)VZgDpl5E627df(=# z=7}4~=A1LG&($emmW6T4P6*QGKdMJ);ksTZ(WWbTGw%POG?ll&p5Z^QzCeH%>pycm z*{#H^ZKoeo0?c=E75aeVtE}+ti?FlC_<}~r`HDBoTGcDD#mnSWp@)<<HP=jEtpqc| zQ!}>pCpU+z+^l}LOAN;X3g^eao?YKk-%YMfNO%|hPSw?mI9bHfcb~WZ0Z1XY%nITh zKGD==He5{z%RP=p;gH#*l`=0)Kz2GNCfcvw$WfKi0;!+8ZLP^%{MXvAIjaLh*(Xk+ zl7HFZ(*%?TEh)3jPx|jj-}$zRj*#-?^vFcTo7@LbHXW>RO>*%`w+>8v$t*kAvgI3t zmbJ$M&6bA-__}~ArO0@?gisQO43?A-qPkP^5UKVubxO?sW~IF}%M;+#UG<VR(djOB z=pUD+gddyF1FT|#@JboIk`5BhKbJiWnwDB7n;g4FvLYn>e-&Ejmelb>Po_x{eA4fp zzJ%!B*Ai+a4E~ZahX?!{hJxNF9|EJvB#z3LH9Zd0$9)7dB_mN-Y1PM!B-3<5xTfs4 ziNWPJkI5(wH=nJfCDyH=`AEY;80XGN<&UL_iBS*+JKG2U-{x5F-PSCVk<aL16`MC{ z#D_Fi_bsgy_TolR6A6eW47kw#*n&A#r&P2PL98+D6zrWev12?k5%H!6zp|U$=DAT1 zrU<tvurKDybnhsx@L_*pO^VM;FE(rkN_yND-+icVCJ>t!l%1`&X3|?Cy&|UL!96qi zFof2=B3p^`#tn?(N5ror+Byoa{c`Ef&o@4aN_X-LjQ4dn7suVN?7?-iDOHqtkZu5} zE&+i2dIalQ<Az|m!yoIbwMdtxzzU5y$n2PuDkBc<9jTGT=|!@fAX3t}<+#_)4$$Z8 z)jAw!mqkj8iWKzZvM)^P1P_Va77go<3RU|Lfwx5V0YD2Du`h)}!87<gfBd9`R()~& z)X}UOJynd$*$;5}3)$E_+-+I0@WR}~aRO|vV<RiHsBFB?qcdA?>eIhg7a|*RgZlBs zYEQ|PHv{F_QZh%zhd=ugfX6N$apA`)vvz=3;y>H47$#oMwx$*!UHsHm_L06M*E&ro zx;MVdU<XU!)!7b=z+zUENMO-I-Y<PWu$(BD;U<ZAEYBVT&%96pAth#o6r_Uib_Icp z!`TCbjWO$E%b{xfMXnrymVf#$4^}a<bn#sqta4`yfmbOtQHd*?b)|3CTkF_glrdJ3 zUwB<qgEdq`Th>oyoEC5*0}@SLxZktxai)D5K5uf{;dUDvofM$qQwf&(&UM=J9ym5V z@Kq7CNu6^tG~ZPiLR^&LJbmWOY(zghnDQJG%*EX3PyD?gApFfJgiW0j7>!4NIx28F z773%6TYlsO{}*U=d~B`1y2-BWoRLaKbjt;);Qf2C0a!SB#vKC!eI+z2wr)whefa3M zIbt0*bq+=~Ww3Tv6*Ac;N~x(+VR0E*){!qmO4A<Ap-7I~z;!H$t$LLND0LTTpsz2t zh9)hgGqMvs!9W(yu{?$OmIz8S*<YKWA>C^W>jn-O>4(_&8QX|hQizLCYRL9D2~%9Y zN=JJO(;2W|o+|HwgXFkM@yE}$P+3%7sUytjPS~ZOQbE#6SUq4s%|l5J%|h`VnWZxE zx79DnPVb263#`X%i$A6L;>r2As?vflW>Y;tb9goq>g)31IjU0;JCCw2&cx127+p&M z(G&h0+*nvAModmu1v=Yz4@_1%^r{t6McT;Yq$peVAE8rlE$YO2Rq+Hnx^U354!1qe zOdaFP#+-}dun2Xu^M}50@w2Suu;1Qu2^6CK=Cq9uPdukiJ{5A7l7a)EK2a#^lOYux z6Z`vN<BO<!t`~fkGhaTyU64~}VVrGNfBgSt8Gh~&0Fo&lbwvMIzvljEq9&;$xu+|S zN2vdyh(<jLkLd!pM&r-0xCEXlm~~>)Ek?-*i;Fp?r~^CdoHjH`{8AMI%lF>6`eVhF zK3J=xhcZYX)oGW!H38WzsPo}<PBno11u(}Muip{D9z&%_%NFE`8UbfpdgLEh-;Jo~ z(-L(Q)2N%*#hzz4T2f|R31ow@S-21=MdwoQn^XE@a!}qJ>f3Ox(F^OhG>45F$?()R z#ar<xk<8(1k7f9qdTvoiI2-W$Y$o!DEgDc}zAsf;qPr<EmPUPz6-&}$biI^*JA=b+ zYO0EG+%3>X70#Rx@8Q7qx-xgGRzzE*qqqEUK>zo9mW&9K2~SQ2U7YtmF33jhn<NTC z4w%#G<{~!bN`f5*^-uv^BSD=sjp-MpTe_wvp{!l)%<z)DM`|^4<~nD1fqad)a5h!W z7H#(s^6jRloTtxC5VEU@h+Q|gKsQ0yduR8o@Qzi2GPvrMrckLPEz_oRO__uK`AkE$ z)hgyMg9PKX`rj`(1yi=j8<6S_b%6!@Uzz74GD$kr>-YMgv2`x@{lyb%$=mFfGi#6| zd<dfq?Xxwo3tRtzrH>g^gE<r6aJ&_1NJcb-<?qReB;~#u*@j2X&Xp9A;CS1(erZWG zd5>?2B(Yv3npKW9$=R^aF;I&dc5%p{H1Mgin)vfk*P4-GKp=BN7S;Qi<yk&=>JGa< zT|a0{-zyNiw%w%XW*Rt9g=mtPt4)W56`BPESf!}#GSyVRssCkD!cq4by+Zg#Bb<DR zSckFPG^jO^#xwr1z{EzdATN)Ck8c5Ma+<6``C6-j=5-~Tt{=UjP5>z`PcI-rq=*;g zY`xPa3FxhAa(q1axrIFBQ@Db!oTvRh5~e-|nX?(^YMZNYGfiEJnw?s6ERkMRpFRCI zFFmj~eDo>O!j<>%Zs!>$ZCZc;ZxMNqdhK1r0!0}42I?qN7p9HW%@{gWU{5IK6lqNO zi3dD4PuBL-de;^?$sL9mI8OK=u^-aJ()-`K%zw%=R=dv&kNz6XYOz!Y{yP(7TT&Fo zeCih#W!@=!jy_hfoI)r|*c+i{+w&X>^)h$)*E&_Q3Pb7>x<S+#JghxwxtR&%J#uWQ zAeij!43Qb%YsmGH9daEAehC|f^-l%_$ISdFPE~n){EO+wqw$;1ma?gPoBP;bmrv@A zK>U>qE8l~6+asd*UQ-j$CJ&?d6CWaAP-fC5w(YiG_p&{D@xOy7MPIGPQhBuVu6p7g z*DqL6E#=ewX2>twv^6^PL->TiZCK&*rR|;iTatbW455I&FnrVgaKihMi3#oO28Gn& z?s|{S{LjU2z;C{P^N+;5<_vZrkbMPISSf3_8bv;TgiZOvD<fJPMSw|hmrjDRQHK+> zQKD0rrM;^#N3>>?j5qHMT_x`i4S{Liuq^_ELRoY0H)NxgDL`w3-d-BrJf%=EA{?hY z$J<6!IM<Ts`L4me=P0O8D2wrk<8B~(1f&4h63UJrX7Mj44EP1MXTR{ffM~J<l}oHD z)oI`5Vl#we0VS%LPF<vbD^TBt$PgJ+juiS6axn3&*BL34Mchi6pb}=29xF}0$&JjA zgn8{uWM#LK<0D|Wom}sTVUJ1u8e~Ktehk0G!;HY1`@5ZPm2m$jBjAfjhMQIVkB-Zp zt(67??-w@%9*c)xhL*-W{suNynOqtM#3;ct6ff}m9l4Jpb2?fDyic&|a3z`~xk-8P zUYwK8687nb(r_%cb${vu(PyOylX*r{=lg(-y;#Kv!((#o6wHQDkxul`C5Mjr)POtZ zCl4fnHLuh5+r<f=g8c$z5z-`mPsReJ^q`a9Ehdi09=D5pX9q~ODuxnTKZxAB$8A#u zb95+*CiqXI0vxbmeq&Z0y_lY$$tw0!EI*HvLjQ@+{=?7jcTLQ6fX`b@bi7q=Xaq^5 zbWEG5EQ$p5W(j2l`ew+%wy1kq`_FN~_`UXFSZ2?C{mr$>(an&SQ*DL!6_{Y76ZGUN zKJD(cI0d98a7$?2oaunTD7&mP!q=e@bfV?_|Mwzv(BvOSG?pRToV$zFMc1%Yf2wuz zDOlqJO9}@Q+dPRT2g`Z7M!c~LYZ#}jqH(cBcN@3h$!_6HK#5BaGSL7oOH#^gYt=<) z&_*SOuB!+L!Ef=NWf`izTM~h_5B0_4{?kKL8L$$c**21}7gx=lEW*dz5~C?2bw~dh z{6W4Zejz8V<c~?}Nr3>X)(dgFi%m`9^S!o(Y6odTDnHcsaCUt3=sB{zK}>YOg=*8Q z9;u<h<LoJjzD98$>{x^QP_7>lY;2-Q%ip{7vC(MaQYu!c=DLz(a-R+DY;A4*2>t)K z`l_fn*KOJ07J>(N3l71Z;7$nc!GZ;McXxMpcemgUjk`7$+_iz*tb5Phdz~j98KeJi zT2;-ODwi@cGPBt!fF$NO>ibYHyUdj2?C6v+ugd}KOVIUHenW$D`BI?;c81dvZVTv& zj@hj@)WxlbTK!=JR(T;rQ-=U1l>S$TRxi6`)P?IU!eQEX?m?7e|6@i041c$MQk86A zQc38Z#(SAOCZFj#-}A%{6-7e?SAV7<B9<N<ewcYFa1}bw!g|JQJj_(wA3j_|+$|7u zB#_)+B0X1s<3G+`wQ%PpgE)=u=pyLbCBC|`V;rK9RsuJ<`Bs+I(e3lA|0}(5k${sY zF)A5$&!UfNDj7^?8+AT?Qlga8j21Os@*ZsFu&8V9<=#y9{&MP~EMVnww^&-9H81UO z;VVVmn1At7W6h#!R}5B=w~T-{ObVq!CO7zLa3P|af;F@RKcZX-?j1naaN{l{)kZhK zXMuffkAtZmC0UYYBt%UK+ooRfDOdbF#2a@yjveA_-M%Ls;}WS+G6=c&nJ|fCRDjjS zH#RXz+6N^!dT0kcgn2)djozSWY*OvkU|;r%c6g;ugz&Z6EVvat?gdLYSJJ&NpMEf* zv}JWZ?SRIrMeVA$m%bB-sU$!*$T#hz%A_UPgq}tnoTC9iE@bG8LoKA87kfx129oC< z1q<YZK(mvw*7t<dipF2}II>xT3dn=dsR9pp9iKDIp>JYOlRndQO1N>unj{)X>T+Mr zg1fIfL8~~(z0TE(_GYoN+25&7Y>=9=He=&L5_;#o*|ftk6^nf7Ad&qxWH8$C8|CoV z^K*T~|A31BSDCUx^j^Yh6@<r1Au7YEsgWI#401CvAC#&@W6Jefj}E%!O=-g!BlFEV zTRJ{5%QK|@;tqFEkyAAj4V}uEV`33Nn&GSP=o!0krt>(D-Oir|P3ifjZdvWD_T=G0 zS`zz`%L$FO(@u}z%Vs<vHEL)mh40jngxx3Q=GCF=$K{}<`F+-Ru(Ly-QBsP{PL4c# zbR8bc;b4$5V_#+Puh2x28lUb?LSG5HMzeu#`1k@gFF3DW^6~CsCO&pbA|)6WWgDT+ z!TlC@sQqmp$qvJLrq_k8vsHqxWxBkrl^HM9WJCg#=p(<Q-pik34>f<7o*9s(AvFvi zVCvT%<QPJ`P49@d!;K!ABmNY6AS&3ZCdeYku=`t>PwL>Uf3rKOo@>N<z0L8dysAo7 zFm~}1p3BEc9{10`vYZ8ytQZH}%bme0Q`MFGh<&Mci%t*i-hJK$(r+GHp(5yGiTpYF zv4|PW4cJxuZ?iZv<*YN{oG5UC5)+RGfcsFA3A0F<GnN7CZ~=A#cRj73R9*M~mSb;@ z1#6uAQ__mt&B$(id+Gd*g4h*q(G`VQcE6IKIUfyR;I|46F^@LfB-uqHX)<Tp{q4rd z`nhiX$7t|RzbE*EZt`EzmZS1dEwdRyFlF*>ZUM{(($nqGAt`*P7uL~m3yHTut1_q1 zDJW&KRw=3|$0SwxJ4V5nNRCMv+8r~^97;Yp8oj-Dfh9}uMgF_>1M%6lnkiMwt-{2o zpg+?-e`3jUjQQ*h(?q}@svfmw^<Mese9)<Sy)7JvCDUM39(sbZYA!8~IC5wwMVhU= z6dELt&tz-r;VJ`h-_vdXVJ?)7?0MB;g4EX)eC*pyY<%W*d;sy+Xi?%(bxizdWf$G% z&jx<a{)vGP&)3(rzuXbbA?JL+?)z^I8<1diVEc$LUnk;DVdc@1s-nICG<B)6(5SsE zR9d^DvwV3EOm<pCJr&^)e-5j*C*GK#Zsec=+p=EB;qzHLN*#*B^cmR=J^<w)gB2-R z+m1$_&kSAafZ`0|1yA>_#K&D|%xl?TF7acdd`<y-*1n5yXcY=N->2;#^$M~QC}vHl z0Hf?!%+mgVZHk;$xSO0aopESqGF1i-OK1lqsI}4MvCZ-hI0qEdX&=TFe>Eu~IB+G~ z%i^*d*rR~s|JwG^v(6tCV_izBZrz_eca;ZieH-4?Txbl3ApQLC@XHj+kVL#U7ePp< zphCQvzx-wg5_e%L!WsE}Z*AOv2())PUy}fwztiDuZ65ix#TD1SKpIzJwy4^K!##}% zFELO!lopOLh8`;pZ}0<IBV~rjDZ5y%N63DvV&v@%xu8n{uY|uQVW6mXZEvPc>5)#_ zqT~^R|3S_=Ip7E!c`gp-D;kp^MFrKNx}&r&h)w5v0$uo*q;lV_R~zBx*_vrr?_uM( zXAnwD`iOzy*`NG6N+_qXLr5|M9EhZ*bD^GoUM=abxi>Sp?G`A}r70GW@Sq)r&faV^ zUYlj+E4W#$G~k|hJrltk3cbC0PItPaIGk<}YG^ELj_d`lN(yyH6$bKt*BRr_krE1@ zrM1v1Sl5LDiBqzD8;$)gXfFJHmNE7FJ|SZ5xUoyTvR0sOt5nNFL@C#4fZ;SZ>-v1L z88?w?#rH1(dM`(dE+@4^%&s#=+O~VfIGk4WUs>(dn?E?IDQ}l1WA6+&tL0GiztkB| z{T>MXK+OX~akhOgv39^*1@F=!WBNTF+JTlOUBK%LW4bFCYMtDZK-0}86lY0l0B<%z ztg~gT$k0D(VI9LyLOa8-zxO@jkUY(uY&}QI5%5M-fh^3cseg%5Fhps)xiBUUn`DKZ z*&y9MvFE(FhKvY)l6y6D7vcoU^woX+{|dtQbZu(?&qTn8z}UWu&_IJ@YgB_Fw1CF) zq)(P{1Knmv`uuX(B|(e2NBWJK=JF=V1bIJ_wfodK4@Q#YWp9N^gweIS#|t0C>?Yl1 z#cm1)>4NV-SSv(;SG}Z6WM8M~_fUr6!i^#$NEYU^W-9H^D?Gk{Md<IfEQJ4&pEzSg zSPeOsfB=AQkl7w&Asp+7l^+yB;~8sBTA39FIZjFsw4g!RK85Bp+x>LjEut22hFu7- zBDoR7GaRhkPr&&lhJ=K8>Az|{WIrV!gU(Lq4~qi6I}~KFRI`oaF#g?W*SQalB0Jfd zR@jᦤ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Μ{$+1u7qRXy1G3{9;f%^~O3dF9J0 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Ν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 SrFa4Z5O})!xvX<aXaWG7vjPSH literal 0 HcmV?d00001 diff --git a/docs/source/figures/reflection_types_and_microfacets.png b/docs/source/figures/reflection_types_and_microfacets.png new file mode 100644 index 0000000000000000000000000000000000000000..37245d96043c0de386de3b489e77468f4d9f2d6c GIT binary patch literal 39864 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%0AgUmrTtiibFM!hdk>1=t|3 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 literal 0 HcmV?d00001 diff --git a/docs/source/figures/surface-definition.png b/docs/source/figures/surface-definition.png new file mode 100644 index 0000000000000000000000000000000000000000..6b1e8f684ee2ca901cceb43fd9de9332c96dfa8a GIT binary patch literal 6184 zcmcIocT|(XvJbsC6_6&PR22xlccn@dkxnQ{=mA1edT#;>MCo0M^o|&+^cq0vO?nNz z3*p6k-?`_!chCFpzVn^^=6o~ro86tAZ)bO+bhK0`NEt~1004!W>T^9TUB|XEVnXcu z%PHPHEFrYgP<alx{rhA$7bapkBrd8(Z~%bp&fkVJHt&D^m+9%E0L9XO-~q#s2tfdV z4z2cFLI3RxdfvxWf32nG5LKFjlcJ!YtBgwKR^>*hFN=r}%(AC^so;|u=&x_lLf0Ut zrt7r}C0PcVck<1|n#assW+0;lWuJ}XrM@E6*^UiB)>a8|rie7bUY8V!0CFpHoFJq3 zOXrxpWFuLJ8_1Av*1L5h2guf(a|07wXaTo9{lif4Ec{o*6bT^G9ECeC*@RVtc|r-* zfMFvK`H5<9B03b5(vohBNf<O(SI0F87I>TURo07sEIBrMvl(}kv^!Qpt6Y{8AD}8T z8cJP$9zQd^0LqCQ8%C{&TdXCyX4g)n<y#a#^t5@lu@^>la3EEGR#C2D7oak$Qytqy z#n-d0|EDT_{Y!CaXhMSEuV;NF?ZSpC@gUqk(5zQ(6A4f1VZ%f}@d=Ynj7d%o&dk3j zhbx+AAU`He?Biy8WzP`2yIk^;SJ{#F2l!0$r;oLP@?3W8=>{`UcvO5=qq#&Wa$W)^ z=<Uv*14>3@i3yLgIn!Wd0jGfjN0)<?G-`>J0CA0}dY+J4+NY`-10V<Uz88~*;MxA~ zn(v_A>*04~QHDKxcc8~dAL?f+=^d?4qiMf@9dk!W#7U<S@!iq1B`Ay5n$R*<aHUlj z>6oDG4+GD5t{x*Y8WQQ|Ww3LVqEm*~dBKciLEb4|ThUoad;3I#5Suq4mF(GlGRSj0 zPlJ{t0bYt_$&mJ0rkL-ZjHNuFfeF09bb37brMPGIfp(M1fj_&CE;XhIl1mLLp^2aB zGRxpuYkes+8)f^dd$Xl|O>v=}_k#iHJ!%%M;Uu~wG1V8ZntUE_?V!JiHS}^i+{g*& zDh+M<R14X?y>wdUC3KlFlnfxb3>0~U>*FbEv?F$Ct4`|5w&6I(+|_|n#-a3d0NbEn zXU2&-Xo@b<E%Xcm15opYQZpw0<D>qU9G64`?G;q<D}{m_-EKjr5>tnxoc50@tkn@v zXwr0jwM0eG*P7+3`^>vjCj>Zoobn5F^)pW@pT|NCe97a2og6RR#60}p06iusp5te{ zvwK6k$LX-+h0ve0Bw<xjv`XKobE6Y#XuhI59@P9`sn6cyMqk}h-$Hx*fCaVF-YJ;6 z`cZ!Z>YQ(5PEN<1xgf%xGD?S6@3CJ=mV9VSdc3Tdo%50Q{R1wk8yV8_fCPA+-2oXO zaWRF+DiF|C(MWbi?t6DHAZ}5U#QJ=@(D1VnU=(h2xcYK1EbZIv;cFXzBl&Bz?mgdY z%FATKN?(pc13Yohnzu`Wf}$HVUOq9T+VS-sw%EAqEItuQS0{#1LR|OcjIu~{qYe$z zBYF)##rch=IDg)KHsK2ju1;M?R@WmwCR|A=1{TkkyaO3=f*w736u7(VIN|F-29uSF zfG-Z-Xh+-$RF57Aq!v-k?O#fG*o}%IsJ^VP#3?>IN@2-`#&Ho7y_+R%L&%S9HE=22 zSBn~zr{&@=*(t_<CFrF|f(N?jED5B>RN8VX1m@g=2my5-6oDZ!Na>p9#Q@9$J3G5l z^M+XC?E0BMKE)c9F`N?kyD27V;WZoXwX9iNwy%=-xlYj=$;`NyTl=~$LE6MJfq_EJ zPY7Ed$Wx(o&QGp`c~Vtgjj3r{4BOn*G3Zz&K*eWf4&Uvg(WyzxDgmggT6`v%VEFQ- z@MyP&=ZmyB1L?#^>(-O^eV9u|x4=j{*IHr5eez=F9&#qRSZdIKC)1fs<1f%7w&A<X zsLXRIhhVPC0AiileiU0cnHFV+KO<u*28gj&LswwRD+r2~Y~D$XuZ`cMpA--l{j4_H zZoga1*M?+q;srHhCJH{#8Wfn);}+QL#ho|#hj(}3f_J6#2;x?l@hkV&I>t-XG)#u1 zvV4gGn~&~fF>BNx`r1<h;|OXV>4Y%uk5LP!cM_{wv?jcH2?k{hh_Ge4T?6dT;GHD8 znvdT%@wec4TiAeQvJ29U+sMT!*DQ&YO${s|gyh0OGSQb*axrxj$Wh|og~NOLCQ}!7 zp`|ljpV6XLMAzT*JWO-*cpGh1X2(jAQ1_KlVXt4MIXRfY*mZ_$(*A*goN~(6mN!D> zNvs`z%x#{&e)r4_p~%2CcY72-T`=~lcnmTW)9BB<MIbTd0GgY(KCl_dB2i0J)9!;@ zj*ZU+Od4(<%YxU+86z_^5DddI-)w+HIMbzuEL{`^QvCT4+~1Qkda0Q!4>Xb1t95rD za=kB1DqpAaddZ$$kC@Z0-js2m5y~58_$7>}T1k`MjxFrv4|yfGR_$a$(k&~{`68^g zKDfLhPjaSUBe#C<<+uI;2Rjr_wQ-y9r9Yh<_2iM9=aeC)Evv7m|7IY|I{HO(oIxG0 ziLy3VFf(Z+=xS3)$er!(-pZ&&@jE*|PJZ6Q&}ZytbeX327WGRvT4hob=v{B5Ibq)E zyMy{iK~sHIyZs9<N<Nt0msVQrFGy2xJwKS@9e-w$*jHUBn6_6}4ca5{{8W?t`so`z z&nr)VPM%9p>NWS*d7fnSrY&k4I&{MT2`()a7JRhdo}Q%O?a5GUx$8!E%MIyNv_I2m zvTw{v$tz2j{{8_a%LAGSkQto5DGl+s-AVcKLz0}dzIrRdyRCQR8_)C5w_;BuhcLkk zAwS{KY3-~v0RvK9OD=)!G1IIlA~hu`FPq~tisYM{*@p7Hw)mb`WVbJIXrvF<YdVEn zH#2s7FC325?z?=?%LwTLP?`{uCHZ#Ug%wa+6GosPKQK>NaOZv+%s}!n<@LZ_7#~+R z$olY_<Ks2{KpLdiiGiwmwMMNe#T&Vi7sDG8rZJIteUdi!089vFD@kgVhxo(Kqj;<c zH8L|JPD_&_Gib@^63yTkC5^X^1g%US7@W_;U*<uii(>_G6H$4^I7%N=;&z}cZ$)7R zv^P!L?8%C@AAQjp|9%R1k))@q+g=SG5cfVQn#p&6l0~+jWZ|!-t{y!Dj?~h7^{Qj3 zBjWHhghZD9WzxXfakHJ*y1Vx<?a}#RqO31pa!^o^I5VXwU(!r|q}Ids-CSHeJXhrW zuV24v1Hg*#Cr_V_?wlsS<gC-!(wC(l{78-JTjAi~K$|9x<Ss8;DJm&p<@QNqU$M4w za&qwT@m)4Y*y=QBGO~+`A~83AO3aiCj~hYT?vg)#{9sR(Eyl8O@Cb|k$;KsT5DX0= zqAE5E-vsr1U@fe!PGD0CFEUdvJnjQ2tEj-2J4yU8mr$%lmR?AWCGb8m71bLgLTe+j zomu)V(C^O~xF?RqKIxI<$05jLX=!G;R)brYmq}}h?f8-J-&=1CX9$am7Mn$H0z5oC zc4r$tU>#Co7X9YJ5!e&Mpyc5pg7ie&-}Z-YpY2YE{M=&aE2ot!Uc>Vh{X3z~?(U;L zuB={U0ab5bpXF4gB|Z_+f5UN{n+}!ZSmm^|wBoSt^AvYmB^FJKCArI4WEMSn;SS`b zBeU*_1Fv+)ri;5OqTM>dSHDB3Dvirxm?iYkdQIJhAtXTPFmsIZ;}V0iguAoI?ETi; z%$q;wLOy4X_qsML%7=V0@u@F4*HKxhAUvYp!9mAc(y&Q=1A_$YrXET2I5Ij~56N6{ zbO{RHc$qz213lg<%u*Le5*PkdGd5<#U@&v<HZts!Ry@pp7s8~ve|>$0V~F(R9UmWO z=i>5uk2h|ntE)?c%{$m321h*b?%ilz_F$XeBg$%kEe`+LZ`B5Y9`^P1#oQOb&C|Uh zi}iw@o}TVb*F<FbUq$_G*eP1p85>oCaIlN<8Y%tS9f=(bQHJdGfs*ImTvRT%;WN7O zZ_A|E0KhID^mkj3ebST@A}J{`HMKwfu?p59&y|($FfjC0T6TQ*h?wRLd1vhEDiA@* z^250SesN$)$twHO%#7uYnFlR)DD%rc0rpRLdDpt=E!cs;j|60N?(Zw$bp{QT>y4&- z^m5k+cub;>*|oKZG+0Z@#l_pYfg?4o!bE!Ww>U3!->$~15EB#YAlWsyL=)x8a=1w! zE5|(K<P6tL6FqDvV%4#*(6+M5oyosUuXC8xd;Pj=x$8X#g8pH96QJ2#TTkx})~&u5 zOA(u=-UZrO<(@{edDy5%&o;n&SiD9mEad72M%hW$IoR3Rv9?g*6imcqWRC>}_0S2v zKd@hwhR<S{#I(?Q!X+l@ppgerQBluTRE{T1V11YMEN#vW#~oC1K=}Hgvq!X<NnfU2 zkXE7fLesnZ<b)IDW+k2yW(#NN*{yk`*c%JKx|w|2RQwv+L5Pspy!%}#uRX*7f31?W z^<yc|UC{1S)sgcw?zB{yu=k1W)%iZEhc%$olgDhL#qUp1dHHA5=dy?GjvOy^Rn^t4 z_7|G((bJchX(iRd7G<&8{6bZK#Ua>L8mob7yTRe(eEAyH(~k3dJ0?jQ!PA7%xj?L8 z&)un|<4NeQEiElA0)YTJP64qy#vS%#Ct=9o{U=YJaC39(AQ61|m6g<ik&(oIg{$}> zqKv|})XhHUki`~1au!K_w3~2QZf@{KR=|V2QB@@+Jn!aAv4yvIHPeIhUW>eo5zY7{ z5T@1jbv=Fk&|Gfco72hN#n!CtZTmZS?=r~wH_fcsX<;YPc+~%Ny4lC;*pG;k88lOC zcc-6D6*w!edruU$Fy}kF<N^|AWDu|loyoVG!2S{_(t<vl<!~lv9ptm2L{zd9H9fu8 z!e#ZODO|JRdh7i}bYvuGBwJyqAu%DrCZH}Dzb4kX`E;UY*f>SlE@6hlZlTuhCjd2K zaQ0>g+mUTsAkGkft^duHz}o{*oyJ(x)RbXxNJ!)9goz*K4w|LQp%rxCXG!Z3+v2*5 zLc8gD@7|Ze!xKmuvQNSbq}*KGV3*8SM(gnv9sH*G@dJ4V<Jk(7EqHn$<c`sqdzvWc z+>q9vN(gi8ckm|k@oz5c{u)b0$0J!(^tE%b!uT(6U;b{~celC$TP_yP)&+Bt0&=il zs8-=CBz{(5htD6we_bO<3aA;`+wUoc{3Ff$A9v0Fpb~^EZmoQ^>(lKrVkRuo`9<(_ zaH7hYo<}xfpv7IsOrmvHj=P=C=~265P&@JOo;CCFsw!KduNBhD@uGaBoZpEnOvAMY zim4#h4v~-geUs4^egkS(KwrH_9i<9TjP3`?ezMk;Kf5pqa4k@fE$W~JGxT2j(`uEF zSj1SdByRAJMsGLi2@oC^Y3pM#AAV|84D@z6Ss<|YYvNX3>)W)SB4`QcC3OvnMeu&h zZ_zN=aetV@ytA0aP0^6UV@&nl`4?xt(>Wl4V(i9Mf|%?f-%2Uwy82K}PbfgTa9kC0 z^Qo}%O2Gs3=a~=M51@F<ix|txm*<9OUOx5nyypMP<Uj9u|GcFVF``5d6DqC<vJk~^ zeN73YpI;B#q-9Z%L8ms$7VwYcjPSb<H+zSAUe()hJM7}g@0ty~T@PzLzF^(Q|6P)f z2feAOI&bL|s;F3s_bJ5lxxnbq>hwcz&t<VEX8zJ8D(#lxm4NT`+~E1tGDDbQe_}ur z1@YPf{lvlGjs}UC+4opzCzDH*Q}mvEu$n}&^GbNs5&%E6N2rQQe{CO@T6y^Fc4$fg zaKb>V4G%DallxHMYie8R{Cp9P_oF}{pUP#GyYTn5TAo2|<ErS3hKKF=o33ZEnF?*A z(KU)5>-*m6g||(=x!mz&l!9j7JC;y5@qN=A<y8fe1Rg2&@1#0Uuz)5}@}@RF1CQ=7 zO5c_|;gx_}<Yip527!)^gc@U$MwQ(y=KT#Oj^FV`wBDS%t<TY;4u^OUxK*wK^b~5x zb47OC*5U0vJ{BAEvC5o1Cy#FPk}etUOYKPFX4}~s_tR(j$K|!sILuAa&6>KU<(hm1 zxsejvL~?xas8|C4U9MOgU7u&YuNN08J%P^M@arCK-yUeLmrM!b;6^py@DDoLvr@}` zic(tGNE@nvhwALHh%|PICM^_fQm(r6buT8S!ezAiGAKkS87VOM7j@08_N8#0Nq~1^ z3K`32QHg6{D}$Wh&TMTMGvAi)g0$%vdQpIBJIIa5Lq5CDcY0^^PYTJm=ijgSD)&Cm zMDCT$YGY2A#p8>1e$3p&aXeSqKHE>e>0ao{upTeZ-LZGHWnXLba%r3!{4SH3(?R&v zEJ1q{tCH)-$YiFEc&+Ow*UiKaKG;iR`dW-YHKNDSTmimesflv5;Mb+MJsYe0hw0s$ z`!Jg;%}GHXOJVQk{ga!8^y_$#c|qdqwWdEaHJkScGIF!INu#yc>{rFhn=z2Hb!SW1 z9-X@+R-mMN!Yvtgu`0FYP*rV*Hc59&cQoF!W1SL8{wfrp0XE`kG?7)%J=t{6te1RY zsrqX?lp=Or9>b^pflie7ymox&^rcqhmp2vO^{{D*z}z=F_gJEHM})XP_AXiK8>Dxd z8^&nqjy(Oo?qouEk^|LwnBTP~?yxhm<Dt1Tk+VsAWpwr1tx-bvb!_fWkqVQ-S3t(o z`!R%Peim(40XDk+gK(`1uspFCDnsiz0!9u48`P7zh={svBk+c|ChgN&GM2@Sx~g(J zHtI^eyoZreZX?}M{na&}2_g)WN%gZz+4qqZC<i7GDZ8VsFj2sq#)6kcMuYEQr^?p2 z1}b9Tse4TlSsq!W@SKurWg{!@z<gU}qplgfz|g7L6isp4YZZXWIsVM=V=WN3&4@WF z{ItP~g!q!THuFa%<bj8B@VR-OtibpoK>J-6$b#S}^Y)-8GA3pp@Q(!vPPck;^I^X| zk*-M7Z*zQeeDsnT&`mlqiFg|8isNSv<_oH*Y-)gW%LW_c_Ks^KW7;g!i8A0S+*mF1 z{GoG0VcG23540zZRTVC^-w^&474ul_+|LJdC%{v{c8V-K7n;T=R<2CX*O2znza0>I zLI#;zrO5>Ic^zMA^RwQQ1-z)hx;M>1ZvK4JU6L@|$kx$a%xSg;W3G^?q@bJ^)RxI< z?~oz<D@)?%<oIT8jzSC10-Ye@l*T>_G%_{PJ87|Nb*!?e<I@k%9BXU8N%5E!CZu91 zI@{Z#7DUjRsbdy!MIpXg(^cw#;9$HI0VrwJO77^EyM59mhZ^t&67k94;Cyld2XrDN zDB}8T@&-cgk5R7E*coF_&NcHUAi5)D&%NFTD9;j!dYJNii~7#XQ9fn4$LH7n^)uiL z=0%CCr=e8w|6Vlx4<@qz@4QI}b5b1*8YnyKI5msq6Z7thr6d^nY8I#MwxU>gJnWM) zjP~^5;KP=R`@kD+jei5-7hgxj7I51q`=^J0&qf`Hl+9RM*81Nn$N%nxtAK=1sjV^s z{lC?84)+&^?lzY0U`cB?FqQxWfdWFjKp|cM5q$w+NfDqVQ1B^GKoSUiU@w&MKLAcJ z8#~Cm{{;{hmlP5GCjh5i)(Q)t{o8}P9Rwn2>F8o>39y92Vb*rou54@r@-SG1d;d~! zv4y$AoWSmGcGdtr6~&r|YKvGo{lDaHFfTZ-3)qbpj#Y612nY*)I+OZa`Uj}J(0X33 H2nzl$J{8Sq literal 0 HcmV?d00001 diff --git a/docs/source/user_guide/user_guide_actors.rst b/docs/source/user_guide/user_guide_actors.rst index 34b56af94..76da822db 100644 --- a/docs/source/user_guide/user_guide_actors.rst +++ b/docs/source/user_guide/user_guide_actors.rst @@ -2,3 +2,5 @@ Actors and Filters =================== The "Actors" are scorers that can store information during simulation, such as dose map or phase-space (like a "tally" in MCNPX). They can also be used to modify the behavior of a simulation, such as the `KillActor` that allows stopping tracking particles when they reach some defined regions. This is why they are called "actors" rather than "scorers". + +The list of actors is here: :ref:`actors-label`. \ No newline at end of file diff --git a/docs/source/user_guide/user_guide_how_to_convert_example_1.rst b/docs/source/user_guide/user_guide_how_to_convert_example_1.rst new file mode 100644 index 000000000..f67ecdb76 --- /dev/null +++ b/docs/source/user_guide/user_guide_how_to_convert_example_1.rst @@ -0,0 +1,331 @@ +Convert From Gate 9 to Gate 10 - Example : +========================================================================= +This section walks you through Gate 9 and Gate 10 versions of a simulation file used to create optical transport dataset for training OptiGAN. + +Defining World Geometry - +---------------------- + +Gate 9 - + +.. code-block:: + + /gate/world/geometry/setXLength 10. cm + /gate/world/geometry/setYLength 10. cm + /gate/world/geometry/setZLength 15. cm + /gate/world/setMaterial Air + +Gate 10 - + +.. code-block:: python + + sim.world.size = [10 * cm, 10 * cm, 15 * cm] + +The material of the world volume is set to 'Air' by default. + +Gate 9 - + +.. code-block:: + + /gate/world/daughters/name OpticalSystem + /gate/world/daughters/insert box + /gate/OpticalSystem/geometry/setXLength 10. cm + /gate/OpticalSystem/geometry/setYLength 10. cm + /gate/OpticalSystem/geometry/setZLength 14.0 cm + /gate/OpticalSystem/placement/setTranslation 0 0 0.0 cm + /gate/OpticalSystem/setMaterial Air + +Gate 10 - + +.. code-block:: python + + optical_system = sim.add_volume("Box", "optical_system") + optical_system.size = [10 * cm, 10 * cm, 14 * cm] + optical_system.material = "G4_AIR" + optical_system.translation = [0 * cm, 0 * cm, 0 * cm] + +Gate 9 - + +.. code-block:: + + /gate/OpticalSystem/daughters/name crystal + /gate/OpticalSystem/daughters/insert box + /gate/crystal/geometry/setXLength 3.0 mm + /gate/crystal/geometry/setYLength 3.0 mm + /gate/crystal/geometry/setZLength 3.0 mm + /gate/crystal/placement/setTranslation 0 0 10 mm + /gate/crystal/setMaterial BGO + +Gate 10 - + +.. code-block:: python + + crystal = sim.add_volume("Box", "crystal") + crystal.mother = optical_system.name + crystal.size = [3 * mm, 3 * mm, 20 * mm] + crystal.translation = [0 * mm, 0 * mm, 10 * mm] + crystal.material = "BGO" + +Gate 9 - + +.. code-block:: + + /gate/OpticalSystem/daughters/name grease + /gate/OpticalSystem/daughters/insert box + /gate/grease/geometry/setXLength 3.0 mm + /gate/grease/geometry/setYLength 3.0 mm + /gate/grease/geometry/setZLength 0.015 mm + /gate/grease/setMaterial Epoxy + /gate/grease/placement/setTranslation 0 0 20.0075 mm + +Gate 10 - + +.. code-block:: python + + grease = sim.add_volume("Box", "grease") + grease.mother = optical_system.name + grease.size = [3 * mm, 3 * mm, 0.015 * mm] + grease.material = "Epoxy" + grease.translation = [0 * mm, 0 * mm, 20.0075 * mm] + +Gate 9 - + +.. code-block:: + + /gate/OpticalSystem/daughters/name pixel + /gate/OpticalSystem/daughters/insert box + /gate/pixel/geometry/setXLength 3 mm + /gate/pixel/geometry/setYLength 3 mm + /gate/pixel/geometry/setZLength 0.1 mm + /gate/pixel/setMaterial SiO2 + /gate/pixel/placement/setTranslation 0 0 20.065 mm + +Gate 10 - + +.. code-block:: python + + pixel = sim.add_volume("Box", "pixel") + pixel.mother = optical_system.name + pixel.size = [3 * mm, 3 * mm, 0.1 * mm] + pixel.material = "SiO2" + pixel.translation = [0 * mm, 0 * mm, 20.065 * mm] + +Defining Physics - +---------------- + +Gate 9 - + +.. code-block:: + + /gate/physics/addPhysicsList emstandard_opt4 + /gate/physics/addPhysicsList optical + + /gate/physics/addProcess Scintillation + /gate/physics/addProcess Cerenkov e+ + /gate/physics/addProcess Cerenkov e- + + /gate/physics/Electron/SetCutInRegion world 10 mm + /gate/physics/Positron/SetCutInRegion world 10 um + /gate/physics/Electron/SetCutInRegion crystal 10 um + /gate/physics/Positron/SetCutInRegion crystal 10 um + + /gate/physics/processList Enabled + /gate/physics/processList Initialized + +Gate 10 - + +.. code-block:: python + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option4" + + # This also includes Scintillation and Cerenkov processes. + sim.physics_manager.special_physics_constructors.G4OpticalPhysics = True + + sim.physics_manager.set_production_cut("world", "electron", 10 * mm) + sim.physics_manager.set_production_cut("world", "positron", 10 * um) + sim.physics_manager.set_production_cut("crystal", "electron", 10 * um) + sim.physics_manager.set_production_cut("crystal", "positron", 10 * um) + + # In Gate 10, enery range limits should be set like this for scintillation. + # Reason for this is unknown. + sim.physics_manager.energy_range_min = 10 * eV + sim.physics_manager.energy_range_max = 1 * MeV + + +Defining Optical Surfaces - +--------------------------- + +Gate 9 - + +.. code-block:: + + /gate/crystal/surfaces/name surface1 + /gate/crystal/surfaces/insert OpticalSystem + /gate/crystal/surfaces/surface1/setSurface Customized3_LUT + +Gate 10 - + +.. code-block:: python + + opt_surf_optical_system_to_crystal = sim.physics_manager.add_optical_surface( + volume_from="optical_system", + volume_to="crystal", + g4_surface_name="Customized3_LUT", + ) + +Gate 9 - + +.. code-block:: + + /gate/OpticalSystem/surfaces/name surface2 + /gate/OpticalSystem/surfaces/insert crystal + /gate/OpticalSystem/surfaces/surface2/setSurface Customized3_LUT + +Gate 10 - + +.. code-block:: python + + opt_surf_crystal_to_optical_system = sim.physics_manager.add_optical_surface( + "crystal", "optical_system", "Customized3_LUT" + ) + +Gate 9 - + +.. code-block:: + + /gate/crystal/surfaces/name surface5 + /gate/crystal/surfaces/insert grease + /gate/crystal/surfaces/surface5/setSurface Customized2_LUT + +Gate 10 - + +.. code-block:: python + + opt_surf_grease_to_crystal = sim.physics_manager.add_optical_surface("grease", "crystal", "Customized2_LUT") + +Gate 9 - + +.. code-block:: + + /gate/grease/surfaces/name surface6 + /gate/grease/surfaces/insert crystal + /gate/grease/surfaces/surface6/setSurface Customized2_LUT + +Gate 10 - + +.. code-block:: python + + opt_surf_crystal_to_grease = sim.physics_manager.add_optical_surface("crystal", "grease", "Customized2_LUT") + +Gate 9 - + +.. code-block:: + + /gate/grease/surfaces/name Detection1 + /gate/grease/surfaces/insert pixel + /gate/grease/surfaces/Detection1/setSurface Customized4_LUT + +Gate 10 - + +.. code-block:: python + + opt_surface_pixel_to_grease = sim.physics_manager.add_optical_surface("pixel", "grease", "Customized4_LUT") + +Gate 9 - + +.. code-block:: + + /gate/pixel/surfaces/name Detection2 + /gate/pixel/surfaces/insert grease + /gate/pixel/surfaces/Detection2/setSurface Customized4_LUT + +Gate 10 - + +.. code-block:: python + + opt_surf_grease_to_pixel = sim.physics_manager.add_optical_surface("grease", "pixel", "Customized4_LUT") + +Defining Electron Source - +--------------------------- + +Gate 9 - + +.. code-block:: + + /gate/source/addSource Mysource + /gate/source/Mysource/gps/particle e- + /gate/source/Mysource/gps/energytype Mono + /gate/source/Mysource/gps/type Volume + /gate/source/Mysource/gps/shape Sphere + /gate/source/Mysource/gps/radius 0. mm + /gate/source/Mysource/setActivity 1000 becquerel + /gate/source/Mysource/gps/monoenergy 420 keV + /gate/source/Mysource/gps/centre 0 0 19 mm + + /gate/source/Mysource/gps/ang/type iso + /gate/source/Mysource/gps/ang/mintheta 163. deg + /gate/source/Mysource/gps/ang/maxtheta 165. deg + +Gate 10 - + +.. code-block:: python + + source = sim.add_source("GenericSource", "my_source") + source.particle = "e-" + source.energy.type = "mono" + source.energy.mono = 420 * keV + source.position.type = "sphere" + source.position.radius = 0 * mm + source.activity = 1000 * Bq + source.direction.type = "iso" + source.direction.theta = [163 * deg, 165 * deg] + source.direction.phi = [100 * deg, 110 * deg] + source.position.translation = [0 * mm, 0 * mm, 19 * mm] + +Defining Actor - +--------------------------- + +Gate 9 - + +.. code-block:: + + /gate/actor/addActor PhaseSpaceActor MyActor + /gate/actor/MyActor/attachTo pixel + + /gate/actor/MyActor/enableTime true + /gate/actor/MyActor/enableLocalTime true + /gate/actor/MyActor/enableTimeFromBeginOfEvent true + /gate/actor/MyActor/enableTProd true + /gate/actor/MyActor/enableTOut true + /gate/actor/MyActor/enableTrackLength true + /gate/actor/MyActor/enableEmissionPoint true + /gate/actor/MyActor/enableElectronicDEDX true + /gate/actor/MyActor/save ./output/{NameOutputSimu}/MyActorPixel_In.root + +Gate 10 - + +.. code-block:: python + + phase = sim.add_actor("PhaseSpaceActor", "Phase") + phase.attached_to = pixel.name + phase.output_filename = "test075_optigan_create_dataset_first_phase_space_with_track_volume.root" + phase.attributes = [ + "EventID", + "ParticleName", + "Position", + "TrackID", + "ParentID", + "Direction", + "KineticEnergy", + "PreKineticEnergy", + "PostKineticEnergy", + "TotalEnergyDeposit", + "LocalTime", + "GlobalTime", + "TimeFromBeginOfEvent", + "StepLength", + "TrackCreatorProcess", + "TrackLength", + "TrackVolumeName", + "PDGCode", + ] + diff --git a/docs/source/user_guide/user_guide_installation.rst b/docs/source/user_guide/user_guide_installation.rst index bcff775bc..c32fd8b60 100644 --- a/docs/source/user_guide/user_guide_installation.rst +++ b/docs/source/user_guide/user_guide_installation.rst @@ -66,4 +66,4 @@ There are additional command line tools available; see the `addons section <user - `Exercises <https://gitlab.in2p3.fr/davidsarrut/gate_exercices_2>`_ (initially developed for DQPRM, French medical physics diploma) -- `Exercises <https://drive.google.com/drive/folders/1bcIS5OPLOBzhLo0NvrLJL5IxVQidNYCF>`_ (initially developed for Opengate teaching) +- `Training <https://drive.google.com/drive/folders/1bcIS5OPLOBzhLo0NvrLJL5IxVQidNYCF>`_ (initially developed for Opengate teaching) diff --git a/docs/source/user_guide/user_guide_physics.rst b/docs/source/user_guide/user_guide_physics.rst index bb5afd4a8..cc09736dc 100644 --- a/docs/source/user_guide/user_guide_physics.rst +++ b/docs/source/user_guide/user_guide_physics.rst @@ -119,6 +119,64 @@ Electromagnetic parameters are managed by a specific Geant4 object called G4EmPa ... +OptiGAN +======== + +Refer to this `testcase <https://github.com/OpenGATE/opengate/blob/6cd98d3f7d76144889b1615e28a00873ebc28f81/opengate/tests/src/test081_simulation_optigan_with_random_seed.py>`_ for a simulation example. + +In the default optical simulations of Gate v10, each optical photon generated is treated as a separate track, which can be quite resource-intensive. For instance, approximately one second is required to simulate the spatial distribution of optical photons detected from a single 511 keV gamma ray interaction in a 20 mm thick layer of bismuth germanate (BGO), which has a light yield of about 8500 photons per MeV. Recent advancements in Monte Carlo simulations using deep learning, particularly with Generative Adversarial Networks (GANs), have shown significant potential in reducing simulation times. We have adopted a specific type of GAN known as Wasserstein GAN to enhance the efficiency of generating optical photons in scintillation crystals, which we have named OptiGAN. For more detailed information, you can refer to this `research paper <https://iopscience.iop.org/article/10.1088/2632-2153/acc782>`_. + +The OptiGAN model trained with 3 x 3 x 3 mm\ :sup:`3` BGO crystal is already included with Gate 10. More models will be added in the future. + +Users can utilize OptiGAN in two ways: they can integrate it into the simulation file, or they can use it after running the simulation. + +Method 1 - Running OptiGAN with Simulation +------------------------------------------ + +.. code-block:: python + + optigan = OptiGAN(input_phsp_actor=phsp_actor) + +Method 2 - Running OptiGAN After Simulation +------------------------------------------- + +.. code-block:: python + + optigan = OptiGAN(root_file_path=hc.get_output_path()) + +Method 1 can be used when a user wants to run OptiGAN within the same simulation file. The ``input_phsp_actor`` parameter must be set to the phase space actor attached to the crystal in the simulation. The output will then be saved in the folder specified by ``sim.output_dir``. + +Method 2 can be used when a user wants to use OptiGAN in a file outside their main simulation file. In this case, the ``root_file_path`` must be set to the path of the root file obtained from the simulation. + +Workflow of OptiGAN Module in Gate 10 +------------------------------------- + +OptiGAN requires two pieces of input information: the position of gamma interaction in the crystal and the number of optical photons emitted. This information is automatically parsed from the root files when users utilize OptiGAN. + +- **Position of gamma interaction:** This refers to the coordinate information of gamma interaction with the scintillation crystal. + +- **Number of optical photons emitted:** This indicates the total number of optical photons emitted per gamma event. + +Obtaining the number of optical photons emitted without modifying Geant4 is challenging. As a workaround for now, we ask users to use a kill actor and add a filter in the test case to eliminate optical photons. + +.. code-block:: python + + # filter : remove opticalphoton + fe = sim.add_filter("ParticleFilter", "fe") + fe.particle = "opticalphoton" + fe.policy = "reject" + + # add a kill actor to the crystal + ka = sim.add_actor("KillActor", "kill_actor2") + ka.attached_to = crystal + ka.filters.append(fe) + +NOTE: Using a kill actor still creates optical photons, but it terminates the track after the first step. This approach provides us with the required information (number of optical photons emitted) as an input for OptiGAN, while also saving tracking time by terminating the photons early. + +.. image:: ../figures/kill_actor.png + +NOTE: The analysis of computation time gained by using OptiGAN in Gate 10 is still in works by the team at UC Davis. + Managing Cuts and Limits ------------------------ diff --git a/docs/source/user_guide/user_guide_reference_actors.rst b/docs/source/user_guide/user_guide_reference_actors.rst index d33606f59..3741e5e06 100644 --- a/docs/source/user_guide/user_guide_reference_actors.rst +++ b/docs/source/user_guide/user_guide_reference_actors.rst @@ -1,3 +1,6 @@ + +.. _actors-label: + Actors ====== @@ -20,7 +23,7 @@ The SimulationStatisticsActor actor is a very basic tool that allows counting th print(stats) print(stats.counts) - In addition, if the flag `track_types_flag` is enabled, the actor will save a dictionary structure with all types of particles that have been created during the simulation, which is available as `stats.counts.track_types`. The start and end time of the whole simulation are available and speeds are estimated (primary per sec, track per sec, and step per sec). +In addition, if the flag `track_types_flag` is enabled, the actor will save a dictionary structure with all types of particles that have been created during the simulation, which is available as `stats.counts.track_types`. The start and end time of the whole simulation are available and speeds are estimated (primary per sec, track per sec, and step per sec). Reference @@ -112,7 +115,7 @@ The `DigitizerHitsCollectionActor` collects hits occurring in a given volume (or hc.attributes = ['TotalEnergyDeposit', 'KineticEnergy', 'PostPosition', 'CreatorProcess', 'GlobalTime', 'VolumeName', 'RunID', 'ThreadID', 'TrackID'] -The names of the attributes align with Geant4 terminology. The list of available attributes is defined in the file `GateDigiAttributeList.cpp` and can be printed with: +In this example, the actor is attached (attached_to option) to several volumes (crystal1 and crystal2 ) but most of the time, one single volume is sufficient. This volume is important: every time an interaction (a step) is occurring in this volume, a hit will be created. The list of attributes is defined with the given array of attribute names. The names of the attributes are as close as possible to the Geant4 terminology. They can be of a few types: 3 (ThreeVector), D (double), S (string), I (int), U (unique volume ID, see DigitizerAdderActor section). The list of available attributes is defined in the file `GateDigiAttributeList.cpp` and can be printed with: .. code-block:: python @@ -120,19 +123,31 @@ The names of the attributes align with Geant4 terminology. The list of available am = gate_core.GateDigiAttributeManager.GetInstance() print(am.GetAvailableDigiAttributeNames()) +Warning: KineticEnergy, Position and Direction are available for PreStep and for PostStep, and there is a โdefaultโ version corresponding to the legacy Gate (9.X). + ++------------------+-------------------+---------------------+ +| Pre version | Post version | default version | ++==================+===================+=====================+ +| PreKineticEnergy | PostKineticEnergy | KineticEnergy (Pre) | ++------------------+-------------------+---------------------+ +| PrePosition | PostPosition | Position (Post) | ++------------------+-------------------+---------------------+ +| PreDirection | PostDirection | Direction (Post) | ++------------------+-------------------+---------------------+ + Attributes correspondence with Gate 9.X for Hits and Singles: -+------------------------+-------------------------+ -| Gate 9.X | Gate 10 | -+========================+=========================+ -| edep or energy | TotalEnergyDeposit | -+------------------------+-------------------------+ -| posX/Y/Z of globalPosX/Y/Z | PostPosition_X/Y/Z | -+------------------------+-------------------------+ -| time | GlobalTime | -+------------------------+-------------------------+ ++----------------------------+-------------------------+ +| Gate 9.X | Gate 10 | ++============================+=========================+ +| edep or energy | TotalEnergyDeposit | ++----------------------------+-------------------------+ +| posX/Y/Z of globalPosX/Y/Z | PostPosition_X/Y/Z | ++----------------------------+-------------------------+ +| time | GlobalTime | ++----------------------------+-------------------------+ -The list of hits can be written to a ROOT file at the end of the simulation. Like in Gate, hits with zero energy are ignored. If zero-energy hits are needed, use a PhaseSpaceActor. +At the end of the simulation, the list of hits can be written as a root file and/or used by subsequent digitizer modules (see next sections). The Root output is optional, if the output name is None nothing will be written. Note that, like in Gate, every hit with zero deposited energy is ignored. If you need them, you should probably use a PhaseSpaceActor. Several tests using DigitizerHitsCollectionActor are proposed: test025, test028, test035, etc. The actors used to convert some `hits` to one `digi` are `DigitizerHitsAdderActor` and `DigitizerReadoutActor` (see next sections). @@ -307,8 +322,7 @@ The Angular Response Function (ARF) is a method designed to accelerate SPECT sim 3. Apply the trained ARF to enhance simulation efficiency. .. warning:: - -Ensure that torch and garf (Gate ARF) packages are installed prior to use. Install them with: `pip install torch gaga_phsp garf` + Ensure that torch and garf (Gate ARF) packages are installed prior to use. Install them with: `pip install torch gaga_phsp garf` Step 1: Creating the Training Dataset ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -332,7 +346,7 @@ After generating the dataset, train the ARF model using garf_train, which trains .. code-block:: bash - garf_train train_arf_options.json arf_training_dataset.root arf.pth + garf_train train_arf_v058.json arf_training_dataset.root arf.pth Step 3: Using the Trained ARF Model in Simulation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/user_guide/user_guide_reference_simulation.rst b/docs/source/user_guide/user_guide_reference_simulation.rst index cba03e28c..c4f9456c7 100644 --- a/docs/source/user_guide/user_guide_reference_simulation.rst +++ b/docs/source/user_guide/user_guide_reference_simulation.rst @@ -72,7 +72,7 @@ If you want to personalize the ``pyvista`` GUI, you can set ``sim.visu_type = "v GDML ^^^^ -.. image:: figures/visu_gdml.png +.. image:: ../figures/visu_gdml.png With GDML visualization, you can only view the geometry, not the paths of the particles. It is enabled with ``sim.visu_type = "gdml"``. GDML visualization needs to be enabled in Geant4 with ``GEANT4_USE_GDML=ON`` during the compilation, but you need to have ``xerces-c`` available on your computer (install it with yum, brew, or apt-get, ...). diff --git a/docs/source/user_guide/user_guide_sources.rst b/docs/source/user_guide/user_guide_sources.rst index 87325deda..d7c569308 100644 --- a/docs/source/user_guide/user_guide_sources.rst +++ b/docs/source/user_guide/user_guide_sources.rst @@ -166,6 +166,91 @@ available : F18, Ga68, Zr89, Na22, C11, N13, O15, Rb82. See http://www.lnhb.fr/nuclear-data/module-lara. One example is available in ``test031``. +Energy spectrums +^^^^^^^^^^^^^^^^ + +**Discrete for gamma spectrum** + +One can configure a generic source to produce particles with energies depending on weights. +To do so, one must provide two lists of the same size: one for energies, one for weights. +Each energy is associated to the corresponding weight. +Probabilities are derived from weights simply by normalizing the weights list. + +Several spectrums are provided through the `get_rad_gamma_spectrum` function: + +.. code:: python + + spectrum = gate.sources.generic.get_rad_gamma_spectrum("Lu177") + + +The source can be configured like this: + + +.. code:: python + + source = sim.add_source("GenericSource", "source") + source.particle = "gamma" + source.energy.type = "spectrum_discrete" + source.energy.spectrum_energies = spectrum.energies + source.energy.spectrum_weights = spectrum.weights + + +For example, using this: + +.. code:: python + + source.energy.spectrum_energies = [0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8] + source.energy.spectrum_weights = [0.2, 0.4, 0.6, 0.8, 1.0, 0.8, 0.6, 0.4, 0.2] + +The produced particles will follow this pattern: + +.. image:: ../figures/generic_source_spectrum_discrete.png + +**Histogram for beta spectrum** + +One can configure a generic source to produce particles with energies according to a given histogram. +Histograms are defined in the same way as `numpy`, using bin edges and histogram values. + +Several spectrums are provided through the `get_rad_beta_spectrum` function. +This data comes from `[doseinfo-radar] <https://www.doseinfo-radar.com/RADARDecay.html>`_ (`[direct link to the excel file] <https://www.doseinfo-radar.com/BetaSpec.zip>`_). + +.. code:: python + + spectrum = gate.sources.generic.get_rad_beta_spectrum("Lu177") + +The source can be configured like this: + +.. code:: python + + source = sim.add_source("GenericSource", "source") + source.particle = "e-" + source.energy.type = "spectrum_histogram" + source.energy.spectrum_energy_bin_edges = spectrum.energy_bin_edges + source.energy.spectrum_weights = spectrum.weights + +For example, using this (which is what you get from `get_rad_beta_spectrum("Lu177")`): + +.. code:: python + + source.energy.spectrum_energies = [ + 0.0, 0.0249, 0.0497, 0.0746, 0.0994, 0.1243, 0.1491, + 0.174, 0.1988, 0.2237, 0.2485, 0.2734, 0.2983, 0.3231, + 0.348, 0.3728, 0.3977, 0.4225, 0.4474, 0.4722, 0.497, + ] + source.energy.spectrum_weights = [ + 0.135, 0.122, 0.109, 0.0968, 0.0851, 0.0745, 0.0657, + 0.0588, 0.0522, 0.0456, 0.0389, 0.0324, 0.0261, 0.0203, + 0.015, 0.0105, 0.00664, 0.00346, 0.00148, 0.000297, + ] + +The produced particles will follow this pattern: + +.. image:: ../figures/generic_source_spectrum_histogram.png + +**Interpolation** + +TODO + Confined source ^^^^^^^^^^^^^^^ @@ -376,15 +461,80 @@ See all test019 and test060 as examples. GAN sources (Generative Adversarial Network) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -(documentation TODO) +A Phase-Space (phsp) source typically uses a large file containing particle properties (e.g., energy, position, direction, time) to generate primary events in a simulation. This traditional phsp source can be replaced by a neural network-based particle generator that replicates similar distribution probabilities in a more compact form. GAN sources utilize Generative Adversarial Networks (GANs) trained to reproduce these particle properties based on an initial phsp. This approach, proposed in `[Sarrut et al, PMB, 2019] <https://doi.org/10.1088/1361-6560/ab3fc1/>`_, can be applied across various applications: + +- Linac phsp: `test034 <https://github.com/OpenGATE/opengate/tree/master/opengate/tests/src>`_ `[Sarrut et al, PMB, 2019] <https://doi.org/10.1088/1361-6560/ab3fc1>`_ +- SPECT: `test038 <https://github.com/OpenGATE/opengate/tree/master/opengate/tests/src>`_ and `test047 <https://github.com/OpenGATE/opengate/tree/master/opengate/tests/src>`_ `[Sarrut et al, PMB, 2021] <https://doi.org/10.1088/1361-6560/abde9a>`_ and `[Saporta et al, PMB, 2022] <https://doi.org/10.1088/1361-6560/aca068>`_ +- PET: `test040 <https://github.com/OpenGATE/opengate/tree/master/opengate/tests/src>`_ `[Sarrut et al, PMB, 2023] <https://doi.org/10.1088/1361-6560/acdfb1>`_ + +**Installation Requirements** + +To use GAN sources, first install the required `torch` and `gaga_phsp` libraries with: + +.. code:: bash + + pip install torch gaga_phsp + +The `gaga_phsp` library provides tools for training and using GAN models: https://github.com/OpenGATE/gaga-phsp. + +**Process Overview** + +The process to use a GAN source involves three main steps: + +1. Generate the training dataset. +2. Train the GAN model. +3. Use the GAN model as a source in GATE. + +For Linac applications, a conventional Linac phsp can serve as the training dataset. In SPECT or PET applications, a conditional GAN is used to generate particles exiting the patient, conditioned on the activity distribution within the patient. In this case, the training dataset must include not only the particle properties at the patient exit (e.g., position and direction in a spheroid or cylinder around the patient) but also the initial emission point inside the patient (using `EventPosition` and `EventDirection`). An example can be found in `test038_gan_phsp_spect_training_dataset_mt.py`. + +**Training the GAN** + +Once the training data is generated, train the GAN model outside of GATE using `gaga_phsp`. Example command: + +.. code:: bash + + gaga_train my_phsp.root gaga_train_options.json -pi epoch 50 -o gan_source.pth + +A sample JSON file for GAN options, `train_gaga_v124.json`, can be found in the `tests/data/test038` folder. Training can be resource-intensive, typically requiring a GPU and several hours. The resulting generator model is saved as a compact `.pth` file, containing the neural network weights (generally a few tens of MB). + +**Using the GAN Source in GATE** + +Once trained, the generator can be used as a source in GATE using the ``GANSource`` type, as in the example below: + +.. code:: python + + gsource = sim.add_source("GANSource", "my_gan_source") + gsource.particle = "gamma" + gsource.activity = 1 * MBq + gsource.pth_filename = "gan_source.pth" + + gsource.position_keys = ["PrePosition_X", "PrePosition_Y", "PrePosition_Z"] + gsource.direction_keys = ["PreDirection_X", "PreDirection_Y", "PreDirection_Z"] + gsource.energy_key = "KineticEnergy" + gsource.time_key = None + gsource.weight_key = None + + gsource.energy_min_threshold = 10 * keV + gsource.backward_distance = 5 * cm + # Use ZeroEnergy policy to avoid altering event counts + gsource.skip_policy = "ZeroEnergy" + + gsource.batch_size = 5e4 + gsource.verbose_generator = True + gsource.gpu_mode = "auto" + + cond_gen = gate.sources.gansources.VoxelizedSourceConditionGenerator("myactivity.mhd") + cond_gen.compute_directions = True + gen = gate.sources.gansources.GANSourceConditionalGenerator(gsource, cond_gen.generate_condition) + source.generator = gen + +In this example, the GAN source emits 10 MBq of gamma particles with position and direction distributions learned by the GAN. Each attribute of the particles (e.g., position, direction, energy) corresponds to a key in the GAN file. The `energy_min_threshold` parameter defines a lower limit for energy; particles with energy below this threshold can either be skipped (`skip_policy = "SkipEvents"`) or assigned zero energy (`skip_policy = "ZeroEnergy"`), meaning they are not tracked. + +The GAN operates in batches, with the size defined by `batch_size`. In this case, a conditional GAN is used to control the emitted particles based on an internal activity distribution provided by a voxelized source (`myactivity.mhd` file). This approach can efficiently replicate complex spatial dependencies in the particle emission process. + +The GAN-based source is an experimental feature in GATE. While it offers promising advantages in terms of reduced file size and simulation speed, users are encouraged to approach it cautiously. We strongly recommend thoroughly reviewing the associated publications `[Sarrut et al, PMB, 2019] <https://doi.org/10.1088/1361-6560/ab3fc1>`_, `[Sarrut et al, PMB, 2021] <https://doi.org/10.1088/1361-6560/abde9a>`_, and `[Saporta et al, PMB, 2022] <https://doi.org/10.1088/1361-6560/aca068>`_ to understand the methodโs assumptions, limitations, and best practices. This method is best suited for research purposes and may not yet be appropriate for clinical or regulatory applications without extensive validation. -- ``test034`` : GAN for linac -- ``test038`` : GAN for SPECT -- ``test040`` : GAN for PET -1) generate training dataset -2) train GAN -3) use GAN as source ; compare to reference PHID source (Photon from Ion Decay) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/source/user_guide/user_guide_volumes.rst b/docs/source/user_guide/user_guide_volumes.rst index 83ab98bf0..115aa3744 100644 --- a/docs/source/user_guide/user_guide_volumes.rst +++ b/docs/source/user_guide/user_guide_volumes.rst @@ -93,6 +93,47 @@ To dump a list of all available volume types: print('Volume types :') print(sim.volume_manager.dump_volume_types()) +Examples of possible volumes +--------------------------- + +.. code:: python + + # A box + myBoxVolume = sim.add_volume("Box", "myBoxVolume") + myBoxVolume.size = [8 * cm, 20 * cm, 8 * cm] + myBoxVolume.translation = [0 * cm, 8 * cm, 0 * cm] + myBoxVolume.mother = "world" + myBoxVolume.material = "Water" # from your GateMaterials.db + myBoxVolume.color = [0, 0, 0, 0.5] + + +.. code:: python + + # A sphere + mySphereVolume = sim.add_volume("Sphere", "mySphereVolume") + mySphereVolume.mother = "world" + mySphereVolume.rmin = 0 * cm + mySphereVolume.rmax = 5 * cm + mySphereVolume.translation = [0 * cm, 0 * cm, 20 * cm] + mySphereVolume.material = "Water" # from your GateMaterials.db + mySphereVolume.color = [1, 0, 0, 1] + + +.. code:: python + + # A Trd is a trapezoid with the x and y dimensions varying along z + myTrdVolume = sim.add_volume("Trd", "myTrdVolume") + myTrdVolume.mother = "world" + myTrdVolume.dx1 = 5.0 * cm + myTrdVolume.dx2 = 5.0 * cm + myTrdVolume.dy1 = 5.5 * cm + myTrdVolume.dy2 = 2.5 * cm + myTrdVolume.dz = 10 * cm + myTrdVolume.translation = [0 * cm, -12 * cm, 20 * cm] + myTrdVolume.material = "Water" # from your GateMaterials.db + myTrdVolume.color = [0, 1, 0, 1] + + Volume hierarchy ---------------- diff --git a/opengate/actors/actoroutput.py b/opengate/actors/actoroutput.py index ceba7d140..8720d2de4 100644 --- a/opengate/actors/actoroutput.py +++ b/opengate/actors/actoroutput.py @@ -74,6 +74,14 @@ def get_output_path(self, **kwargs): kwargs.update(self._kwargs_for_interface_calls) return self._user_output.get_output_path(**kwargs) + def get_run_indices(self, **kwargs): + kwargs.update(self._kwargs_for_interface_calls) + return self._user_output.get_run_indices(**kwargs) + + def get_data(self, **kwargs): + kwargs.update(self._kwargs_for_interface_calls) + return self._user_output.get_data(**kwargs) + @property def write_to_disk(self): try: @@ -100,6 +108,19 @@ def output_filename(self): def output_filename(self, value): self._user_output.set_output_filename(value, **self._kwargs_for_interface_calls) + @property + def keep_data_per_run(self): + try: + return self._user_output.get_keep_data_per_run( + **self._kwargs_for_interface_calls + ) + except NotImplementedError: + raise AttributeError + + @keep_data_per_run.setter + def keep_data_per_run(self, value): + self._user_output.keep_data_per_run = value + @property def item_suffix(self): try: @@ -184,17 +205,6 @@ def _setter_hook_belongs_to(self, belongs_to): return belongs_to_name -def _setter_hook_active(self, active): - if self.__can_be_deactivated__ is True: - return bool(active) - else: - if bool(active) is not True: - warning( - f"The output {self.name} of actor {self.belongs_to_actor.name} cannot be deactivated." - ) - return True - - class ActorOutputBase(GateObject): # hints for IDE @@ -236,16 +246,13 @@ def __init__(self, *args, **kwargs): self.data_per_run = {} # holds the data per run in memory self.merged_data = None # holds the data merged from multiple runs in memory - # internal flag which can set by the actor when it creating an actor output - # via _add_actor_output - # __can_be_deactivated = False forces the "active" user info to True - # This is the expected behavior in most digitizers - # In the DoseActor, on the other hand, users might not want to calculate uncertainty - self.__can_be_deactivated__ = False def __len__(self): return len(self.data_per_run) + def get_run_indices(self, **kwargs): + return [k for k, v in self.data_per_run.items() if v is not None] + def set_write_to_disk(self, value, **kwargs): raise NotImplementedError @@ -280,19 +287,6 @@ def initialize(self): def _generate_auto_output_filename(self, **kwargs): return f"{self.name}_from_{self.belongs_to_actor.type_name.lower()}_{self.belongs_to_actor.name}.{self.default_suffix}" - # def initialize_output_filename(self, **kwargs): - # if self.get_output_filename(**kwargs) == 'auto': - # self.set_output_filename(self._generate_auto_output_filename(), **kwargs) - # - # for k, v in self.data_item_config.items(): - # if 'write_to_disk' in v and v['write_to_disk'] is True: - # if 'output_filename' not in v or v['output_filename'] in ['auto', '', None]: - # if len(self.data_item_config) > 0: - # item_suffix = k - # else: - # item_suffix = '' - # v['output_filename'] = f"{self.name}_from_{self.belongs_to_actor.type_name.lower()}_{self.belongs_to_actor.name}_{item_suffix}.{self.default_suffix}" - def _compose_output_path(self, which, output_filename): full_data_path = self.simulation.get_output_path(output_filename) @@ -308,7 +302,7 @@ def _compose_output_path(self, which, output_filename): f"Valid arguments are a run index (int) or the term 'merged'. " ) run_index = None # remove warning from IDE - return insert_suffix_before_extension(full_data_path, f"run{run_index:04f}") + return insert_suffix_before_extension(full_data_path, f"run{run_index}") def get_output_path(self, which="merged", **kwargs): # try to get the output_filename via 2 successive attempts @@ -368,11 +362,12 @@ def load_data(self, which): ) -class MergeableActorOutput(ActorOutputBase): +class ActorOutputUsingDataItemContainer(ActorOutputBase): # hints for IDE merge_data_after_simulation: bool keep_data_per_run: bool + data_item_config: Optional[Box] user_info_defaults = { "merge_data_after_simulation": ( @@ -387,42 +382,6 @@ class MergeableActorOutput(ActorOutputBase): "doc": "In case the simulation has multiple runs, should separate results per run be kept?" }, ), - } - - def merge_data_from_runs(self): - self.merged_data = merge_data(list(self.data_per_run.values())) - - def merge_into_merged_data(self, data): - if self.merged_data is None: - self.merged_data = data - else: - self.merged_data = merge_data([self.merged_data, data]) - - def end_of_run(self, run_index): - if self.merge_data_after_simulation is True: - self.merge_into_merged_data(self.data_per_run[run_index]) - if self.keep_data_per_run is False: - self.data_per_run.pop(run_index) - - def end_of_simulation(self, **kwargs): - try: - self.write_data_if_requested(which="all", **kwargs) - except NotImplementedError: - raise GateImplementationError( - "Unable to run end_of_simulation " - f"in user_output {self.name} of actor {self.belongs_to_actor.name}" - f"because the class does not implement a write_data_if_requested() " - f"and/or write_data() method. " - f"A developer needs to fix this. " - ) - - -class ActorOutputUsingDataItemContainer(MergeableActorOutput): - - # hints for IDE - data_item_config: Optional[Box] - - user_info_defaults = { "data_item_config": ( Box( { @@ -627,12 +586,6 @@ def store_data(self, which, *data): else: try: run_index = int(which) # might be a run_index - # if run_index not in self.data_per_run: - # else: - # fatal( - # f"A data item is already set for run index {run_index}. " - # f"You can only merge additional data into it. Overwriting is not allowed. " - # ) except ValueError: fatal( f"Invalid argument 'which' in store_data() method of ActorOutput {self.name}. " @@ -706,8 +659,30 @@ def write_data_if_requested(self, which="all", item="all", **kwargs): ] self.write_data(which=which, item=items) + def merge_data_from_runs(self): + self.merged_data = merge_data(list(self.data_per_run.values())) + + def end_of_run(self, run_index): + if self.merge_data_after_simulation is True: + self.merged_data.inplace_merge_with(self.data_per_run[run_index]) + if self.keep_data_per_run is False: + self.data_per_run.pop(run_index) + + def start_of_simulation(self, **kwargs): + if self.merge_data_after_simulation is True: + self.merged_data = self.data_container_class(belongs_to=self) + def end_of_simulation(self, item="all", **kwargs): - self.write_data_if_requested(which="all", item=item) + try: + self.write_data_if_requested(item="all", **kwargs) + except NotImplementedError: + raise GateImplementationError( + "Unable to run end_of_simulation " + f"in user_output {self.name} of actor {self.belongs_to_actor.name}" + f"because the class does not implement a write_data_if_requested() " + f"and/or write_data() method. " + f"A developer needs to fix this. " + ) class ActorOutputImage(ActorOutputUsingDataItemContainer): @@ -837,7 +812,6 @@ def initialize_cpp_parameters(self): process_cls(ActorOutputBase) -process_cls(MergeableActorOutput) process_cls(ActorOutputUsingDataItemContainer) process_cls(ActorOutputImage) process_cls(ActorOutputSingleImage) diff --git a/opengate/actors/base.py b/opengate/actors/base.py index f2c4bf141..2f83eef84 100644 --- a/opengate/actors/base.py +++ b/opengate/actors/base.py @@ -226,6 +226,16 @@ def write_to_disk(self, write_to_disk): for k, v in self.interfaces_to_user_output.items(): v.write_to_disk = write_to_disk + @property + @shortcut_for_single_output_actor + def keep_data_per_run(self): + return list(self.interfaces_to_user_output.values())[0].keep_data_per_run + + @keep_data_per_run.setter + def keep_data_per_run(self, keep_data_per_run): + for k, v in self.interfaces_to_user_output.items(): + v.keep_data_per_run = keep_data_per_run + def get_output_path(self, name=None, **kwargs): if name is not None: try: diff --git a/opengate/actors/dataitems.py b/opengate/actors/dataitems.py index 119b79698..9bd5bfe50 100644 --- a/opengate/actors/dataitems.py +++ b/opengate/actors/dataitems.py @@ -16,14 +16,24 @@ ) -# base classes class DataItem: + """This is the base class for all data items. + It stores the actual data, e.g. an array, an image, etc. in an attribute 'data'. + + Derived classes can (should) implement merge_with and inplace_merge_with + so actor output using this data container with the respective data items can be merged after runs. + + Derived classes should also implement an appropriate write method + if data writing is supposed to be handled on the python-side. + + Derived classes can also implement arithmetic operator like __add__, __mul__, etc. + """ def __init__(self, *args, data=None, meta_data=None, **kwargs): self.data = None if data is not None: self.set_data(data) - self.meta_data = Box() + self.meta_data = Box({"number_of_samples": 1}) if meta_data: try: for k, v in meta_data.items(): @@ -81,29 +91,20 @@ def hand_down(*args, **kwargs): raise AttributeError(f"No such attribute '{item}'") def merge_with(self, other): - """The base class implements merging as summation. + """The base class does not implement merging. Specific classes can override this, e.g. to merge mean values. """ - try: - return self + other - except ValueError as e: - raise NotImplementedError( - f"method 'merge_with' probably not implemented for data item class {type(self)} " - f"because the following ValueError was encountered: \n{e}" - ) + raise NotImplementedError( + f"Method 'inplace_merge_with' not implemented for data item class {type(self)} " + ) def inplace_merge_with(self, other): - """The base class implements merging as summation. + """The base class does not implement merging. Specific classes can override this, e.g. to merge mean values. """ - try: - self += other - except ValueError as e: - raise NotImplementedError( - f"method 'inplace_merge_with' probably not implemented for data item class {type(self)} " - f"because the following ValueError was encountered: \n{e}" - ) - return self + raise NotImplementedError( + f"Method 'inplace_merge_with' not implemented for data item class {type(self)} " + ) def write(self, *args, **kwargs): raise NotImplementedError(f"This is the base class. ") @@ -141,11 +142,15 @@ def merge_with(self, other): return result def inplace_merge_with(self, other): - self *= self.number_of_samples - other *= other.number_of_samples - self += other - self /= self.number_of_samples + other.number_of_samples - self.number_of_samples = self.number_of_samples + other.number_of_samples + if self.data is None: + self.set_data(other.data) + self.number_of_samples = other.number_of_samples + else: + self *= self.number_of_samples + other *= other.number_of_samples + self += other + self /= self.number_of_samples + other.number_of_samples + self.number_of_samples = self.number_of_samples + other.number_of_samples return self @@ -229,6 +234,15 @@ class ItkImageDataItem(DataItem): def image(self): return self.data + def inplace_merge_with(self, other): + if self.data is None: + self.set_data(other.data) + self.number_of_samples = other.number_of_samples + else: + self.__iadd__(other) + self.number_of_samples += other.number_of_samples + return self + def __iadd__(self, other): self._assert_data_is_not_none() self.set_data(sum_itk_images([self.data, other.data])) @@ -312,6 +326,9 @@ class DataContainer: def __init__(self, belongs_to, *args, **kwargs): self.belongs_to = belongs_to + def __copy__(self): + return type(self)(self.belongs_to) + class DataDictionary(DataContainer): @@ -345,6 +362,11 @@ def __init__(self, *args, data=None, **kwargs): if data is not None: self.set_data(*data) + def __copy__(self): + obj = super().__copy__() + obj.set_data(*self.data) + return obj + @classmethod def get_default_data_item_config(cls): default_data_item_config = None @@ -511,21 +533,14 @@ def __truediv__(self, other): def inplace_merge_with(self, other): for i in range(self._tuple_length): # can only apply merge of both items exist (and contain data) - if ( - (self.data[i] is not None) - and (other.data[i] is not None) - and (self.data[i].data is not None) - and (other.data[i].data is not None) - ): + if self.data[i] is not None and other.data[i] is not None: self.data[i].inplace_merge_with(other.data[i]) else: # the case of both item None is acceptable # because the component not be activated in the actor, e.g. edep uncertainty, # but it should not occur that one item is None and the other is not. - if (self.data[i] is None or self.data[i].data is None) is not ( - other.data[i] is None or other.data[i].data is None - ): - s_not = {True: "", False: "not_"} + if (self.data[i] is None) is not (other.data[i] is None): + s_not = {True: "", False: "not"} fatal( "Cannot apply inplace merge data to container " "with unset (None) data items. " @@ -661,9 +676,6 @@ class SingleItkImageWithVariance(DataItemContainer): def get_variance_or_uncertainty(self, which_quantity): try: - # if not self.data[0].number_of_samples == self.data[1].number_of_samples: - # fatal(f"Something is wrong in this data item container: " - # f"the two data items contain different numbers of samples. ") number_of_samples = self.data[0].number_of_samples value_array = np.asarray(self.data[0].data) if not number_of_samples > 1: @@ -747,7 +759,9 @@ class QuotientMeanItkImage(QuotientItkImage): def merge_data(list_of_data): - merged_data = list_of_data[0] + merged_data = type(list_of_data[0])( + list_of_data[0].belongs_to, data=list_of_data[0].data + ) for d in list_of_data[1:]: merged_data.inplace_merge_with(d) return merged_data diff --git a/opengate/actors/digitizers.py b/opengate/actors/digitizers.py index 8c23fba74..3b50f166d 100644 --- a/opengate/actors/digitizers.py +++ b/opengate/actors/digitizers.py @@ -224,7 +224,7 @@ class DigitizerBase(ActorBase): ), } - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(self, *args, **kwargs) def _add_user_output_root(self, **kwargs): diff --git a/opengate/actors/doseactors.py b/opengate/actors/doseactors.py index d2c0c46d5..10d91f15a 100644 --- a/opengate/actors/doseactors.py +++ b/opengate/actors/doseactors.py @@ -52,7 +52,8 @@ class VoxelDepositActor(ActorBase): [1 * g4_units.mm, 1 * g4_units.mm, 1 * g4_units.mm], { "doc": "Voxel spacing along the x-, y-, z-axes. " - "(The user set the units by multiplication with g4_units.XX)", + "The user sets the units by multiplication with g4_units.XX. " + "The default spacing is in g4_unit.mm. ", }, ), "translation": ( @@ -95,9 +96,9 @@ class VoxelDepositActor(ActorBase): "img_coord_system": ( None, { - "deprecated": f"The user input parameter 'img_coord_system' is deprecated. " - f"Use my_actor.output_coordinate_system='attached_to_image' instead, " - f"where my_actor should be replaced with your actor object. ", + "deprecated": "The user input parameter 'img_coord_system' is deprecated. " + "Use my_actor.output_coordinate_system='attached_to_image' instead, " + "where my_actor should be replaced with your actor object. ", }, ), "output_coordinate_system": ( @@ -109,7 +110,7 @@ class VoxelDepositActor(ActorBase): ), } - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: ActorBase.__init__(self, *args, **kwargs) def check_user_input(self): @@ -118,11 +119,29 @@ def check_user_input(self): self.attached_to_volume, "native_translation" ) or not hasattr(self.attached_to_volume, "native_rotation"): fatal( - f"User input 'output_coordinate_system' = {self.output_coordinate_system} is not compatible " + f"User input 'output_coordinate_system' = {self.output_coordinate_system} " + f"of actor {self.name} is not compatible " f"with the volume to which this actor is attached: " f"{self.attached_to} ({self.attached_to_volume.volume_type})" ) + def initialize(self): + super().initialize() + + msg = ( + f"cannot be used in actor {self.name} " + f"because the volume ({self.attached_to}, {self.attached_to_volume.type_name}) " + f"to which the actor is attached does not support it. " + ) + if isinstance(self.spacing, str) and self.spacing == "like_image_volume": + if not hasattr(self.attached_to_volume, "spacing"): + fatal("spacing = 'like_image_volume' " + msg) + self.spacing = self.attached_to_volume.spacing + if isinstance(self.size, str) and self.size == "like_image_volume": + if not hasattr(self.attached_to_volume, "size_pix"): + fatal("size = 'like_image_volume' " + msg) + self.size = self.attached_to_volume.size_pix + def get_physical_volume_name(self): # init the origin and direction according to the physical volume # (will be updated in the BeginOfRun) @@ -243,10 +262,16 @@ def EndOfRunActionMasterThread(self, run_index): u.end_of_run(run_index) return 0 + def StartSimulationAction(self): + # inform actor output that this simulation is starting + for u in self.user_output.values(): + if u.get_active(item="any"): + u.start_of_simulation() + def EndSimulationAction(self): # inform actor output that this simulation is over and write data for u in self.user_output.values(): - if u.get_active(item="all"): + if u.get_active(item="any"): u.end_of_simulation() @@ -1029,7 +1054,7 @@ def initialize(self): # no options yet if self.uncertainty or self.scatter: - fatal(f"FluenceActor : uncertainty and scatter not implemented yet") + fatal("FluenceActor : uncertainty and scatter not implemented yet") self.InitializeUserInput(self.user_info) # Set the physical volume name on the C++ side diff --git a/opengate/actors/filters.py b/opengate/actors/filters.py index e59e16300..d0709ed77 100644 --- a/opengate/actors/filters.py +++ b/opengate/actors/filters.py @@ -24,7 +24,7 @@ class FilterBase(GateObject): ), } - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) def __initcpp__(self): diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index e561f8608..0d2fd0805 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -1,6 +1,8 @@ from box import Box import platform +from anytree import Node, RenderTree import opengate_core as g4 +from anytree import Node, RenderTree from .base import ActorBase from ..utility import g4_units, g4_best_unit_tuple from .actoroutput import ActorOutputBase @@ -496,6 +498,88 @@ def initialize(self): self.InitializeCpp() +class LastVertexInteractionSplittingActor(ActorBase,g4.GateLastVertexInteractionSplittingActor): + """This splitting actor proposes an interaction splitting at the last particle vertex before the exit + of the biased volume. This actor can be usefull for application where collimation are important, + such as in medical LINAC (Linear Accelerator) simulations or radiation shielding. + """ + + user_info_defaults = { + "splitting_factor": ( + 1, + { + "doc": "Defines the number of particles exiting at each split process. Unlike other split actors, this splitting factor counts particles that actually exit, not just those generated.", + }, + ), + + "angular_kill": ( + False, + { + "doc": "If enabled, particles with momentum outside a specified angular range are killed.", + }, + ), + + "max_theta": ( + 90 * g4_units.deg, + { + "doc": "Defines the maximum angle (in degrees) from a central axis within which particles are retained. Particles with momentum beyond this angle are removed. The angular range spans from 0 to max_theta, measured from the vector_director", + }, + ), + + + "vector_director": ( + [0, 0, 1], + { + "doc": "Specifies the reference direction vector from which max_theta is measured. Particlesโ angular range is calculated based on this direction.", + }, + ), + "rotation_vector_director": ( + False, + { + "doc": "If enabled, the vector_director rotates in alignment with any rotation applied to the biased volume attached to this actor.", + }, + ), + "batch_size": ( + 1, + { + "doc": "Defines a batch of number of processes to regenerate, calculated as batch_size * splitting_factor. The optimal value depends on the collimation setup; for example, a batch_size of 10 works well for LINAC head configurations.", + }, + ), + + + } + + + def __init__(self, *args, **kwargs): + ActorBase.__init__(self, *args, **kwargs) + self.__initcpp__() + self.list_of_volume_name = [] + + def __initcpp__(self): + g4.GateLastVertexInteractionSplittingActor.__init__(self, {"name": self.name}) + self.AddActions({"BeginOfRunAction", + "BeginOfEventAction", + "PreUserTrackingAction", + "SteppingAction", + "PostUserTrackingAction", + "EndOfEventAction"}) + + + def initialize(self): + ActorBase.initialize(self) + self.InitializeUserInput(self.user_info) + self.InitializeCpp() + volume_tree = self.simulation.volume_manager.get_volume_tree() + dico_of_volume_tree = {} + for pre, _, node in RenderTree(volume_tree): + dico_of_volume_tree[str(node.name)] = node + volume_name = self.user_info.attached_to + while volume_name != "world": + node = dico_of_volume_tree[volume_name] + volume_name = node.mother + self.list_of_volume_name.append(volume_name) + self.fListOfVolumeAncestor = self.list_of_volume_name + class BremSplittingActor(SplittingActorBase, g4.GateBOptrBremSplittingActor): # hints for IDE processes: list @@ -528,6 +612,7 @@ def initialize(self): process_cls(SimulationStatisticsActor) process_cls(KillActor) process_cls(KillAccordingProcessesActor) +process_cls(LastVertexInteractionSplittingActor) process_cls(SplittingActorBase) process_cls(ComptSplittingActor) process_cls(BremSplittingActor) diff --git a/opengate/base.py b/opengate/base.py index bd4b63559..5349822b2 100644 --- a/opengate/base.py +++ b/opengate/base.py @@ -331,17 +331,12 @@ def make_docstring_for_user_info(name, default_value, options): docstring = f"{name}" if "deprecated" in options: docstring += f"\n\n{begin_of_line}Deprecated: {options['deprecated']}\n\n" - # docstring += indent - # docstring += "Info: " - # docstring += options["deprecated"] - # docstring += "\n\n" else: if "required" in options and options["required"] is True: docstring += " (must be provided)" if "read_only" in options and options["read_only"] is True: docstring += " (set internally, i.e. read-only)" docstring += ":\n\n" - # docstring += (20 - len(k)) * " " docstring += f"{begin_of_line}Default value: {default_value}\n\n" if "allowed_values" in options: docstring += ( @@ -349,9 +344,6 @@ def make_docstring_for_user_info(name, default_value, options): ) if "doc" in options: docstring += f"{begin_of_line}Description: {options['doc']}\n\n" - # docstring += options["doc"] - # docstring += "\n\n" - # docstring += "\n" return docstring @@ -443,7 +435,7 @@ def __init__(self, *args, simulation=None, **kwargs): if type(parent).__name__ != "pybind11_type": try: super().__init__(*args, **kwargs) - except TypeError as e: + except TypeError: raise TypeError( f"There was a problem " f"while trying to create the {type(self).__name__} called {self.name}. \n" @@ -498,11 +490,6 @@ def __getstate__(self): def __setstate__(self, d): """Method needed for pickling. May be overridden in inheriting classes.""" self.__dict__ = d - """print( - f"DEBUG: in __setstate__ of {type(self).__name__}: {type(self).known_attributes}" - ) - print(f"DEBUG: type(self).known_attributes: {type(self).known_attributes}") - print(f"DEBUG: list(self.__dict__.keys()): {list(self.__dict__.keys())}")""" def __reduce__(self): """This method is called when the object is pickled. @@ -546,7 +533,7 @@ def __setattr__(self, key, value): close_matches = get_close_matches(key, known_attributes) if len(close_matches) > 0: msg_close_matches = ( - f"Did you mean: " + " or ".join(close_matches) + "\n" + "Did you mean: " + " or ".join(close_matches) + "\n" ) msg += msg_close_matches known_attr = ", ".join( @@ -594,7 +581,6 @@ def close(self): warning( f"close() called in object '{self.name}' of type {type(self).__name__}." ) - pass def release_g4_references(self): """Dummy implementation for inherited classes which do not implement this method.""" @@ -774,9 +760,6 @@ def create_changers(self): return [] -# DICTIONARY HANDLING - - class GateUserInputSwitchDict(Box): """ NOT USED YET! @@ -843,7 +826,7 @@ def recursive_userinfo_to_dict(obj): ret = [] for e in obj: ret.append(recursive_userinfo_to_dict(e)) - elif isinstance(obj, (GateObject)): + elif isinstance(obj, GateObject): ret = obj.to_dictionary() else: ret = obj diff --git a/opengate/bin/opengate_tests.py b/opengate/bin/opengate_tests.py index a99fcc38d..d10c3df03 100755 --- a/opengate/bin/opengate_tests.py +++ b/opengate/bin/opengate_tests.py @@ -445,9 +445,9 @@ def run_test_cases( start = time.time() if processes_run in ["legacy"]: run_single_case = lambda k: run_one_test_case(k, processes_run, path_tests_src) - runs_status_info = list(map(run_single_case, files)) + runs_status_info = [run_single_case(file) for file in files] elif processes_run in ["sp"]: - runs_status_info = list(map(run_one_test_case_mp, files)) + runs_status_info = [run_one_test_case_mp(file) for file in files] else: num_processes = int(float(num_processes)) if num_processes != "all" else None with Pool(processes=num_processes) as pool: diff --git a/opengate/contrib/linacs/dicomrtplan.py b/opengate/contrib/linacs/dicomrtplan.py index 5d82e0b78..fa0842b65 100644 --- a/opengate/contrib/linacs/dicomrtplan.py +++ b/opengate/contrib/linacs/dicomrtplan.py @@ -3,130 +3,327 @@ import pydicom -def extract_dataset(file): +def liste_CP(file, beam_sequence_ID): + ds = pydicom.dcmread(file)[0x300A, 0x00B0].value[beam_sequence_ID][0x300A, 0x0111] + return len(ds.value) + + +def list_of_beam_sequence_ID(file): + patient_info = pydicom.dcmread(file)[0x300A, 0x0070] + nb_of_beam_seq = len(patient_info.value[0][("0x300C", "0x0004")].value) + id_of_beam_sequence = [] + for i in range(nb_of_beam_seq): + number_of_mu = retrieve_value_in_DICOM_RTPlan( + patient_info.value[0], + [[("0x300C", "0x0004"), i], [("0x300A", "0x0086"), None]], + ) + if number_of_mu != None: + id_of_beam_sequence.append(i) + return id_of_beam_sequence + + +def read(file, cp_id="all_cp", arc_id=None): + mm = g4_units.mm + jaws_1 = 0 + jaws_2 = 1 + angle = 2 + dir_angle = 3 + isocenter = 4 + dose_weights = 5 + collimation_angle = 6 + leaves = 7 + Mu_number = 8 + + jaw_1_array = [] + jaw_2_array = [] + rot_angle_array = [] + isocenter_array = [] + dose_weight_array = [] + collimation_angle_array = [] + leaf_array = [] + if arc_id == None: + id_of_beam_sequence = list_of_beam_sequence_ID(file) + else: + arc_id = int(arc_id) + id_of_beam_sequence = [arc_id] + + for beam_seq_id in id_of_beam_sequence: + l_jaws_1 = [] + l_jaws_2 = [] + l_angle = [] + l_dir_angle = [] + l_isocenter = [] + x_leaf = [] + l_dose_weights = [] + l_collimation_angle = [] + data_set = extract_dataset(file, beam_seq_id) + number_of_MU = data_set[Mu_number]["MU number"] + key_list = [ + "jaws_1", + "jaws_2", + "rot_angle", + "dir_angle", + "isocenter", + "cumulative weight", + "collimation angle", + ] + l_parameters = [ + l_jaws_1, + l_jaws_2, + l_angle, + l_dir_angle, + l_isocenter, + l_dose_weights, + l_collimation_angle, + ] + if cp_id == "all_cp": + nb_cp_id = len(data_set[jaws_1]["jaws_1"]) + l_cp_id = np.arange(0, nb_cp_id, 1) + for id in l_cp_id: + for i, key in enumerate(key_list): + if data_set[i][key][id] != None: + if key == "isocenter": + l_parameters[i].append(np.array(data_set[i][key][id]) * mm) + elif ( + key != "rot_angle" + and key != "dir_angle" + and key != "collimation angle" + ): + l_parameters[i].append(float(data_set[i][key][id]) * mm) + else: + l_parameters[i].append(data_set[i][key][id]) + else: + if id != 0: + if data_set[i][key][id] == None: + if l_parameters[i][id - 1] != None: + l_parameters[i].append(l_parameters[i][id - 1]) + else: + l_parameters[i].append(data_set[i][key][id]) + else: + l_parameters[i].append(data_set[i][key][id]) + + tmp_dir_angle = [] + for i in range(len(l_dir_angle)): + + if l_dir_angle[i] == "CW": + tmp_dir_angle.append(1) + elif l_dir_angle[i] == "CC": + tmp_dir_angle.append(-1) + else: + tmp_dir_angle.append(1) + + l_dir_angle = np.array(tmp_dir_angle) + + for id in l_cp_id: + leaf_block_1 = [] + leaf_block_2 = [] + leaf_blocks = [leaf_block_1, leaf_block_2] + for i in range(len(data_set[leaves][0])): + for side_ID in range(2): + if data_set[leaves][0][i]["leaves"][id] != None: + leaf_blocks[side_ID].append( + data_set[leaves][side_ID][i]["leaves"][id] + ) + else: + if id != 0: + if data_set[leaves][0][i]["leaves"][id - 1] != None: + leaf_blocks[side_ID].append( + data_set[leaves][side_ID][i]["leaves"][id - 1] + ) + else: + leaf_blocks[side_ID].append(None) + else: + leaf_blocks[side_ID].append(None) + if None not in leaf_block_1 and None not in leaf_block_2: + leaf_block_tmp = np.array(leaf_block_1 + leaf_block_2, dtype=float) * mm + x_leaf.append(leaf_block_tmp) + else: + x_leaf.append([None]) + + if None in l_dose_weights: + diff_dose_weights = None + else: + l_dose_weights = np.array(l_dose_weights) + diff_dose_weights = np.diff(l_dose_weights) * number_of_MU + dose_weight_array += diff_dose_weights.tolist() + # diff_dose_weights = diff_dose_weights / np.median(diff_dose_weights) + if None in l_angle: + l_angle = None + else: + l_angle = np.array(l_angle) * l_dir_angle + l_angle[l_angle < 0] = -l_angle[l_angle < 0] + diff_angle = np.diff(l_angle) + diff_angle[diff_angle > 300] = diff_angle[diff_angle > 300] - 360 + diff_angle[diff_angle < -300] = diff_angle[diff_angle < -300] + 360 + l_angle = l_angle[0:-1] + diff_angle / 2 + l_angle[l_angle > 360] -= 360 + l_angle[l_angle < 0] += 360 + rot_angle_array += l_angle.tolist() + if None in x_leaf[0]: + x_leaf = None + else: + x_leaf = np.array(x_leaf) + x_leaf = x_leaf[0:-1] + np.diff(x_leaf, axis=0) / 2 + leaf_array += x_leaf.tolist() + + if None in l_jaws_1: + l_jaws_1 = None + else: + l_jaws_1 = np.array(l_jaws_1) + l_jaws_1 = l_jaws_1[0:-1] + np.diff(l_jaws_1) / 2 + jaw_1_array += l_jaws_1.tolist() + + if None in l_jaws_2: + l_jaws_2 = None + else: + l_jaws_2 = np.array(l_jaws_2) + l_jaws_2 = l_jaws_2[0:-1] + np.diff(l_jaws_2) / 2 + jaw_2_array += l_jaws_2.tolist() + + if None in l_collimation_angle: + l_collimation_angle = None + else: + l_collimation_angle = np.array(l_collimation_angle, dtype=float) + l_collimation_angle = ( + l_collimation_angle[0:-1] + np.diff(l_collimation_angle) / 2 + ) + collimation_angle_array += l_collimation_angle.tolist() + + isocenter_array += l_isocenter + + jaw_1_array = np.array(jaw_1_array) + jaw_2_array = np.array(jaw_2_array) + leaf_array = np.array(leaf_array) + rot_angle_array = np.array(rot_angle_array) + dose_weight_array = np.array(dose_weight_array) + dose_weight_array = dose_weight_array / np.median(dose_weight_array) + isocenter_array = np.array(isocenter_array) + collimation_angle_array = np.array(collimation_angle_array) + + rt_plan_parameters = { + "jaws 1": jaw_1_array, + "weight": dose_weight_array, + "jaws 2": jaw_2_array, + "leaves": leaf_array, + "gantry angle": rot_angle_array, + "isocenter": isocenter_array, + "collimation angle": collimation_angle_array, + } + return rt_plan_parameters + + +file = "/home/mjacquet/Documents/Simulation_RT_plan/patient_data/IGR/AGORL_CLB_P1toP20/AGORL_P17/RP.1.2.752.243.1.1.20200423201445736.8300.22461.dcm" + + +def retrieve_value_in_DICOM_RTPlan(cp, list): + if list[0][0] in cp: + if list[0][1] != None: + value = cp[list[0][0]].value[list[0][1]] + else: + value = cp[list[0][0]].value + if len(list) > 1: + return retrieve_value_in_DICOM_RTPlan(value, list[1:]) + if len(list) == 1: + if value == "NONE": + return None + return value + return None + + +def extract_dataset(file, beam_sequence_ID): jaws_1 = 0 jaws_2 = 1 - leaves = 2 - angle = 3 - dir_angle = 4 - isocenter = 5 - dose_weights = 6 + angle = 2 + dir_angle = 3 + isocenter = 4 + dose_weights = 5 + limiting_device_angle = 6 + leaves = 7 + MU_number = 8 - ds = pydicom.dcmread(file)[0x300A, 0x00B0].value[0][0x300A, 0x0111] - nb_leaf = int(len(ds[0][0x300A, 0x011A].value[1][0x300A, 0x011C].value) / 2) + ds = pydicom.dcmread(file)[0x300A, 0x00B0].value[beam_sequence_ID][0x300A, 0x0111] + nb_leaf = 1 + for j in range(len(ds[0][0x300A, 0x011A].value)): + if ds[0][0x300A, 0x011A].value[j][0x300A, 0x00B8].value == "MLCX": + nb_leaf = int(len(ds[0][0x300A, 0x011A].value[j][0x300A, 0x011C].value) / 2) - data_set = [None] * 7 + data_set = [None] * 9 data_set[jaws_1] = {"jaws_1": []} data_set[jaws_2] = {"jaws_2": []} - data_set[leaves] = [[{"leaves": []} for i in range(nb_leaf)] for j in range(2)] data_set[angle] = {"rot_angle": []} data_set[dir_angle] = {"dir_angle": []} data_set[isocenter] = {"isocenter": []} data_set[dose_weights] = {"cumulative weight": []} + data_set[leaves] = [[{"leaves": []} for i in range(nb_leaf)] for j in range(2)] + data_set[MU_number] = {"MU number": 0} + data_set[limiting_device_angle] = {"collimation angle": []} + count = 0 + patient_info = pydicom.dcmread(file)[0x300A, 0x0070] + number_of_mu = retrieve_value_in_DICOM_RTPlan( + patient_info.value[0], + [[("0x300C", "0x0004"), beam_sequence_ID], [("0x300A", "0x0086"), None]], + ) + data_set[MU_number]["MU number"] = number_of_mu for cp in ds.value: - data_set[jaws_1]["jaws_1"].append( - cp[0x300A, 0x011A].value[0][0x300A, 0x011C].value[0] + bool_MLC = False + bool_jaws = False + idx_jaws = 0 + idx_MLC = 0 + if (0x300A, 0x011A) in cp: + for j in range(len(cp[0x300A, 0x011A].value)): + if cp[0x300A, 0x011A].value[j][0x300A, 0x00B8].value == "MLCX": + idx_MLC = j + bool_MLC = True + + if cp[0x300A, 0x011A].value[j][0x300A, 0x00B8].value == "ASYMY": + idx_jaws = j + bool_jaws = True + if bool_jaws: + data_set[jaws_1]["jaws_1"].append( + retrieve_value_in_DICOM_RTPlan( + cp, [[("0x300A", "0x011A"), idx_jaws], [("0x300A", "0x011C"), 0]] + ) + ) + data_set[jaws_2]["jaws_2"].append( + retrieve_value_in_DICOM_RTPlan( + cp, [[("0x300A", "0x011A"), idx_jaws], [("0x300A", "0x011C"), 1]] + ) + ) + else: + data_set[jaws_1]["jaws_1"].append(None) + data_set[jaws_2]["jaws_2"].append(None) + + data_set[angle]["rot_angle"].append( + retrieve_value_in_DICOM_RTPlan(cp, [[("0x300A", "0x011E"), None]]) ) - data_set[jaws_2]["jaws_2"].append( - cp[0x300A, 0x011A].value[0][0x300A, 0x011C].value[1] + data_set[dir_angle]["dir_angle"].append( + retrieve_value_in_DICOM_RTPlan(cp, [[("0x300A", "0x011F"), None]]) ) - data_set[angle]["rot_angle"].append(cp[0x300A, 0x011E].value) - data_set[dir_angle]["dir_angle"].append(cp[0x300A, 0x011F].value) data_set[dose_weights]["cumulative weight"].append( - cp[0x300C, 0x0050].value[0][0x300A, 0x010C].value + retrieve_value_in_DICOM_RTPlan( + cp, [[("0x300C", "0x0050"), 0], [("0x300A", "0x010C"), None]] + ) ) - data_set[isocenter]["isocenter"].append(ds.value[0][0x300A, 0x012C].value) - for Sideid in range(2): - for i in range(nb_leaf): - data_set[leaves][Sideid][i]["leaves"].append( - cp[0x300A, 0x011A].value[1][0x300A, 0x011C].value[80 * Sideid + i] - ) - - return data_set - - -def read(file, cp_id="all_cp"): - mm = g4_units.mm - jaws_1 = 0 - jaws_2 = 1 - leaves = 2 - angle = 3 - dir_angle = 4 - isocenter = 5 - dose_weights = 6 - - l_jaws_1 = [] - l_jaws_2 = [] - l_angle = [] - l_dir_angle = [] - l_isocenter = [] - x_leaf = [] - l_dose_weights = [] - - data_set = extract_dataset(file) - - if cp_id == "all_cp": - nb_cp_id = len(data_set[jaws_1]["jaws_1"]) - cp_id = np.arange(0, nb_cp_id, 1) - for id in cp_id: - l_jaws_1.append(float(data_set[jaws_1]["jaws_1"][id]) * mm) - l_jaws_2.append(float(data_set[jaws_2]["jaws_2"][id]) * mm) - l_angle.append(float(data_set[angle]["rot_angle"][id])) - l_dir_angle.append(data_set[dir_angle]["dir_angle"][id]) - l_isocenter.append( - np.array(data_set[isocenter]["isocenter"][id], dtype=float) * mm + data_set[limiting_device_angle]["collimation angle"].append( + retrieve_value_in_DICOM_RTPlan(cp, [[("0x300A", "0x0120"), None]]) ) - l_dose_weights.append(float(data_set[dose_weights]["cumulative weight"][id])) - - tmp_dir_angle = [] - for i in range(len(l_dir_angle)): - if l_dir_angle[i] == "CW": - tmp_dir_angle.append(1) - elif l_dir_angle[i] == "CC": - tmp_dir_angle.append(-1) + data_set[isocenter]["isocenter"].append(ds.value[0]["0x300A", "0x012C"].value) + if bool_MLC: + for side_ID in range(2): + for i in range(nb_leaf): + data_set[leaves][side_ID][i]["leaves"].append( + retrieve_value_in_DICOM_RTPlan( + cp, + [ + [("0x300A", "0x011A"), idx_MLC], + [("0x300A", "0x011C"), 80 * side_ID + i], + ], + ) + ) else: - tmp_dir_angle.append(1) - - l_dir_angle = np.array(tmp_dir_angle) - for id in cp_id: - leaf_block_1 = [] - leaf_block_2 = [] - for i in range(len(data_set[leaves][0])): - leaf_block_1.append(data_set[leaves][0][i]["leaves"][id]) - leaf_block_2.append(data_set[leaves][1][i]["leaves"][id]) - leaf_block_tmp = np.array(leaf_block_1 + leaf_block_2, dtype=float) * mm - for i in range(int(len(leaf_block_tmp) / 2)): - if leaf_block_tmp[i] == -leaf_block_tmp[i + int(len(leaf_block_tmp) / 2)]: - leaf_block_tmp[i] = -0 * mm - leaf_block_tmp[i + int(len(leaf_block_tmp) / 2)] = 0 * mm - x_leaf.append(leaf_block_tmp) - - l_dose_weights = np.array(l_dose_weights) - diff_dose_weights = np.diff(l_dose_weights) - l_angle = np.array(l_angle) * l_dir_angle - l_angle[l_angle < 0] = -l_angle[l_angle < 0] - diff_angle = np.diff(l_angle) - diff_angle[diff_angle > 300] = diff_angle[diff_angle > 300] - 360 - diff_angle[diff_angle < -300] = diff_angle[diff_angle < -300] + 360 - l_angle = l_angle[0:-1] + diff_angle / 2 - l_angle[l_angle > 360] -= 360 - l_angle[l_angle < 0] += 360 - x_leaf = np.array(x_leaf) - l_jaws_1 = np.array(l_jaws_1) - l_jaws_1 = l_jaws_1[0:-1] + np.diff(l_jaws_1) / 2 - l_jaws_2 = np.array(l_jaws_2) - l_jaws_2 = l_jaws_2[0:-1] + np.diff(l_jaws_2) / 2 - x_leaf = x_leaf[0:-1] + np.diff(x_leaf, axis=0) / 2 - - diff_dose_weights = diff_dose_weights / np.median(diff_dose_weights) - rt_plan_parameters = { - "jaws 1": l_jaws_1, - "weight": diff_dose_weights, - "jaws 2": l_jaws_2, - "leaves": x_leaf, - "gantry angle": l_angle, - "isocenter": l_isocenter, - } + for side_ID in range(2): + data_set[leaves][side_ID][0]["leaves"].append(None) - return rt_plan_parameters + count += 1 + return data_set diff --git a/opengate/contrib/linacs/elekta_versa_materials.db b/opengate/contrib/linacs/elekta_versa_materials.db index 0476c4406..798ac6a37 100644 --- a/opengate/contrib/linacs/elekta_versa_materials.db +++ b/opengate/contrib/linacs/elekta_versa_materials.db @@ -9,6 +9,13 @@ Hydrogen: S= H ; Z= 1. ; A= 1.01 g/mole Carbon: S= C ; Z= 6. ; A= 12.01 g/mole Oxygen: S= O ; Z= 8. ; A= 16.00 g/mole Aluminium: S= Al ; Z= 13. ; A= 26.98 g/mole +Manganese: S=Mn ; Z= 25. ; A= 54.94 g/mole +Nitrogen: S=N ; Z= 7. ; A= 14.006 g/mole +Phosphorus: S= P ; Z= 15. ; A= 30.974 g/mole +Sulfur: S=S ; Z= 16. ; A= 32.065 g/mole +Silicon: S=Si ; Z= 14. ; A= 28.085 g/mole + + [Materials] @@ -31,7 +38,7 @@ mat_leaf: d=18 g/cm3; n=3 +el: name=Nickel; f=0.0375 +el: name=Iron; f=0.0125 -target_tungsten: d=19.4 g/cm3; n=2 +target_tungsten: d=19.49 g/cm3; n=2 +el: name=Tungsten; f=0.90 +el: name=Rhenium; f=0.10 @@ -46,6 +53,27 @@ flattening_filter_material: d=7.9 g/cm3; n=3 +el: name=Nickel; f=0.08 +flattening_filter_material_stain_steel: d=7.9 g/cm3; n=10 + +el: name=Carbon; f = 0.005 + +el: name=Chromium; f=0.18 + +el: name=Copper; f= 0.005 + +el: name=Manganese; f = 0.01 + +el: name=Nitrogen; f=0.00055 + +el: name=Nickel; f=0.09 + +el: name=Phosphorus; f=0.000225 + +el: name=Sulfur; f = 0.00175 + +el: name=Silicon; f=0.005 + +el: name=Iron; f=0.702475 + + +flattening_filter_material_mild_steel: d=7.9 g/cm3; n=5 + +el: name=Carbon; f = 0.0015 + +el: name=Manganese; f = 0.0075 + +el: name=Phosphorus; f=0.00075 + +el: name=Sulfur; f = 0.0005 + +el: name=Iron; f=0.98975 + + linac_aluminium: d=2.7 g/cm3 ; n=1 ; state=solid +el: name=Aluminium ; n=1 diff --git a/opengate/contrib/linacs/elektaversa.py b/opengate/contrib/linacs/elektaversa.py index 47091ce4e..a57e8a451 100644 --- a/opengate/contrib/linacs/elektaversa.py +++ b/opengate/contrib/linacs/elektaversa.py @@ -5,7 +5,9 @@ from opengate.geometry.utility import get_grid_repetition from opengate.geometry.volumes import unite_volumes, intersect_volumes, subtract_volumes from opengate.geometry import volumes +import scipy import numpy as np +import itk def add_linac(sim, linac_name, sad=1000): @@ -36,9 +38,10 @@ def add_empty_linac_box(sim, linac_name, sad=1000): linac = sim.add_volume("Box", linac_name) linac.material = "G4_AIR" linac.size = [1 * m, 1 * m, 0.52 * m] - translation_linac_box = np.array([0 * mm, 0, sad - linac.size[2] / 2]) + translation_linac_box = np.array([0 * mm, 0, sad - linac.size[2] / 2 + 3.5 * mm]) + # Isocenter begin at the end of the target, That's why 3.5 mm is added. linac.translation = translation_linac_box - linac.color = [1, 1, 1, 0.8] + linac.color = [1, 1, 1, 0] return linac @@ -69,7 +72,7 @@ def add_target(sim, linac_name): target_support.material = "G4_AIR" target_support.rmin = 0 target_support.rmax = 15 * mm - target_support.dz = 11 * mm / 2.0 + target_support.dz = 13.5 * mm / 2.0 target_support.translation = [0, 0, z_linac / 2 - target_support.dz - 1 * nm] target_support.color = [0, 1, 0, 1] @@ -79,7 +82,7 @@ def add_target(sim, linac_name): target.rmin = 0 target.rmax = 2.7 * mm target.dz = 1 * mm / 2.0 - target.translation = [0, 0, 5 * mm] + target.translation = [0, 0, target_support.dz - 3 * mm] target.color = red target_support_top = sim.add_volume("Tubs", f"{linac_name}_target_support_top") @@ -87,8 +90,8 @@ def add_target(sim, linac_name): target_support_top.material = copper target_support_top.rmin = 2.7 * mm target_support_top.rmax = 15 * mm - target_support_top.dz = 1 * mm / 2.0 - target_support_top.translation = [0, 0, 5 * mm] + target_support_top.dz = 3.5 * mm / 2.0 + target_support_top.translation = [0, 0, target_support.dz - 1.75 * mm] target_support_top.color = green target_support_bottom = sim.add_volume( @@ -96,11 +99,11 @@ def add_target(sim, linac_name): ) target_support_bottom.mother = target_support.name - target_support_bottom.material = target_material + target_support_bottom.material = copper target_support_bottom.rmin = 0 target_support_bottom.rmax = 15 * mm target_support_bottom.dz = 10 * mm / 2.0 - target_support_bottom.translation = [0, 0, -0.5 * mm] + target_support_bottom.translation = [0, 0, target_support.dz - 8.5 * mm] target_support_bottom.color = green return target_support @@ -128,7 +131,7 @@ def kill_around_target(sim, linac_name): target_around.material = "G4_AIR" target_around.rmin = 15.1 * mm target_around.rmax = target_around.rmin + 1 * nm - target_around.dz = 5.5 * mm + target_around.dz = 13.5 / 2 * mm target_around.translation = [0, 0, z_linac / 2 - target_around.dz - 1 * nm] target_around.color = [1, 0, 0, 1] @@ -152,8 +155,8 @@ def add_primary_collimator(sim, linac_name): primary_collimator.dz = 101 * mm / 2.0 primary_collimator.sphi = 0 primary_collimator.dphi = 360 * deg - primary_collimator.translation = [0, 0, z_linac / 2 - 65.5 * mm] - primary_collimator.color = [0.5, 0.5, 1, 0.8] + primary_collimator.translation = [0, 0, z_linac / 2 - 70.6 * mm] + primary_collimator.color = [0, 0, 1, 0.8] def add_flattening_filter(sim, linac_name): @@ -171,16 +174,16 @@ def add_flattening_filter(sim, linac_name): flattening_filter.mother = linac_name flattening_filter.material = "G4_AIR" flattening_filter.rmin = 0 - flattening_filter.rmax = 40 * mm - flattening_filter.dz = 24.1 * mm / 2 - flattening_filter.translation = [0, 0, z_linac / 2 - 146.05 * mm] + flattening_filter.rmax = 54 * mm + flattening_filter.dz = 26.1 * mm / 2 + flattening_filter.translation = [0, 0, z_linac / 2 - 149.55 * mm] flattening_filter.color = [1, 0, 0, 0] # invisible # create all cones def add_cone(sim, p): c = sim.add_volume("Cons", f"{linac_name}_flattening_filter_cone_{p.name}") c.mother = flattening_filter.name - c.material = "flattening_filter_material" + c.material = "flattening_filter_material_stain_steel" c.rmin1 = 0 c.rmax1 = p.rmax1 c.rmin2 = 0 @@ -191,14 +194,125 @@ def add_cone(sim, p): c.translation = [0, 0, p.tr] c.color = yellow - cones = [ - [0.001, 5.45, 3.40, 10.35], - [5.45, 9, 2.7, 7.3], - [9, 14.5, 4.9, 3.5], - [14.5, 22.5, 5.5, -1.7], - [22.5, 32.5, 5.6, -7.25], - [38.5, 38.5, 2, -11.05], - ] ## FIXME check 32.5 ? + cones = np.array( + [ + [0.001, 2.6, 1.2, flattening_filter.dz - 0.6], + [2.6, 9, 4.9, flattening_filter.dz - 3.65], + [9, 14.5, 4.9, flattening_filter.dz - 8.55], + [14.5, 22.5, 5.5, flattening_filter.dz - 13.75], + [22.5, 32.5, 5.6, flattening_filter.dz - 19.3], + ] + ) + + cyl = volumes.TubsVolume(name=f"{linac_name}_flattening_filter_cyl_base") + cyl.rmin = 0 + cyl.rmax = 46.5 * mm + cyl.dz = 4 * mm + + trap = volumes.TrapVolume(name=f"{linac_name}_flattening_filter_trap_base") + dz = 93 / 2 * mm + dy1 = 171.39 / 2 * mm + dy2 = 36.26 * mm / 2 + + dx1 = 4 / 2 * mm + alpha1 = 0 + alpha2 = alpha1 + phi = 0 + theta = 0 + dx2 = dx1 + dx3 = dx1 + dx4 = dx1 + + trap.dx1 = dx1 + trap.dx2 = dx2 + trap.dx3 = dx3 + trap.dx4 = dx4 + trap.dy1 = dy1 + trap.dy2 = dy2 + trap.dz = dz + trap.alp1 = alpha1 + trap.alp2 = alpha2 + trap.theta = theta + trap.phi = phi + rot = Rotation.from_euler("Y", -90, degrees=True).as_matrix() + ff_base = intersect_volumes(cyl, trap, [0, 0, 0], rot) + ff_base.name = f"{linac_name}_flattening_filter_base" + sim.volume_manager.add_volume(ff_base, f"{linac_name}_flattening_filter_base") + ff_base.mother = flattening_filter.name + ff_base.translation = [0, 0, flattening_filter.dz - 24.1 * mm] + ff_base.color = yellow + ff_base.material = "flattening_filter_material_stain_steel" + + cons_1 = volumes.ConsVolume(name=f"{linac_name}_flattening_filter_cons_1") + cons_1.rmin1 = 77 / 2 * mm + cons_1.rmax1 = 93 / 2 * mm + cons_1.rmin2 = 77 / 2 * mm + cons_1.rmax2 = 93 / 2 * mm + cons_1.dz = 3.5 * mm / 2.0 + cons_1.sphi = 0 + cons_1.dphi = 360 * deg + + ff_cons_1 = intersect_volumes(cons_1, trap, [0, 0, 0], rot) + ff_cons_1.name = f"{linac_name}_flattening_filter_cons_1" + sim.volume_manager.add_volume(ff_cons_1, f"{linac_name}_flattening_filter_cons_1") + ff_cons_1.mother = flattening_filter.name + ff_cons_1.material = "flattening_filter_material_stain_steel" + ff_cons_1.translation = [0, 0, flattening_filter.dz - 20.35 * mm] + ff_cons_1.color = yellow + + ff_cons_2 = sim.add_volume("ConsVolume", f"{linac_name}_flattening_filter_cons_2") + ff_cons_2.rmin1 = 77 / 2 * mm + ff_cons_2.rmax1 = 79.99 / 2 * mm + ff_cons_2.rmin2 = 77 / 2 * mm + ff_cons_2.rmax2 = 79.99 / 2 * mm + ff_cons_2.dz = 3.5 * mm / 2.0 + ff_cons_2.sphi = 0 + ff_cons_2.dphi = 360 * deg + ff_cons_2.mother = flattening_filter.name + ff_cons_2.material = "flattening_filter_material_stain_steel" + ff_cons_2.translation = [0, 0, flattening_filter.dz - 16.85 * mm] + ff_cons_2.color = yellow + + ff_cons_3 = sim.add_volume("ConsVolume", f"{linac_name}_flattening_filter_cons_3") + ff_cons_3.rmin1 = 73 / 2 * mm + ff_cons_3.rmax1 = 77 / 2 * mm + ff_cons_3.rmin2 = 73 / 2 * mm + ff_cons_3.rmax2 = 77 / 2 * mm + ff_cons_3.dz = 22.1 * mm / 2.0 + ff_cons_3.sphi = 0 + ff_cons_3.dphi = 360 * deg + ff_cons_3.mother = flattening_filter.name + ff_cons_3.material = "flattening_filter_material_stain_steel" + ff_cons_3.translation = [0, 0, flattening_filter.dz - 11.05 * mm] + ff_cons_3.color = yellow + + ff_cons_4 = sim.add_volume("ConsVolume", f"{linac_name}_flattening_filter_cons_4") + ff_cons_4.rmin1 = 77 / 2 * mm + ff_cons_4.rmax1 = 79 / 2 * mm + ff_cons_4.rmin2 = 77 / 2 * mm + ff_cons_4.rmax2 = 79 / 2 * mm + ff_cons_4.dz = 15.1 * mm / 2.0 + ff_cons_4.sphi = 0 + ff_cons_4.dphi = 360 * deg + ff_cons_4.mother = flattening_filter.name + ff_cons_4.material = "flattening_filter_material_stain_steel" + ff_cons_4.translation = [0, 0, flattening_filter.dz - 7.55 * mm] + ff_cons_4.color = yellow + + ## cons 5 is for the moment an assumption of the secondary filter carrier design + ff_cons_5 = sim.add_volume("ConsVolume", f"{linac_name}_flattening_filter_cons_5") + ff_cons_5.rmin1 = 79.99 / 2 * mm + ff_cons_5.rmax1 = 106 / 2 * mm + ff_cons_5.rmin2 = 79.99 / 2 * mm + ff_cons_5.rmax2 = 106 / 2 * mm + ff_cons_5.dz = 5 * mm / 2.0 + ff_cons_5.sphi = 0 + ff_cons_5.dphi = 360 * deg + ff_cons_5.mother = flattening_filter.name + ff_cons_5.material = "flattening_filter_material_mild_steel" + ff_cons_5.translation = [0, 0, flattening_filter.dz - 16.1 * mm] + ff_cons_5.color = yellow + i = 0 for c in cones: cone = Box() @@ -224,7 +338,7 @@ def add_ionizing_chamber(sim, linac_name): ionizing_chamber.rmin = 0 ionizing_chamber.rmax = 45 * mm ionizing_chamber.dz = 9.28 * mm / 2 - ionizing_chamber.translation = [0, 0, z_linac / 2 - 169 * mm] + ionizing_chamber.translation = [0, 0, z_linac / 2 - 172.5 * mm] ionizing_chamber.color = [0, 0, 0, 0] # layers @@ -272,7 +386,7 @@ def add_back_scatter_plate(sim, linac_name): bsp.mother = linac_name bsp.material = "linac_aluminium" bsp.size = [116 * mm, 84 * mm, 3 * mm] - bsp.translation = [0, 0, z_linac / 2 - 183 * mm] + bsp.translation = [0, 0, z_linac / 2 - 187.5 * mm] bsp.color = [1, 0.7, 0.7, 0.8] @@ -288,7 +402,7 @@ def add_mirror(sim, linac_name): m.mother = linac_name m.material = "G4_AIR" m.size = [137 * mm, 137 * mm, 1.5 * mm] - m.translation = [0, 0, z_linac / 2 - 225 * mm] + m.translation = [0, 0, z_linac / 2 - 227.5 * mm] rot = Rotation.from_euler("x", 37.5, degrees=True) m.rotation = rot.as_matrix() @@ -311,35 +425,67 @@ def add_mirror(sim, linac_name): def enable_brem_splitting(sim, linac_name, splitting_factor): # create a region - linac = sim.volume_manager.get_volume(linac_name) - region_linac = sim.physics_manager.add_region(name=f"{linac.name}_region") - region_linac.associate_volume(linac) + linac_target = sim.volume_manager.get_volume(f"{linac_name}_target") + region_linac = sim.physics_manager.add_region(name=f"{linac_target.name}_region") + region_linac.associate_volume(linac_target) # set the brem splitting s = f"/process/em/setSecBiasing eBrem {region_linac.name} {splitting_factor} 50 MeV" sim.g4_commands_after_init.append(s) -def add_electron_source(sim, linac_name): +def add_electron_source(sim, linac_name, ekin, sx, sy): MeV = g4_units.MeV mm = g4_units.mm nm = g4_units.nm + deg = g4_units.deg source = sim.add_source("GenericSource", f"{linac_name}_e-_source") source.particle = "e-" source.mother = f"{linac_name}_target" source.energy.type = "gauss" - source.energy.mono = 6.4 * MeV - source.energy.sigma_gauss = source.energy.mono * (0.03 / 2.35) + source.energy.mono = ekin * MeV + source.energy.sigma_gauss = source.energy.mono * (0.08 / 2.35) source.position.type = "disc" - source.position.sigma_x = 0.468 * mm - source.position.sigma_y = 0.468 * mm + source.position.sigma_x = sx * mm + source.position.sigma_y = sy * mm source.position.translation = [0, 0, 0.5 * mm - 1 * nm] source.direction.type = "momentum" source.direction.momentum = [0, 0, -1] - source.n = 10 source.direction_relative_to_attached_volume = True + source.n = 10 return source +def set_cut_on_linac_head(sim, linac_name, particles, cuts): + ##FIXME Probably not adapted to the current geometry of the LINAC, need to be corrected + l_volume = [ + f"{linac_name}_mlc", + "jaw_box_right", + "jaw_box_left", + f"{linac_name}_primary_collimator", + f"{linac_name}_flattening_filter", + f"{linac_name}_back_scatter_plate", + f"{linac_name}_target_support_top", + f"{linac_name}_target_support_bottom", + ] + mm = g4_units.mm + reg = sim.physics_manager.add_region(name=f"{linac_name}_region") + for volume in l_volume: + reg.associate_volume(volume) + if type(particles) == str: + if particles == "all": + reg.production_cuts.gamma = cuts + reg.production_cuts.electron = cuts + reg.production_cuts.positron = cuts + else: + for i, particle in enumerate(particles): + if particle == "gamma": + reg.production_cuts.gamma = cuts[i] + elif particle == "electron": + reg.production_cuts.electron = cuts[i] + elif particle == "positron": + reg.production_cuts.positron = cuts[i] + + def add_phase_space_plane(sim, linac_name, src_phsp_distance): mm = g4_units.mm m = g4_units.m @@ -357,8 +503,11 @@ def add_phase_space_plane(sim, linac_name, src_phsp_distance): return plane -def add_phase_space(sim, plane_name): - phsp = sim.add_actor("PhaseSpaceActor", f"{plane_name}_phsp") +def add_phase_space(sim, plane_name, i=0): + if i == 0: + phsp = sim.add_actor("PhaseSpaceActor", f"{plane_name}_phsp") + else: + phsp = sim.add_actor("PhaseSpaceActor", f"{plane_name}_phsp_" + str(i)) phsp.attached_to = plane_name phsp.attributes = [ "KineticEnergy", @@ -377,16 +526,15 @@ def add_phase_space_source(sim, plane_name): source.mother = plane_name source.position_key = "PrePositionLocal" source.direction_key = "PreDirectionLocal" - source.weight_key = None + # source.weight_key = "Weight" source.global_flag = False source.particle = "" source.batch_size = 100000 # source.translate_position = True - # source.position.translation = [0 * m, 0 * m, -1000 * mm] return source -def bool_leaf_x_neg(pair, linac_name, count=1): +def mlc_leaf(linac_name): mm = g4_units.mm interleaf_gap = 0.09 * mm leaf_length = 155 * mm @@ -394,123 +542,20 @@ def bool_leaf_x_neg(pair, linac_name, count=1): leaf_mean_width = 1.76 * mm tongues_length = 0.8 * mm - cyl = volumes.TubsVolume(name=f"{linac_name}_cylinder_leaf_" + str(count)) + cyl = volumes.TubsVolume(name=f"{linac_name}_cylinder_leaf") cyl.rmin = 0 cyl.rmax = 170 * mm - box_rot_leaf = volumes.BoxVolume(name=f"{linac_name}_Box_leaf_" + str(count)) + box_rot_leaf = volumes.BoxVolume(name=f"{linac_name}_Box_leaf") box_rot_leaf.size = [200 * mm, leaf_length, leaf_height] - trap_leaf = volumes.TrapVolume(name=f"{linac_name}_trap_leaf_" + str(count)) + trap_leaf = volumes.TrapVolume(name=f"{linac_name}_trap_leaf") dz = leaf_height / 2 dy1 = leaf_length / 2 - if pair: - dx1 = 1.94 * mm / 2 - dx3 = 1.58 * mm / 2 - theta = np.arctan((dx3 - dx1) / (2 * dz)) - else: - dx1 = 1.58 * mm / 2 - dx3 = 1.94 * mm / 2 - theta = np.arctan((dx1 - dx3) / (2 * dz)) - alpha1 = 0 - alpha2 = alpha1 - phi = 0 - dy2 = dy1 - dx2 = dx1 - dx4 = dx3 + dx1 = 1.94 * mm / 2 + dx3 = 1.58 * mm / 2 + theta = 0 - trap_leaf.dx1 = dx1 - trap_leaf.dx2 = dx2 - trap_leaf.dx3 = dx3 - trap_leaf.dx4 = dx4 - trap_leaf.dy1 = dy1 - trap_leaf.dy2 = dy2 - trap_leaf.dz = dz - trap_leaf.alp1 = alpha1 - trap_leaf.alp2 = alpha2 - trap_leaf.theta = theta - trap_leaf.phi = phi - - rot_leaf = Rotation.from_euler("Z", -90, degrees=True).as_matrix() - rot_cyl = Rotation.from_euler("X", 90, degrees=True).as_matrix() - - if pair: - trap_tongue = volumes.TrapVolume( - name=f"{linac_name}_trap_tongue_p_" + str(count) - ) - else: - trap_tongue = volumes.TrapVolume( - name=f"{linac_name}_trap_tongue_o_" + str(count) - ) - dz = tongues_length / 2 - dy1 = leaf_length / 2 - dx1 = interleaf_gap / 2 - dx3 = dx1 - alpha1 = 0 - alpha2 = alpha1 - if pair: - theta = np.arctan((1.58 * mm - 1.94 * mm) / leaf_height) - else: - theta = 0 - phi = 0 - dy2 = dy1 - dx2 = dx1 - dx4 = dx1 - - trap_tongue.dx1 = dx1 - trap_tongue.dx2 = dx2 - trap_tongue.dx3 = dx3 - trap_tongue.dx4 = dx4 - trap_tongue.dy1 = dy1 - trap_tongue.dy2 = dy2 - trap_tongue.dz = dz - trap_tongue.alp1 = alpha1 - trap_tongue.alp2 = alpha2 - trap_tongue.theta = theta - trap_tongue.phi = phi - - bool_leaf = intersect_volumes(box_rot_leaf, trap_leaf, [0, 0, 0], rot_leaf) - bool_tongue = intersect_volumes(box_rot_leaf, trap_tongue, [0, 0, 0], rot_leaf) - bool_leaf = unite_volumes( - bool_leaf, bool_tongue, [0 * mm, (leaf_mean_width + interleaf_gap) / 2, 0 * mm] - ) - # bool_leaf = unite_volumes(trap_leaf, trap_tongue, [(leaf_mean_width + interleaf_gap) / 2,0 * mm, 0 * mm]) - bool_leaf = intersect_volumes(bool_leaf, cyl, [-92.5 * mm, 0, 7.5 * mm], rot_cyl) - - # leaf = sim.volume_manager.add_volume(bool_leaf,'leaf') - # # leaf.rotation = rot_leaf - # a = sim.add_volume("Box",'test') - # a.size = [2*mm,2*mm,2*mm] - - return bool_leaf - - -def bool_leaf_x_pos(pair, linac_name, count=1): - mm = g4_units.mm - interleaf_gap = 0.09 * mm - leaf_length = 155 * mm - leaf_height = 90 * mm - leaf_mean_width = 1.76 * mm - tongues_length = 0.8 * mm - - cyl = volumes.TubsVolume(name=f"{linac_name}_cylinder_leaf_" + str(count)) - cyl.rmin = 0 - cyl.rmax = 170 * mm - - box_rot_leaf = volumes.BoxVolume(name=f"{linac_name}_Box_leaf_" + str(count)) - box_rot_leaf.size = [200 * mm, leaf_length, leaf_height] - - trap_leaf = volumes.TrapVolume(name=f"{linac_name}_trap_leaf_" + str(count)) - dz = leaf_height / 2 - dy1 = leaf_length / 2 - if pair: - dx1 = 1.94 * mm / 2 - dx3 = 1.58 * mm / 2 - theta = np.arctan((dx3 - dx1) / (2 * dz)) - else: - dx1 = 1.58 * mm / 2 - dx3 = 1.94 * mm / 2 - theta = np.arctan((dx1 - dx3) / (2 * dz)) alpha1 = 0 alpha2 = alpha1 phi = 0 @@ -533,24 +578,16 @@ def bool_leaf_x_pos(pair, linac_name, count=1): rot_leaf = Rotation.from_euler("Z", -90, degrees=True).as_matrix() rot_cyl = Rotation.from_euler("X", 90, degrees=True).as_matrix() - if pair: - trap_tongue = volumes.TrapVolume( - name=f"{linac_name}_trap_tongue_p_" + str(count) - ) - else: - trap_tongue = volumes.TrapVolume( - name=f"{linac_name}_trap_tongue_o_" + str(count) - ) + trap_tongue = volumes.TrapVolume(name=f"{linac_name}_trap_tongue") dz = tongues_length / 2 dy1 = leaf_length / 2 - dx1 = interleaf_gap / 2 + + ##FIXME I need to remove 2 um to the tongues to avoid an overleap between leaves + dx1 = (interleaf_gap - 2.2 * 10 ** (-3) * mm) / 2 dx3 = dx1 alpha1 = 0 alpha2 = alpha1 - if pair: - theta = np.arctan((1.58 * mm - 1.94 * mm) / leaf_height) - else: - theta = 0 + theta = np.arctan((1.58 * mm - 1.94 * mm) * 0.5 / leaf_height) phi = 0 dy2 = dy1 dx2 = dx1 @@ -579,120 +616,106 @@ def bool_leaf_x_pos(pair, linac_name, count=1): def add_mlc(sim, linac_name): + mm = g4_units.mm + cm = g4_units.cm linac = sim.volume_manager.get_volume(linac_name) + leaf_height = 90 * mm z_linac = linac.size[2] - center_mlc = 349.3 * mm + center_mlc = 349.3 * mm + 3.5 * mm interleaf_gap = 0.09 * mm leaf_width = 1.76 * mm leaf_lenght = 155 * mm nb_leaf = 160 + rotation_angle = np.arctan((1.94 * mm - 1.58 * mm) * 0.5 / leaf_height) + + mlc = sim.add_volume("Box", f"{linac_name}_mlc") + mlc_bank_rotation = Rotation.from_euler( + "X", np.arctan(3.25 / 349.3), degrees=False + ).as_matrix() + mlc.rotation = mlc_bank_rotation + mlc.size = [linac.size[0] - 2 * cm, linac.size[1] - 2 * cm, 100 * mm] + mlc.translation = np.array([0, 0, z_linac / 2 - center_mlc]) + mlc.mother = linac_name + mlc.color = [0, 0, 0, 0] + + leaf_color = [0.92, 0.61, 0.32, 0.4] + leaf = mlc_leaf(linac_name) + leaf.name = f"{linac_name}_leaf" + sim.volume_manager.add_volume(leaf, f"{linac_name}_leaf") + leaf.material = "mat_leaf" + leaf.mother = mlc + leaf.color = leaf_color + + size = [2, int(0.5 * nb_leaf), 1] + tr_blocks = np.array([leaf_lenght, leaf_width + interleaf_gap, 0]) + leaves_pos = np.array(get_grid_repetition(size, tr_blocks)) + l_rotation = [] + l_center_translation = [] + + for i in range(len(leaves_pos)): + if i <= int(nb_leaf / 4) - 1: + angle = 2 * rotation_angle * (i - int(nb_leaf / 4)) + rotation_angle + elif int(nb_leaf / 4) - 1 < i <= 2 * int(nb_leaf / 4) - 1: + angle = 2 * rotation_angle * (i - int(nb_leaf / 4) + 1) - rotation_angle + elif 2 * int(nb_leaf / 4) - 1 < i <= 3 * int(nb_leaf / 4) - 1: + angle = 2 * rotation_angle * (i - 3 * int(nb_leaf / 4)) + rotation_angle + elif 3 * int(nb_leaf / 4) - 1 < i: + angle = 2 * rotation_angle * (i - 3 * int(nb_leaf / 4) + 1) - rotation_angle + + cos_angle = np.cos(np.abs(angle)) + sin_angle = np.sin(np.abs(angle)) + tan_angle_pos = np.tan(np.pi / 2 - rotation_angle - np.abs(angle)) + tan_angle_neg = np.tan(np.pi / 2 - rotation_angle + np.abs(angle)) + + left = (leaf_width / 2) * (cos_angle + sin_angle / tan_angle_pos) + right = (leaf_width / 2) * (cos_angle - sin_angle / tan_angle_neg) + + if angle < 0: + l_center_translation.append([left, right]) + else: + l_center_translation.append([right, left]) - leaf_p_1 = bool_leaf_x_neg(True, linac_name) - leaf_o_1 = bool_leaf_x_neg(False, linac_name) - leaf_p_2 = bool_leaf_x_pos(True, linac_name, count=2) - leaf_o_2 = bool_leaf_x_pos(False, linac_name, count=2) - - sim.volume_manager.add_volume(leaf_p_1, f"{linac_name}_leaf_p_1") - leaf_p_1.material = "mat_leaf" - leaf_p_1.mother = linac_name - leaf_p_1.color = [1, 0.2, 0.6, 0.7] - - sim.volume_manager.add_volume(leaf_o_1, f"{linac_name}_leaf_o_1") - leaf_o_1.material = "mat_leaf" - leaf_o_1.mother = linac_name - leaf_o_1.color = [1, 0.2, 0.6, 0.7] - - sim.volume_manager.add_volume(leaf_p_2, f"{linac_name}_leaf_p_2") - leaf_p_2.material = "mat_leaf" - leaf_p_2.mother = linac_name - leaf_p_2.color = [1, 0.2, 0.6, 0.7] - - sim.volume_manager.add_volume(leaf_o_2, f"{linac_name}_leaf_o_2") - leaf_o_2.material = "mat_leaf" - leaf_o_2.mother = linac_name - leaf_o_2.color = [1, 0.2, 0.6, 0.7] - - size = [1, int(0.25 * nb_leaf), 1] - tr_blocks = np.array([leaf_lenght, 2 * leaf_width + 2 * interleaf_gap, 0]) - - mlc_p_1 = get_grid_repetition(size, tr_blocks) - mlc_o_1 = get_grid_repetition(size, tr_blocks) - mlc_p_2 = get_grid_repetition(size, tr_blocks) - mlc_o_2 = get_grid_repetition(size, tr_blocks) - - for i in range(len(mlc_p_1)): - mlc_p_1[i] += np.array( - [ - -leaf_lenght / 2, - leaf_width + interleaf_gap - (leaf_width + interleaf_gap) / 2, - z_linac / 2 - center_mlc, - ] - ) - mlc_o_1[i] += np.array( - [ - -leaf_lenght / 2, - -(leaf_width + interleaf_gap) / 2, - z_linac / 2 - center_mlc, - ] - ) - mlc_p_2[i] += np.array( - [ - leaf_lenght / 2, - leaf_width + interleaf_gap - (leaf_width + interleaf_gap) / 2, - z_linac / 2 - center_mlc, - ] - ) - mlc_o_2[i] += np.array( - [ - leaf_lenght / 2, - -(leaf_width + interleaf_gap) / 2, - z_linac / 2 - center_mlc, - ] - ) + if leaves_pos[i, 0] > 0: + rot = Rotation.from_euler("X", angle, degrees=False).as_matrix() + l_rotation.append(rot) + else: + rot = Rotation.from_euler("XZ", [angle, np.pi], degrees=False).as_matrix() + l_rotation.append(rot) - leaf_p_1.translation = mlc_p_1 - leaf_o_1.translation = mlc_o_1 - leaf_p_2.translation = mlc_p_2 - leaf_o_2.translation = mlc_o_2 - - mlc = [] - for i in range(len(mlc_p_1)): - mlc.append( - { - "translation": mlc_o_1[i], - "mother_name": leaf_o_1.name, - "name": leaf_o_1.name + "_rep_" + str(i), - "leaf_index": i, - } - ) - mlc.append( - { - "translation": mlc_p_1[i], - "mother_name": leaf_p_1.name, - "name": leaf_p_1.name + "_rep_" + str(i), - "leaf_index": i, - } - ) - for i in range(len(mlc_p_2)): - mlc.append( - { - "translation": mlc_o_2[i], - "mother_name": leaf_o_2.name, - "name": leaf_o_2.name + "_rep_" + str(i), - "leaf_index": i, - } - ) - mlc.append( - { - "translation": mlc_p_2[i], - "mother_name": leaf_p_2.name, - "name": leaf_p_2.name + "_rep_" + str(i), - "leaf_index": i, - } + translation_to_apply = [] + l_center_translation = l_center_translation[: int(len(l_center_translation) / 2)] + + for i in range(len(l_center_translation)): + if i != 0: + translation_to_apply.append( + l_center_translation[i - 1][1] + l_center_translation[i][0] + ) + + translation_to_apply = np.array(translation_to_apply) - leaf_width + translation_to_apply = translation_to_apply[ + : int(len(translation_to_apply) / 2) + 1 + ] + translation_to_apply[-1] = translation_to_apply[-1] / 2 + final_translation_to_apply = [] + for j in range(len(translation_to_apply)): + final_translation_to_apply.append(np.sum(translation_to_apply[j:])) + + final_translation_to_apply = np.array( + ( + ( + (-np.array(final_translation_to_apply)).tolist() + + final_translation_to_apply[::-1] + ) + * 2 ) + ) + for i in range(len(leaves_pos)): + leaves_pos[i, 1] += final_translation_to_apply[i] + leaf.translation = leaves_pos + leaf.rotation = np.array(l_rotation) - return mlc + return leaf def trap_g4_param( @@ -715,299 +738,776 @@ def add_jaws(sim, linac_name): return [add_jaw(sim, linac_name, "left"), add_jaw(sim, linac_name, "right")] -def add_jaws_visu(sim, linac_name): - return [ - add_jaw_visu(sim, linac_name, "left"), - add_jaw_visu(sim, linac_name, "right"), +def add_jaw(sim, linac_name, side): + nm = g4_units.nm + mm = g4_units.mm + um = g4_units.um + cm = g4_units.cm + jaw_x = 210 * mm + jaw_y = 220 * mm + jaw_z = 77 * mm + linac = sim.volume_manager.get_volume(linac_name) + center_jaw_z = 470.5 * mm + + ## Traps definition for three trapzoid composing the front f&ace of the jaw in the order of G4 paramaters : dx1,dx2,dx3,dx4,dy1,dy2,dz + traps_y_dimensions = [ + [32 * mm, 32 * mm, 32 * mm], + [194 * mm, 200 * mm, 210 * mm], + [32 * mm, 32 * mm, 32 * mm], + [194 * mm, 200 * mm, 210 * mm], + [84.822 * mm, 87.964 * mm, 93.2 * mm], + [84.822 * mm, 87.964 * mm, 93.2 * mm], + [10 * mm, 29 * mm, 16 * mm], ] - -def add_base_jaws(sim, linac_name, side): - mm = g4_units.mm - jaws_height = 77 * mm - jaws_length_x = 201.84 * mm - jaws_length_tot_X = 229.58 * mm - jaws_length_y = 205.2 * mm - - # Jaws Structure - box_jaws = volumes.BoxVolume(name=f"{linac_name}_box_jaws" + "_" + side) - box_jaws.size = np.array([jaws_length_x, jaws_length_y, jaws_height]) - box_to_remove = volumes.BoxVolume(name=f"{linac_name}_box_to_remove" + "_" + side) - box_to_remove.size = np.array( + traps_y_positions = [ + [0, 0, 0], [ - jaws_length_x + 1 * mm, - jaws_length_y - 17.83 * mm + 1 * mm, - jaws_height - 21.64 * mm + 1 * mm, - ] - ) - bool_box_jaws = subtract_volumes( - box_jaws, - box_to_remove, - [0, -(17.83) / 2 * mm - 1 / 2 * mm, (-21.64) / 2 * mm - 1 / 2 * mm], - ) - - # Jaws fine sub-structure : Box + Traps - box_to_add = volumes.BoxVolume(name=f"{linac_name}_box_to_add" + "_" + side) - box_to_add.size = np.array( - [35.63 * mm, 104.61 * mm - 27.95 * mm, jaws_height - 21.64 * mm] - ) - trap_jaws = volumes.TrapVolume(name=f"{linac_name}_trap_jaws" + "_" + side) - trap_g4_param( - trap_jaws, - 18.44 * mm / 2, - 18.44 * mm / 2, - 18.44 * mm / 2, - 18.44 * mm / 2, - 35.63 * mm / 2, - jaws_length_tot_X / 2, - (jaws_length_y - 17.83 * mm - 104.61 * mm) / 2, - ) - rot_trap_jaws = Rotation.from_euler("YZ", [90, 90], degrees=True).as_matrix() - - trap_jaws_2 = volumes.TrapVolume(name=f"{linac_name}_trap_jaws_" + "_" + side) - trap_g4_param( - trap_jaws_2, - 29.93 * mm / 2, - 29.93 * mm / 2, - 29.93 * mm / 2, - 29.93 * mm / 2, - 35.63 * mm / 2, - (jaws_length_x + 4.91 * 2 * mm) / 2, - (jaws_length_y - 17.83 * mm - 104.61 * mm - 7.65 * mm) / 2, - ) - box_trap_2 = volumes.BoxVolume(name=f"{linac_name}_box_trap_2" + "_" + side) - box_trap_2.size = [jaws_length_x + 4.92 * mm * 2, 7.65 * mm, 29.93 * mm] - trap_jaws_3 = volumes.TrapVolume(name=f"{linac_name}_trap_jaws_3" + "_" + side) - trap_g4_param( - trap_jaws_3, - (jaws_height - 21.64 * mm - 18.44 * mm - 29.93 * mm) / 2, - (jaws_height - 21.64 * mm - 18.44 * mm - 29.93 * mm) / 2, - (jaws_height - 21.64 * mm - 18.44 * mm - 29.93 * mm) / 2, - (jaws_height - 21.64 * mm - 18.44 * mm - 29.93 * mm) / 2, - 35.63 * mm / 2, - jaws_length_x / 2, - (jaws_length_y - 17.83 * mm - 104.61 * mm - 11.84 * mm) / 2, - ) - box_trap_3 = volumes.BoxVolume(name=f"{linac_name}_box_trap_3" + "_" + side) - box_trap_3.size = [ - jaws_length_x, - 11.84 * mm, - (jaws_height - 18.44 * mm - 29.93 * mm - 21.64 * mm), + jaw_y / 2 - 16.3 * mm - (93.2 - 84.822) * mm - (84.822) / 2 * mm, + jaw_y / 2 - 16.3 * mm - (93.2 - 87.964) * mm - (87.964) / 2 * mm, + jaw_y / 2 - 16.3 * mm - 93.2 / 2 * mm, + ], + [ + jaw_z / 2 - 22 * mm - 10 * mm / 2, + jaw_z / 2 - 32 * mm - 29 * mm / 2, + jaw_z / 2 - 32 * mm - 29 * mm - 16 * mm / 2, + ], ] - bool_box_jaws = unite_volumes( - bool_box_jaws, - box_to_add, + boxes_to_complete_traps_y_dimensions = [ + [194 * mm, 200 * mm, 210 * mm], + [(109.5 - 84.822) * mm, (109.5 - 87.964) * mm, 16.3 * mm], + [10 * mm, 29 * mm, 16 * mm], + ] + boxes_to_complete_traps_y_positions = [ + [0, 0, 0], [ - 0, - -jaws_length_y / 2 + 27.95 * mm + 0.5 * (104.61 * mm - 27.95 * mm), - -21.64 / 2 * mm, + jaw_y / 2 - (109.5 - 84.822) / 2 * mm, + jaw_y / 2 - (109.5 - 87.964) / 2 * mm, + jaw_y / 2 - 16.3 / 2 * mm, ], - ) - bool_box_jaws = unite_volumes( - bool_box_jaws, - trap_jaws, [ - 0, - -jaws_length_y / 2 - + 104.61 * mm - + (jaws_length_y - 17.83 * mm - 104.61 * mm) / 2, - -jaws_height / 2 + 18.44 * mm / 2, + jaw_z / 2 - 22 * mm - 10 * mm / 2, + jaw_z / 2 - 32 * mm - 29 * mm / 2, + jaw_z / 2 - 32 * mm - 29 * mm - 16 * mm / 2, ], - rot_trap_jaws, + ] + middle_trap_dimension = [ + [32 * mm, 32 * mm], + [73.2 * mm, 73.2 * mm], + [45 * mm, 45 * mm], + ] + middle_trap_position = [ + 0, + jaw_y / 2 - 109.5 * mm - 73.2 * mm / 2, + jaw_z / 2 - 32 * mm - 45 * mm / 2, + ] + + jaw_box = sim.volume_manager.add_volume("Box", "jaw_box_" + side) + jaw_box.mother = linac_name + jaw_box.size = np.array([jaw_x, jaw_y, jaw_z]) + jaw_box.translation = [0, 0, linac.size[2] / 2 - center_jaw_z - 3.5 * mm] + jaw_box.color = [0, 0, 0, 0] + + middle_trap = sim.volume_manager.add_volume("Trap", "middle_trap_" + side) + trap_g4_param( + middle_trap, + middle_trap_dimension[0][1] / 2, + middle_trap_dimension[0][1] / 2, + middle_trap_dimension[0][0] / 2, + middle_trap_dimension[0][0] / 2, + middle_trap_dimension[1][0] / 2, + middle_trap_dimension[1][1] / 2, + middle_trap_dimension[2][0] / 2, ) - bool_box_jaws = unite_volumes( - bool_box_jaws, - trap_jaws_2, + middle_trap.translation = middle_trap_position + middle_trap.color = [0.8, 0.4, 0.65, 0.6] + middle_trap.mother = jaw_box.name + middle_trap.material = "mat_leaf" + + for i in range(1, len(traps_y_positions)): + traps = sim.volume_manager.add_volume("Trap", "trap_" + side + "_" + str(i)) + trap_g4_param( + traps, + traps_y_dimensions[0][i] / 2, + traps_y_dimensions[1][i] / 2, + traps_y_dimensions[2][i] / 2, + traps_y_dimensions[3][i] / 2, + traps_y_dimensions[4][i] / 2, + traps_y_dimensions[5][i] / 2, + traps_y_dimensions[6][i] / 2, + ) + + traps.translation = [ + traps_y_positions[0][i], + traps_y_positions[1][i], + traps_y_positions[2][i], + ] + traps.mother = jaw_box.name + traps.material = "mat_leaf" + traps.color = [0.8, 0.4, 0.65, 0.6] + + # Jaws curve tips + cylindre = volumes.TubsVolume(name=f"{linac_name}_cyl_leaf" + "_" + side) + cylindre.rmin = 0 + cylindre.rmax = 135 * mm + cylindre.dz = jaw_x + rot_cyl = Rotation.from_euler("Y", 90, degrees=True).as_matrix() + + for i in range(3): + box = volumes.BoxVolume(name="Box_to_complete_traps_y_" + side + "_" + str(i)) + box.size = [ + boxes_to_complete_traps_y_dimensions[0][i], + boxes_to_complete_traps_y_dimensions[1][i], + boxes_to_complete_traps_y_dimensions[2][i], + ] + box.translation = [ + boxes_to_complete_traps_y_positions[0][i], + boxes_to_complete_traps_y_positions[1][i], + boxes_to_complete_traps_y_positions[2][i], + ] + box.mother = jaw_box.name + the_box = intersect_volumes( + box, + cylindre, + [ + 0, + boxes_to_complete_traps_y_dimensions[1][i] / 2 - 135 * mm, + jaw_z / 2 - 35 * mm - boxes_to_complete_traps_y_positions[2][i], + ], + rot_cyl, + ) + sim.volume_manager.add_volume(the_box, "curved_" + box.name) + the_box.mother = jaw_box.name + the_box.material = "mat_leaf" + the_box.color = [0.8, 0.4, 0.65, 0.6] + + top_jaw_box = volumes.BoxVolume(name="top_jaw_box_" + side) + top_jaw_box.size = [194 * mm, jaw_y, 22 * mm] + top_jaw_box.translation = [0, 0, jaw_z / 2 - 22 * mm / 2] + + top_jaw_box = intersect_volumes( + top_jaw_box, + cylindre, [ 0, - -jaws_length_y / 2 - + 104.61 * mm - + (jaws_length_y - 17.83 * mm - 104.61 * mm - 7.65 * mm) / 2, - -jaws_height / 2 + 18.44 * mm + 29.93 * mm / 2, + top_jaw_box.size[1] / 2 - 135 * mm, + jaw_z / 2 - 35 * mm - top_jaw_box.translation[2], ], - rot_trap_jaws, + rot_cyl, ) - bool_box_jaws = unite_volumes( - bool_box_jaws, - box_trap_2, + dimension_corner = np.array( [ - 0, - -jaws_length_y / 2 - + 104.61 * mm - + (jaws_length_y - 17.83 * mm - 104.61 * mm - 7.65 * mm) - + 7.65 / 2 * mm, - -jaws_height / 2 + 18.44 * mm + 29.93 * mm / 2, - ], + np.sqrt(22**2 + 6.9**2) * mm, + np.sqrt(22**2 + 6.9**2) * 6.9 / 22 * mm, + 22 * mm + 10 * um, + ] ) - bool_box_jaws = unite_volumes( - bool_box_jaws, - trap_jaws_3, + + for i in range(2): + corner_to_remove = volumes.BoxVolume( + name="corner_to_remove_" + side + "_" + str(i) + ) + corner_to_remove.size = dimension_corner + angle_for_corner = np.arctan(6.9 / 22) * 180 / np.pi + if i == 1: + angle_for_corner = -angle_for_corner + rot_for_corner = Rotation.from_euler("z", -angle_for_corner, degrees=True) + translation_to_apply = np.array( + [ + 194 * mm / 2 - corner_to_remove.size[0] / 2, + -jaw_y / 2 - corner_to_remove.size[1] / 2, + 0, + ] + ) + t = gate.geometry.utility.get_translation_from_rotation_with_center( + rot_for_corner, + [+corner_to_remove.size[0] / 2, -corner_to_remove.size[1] / 2, 0], + ) + if i == 1: + translation_to_apply[0] = -translation_to_apply[0] + t = gate.geometry.utility.get_translation_from_rotation_with_center( + rot_for_corner, + [-corner_to_remove.size[0] / 2, -corner_to_remove.size[1] / 2, 0], + ) + translation_to_apply += t + top_jaw_box = subtract_volumes( + top_jaw_box, + corner_to_remove, + translation_to_apply, + rot_for_corner.as_matrix(), + ) + sim.volume_manager.add_volume(top_jaw_box, "curved_" + top_jaw_box.name) + top_jaw_box.material = "mat_leaf" + top_jaw_box.mother = jaw_box.name + top_jaw_box.color = [0.8, 0.4, 0.65, 0.6] + + below_top_jaw_box = volumes.BoxVolume(name="below_top_jaw_box_" + side) + below_top_jaw_box.size = [154 * mm, jaw_y - (109.5 - 84.822) * mm, 10 * mm] + below_top_jaw_box_size = list(below_top_jaw_box.size) + below_top_jaw_box.translation = [ + 0, + jaw_y / 2 - (109.5 - 84.822) * mm - (jaw_y - (109.5 - 84.822)) / 2 * mm, + jaw_z / 2 - 22 * mm - 10 * mm / 2, + ] + dimension_corner = np.array( [ - 0, - -jaws_length_y / 2 - + 104.61 * mm - + (jaws_length_y - 17.83 * mm - 104.61 * mm - 11.84 * mm) / 2, - -jaws_height / 2 - + 18.44 * mm - + 29.93 * mm - + 0.5 * (jaws_height - 18.44 * mm - 29.93 * mm - 21.64 * mm), - ], - rot_trap_jaws, + np.sqrt(22**2 + 6.9**2) * mm, + np.sqrt(22**2 + 6.9**2) * 6.9 / 22 * mm, + 10 * mm + 10 * um, + ] ) - bool_box_jaws = unite_volumes( - bool_box_jaws, - box_trap_3, + for i in range(2): + corner_to_remove = volumes.BoxVolume( + name="corner_to_remove_" + side + "_" + str(i + 2) + ) + corner_to_remove.size = dimension_corner + angle_for_corner = np.arctan(6.9 / 22) * 180 / np.pi + if i == 1: + angle_for_corner = -angle_for_corner + rot_for_corner = Rotation.from_euler("z", -angle_for_corner, degrees=True) + translation_to_apply = np.array( + [ + 194 * mm / 2 - corner_to_remove.size[0] / 2, + -(jaw_y - (109.5 - 84.822) * mm) / 2 - corner_to_remove.size[1] / 2, + 0, + ] + ) + t = gate.geometry.utility.get_translation_from_rotation_with_center( + rot_for_corner, + [+corner_to_remove.size[0] / 2, -corner_to_remove.size[1] / 2, 0], + ) + if i == 1: + translation_to_apply[0] = -translation_to_apply[0] + t = gate.geometry.utility.get_translation_from_rotation_with_center( + rot_for_corner, + [-corner_to_remove.size[0] / 2, -corner_to_remove.size[1] / 2, 0], + ) + translation_to_apply += t + below_top_jaw_box = subtract_volumes( + below_top_jaw_box, + corner_to_remove, + translation_to_apply, + rot_for_corner.as_matrix(), + ) + left_edge_box_to_remove = volumes.BoxVolume(name="left_edge_box_to_remove_" + side) + left_edge_box_to_remove.size = [12.05 * mm, 59 * mm, 10 * mm + 10 * um] + size_box = left_edge_box_to_remove.size + # [- 154 * mm / 2 + size_box[0] / 2, below_top_jaw_box_size[1] / 2 - 89 * mm - size_box[1] / 2, + # jaw_z / 2 - 22 * mm - 10 * mm / 2] + below_top_jaw_box = subtract_volumes( + below_top_jaw_box, + left_edge_box_to_remove, [ + -154 * mm / 2 + size_box[0] / 2, + -below_top_jaw_box_size[1] / 2 + 80 * mm + size_box[1] / 2, 0, - -jaws_length_y / 2 - + 104.61 * mm - + (jaws_length_y - 17.83 * mm - 104.61 * mm - 11.84 * mm) - + 11.84 / 2 * mm, - -jaws_height / 2 - + 18.44 * mm - + 29.93 * mm - + 0.5 * (jaws_height - 18.44 * mm - 29.93 * mm - 21.64 * mm), ], + np.identity(3), + ) + sim.volume_manager.add_volume( + below_top_jaw_box, "truncated_" + below_top_jaw_box.name ) + below_top_jaw_box.material = "mat_leaf" + below_top_jaw_box.mother = jaw_box.name + below_top_jaw_box.color = [0.8, 0.4, 0.65, 0.6] - return bool_box_jaws + for i in range(2): + trap_to_complete_jaw = sim.volume_manager.add_volume( + "Trap", "trap_to_complete_jaw_" + side + "_" + str(i) + ) + alpha = np.arctan(0.5 * 20 / 20.8775) + if i == 1: + alpha = -alpha + trap_g4_param( + trap_to_complete_jaw, + 1 * nm / 2, + 20 * mm / 2, + 1 * nm / 2, + 20 * mm / 2, + 20.8775 / 2 * mm, + 20.8775 / 2 * mm, + 10 * mm / 2, + alpha1=alpha, + alpha2=alpha, + ) + trap_to_complete_jaw.color = [0.8, 0.4, 0.65, 0.6] + trap_to_complete_jaw.mother = jaw_box.name + if i == 0: + trap_to_complete_jaw.translation = [ + jaw_x / 2 - (210 - 194) / 2 * mm - 20 / 2 * mm - 10 / 2 * mm + 1 * nm, + jaw_y / 2 - (109.5 - 84.822) * mm - 20.8775 / 2 * mm, + jaw_z / 2 - 22 * mm - 10 * mm / 2, + ] + if i == 1: + trap_to_complete_jaw.translation = [ + -( + jaw_x / 2 + - (210 - 194) / 2 * mm + - 20 / 2 * mm + - 10 / 2 * mm + + 1 * nm + ), + jaw_y / 2 - (109.5 - 84.822) * mm - 20.8775 / 2 * mm, + jaw_z / 2 - 22 * mm - 10 * mm / 2, + ] + trap_to_complete_jaw.color = [0.8, 0.4, 0.65, 0.6] + trap_to_complete_jaw.material = "mat_leaf" -def add_jaw_visu(sim, linac_name, side): - mm = g4_units.mm - linac = sim.volume_manager.get_volume(linac_name) - center_jaws = 470.5 * mm - jaws_length_y = 205.2 * mm - z_linac = linac.size[2] + for i in range(2): + traps_edge_jaw = sim.volume_manager.add_volume( + "Trap", "traps_edge_jaw_" + side + "_" + str(i) + ) + alpha = -np.arctan(0.5 * 6.324 / 10.64) + if i == 1: + alpha = -alpha + + # alpha = 0 + trap_g4_param( + traps_edge_jaw, + (1 * nm) / 2, + (57.5 * mm) / 2, + (1 * nm) / 2, + (57.5 * mm) / 2, + 21.28 * mm / 2 * mm, + 21.28 * mm / 2 * mm, + 15 * mm / 2, + alpha1=alpha, + alpha2=alpha, + ) + traps_edge_jaw.color = [0.8, 0.4, 0.65, 0.6] + traps_edge_jaw.mother = jaw_box.name + traps_edge_jaw.translation = np.array( + [ + jaw_x / 2 + - (210 - 194) / 2 * mm + - 20 * mm + - 0.5 * 21.28 * mm * np.tan(alpha) + - 57.5 * mm / 2, + jaw_y / 2 - (109.5 - 84.822) * mm - 20.8775 * mm - 21.28 * mm / 2, + jaw_z / 2 - 32 * mm - 15 * mm / 2, + ] + ) + if i == 1: + traps_edge_jaw.translation[0] = ( + -jaw_x / 2 + + (210 - 194) / 2 * mm + + 20 * mm + - 0.5 * 21.28 * mm * np.tan(alpha) + + 57.5 * mm / 2 + ) + rot_angle = np.arctan(87.964 / ((200 - 32) / 2)) + if i == 1: + rot_angle = -rot_angle + rot = Rotation.from_euler("z", rot_angle * 180 / np.pi, degrees=True) + rot_inversion = Rotation.from_euler( + "xz", [180, -180 + rot_angle * 180 / np.pi], degrees=True + ) + vec_translation_for_rotation = np.array( + [57.5 * mm / 2 + 0.5 * 21.28 * mm * np.tan(alpha), 21.28 * mm / 2, 0] + ) + if i == 1: + vec_translation_for_rotation[0] = ( + -57.5 * mm / 2 + 0.5 * 21.28 * mm * np.tan(alpha) + ) + t = gate.geometry.utility.get_translation_from_rotation_with_center( + rot, vec_translation_for_rotation + ) + traps_edge_jaw.translation += t + translation_for_overlap_correction = np.array([32.19 * um, -32.19 * um, 0]) + if i == 1: + translation_for_overlap_correction[0] = -translation_for_overlap_correction[ + 0 + ] + traps_edge_jaw.translation += np.array(translation_for_overlap_correction) + if i == 0: + traps_edge_jaw.rotation = rot.as_matrix() + else: + traps_edge_jaw.rotation = rot_inversion.as_matrix() + traps_edge_jaw.material = "mat_leaf" - bool_box_jaw = add_base_jaws(sim, linac_name, side) - sim.volume_manager.add_volume(bool_box_jaw, "jaws" + "_" + side) - bool_box_jaw.mother = linac_name - bool_box_jaw.material = "mat_leaf" + band_box_to_add_left_side = sim.volume_manager.add_volume( + "Box", "band_box_to_add_left_side_" + side + ) + band_box_to_add_left_side.size = [20 * mm, 10 * mm, 10 * mm] + band_box_to_add_left_side.translation = [ + -194 * mm / 2 + band_box_to_add_left_side.size[0] / 2, + -jaw_y / 2 + 70 * mm + band_box_to_add_left_side.size[1] / 2, + jaw_z / 2 - 22 * mm - 10 * mm / 2, + ] + band_box_to_add_left_side.mother = jaw_box.name + band_box_to_add_left_side.color = [0.8, 0.4, 0.65, 0.6] + band_box_to_add_left_side.material = "mat_leaf" if side == "left": - bool_box_jaw.translation = np.array( - [0, -jaws_length_y / 2, z_linac / 2 - center_jaws] - ) + jaw_box.translation += np.array([0, -jaw_y / 2, 0]) if side == "right": rot_jaw = Rotation.from_euler("Z", 180, degrees=True).as_matrix() - bool_box_jaw.translation = np.array( - [0, jaws_length_y / 2, z_linac / 2 - center_jaws] - ) - bool_box_jaw.rotation = rot_jaw - return bool_box_jaw + jaw_box.translation += np.array([0, jaw_y / 2, 0]) + jaw_box.rotation = rot_jaw + return jaw_box -def add_jaw(sim, linac_name, side): - mm = g4_units.mm - linac = sim.volume_manager.get_volume(linac_name) - center_jaws = 470.5 * mm - jaws_height = 77 * mm - jaws_length_x = 201.84 * mm - jaws_length_tot_X = 229.58 * mm - jaws_length_y = 205.2 * mm - z_linac = linac.size[2] - bool_box_jaws = add_base_jaws(sim, linac_name, side) - # Correction of the front jaw shape - minibox_to_add = volumes.BoxVolume(name=f"{linac_name}_minibox_to_add" + "_" + side) - minibox_to_add.size = np.array( - [0.5 * (jaws_length_tot_X - jaws_length_x), 17.83 * mm, 18.44 * mm] - ) - minibox_to_add_2 = volumes.BoxVolume( - name=f"{linac_name}_minibox_to_add_2" + "_" + side - ) - minibox_to_add_2.size = np.array([4.91 * mm, 17.83 * mm, 29.93 * mm]) +def retrieve_offset_data_to_apply( + offset_type: object, collimation_type: object +) -> object: + mlc_offset_thales_to_radiation_field = [ + [ + -149.19, + -144.56, + -135.25, + -125.90, + -116.51, + -107.06, + -97.57, + -88.03, + -78.45, + -68.81, + -59.12, + -49.39, + -39.61, + -29.78, + -19.9, + -9.98, + 0, + 10.02, + 20.10, + 30.22, + 40.39, + 50.61, + 60.88, + 71.19, + 81.55, + 91.97, + 102.43, + 112.94, + 123.49, + 134.1, + 144.75, + 155.44, + 166.19, + 176.98, + 187.92, + 198.71, + 209.64, + ], + [ + -5.53, + -5.17, + -4.47, + -3.82, + -3.22, + -2.67, + -2.16, + -1.70, + -1.29, + -0.93, + -0.61, + -0.35, + -0.13, + 0.04, + 0.16, + 0.23, + 0.25, + 0.23, + 0.15, + 0.03, + -0.14, + -0.36, + -0.63, + -0.94, + -1.31, + -1.72, + -2.18, + -2.69, + -3.25, + -3.85, + -4.51, + -5.20, + -5.95, + -6.74, + -7.58, + -8.47, + -9.4, + ], + ] - rot_block_to_remove = volumes.BoxVolume( - name=f"{linac_name}_rot_block_to_remove" + "_" + side - ) - rot_block_to_remove.size = [ - 14.55 * np.sqrt(2) * mm, - 14.55 * np.sqrt(2) * mm, - 21.64 * mm + 1 * mm, + jaws_offset_thales_to_radiation_field = [ + [ + -127.57, + -117.93, + -108.26, + -98.56, + -88.83, + -79.08, + -69.29, + -59.48, + -49.64, + -39.77, + -29.87, + -19.94, + -9.99, + -0, + 10.01, + 20.06, + 30.13, + 40.23, + 50.36, + 60.52, + 70.71, + 80.92, + 91.17, + 101.44, + 111.74, + 122.07, + 132.43, + 142.82, + 153.23, + 163.68, + 174.15, + 184.64, + 195.17, + 205.72, + ], + [ + -2.18, + -1.82, + -1.49, + -1.19, + -0.92, + -0.68, + -0.46, + -0.28, + -0.12, + 0.01, + 0.11, + 0.18, + 0.23, + 0.24, + 0.22, + 0.18, + 0.11, + 0.01, + -0.13, + -0.28, + -0.47, + -0.69, + -0.93, + -1.21, + -1.51, + -1.84, + -2.20, + -2.59, + -3, + -3.44, + -3.91, + -4.41, + -4.94, + -5.49, + ], ] - rot_block = Rotation.from_euler("Z", 45, degrees=True).as_matrix() - bool_box_jaws = unite_volumes( - bool_box_jaws, - minibox_to_add, + mlc_offset_light_field_to_radiation_field = [ [ - (-jaws_length_x - 0.5 * (jaws_length_tot_X - jaws_length_x)) / 2, - (jaws_length_y - 17.83 * mm) / 2, - -jaws_height / 2 + 18.44 / 2 * mm, + -149.19, + -144.56, + -135.25, + -125.90, + -116.51, + -107.06, + -97.57, + -88.03, + -78.45, + -68.81, + -59.12, + -49.39, + -39.61, + -29.78, + -19.9, + -9.98, + 0, + 10.02, + 20.10, + 30.22, + 40.39, + 50.61, + 60.88, + 71.19, + 81.55, + 91.97, + 102.43, + 112.94, + 123.49, + 134.1, + 144.75, + 155.44, + 166.19, + 176.98, + 187.92, + 198.71, + 209.64, ], - ) - bool_box_jaws = unite_volumes( - bool_box_jaws, - minibox_to_add, [ - (jaws_length_x + 0.5 * (jaws_length_tot_X - jaws_length_x)) / 2, - (jaws_length_y - 17.83 * mm) / 2, - -jaws_height / 2 + 18.44 / 2 * mm, + 0.28, + 0.28, + 0.28, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, ], - ) - bool_box_jaws = unite_volumes( - bool_box_jaws, - minibox_to_add_2, + ] + + jaws_offset_light_field_to_radiation_field = [ [ - (-jaws_length_x - 4.91 * mm) / 2, - (jaws_length_y - 17.83 * mm) / 2, - -jaws_height / 2 + 18.44 * mm + 29.93 / 2 * mm, + -127.57, + -117.93, + -108.26, + -98.56, + -88.83, + -79.08, + -69.29, + -59.48, + -49.64, + -39.77, + -29.87, + -19.94, + -9.99, + -0, + 10.01, + 20.06, + 30.13, + 40.23, + 50.36, + 60.52, + 70.71, + 80.92, + 91.17, + 101.44, + 111.74, + 122.07, + 132.43, + 142.82, + 153.23, + 163.68, + 174.15, + 184.64, + 195.17, + 205.72, ], - ) - bool_box_jaws = unite_volumes( - bool_box_jaws, - minibox_to_add_2, [ - (jaws_length_x + 4.91 * mm) / 2, - (jaws_length_y - 17.83 * mm) / 2, - -jaws_height / 2 + 18.44 * mm + 29.93 / 2 * mm, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, ], - ) - bool_box_jaws = subtract_volumes( - bool_box_jaws, - rot_block_to_remove, - [-jaws_length_x / 2, -jaws_length_y / 2, jaws_height / 2 - 21.74 / 2 * mm], - rot_block, - ) - bool_box_jaws = subtract_volumes( - bool_box_jaws, - rot_block_to_remove, - [jaws_length_x / 2, -jaws_length_y / 2, jaws_height / 2 - 21.74 / 2 * mm], - rot_block, - ) + ] - # Jaws curve tips - cylindre = volumes.TubsVolume(name=f"{linac_name}_cyl_leaf" + "_" + side) - cylindre.rmin = 0 - cylindre.rmax = 135 * mm - cylindre.dz = jaws_length_tot_X - rot_cyl = Rotation.from_euler("Y", 90, degrees=True).as_matrix() - jaw = intersect_volumes( - bool_box_jaws, - cylindre, - [0, -(135 * mm - jaws_length_y / 2), -3.5 * mm], - rot_cyl, - ) + if collimation_type == "jaw": + if offset_type == "from thales": + return ( + jaws_offset_thales_to_radiation_field[0], + jaws_offset_thales_to_radiation_field[1], + ) + elif offset_type == "from light field": + return ( + jaws_offset_light_field_to_radiation_field[0], + jaws_offset_light_field_to_radiation_field[1], + ) - # Add final jaw volume - sim.volume_manager.add_volume(jaw, "jaws" + "_" + side) - jaw.mother = linac_name - jaw.material = "mat_leaf" + elif collimation_type == "mlc": + if offset_type == "from thales": + return ( + mlc_offset_thales_to_radiation_field[0], + mlc_offset_thales_to_radiation_field[1], + ) + elif offset_type == "from light field": + return ( + mlc_offset_light_field_to_radiation_field[0], + mlc_offset_thales_to_radiation_field[1], + ) - if side == "left": - jaw.translation = np.array([0, -jaws_length_y / 2, z_linac / 2 - center_jaws]) - if side == "right": - rot_jaw = Rotation.from_euler("Z", 180, degrees=True).as_matrix() - jaw.translation = np.array([0, jaws_length_y / 2, z_linac / 2 - center_jaws]) - jaw.rotation = rot_jaw - return jaw + +def define_pos_jaws_rectangular_field(y_field, sad=1000): + mm = g4_units.mm + center_jaws = 470.5 * mm + jaws_height = 77 * mm + center_curve_jaws = center_jaws - (jaws_height / 2 - 35 * mm) + + jaws_y_aperture = y_field / 2 * center_curve_jaws / sad + pos_y_jaws = np.array( + [-int(10 * jaws_y_aperture) / 10, int(10 * jaws_y_aperture) / 10] + ) + return pos_y_jaws -def define_pos_mlc_jaws_rectangular_field(x_field, y_field, sad=1000): +def define_pos_mlc_jaws_rectangular_field( + mlc, x_field, y_field, sad=1000, opposite_leaf_gap=0 +): mm = g4_units.mm + center_mlc = 349.3 * mm center_jaws = 470.5 * mm jaws_height = 77 * mm @@ -1015,65 +1515,98 @@ def define_pos_mlc_jaws_rectangular_field(x_field, y_field, sad=1000): leaf_width = 1.76 * mm + 0.09 * mm center_curve_jaws = center_jaws - (jaws_height / 2 - 35 * mm) - jaws_y_aperture = y_field / 2 * center_curve_jaws / sad - mlc_x_aperture = x_field / 2 * center_curve_mlc / sad - mlc_y_aperture = y_field * center_mlc / sad - nb_of_leaf_open = int(mlc_y_aperture / leaf_width) + 1 - if nb_of_leaf_open % 2 == 1: - nb_of_leaf_open += 1 + #### way to correct the thales correction at the isocenter of an offset to obtain the light field #### + #### Not used here since we directly have the offset to apply from thales to obtain the radiation field + # R_mlc = 170 * mm + # R_jaws = 135 * mm + # theta_x = np.arctan(0.5 * x_field / sad) + # theta_y = np.arctan(0.5 * y_field / sad) + + # mlc_x_aperture = (x_field / 2) * (center_curve_mlc + R_mlc * np.sin(theta_x)) / sad - R_mlc * (1 - np.cos(theta_x)) + # jaws_y_aperture = (y_field / 2) * (center_curve_jaws + R_jaws * np.sin(theta_y)) / sad - R_jaws * (1 - np.cos(theta_y)) + ############################################### + + mlc_position, mlc_offset = retrieve_offset_data_to_apply("from thales", "mlc") + jaw_position, jaw_offset = retrieve_offset_data_to_apply("from thales", "jaw") + + f_offset_mlc = scipy.interpolate.interp1d(mlc_position, mlc_offset, kind="cubic") + f_offset_jaw = scipy.interpolate.interp1d(jaw_position, jaw_offset, kind="cubic") + + mlc_x_aperture = (center_curve_mlc / sad) * ( + x_field / 2 - f_offset_mlc(x_field / 2) + ) + jaws_y_aperture = (center_curve_jaws / sad) * ( + y_field / 2 - f_offset_jaw(y_field / 2) + ) + + max_field_jaw = np.sqrt(110**2 - (jaws_height / 2) ** 2) + max_y_aperture = (110 * mm - max_field_jaw) + jaws_y_aperture + y_position_for_opening = max_y_aperture * 349.3 / (470.5 - (jaws_height / 2)) + + leaves_position_y = mlc.translation[:, 1] + leaves_ID = np.arange(160) + leaves_to_open = leaves_ID[ + (leaves_position_y > -(y_position_for_opening + leaf_width / 2)) + & (leaves_position_y < y_position_for_opening + leaf_width / 2) + ] + leaves_to_open_left = leaves_to_open[leaves_to_open < 80] + leaves_to_open_right = leaves_to_open[leaves_to_open >= 80] + + opposite_leaf_gap = opposite_leaf_gap * center_mlc / sad pos_x_leaves = np.zeros(160) - pos_x_leaves[0:80] -= 0.0 * mm - pos_x_leaves[80:160] += 0.0 * mm - pos_x_leaves[ - 39 - int(nb_of_leaf_open / 2) + 1 : 39 + int(nb_of_leaf_open / 2) + 1 - ] = -mlc_x_aperture - pos_x_leaves[ - 119 - int(nb_of_leaf_open / 2) + 1 : 119 + int(nb_of_leaf_open / 2) + 1 - ] = mlc_x_aperture + pos_x_leaves[0:80] -= opposite_leaf_gap / 2 + pos_x_leaves[80:160] += opposite_leaf_gap / 2 + pos_x_leaves[leaves_to_open_left] = -mlc_x_aperture + pos_x_leaves[leaves_to_open_right] = mlc_x_aperture pos_x_leaves = np.array(10 * pos_x_leaves, dtype=int) / 10 pos_y_jaws = np.array( [-int(10 * jaws_y_aperture) / 10, int(10 * jaws_y_aperture) / 10] ) - return pos_x_leaves, pos_y_jaws + return pos_x_leaves, pos_y_jaws -def field(sim, mlc, jaws, pos_x_leaves, pos_y_jaws): - for i, mlc_leaf in enumerate(mlc): - mother_leaf_name = mlc_leaf["mother_name"] - leaf_index = mlc_leaf["leaf_index"] - leaf = sim.volume_manager.get_volume(mother_leaf_name) - leaf.translation[leaf_index][0] += pos_x_leaves[i] +def field(mlc, jaws, pos_x_leaves, pos_y_jaws): + mlc.translation[:, 0] += pos_x_leaves for i, jaw in enumerate(jaws): jaw.translation[1] += pos_y_jaws[i] -def set_rectangular_field(sim, mlc, jaws, x_field, y_field, sad=1000): +def convert_degree_minuts_to_decimal_degree(degree, minuts): + decimal_degree = degree + 1 / 60 * minuts + return decimal_degree + + +def set_rectangular_field(mlc, jaws, x_field, y_field, sad=1000, opposite_leaf_gap=0): pos_x_leaves, pos_y_jaws = define_pos_mlc_jaws_rectangular_field( - x_field, y_field, sad + mlc, x_field, y_field, sad, opposite_leaf_gap ) - field(sim, mlc, jaws, pos_x_leaves, pos_y_jaws) + field(mlc, jaws, pos_x_leaves, pos_y_jaws) -def linac_rotation(sim, linac_name, angle, cp_id="all_cp"): + +def linac_rotation(sim, linac_name, angles, cp_id="all_cp"): + gantry_angle = angles[0] + collimation_angle = angles[1] linac = sim.volume_manager.get_volume(linac_name) rotations = [] translations = [] if cp_id == "all_cp": - nb_cp_id = len(angle) + nb_cp_id = len(gantry_angle) cp_id = np.arange(0, nb_cp_id, 1) translation_linac = linac.translation for n in cp_id: - rot = Rotation.from_euler("y", angle[n], degrees=True) + rot = Rotation.from_euler( + "YZ", [gantry_angle[n], collimation_angle[n]], degrees=True + ) t = gate.geometry.utility.get_translation_from_rotation_with_center( rot, [0, 0, -translation_linac[2]] ) rot = rot.as_matrix() rotations.append(rot) translations.append(np.array(t) + translation_linac) - v = sim.volume_manager.get_volume(linac_name) - v.add_dynamic_parametrisation(translation=translations, rotation=rotations) + linac.add_dynamic_parametrisation(translation=translations, rotation=rotations) def translation_from_sad(sim, linac_name, translation, sad=1000): @@ -1096,59 +1629,54 @@ def rotation_around_user_point( linac.rotation = rot.as_matrix() -def jaw_translation( - sim, linac_name, jaw, jaw_positions, side, cp_id="all_cp", sad=1000 -): - linac = sim.volume_manager.get_volume(linac_name) - z_linac = linac.size[2] +def jaw_dynamic_translation(jaw, jaw_positions, side, cp_id="all_cp", sad=1000): mm = g4_units.mm translations = [] rotations = [] - jaw_length_y = 205.2 * mm jaw_height = 77 * mm center_jaw = 470.5 * mm center_curve_jaw = center_jaw - (jaw_height / 2 - 35 * mm) - fact_iso = center_curve_jaw / sad rot_jaw = Rotation.from_euler("Z", 180, degrees=True).as_matrix() if cp_id == "all_cp": nb_cp_id = len(jaw_positions) cp_id = np.arange(0, nb_cp_id, 1) for n in cp_id: - jaw_tr = None + jaw_position, jaw_offset = retrieve_offset_data_to_apply("from thales", "jaw") + f_offset_jaw = scipy.interpolate.interp1d( + jaw_position, jaw_offset, kind="cubic" + ) if side == "left": - jaw_tr = np.array( - [ - 0, - -jaw_length_y / 2 + jaw_positions[n] * fact_iso, - 0.5 * z_linac - center_jaw, - ] - ) + jaw_pos = -jaw_positions[n] + if side == "right": + jaw_pos = jaw_positions[n] + jaws_y_aperture = (center_curve_jaw / sad) * (jaw_pos - f_offset_jaw(jaw_pos)) + if side == "left": + jaws_y_aperture = -jaws_y_aperture rotations.append(np.identity(3)) if side == "right": - jaw_tr = np.array( - [ - 0, - jaw_length_y / 2 + jaw_positions[n] * fact_iso, - 0.5 * z_linac - center_jaw, - ] - ) rotations.append(rot_jaw) - translations.append(jaw_tr) + jaw_translation = jaw.translation + np.array([0, jaws_y_aperture, 0]) + translations.append(jaw_translation) jaw.add_dynamic_parametrisation(translation=translations, rotation=rotations) -def mlc_leaves_translation( - sim, linac_name, mlc, leaves_position, cp_id="all_cp", sad=1000 +def mlc_leaves_dynamic_translation( + mlc, leaves_position, cp_id="all_cp", sad=1000, only_return_position=False ): - linac = sim.volume_manager.get_volume(linac_name) - z_linac = linac.size[2] mm = g4_units.mm center_mlc = 349.3 * mm center_curve_mlc = center_mlc - 7.5 * mm - fact_iso = center_curve_mlc / sad nb_leaves = 160 motion_leaves_t = [] motion_leaves_r = [] + mlc_leaves_name = [] + + mlc_position, mlc_offset = retrieve_offset_data_to_apply("from thales", "mlc") + f_offset_mlc = scipy.interpolate.interp1d(mlc_position, mlc_offset, kind="cubic") + + for i in range(nb_leaves): + mlc_leaves_name.append(mlc.name + "_rep_" + str(i)) + for i in range(nb_leaves): motion_leaves_t.append([]) motion_leaves_r.append([]) @@ -1158,22 +1686,24 @@ def mlc_leaves_translation( nb_cp_id = len(leaves_position) cp_id = np.arange(0, nb_cp_id, 1) for n in cp_id: - for i in range(nb_leaves): - translation_mlc.append(np.copy(mlc[i]["translation"])) + mlc_x_aperture_left = (center_curve_mlc / sad) * ( + leaves_position[n] - f_offset_mlc(-leaves_position[n]) + ) + mlc_x_aperture_right = (center_curve_mlc / sad) * ( + leaves_position[n] - f_offset_mlc(leaves_position[n]) + ) + mlc_x_aperture = np.array( + mlc_x_aperture_left[:80].tolist() + mlc_x_aperture_right[80:].tolist() + ) + for i in range(len(mlc_x_aperture)): + translation_mlc.append(np.copy(mlc.translation[i])) motion_leaves_t[i].append( - translation_mlc[i] + np.array([leaves_position[n, i] * fact_iso, 0, 0]) + translation_mlc[i] + np.array([mlc_x_aperture[i], 0, 0]) ) - motion_leaves_r[i].append(np.identity(3)) - + motion_leaves_r[i].append(mlc.rotation[i]) for i in range(nb_leaves): - n = mlc[i]["name"] - number = int(n.rsplit("_rep_", 1)[-1]) - vol_name = n.rsplit("_rep_", 1)[0] - v = sim.volume_manager.get_volume(vol_name) - v.add_dynamic_parametrisation( - repetition_index=number, - translation=motion_leaves_t[i], - rotation=motion_leaves_r[i], + mlc.add_dynamic_parametrisation( + repetition_index=i, translation=motion_leaves_t[i] ) @@ -1184,10 +1714,33 @@ def set_linac_head_motion( jaw_1_positions = rt_plan_parameters["jaws 1"] jaw_2_positions = rt_plan_parameters["jaws 2"] linac_head_positions = rt_plan_parameters["gantry angle"] - mlc_leaves_translation(sim, linac_name, mlc, leaves_position, cp_id, sad) - jaw_translation(sim, linac_name, jaws[0], jaw_1_positions, "left", cp_id, sad) - jaw_translation(sim, linac_name, jaws[1], jaw_2_positions, "right", cp_id, sad) - linac_rotation(sim, linac_name, linac_head_positions, cp_id) + collimation_rotation = rt_plan_parameters["collimation angle"] + mlc_leaves_dynamic_translation(mlc, leaves_position, cp_id, sad) + jaw_dynamic_translation(jaws[0], jaw_1_positions, "left", cp_id, sad) + jaw_dynamic_translation(jaws[1], jaw_2_positions, "right", cp_id, sad) + linac_rotation(sim, linac_name, [linac_head_positions, collimation_rotation], cp_id) + + +def get_patient_translation_and_rotation_from_RT_plan_to_IEC(rt_plan_parameters, img): + isocenter = rt_plan_parameters["isocenter"][0] + image = itk.imread(img) + offset = np.array(image.GetOrigin()) + dim = np.array(image.GetLargestPossibleRegion().GetSize()) + spacing = np.array(image.GetSpacing()) + + # IMAGE ROTATION ACCORDING TO IEC 61217 + + size = (dim - 1) * spacing + center = offset + size / 2 + rotation_to_apply = Rotation.from_euler("X", -90, degrees=True).as_matrix() + + isocenter_vector = isocenter - center + rotated_isocenter_vector = np.dot(rotation_to_apply, isocenter_vector) + + isocenter_rot_img = center + rotated_isocenter_vector + translation_to_apply = center - isocenter_rot_img + + return (translation_to_apply, rotation_to_apply) def set_time_intervals_from_rtplan(sim, rt_plan_parameters, cp_id="all_cp"): diff --git a/opengate/contrib/tps/treatmentPlanPhsSource.py b/opengate/contrib/tps/treatmentPlanPhsSource.py index ac350d0bf..21eb904b0 100644 --- a/opengate/contrib/tps/treatmentPlanPhsSource.py +++ b/opengate/contrib/tps/treatmentPlanPhsSource.py @@ -1,7 +1,6 @@ -from scipy.spatial.transform import Rotation import opengate_core from opengate.contrib.tps.ionbeamtherapy import * -import os +from ...exception import fatal class TreatmentPlanPhsSource(TreatmentPlanSource): @@ -97,11 +96,10 @@ def initialize_tpPhssource(self): if self.phaseSpaceList.get(spot.energy) is not None: source.phsp_file = self.phaseSpaceList.get(spot.energy) else: - print( - "ERROR in TreatmentPlanPhsSource: Energy requested from plan file does not exist. Aborting." + fatal( + f"Error in TreatmentPlanPhsSource: Energy requested from plan file was {spot.energy}, " + "but it does not exist. Aborting." ) - print("Requested energy was: ", spot.energy) - exit(-1) # set keys of phase space file to use source.position_key_x = self.position_key_x @@ -176,13 +174,9 @@ def verify_phs_files_exist(self, phs_dict): # ) if not os.path.isfile(phs_file): - print( - "ERROR in ThreatmenPlanPhsSource: File {} does not exist".format( - phs_file - ) + fatal( + f"Error in ThreatmenPlanPhsSource: File {phs_file} does not exist. Aborting" ) - print("Error: File in Phase space dictionary does not exist. Aborting.") - exit(-1) return True def read_list_of_Phs(self, file_name: str, path_to_phsp=""): @@ -215,12 +209,9 @@ def read_list_of_Phs(self, file_name: str, path_to_phsp=""): # convert to dictionary if input_arr.shape == (0,): - print( - "Error in TreatmentPlanPhsSource: No data found in file: ", - file_name, - " Aborting.", + fatal( + f"Error in TreatmentPlanPhsSource: No data found in file: {file_name}. ", ) - exit(-1) if input_arr.ndim == 0: # only single line read, convert to array input_arr = np.array([input_arr]) @@ -234,16 +225,13 @@ def read_list_of_Phs(self, file_name: str, path_to_phsp=""): phs_dict = {float(i[0]): str(i[1]) for i in input_arr} # print("phs_dict read: ", phs_dict) except Exception as e: - print( - "Error in TreatmentPlanPhsSource: could not read the phase space file list. Aborting." + fatal( + f"Error in TreatmentPlanPhsSource: Could not read the phase space file list. The error was: \n{e}" ) - print("The error was: ", e) - exit(-1) if len(phs_dict) == 0: - print( + fatal( "Error in TreatmentPlanPhsSource: the phase space file list is empty. Aborting." ) - exit(-1) return phs_dict def verify_necessary_parameters_are_set(self): @@ -251,28 +239,22 @@ def verify_necessary_parameters_are_set(self): E.g. not None It does not check sensibility""" if self.phaseSpaceList_file_name is None: - print( + fatal( "Error in TreatmentPlanPhsSource: phaseSpaceList_file_name is None. Aborting." ) - exit(-1) if self.distance_source_to_isocenter is None: - print( + fatal( "Error in TreatmentPlanPhsSource: distance_source_to_isocenter is None. Aborting." ) - exit(-1) if self.distance_stearmag_to_isocenter_x is None: - print( + fatal( "Error in TreatmentPlanPhsSource: distance_stearmag_to_isocenter_x is None. Aborting." ) - exit(-1) if self.distance_stearmag_to_isocenter_y is None: - print( + fatal( "Error in TreatmentPlanPhsSource: distance_stearmag_to_isocenter_y is None. Aborting." ) - exit(-1) if self.batch_size is None: - print("Error in TreatmentPlanPhsSource: batch_size is None. Aborting.") - exit(-1) + fatal("Error in TreatmentPlanPhsSource: batch_size is None. Aborting.") if self.spots is None: - print("Error in TreatmentPlanPhsSource: No spots have been set. Aborting.") - exit(-1) + fatal("Error in TreatmentPlanPhsSource: No spots have been set. Aborting.") diff --git a/opengate/data/rad_beta_spectrum.json b/opengate/data/rad_beta_spectrum.json new file mode 100644 index 000000000..4a0e5cdba --- /dev/null +++ b/opengate/data/rad_beta_spectrum.json @@ -0,0 +1,425 @@ +{ + "Co60": { + "energy_bin_edges": [ + 0.0, + 0.0746, + 0.1491, + 0.2237, + 0.2982, + 0.3728, + 0.4473, + 0.5219, + 0.5964, + 0.671, + 0.7455, + 0.8201, + 0.8947, + 0.9692, + 1.0438, + 1.1183, + 1.1929, + 1.2674, + 1.342, + 1.4165, + 1.4911 + ], + "weights": [ + 0.445, + 0.33, + 0.178, + 0.0458, + 0.000636, + 5.99e-05, + 5.96e-05, + 5.77e-05, + 5.54e-05, + 5.4e-05, + 5.23e-05, + 4.99e-05, + 4.66e-05, + 4.24e-05, + 3.71e-05, + 3.06e-05, + 2.31e-05, + 1.47e-05, + 6.66e-06, + 1.57e-06 + ] + }, + "Cu67": { + "energy_bin_edges": [ + 0.0, + 0.0287, + 0.0575, + 0.0862, + 0.115, + 0.1437, + 0.1725, + 0.2012, + 0.23, + 0.2587, + 0.2875, + 0.3162, + 0.345, + 0.3737, + 0.4025, + 0.4312, + 0.46, + 0.4887, + 0.5175, + 0.5462, + 0.575 + ], + "weights": [ + 0.125, + 0.12, + 0.116, + 0.109, + 0.1, + 0.0905, + 0.0795, + 0.0677, + 0.0556, + 0.0436, + 0.0323, + 0.0224, + 0.0146, + 0.0096, + 0.00628, + 0.00391, + 0.0023, + 0.00119, + 0.00045, + 7.59e-05 + ] + }, + "Y90": { + "energy_bin_edges": [ + 0.0, + 0.1142, + 0.2284, + 0.3426, + 0.4568, + 0.571, + 0.6852, + 0.7994, + 0.9136, + 1.0278, + 1.142, + 1.2562, + 1.3704, + 1.4846, + 1.5988, + 1.713, + 1.8272, + 1.9414, + 2.0556, + 2.1698, + 2.284 + ], + "weights": [ + 0.0426, + 0.0518, + 0.0594, + 0.0649, + 0.0686, + 0.0708, + 0.0717, + 0.0715, + 0.0704, + 0.0685, + 0.0657, + 0.0619, + 0.0569, + 0.0507, + 0.043, + 0.0342, + 0.0246, + 0.015, + 0.00643, + 0.00113 + ] + }, + "Ru106": { + "energy_bin_edges": [ + 0.0, + 0.002, + 0.0039, + 0.0059, + 0.0079, + 0.0098, + 0.0118, + 0.0138, + 0.0158, + 0.0177, + 0.0197, + 0.0217, + 0.0236, + 0.0256, + 0.0276, + 0.0295, + 0.0315, + 0.0335, + 0.0355, + 0.0374, + 0.0394 + ], + "weights": [ + 0.139, + 0.126, + 0.113, + 0.101, + 0.0896, + 0.0789, + 0.0689, + 0.0595, + 0.0507, + 0.0425, + 0.035, + 0.0282, + 0.0221, + 0.0167, + 0.0121, + 0.00813, + 0.00499, + 0.00253, + 0.000993, + 0.000178 + ] + }, + "I131": { + "energy_bin_edges": [ + 0.0, + 0.0403, + 0.0807, + 0.121, + 0.1614, + 0.2017, + 0.2421, + 0.2824, + 0.3227, + 0.3631, + 0.4034, + 0.4438, + 0.4841, + 0.5245, + 0.5648, + 0.6052, + 0.6455, + 0.6858, + 0.7262, + 0.7665, + 0.8069 + ], + "weights": [ + 0.138, + 0.131, + 0.123, + 0.113, + 0.102, + 0.0894, + 0.077, + 0.0647, + 0.053, + 0.0415, + 0.0303, + 0.02, + 0.0112, + 0.00455, + 0.000855, + 0.000115, + 6.94e-05, + 4.13e-05, + 1.73e-05, + 2.96e-06 + ] + }, + "Sm153": { + "energy_bin_edges": [ + 0.0, + 0.0408, + 0.0817, + 0.1225, + 0.1634, + 0.2042, + 0.2451, + 0.2859, + 0.3268, + 0.3676, + 0.4085, + 0.4493, + 0.4902, + 0.5311, + 0.5719, + 0.6127, + 0.6536, + 0.6945, + 0.7353, + 0.7761, + 0.817 + ], + "weights": [ + 0.103, + 0.103, + 0.101, + 0.0976, + 0.0932, + 0.0874, + 0.0804, + 0.0724, + 0.0635, + 0.0541, + 0.0444, + 0.0349, + 0.0258, + 0.0177, + 0.0109, + 0.00602, + 0.00292, + 0.00127, + 0.000486, + 7.24e-05 + ] + }, + "Ta175": { + "energy_bin_edges": [ + 0.0, + 0.0548, + 0.1096, + 0.1645, + 0.2193, + 0.2741, + 0.329, + 0.3838, + 0.4386, + 0.4934, + 0.5483, + 0.6031, + 0.6579, + 0.7127, + 0.7675, + 0.8224, + 0.8772, + 0.932, + 0.9869, + 1.0417, + 1.0965 + ], + "weights": [ + 1.05e-05, + 0.000106, + 0.00027, + 0.000433, + 0.000563, + 0.000648, + 0.000686, + 0.000682, + 0.00064, + 0.000568, + 0.000476, + 0.000374, + 0.000274, + 0.000185, + 0.000117, + 7.37e-05, + 4.29e-05, + 2.03e-05, + 7.36e-06, + 1.26e-06 + ] + }, + "Lu177": { + "energy_bin_edges": [ + 0.0, + 0.0249, + 0.0497, + 0.0746, + 0.0994, + 0.1243, + 0.1491, + 0.174, + 0.1988, + 0.2237, + 0.2485, + 0.2734, + 0.2983, + 0.3231, + 0.348, + 0.3728, + 0.3977, + 0.4225, + 0.4474, + 0.4722, + 0.4971 + ], + "weights": [ + 0.135, + 0.122, + 0.109, + 0.0968, + 0.0851, + 0.0745, + 0.0657, + 0.0588, + 0.0522, + 0.0456, + 0.0389, + 0.0324, + 0.0261, + 0.0203, + 0.015, + 0.0105, + 0.00664, + 0.00346, + 0.00148, + 0.000297 + ] + }, + "Re186": { + "energy_bin_edges": [ + 0.0, + 0.0538, + 0.1076, + 0.1615, + 0.2153, + 0.2691, + 0.323, + 0.3768, + 0.4306, + 0.4844, + 0.5383, + 0.5921, + 0.6459, + 0.6997, + 0.7535, + 0.8074, + 0.8612, + 0.915, + 0.9688, + 1.0227, + 1.0765 + ], + "weights": [ + 0.075, + 0.078, + 0.0798, + 0.0803, + 0.0794, + 0.0771, + 0.0737, + 0.0692, + 0.0637, + 0.0573, + 0.0503, + 0.0428, + 0.0352, + 0.0277, + 0.0206, + 0.0141, + 0.00869, + 0.00459, + 0.00177, + 0.000348 + ] + } +} \ No newline at end of file diff --git a/opengate/data/rad_gamma_spectrum.json b/opengate/data/rad_gamma_spectrum.json new file mode 100644 index 000000000..19c880a09 --- /dev/null +++ b/opengate/data/rad_gamma_spectrum.json @@ -0,0 +1,84 @@ +{ + "Tc99m": { + "energies": [ + 0.140511 + ], + "weights": [ + 0.885 + ] + }, + "Lu177": { + "energies": [ + 0.0716418, + 0.1129498, + 0.1367245, + 0.2083662, + 0.2496742, + 0.3213159 + ], + "weights": [ + 0.001726, + 0.0620, + 0.000470, + 0.1038, + 0.002012, + 0.00216 + ] + }, + "In111": { + "energies": [ + 0.15081, + 0.17128, + 0.24535 + ], + "weights": [ + 0.000015, + 0.9061, + 0.9412 + ] + }, + "I131": { + "energies": [ + 0.080185, + 0.0859, + 0.163930, + 0.177214, + 0.23218, + 0.272498, + 0.284305, + 0.2958, + 0.3024, + 0.318088, + 0.324651, + 0.325789, + 0.3584, + 0.364489, + 0.404814, + 0.503004, + 0.636989, + 0.642719, + 0.722911 + ], + "weights": [ + 0.02607, + 0.000051, + 0.000211, + 0.00277, + 0.000023, + 0.000581, + 0.0614, + 0.000012, + 0.000046, + 0.000807, + 0.000244, + 0.00274, + 0.00017, + 0.812, + 0.000552, + 0.003540, + 0.0712, + 0.002183, + 0.01786 + ] + } +} diff --git a/opengate/data/readme_rad_spectrum.md b/opengate/data/readme_rad_spectrum.md new file mode 100644 index 000000000..8970fa38f --- /dev/null +++ b/opengate/data/readme_rad_spectrum.md @@ -0,0 +1,11 @@ +## Data for radionuclide spectra + +### Beta spectrum + +The file `rad_beta_spectrum.json` is generated using data from +[doseinfo-radar](https://www.doseinfo-radar.com/RADARDecay.html) +([direct link to the excel file](https://www.doseinfo-radar.com/BetaSpec.zip)). + +Column C (n (MeV)) is used as energy bin edges with a prepend 0 (which is valid +because the step is constant and equal to the first value). +Column F (#/nt) is used as weights. diff --git a/opengate/engines.py b/opengate/engines.py index 6c9116de5..b0e0a73ce 100644 --- a/opengate/engines.py +++ b/opengate/engines.py @@ -29,7 +29,7 @@ class EngineBase: Base class for all engines (SimulationEngine, VolumeEngine, etc.) """ - def __init__(self, simulation_engine): + def __init__(self, simulation_engine) -> None: self.simulation_engine = simulation_engine # debug verbose self.verbose_getstate = simulation_engine.verbose_getstate @@ -87,7 +87,7 @@ def __init__(self, simulation_engine): def close(self): if self.verbose_close: - warning(f"Closing SourceEngine") + warning("Closing SourceEngine") self.release_g4_references() super().close() @@ -102,7 +102,7 @@ def initialize(self, run_timing_intervals, progress_bar=False): assert_run_timing(self.run_timing_intervals) if len(self.simulation_engine.simulation.source_manager.user_info_sources) == 0: self.simulation_engine.simulation.warn_user( - f"No source: no particle will be generated" + "No source: no particle will be generated" ) self.progress_bar = progress_bar @@ -231,7 +231,7 @@ def __init__(self, *args): def close(self): if self.verbose_close: - warning(f"Closing PhysicsEngine") + warning("Closing PhysicsEngine") self.close_physics_constructors() self.release_g4_references() self.release_optical_surface_g4_references() @@ -326,9 +326,7 @@ def initialize_global_cuts(self): # range if ui.energy_range_min is not None and ui.energy_range_max is not None: - self.physics_manager.warn_user( - f"WARNING ! SetEnergyRange only works in MT mode" - ) + self.physics_manager.warn_user("SetEnergyRange only works in MT mode") pct = g4.G4ProductionCutsTable.GetProductionCutsTable() pct.SetEnergyRange(ui.energy_range_min, ui.energy_range_max) @@ -494,7 +492,7 @@ def __init__(self, *args): def close(self): if self.verbose_close: - warning(f"Closing ActionEngine") + warning("Closing ActionEngine") self.release_g4_references() super().close() @@ -591,7 +589,7 @@ def actor_manager(self): def close(self): if self.verbose_close: - warning(f"Closing ActorEngine") + warning("Closing ActorEngine") for actor in self.actor_manager.actors.values(): actor.close() super().close() @@ -940,9 +938,9 @@ def store_sources(self, simulation_engine): self.sources = {} if simulation_engine.simulation.multithreaded is True: th = {} - self.sources_by_thread = [{}] * ( - simulation_engine.simulation.number_of_threads + 1 - ) + self.sources_by_thread = [ + {} for _ in range(simulation_engine.simulation.number_of_threads + 1) + ] for source in simulation_engine.source_engine.sources: n = source.user_info.name if n in th: @@ -976,7 +974,7 @@ def get_source_mt(self, name, thread): self.simulation.number_of_threads <= 1 and not self.simulation.force_multithread_mode ): - fatal(f"Cannot use get_source_mt in monothread mode") + fatal("Cannot use get_source_mt in monothread mode") if thread >= len(self.sources_by_thread): fatal( f"Cannot get source {name} with thread {thread}, while " @@ -1218,7 +1216,7 @@ def start_and_stop(self): f"Simulation: STOP. Run: {len(self.run_timing_intervals)}. " # f'Events: {self.source_manager.total_events_count}. ' f"Time: {end - start:0.1f} seconds.\n" - + f"-" * 80 + + "-" * 80 ) def initialize_random_engine(self): @@ -1230,7 +1228,7 @@ def initialize_random_engine(self): self.g4_HepRandomEngine = g4.MTwistEngine() if not self.g4_HepRandomEngine: s = f"Cannot find the random engine {engine_name}\n" - s += f"Use: MersenneTwister or MixMaxRng" + s += "Use: MersenneTwister or MixMaxRng" fatal(s) # set the random engine @@ -1441,7 +1439,7 @@ def add_g4_command_after_init(self, command): 500: "fParameterOutOfCandidates", 600: "fAliasNotFound", } - closest_err_code = max(filter(lambda x: x <= code, err_codes.keys())) + closest_err_code = max([x for x in err_codes if x <= code]) closest_err_msg = err_codes[closest_err_code] fatal(f'Error in apply_g4_command "{command}": {code} {closest_err_msg}') diff --git a/opengate/geometry/materials.py b/opengate/geometry/materials.py index cd4e3871f..7b74a09a7 100644 --- a/opengate/geometry/materials.py +++ b/opengate/geometry/materials.py @@ -137,10 +137,10 @@ def HU_find_max_density_difference(hu_min, hu_max, d_min, d_max, densities): j = j + 1 j = j - 1 for x in range(i, j, 1): - if densities[i]["density"] < d_min: - d_min = densities[i]["density"] - if densities[i]["density"] > d_max: - d_max = densities[i]["density"] + if densities[x]["density"] < d_min: + d_min = densities[x]["density"] + if densities[x]["density"] > d_max: + d_max = densities[x]["density"] return d_max - d_min @@ -204,15 +204,15 @@ def HounsfieldUnit_to_material(simulation, density_tolerance, file_mat, file_den weights_nz = [] elems_symbol_nz = [] # remove the weight equal to zero - sum = 0 + sum_of_weights = 0 for a, e in zip(weights, elems_symbol): if a > 0: weights_nz.append(a) elems_symbol_nz.append(e) - sum += a + sum_of_weights += a # normalise weight for k in range(len(weights_nz)): - weights_nz[k] = weights_nz[k] / sum + weights_nz[k] = weights_nz[k] / sum_of_weights # define a new material (will be created later at MaterialDatabase initialize) name = f'{mat["name"]}_{num}' simulation.volume_manager.material_database.add_material_weights( @@ -761,7 +761,7 @@ def FindOrBuildMaterial(self, material_name): def FindOrBuildElement(self, element_name): self.init_NIST() # return if already exist - if element_name in self.g4_elements.keys(): + if element_name in self.g4_elements: return self.g4_elements[element_name] # we build and store the G4 element if not if element_name in self.nist_element_names: @@ -782,5 +782,4 @@ def get_database_material_names(self, db=None): fatal( f"The database '{db}' is not in the list of read database: {self.filenames}" ) - list = self.material_builders_by_filename[db] - return [name for name in list] + return [name for name in self.material_builders_by_filename[db]] diff --git a/opengate/geometry/utility.py b/opengate/geometry/utility.py index bc4c763e4..766948a27 100644 --- a/opengate/geometry/utility.py +++ b/opengate/geometry/utility.py @@ -233,7 +233,7 @@ def get_transform_world_to_local_old(vol_name): crot = np.matmul(rot, crot) ctr = rot.dot(ctr) + tr - if pv.GetMotherLogical() == None: + if pv.GetMotherLogical() is None: vol_name = __world_name__ else: vol_name = pv.GetMotherLogical().GetName() @@ -319,11 +319,11 @@ def get_grid_repetition(size, spacing, start=None, return_lut=False): list : A list of translations vectors. dict : (Optional) A dictionary mapping copy index to the respective translation vector. Only if `return_lut` is `True`. """ - if not len(size) == 3: + if len(size) != 3: fatal( f"Input `size` must be a 3-item list or numpy array. Found length {len(size)}." ) - if not len(spacing) == 3: + if len(spacing) != 3: fatal( f"Input `spacing` must be a 3-item list or numpy array. Found length {len(spacing)}." ) diff --git a/opengate/geometry/volumes.py b/opengate/geometry/volumes.py index d1126cfd7..80aa0b479 100644 --- a/opengate/geometry/volumes.py +++ b/opengate/geometry/volumes.py @@ -565,7 +565,10 @@ def add_dynamic_parametrisation(self, repetition_index=0, **params): class BooleanVolume(RepeatableVolume, solids.BooleanSolid): - """Volume resulting from a boolean operation of the solids contained in two volumes.""" + """ + Volume resulting from a boolean operation + of the solids contained in two volumes. + """ # Function to handle boolean operations on volumes @@ -675,7 +678,10 @@ class TubsVolume(RepeatableVolume, solids.TubsSolid): class TesselatedVolume(RepeatableVolume, solids.TesselatedSolid): - """Volume based on a mesh volume by reading an STL file.""" + """ + Volume based on a mesh volume + by reading an STL file. + """ class RepeatParametrisedVolume(VolumeBase): @@ -786,6 +792,12 @@ def create_repeat_parametrisation(self): self.g4_repeat_parametrisation.SetUserInfo(p) +def _setter_hook_image(self, image): + if image != self.image: + self._itk_image = None + return image + + class ImageVolume(VolumeBase, solids.ImageSolid): """ Store information about a voxelized volume @@ -804,7 +816,12 @@ class ImageVolume(VolumeBase, solids.ImageSolid): ), "image": ( "", - {"doc": "Path to the image file", "is_input_file": True, "dynamic": True}, + { + "doc": "Path to the image file", + "is_input_file": True, + "dynamic": True, + "setter_hook": _setter_hook_image, + }, ), "dump_label_image": ( None, @@ -875,12 +892,16 @@ def itk_image(self, image): # FIXME: replace this property by function in opengate.image @property def size_pix(self): + if self.itk_image is None: + self.load_input_image() return np.array(itk.size(self.itk_image)).astype(int) # @requires_fatal('itk_image') # FIXME: replace this property by function in opengate.image @property def spacing(self): + if self.itk_image is None: + self.load_input_image() return np.array(self.itk_image.GetSpacing()) # @requires_fatal("itk_image") diff --git a/opengate/image.py b/opengate/image.py index 0cbadfa01..fd09986f7 100644 --- a/opengate/image.py +++ b/opengate/image.py @@ -12,6 +12,8 @@ ) from .definitions import __gate_list_objects__ +import SimpleITK as sitk + def update_image_py_to_cpp(py_img, cpp_img, copy_data=False): cpp_img.set_size(py_img.GetLargestPossibleRegion().GetSize()) @@ -333,16 +335,48 @@ def divide_itk_images( return imgarrOut -def sum_itk_images(images): - image_type = type(images[0]) - add_image_filter = itk.AddImageFilter[image_type, image_type, image_type].New() - output = images[0] - for img in images[1:]: - add_image_filter.SetInput1(output) - add_image_filter.SetInput2(img) - add_image_filter.Update() - output = add_image_filter.GetOutput() - return output +# IMPLEMENTATION BASED ON ITK +# def sum_itk_images(images): +# image_type = type(images[0]) +# add_image_filter = itk.AddImageFilter[image_type, image_type, image_type].New() +# output = images[0] +# for img in images[1:]: +# add_image_filter.SetInput1(output) +# add_image_filter.SetInput2(img) +# add_image_filter.Update() +# output = add_image_filter.GetOutput() +# return output + + +def itk_to_sitk(itk_image): + array = itk.GetArrayFromImage(itk_image) + sitk_image = sitk.GetImageFromArray(array) + sitk_image.SetOrigin(np.array(itk_image.GetOrigin())) + sitk_image.SetSpacing(np.array(itk_image.GetSpacing())) + sitk_image.SetDirection(np.array(itk_image.GetDirection()).flatten()) + return sitk_image + + +def sitk_to_itk(sitk_image): + array = sitk.GetArrayFromImage(sitk_image) # Convert SimpleITK image to NumPy array + itk_image = itk.GetImageFromArray(array) # Convert NumPy array to ITK image + + # Set the metadata from SimpleITK to ITK image + itk_image.SetOrigin(np.array(sitk_image.GetOrigin())) + itk_image.SetSpacing(np.array(sitk_image.GetSpacing())) + itk_image.SetDirection(np.array(sitk_image.GetDirection()).reshape(3, 3)) + + return itk_image + + +def sum_itk_images(itk_image_list): + if not itk_image_list: + raise ValueError("The image list is empty.") + summed_image = itk_to_sitk(itk_image_list[0]) + for itk_image in itk_image_list[1:]: + sitk_image = itk_to_sitk(itk_image) + summed_image = sitk.Add(summed_image, sitk_image) + return sitk_to_itk(summed_image) def multiply_itk_images(images): diff --git a/opengate/managers.py b/opengate/managers.py index 42709ac35..dbfd6a783 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -74,6 +74,7 @@ SimulationStatisticsActor, KillActor, KillAccordingProcessesActor, + LastVertexInteractionSplittingActor, SplittingActorBase, ComptSplittingActor, BremSplittingActor, @@ -120,6 +121,7 @@ "DigitizerEnergyWindowsActor": DigitizerEnergyWindowsActor, "DigitizerHitsCollectionActor": DigitizerHitsCollectionActor, "PhaseSpaceActor": PhaseSpaceActor, + "LastVertexInteractionSplittingActor":LastVertexInteractionSplittingActor, } @@ -258,7 +260,7 @@ def __str__(self): return s def dump_source_types(self): - s = f"" + s = "" # FIXME: workaround to avoid circular import, will be solved when refactoring sources from opengate.sources.builders import source_builders @@ -332,7 +334,7 @@ class ActorManager(GateObject): Manage all the actors in the simulation """ - def __init__(self, simulation, *args, **kwargs): + def __init__(self, simulation, *args, **kwargs) -> None: kwargs["name"] = "actor_manager" kwargs["simulation"] = simulation super().__init__(*args, **kwargs) @@ -399,10 +401,10 @@ def dump_actor_types(self): def get_actor_user_info(self, name): self.warn_user( - f"Deprecation warning: The function 'get_actor_user_info' will soon be removed." - f"Use my_actor.user_info instead, where 'my_actor' " - f"should be replace by your actor object. " - f"You can also access user input parameters directly, e.g. my_actor.attached_to=..." + "Deprecation warning: The function 'get_actor_user_info' will soon be removed." + "Use my_actor.user_info instead, where 'my_actor' " + "should be replace by your actor object. " + "You can also access user input parameters directly, e.g. my_actor.attached_to=..." ) actor = self.get_actor(name) return actor.user_info @@ -671,7 +673,7 @@ class PhysicsManager(GateObject): # ), } - def __init__(self, simulation, *args, **kwargs): + def __init__(self, simulation, *args, **kwargs) -> None: super().__init__(name="physics_manager", *args, **kwargs) # Keep a pointer to the current simulation @@ -758,7 +760,7 @@ def dump_production_cuts(self): for k, v in self.user_info.global_production_cuts.items(): s += f"{k}: {v}\n" if len(self.regions.keys()) > 0: - s += f"*** Production cuts per regions ***\n" + s += "*** Production cuts per regions ***\n" for region in self.regions.values(): s += f"In region {region.name}:\n" s += region.dump_production_cuts() @@ -816,7 +818,7 @@ def add_optical_surface(self, volume_from, volume_to, g4_surface_name): name = "optical_surface_" + volume_from + "_" + volume_to # Throw an error if the optical surface already exists - if name in self.optical_surfaces.keys(): + if name in self.optical_surfaces: fatal("An optical surface between these volumes already exists") self.optical_surfaces[name] = OpticalSurface( @@ -830,13 +832,13 @@ def add_optical_surface(self, volume_from, volume_to, g4_surface_name): return self.optical_surfaces[name] def add_region(self, name): - if name in self.regions.keys(): + if name in self.regions: fatal("A region with this name already exists.") self.regions[name] = Region(name=name, simulation=self.simulation) return self.regions[name] def find_or_create_region(self, volume_name): - if volume_name not in self.volumes_regions_lut.keys(): + if volume_name not in self.volumes_regions_lut: region = self.add_region(volume_name + "_region") region.associate_volume(volume_name) else: @@ -957,7 +959,7 @@ def add_post_processor(self, post_processor): try: name = post_processor.name except AttributeError: - fatal(f"Cannot retrieve the name of the post-processor.") + fatal("Cannot retrieve the name of the post-processor.") if name not in self.post_processors: self.post_processors[name] = post_processor # add finalizers to make sure the post-processor is shut down gracefully @@ -966,7 +968,7 @@ def add_post_processor(self, post_processor): post_processor, post_processor.close ) else: - fatal(f"A post-processor with this name has already been added. ") + fatal("A post-processor with this name has already been added. ") class VolumeManager(GateObject): @@ -990,7 +992,7 @@ class VolumeManager(GateObject): "TesselatedVolume": TesselatedVolume, } - def __init__(self, simulation, *args, **kwargs): + def __init__(self, simulation, *args, **kwargs) -> None: """ Class that store geometry description. """ @@ -1139,7 +1141,7 @@ def create_volume(self, volume_type, name): # check that another element with the same name does not already exist volume_type_variants = [volume_type, volume_type + "Volume"] for vt in volume_type_variants: - if vt in self.volume_types.keys(): + if vt in self.volume_types: return self.volume_types[vt](name=name) fatal( f"Unknown volume type {volume_type}. Known types are: {list(self.volume_types.keys())}." @@ -1194,16 +1196,29 @@ def print_volume_tree(self): def dump_volume_types(self): - s = f"" + s = "" for vt in self.volume_types: s += f"{vt} " return s - + + def get_volume_tree(self): + return self.volume_tree_root + + def print_volume_types(self): print(self.dump_volume_types()) def dump_material_database_names(self): return list(self.material_database.filenames) + + def get_volume_tree(self): + return (self.volume_tree_root) + + def get_volume_tree(self): + return self.volume_tree_root + + def get_volume_tree(self): + return self.volume_tree_root def print_material_database_names(self): print(self.dump_material_database_names()) @@ -1592,17 +1607,17 @@ def to_json_file(self, directory=None, filename=None): def from_json_string(self, json_string): warning( - f"**********************************************************************************\n" - f"* WARNING: Only parts of the simulation can currently be reloaded from JSON. *\n" - f"**********************************************************************************\n" + "**********************************************************************************\n" + "* WARNING: Only parts of the simulation can currently be reloaded from JSON. *\n" + "**********************************************************************************\n" ) self.from_dictionary(loads_json(json_string)) def from_json_file(self, path): warning( - f"**********************************************************************************\n" - f"* WARNING: Only parts of the simulation can currently be reloaded from JSON. *\n" - f"**********************************************************************************\n" + "**********************************************************************************\n" + "* WARNING: Only parts of the simulation can currently be reloaded from JSON. *\n" + "**********************************************************************************\n" ) with open(path, "r") as f: self.from_dictionary(load_json(f)) diff --git a/opengate/sources/builders.py b/opengate/sources/builders.py index 14914fd8f..6d14ecfe8 100644 --- a/opengate/sources/builders.py +++ b/opengate/sources/builders.py @@ -1,4 +1,4 @@ -from .generic import GenericSource, TemplateSource +from .generic import GenericSource, TemplateSource, LastVertexSource from .voxelsources import VoxelsSource from .gansources import GANSource, GANPairsSource from .beamsources import IonPencilBeamSource, TreatmentPlanPBSource @@ -20,6 +20,7 @@ GANPairsSource, IonPencilBeamSource, TemplateSource, + LastVertexSource, PhaseSpaceSource, TreatmentPlanPBSource, PhotonFromIonDecaySource, diff --git a/opengate/sources/generic.py b/opengate/sources/generic.py index fc24be478..fd14f7a6c 100644 --- a/opengate/sources/generic.py +++ b/opengate/sources/generic.py @@ -1,7 +1,9 @@ from box import Box from scipy.spatial.transform import Rotation +import os import pathlib import numpy as np +import json import opengate_core from ..utility import g4_units @@ -102,82 +104,62 @@ def generate_isotropic_directions( return v -def get_rad_gamma_energy_spectrum(rad): - weights = {} - energies = {} - MeV = g4_units.MeV - # convert to lowcase - rad = rad.lower() - # Tc99m - weights["tc99m"] = [0.885] - energies["tc99m"] = [0.140511 * MeV] - # Lu177 - weights["lu177"] = [0.001726, 0.0620, 0.000470, 0.1038, 0.002012, 0.00216] - energies["lu177"] = [ - 0.0716418 * MeV, - 0.1129498 * MeV, - 0.1367245 * MeV, - 0.2083662 * MeV, - 0.2496742 * MeV, - 0.3213159 * MeV, - ] - - # In111 - weights["in111"] = [0.000015, 0.9061, 0.9412] - energies["in111"] = [0.15081 * MeV, 0.17128 * MeV, 0.24535 * MeV] - # I131 - weights["i131"] = [ - 0.02607, - 0.000051, - 0.000211, - 0.00277, - 0.000023, - 0.000581, - 0.0614, - 0.000012, - 0.000046, - 0.000807, - 0.000244, - 0.00274, - 0.00017, - 0.812, - 0.000552, - 0.003540, - 0.0712, - 0.002183, - 0.01786, - ] - energies["i131"] = [ - 0.080185 * MeV, - 0.0859 * MeV, - 0.163930 * MeV, - 0.177214 * MeV, - 0.23218 * MeV, - 0.272498 * MeV, - 0.284305 * MeV, - 0.2958 * MeV, - 0.3024 * MeV, - 0.318088 * MeV, - 0.324651 * MeV, - 0.325789 * MeV, - 0.3584 * MeV, - 0.364489 * MeV, - 0.404814 * MeV, - 0.503004 * MeV, - 0.636989 * MeV, - 0.642719 * MeV, - 0.722911 * MeV, - ] - - return weights[rad], energies[rad] +def get_rad_gamma_spectrum(rad): + path = ( + pathlib.Path(os.path.dirname(__file__)) + / ".." + / "data" + / "rad_gamma_spectrum.json" + ) + with open(path, "r") as f: + data = json.load(f) + + if rad not in data: + fatal(f"get_rad_gamma_spectrum: {path} does not contain data for ion {rad}") + + # select data for specific ion + data = Box(data[rad]) + + data.energies = np.array(data.energies) * g4_units.MeV + data.weights = np.array(data.weights) + + return data + + +def get_rad_beta_spectrum(rad: str): + path = ( + pathlib.Path(os.path.dirname(__file__)) + / ".." + / "data" + / "rad_beta_spectrum.json" + ) + with open(path, "r") as f: + data = json.load(f) + + if rad not in data: + fatal(f"get_rad_beta_spectrum: {path} does not contain data for ion {rad}") + + # select data for specific ion + data = Box(data[rad]) + + bin_edges = data.energy_bin_edges + n = len(bin_edges) - 1 + energies = [(bin_edges[i] + bin_edges[i + 1]) / 2 for i in range(n)] + + data.energy_bin_edges = np.array(data.energy_bin_edges) * g4_units.MeV + data.weights = np.array(data.weights) + data.energies = np.array(energies) + + return data def set_source_rad_energy_spectrum(source, rad): - w, en = get_rad_gamma_energy_spectrum(rad) + rad_spectrum = get_rad_gamma_spectrum(rad) + source.particle = "gamma" - source.energy.type = "spectrum_lines" - source.energy.spectrum_weight = w - source.energy.spectrum_energy = en + source.energy.type = "spectrum_discrete" + source.energy.spectrum_weights = rad_spectrum.weights + source.energy.spectrum_energies = rad_spectrum.energies def get_source_skipped_events(sim, source_name): @@ -351,6 +333,10 @@ def set_default_user_info(user_info): user_info.energy.max_energy = None user_info.energy.histogram_weight = None user_info.energy.histogram_energy = None + user_info.energy.spectrum_weights = None + user_info.energy.spectrum_energies = None + user_info.energy.spectrum_energy_bin_edges = None + user_info.energy.spectrum_histogram_interpolation = None def create_g4_source(self): return opengate_core.GateGenericSource() @@ -411,7 +397,8 @@ def initialize(self, run_timing_intervals): "O15_analytic", "C11_analytic", "histogram", - "spectrum_lines", + "spectrum_discrete", + "spectrum_histogram", "range", ] l.extend(all_beta_plus_radionuclides) @@ -566,3 +553,21 @@ def initialize(self, run_timing_intervals): # initialize SourceBase.initialize(self, run_timing_intervals) + + +class LastVertexSource(SourceBase): + type_name = "LastVertexSource" + + @staticmethod + def set_default_user_info(user_info): + SourceBase.set_default_user_info(user_info) + user_info.n = 0 + + def create_g4_source(self): + return opengate_core.GateLastVertexSource() + + def __init__(self, user_info): + super().__init__(user_info) + + def initialize(self, run_timing_intervals): + SourceBase.initialize(self, run_timing_intervals) diff --git a/opengate/sources/phidsources.py b/opengate/sources/phidsources.py index 5631b1e06..cc5eced28 100644 --- a/opengate/sources/phidsources.py +++ b/opengate/sources/phidsources.py @@ -137,7 +137,7 @@ def update_tac_activity_ui(ui, g4_source): ) # scale the activity if energy_spectrum is given (because total may not be 100%) - total = sum(ui.energy.spectrum_weight) + total = sum(ui.energy.spectrum_weights) sec = g4_units.s Bq = g4_units.Bq @@ -158,7 +158,7 @@ def update_tac_activity_ui(ui, g4_source): if ui.verbose: print( f"GammaFromIon source {ui.name} total = {total * 100:8.2f}% " - f" gammas lines = {len(ui.energy.spectrum_weight)} " + f" gammas lines = {len(ui.energy.spectrum_weights)} " f" total activity = {sum(ui.tac_activities) / Bq:10.3f}" f" first activity = {ui.tac_activities[0] / Bq:5.2f}" f" last activity = {ui.tac_activities[-1] / Bq:5.2f}" @@ -1158,11 +1158,11 @@ def gid_build_one_sub_source(stype, ui, daughter, ene, w, first_nuclide): s._name = f"{ui.name}_{stype}_{daughter.nuclide.nuclide}" # additional info, specific to ion gamma source s.particle = "gamma" - s.energy.type = "spectrum_lines" + s.energy.type = "spectrum_discrete" s.energy.ion_gamma_mother = Box({"z": first_nuclide.Z, "a": first_nuclide.A}) s.energy.ion_gamma_daughter = ion_gamma_daughter - s.energy.spectrum_weight = w - s.energy.spectrum_energy = ene + s.energy.spectrum_weights = w + s.energy.spectrum_energies = ene s.activity = ui.activity s.n = ui.n # prepare times and activities that will be set during initialisation diff --git a/opengate/sources/phspsources.py b/opengate/sources/phspsources.py index 704132a85..9e47b9621 100644 --- a/opengate/sources/phspsources.py +++ b/opengate/sources/phspsources.py @@ -3,6 +3,7 @@ import numbers from scipy.spatial.transform import Rotation from box import Box +import sys import opengate_core from ..exception import fatal, warning @@ -57,7 +58,7 @@ def read_phsp_and_keys(self): fatal( f"PhaseSpaceSourceGenerator: No usable branches in the root file {self.user_info.phsp_file}. Aborting." ) - exit() + sys.exit() self.num_entries = int(self.root_file.num_entries) @@ -71,7 +72,7 @@ def get_entry_start(self): ui = self.user_info if not opengate_core.IsMultithreadedApplication(): if not isinstance(ui.entry_start, numbers.Number): - fatal(f"entry_start must be a simple number is mono-thread mode") + fatal("entry_start must be a simple number is mono-thread mode") n = int(self.user_info.entry_start % self.num_entries) if self.user_info.entry_start > self.num_entries: warning( @@ -334,11 +335,11 @@ def initialize(self, run_timing_intervals): if ui.generate_until_next_primary: if ui.primary_PDGCode == 0: fatal( - f"PhaseSpaceSource: generate_until_next_primary is True but no primary particle is defined" + "PhaseSpaceSource: generate_until_next_primary is True but no primary particle is defined" ) if ui.primary_lower_energy_threshold <= 0: fatal( - f"PhaseSpaceSource: generate_until_next_primary is True but no primary_lower_energy_threshold is defined" + "PhaseSpaceSource: generate_until_next_primary is True but no primary_lower_energy_threshold is defined" ) # if not set, initialize the entry_start to 0 or to a list for multithreading diff --git a/opengate/tests/data b/opengate/tests/data index b3942b1ee..04fbd2130 160000 --- a/opengate/tests/data +++ b/opengate/tests/data @@ -1 +1 @@ -Subproject commit b3942b1ee3d5f40e7f14f8e6401be6d785bbc2f2 +Subproject commit 04fbd21303a8b07a9123c584e7679f4256cba08b diff --git a/opengate/tests/src/test008_dose_actor.py b/opengate/tests/src/test008_dose_actor.py index cc8f6d161..03f38fc8b 100755 --- a/opengate/tests/src/test008_dose_actor.py +++ b/opengate/tests/src/test008_dose_actor.py @@ -81,11 +81,15 @@ dose.hit_type = "random" dose.output_coordinate_system = "local" dose.output_filename = "test.nii.gz" + dose.edep.keep_data_per_run = True # add stat actor stat = sim.add_actor("SimulationStatisticsActor", "Stats") stat.track_types_flag = True + sim.run_timing_intervals = [ + (x / 3.0 * gate.g4_units.s, (x + 1) / 3.0 * gate.g4_units.s) for x in range(3) + ] # start simulation sim.run(start_new_process=True) @@ -95,6 +99,7 @@ # tests stats_ref = utility.read_stat_file(ref_path / "stat.txt") + stat.counts.runs = 1 # because ref had only 1 run is_ok = utility.assert_stats(stat, stats_ref, 0.11) print("\nDifference for EDEP") diff --git a/opengate/tests/src/test009_voxels_dynamic.py b/opengate/tests/src/test009_voxels_dynamic.py index 48de31a4b..557ff1ad3 100755 --- a/opengate/tests/src/test009_voxels_dynamic.py +++ b/opengate/tests/src/test009_voxels_dynamic.py @@ -92,6 +92,7 @@ # add dose actor dose = sim.add_actor("DoseActor", "dose") dose.output_filename = "test009-edep.mhd" + dose.edep.keep_data_per_run = True dose.attached_to = "patient" dose.size = [99, 99, 99] dose.spacing = [2 * mm, 2 * mm, 2 * mm] diff --git a/opengate/tests/src/test010_generic_source_energy_spectrum_discrete.py b/opengate/tests/src/test010_generic_source_energy_spectrum_discrete.py new file mode 100755 index 000000000..878023493 --- /dev/null +++ b/opengate/tests/src/test010_generic_source_energy_spectrum_discrete.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +from opengate.tests import utility +import numpy as np +import gatetools + +import matplotlib.pyplot as plt + + +def plot(output_file, ekin, data_x, data_y, relerrs): + bins = len(data_x) + hist_y, hist_x = np.histogram(ekin, bins=bins) + + fig, ax = plt.subplots(figsize=(8.5, 6)) + ax.set_xlabel("Energy (MeV)") + ax.set_ylabel("Number of particles") + ax.scatter(data_x, data_y, label="input energy spectrum", marker="o") + ax.scatter( + data_x, hist_y, label="simulated energy spectrum", marker=".", linewidth=0.9 + ) + ax.legend() + + ax2 = ax.twinx() + ax2.set_ylabel("Relative uncertainty") + ax2.set_ylim([0, 0.05]) + ax2.plot(data_x, relerrs, label="relative uncertainty", linewidth=0.4) + + fig.savefig(output_file, dpi=300) + plt.close(fig) + + +def root_load_ekin(root_file: str): + data_ref, keys_ref, m_ref = gatetools.phsp.load(root_file) + + index_ekin = keys_ref.index("KineticEnergy") + ekin = [data_ref_i[index_ekin] for data_ref_i in data_ref] + + return ekin + + +def add_source_energy_spectrum_discrete(sim, phsp): + spectrum = gate.sources.generic.get_rad_gamma_spectrum("Lu177") + + source = sim.add_source("GenericSource", "beam") + source.mother = phsp.name + source.particle = "gamma" + source.n = 5e5 / sim.number_of_threads + source.position.type = "point" + source.direction.type = "iso" + source.energy.type = "spectrum_discrete" + source.energy.spectrum_energies = spectrum.energies + source.energy.spectrum_weights = spectrum.weights + + return source + + +def run_simulation(paths): + # create the simulation + sim = gate.Simulation() + + # main options + sim.g4_verbose = False + sim.g4_verbose_level = 1 + sim.visu = False + sim.number_of_threads = 1 + # sim.random_seed = 987654321 + sim.output_dir = paths.output + + # units + m = gate.g4_units.m + g_cm3 = gate.g4_units.g_cm3 + + # materials + sim.volume_manager.material_database.add_material_weights( + "Vacuum", ["H"], [1], 1e-12 * g_cm3 + ) + + world = sim.world + world.size = [2 * m, 2 * m, 2 * m] + world.material = "Vacuum" + + phsp = sim.add_volume("Sphere", "phsp") + phsp.rmin = 0.1 * m + phsp.rmax = 1 * m + phsp.material = world.material + + source = add_source_energy_spectrum_discrete(sim, phsp) + + # actors + phsp_actor = sim.add_actor("PhaseSpaceActor", "phsp_actor") + phsp_actor.output_filename = "test010_energy_spectrum_discrete.root" + phsp_actor.attached_to = phsp.name + phsp_actor.attributes = [ + "KineticEnergy", + ] + + # verbose + sim.g4_commands_after_init.append("/tracking/verbose 0") + + # start simulation + sim.run() + + # get info + ekin = root_load_ekin(str(phsp_actor.get_output_path())) + + # test + data_x = source.energy.spectrum_energies + + data_y = ( + np.array(source.energy.spectrum_weights) + * source.n + / np.sum(source.energy.spectrum_weights) + ) + + energy_counts = {} + for energy in ekin: + if energy not in energy_counts: + energy_counts[energy] = 0 + energy_counts[energy] += 1 + + output_y = [energy_counts[x] for x in data_x] + + relerrs = [abs(output_y[i] - data_y[i]) / data_y[i] for i in range(len(data_y))] + oks = [ + utility.check_diff_abs(0, relerr, tolerance=0.05, txt="relative error") + for relerr in relerrs + ] + is_ok = all(oks) + + plot( + paths.output / "test010_plot_energy_spectrum_discrete.png", + ekin, + data_x, + data_y, + relerrs, + ) + + utility.test_ok(is_ok) + + +if __name__ == "__main__": + paths = utility.get_default_test_paths( + __file__, "test010_generic_source_energy_spectrum", output_folder="test010" + ) + + run_simulation(paths) diff --git a/opengate/tests/src/test010_generic_source_energy_spectrum_histogram.py b/opengate/tests/src/test010_generic_source_energy_spectrum_histogram.py new file mode 100755 index 000000000..945e08f92 --- /dev/null +++ b/opengate/tests/src/test010_generic_source_energy_spectrum_histogram.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +from opengate.tests import utility +import numpy as np +import gatetools + +import matplotlib.pyplot as plt + + +def plot(output_file, ekin, data_x, data_y, relerrs): + bins = len(data_x) + hist_y, hist_x = np.histogram(ekin, bins=bins) + + fig, ax = plt.subplots(figsize=(8.5, 6)) + ax.set_xlabel("Energy (MeV)") + ax.set_ylabel("Number of particles") + ax.plot(data_x, data_y, label="input energy spectrum", marker="o") + ax.plot( + data_x, hist_y, label="simulated energy spectrum", marker=".", linewidth=0.9 + ) + ax.legend() + + ax2 = ax.twinx() + ax2.set_ylabel("Relative uncertainty") + ax2.set_ylim([0, 0.05]) + ax2.plot(data_x, relerrs, label="relative uncertainty", linewidth=0.4) + + fig.savefig(output_file, dpi=300) + plt.close(fig) + + +def root_load_ekin(root_file: str): + data_ref, keys_ref, m_ref = gatetools.phsp.load(root_file) + + index_ekin = keys_ref.index("KineticEnergy") + ekin = [data_ref_i[index_ekin] for data_ref_i in data_ref] + + return ekin + + +def add_source_energy_spectrum_histogram(sim, phsp, interpolation: str = None): + spectrum = gate.sources.generic.get_rad_beta_spectrum("Lu177") + + source = sim.add_source("GenericSource", "beam") + source.mother = phsp.name + source.particle = "gamma" + source.n = 5e5 / sim.number_of_threads + source.position.type = "point" + source.direction.type = "iso" + source.energy.type = "spectrum_histogram" + source.energy.spectrum_energy_bin_edges = spectrum.energy_bin_edges + source.energy.spectrum_weights = spectrum.weights + source.energy.spectrum_histogram_interpolation = interpolation + + return source + + +def run_simulation(paths, interpolation: str = None): + # create the simulation + sim = gate.Simulation() + + # main options + sim.g4_verbose = False + sim.g4_verbose_level = 1 + sim.visu = False + sim.number_of_threads = 1 + sim.random_seed = 987654321 + sim.output_dir = paths.output + + # units + m = gate.g4_units.m + g_cm3 = gate.g4_units.g_cm3 + + # materials + sim.volume_manager.material_database.add_material_weights( + "Vacuum", ["H"], [1], 1e-12 * g_cm3 + ) + + world = sim.world + world.size = [2 * m, 2 * m, 2 * m] + world.material = "Vacuum" + + phsp = sim.add_volume("Sphere", "phsp") + phsp.rmin = 0 * m + phsp.rmax = 1 * m + phsp.material = world.material + + source = add_source_energy_spectrum_histogram(sim, phsp, interpolation) + + # actors + stats = sim.add_actor("SimulationStatisticsActor", "Stats") + + phsp_actor = sim.add_actor("PhaseSpaceActor", "phsp_actor") + phsp_actor.output_filename = ( + f"test010_energy_spectrum_histogram_{interpolation}.root" + ) + # phsp_actor.attached_to = phsp + phsp_actor.attributes = [ + "KineticEnergy", + ] + + # verbose + sim.g4_commands_after_init.append("/tracking/verbose 0") + + # start simulation + sim.run(start_new_process=True) + + # get info + ekin = root_load_ekin(str(phsp_actor.get_output_path())) + + # test + bin_edges = source.energy.spectrum_energy_bin_edges + data_x = [(bin_edges[i] + bin_edges[i + 1]) / 2 for i in range(len(bin_edges) - 1)] + + data_y = ( + np.array(source.energy.spectrum_weights) + * source.n + / np.sum(source.energy.spectrum_weights) + ) + + bins = len(data_x) + hist_y, hist_x = np.histogram(ekin, bins=bins) + + relerrs = [abs(hist_y[i] - data_y[i]) / data_y[i] for i in range(len(data_y))] + oks = [ + utility.check_diff_abs(0, relerr, tolerance=0.05, txt="relative error") + for relerr in relerrs + ] + is_ok = all(oks) + + plot( + paths.output / f"test010_plot_energy_spectrum_histogram_{interpolation}.png", + ekin, + data_x, + data_y, + relerrs, + ) + + utility.test_ok(is_ok) + + +if __name__ == "__main__": + paths = utility.get_default_test_paths( + __file__, "test010_generic_source_energy_spectrum", output_folder="test010" + ) + + run_simulation(paths, interpolation=None) + # run_simulation(paths, interpolation="linear") # relative error ok until last values diff --git a/opengate/tests/src/test019_linac_elekta_versa.py b/opengate/tests/src/test019_linac_elekta_versa.py index 958e921d7..159dc978c 100755 --- a/opengate/tests/src/test019_linac_elekta_versa.py +++ b/opengate/tests/src/test019_linac_elekta_versa.py @@ -28,6 +28,7 @@ nm = gate.g4_units.nm m = gate.g4_units.m mm = gate.g4_units.mm + MeV = gate.g4_units.MeV # world world = sim.world @@ -43,7 +44,7 @@ ) # add linac e- source - source = versa.add_electron_source(sim, linac.name) + source = versa.add_electron_source(sim, linac.name, 6.49 * MeV, 1 * mm, 0.5 * mm) source.n = 8e4 / sim.number_of_threads if sim.visu: source.n = 200 @@ -70,6 +71,7 @@ # compare root br = "versa_phsp_plane_phsp" + print(paths.output_ref) root_ref = paths.output_ref / "phsp_versa_no_tr_no_rot.root" keys = ["KineticEnergy", "PrePositionLocal_X", "PrePositionLocal_Y"] tols = [0.1, 2.5, 2.5] diff --git a/opengate/tests/src/test019_linac_elekta_versa_rt_plan_isocenter.py b/opengate/tests/src/test019_linac_elekta_versa_rt_plan_isocenter.py index 0cb6b414c..a43327a99 100755 --- a/opengate/tests/src/test019_linac_elekta_versa_rt_plan_isocenter.py +++ b/opengate/tests/src/test019_linac_elekta_versa_rt_plan_isocenter.py @@ -25,7 +25,8 @@ def add_phase_space_isocenter(sim, name, pos): isocenter_sphere.material = "G4_Galactic" isocenter_sphere.mother = name isocenter_sphere.translation = pos - isocenter_sphere.radius = 1 * cm + isocenter_sphere.rmax = 0 + isocenter_sphere.rmax = 1 * cm isocenter_sphere.color = [0, 1, 0, 1] # red phsp = sim.add_actor("PhaseSpaceActor", f"phsp") diff --git a/opengate/tests/src/test019_linac_elekta_versa_with_mlc.py b/opengate/tests/src/test019_linac_elekta_versa_with_mlc.py index 53afb8f4c..86bd1847e 100755 --- a/opengate/tests/src/test019_linac_elekta_versa_with_mlc.py +++ b/opengate/tests/src/test019_linac_elekta_versa_with_mlc.py @@ -7,6 +7,7 @@ from opengate.tests import utility import numpy as np import uproot +import matplotlib.pyplot as plt def generalised_normal(x, A, mu, alpha, beta): @@ -29,7 +30,7 @@ def is_ok_test019(rootfile, x_field, y_field, tol=0.15): x = rootfile["PrePosition_X"][rootfile["ParticleName"] == "alpha"] y = rootfile["PrePosition_Y"][rootfile["ParticleName"] == "alpha"] - hist_x_pos = np.histogram(x, bins=100, density=True) + hist_x_pos = plt.hist(x, bins=100, density=True) x_hist_x_pos = hist_x_pos[1][:-1] + 0.5 * (hist_x_pos[1][1] - hist_x_pos[1][0]) median_hist_x_pos = np.median(hist_x_pos[0][hist_x_pos[0] > 0]) y_hist_x_pos = hist_x_pos[0] @@ -41,7 +42,7 @@ def is_ok_test019(rootfile, x_field, y_field, tol=0.15): generalised_normal, x_hist_x_pos, y_hist_x_pos, p0=p0_x_pos ) - hist_y_pos = np.histogram(y, bins=100, density=True) + hist_y_pos = plt.hist(y, bins=100, density=True) x_hist_y_pos = hist_y_pos[1][:-1] + 0.5 * (hist_y_pos[1][1] - hist_y_pos[1][0]) median_hist_y_pos = np.median(hist_y_pos[0][hist_y_pos[0] > 0]) y_hist_y_pos = hist_y_pos[0] @@ -84,7 +85,7 @@ def is_ok_test019(rootfile, x_field, y_field, tol=0.15): sim.check_volumes_overlap = False sim.number_of_threads = 1 sim.output_dir = paths.output # FIXME (not yet) - sim.random_seed = 123456789 + sim.random_seed = 12345678 sim.check_volumes_overlap = True sim.output_dir = paths.output @@ -97,7 +98,7 @@ def is_ok_test019(rootfile, x_field, y_field, tol=0.15): # world world = sim.world - world.size = [2 * m, 2 * m, 2 * m] + world.size = [2 * m, 2 * m, 2.2 * m] world.material = "G4_Galactic" a = np.array([0]) @@ -108,16 +109,17 @@ def is_ok_test019(rootfile, x_field, y_field, tol=0.15): linac.material = "G4_Galactic" # jaws - if sim.visu: - jaws = versa.add_jaws_visu(sim, linac.name) - else: - jaws = versa.add_jaws(sim, linac.name) + + jaws = versa.add_jaws(sim, linac.name) # mlc mlc = versa.add_mlc(sim, linac.name) x_field = np.random.randint(10, 20, 1)[0] * cm y_field = np.random.randint(10, 20, 1)[0] * cm - versa.set_rectangular_field(sim, mlc, jaws, x_field, y_field, sad) + versa.set_rectangular_field(mlc, jaws, x_field, y_field, sad) + + mlc_box = sim.volume_manager.get_volume(f"linac_box_mlc") + mlc_box.material = "G4_Galactic" # add alpha source source = sim.add_source("GenericSource", f"alpha_source") @@ -133,7 +135,7 @@ def is_ok_test019(rootfile, x_field, y_field, tol=0.15): source.direction.type = "iso" source.n = 5e5 / sim.number_of_threads if sim.visu: - source.n = 20 + source.n = 100 # physics sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" diff --git a/opengate/tests/src/test019_linac_elekta_versa_with_mlc_phsp_source.py b/opengate/tests/src/test019_linac_elekta_versa_with_mlc_phsp_source.py index 546ea3339..6985d4547 100755 --- a/opengate/tests/src/test019_linac_elekta_versa_with_mlc_phsp_source.py +++ b/opengate/tests/src/test019_linac_elekta_versa_with_mlc_phsp_source.py @@ -98,7 +98,7 @@ def is_ok_test019(rootfile, x_field, y_field, tol=0.15): # world world = sim.world - world.size = [2 * m, 2 * m, 2 * m] + world.size = [2 * m, 2 * m, 2.1 * m] world.material = "G4_Galactic" a = np.array([0]) @@ -109,16 +109,15 @@ def is_ok_test019(rootfile, x_field, y_field, tol=0.15): linac.material = "G4_Galactic" # jaws - if sim.visu: - jaws = versa.add_jaws_visu(sim, linac.name) - else: - jaws = versa.add_jaws(sim, linac.name) + jaws = versa.add_jaws(sim, linac.name) # mlc mlc = versa.add_mlc(sim, linac.name) + mlc_box = sim.volume_manager.get_volume(f"linac_box_mlc") + mlc_box.material = "G4_Galactic" x_field = np.random.randint(10, 20, 1)[0] * cm y_field = np.random.randint(10, 20, 1)[0] * cm - versa.set_rectangular_field(sim, mlc, jaws, x_field, y_field, sad) + versa.set_rectangular_field(mlc, jaws, x_field, y_field, sad) # add alpha source plan = versa.add_phase_space_plane(sim, linac.name, 300) diff --git a/opengate/tests/src/test019_linac_elekta_versa_with_mlc_rt_plan.py b/opengate/tests/src/test019_linac_elekta_versa_with_mlc_rt_plan.py index 39a208978..403e04c91 100755 --- a/opengate/tests/src/test019_linac_elekta_versa_with_mlc_rt_plan.py +++ b/opengate/tests/src/test019_linac_elekta_versa_with_mlc_rt_plan.py @@ -11,28 +11,23 @@ import itk -def calc_mlc_aperture( - x_leaf_position, y_jaws, pos_mlc=349.3, pos_jaws=470.5, sad=1000, leaf_width=1.85 -): - mm = gate.g4_units.mm - leaf_width = leaf_width * mm - left = x_leaf_position[:80] * pos_mlc / sad - right = x_leaf_position[80:] * pos_mlc / sad - left[left != 0] = left[left != 0] - left[left != 0] % 0.5 - right[right != 0] = right[right != 0] + 0.5 - right[right != 0] % 0.5 - - pos_y_leaf = np.arange( - -leaf_width * 40 + leaf_width / 2, - leaf_width * 40 - leaf_width / 2 + 0.01, - leaf_width, - ) - left[pos_y_leaf < y_jaws[0] * pos_jaws / sad] = 0 - left[pos_y_leaf > y_jaws[1] * pos_jaws / sad] = 0 - right[pos_y_leaf < y_jaws[0] * pos_jaws / sad] = 0 - right[pos_y_leaf > y_jaws[1] * pos_jaws / sad] = 0 - diff = np.array(right - left) +def calc_mlc_aperture(x_leaf_position, y_jaws_position): + + left = x_leaf_position[:80] + right = x_leaf_position[80:] + proj_leaves_y = np.arange(-40 * 5 + 2.5, 40 * 5 - 2.5 + 0.1, 5) + + left[ + (proj_leaves_y - 2.5 < y_jaws_position[0]) + | (proj_leaves_y + 2.5 > y_jaws_position[1]) + ] = 0 + right[ + (proj_leaves_y - 2.5 < y_jaws_position[0]) + | (proj_leaves_y + 2.5 > y_jaws_position[1]) + ] = 0 + diff = np.array(np.abs(right - left)) * 5 - return np.sum(diff) * leaf_width + return np.sum(diff) def add_volume_to_irradiate(sim, name, l_cp): @@ -42,11 +37,11 @@ def add_volume_to_irradiate(sim, name, l_cp): plane.mother = name plane.material = "G4_WATER" plane.size = [0.4 * m, 0.4 * m, 2 * cm] - plane.translation = [0 * mm, 0 * mm, 0 * mm] + plane.translation = [0 * mm, 0 * mm, -1 * cm] plane.color = [1, 0, 0, 1] # red - voxel_size_x = 0.5 * mm - voxel_size_y = 0.5 * mm + voxel_size_x = 1.2 * mm + voxel_size_y = 1.2 * mm voxel_size_z = 2 * cm dim_box = [ @@ -73,15 +68,18 @@ def add_volume_to_irradiate(sim, name, l_cp): plane.add_dynamic_parametrisation(rotation=rotations, translation=translations) -def add_alpha_source(sim, name, pos_Z, nb_part): +def add_alpha_source(sim, name, nb_part): + linac = sim.volume_manager.get_volume(name) + z_linac = linac.size[2] mm = gate.g4_units.mm nm = gate.g4_units.nm + deg = gate.g4_units.deg plan_source = sim.add_volume("Box", "plan_alpha_source") plan_source.material = "G4_Galactic" plan_source.mother = name - plan_size = np.array([250 * mm, 148 * mm, 1 * nm]) + plan_size = np.array([1 * nm, 1 * nm, 1 * nm]) plan_source.size = np.copy(plan_size) - plan_source.translation = [0 * mm, 0 * mm, -pos_Z / 2 + 300 * mm] + plan_source.translation = [0 * mm, 0 * mm, z_linac / 2 - 3.5 * mm] source = sim.add_source("GenericSource", "alpha_source") Bq = gate.g4_units.Bq @@ -92,10 +90,12 @@ def add_alpha_source(sim, name, pos_Z, nb_part): source.energy.mono = 1 * MeV source.position.type = "box" source.position.size = np.copy(plan_size) - source.direction.type = "momentum" + source.direction.type = "iso" source.direction_relative_to_attached_volume = True source.direction.momentum = [0, 0, -1] source.activity = nb_part * Bq / sim.number_of_threads + source.direction.theta = [0, 17 * deg] + source.direction.phi = [0, 360 * deg] def validation_test_19_rt_plan( @@ -105,7 +105,7 @@ def validation_test_19_rt_plan( rt_plan_parameters, nb_part_init, nb_part_sent, - tol=0.1, + tol=0.2, ): print( "Area from theoretical calculations for the chosen CP:", @@ -149,11 +149,12 @@ def validation_test_19_rt_plan( # main options sim.g4_verbose = False # sim.visu = True - sim.visu_type = "vrml" + sim.visu_type = "qt" sim.check_volumes_overlap = False sim.number_of_threads = 1 sim.output_dir = paths.output sim.random_seed = 123456789 + # sim.random_seed = "auto" sim.check_volumes_overlap = True # unit @@ -178,21 +179,21 @@ def validation_test_19_rt_plan( linac.material = "G4_Galactic" # jaws - if sim.visu: - jaws = versa.add_jaws_visu(sim, linac.name) - else: - jaws = versa.add_jaws(sim, linac.name) + + jaws = versa.add_jaws(sim, linac.name) # mlc mlc = versa.add_mlc(sim, linac.name) - + mlc_box = sim.volume_manager.get_volume(f"linac_box_mlc") + mlc_box.material = "G4_Galactic" # add alpha source : - nb_part = 750000 + nb_part = 1000000 z_linac = linac.size[2] rt_plan_parameters = rtplan.read(str(paths.data / "DICOM_RT_plan.dcm")) MU = 0 while MU == 0: l_cp = [np.random.randint(0, len(rt_plan_parameters["jaws 1"]), 1)[0]] + # l_cp = [3] MU = rt_plan_parameters["weight"][l_cp[0]] nb_part = nb_part / MU versa.set_linac_head_motion( @@ -200,9 +201,9 @@ def validation_test_19_rt_plan( ) if sim.visu: - add_alpha_source(sim, linac.name, z_linac / 2 - 5.6 * mm, 10) + add_alpha_source(sim, linac.name, 100) else: - add_alpha_source(sim, linac.name, z_linac / 2 - 5.6 * mm, nb_part) + add_alpha_source(sim, linac.name, nb_part) # Linac head rotation and jaws and mlc translation according to a DICOM rt plan @@ -221,24 +222,29 @@ def validation_test_19_rt_plan( # The number of particles provided (sim.activity) will be adapted # according to the number of MU delivered at each control points. versa.set_time_intervals_from_rtplan(sim, rt_plan_parameters, cp_id=l_cp) + # leaves_position = rt_plan_parameters["leaves"] + # jaw_1_positions = rt_plan_parameters["jaws 1"] + # jaw_2_positions = rt_plan_parameters["jaws 2"] + + leaves = rt_plan_parameters["leaves"][l_cp[0]] + jaws_1 = rt_plan_parameters["jaws 1"][l_cp[0]] + jaws_2 = rt_plan_parameters["jaws 2"][l_cp[0]] + jaws = [jaws_1, jaws_2] + theoretical_area = calc_mlc_aperture(leaves, jaws) + sim.run() # print results print(stats) # test - leaves = rt_plan_parameters["leaves"][l_cp[0]] - jaws_1 = rt_plan_parameters["jaws 1"][l_cp[0]] - jaws_2 = rt_plan_parameters["jaws 2"][l_cp[0]] - jaws = [jaws_1, jaws_2] - theoretical_area = calc_mlc_aperture(leaves, jaws, sad=sad) dose2 = sim.get_actor("dose_water_slice") - img_MC = dose2.edep.get_data() + img_MC = dose2.edep.image # img_MC = itk.imread(dose2.get_output_path("edep")) array_MC = itk.GetArrayFromImage(img_MC) bool_MC = array_MC[array_MC != 0] - simulated_area = len(bool_MC) / 4 + simulated_area = len(bool_MC) * 1.44 is_ok = validation_test_19_rt_plan( theoretical_area, simulated_area, diff --git a/opengate/tests/src/test043_garf_helpers.py b/opengate/tests/src/test043_garf_helpers.py index 6a8e38127..98e386624 100644 --- a/opengate/tests/src/test043_garf_helpers.py +++ b/opengate/tests/src/test043_garf_helpers.py @@ -46,7 +46,9 @@ def sim_phys(sim): def sim_source_test(sim, activity): - w, e = gate.sources.generic.get_rad_gamma_energy_spectrum("Tc99m") + tc99m = gate.sources.generic.get_rad_gamma_spectrum("Tc99m") + e = tc99m.energies + w = tc99m.weights # first sphere s1 = sim.add_source("GenericSource", "s1") @@ -57,9 +59,9 @@ def sim_source_test(sim, activity): s1.position.translation = [0, 0, 0] s1.direction.type = "momentum" s1.direction.momentum = [0, 0, -1] - s1.energy.type = "spectrum_lines" - s1.energy.spectrum_energy = e - s1.energy.spectrum_weight = w + s1.energy.type = "spectrum_discrete" + s1.energy.spectrum_energies = e + s1.energy.spectrum_weights = w # second sphere s2 = sim.add_source("GenericSource", "s2") @@ -69,9 +71,9 @@ def sim_source_test(sim, activity): s2.position.radius = 15 * mm s2.position.translation = [15 * cm, 0, 0] s2.direction.type = "iso" - s2.energy.type = "spectrum_lines" - s2.energy.spectrum_energy = e - s2.energy.spectrum_weight = w + s2.energy.type = "spectrum_discrete" + s2.energy.spectrum_energies = e + s2.energy.spectrum_weights = w # third sphere s3 = sim.add_source("GenericSource", "s3") @@ -81,9 +83,9 @@ def sim_source_test(sim, activity): s3.position.radius = 28 * mm s3.position.translation = [-10 * cm, 5 * cm, 0] s3.direction.type = "iso" - s3.energy.type = "spectrum_lines" - s3.energy.spectrum_energy = e - s3.energy.spectrum_weight = w + s3.energy.type = "spectrum_discrete" + s3.energy.spectrum_energies = e + s3.energy.spectrum_weights = w return s1, s2, s3 diff --git a/opengate/tests/src/test046_rad.py b/opengate/tests/src/test046_rad.py index a98963148..0dd555683 100755 --- a/opengate/tests/src/test046_rad.py +++ b/opengate/tests/src/test046_rad.py @@ -67,8 +67,8 @@ yields = [0.885, 0.172168, 1.847315, 1.0024600000000004] i = 0 for rad in radionuclides: - w, e = gate.sources.generic.get_rad_gamma_energy_spectrum(rad) - tw = np.array(w).sum() + rad_spectrum = gate.sources.generic.get_rad_gamma_spectrum(rad) + tw = rad_spectrum["weights"].sum() ok = tw == yields[i] utility.print_test(ok, f"Test yield {rad}: {tw} {yields[i]} {ok}") is_ok = is_ok and ok diff --git a/opengate/tests/src/test053_phid_helpers1.py b/opengate/tests/src/test053_phid_helpers1.py index 7f69bb4d6..2e00e7d67 100644 --- a/opengate/tests/src/test053_phid_helpers1.py +++ b/opengate/tests/src/test053_phid_helpers1.py @@ -178,7 +178,7 @@ def analyse_ion_gamma_from_root(filename, ion_names, events_nb): print("event id", e["EventID"]) print(f"track {len(track)}") print(f"track {track}") - exit(0) + sys.exit(0) ion = track[e["ParentID"]]["ParticleName"] # ene = e["KineticEnergy"] # if ene < 100 * keV: @@ -248,7 +248,7 @@ def analyse(paths, sim, output, ion_name, z, a, daughters, log_flag=True, tol=0. # direct computation of gammas print() - print(f"Data extracted from the database") + print("Data extracted from the database") ge = PhotonIonDecayIsomericTransitionExtractor( z, a, verbose=True ) ## FIXME change verbose @@ -270,7 +270,7 @@ def analyse(paths, sim, output, ion_name, z, a, daughters, log_flag=True, tol=0. f_mc = str(paths.output / f"test053_{ion_name}_mc.txt") with open(f_mc, "w") as f: f.write(f"# gamma intensity for {ion_name} -> {' '.join(daughters)}\n") - f.write(f"# from Monte Carlo\n") + f.write("# from Monte Carlo\n") for e, w in zip(g2_ene, g2_w): f.write(f"{e} {w}\n") @@ -278,7 +278,7 @@ def analyse(paths, sim, output, ion_name, z, a, daughters, log_flag=True, tol=0. f_model = str(paths.output / f"test053_{ion_name}_model.txt") with open(f_model, "w") as f: f.write(f"# gamma intensity for {ion_name} -> {' '.join(daughters)}\n") - f.write(f"# from model\n") + f.write("# from model\n") for e, w in zip(g1_ene, g1_w): f.write(f"{e} {w}\n") diff --git a/opengate/tests/src/test053_production_cuts.py b/opengate/tests/src/test053_production_cuts.py index d9b611763..314145e5c 100755 --- a/opengate/tests/src/test053_production_cuts.py +++ b/opengate/tests/src/test053_production_cuts.py @@ -156,7 +156,7 @@ def simulate(number_of_threads=1, start_new_process=False): print(f"Volume {item[0]}:") value_dict = item[1] if item[0] != "world": - if item[0] in requested_cuts_proton.keys(): + if item[0] in requested_cuts_proton: print( f"Requested production cut for protons: {requested_cuts_proton[item[0]]}" ) @@ -196,7 +196,7 @@ def check_production_cuts(simulation_engine): which can be accessed via the output of the simulation. """ - print(f"Entered hook") + print("Entered hook") for ( volume_name, volume, diff --git a/opengate/tests/src/test066_spect_gaga_garf_helpers.py b/opengate/tests/src/test066_spect_gaga_garf_helpers.py new file mode 100644 index 000000000..bb10e4fe9 --- /dev/null +++ b/opengate/tests/src/test066_spect_gaga_garf_helpers.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +import opengate.contrib.phantoms.nemaiec as gate_iec +import opengate.sources.gansources as gansources +from opengate.sources.generic import get_rad_gamma_spectrum +from opengate.contrib.spect import ge_discovery_nm670 +from scipy.spatial.transform import Rotation + + +def create_world(sim): + # world size + m = gate.g4_units.m + sim.world.size = [2 * m, 2 * m, 2 * m] + sim.world.material = "G4_AIR" + + +def set_phys(sim): + m = gate.g4_units.m + sim.physics_manager.physics_list = "G4EmStandardPhysics_option3" + sim.physics_manager.set_production_cut("world", "all", 1e3 * m) + + +def create_simu_with_genm670(sim, collimator_type="lehr", debug=False): + # units + mm = gate.g4_units.mm + + # world size + create_world(sim) + + # spect system + head1, crystal1 = genm670.add_spect_head( + sim, "spect1", collimator_type=collimator_type, debug=debug + ) + head2, crystal2 = genm670.add_spect_head( + sim, "spect2", collimator_type=collimator_type, debug=debug + ) + + # default rotation to be in front of the IEC + radius = 280 * mm + genm670.set_head_orientation(head1, collimator_type, radius, 0) + genm670.set_head_orientation(head2, collimator_type, radius, 180) + + # digitizer + keV = gate.g4_units.keV + channels = [ + {"name": f"scatter", "min": 114 * keV, "max": 126 * keV}, + {"name": f"peak140", "min": 126 * keV, "max": 154 * keV}, + ] + proj1 = genm670.add_digitizer(sim, crystal1.name, channels) + proj2 = genm670.add_digitizer(sim, crystal2.name, channels) + proj1.spacing = [3 * mm, 3 * mm] + proj2.spacing = [3 * mm, 3 * mm] + + # stats + sim.add_actor("SimulationStatisticsActor", "stats") + + # phys + set_phys(sim) + sim.physics_manager.set_production_cut("spect1", "all", 1 * mm) + sim.physics_manager.set_production_cut("spect2", "all", 1 * mm) + + return proj1, proj2 + + +def create_simu_with_gaga( + sim, total_activity, activity_source, gaga_pth_filename, garf_pth_filename +): + # world size + create_world(sim) + + # add gaga source + gsource = add_gaga_source(sim, total_activity, activity_source, gaga_pth_filename) + gsource.position.rotation = Rotation.from_euler("x", 90, degrees=True).as_matrix() + + # add arf plane + mm = gate.g4_units.mm + colli_type = "lehr" + radius = 280 * mm + size = [128, 128] + spacing = [3 * mm, 3 * mm] + plane_size = [size[0] * spacing[0], size[1] * spacing[1]] + _, crystal_dist, _ = genm670.compute_plane_position_and_distance_to_crystal( + colli_type + ) + det1 = genm670.add_detection_plane_for_arf( + sim, plane_size, colli_type, radius, 0, "det1" + ) + det2 = genm670.add_detection_plane_for_arf( + sim, plane_size, colli_type, radius, 180, "det2" + ) + + print(f"{crystal_dist=}") + + # add actors + arf1 = add_arf_actor( + sim, det1, size, spacing, crystal_dist, "arf1", garf_pth_filename + ) + arf2 = add_arf_actor( + sim, det2, size, spacing, crystal_dist, "arf2", garf_pth_filename + ) + + # stats + sim.add_actor("SimulationStatisticsActor", "stats") + + # phys + set_phys(sim) + + return arf1, arf2 + + +def add_gaga_source(sim, total_activity, activity_source, gaga_pth_filename): + mm = gate.g4_units.mm + keV = gate.g4_units.keV + gsource = sim.add_source("GANSource", "gaga") + gsource.particle = "gamma" + gsource.activity = total_activity + gsource.pth_filename = gaga_pth_filename + gsource.position_keys = ["PrePosition_X", "PrePosition_Y", "PrePosition_Z"] + gsource.backward_distance = 50 * mm + gsource.backward_force = True + gsource.direction_keys = ["PreDirection_X", "PreDirection_Y", "PreDirection_Z"] + gsource.energy_key = "KineticEnergy" + gsource.energy_min_threshold = 10 * keV + gsource.skip_policy = "ZeroEnergy" + gsource.weight_key = None + gsource.time_key = None + gsource.batch_size = 5e4 # OSX + # Linux 5e4 with 1 thread, 1e4 with 8 threads + gsource.batch_size = 2e5 / sim.user_info.number_of_threads + gsource.verbose_generator = False # True + gsource.gpu_mode = "auto" + + # conditional generator + cond_generator = gansources.VoxelizedSourceConditionGenerator( + activity_source, use_activity_origin=True + ) + cond_generator.compute_directions = True + gene = gansources.GANSourceConditionalGenerator( + gsource, cond_generator.generate_condition + ) + gsource.generator = gene + return gsource + + +def add_garf_detector_planes_OLD(sim, plane_size, head_radius): + cm = gate.g4_units.cm + nm = gate.g4_units.nm + pos, crystal_dist, psd = genm670.compute_plane_position_and_distance_to_crystal( + "lehr" + ) + print(pos, crystal_dist, psd) + print(f"The detector plane position = {pos} mm") + print(f"The detector head radius = {head_radius} mm") + + detector_plane1 = sim.add_volume("Box", "det_plane1") + detector_plane1.material = "G4_Galactic" + detector_plane1.color = [1, 0, 0, 1] + detector_plane1.size = [plane_size[0], plane_size[1], 1 * nm] + r = Rotation.from_euler("x", 0, degrees=True) + detector_plane1.rotation = r.as_matrix() + detector_plane1.translation = [0, 0, -head_radius - pos] + print(f"{detector_plane1.translation=}") + + detector_plane2 = sim.add_volume("Box", "det_plane2") + detector_plane2.material = "G4_Galactic" + detector_plane2.color = [1, 0, 0, 1] + detector_plane2.size = [plane_size[0], plane_size[1], 1 * nm] + r = Rotation.from_euler("y", 180, degrees=True) + detector_plane2.rotation = r.as_matrix() + detector_plane2.translation = [0, 0, -head_radius - pos] + detector_plane2.translation = r.apply(detector_plane2.translation) + print(f"{detector_plane2.translation=}") + # FIXME apply rotation to translation + + return detector_plane1, detector_plane2 + + +def add_arf_actor(sim, detector_plane, size, spacing, crystal_dist, name, pth_filename): + # arf actor + arf = sim.add_actor("ARFActor", f"arf_{name}") + arf.mother = detector_plane.name + arf.batch_size = 1e5 + arf.image_size = size + arf.image_spacing = spacing + arf.verbose_batch = True + arf.distance_to_crystal = crystal_dist + arf.gpu_mode = "auto" + arf.enable_hit_slice = False + arf.pth_filename = pth_filename + return arf + + +def set_duration(sim, total_activity, w, duration): + Bq = gate.g4_units.Bq + sec = gate.g4_units.second + ui = sim.user_info + nb_decays = total_activity / Bq * duration / sec * ui.number_of_threads + weights = sum(w) + print(f"Estimated total decay {nb_decays:.0f} decays") + print( + f"Estimated gammas {nb_decays * weights:.0f} gammas (weights = {weights:.4f})" + ) + sim.run_timing_intervals = [[0, duration]] + + +def add_iec_Tc99m_source(sim, activity_concentration): + cm3 = gate.g4_units.cm3 + Bq = gate.g4_units.Bq + kBq = 1000 * Bq + MBq = 1000 * kBq + BqmL = Bq / cm3 + ui = sim.user_info + + # same concentration in all spheres + a = activity_concentration / ui.number_of_threads + all_activities = [a] * 6 + + # Tc99m + sources = gate_iec.add_spheres_sources(sim, "iec", "src", "all", all_activities) + total_activity = 0 + print(f"Activity concentration = {all_activities[5] / BqmL:.0f} BqmL") + tc99m = get_rad_gamma_spectrum("Tc99m") + w = tc99m.weights + e = tc99m.energies + + for source in sources: + source.particle = "gamma" + source.energy.type = "spectrum_discrete" + source.energy.spectrum_weights = w + source.energy.spectrum_energies = e + total_activity += source.activity + print( + f"Activity {source.name} = {source.activity / Bq:.2f} Bq {activity_concentration / BqmL:.0f} Bq/mL" + ) + + print(f"Total activity 1 thread = {total_activity / Bq:.2f} Bq") + print(f"Total activity 1 thread = {total_activity / kBq:.2f} kBq") + print(f"Total activity 1 thread = {total_activity / MBq:.2f} MBq") + print( + f"Total activity {ui.number_of_threads} threads = {total_activity / MBq * ui.number_of_threads:.2f} MBq" + ) + + return total_activity, w, e diff --git a/opengate/tests/src/test066_stop_simulation_criteria_multiple_runs_mt.py b/opengate/tests/src/test066_stop_simulation_criteria_multiple_runs_mt.py index 2fe665309..60625ffa5 100755 --- a/opengate/tests/src/test066_stop_simulation_criteria_multiple_runs_mt.py +++ b/opengate/tests/src/test066_stop_simulation_criteria_multiple_runs_mt.py @@ -106,6 +106,7 @@ def calculate_mean_unc(edep_arr, unc_arr, edep_thresh_rel=0.7): mm = gate.g4_units.mm dose.spacing = [2.5 * mm, 2.5 * mm, 2.5 * mm] dose.edep_uncertainty.active = True + dose.edep.keep_data_per_run = True dose.uncertainty_goal = unc_goal_run dose.uncertainty_first_check_after_n_events = 100 dose.uncertainty_voxel_edep_threshold = thresh_voxel_edep_for_unc_calc @@ -125,7 +126,29 @@ def calculate_mean_unc(edep_arr, unc_arr, edep_thresh_rel=0.7): # print results at the end print(stat) - # test that final mean uncertainty satisfies the goal uncertainty + ok = True + # test that the uncertainty goal was met in each run: + for i in range(len(sim.run_timing_intervals)): + edep_arr = np.asarray(dose.edep.get_data(which=i)) + unc_arr = np.asarray(dose.edep_uncertainty.get_data(which=i)) + unc_mean = calculate_mean_unc( + edep_arr, unc_arr, edep_thresh_rel=thresh_voxel_edep_for_unc_calc + ) + b = unc_mean < unc_goal_run + print( + f"For run index {i}: \n" + f" mean uncertainty = {unc_mean}, " + f" goal = {unc_goal_run}" + f" OK? -> {b}" + ) + ok &= b + + # Test that mean uncertainty satisfies the goal uncertainty + # in the edep image merged over the runs + # The goal might not be strictly met + # because the voxels over which the uncertainty is calculated are not strictly the same + # because they are threshold-based + # Therefore, we allow some margin around the goal test_thresh_rel = 0.01 edep_arr = np.asarray(dose.edep.image) @@ -136,7 +159,11 @@ def calculate_mean_unc(edep_arr, unc_arr, edep_thresh_rel=0.7): ) print(f"{unc_expected = }") print(f"{unc_mean = }") - ok = unc_mean < unc_expected and unc_mean > unc_expected - test_thresh_rel + ok &= ( + unc_mean < unc_expected + test_thresh_rel + and unc_mean > unc_expected - test_thresh_rel + ) + print("OK? -> ", ok) # test that the simulation stopped because of the threshold crtierion, # and not simply because we reached the planned number of events @@ -144,6 +171,8 @@ def calculate_mean_unc(edep_arr, unc_arr, edep_thresh_rel=0.7): n_effective = stat.counts.events print(f"{n_planned = }") print(f"{n_effective = }") - ok = ok and n_effective < n_planned + b = n_effective < n_planned + print("OK? -> ", b) + ok = ok and b utility.test_ok(ok) diff --git a/opengate/tests/src/test072_pseudo_transport_RR.py b/opengate/tests/src/test072_pseudo_transport_RR.py new file mode 100644 index 000000000..3ed0038e4 --- /dev/null +++ b/opengate/tests/src/test072_pseudo_transport_RR.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +import uproot +import numpy as np +import matplotlib.pyplot as plt +import matplotlib as mpl +import scipy +from scipy.spatial.transform import Rotation +from opengate.tests import utility + + +def Ekin_per_event(weight, EventID, Ekin): + l_edep = [] + Edep_per_event = 0 + last_EventID = 0 + for i in range(len(weight)): + if i > 0: + last_EventID = EventID[i - 1] + if EventID[i] != last_EventID and i > 0: + l_edep.append(Edep_per_event) + Edep_per_event = 0 + Edep_per_event += Ekin[i] * weight[i] + return np.asarray(l_edep) + + +def validation_test(arr_no_RR, arr_RR, nb_event, splitting_factor, tol=0.15): + + Tracks_no_RR = arr_no_RR[ + (arr_no_RR["TrackCreatorProcess"] == "biasWrapper(compt)") + & (arr_no_RR["ParticleName"] == "gamma") + ] + + Tracks_RR = arr_RR[ + (arr_RR["TrackCreatorProcess"] == "biasWrapper(compt)") + & (arr_RR["ParticleName"] == "gamma") + ] + + Ekin_no_RR = Tracks_no_RR["KineticEnergy"] + w_no_RR = Tracks_no_RR["Weight"] + Event_ID_no_RR = Tracks_no_RR["EventID"] + + Ekin_per_event_no_RR = Ekin_per_event(w_no_RR, Event_ID_no_RR, Ekin_no_RR) + + Ekin_RR = Tracks_RR["KineticEnergy"] + w_RR = Tracks_RR["Weight"] + Event_ID_RR = Tracks_RR["EventID"] + + bool_RR = (np.min(w_RR) > 0.1 * 1 / splitting_factor) & ( + np.max(w_RR) < 1 / splitting_factor + ) + + Ekin_per_event_RR = Ekin_per_event(w_RR, Event_ID_RR, Ekin_RR) + + mean_ekin_event_no_RR = np.sum(Ekin_per_event_no_RR) / nb_event + mean_ekin_event_RR = np.sum(Ekin_per_event_RR) / nb_event + diff = (mean_ekin_event_RR - mean_ekin_event_no_RR) / mean_ekin_event_RR + print( + "Mean kinetic energy per event without russian roulette :", + np.round(1000 * mean_ekin_event_no_RR, 1), + "keV", + ) + print( + "Mean kinetic energy per event with russian roulette :", + np.round(1000 * mean_ekin_event_RR, 1), + "keV", + ) + print("Percentage of difference :", diff * 100, "%") + + return (abs(diff) < tol) & (bool_RR) + # mean_energy_per_history_no_RR = np.sum(Tracks_no_RR["KineticEnergy"]*Tracks_no_RR["Weight"])/nb_event + # mean_energy_per_history_RR = np.sum(Tracks_RR["KineticEnergy"] * Tracks_RR["Weight"]) / nb_event + + # print(mean_energy_per_history_no_RR,mean_energy_per_history_RR) + + +if __name__ == "__main__": + + for j in range(2): + paths = utility.get_default_test_paths( + __file__, "test072_pseudo_transportation", output_folder="test072" + ) + + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + + # ui.visu = True + # ui.visu_type = "vrml" + ui.check_volumes_overlap = False + # ui.running_verbose_level = gate.EVENT + ui.number_of_threads = 1 + ui.random_seed = "auto" + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + um = gate.g4_units.um + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + gcm3 = gate.g4_units.g / gate.g4_units.cm3 + deg = gate.g4_units.deg + + # adapt world size + world = sim.world + world.size = [1.2 * m, 1.2 * m, 2 * m] + world.material = "G4_Galactic" + + ####### GEOMETRY TO IRRADIATE ############# + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + simple_collimation = sim.add_volume("Box", "colli_box") + simple_collimation.material = "G4_Galactic" + simple_collimation.mother = world.name + simple_collimation.size = [1 * m, 1 * m, 40 * cm] + simple_collimation.color = [0.3, 0.1, 0.8, 1] + + W_leaf = sim.add_volume("Box", "W_leaf") + W_leaf.mother = simple_collimation.name + W_leaf.size = [1 * m, 1 * m, 0.5 * cm] + W_leaf.material = "Tungsten" + leaf_translation = [] + for i in range(10): + leaf_translation.append( + [ + 0, + 0, + -0.5 * simple_collimation.size[2] + + 0.5 * W_leaf.size[2] + + i * W_leaf.size[2], + ] + ) + W_leaf.translation = leaf_translation + W_leaf.color = [0.8, 0.2, 0.1, 1] + + ######## pseudo_transportation ACTOR ######### + nb_split = 5 + pseudo_transportation_actor = sim.add_actor( + "ComptPseudoTransportationActor", "pseudo_transportation_actor" + ) + pseudo_transportation_actor.mother = simple_collimation.name + pseudo_transportation_actor.splitting_factor = nb_split + pseudo_transportation_actor.relative_min_weight_of_particle = 10000 + if j == 0: + pseudo_transportation_actor.russian_roulette_for_weights = False + if j == 1: + pseudo_transportation_actor.russian_roulette_for_weights = True + list_processes_to_bias = pseudo_transportation_actor.gamma_processes + + ##### PHASE SPACE plan ######" + plan = sim.add_volume("Box", "phsp") + plan.material = "G4_Galactic" + plan.mother = world.name + plan.size = [1 * m, 1 * m, 1 * nm] + plan.color = [0.2, 1, 0.8, 1] + plan.translation = [0, 0, -20 * cm - 1 * nm] + + ####### gamma source ########### + nb_event = 20000 + source = sim.add_source("GenericSource", "source1") + source.particle = "gamma" + source.n = nb_event + source.position.type = "sphere" + source.position.radius = 1 * nm + source.direction.type = "momentum" + source.direction.momentum = [0, 0, -1] + source.energy.type = "mono" + source.energy.mono = 6 * MeV + source.position.translation = [0, 0, 18 * cm] + + ####### PHASE SPACE ACTOR ############## + + phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + phsp_actor.mother = plan.name + phsp_actor.attributes = [ + "EventID", + "TrackCreatorProcess", + "TrackVertexPosition", + "PrePosition", + "Weight", + "KineticEnergy", + "ParentID", + "ParticleName", + ] + data_name = "test072_output_data_RR_" + str(j) + ".root" + phsp_actor.output = paths.output / data_name + + ##### MODIFIED PHYSICS LIST ############### + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + ### Perhaps avoid the user to call the below boolean function ? ### + sim.physics_manager.special_physics_constructors.G4GenericBiasingPhysics = True + sim.physics_manager.processes_to_bias.gamma = list_processes_to_bias + s = f"/process/em/UseGeneralProcess false" + sim.add_g4_command_before_init(s) + + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1 * km + sim.physics_manager.global_production_cuts.positron = 1 * km + + output = sim.run(True) + + # + # # print results + stats = sim.output.get_actor("Stats") + h = sim.output.get_actor("PhaseSpace") + print(stats) + # + + f_1 = uproot.open(paths.output / "test072_output_data_RR_0.root") + f_2 = uproot.open(paths.output / "test072_output_data_RR_1.root") + + arr_no_RR = f_1["PhaseSpace"].arrays() + arr_RR = f_2["PhaseSpace"].arrays() + + is_ok = validation_test(arr_no_RR, arr_RR, nb_event, nb_split) + utility.test_ok(is_ok) diff --git a/opengate/tests/src/test072_pseudo_transportation.py b/opengate/tests/src/test072_pseudo_transportation.py new file mode 100644 index 000000000..64f8e822f --- /dev/null +++ b/opengate/tests/src/test072_pseudo_transportation.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +import uproot +import numpy as np +import matplotlib.pyplot as plt +import matplotlib as mpl +import scipy +from scipy.spatial.transform import Rotation +from opengate.tests import utility + + +def validation_test(arr, NIST_data, nb_split, tol=0.01): + tab_ekin = NIST_data[:, 0] + mu_att = NIST_data[:, -2] + + log_ekin = np.log(tab_ekin) + log_mu = np.log(mu_att) + + f_mu = scipy.interpolate.interp1d(log_ekin, log_mu, kind="cubic") + # print(arr["TrackCreatorProcess"]) + Tracks = arr[ + (arr["TrackCreatorProcess"] == "biasWrapper(compt)") + & (arr["KineticEnergy"] > 0.1) + & (arr["ParticleName"] == "gamma") + & (arr["Weight"] > 10 ** (-20)) + ] + ekin_tracks = Tracks["KineticEnergy"] + x_vertex = Tracks["TrackVertexPosition_X"] + y_vertex = Tracks["TrackVertexPosition_Y"] + z_vertex = Tracks["TrackVertexPosition_Z"] + x = Tracks["PrePosition_X"] + y = Tracks["PrePosition_Y"] + z = Tracks["PrePosition_Z"] + weights = Tracks["Weight"] * nb_split + dist = np.sqrt((x - x_vertex) ** 2 + (y - y_vertex) ** 2 + (z - z_vertex) ** 2) + + G4_mu = -np.log(weights) / (0.1 * dist * 19.3) + X_com_mu = np.exp(f_mu(np.log(ekin_tracks))) + diff = (G4_mu - X_com_mu) / G4_mu + print( + "Median difference between mu calculated from XCOM database and from GEANT4 free flight operation:", + np.round(100 * np.median(diff), 1), + "%", + ) + return np.median(diff) < tol + + +if __name__ == "__main__": + paths = utility.get_default_test_paths( + __file__, "test072_pseudo_transportation", output_folder="test072" + ) + + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + + # ui.visu = True + # ui.visu_type = "vrml" + ui.check_volumes_overlap = False + # ui.running_verbose_level = gate.EVENT + ui.number_of_threads = 1 + ui.random_seed = "auto" + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + um = gate.g4_units.um + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + gcm3 = gate.g4_units.g / gate.g4_units.cm3 + deg = gate.g4_units.deg + + # adapt world size + world = sim.world + world.size = [1.2 * m, 1.2 * m, 2 * m] + world.material = "G4_Galactic" + + ####### GEOMETRY TO IRRADIATE ############# + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + simple_collimation = sim.add_volume("Box", "colli_box") + simple_collimation.material = "G4_Galactic" + simple_collimation.mother = world.name + simple_collimation.size = [1 * m, 1 * m, 40 * cm] + simple_collimation.color = [0.3, 0.1, 0.8, 1] + + W_leaf = sim.add_volume("Box", "W_leaf") + W_leaf.mother = simple_collimation.name + W_leaf.size = [1 * m, 1 * m, 2 * cm] + W_leaf.material = "Tungsten" + leaf_translation = [] + for i in range(10): + leaf_translation.append( + [ + 0, + 0, + -0.5 * simple_collimation.size[2] + + 0.5 * W_leaf.size[2] + + i * W_leaf.size[2], + ] + ) + print(leaf_translation) + W_leaf.translation = leaf_translation + W_leaf.color = [0.8, 0.2, 0.1, 1] + + ######## pseudo_transportation ACTOR ######### + nb_split = 5 + pseudo_transportation_actor = sim.add_actor( + "ComptPseudoTransportationActor", "pseudo_transportation_actor" + ) + pseudo_transportation_actor.mother = simple_collimation.name + pseudo_transportation_actor.splitting_factor = nb_split + pseudo_transportation_actor.relative_min_weight_of_particle = np.inf + list_processes_to_bias = pseudo_transportation_actor.gamma_processes + + ##### PHASE SPACE plan ######" + plan = sim.add_volume("Box", "phsp") + plan.material = "G4_Galactic" + plan.mother = world.name + plan.size = [1 * m, 1 * m, 1 * nm] + plan.color = [0.2, 1, 0.8, 1] + plan.translation = [0, 0, -20 * cm - 1 * nm] + + ####### gamma source ########### + source = sim.add_source("GenericSource", "source1") + source.particle = "gamma" + source.n = 1000 + source.position.type = "sphere" + source.position.radius = 1 * nm + source.direction.type = "momentum" + source.direction.momentum = [0, 0, -1] + source.energy.type = "mono" + source.energy.mono = 6 * MeV + source.position.translation = [0, 0, 18 * cm] + + ####### PHASE SPACE ACTOR ############## + + phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + phsp_actor.mother = plan.name + phsp_actor.attributes = [ + "EventID", + "TrackCreatorProcess", + "TrackVertexPosition", + "PrePosition", + "Weight", + "KineticEnergy", + "ParentID", + "ParticleName", + ] + + phsp_actor.output = paths.output / "test072_output_data.root" + + ##### MODIFIED PHYSICS LIST ############### + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + ### Perhaps avoid the user to call the below boolean function ? ### + sim.physics_manager.special_physics_constructors.G4GenericBiasingPhysics = True + sim.physics_manager.processes_to_bias.gamma = list_processes_to_bias + s = f"/process/em/UseGeneralProcess false" + sim.add_g4_command_before_init(s) + + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1 * km + sim.physics_manager.global_production_cuts.positron = 1 * km + + output = sim.run() + + # + # # print results + stats = sim.output.get_actor("Stats") + h = sim.output.get_actor("PhaseSpace") + print(stats) + # + f_phsp = uproot.open(paths.output / "test072_output_data.root") + data_NIST_W = np.loadtxt(paths.data / "NIST_W.txt", delimiter="|") + arr = f_phsp["PhaseSpace"].arrays() + # + is_ok = validation_test(arr, data_NIST_W, nb_split) + utility.test_ok(is_ok) diff --git a/opengate/tests/src/test073_test3_intevo_tc99m_mt.py b/opengate/tests/src/test073_test3_intevo_tc99m_mt.py index f246384e6..d87fbb35c 100755 --- a/opengate/tests/src/test073_test3_intevo_tc99m_mt.py +++ b/opengate/tests/src/test073_test3_intevo_tc99m_mt.py @@ -32,7 +32,7 @@ # source Bq = gate.g4_units.Bq - set_source_rad_energy_spectrum(source, "tc99m") + set_source_rad_energy_spectrum(source, "Tc99m") source.activity = 2e7 * Bq / sim.number_of_threads # start simulation diff --git a/opengate/tests/src/test073_test4_intevo_lu177_mt.py b/opengate/tests/src/test073_test4_intevo_lu177_mt.py index 7b0bfa5d4..4df49b022 100755 --- a/opengate/tests/src/test073_test4_intevo_lu177_mt.py +++ b/opengate/tests/src/test073_test4_intevo_lu177_mt.py @@ -32,7 +32,7 @@ # source Bq = gate.g4_units.Bq - set_source_rad_energy_spectrum(source, "lu177") + set_source_rad_energy_spectrum(source, "Lu177") source.activity = 2e8 * Bq / sim.number_of_threads # start simulation diff --git a/opengate/tests/src/test073_test5_discovery_lu177_mt.py b/opengate/tests/src/test073_test5_discovery_lu177_mt.py index cb1878e5f..5e907e4cf 100755 --- a/opengate/tests/src/test073_test5_discovery_lu177_mt.py +++ b/opengate/tests/src/test073_test5_discovery_lu177_mt.py @@ -28,7 +28,7 @@ # source Bq = gate.g4_units.Bq - set_source_rad_energy_spectrum(source, "lu177") + set_source_rad_energy_spectrum(source, "Lu177") source.activity = 3e8 * Bq / sim.number_of_threads # start simulation diff --git a/opengate/tests/src/test073_test5_discovery_tc99m_mt.py b/opengate/tests/src/test073_test5_discovery_tc99m_mt.py index 66d75dbfa..7af48805c 100755 --- a/opengate/tests/src/test073_test5_discovery_tc99m_mt.py +++ b/opengate/tests/src/test073_test5_discovery_tc99m_mt.py @@ -28,7 +28,7 @@ # source Bq = gate.g4_units.Bq - set_source_rad_energy_spectrum(source, "tc99m") + set_source_rad_energy_spectrum(source, "Tc99m") source.activity = 4e7 * Bq / sim.number_of_threads # start simulation diff --git a/opengate/tests/src/test074_kill_non_interacting_particles.py b/opengate/tests/src/test074_kill_non_interacting_particles.py new file mode 100644 index 000000000..36616f210 --- /dev/null +++ b/opengate/tests/src/test074_kill_non_interacting_particles.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +from opengate.tests import utility +from scipy.spatial.transform import Rotation +import numpy as np +from anytree import Node, RenderTree +import uproot + + +def test074_test(entry_data, exit_data_1, exit_data_2): + liste_ekin = [] + liste_evtID = [] + liste_trackID = [] + evt_ID_entry_data = entry_data["EventID"] + j = 0 + i = 0 + while i < len(evt_ID_entry_data): + if ( + j < len(exit_data_1["EventID"]) + and evt_ID_entry_data[i] == exit_data_1["EventID"][j] + ): + TID_entry = entry_data["TrackID"][i] + TID_exit = exit_data_1["TrackID"][j] + Ekin_entry = entry_data["KineticEnergy"][i] + Ekin_exit = exit_data_1["KineticEnergy"][j] + + if (TID_entry == TID_exit) and (Ekin_exit == Ekin_entry): + liste_ekin.append(exit_data_1["KineticEnergy"][j]) + liste_evtID.append(exit_data_1["EventID"][j]) + liste_trackID.append(exit_data_1["TrackID"][j]) + if (j < len(exit_data_1["EventID"]) - 1) and ( + exit_data_1["EventID"][j] == exit_data_1["EventID"][j + 1] + ): + i = i - 1 + j += 1 + i += 1 + liste_ekin = np.asarray(liste_ekin) + print("Number of tracks to kill =", len(liste_ekin)) + print( + "Number of killed tracks =", + (len(exit_data_1["EventID"]) - len(exit_data_2["EventID"])), + ) + + return len(liste_ekin) == ( + len(exit_data_1["EventID"]) - len(exit_data_2["EventID"]) + ) + + +if __name__ == "__main__": + paths = utility.get_default_test_paths(__file__) + output_path = paths.output + + print(output_path) + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + # ui.visu = True + ui.visu_type = "vrml" + ui.check_volumes_overlap = False + # ui.running_verbose_level = gate.logger.EVENT + ui.number_of_threads = 1 + ui.random_seed = "auto" + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + sec = gate.g4_units.s + gcm3 = gate.g4_units["g/cm3"] + + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + + # adapt world size + world = sim.world + world.size = [1 * m, 1 * m, 1 * m] + world.material = "G4_AIR" + + big_box = sim.add_volume("Box", "big_box") + big_box.mother = world.name + big_box.material = "G4_AIR" + big_box.size = [0.8 * m, 0.8 * m, 0.8 * m] + + actor_box = sim.add_volume("Box", "actor_box") + actor_box.mother = big_box.name + actor_box.material = "G4_AIR" + actor_box.size = [0.6 * m, 0.6 * m, 0.6 * m] + actor_box.translation = [0, 0, -0.1 * m] + + source = sim.add_source("GenericSource", "photon_source") + source.particle = "gamma" + source.position.type = "box" + source.mother = world.name + source.position.size = [6 * cm, 6 * cm, 6 * cm] + source.position.translation = [0, 0, 0.3 * m] + source.direction.type = "momentum" + source.force_rotation = True + # source1.direction.focus_point = [0*cm, 0*cm, -5 *cm] + source.direction.momentum = [0, 0, -1] + source.energy.type = "mono" + source.energy.mono = 6 * MeV + source.n = 1000 + + tungsten_leaves = sim.add_volume("Box", "tungsten_leaves") + tungsten_leaves.mother = actor_box + tungsten_leaves.size = [0.6 * m, 0.6 * m, 0.3 * cm] + tungsten_leaves.material = "Tungsten" + liste_translation_W = [] + for i in range(7): + liste_translation_W.append([0, 0, 0.25 * m - i * 6 * cm]) + tungsten_leaves.translation = liste_translation_W + tungsten_leaves.color = [0.9, 0.0, 0.4, 0.8] + + kill_No_int_act = sim.add_actor("KillNonInteractingParticleActor", "killact") + kill_No_int_act.mother = actor_box.name + + entry_phase_space = sim.add_volume("Box", "entry_phase_space") + entry_phase_space.mother = big_box + entry_phase_space.size = [0.8 * m, 0.8 * m, 1 * nm] + entry_phase_space.material = "G4_AIR" + entry_phase_space.translation = [0, 0, 0.21 * m] + entry_phase_space.color = [0.5, 0.9, 0.3, 1] + + exit_phase_space_1 = sim.add_volume("Box", "exit_phase_space_1") + exit_phase_space_1.mother = actor_box + exit_phase_space_1.size = [0.6 * m, 0.6 * m, 1 * nm] + exit_phase_space_1.material = "G4_AIR" + exit_phase_space_1.translation = [0, 0, -0.3 * m + 1 * nm] + exit_phase_space_1.color = [0.5, 0.9, 0.3, 1] + + exit_phase_space_2 = sim.add_volume("Box", "exit_phase_space_2") + exit_phase_space_2.mother = world.name + exit_phase_space_2.size = [0.6 * m, 0.6 * m, 1 * nm] + exit_phase_space_2.material = "G4_AIR" + exit_phase_space_2.translation = [0, 0, -0.4 * m - 1 * nm] + exit_phase_space_2.color = [0.5, 0.9, 0.3, 1] + + # print(sim.volume_manager.dump_volume_tree()) + liste_phase_space_name = [ + entry_phase_space.name, + exit_phase_space_1.name, + exit_phase_space_2.name, + ] + for name in liste_phase_space_name: + + phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_" + name) + phsp.mother = name + phsp.attributes = ["EventID", "TrackID", "KineticEnergy"] + name_phsp = "test074_" + name + ".root" + phsp.output = output_path / name_phsp + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + sim.physics_manager.enable_decay = False + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1 * mm + sim.physics_manager.global_production_cuts.positron = 1 * mm + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + + # go ! + sim.run() + output = sim.output + stats = sim.output.get_actor("Stats") + print(stats) + + entry_phsp = uproot.open( + str(output_path) + + "/test074_" + + liste_phase_space_name[0] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[0] + ) + exit_phase_space_1 = uproot.open( + str(output_path) + + "/test074_" + + liste_phase_space_name[1] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[1] + ) + exit_phase_space_2 = uproot.open( + str(output_path) + + "/test074_" + + liste_phase_space_name[2] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[2] + ) + + df_entry = entry_phsp.arrays() + df_exit_1 = exit_phase_space_1.arrays() + df_exit_2 = exit_phase_space_2.arrays() + + is_ok = test074_test(df_entry, df_exit_1, df_exit_2) + + utility.test_ok(is_ok) diff --git a/opengate/tests/src/test075_geometrical_splitting_volume_surface.py b/opengate/tests/src/test075_geometrical_splitting_volume_surface.py new file mode 100644 index 000000000..58af57f28 --- /dev/null +++ b/opengate/tests/src/test075_geometrical_splitting_volume_surface.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +from opengate.tests import utility +from scipy.spatial.transform import Rotation +import numpy as np +from anytree import Node, RenderTree +import uproot + + +def test075(entry_data, exit_data, splitting_factor): + splitted_particle_data_entry = entry_data[ + entry_data["TrackCreatorProcess"] == "none" + ] + + splitted_particle_data_exit = exit_data[exit_data["TrackCreatorProcess"] == "none"] + + array_weight_1 = splitted_particle_data_entry["Weight"] + array_weight_2 = splitted_particle_data_exit["Weight"] + + if (np.round(np.sum(array_weight_1), 3) == 1) and len( + array_weight_1 + ) == splitting_factor: + if (np.round(np.sum(array_weight_2), 3) == 1) and len( + array_weight_2 + ) == splitting_factor: + return True + else: + return False + else: + return False + + +if __name__ == "__main__": + paths = utility.get_default_test_paths(__file__) + output_path = paths.output + + print(output_path) + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + # ui.visu = True + ui.visu_type = "vrml" + ui.check_volumes_overlap = False + # ui.running_verbose_level = gate.logger.EVENT + ui.number_of_threads = 1 + ui.random_seed = "auto" + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + sec = gate.g4_units.s + gcm3 = gate.g4_units["g/cm3"] + + # adapt world size + world = sim.world + world.size = [1 * m, 1 * m, 1 * m] + world.material = "G4_Galactic" + + big_box = sim.add_volume("Box", "big_box") + big_box.mother = world.name + big_box.material = "G4_Galactic" + big_box.size = [0.8 * m, 0.8 * m, 0.8 * m] + + actor_box = sim.add_volume("Box", "actor_box") + actor_box.mother = big_box.name + actor_box.material = "G4_Galactic" + actor_box.size = [0.6 * m, 0.6 * m, 0.6 * m] + actor_box.translation = [0, 0, -0.1 * m] + + source_1 = sim.add_source("GenericSource", "elec_source_1") + source_1.particle = "e-" + source_1.position.type = "box" + source_1.mother = big_box.name + source_1.position.size = [1 * cm, 1 * cm, 1 * cm] + source_1.position.translation = [0, 0.35 * m, 0] + source_1.direction.type = "momentum" + source_1.direction.momentum = [0, -1, 0] + source_1.energy.type = "mono" + source_1.energy.mono = 10 * MeV + source_1.n = 1 + + source_2 = sim.add_source("GenericSource", "elec_source_2") + source_2.particle = "e-" + source_2.position.type = "box" + source_2.mother = big_box.name + source_2.position.size = [1 * cm, 1 * cm, 1 * cm] + source_2.position.translation = [0, 0, -0.39 * m] + source_2.direction.type = "momentum" + source_2.direction.momentum = [0, 0, -1] + source_2.energy.type = "mono" + source_2.energy.mono = 10 * MeV + source_2.n = 1 + + geom_splitting = sim.add_actor("SurfaceSplittingActor", "splitting_act") + geom_splitting.mother = actor_box.name + geom_splitting.splitting_factor = 10 + geom_splitting.weight_threshold = 1 + geom_splitting.split_entering_particles = True + geom_splitting.split_exiting_particles = True + + entry_phase_space = sim.add_volume("Box", "entry_phase_space") + entry_phase_space.mother = actor_box + entry_phase_space.size = [0.6 * m, 1 * nm, 0.6 * m] + entry_phase_space.material = "G4_Galactic" + entry_phase_space.translation = [0, 0.3 * m - 0.5 * nm, 0] + entry_phase_space.color = [0.5, 0.9, 0.3, 1] + + exit_phase_space = sim.add_volume("Box", "exit_phase_space") + exit_phase_space.mother = world.name + exit_phase_space.size = [0.6 * m, 0.6 * m, 1 * nm] + exit_phase_space.material = "G4_Galactic" + exit_phase_space.translation = [0, 0, -0.4 * m - 1 * nm] + exit_phase_space.color = [0.5, 0.9, 0.3, 1] + + # # print(sim.volume_manager.dump_volume_tree()) + liste_phase_space_name = [ + entry_phase_space.name, + exit_phase_space.name, + ] + for name in liste_phase_space_name: + + phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_" + name) + phsp.mother = name + phsp.attributes = [ + "EventID", + "TrackID", + "Weight", + "PDGCode", + "TrackCreatorProcess", + ] + name_phsp = "test075_" + name + ".root" + phsp.output = output_path / name_phsp + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + sim.physics_manager.enable_decay = False + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1 * mm + sim.physics_manager.global_production_cuts.positron = 1 * mm + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + + # go ! + sim.run() + output = sim.output + stats = sim.output.get_actor("Stats") + print(stats) + + # + entry_phsp = uproot.open( + str(output_path) + + "/test075_" + + liste_phase_space_name[0] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[0] + ) + exit_phase_space = uproot.open( + str(output_path) + + "/test075_" + + liste_phase_space_name[1] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[1] + ) + + # + df_entry = entry_phsp.arrays() + df_exit = exit_phase_space.arrays() + # + + is_ok = test075(df_entry, df_exit, geom_splitting.splitting_factor) + + utility.test_ok(is_ok) diff --git a/opengate/tests/src/test077_kill_interacting_particles.py b/opengate/tests/src/test077_kill_interacting_particles.py new file mode 100644 index 000000000..b36776d8f --- /dev/null +++ b/opengate/tests/src/test077_kill_interacting_particles.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +from opengate.tests import utility +from scipy.spatial.transform import Rotation +import numpy as np +from anytree import Node, RenderTree +import uproot + + +def test077_test(entry_data, exit_data_1, exit_data_2): + + + + liste_ekin = [] + liste_evtID = [] + liste_trackID = [] + evt_ID_entry_data = entry_data["EventID"] + j = 0 + i = 0 + while i < len(evt_ID_entry_data): + if ( + j < len(exit_data_1["EventID"]) + and evt_ID_entry_data[i] == exit_data_1["EventID"][j] + ): + TID_entry = entry_data["TrackID"][i] + TID_exit = exit_data_1["TrackID"][j] + Ekin_entry = entry_data["KineticEnergy"][i] + Ekin_exit = exit_data_1["KineticEnergy"][j] + + + if (TID_entry != TID_exit) or (Ekin_exit != Ekin_entry): + liste_ekin.append(exit_data_1["KineticEnergy"][j]) + liste_evtID.append(exit_data_1["EventID"][j]) + liste_trackID.append(exit_data_1["TrackID"][j]) + if (j < len(exit_data_1["EventID"]) - 1) and ( + exit_data_1["EventID"][j] == exit_data_1["EventID"][j + 1] + ): + i = i - 1 + j += 1 + i += 1 + liste_ekin = np.asarray(liste_ekin) + print("Number of tracks to kill =", len(liste_ekin)) + print( + "Number of killed tracks =", + (len(exit_data_1["EventID"]) - len(exit_data_2["EventID"])), + ) + + return len(liste_ekin) == ( + len(exit_data_1["EventID"]) - len(exit_data_2["EventID"]) + ) + + +if __name__ == "__main__": + paths = utility.get_default_test_paths(__file__) + output_path = paths.output + + print(output_path) + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + # ui.visu = True + ui.visu_type = "vrml" + ui.check_volumes_overlap = False + # ui.running_verbose_level = gate.logger.EVENT + ui.number_of_threads = 1 + ui.random_seed = "auto" + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + sec = gate.g4_units.s + gcm3 = gate.g4_units["g/cm3"] + + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + + # adapt world size + world = sim.world + world.size = [1 * m, 1 * m, 1 * m] + world.material = "G4_AIR" + + big_box = sim.add_volume("Box", "big_box") + big_box.mother = world.name + big_box.material = "G4_AIR" + big_box.size = [0.8 * m, 0.8 * m, 0.8 * m] + + actor_box = sim.add_volume("Box", "actor_box") + actor_box.mother = big_box.name + actor_box.material = "G4_AIR" + actor_box.size = [0.6 * m, 0.6 * m, 0.6 * m] + actor_box.translation = [0, 0, -0.1 * m] + + source = sim.add_source("GenericSource", "photon_source") + source.particle = "gamma" + source.position.type = "box" + source.mother = world.name + source.position.size = [6 * cm, 6 * cm, 6 * cm] + source.position.translation = [0, 0, 0.3 * m] + source.direction.type = "momentum" + source.force_rotation = True + # source1.direction.focus_point = [0*cm, 0*cm, -5 *cm] + source.direction.momentum = [0, 0, -1] + source.energy.type = "mono" + source.energy.mono = 6 * MeV + source.n = 1000 + + tungsten_leaves = sim.add_volume("Box", "tungsten_leaves") + tungsten_leaves.mother = actor_box + tungsten_leaves.size = [0.6 * m, 0.6 * m, 0.3 * cm] + tungsten_leaves.material = "Tungsten" + liste_translation_W = [] + for i in range(7): + liste_translation_W.append([0, 0, 0.25 * m - i * 6 * cm]) + tungsten_leaves.translation = liste_translation_W + tungsten_leaves.color = [0.9, 0.0, 0.4, 0.8] + + kill_int_act = sim.add_actor("KillInteractingParticleActor", "killact") + kill_int_act.mother = actor_box.name + + entry_phase_space = sim.add_volume("Box", "entry_phase_space") + entry_phase_space.mother = big_box + entry_phase_space.size = [0.8 * m, 0.8 * m, 1 * nm] + entry_phase_space.material = "G4_AIR" + entry_phase_space.translation = [0, 0, 0.21 * m] + entry_phase_space.color = [0.5, 0.9, 0.3, 1] + + exit_phase_space_1 = sim.add_volume("Box", "exit_phase_space_1") + exit_phase_space_1.mother = actor_box + exit_phase_space_1.size = [0.6 * m, 0.6 * m, 1 * nm] + exit_phase_space_1.material = "G4_AIR" + exit_phase_space_1.translation = [0, 0, -0.3 * m + 1 * nm] + exit_phase_space_1.color = [0.5, 0.9, 0.3, 1] + + exit_phase_space_2 = sim.add_volume("Box", "exit_phase_space_2") + exit_phase_space_2.mother = world.name + exit_phase_space_2.size = [0.6 * m, 0.6 * m, 1 * nm] + exit_phase_space_2.material = "G4_AIR" + exit_phase_space_2.translation = [0, 0, -0.4 * m - 1 * nm] + exit_phase_space_2.color = [0.5, 0.9, 0.3, 1] + + # print(sim.volume_manager.dump_volume_tree()) + liste_phase_space_name = [ + entry_phase_space.name, + exit_phase_space_1.name, + exit_phase_space_2.name, + ] + for name in liste_phase_space_name: + + phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_" + name) + phsp.mother = name + phsp.attributes = ["EventID", "TrackID", "KineticEnergy"] + name_phsp = "test077_" + name + ".root" + phsp.output = output_path / name_phsp + + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + sim.physics_manager.enable_decay = False + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1 * mm + sim.physics_manager.global_production_cuts.positron = 1 * mm + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + + # go ! + sim.run() + output = sim.output + stats = sim.output.get_actor("Stats") + print(stats) + + entry_phsp = uproot.open( + str(output_path) + + "/test077_" + + liste_phase_space_name[0] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[0] + ) + exit_phase_space_1 = uproot.open( + str(output_path) + + "/test077_" + + liste_phase_space_name[1] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[1] + ) + exit_phase_space_2 = uproot.open( + str(output_path) + + "/test077_" + + liste_phase_space_name[2] + + ".root" + + ":PhaseSpace_" + + liste_phase_space_name[2] + ) + + df_entry = entry_phsp.arrays() + df_exit_1 = exit_phase_space_1.arrays() + df_exit_2 = exit_phase_space_2.arrays() + + is_ok = test077_test(df_entry, df_exit_1, df_exit_2) + + utility.test_ok(is_ok) diff --git a/opengate/tests/src/test083_kill_according_processes.py b/opengate/tests/src/test083_kill_according_processes.py index 1e6d9ac3f..8cab3494e 100644 --- a/opengate/tests/src/test083_kill_according_processes.py +++ b/opengate/tests/src/test083_kill_according_processes.py @@ -118,52 +118,6 @@ def test083_test(df): name_phsp = "test083_" + phsp.name + ".root" phsp.output_filename= name_phsp - - - - - - # - # kill_int_act = sim.add_actor("KillInteractingParticleActor", "killact") - # kill_int_act.attached_to = tungsten.name - # - # entry_phase_space = sim.add_volume("Box", "entry_phase_space") - # entry_phase_space.mother = big_box - # entry_phase_space.size = [0.8 * m, 0.8 * m, 1 * nm] - # entry_phase_space.material = "G4_AIR" - # entry_phase_space.translation = [0, 0, 0.21 * m] - # entry_phase_space.color = [0.5, 0.9, 0.3, 1] - # - # exit_phase_space_1 = sim.add_volume("Box", "exit_phase_space_1") - # exit_phase_space_1.mother = actor_box - # exit_phase_space_1.size = [0.6 * m, 0.6 * m, 1 * nm] - # exit_phase_space_1.material = "G4_AIR" - # exit_phase_space_1.translation = [0, 0, -0.3 * m + 1 * nm] - # exit_phase_space_1.color = [0.5, 0.9, 0.3, 1] - # - # exit_phase_space_2 = sim.add_volume("Box", "exit_phase_space_2") - # exit_phase_space_2.mother = world.name - # exit_phase_space_2.size = [0.6 * m, 0.6 * m, 1 * nm] - # exit_phase_space_2.material = "G4_AIR" - # exit_phase_space_2.translation = [0, 0, -0.4 * m - 1 * nm] - # exit_phase_space_2.color = [0.5, 0.9, 0.3, 1] - # - # # print(sim.volume_manager.dump_volume_tree()) - # liste_phase_space_name = [ - # entry_phase_space.name, - # exit_phase_space_1.name, - # exit_phase_space_2.name, - # ] - # - # sim.output_dir = paths.output - # for name in liste_phase_space_name: - # - # phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_" + name) - # phsp.attached_to = name - # phsp.attributes = ["EventID", "TrackID", "KineticEnergy"] - # name_phsp = "test083_" + name + ".root" - # phsp.output_filename= name_phsp - # sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" sim.physics_manager.enable_decay = False sim.physics_manager.global_production_cuts.gamma = 1 * mm @@ -171,38 +125,13 @@ def test083_test(df): sim.physics_manager.global_production_cuts.positron = 1 * mm s = f"/process/em/UseGeneralProcess false" sim.g4_commands_before_init.append(s) - # - # s = sim.add_actor("SimulationStatisticsActor", "Stats") - # s.track_types_flag = True - # - # # go ! sim.run() # phsp = uproot.open( str(output_path) + "/test083_PhaseSpace.root" + ":PhaseSpace") - # exit_phase_space_1 = uproot.open( - # str(output_path) - # + "/test083_" - # + liste_phase_space_name[1] - # + ".root" - # + ":PhaseSpace_" - # + liste_phase_space_name[1] - # ) - # exit_phase_space_2 = uproot.open( - # str(output_path) - # + "/test083_" - # + liste_phase_space_name[2] - # + ".root" - # + ":PhaseSpace_" - # + liste_phase_space_name[2] - # ) - # + df = phsp.arrays() - # df_exit_1 = exit_phase_space_1.arrays() - # df_exit_2 = exit_phase_space_2.arrays() - # is_ok = test083_test(df) - # utility.test_ok(is_ok) diff --git a/opengate/tests/src/test084_last_vertex_splittting.py b/opengate/tests/src/test084_last_vertex_splittting.py new file mode 100755 index 000000000..956520b59 --- /dev/null +++ b/opengate/tests/src/test084_last_vertex_splittting.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +import uproot +import numpy as np +from scipy.spatial.transform import Rotation +from opengate.tests import utility + + + +def validation_test(arr_ref, arr_data, nb_split): + arr_ref = arr_ref[arr_ref["ParticleName"] == "gamma"] + arr_data = arr_data[arr_data["ParticleName"] == "gamma"] + + weight_data = np.round(np.mean(arr_data["Weight"]),4) + bool_weight = False + print("Weight mean is equal to",weight_data, "and need to be equal to",1/nb_split) + if weight_data == 1/nb_split: + bool_weight = True + + + bool_events = False + sigma = np.sqrt((len(arr_data["KineticEnergy"])/nb_split))*nb_split + nb_events_ref = len(arr_ref["KineticEnergy"]) + nb_events_data = len(arr_data["KineticEnergy"]) + print("Reference counts number:",nb_events_ref) + print("Biased counts number:", nb_events_data) + if nb_events_data - 4*sigma <= nb_events_ref <= nb_events_data + 4*sigma: + bool_events =True + + keys = ["KineticEnergy", "PreDirection_X","PreDirection_Y","PreDirection_Z","PrePosition_X","PrePosition_Y"] + + bool_distrib = True + for key in keys : + ref = arr_ref[key] + data = arr_data[key] + + mean_ref = np.mean(ref) + mean_data = np.mean(data) + + std_dev_ref = np.std(ref,ddof =1) + std_dev_data = np.std(data,ddof =1) + + std_err_ref = std_dev_ref/np.sqrt(len(ref)) + std_err_data= std_dev_data/(nb_split * np.sqrt(len(data)/nb_split)) + + print(key,"mean ref value:", np.round(mean_ref,3),"+-",np.round(std_err_ref,3)) + print(key,"mean data value:", np.round(mean_data,3),"+-",np.round(std_err_data,3)) + + + if (mean_data - 4 * np.sqrt(std_err_data**2 + std_err_ref**2) > mean_ref) or (mean_data + 4 * np.sqrt(std_err_data**2 + std_err_ref**2) <mean_ref): + bool_distrib = False + + if bool_distrib and bool_events and bool_weight: + return True + else : + return False + + + + +if __name__ == "__main__": + for i in range(2): + if i == 0 : + bias = False + else : + bias = True + paths = utility.get_default_test_paths( + __file__, "test084_last_vertex_splitting", output_folder="test084" + ) + + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + ui.visu = False + ui.visu_type = "vrml" + ui.check_volumes_overlap = False + ui.number_of_threads = 1 + ui.random_seed = 123456789 + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + um = gate.g4_units.um + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + deg = gate.g4_units.deg + gcm3 = gate.g4_units.g / gate.g4_units.cm3 + + # adapt world size + world = sim.world + world.size = [0.25 * m, 0.25 * m, 0.25 * m] + world.material = "G4_Galactic" + + ####### GEOMETRY TO IRRADIATE ############# + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + + W_tubs = sim.add_volume("Tubs", "W_box") + W_tubs.material = "Tungsten" + W_tubs.mother = world.name + + W_tubs.rmin = 0 + W_tubs.rmax = 0.4*cm + W_tubs.dz = 0.05 * m + W_tubs.color = [0.8, 0.2, 0.1, 1] + angle_x = 45 + angle_y = 70 + angle_z = 80 + + rotation = Rotation.from_euler( + "xyz", [angle_y, angle_y, angle_z], degrees=True + ).as_matrix() + W_tubs.rotation = rotation + + if bias : + ###### Last vertex Splitting ACTOR ######### + nb_split = 10 + vertex_splitting_actor = sim.add_actor("LastVertexInteractionSplittingActor", "vertexSplittingW") + vertex_splitting_actor.attached_to = W_tubs.name + vertex_splitting_actor.splitting_factor = nb_split + vertex_splitting_actor.angular_kill = True + vertex_splitting_actor.vector_director = [0,0,-1] + vertex_splitting_actor.max_theta = 90*deg + vertex_splitting_actor.batch_size = 10 + + + plan = sim.add_volume("Box", "plan_phsp") + plan.material = "G4_Galactic" + plan.size = [5*cm,5*cm,1*nm] + plan.translation = [0,0,-1*cm] + + + ####### gamma source ########### + source = sim.add_source("GenericSource", "source1") + source.particle = "gamma" + source.n = 100000 + if bias : + source.n = source.n/nb_split + + + source.position.type = "sphere" + source.position.radius = 1 * nm + source.direction.type = "momentum" + # source.direction.momentum = [0,0,-1] + source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) + source.energy.type = "mono" + source.energy.mono = 4 * MeV + # + ###### LastVertexSource ############# + if bias : + source_0 = sim.add_source("LastVertexSource", "source_vertex") + source_0.n = 1 + + ####### PHASE SPACE ACTOR ############## + sim.output_dir =paths.output + phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + phsp_actor.attached_to = plan.name + phsp_actor.attributes = [ + "EventID", + "TrackID", + "Weight", + "ParticleName", + "KineticEnergy", + "PreDirection", + "PrePosition", + "TrackCreatorProcess", + ] + if bias : + phsp_actor.output_filename ="test084_output_data_last_vertex_biased.root" + else : + phsp_actor.output_filename = "test084_output_data_last_vertex_ref.root" + + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + s = f"/process/em/UseGeneralProcess false" + sim.g4_commands_before_init.append(s) + + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1000 * km + sim.physics_manager.global_production_cuts.positron = 1000 * km + + output = sim.run(True) + print(s) + + + + + f_data = uproot.open(paths.output / "test084_output_data_last_vertex_biased.root") + f_ref_data = uproot.open(paths.output / "test084_output_data_last_vertex_ref.root") + arr_data = f_data["PhaseSpace"].arrays() + arr_ref_data = f_ref_data["PhaseSpace"].arrays() + # # + is_ok = validation_test(arr_ref_data, arr_data, nb_split) + utility.test_ok(is_ok) + diff --git a/opengate/tests/src/test084_last_vertex_splittting_angular_kill.py b/opengate/tests/src/test084_last_vertex_splittting_angular_kill.py new file mode 100755 index 000000000..c9f058618 --- /dev/null +++ b/opengate/tests/src/test084_last_vertex_splittting_angular_kill.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +import uproot +import numpy as np +from scipy.spatial.transform import Rotation +from opengate.tests import utility + + + +def validation_test(arr_data,vector_director,max_theta): + arr_data = arr_data[arr_data["ParticleName"] == "gamma"] + qt_mvt_data = arr_data[["PreDirection_X","PreDirection_Y","PreDirection_Z"]] + mom_data = np.zeros((len(qt_mvt_data["PreDirection_X"]),3)) + mom_data[:,0] += np.array(qt_mvt_data["PreDirection_X"]) + mom_data[:, 1] += np.array(qt_mvt_data["PreDirection_Y"]) + mom_data[:, 2] += np.array(qt_mvt_data["PreDirection_Z"]) + + l_theta = np.zeros(len(mom_data[:,0])) + + for i in range(len(l_theta)): + theta = np.arccos(mom_data[i].dot(vector_director)) + l_theta[i] = theta + print('Number of particles with an angle higher than max_theta:',len(l_theta[l_theta > max_theta])) + if len(l_theta[l_theta > max_theta]) == 0: + return True + else: + return False + + + +if __name__ == "__main__": + bias = True + paths = utility.get_default_test_paths( + __file__, "test084_last_vertex_splitting", output_folder="test084" + ) + + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + ui.visu = False + ui.visu_type = "vrml" + ui.check_volumes_overlap = False + ui.number_of_threads = 1 + ui.random_seed = 123456789 + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + um = gate.g4_units.um + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + deg = gate.g4_units.deg + gcm3 = gate.g4_units.g / gate.g4_units.cm3 + + # adapt world size + world = sim.world + world.size = [0.25 * m, 0.25 * m, 0.25 * m] + world.material = "G4_Galactic" + + ####### GEOMETRY TO IRRADIATE ############# + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + + W_tubs = sim.add_volume("Tubs", "W_box") + W_tubs.material = "Tungsten" + W_tubs.mother = world.name + + W_tubs.rmin = 0 + W_tubs.rmax = 0.4*cm + W_tubs.dz = 0.05 * m + W_tubs.color = [0.8, 0.2, 0.1, 1] + angle_x = 45 + angle_y = 70 + angle_z = 80 + + rotation = Rotation.from_euler( + "xyz", [angle_y, angle_y, angle_z], degrees=True + ).as_matrix() + W_tubs.rotation = rotation + + if bias : + ###### Last vertex Splitting ACTOR ######### + nb_split = 10 + vertex_splitting_actor = sim.add_actor("LastVertexInteractionSplittingActor", "vertexSplittingW") + vertex_splitting_actor.attached_to = W_tubs.name + vertex_splitting_actor.splitting_factor = nb_split + vertex_splitting_actor.angular_kill = True + vertex_splitting_actor.vector_director = [0,0,-1] + vertex_splitting_actor.max_theta = 15*deg + vertex_splitting_actor.batch_size = 10 + + + plan = sim.add_volume("Box", "plan_phsp") + plan.material = "G4_Galactic" + plan.size = [5*cm,5*cm,1*nm] + plan.translation = [0,0,-1*cm] + + if bias : + vector_director = np.array(vertex_splitting_actor.vector_director) + + + + + ####### gamma source ########### + source = sim.add_source("GenericSource", "source1") + source.particle = "gamma" + source.n = 100000 + if bias : + source.n = source.n/nb_split + + + source.position.type = "sphere" + source.position.radius = 1 * nm + source.direction.type = "momentum" + # source.direction.momentum = [0,0,-1] + source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) + source.energy.type = "mono" + source.energy.mono = 4 * MeV + + ###### LastVertexSource ############# + if bias : + source_0 = sim.add_source("LastVertexSource", "source_vertex") + source_0.n = 1 + + ####### PHASE SPACE ACTOR ############## + sim.output_dir =paths.output + phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + phsp_actor.attached_to = plan.name + phsp_actor.attributes = [ + "EventID", + "TrackID", + "Weight", + "ParticleName", + "KineticEnergy", + "PreDirection", + "PrePosition", + "TrackCreatorProcess", + ] + if bias : + phsp_actor.output_filename ="test084_output_data_last_vertex_angular_kill.root" + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + s = f"/process/em/UseGeneralProcess false" + sim.g4_commands_before_init.append(s) + + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1000 * km + sim.physics_manager.global_production_cuts.positron = 1000 * km + + output = sim.run() + print(s) + + f_data = uproot.open(paths.output / "test084_output_data_last_vertex_angular_kill.root") + arr_data = f_data["PhaseSpace"].arrays() + is_ok = validation_test(arr_data, vector_director,vertex_splitting_actor.max_theta) + utility.test_ok(is_ok) + diff --git a/opengate/tests/utility.py b/opengate/tests/utility.py index 0acd736da..e9679c80d 100644 --- a/opengate/tests/utility.py +++ b/opengate/tests/utility.py @@ -39,10 +39,10 @@ def test_ok(is_ok=False, exceptions=None): if exceptions is not None: if isinstance(exceptions, str): exceptions = [exceptions] - s += f"\nThe following exception" + s += "\nThe following exception" if len(exceptions) > 1: s += "s" - s += f" occurred:\n" + s += " occurred:\n" s += "\n".join([f"- {str(e)}" for e in exceptions]) s = "\n" + colored.stylize(s, color_error) print(s) @@ -57,7 +57,7 @@ def read_stat_file(filename, encoder=None): # guess if it is json or not try: return read_stat_file_json(filename) - except (json.JSONDecodeError, ValueError): + except ValueError: pass return read_stat_file_legacy(filename) @@ -106,7 +106,7 @@ def read_stat_file_legacy(filename): if "Date" in line: counts.start_time = line[len("# Date =") :] if "Threads" in line: - a = line[len(f"# Threads =") :] + a = line[len("# Threads =") :] try: counts.nb_threads = int(a) except: @@ -399,7 +399,7 @@ def assert_images( is_ok = is_ok and b # plot - fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(25, 10)) + _, ax = plt.subplots(ncols=1, nrows=1, figsize=(25, 10)) p1 = plot_img_axis(ax, img1, "reference", axis) p2 = plot_img_axis(ax, img2, "test", axis) if sad_profile_tolerance is not None: @@ -469,11 +469,9 @@ def assert_filtered_imagesprofile1D( filter_data = np.squeeze(itk.GetArrayViewFromImage(filter_img1).ravel()) data1 = np.squeeze(itk.GetArrayViewFromImage(img1).ravel()) data2 = np.squeeze(itk.GetArrayViewFromImage(img2).ravel()) - flipflag = True - if flipflag: - filter_data = np.flip(filter_data) - data1 = np.flip(data1) - data2 = np.flip(data2) + filter_data = np.flip(filter_data) + data1 = np.flip(data1) + data2 = np.flip(data2) max_ind = np.argmax(filter_data) L_filter = range(max_ind) d1 = data1[L_filter] @@ -499,7 +497,7 @@ def assert_filtered_imagesprofile1D( filter_data_norm_au = filter_data / np.amax(filter_data) * np.amax(d2) * 0.7 # plot - fig, ax = plt.subplots(ncols=1, nrows=2, figsize=(15, 15)) + _, ax = plt.subplots(ncols=1, nrows=2, figsize=(15, 15)) xV = np.arange(len(data1)) * info1.spacing[0] x_max = np.ceil(xV[max_ind] * 1.05 + 2) plot_profile(ax[0], filter_data_norm_au, info1.spacing[0], "filter") @@ -546,7 +544,7 @@ def fit_exponential_decay(data, start, end): bin_widths = np.diff(bin_borders) bin_centers = bin_borders[:-1] + bin_widths / 2 - popt, pcov = scipy.optimize.curve_fit(exponential_func, bin_centers, bin_heights) + popt, _ = scipy.optimize.curve_fit(exponential_func, bin_centers, bin_heights) xx = np.linspace(start, end, 100) yy = exponential_func(xx, *popt) hl = np.log(2) / popt[1] @@ -705,7 +703,6 @@ def compare_branches_values(b1, b2, key1, key2, tol=0.8, ax=False, nb_bins=200): print_test(ok, s) # figure ? if ax: - nb_bins = nb_bins label = f" {key1} $\mu$={m1:.2f}" ax.hist( b1, nb_bins, density=True, histtype="stepfilled", alpha=0.5, label=label @@ -735,7 +732,7 @@ def compare_trees( if fig: nb_fig = len(keys1) nrow, ncol = gatetools.phsp.fig_get_nb_row_col(nb_fig) - f, ax = plt.subplots(nrow, ncol, figsize=(25, 10)) + _, ax = plt.subplots(nrow, ncol, figsize=(25, 10)) is_ok = True n = 0 print("Compare branches with Wasserstein distance") @@ -1083,7 +1080,7 @@ def get_gauss_param_xy(data, spacing, shape, filepath=None, saveFig=False): mu_y = parameters_y[1] # Save plots - if filepath is not None: + if saveFig and filepath is not None and img_x is not None and img_y is not None: img_x.savefig(filepath + "_x.png") img_y.savefig(filepath + "_y.png") plt.close(img_x) @@ -1136,7 +1133,7 @@ def gaussian_fit(positionVec, dose): mean = sum(positionVec * dose) / sum(dose) sigma = np.sqrt(sum(dose * (positionVec - mean) ** 2) / sum(dose)) - parameters, covariance = scipy.optimize.curve_fit( + parameters, _ = scipy.optimize.curve_fit( gauss_func, positionVec, dose, p0=[max(dose), mean, sigma] ) fit = gauss_func(positionVec, parameters[0], parameters[1], parameters[2]) @@ -1505,7 +1502,7 @@ def compareRange( diff = abs(r2 - r1) if diff > thresh: - print(f"\033[91mRange difference is {diff}mm, threshold is {thresh}mm \033[0m") + print(f"Range difference is {diff}mm, threshold is {thresh}mm") ok = False return ok @@ -1548,11 +1545,11 @@ def compare_dose_at_points( for p in pointsV: # get dose at the position p [mm] - cp1 = min(x1, key=lambda x: abs(x - p)) - d1_p = doseV1[np.where(x1 == cp1)] + cp1 = min(x1, key=lambda x, p=p: abs(x - p)) + d1_p = doseV1[np.nonzero(x1 == cp1)] - cp2 = min(x2, key=lambda x: abs(x - p)) - d2_p = doseV2[np.where(x2 == cp2)] + cp2 = min(x2, key=lambda x, p=p: abs(x - p)) + d2_p = doseV2[np.nonzero(x2 == cp2)] s1 += d1_p s2 += d2_p @@ -1561,7 +1558,7 @@ def compare_dose_at_points( # print(f"Dose difference at {p} mm is {diff_pc}%") if abs(s1 - s2) / s2 > rel_tol: - print(f"\033[91mDose difference above threshold \033[0m") + print("Dose difference above threshold. ") ok = False return ok @@ -1628,7 +1625,7 @@ def assert_images_ratio_per_voxel( ratio = np.divide(data1, data2, out=np.zeros_like(data1), where=data2 != 0) within_tolerance_M = abs(ratio - expected_ratio) < abs_tolerance N_within_tolerance = np.sum(within_tolerance_M) - fraction_within_tolerance = N_within_tolerance / np.array(data1).size + # fraction_within_tolerance = N_within_tolerance / np.array(data1).size # FIXME fraction_within_tolerance = N_within_tolerance / np.sum(data2 != 0) mean = np.mean(ratio) @@ -1744,7 +1741,7 @@ def compare_trees4(p1, p2, param): if param.fig: nb_fig = len(p1.the_keys) nrow, ncol = gatetools.phsp.fig_get_nb_row_col(nb_fig) - f, ax = plt.subplots(nrow, ncol, figsize=(25, 10)) + _, ax = plt.subplots(nrow, ncol, figsize=(25, 10)) is_ok = True n = 0 print("Compare branches with Wasserstein distance") @@ -1839,8 +1836,7 @@ def np_plot_slice( img, crop_coord = np_img_crop(img, crop_center, crop_width) # slice - slice = img[num_slice, :, :] - im = ax.imshow(slice, cmap="gray") + im = ax.imshow(img[num_slice, :, :], cmap="gray") # prepare ticks nticks = 6 diff --git a/setup.py b/setup.py index f2525d55f..71df5eac9 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ "pandas", "requests", "PyYAML", + "SimpleITK", ] + install_requires_windows, ) From 0294cf03e198d182cb5117e2e82061939b252d88 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Wed, 13 Nov 2024 18:03:23 +0100 Subject: [PATCH 52/82] Add err dose map to TLE actor --- .../GateKillNonInteractingParticleActor.cpp | 6 ++++-- .../opengate_lib/GateTLEDoseActor.cpp | 16 +++++++++++++++- opengate/actors/miscactors.py | 5 ----- opengate/contrib/linacs/elektaversa.py | 2 +- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp index f101683f4..1d7ddd745 100644 --- a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp +++ b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp @@ -40,8 +40,10 @@ void GateKillNonInteractingParticleActor::SteppingAction(G4Step *step) { G4String physicalVolumeNamePreStep = "None"; if (step->GetPreStepPoint()->GetPhysicalVolume() !=0) physicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetName(); - if (((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != logNameMotherVolume) && (fIsFirstStep)) || ((fIsFirstStep) && (step->GetTrack()->GetParentID() == 0))) { - if ((fPassedByTheMotherVolume == false) && (((step->GetPreStepPoint()->GetStepStatus() == 1) && (physicalVolumeNamePreStep == fMotherVolumeName)) || ((fIsFirstStep) && (step->GetTrack()->GetParentID() == 0)))) { + //if (((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != logNameMotherVolume) && (fIsFirstStep)) || ((fIsFirstStep) && (step->GetTrack()->GetParentID() == 0))) { + if ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != logNameMotherVolume) && (fIsFirstStep)) { + //if ((fPassedByTheMotherVolume == false) && (((step->GetPreStepPoint()->GetStepStatus() == 1) && (physicalVolumeNamePreStep == fMotherVolumeName)) || ((fIsFirstStep) && (step->GetTrack()->GetParentID() == 0)))) { + if ((fPassedByTheMotherVolume == false) && (((step->GetPreStepPoint()->GetStepStatus() == 1) && (physicalVolumeNamePreStep == fMotherVolumeName)))) { fPassedByTheMotherVolume = true; fKineticEnergyAtTheEntrance = step->GetPreStepPoint()->GetKineticEnergy(); ftrackIDAtTheEntrance = step->GetTrack()->GetTrackID(); diff --git a/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp b/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp index 2de2899ae..35897f33b 100644 --- a/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp +++ b/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp @@ -55,6 +55,8 @@ void GateTLEDoseActor::PreUserTrackingAction(const G4Track *track) { } void GateTLEDoseActor::SteppingAction(G4Step *step) { + auto event_id = + G4RunManager::GetRunManager()->GetCurrentEvent()->GetEventID(); auto &l = fThreadLocalData.Get(); // Update the track id if there is secondary in the current non TLE particle if (!l.fIsTLEGamma) @@ -108,6 +110,18 @@ void GateTLEDoseActor::SteppingAction(G4Step *step) { if (fDoseFlag) { ImageAddValue<Image3DType>(cpp_dose_image, index, dose); } - ImageAddValue<Image3DType>(cpp_edep_image, index, edep); + ImageAddValue<Image3DType>(cpp_edep_image, index, edep); + + if (fEdepSquaredFlag || fDoseSquaredFlag) { + if (fEdepSquaredFlag) { + ScoreSquaredValue(fThreadLocalDataEdep.Get(), cpp_edep_squared_image, + edep, event_id, index); + } + if (fDoseSquaredFlag) { + ScoreSquaredValue(fThreadLocalDataDose.Get(), cpp_dose_squared_image, + dose, event_id, index); + } + } + } } diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index e87ae110b..bbc037bf1 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -10,11 +10,6 @@ from ..exception import fatal, warning from ..base import process_cls from anytree import Node, RenderTree -<<<<<<< HEAD -======= - ->>>>>>> new_kill_non_interacting_particle - def _setter_hook_stats_actor_output_filename(self, output_filename): # By default, write_to_disk is False. diff --git a/opengate/contrib/linacs/elektaversa.py b/opengate/contrib/linacs/elektaversa.py index a57e8a451..9bd5bfa3f 100644 --- a/opengate/contrib/linacs/elektaversa.py +++ b/opengate/contrib/linacs/elektaversa.py @@ -634,7 +634,7 @@ def add_mlc(sim, linac_name): "X", np.arctan(3.25 / 349.3), degrees=False ).as_matrix() mlc.rotation = mlc_bank_rotation - mlc.size = [linac.size[0] - 2 * cm, linac.size[1] - 2 * cm, 100 * mm] + mlc.size = [linac.size[0] - 2 * cm, linac.size[1] - 2 * cm, 95 * mm] mlc.translation = np.array([0, 0, z_linac / 2 - center_mlc]) mlc.mother = linac_name mlc.color = [0, 0, 0, 0] From 915f8b055a76855d654eb03e5b82cc5923af76b6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 13 Nov 2024 17:07:25 +0000 Subject: [PATCH 53/82] [pre-commit.ci] Automatic python and c++ formatting --- .../opengate_lib/GateGenericSource.cpp | 4 +- .../GateKillAccordingProcessesActor.cpp | 192 +- .../opengate_lib/GateKillActor.cpp | 5 +- .../GateKillNonInteractingParticleActor.cpp | 41 +- ...ateLastVertexInteractionSplittingActor.cpp | 676 +- .../GateLastVertexInteractionSplittingActor.h | 73 +- .../opengate_lib/GateLastVertexSource.cpp | 59 +- .../opengate_lib/GateLastVertexSource.h | 37 +- .../GateLastVertexSplittingDataContainer.h | 191 +- .../GateLastVertexSplittingPostStepDoIt.h | 123 +- .../GateLastVertexSplittingSimpleContainer.h | 238 +- .../opengate_lib/GateOptnForceFreeFlight.cpp | 8 +- .../GateOptnVGenericSplitting.cpp | 70 +- .../opengate_lib/GateOptnVGenericSplitting.h | 21 +- ...GateOptrComptPseudoTransportationActor.cpp | 80 +- .../GateOptrComptPseudoTransportationActor.h | 7 +- .../opengate_lib/GateSourceManager.cpp | 3 - .../opengate_lib/GateSourceManager.h | 13 +- .../opengate_lib/GateTLEDoseActor.cpp | 3 +- .../pyGateKillNonInteractingParticleActor.cpp | 3 +- ...ateLastVertexInteractionSplittingActor.cpp | 5 +- core/opengate_core/opengate_lib/tree.hh | 6222 +++++++++-------- core/opengate_core/opengate_lib/tree_util.hh | 108 +- opengate/actors/miscactors.py | 6 +- opengate/managers.py | 18 +- .../src/test077_kill_interacting_particles.py | 3 - .../test082_kill_non_interacting_particles.py | 2 +- .../src/test083_kill_according_processes.py | 57 +- .../src/test084_last_vertex_splittting.py | 110 +- ...084_last_vertex_splittting_angular_kill.py | 63 +- 30 files changed, 4245 insertions(+), 4196 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateGenericSource.cpp b/core/opengate_core/opengate_lib/GateGenericSource.cpp index 74d7e743a..a00707865 100644 --- a/core/opengate_core/opengate_lib/GateGenericSource.cpp +++ b/core/opengate_core/opengate_lib/GateGenericSource.cpp @@ -151,12 +151,12 @@ double GateGenericSource::PrepareNextTime(double current_simulation_time) { if (fMaxN <= 0) { if (fEffectiveEventTime < fStartTime) return fStartTime; - if (fEffectiveEventTime >= fEndTime){ + if (fEffectiveEventTime >= fEndTime) { return -1; } // get next time according to current fActivity double next_time = CalcNextTime(fEffectiveEventTime); - if (next_time >= fEndTime){ + if (next_time >= fEndTime) { return -1; } return next_time; diff --git a/core/opengate_core/opengate_lib/GateKillAccordingProcessesActor.cpp b/core/opengate_core/opengate_lib/GateKillAccordingProcessesActor.cpp index bc380aed0..b80f9363f 100644 --- a/core/opengate_core/opengate_lib/GateKillAccordingProcessesActor.cpp +++ b/core/opengate_core/opengate_lib/GateKillAccordingProcessesActor.cpp @@ -7,90 +7,85 @@ #include "GateKillAccordingProcessesActor.h" #include "G4LogicalVolumeStore.hh" +#include "G4ParticleTable.hh" #include "G4PhysicalVolumeStore.hh" +#include "G4ProcessManager.hh" +#include "G4VProcess.hh" #include "G4ios.hh" #include "GateHelpers.h" #include "GateHelpersDict.h" -#include "G4VProcess.hh" -#include "G4ProcessManager.hh" -#include "G4ParticleTable.hh" - - - - - GateKillAccordingProcessesActor::GateKillAccordingProcessesActor( py::dict &user_info) - : GateVActor(user_info, false) { - -} - - -std::vector<G4String> GateKillAccordingProcessesActor::GetListOfPhysicsListProcesses(){ -std::vector<G4String> listOfAllProcesses = {}; - -G4ParticleTable* particleTable = G4ParticleTable::GetParticleTable(); - - for (G4int i = 0; i < particleTable->size(); ++i) { - G4ParticleDefinition* particle = particleTable->GetParticle(i); - G4String particleName = particle->GetParticleName(); - G4ProcessManager* processManager = particle->GetProcessManager(); - if (!processManager) continue; - G4int numProcesses = processManager->GetProcessListLength(); - for (G4int j = 0; j < numProcesses; ++j) { - const G4VProcess* process = (*(processManager->GetProcessList()))[j]; - G4String processName = process->GetProcessName(); - if (std::find(listOfAllProcesses.begin(),listOfAllProcesses.end(),processName) == listOfAllProcesses.end()) - listOfAllProcesses.push_back(processName); - } + : GateVActor(user_info, false) {} + +std::vector<G4String> +GateKillAccordingProcessesActor::GetListOfPhysicsListProcesses() { + std::vector<G4String> listOfAllProcesses = {}; + + G4ParticleTable *particleTable = G4ParticleTable::GetParticleTable(); + + for (G4int i = 0; i < particleTable->size(); ++i) { + G4ParticleDefinition *particle = particleTable->GetParticle(i); + G4String particleName = particle->GetParticleName(); + G4ProcessManager *processManager = particle->GetProcessManager(); + if (!processManager) + continue; + G4int numProcesses = processManager->GetProcessListLength(); + for (G4int j = 0; j < numProcesses; ++j) { + const G4VProcess *process = (*(processManager->GetProcessList()))[j]; + G4String processName = process->GetProcessName(); + if (std::find(listOfAllProcesses.begin(), listOfAllProcesses.end(), + processName) == listOfAllProcesses.end()) + listOfAllProcesses.push_back(processName); } -return listOfAllProcesses; + } + return listOfAllProcesses; } - void GateKillAccordingProcessesActor::InitializeUserInput(py::dict &user_info) { -GateVActor::InitializeUserInput(user_info); -fProcessesToKillIfOccurence = DictGetVecStr(user_info,"processes_to_kill_if_occurence"); -fProcessesToKillIfNoOccurence = DictGetVecStr(user_info,"processes_to_kill_if_no_occurence"); - + GateVActor::InitializeUserInput(user_info); + fProcessesToKillIfOccurence = + DictGetVecStr(user_info, "processes_to_kill_if_occurence"); + fProcessesToKillIfNoOccurence = + DictGetVecStr(user_info, "processes_to_kill_if_no_occurence"); } -void GateKillAccordingProcessesActor::BeginOfRunAction(const G4Run* run) { +void GateKillAccordingProcessesActor::BeginOfRunAction(const G4Run *run) { fNbOfKilledParticles = 0; std::vector<G4String> listOfAllProcesses = GetListOfPhysicsListProcesses(); - for (auto process:fProcessesToKillIfOccurence) { - if (std::find(listOfAllProcesses.begin(),listOfAllProcesses.end(),process) == listOfAllProcesses.end()){ - G4String errorMessage = "Process '" +process + "' not found. Existing processes are '"; - for (auto aProcess:listOfAllProcesses){ - errorMessage = errorMessage + aProcess + "', "; - } - errorMessage.pop_back(); - errorMessage.pop_back(); - G4Exception("CheckProcessExistence", // Exception origin - "ProcessNotFound.1", // Exception code - FatalException, // Exception severity - errorMessage); + for (auto process : fProcessesToKillIfOccurence) { + if (std::find(listOfAllProcesses.begin(), listOfAllProcesses.end(), + process) == listOfAllProcesses.end()) { + G4String errorMessage = + "Process '" + process + "' not found. Existing processes are '"; + for (auto aProcess : listOfAllProcesses) { + errorMessage = errorMessage + aProcess + "', "; + } + errorMessage.pop_back(); + errorMessage.pop_back(); + G4Exception("CheckProcessExistence", // Exception origin + "ProcessNotFound.1", // Exception code + FatalException, // Exception severity + errorMessage); } - } - for (auto process:fProcessesToKillIfNoOccurence) { - if (std::find(listOfAllProcesses.begin(),listOfAllProcesses.end(),process) == listOfAllProcesses.end()){ - G4String errorMessage = "Process '" +process + "' not found. Existing processes are '"; - for (auto aProcess:listOfAllProcesses){ - errorMessage = errorMessage + aProcess + "', "; - } - errorMessage.pop_back(); - errorMessage.pop_back(); - G4Exception("CheckProcessExistence", // Exception origin - "ProcessNotFound.2", // Exception code - FatalException, // Exception severity - errorMessage); + for (auto process : fProcessesToKillIfNoOccurence) { + if (std::find(listOfAllProcesses.begin(), listOfAllProcesses.end(), + process) == listOfAllProcesses.end()) { + G4String errorMessage = + "Process '" + process + "' not found. Existing processes are '"; + for (auto aProcess : listOfAllProcesses) { + errorMessage = errorMessage + aProcess + "', "; + } + errorMessage.pop_back(); + errorMessage.pop_back(); + G4Exception("CheckProcessExistence", // Exception origin + "ProcessNotFound.2", // Exception code + FatalException, // Exception severity + errorMessage); } - } - - } void GateKillAccordingProcessesActor::PreUserTrackingAction( @@ -99,57 +94,64 @@ void GateKillAccordingProcessesActor::PreUserTrackingAction( } void GateKillAccordingProcessesActor::SteppingAction(G4Step *step) { - - G4String logNameMotherVolume = G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName)->GetName(); + G4String logNameMotherVolume = G4LogicalVolumeStore::GetInstance() + ->GetVolume(fMotherVolumeName) + ->GetName(); G4String physicalVolumeNamePreStep = "None"; - if (step->GetPreStepPoint()->GetPhysicalVolume() !=0) - physicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetName(); - if (((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != logNameMotherVolume) && (fIsFirstStep)) || ((fIsFirstStep) && (step->GetTrack()->GetParentID() == 0))) { - if (((step->GetPreStepPoint()->GetStepStatus() == 1) && (physicalVolumeNamePreStep == fMotherVolumeName)) || ((fIsFirstStep) && (step->GetTrack()->GetParentID() == 0))) { + if (step->GetPreStepPoint()->GetPhysicalVolume() != 0) + physicalVolumeNamePreStep = + step->GetPreStepPoint()->GetPhysicalVolume()->GetName(); + if (((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != + logNameMotherVolume) && + (fIsFirstStep)) || + ((fIsFirstStep) && (step->GetTrack()->GetParentID() == 0))) { + if (((step->GetPreStepPoint()->GetStepStatus() == 1) && + (physicalVolumeNamePreStep == fMotherVolumeName)) || + ((fIsFirstStep) && (step->GetTrack()->GetParentID() == 0))) { fKill = true; } } - G4String processName = "None"; - const G4VProcess* process = step->GetPostStepPoint()->GetProcessDefinedStep(); + const G4VProcess *process = step->GetPostStepPoint()->GetProcessDefinedStep(); if (process != 0) processName = process->GetProcessName(); - //Positron exception to retrieve the annihilation process, since it's an at rest process most of the time + // Positron exception to retrieve the annihilation process, since it's an at + // rest process most of the time - if ((step->GetTrack()->GetParticleDefinition()->GetParticleName() == "e+") && (step->GetTrack()->GetTrackStatus() == 1)) - processName ="annihil"; + if ((step->GetTrack()->GetParticleDefinition()->GetParticleName() == "e+") && + (step->GetTrack()->GetTrackStatus() == 1)) + processName = "annihil"; - if (std::find(fProcessesToKillIfNoOccurence.begin(),fProcessesToKillIfNoOccurence.end(),processName) != fProcessesToKillIfNoOccurence.end()){ + if (std::find(fProcessesToKillIfNoOccurence.begin(), + fProcessesToKillIfNoOccurence.end(), + processName) != fProcessesToKillIfNoOccurence.end()) { fKill = false; } - - G4String logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - if (step->GetPostStepPoint()->GetStepStatus() == 1) { - if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { - if (fKill == true){ + G4String logicalVolumeNamePostStep = step->GetPostStepPoint() + ->GetPhysicalVolume() + ->GetLogicalVolume() + ->GetName(); + if (step->GetPostStepPoint()->GetStepStatus() == 1) { + if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), + logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { + if (fKill == true) { step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); fNbOfKilledParticles++; } - } } + } - if (std::find(fProcessesToKillIfOccurence.begin(),fProcessesToKillIfOccurence.end(),processName) !=fProcessesToKillIfOccurence.end()) - { + if (std::find(fProcessesToKillIfOccurence.begin(), + fProcessesToKillIfOccurence.end(), + processName) != fProcessesToKillIfOccurence.end()) { step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); fNbOfKilledParticles++; + } else { + if (step->GetTrack()->GetTrackStatus() == 3) + fKill = true; } - else { - if (step->GetTrack()->GetTrackStatus() == 3) - fKill=true; - } - - - - - - } diff --git a/core/opengate_core/opengate_lib/GateKillActor.cpp b/core/opengate_core/opengate_lib/GateKillActor.cpp index 6937d65a7..632832795 100644 --- a/core/opengate_core/opengate_lib/GateKillActor.cpp +++ b/core/opengate_core/opengate_lib/GateKillActor.cpp @@ -17,7 +17,10 @@ GateKillActor::GateKillActor(py::dict &user_info) fNbOfKilledParticles = 0; } -void GateKillActor::StartSimulationAction() { fNbOfKilledParticles = 0; std::cout<<"lol"<<std::endl;} +void GateKillActor::StartSimulationAction() { + fNbOfKilledParticles = 0; + std::cout << "lol" << std::endl; +} void GateKillActor::SteppingAction(G4Step *step) { auto track = step->GetTrack(); diff --git a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp index 1d7ddd745..84ce65ac4 100644 --- a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp +++ b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp @@ -8,16 +8,14 @@ #include "GateKillNonInteractingParticleActor.h" #include "G4LogicalVolumeStore.hh" #include "G4PhysicalVolumeStore.hh" +#include "G4TransportationManager.hh" #include "G4ios.hh" #include "GateHelpers.h" #include "GateHelpersDict.h" -#include "G4TransportationManager.hh" GateKillNonInteractingParticleActor::GateKillNonInteractingParticleActor( py::dict &user_info) - : GateVActor(user_info, false) { -} - + : GateVActor(user_info, false) {} void GateKillNonInteractingParticleActor::StartSimulationAction() { fNbOfKilledParticles = 0; @@ -32,28 +30,41 @@ void GateKillNonInteractingParticleActor::PreUserTrackingAction( } void GateKillNonInteractingParticleActor::SteppingAction(G4Step *step) { - G4Navigator* navigator = G4TransportationManager::GetTransportationManager()->GetNavigatorForTracking(); + G4Navigator *navigator = G4TransportationManager::GetTransportationManager() + ->GetNavigatorForTracking(); G4String logNameMotherVolume = G4LogicalVolumeStore::GetInstance() ->GetVolume(fMotherVolumeName) ->GetName(); G4String physicalVolumeNamePreStep = "None"; - if (step->GetPreStepPoint()->GetPhysicalVolume() !=0) - physicalVolumeNamePreStep = step->GetPreStepPoint()->GetPhysicalVolume()->GetName(); - //if (((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != logNameMotherVolume) && (fIsFirstStep)) || ((fIsFirstStep) && (step->GetTrack()->GetParentID() == 0))) { - if ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != logNameMotherVolume) && (fIsFirstStep)) { - //if ((fPassedByTheMotherVolume == false) && (((step->GetPreStepPoint()->GetStepStatus() == 1) && (physicalVolumeNamePreStep == fMotherVolumeName)) || ((fIsFirstStep) && (step->GetTrack()->GetParentID() == 0)))) { - if ((fPassedByTheMotherVolume == false) && (((step->GetPreStepPoint()->GetStepStatus() == 1) && (physicalVolumeNamePreStep == fMotherVolumeName)))) { + if (step->GetPreStepPoint()->GetPhysicalVolume() != 0) + physicalVolumeNamePreStep = + step->GetPreStepPoint()->GetPhysicalVolume()->GetName(); + // if (((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != + // logNameMotherVolume) && (fIsFirstStep)) || ((fIsFirstStep) && + // (step->GetTrack()->GetParentID() == 0))) { + if ((step->GetTrack()->GetLogicalVolumeAtVertex()->GetName() != + logNameMotherVolume) && + (fIsFirstStep)) { + // if ((fPassedByTheMotherVolume == false) && + // (((step->GetPreStepPoint()->GetStepStatus() == 1) && + // (physicalVolumeNamePreStep == fMotherVolumeName)) || ((fIsFirstStep) && + // (step->GetTrack()->GetParentID() == 0)))) { + if ((fPassedByTheMotherVolume == false) && + (((step->GetPreStepPoint()->GetStepStatus() == 1) && + (physicalVolumeNamePreStep == fMotherVolumeName)))) { fPassedByTheMotherVolume = true; fKineticEnergyAtTheEntrance = step->GetPreStepPoint()->GetKineticEnergy(); ftrackIDAtTheEntrance = step->GetTrack()->GetTrackID(); } } - - - G4String logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - if ((fPassedByTheMotherVolume) && (step->GetPostStepPoint()->GetStepStatus() == 1)) { + G4String logicalVolumeNamePostStep = step->GetPostStepPoint() + ->GetPhysicalVolume() + ->GetLogicalVolume() + ->GetName(); + if ((fPassedByTheMotherVolume) && + (step->GetPostStepPoint()->GetStepStatus() == 1)) { if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { if ((step->GetTrack()->GetTrackID() == ftrackIDAtTheEntrance) && diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index 3d4e345e0..23f24c1d1 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -39,69 +39,70 @@ #include "G4Positron.hh" #include "G4ProcessManager.hh" #include "G4ProcessVector.hh" +#include "G4RunManager.hh" #include "G4TrackStatus.hh" #include "G4TrackingManager.hh" #include "G4VParticleChange.hh" #include "G4eplusAnnihilation.hh" #include "GateLastVertexInteractionSplittingActor.h" +#include "GateLastVertexSource.h" #include "GateLastVertexSplittingPostStepDoIt.h" #include "GateOptnComptSplitting.h" -#include "GateLastVertexSource.h" -#include "G4RunManager.hh" #include <cmath> - //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... GateLastVertexInteractionSplittingActor:: GateLastVertexInteractionSplittingActor(py::dict &user_info) - : GateVActor(user_info, false) { - - - + : GateVActor(user_info, false) {} + +void GateLastVertexInteractionSplittingActor::InitializeUserInput( + py::dict &user_info) { + GateVActor::InitializeUserInput(user_info); + fMotherVolumeName = DictGetStr(user_info, "attached_to"); + fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); + fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); + fAngularKill = DictGetBool(user_info, "angular_kill"); + fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); + fMaxTheta = DictGetDouble(user_info, "max_theta"); + fBatchSize = DictGetDouble(user_info, "batch_size"); } +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -void GateLastVertexInteractionSplittingActor::InitializeUserInput(py::dict &user_info) { -GateVActor::InitializeUserInput(user_info); -fMotherVolumeName = DictGetStr(user_info, "attached_to"); -fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); -fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); -fAngularKill = DictGetBool(user_info, "angular_kill"); -fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); -fMaxTheta = DictGetDouble(user_info, "max_theta"); -fBatchSize = DictGetDouble(user_info, "batch_size"); +void GateLastVertexInteractionSplittingActor::print_tree( + const tree<LastVertexDataContainer> &tr, + tree<LastVertexDataContainer>::pre_order_iterator it, + tree<LastVertexDataContainer>::pre_order_iterator end) { + if (!tr.is_valid(it)) + return; + int rootdepth = tr.depth(it); + std::cout << "-----" << std::endl; + while (it != end) { + for (int i = 0; i < tr.depth(it) - rootdepth; ++i) + std::cout << " "; + std::cout << (*it) << std::endl << std::flush; + ++it; + } + std::cout << "-----" << std::endl; } -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -void GateLastVertexInteractionSplittingActor::print_tree(const tree<LastVertexDataContainer>& tr, tree<LastVertexDataContainer>::pre_order_iterator it, tree<LastVertexDataContainer>::pre_order_iterator end) - { - if(!tr.is_valid(it)) return; - int rootdepth=tr.depth(it); - std::cout << "-----" << std::endl; - while(it!=end) { - for(int i=0; i<tr.depth(it)-rootdepth; ++i) - std::cout << " "; - std::cout << (*it) << std::endl << std::flush; - ++it; - } - std::cout << "-----" << std::endl; - } - -G4bool GateLastVertexInteractionSplittingActor::DoesParticleEmittedInSolidAngle(G4ThreeVector dir, G4ThreeVector vectorDirector) { +G4bool GateLastVertexInteractionSplittingActor::DoesParticleEmittedInSolidAngle( + G4ThreeVector dir, G4ThreeVector vectorDirector) { G4double cosTheta = vectorDirector * dir; if (cosTheta < fCosMaxTheta) return false; return true; } -G4VProcess* GateLastVertexInteractionSplittingActor::GetProcessFromProcessName(G4String particleName, G4String pName){ - auto *particle_table = G4ParticleTable::GetParticleTable(); - G4ParticleDefinition *particleDefinition = particle_table->FindParticle(particleName); +G4VProcess *GateLastVertexInteractionSplittingActor::GetProcessFromProcessName( + G4String particleName, G4String pName) { + auto *particle_table = G4ParticleTable::GetParticleTable(); + G4ParticleDefinition *particleDefinition = + particle_table->FindParticle(particleName); G4ProcessManager *processManager = particleDefinition->GetProcessManager(); G4ProcessVector *processList = processManager->GetProcessList(); - G4VProcess* nullProcess = nullptr; + G4VProcess *nullProcess = nullptr; for (size_t i = 0; i < processList->size(); ++i) { auto process = (*processList)[i]; if (process->GetProcessName() == pName) { @@ -109,49 +110,49 @@ G4VProcess* GateLastVertexInteractionSplittingActor::GetProcessFromProcessName(G } } return nullProcess; - } -G4Track* GateLastVertexInteractionSplittingActor::CreateATrackFromContainer(LastVertexDataContainer theContainer){ +G4Track *GateLastVertexInteractionSplittingActor::CreateATrackFromContainer( + LastVertexDataContainer theContainer) { auto *particle_table = G4ParticleTable::GetParticleTable(); SimpleContainer container = theContainer.GetContainerToSplit(); - if (container.GetParticleNameToSplit() != "None"){ - G4ParticleDefinition *particleDefinition = particle_table->FindParticle(container.GetParticleNameToSplit()); + if (container.GetParticleNameToSplit() != "None") { + G4ParticleDefinition *particleDefinition = + particle_table->FindParticle(container.GetParticleNameToSplit()); G4ThreeVector momentum = container.GetMomentum(); G4double energy = container.GetEnergy(); - if (energy <0){ + if (energy < 0) { energy = 0; - momentum = {0,0,0}; + momentum = {0, 0, 0}; } G4int trackStatus = container.GetTrackStatus(); G4ThreeVector position = container.GetVertexPosition(); G4ThreeVector polarization = container.GetPolarization(); - G4DynamicParticle* dynamicParticle = new G4DynamicParticle(particleDefinition,momentum,energy); + G4DynamicParticle *dynamicParticle = + new G4DynamicParticle(particleDefinition, momentum, energy); G4double time = 0; - G4Track* aTrack = new G4Track(dynamicParticle,time, position); + G4Track *aTrack = new G4Track(dynamicParticle, time, position); aTrack->SetPolarization(polarization); - if (trackStatus == 0){ + if (trackStatus == 0) { aTrack->SetTrackStatus(fAlive); } - if (trackStatus == 1){ + if (trackStatus == 1) { aTrack->SetTrackStatus(fStopButAlive); } - if ((trackStatus == 2) || (trackStatus == 3)){ + if ((trackStatus == 2) || (trackStatus == 3)) { aTrack->SetTrackStatus(fAlive); } aTrack->SetWeight(container.GetWeight()); return aTrack; } - - return nullptr; - + return nullptr; } +G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack( + G4ParticleChangeForGamma *gammaProcess, G4Track track, G4double weight) { -G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack(G4ParticleChangeForGamma *gammaProcess, G4Track track, G4double weight) { - G4double energy = gammaProcess->GetProposedKineticEnergy(); G4double globalTime = track.GetGlobalTime(); G4ThreeVector polarization = gammaProcess->GetProposedPolarization(); @@ -167,56 +168,68 @@ G4Track *GateLastVertexInteractionSplittingActor::CreateComptonTrack(G4ParticleC return newTrack; } -void GateLastVertexInteractionSplittingActor::ComptonSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process, LastVertexDataContainer container, G4double batchSize) { +void GateLastVertexInteractionSplittingActor::ComptonSplitting( + G4Step *initStep, G4Step *CurrentStep, G4VProcess *process, + LastVertexDataContainer container, G4double batchSize) { - //G4TrackVector *trackVector = CurrentStep->GetfSecondary(); + // G4TrackVector *trackVector = CurrentStep->GetfSecondary(); GateGammaEmPostStepDoIt *emProcess = (GateGammaEmPostStepDoIt *)process; - for (int i = 0; i < batchSize; i++){ - G4VParticleChange *processFinalState = emProcess->PostStepDoIt(*fTrackToSplit, *initStep); - G4ParticleChangeForGamma *gammaProcessFinalState = (G4ParticleChangeForGamma *)processFinalState; + for (int i = 0; i < batchSize; i++) { + G4VParticleChange *processFinalState = + emProcess->PostStepDoIt(*fTrackToSplit, *initStep); + G4ParticleChangeForGamma *gammaProcessFinalState = + (G4ParticleChangeForGamma *)processFinalState; - G4ThreeVector momentum = gammaProcessFinalState->GetProposedMomentumDirection(); + G4ThreeVector momentum = + gammaProcessFinalState->GetProposedMomentumDirection(); - G4Track *newTrack = CreateComptonTrack(gammaProcessFinalState, *fTrackToSplit, fWeight); + G4Track *newTrack = + CreateComptonTrack(gammaProcessFinalState, *fTrackToSplit, fWeight); - if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(newTrack->GetMomentumDirection(),fVectorDirector) == false)){ + if ((fAngularKill) && + (DoesParticleEmittedInSolidAngle(newTrack->GetMomentumDirection(), + fVectorDirector) == false)) { delete newTrack; - } - else{ + } else { fStackManager->PushOneTrack(newTrack); } + // Special case here, since we generate independently each particle, we will + // not attach an electron to exiting compton photon, but we will the + // secondaries. - // Special case here, since we generate independently each particle, we will not attach an electron to exiting compton photon, but we will the secondaries. - - - if (processFinalState->GetNumberOfSecondaries()> 0){ + if (processFinalState->GetNumberOfSecondaries() > 0) { delete processFinalState->GetSecondary(0); } - processFinalState->Clear(); gammaProcessFinalState->Clear(); } } - - - -G4Track GateLastVertexInteractionSplittingActor::eBremProcessFinalState(G4Track* track, G4Step* step,G4VProcess *process){ - //It seem's that the the along step method apply only to brem results to no deposited energy but a change in momentum direction according to the process - //Whereas the along step method applied to the ionisation well change the deposited energy but not the momentum. Then I apply both to have a correct - //momentum and deposited energy before the brem effect. +G4Track GateLastVertexInteractionSplittingActor::eBremProcessFinalState( + G4Track *track, G4Step *step, G4VProcess *process) { + // It seem's that the the along step method apply only to brem results to no + // deposited energy but a change in momentum direction according to the + // process Whereas the along step method applied to the ionisation well change + // the deposited energy but not the momentum. Then I apply both to have a + // correct momentum and deposited energy before the brem effect. G4String particleName = track->GetDefinition()->GetParticleName(); - G4VProcess* eIoniProcess = GetProcessFromProcessName(particleName, "eIoni"); - G4VProcess* eBremProcess = GetProcessFromProcessName(particleName, "eBrem"); - G4VParticleChange* eIoniProcessAlongState = eIoniProcess->AlongStepDoIt(*track, *step); - G4VParticleChange* eBremProcessAlongState = eBremProcess->AlongStepDoIt(*track, *step); - G4ParticleChangeForLoss* eIoniProcessAlongStateForLoss = (G4ParticleChangeForLoss*) eIoniProcessAlongState; - G4ParticleChangeForLoss* eBremProcessAlongStateForLoss = (G4ParticleChangeForLoss*) eBremProcessAlongState; + G4VProcess *eIoniProcess = GetProcessFromProcessName(particleName, "eIoni"); + G4VProcess *eBremProcess = GetProcessFromProcessName(particleName, "eBrem"); + G4VParticleChange *eIoniProcessAlongState = + eIoniProcess->AlongStepDoIt(*track, *step); + G4VParticleChange *eBremProcessAlongState = + eBremProcess->AlongStepDoIt(*track, *step); + G4ParticleChangeForLoss *eIoniProcessAlongStateForLoss = + (G4ParticleChangeForLoss *)eIoniProcessAlongState; + G4ParticleChangeForLoss *eBremProcessAlongStateForLoss = + (G4ParticleChangeForLoss *)eBremProcessAlongState; G4double LossEnergy = eIoniProcessAlongStateForLoss->GetLocalEnergyDeposit(); - G4ThreeVector momentum = eBremProcessAlongStateForLoss->GetProposedMomentumDirection(); - G4ThreeVector polarization = eBremProcessAlongStateForLoss->GetProposedPolarization(); + G4ThreeVector momentum = + eBremProcessAlongStateForLoss->GetProposedMomentumDirection(); + G4ThreeVector polarization = + eBremProcessAlongStateForLoss->GetProposedPolarization(); G4Track aTrack = G4Track(*track); aTrack.SetKineticEnergy(track->GetKineticEnergy() - LossEnergy); @@ -225,83 +238,92 @@ G4Track GateLastVertexInteractionSplittingActor::eBremProcessFinalState(G4Track* eIoniProcessAlongState->Clear(); eBremProcessAlongState->Clear(); return aTrack; - } - -void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer theContainer, G4double batchSize) { +void GateLastVertexInteractionSplittingActor::SecondariesSplitting( + G4Step *initStep, G4Step *CurrentStep, G4VProcess *process, + LastVertexDataContainer theContainer, G4double batchSize) { SimpleContainer container = theContainer.GetContainerToSplit(); - G4String particleName = fTrackToSplit->GetParticleDefinition()->GetParticleName(); - //G4TrackVector *trackVector = CurrentStep->GetfSecondary(); + G4String particleName = + fTrackToSplit->GetParticleDefinition()->GetParticleName(); + // G4TrackVector *trackVector = CurrentStep->GetfSecondary(); G4VParticleChange *processFinalState = nullptr; - GateBremPostStepDoIt* bremProcess = nullptr; + GateBremPostStepDoIt *bremProcess = nullptr; GateGammaEmPostStepDoIt *emProcess = nullptr; GateplusannihilAtRestDoIt *eplusAnnihilProcess = nullptr; - if ((container.GetAnnihilationFlag() == "PostStep") && (fTrackToSplit->GetKineticEnergy() > 0)){ + if ((container.GetAnnihilationFlag() == "PostStep") && + (fTrackToSplit->GetKineticEnergy() > 0)) { emProcess = (GateGammaEmPostStepDoIt *)process; } - if ((container.GetAnnihilationFlag() == "AtRest") || (fTrackToSplit->GetKineticEnergy() == 0)) { + if ((container.GetAnnihilationFlag() == "AtRest") || + (fTrackToSplit->GetKineticEnergy() == 0)) { eplusAnnihilProcess = (GateplusannihilAtRestDoIt *)process; } - for (int j = 0; j < batchSize;j++){ + for (int j = 0; j < batchSize; j++) { G4int NbOfSecondaries = 0; - - G4int count =0; - while (NbOfSecondaries == 0){ + + G4int count = 0; + while (NbOfSecondaries == 0) { if (process->GetProcessName() == "eBrem") { - G4Track aTrack = eBremProcessFinalState(fTrackToSplit,initStep,process); - bremProcess = (GateBremPostStepDoIt*) process; - processFinalState = bremProcess->GateBremPostStepDoIt::PostStepDoIt(aTrack, *initStep); - } - else { - if ((container.GetAnnihilationFlag() == "PostStep") && (fTrackToSplit->GetKineticEnergy() > 0)){ - processFinalState = emProcess->PostStepDoIt(*fTrackToSplit, *initStep); + G4Track aTrack = + eBremProcessFinalState(fTrackToSplit, initStep, process); + bremProcess = (GateBremPostStepDoIt *)process; + processFinalState = + bremProcess->GateBremPostStepDoIt::PostStepDoIt(aTrack, *initStep); + } else { + if ((container.GetAnnihilationFlag() == "PostStep") && + (fTrackToSplit->GetKineticEnergy() > 0)) { + processFinalState = + emProcess->PostStepDoIt(*fTrackToSplit, *initStep); } - if ((container.GetAnnihilationFlag() == "AtRest") || (fTrackToSplit->GetKineticEnergy() == 0)) { - processFinalState = eplusAnnihilProcess->GateplusannihilAtRestDoIt::AtRestDoIt(*fTrackToSplit,*initStep); + if ((container.GetAnnihilationFlag() == "AtRest") || + (fTrackToSplit->GetKineticEnergy() == 0)) { + processFinalState = + eplusAnnihilProcess->GateplusannihilAtRestDoIt::AtRestDoIt( + *fTrackToSplit, *initStep); } } NbOfSecondaries = processFinalState->GetNumberOfSecondaries(); - if (NbOfSecondaries == 0){ + if (NbOfSecondaries == 0) { processFinalState->Clear(); } - count ++; - //Security break, in case of infinite loop - if (count > 10000){ + count++; + // Security break, in case of infinite loop + if (count > 10000) { G4ExceptionDescription ed; - ed << " infinite loop detected during the track creation for the " <<process->GetProcessName() <<" process"<<G4endl; - G4Exception("GateLastVertexInteractionSplittingActor::SecondariesSplitting","BIAS.LV1",JustWarning,ed); + ed << " infinite loop detected during the track creation for the " + << process->GetProcessName() << " process" << G4endl; + G4Exception( + "GateLastVertexInteractionSplittingActor::SecondariesSplitting", + "BIAS.LV1", JustWarning, ed); G4RunManager::GetRunManager()->AbortEvent(); break; } } G4int idx = 0; - G4bool IsPushBack =false; - for (int i=0; i < NbOfSecondaries; i++){ + G4bool IsPushBack = false; + for (int i = 0; i < NbOfSecondaries; i++) { G4Track *newTrack = processFinalState->GetSecondary(i); G4ThreeVector momentum = newTrack->GetMomentumDirection(); - - if (!(isnan(momentum[0]))){ - if ((fAngularKill) && (DoesParticleEmittedInSolidAngle(momentum,fVectorDirector) == false)){ + + if (!(isnan(momentum[0]))) { + if ((fAngularKill) && (DoesParticleEmittedInSolidAngle( + momentum, fVectorDirector) == false)) { delete newTrack; - } - else if (IsPushBack == true){ + } else if (IsPushBack == true) { delete newTrack; - } - else { + } else { newTrack->SetWeight(fWeight); newTrack->SetCreatorProcess(process); - //trackVector->emplace_back(newTrack); + // trackVector->emplace_back(newTrack); fStackManager->PushOneTrack(newTrack); - //delete newTrack; - IsPushBack=true; - + // delete newTrack; + IsPushBack = true; } - } - else { + } else { delete newTrack; } } @@ -309,60 +331,68 @@ void GateLastVertexInteractionSplittingActor::SecondariesSplitting(G4Step* initS } } - -void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex(G4Step* initStep,G4Step *step,LastVertexDataContainer theContainer, G4double batchSize) { +void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex( + G4Step *initStep, G4Step *step, LastVertexDataContainer theContainer, + G4double batchSize) { // We retrieve the process associated to the process name to split and we // split according the process. Since for compton scattering, the gamma is not // a secondary particles, this one need to have his own splitting function. - G4String processName = fProcessNameToSplit; - G4int nbOfTrackAlreadyInStack = fStackManager->GetNTotalTrack(); - if ((fProcessToSplit == 0) || (fProcessToSplit == nullptr)){ - SimpleContainer container = theContainer.GetContainerToSplit(); - fProcessToSplit = GetProcessFromProcessName(container.GetParticleNameToSplit(),processName); - } - - if (processName == "compt") { - ComptonSplitting(initStep,step, fProcessToSplit, theContainer, batchSize); - } + G4String processName = fProcessNameToSplit; + G4int nbOfTrackAlreadyInStack = fStackManager->GetNTotalTrack(); + if ((fProcessToSplit == 0) || (fProcessToSplit == nullptr)) { + SimpleContainer container = theContainer.GetContainerToSplit(); + fProcessToSplit = GetProcessFromProcessName( + container.GetParticleNameToSplit(), processName); + } - else if((processName != "msc") && (processName != "conv")){ - SecondariesSplitting(initStep, step, fProcessToSplit, theContainer,batchSize); - } - fNumberOfTrackToSimulate = fStackManager->GetNTotalTrack() - nbOfTrackAlreadyInStack; - fNbOfBatchForExitingParticle ++; - if (fNbOfBatchForExitingParticle >500){ - fStackManager->clear(); - } - //stackManager->clear(); + if (processName == "compt") { + ComptonSplitting(initStep, step, fProcessToSplit, theContainer, batchSize); + } + else if ((processName != "msc") && (processName != "conv")) { + SecondariesSplitting(initStep, step, fProcessToSplit, theContainer, + batchSize); + } + fNumberOfTrackToSimulate = + fStackManager->GetNTotalTrack() - nbOfTrackAlreadyInStack; + fNbOfBatchForExitingParticle++; + if (fNbOfBatchForExitingParticle > 500) { + fStackManager->clear(); + } + // stackManager->clear(); } - -void GateLastVertexInteractionSplittingActor::CreateListOfbiasedVolume(G4LogicalVolume *volume) { +void GateLastVertexInteractionSplittingActor::CreateListOfbiasedVolume( + G4LogicalVolume *volume) { G4int nbOfDaughters = volume->GetNoDaughters(); if (nbOfDaughters > 0) { for (int i = 0; i < nbOfDaughters; i++) { - G4String LogicalVolumeName = volume->GetDaughter(i)->GetLogicalVolume()->GetName(); - G4LogicalVolume *logicalDaughtersVolume = volume->GetDaughter(i)->GetLogicalVolume(); - if (!(std::find(fListOfBiasedVolume.begin(),fListOfBiasedVolume.end(),LogicalVolumeName) != fListOfBiasedVolume.end())) - fListOfBiasedVolume.push_back(volume->GetDaughter(i)->GetLogicalVolume()->GetName()); + G4String LogicalVolumeName = + volume->GetDaughter(i)->GetLogicalVolume()->GetName(); + G4LogicalVolume *logicalDaughtersVolume = + volume->GetDaughter(i)->GetLogicalVolume(); + if (!(std::find(fListOfBiasedVolume.begin(), fListOfBiasedVolume.end(), + LogicalVolumeName) != fListOfBiasedVolume.end())) + fListOfBiasedVolume.push_back( + volume->GetDaughter(i)->GetLogicalVolume()->GetName()); CreateListOfbiasedVolume(logicalDaughtersVolume); } } } - -void GateLastVertexInteractionSplittingActor::FillOfDataTree(G4Step*step){ +void GateLastVertexInteractionSplittingActor::FillOfDataTree(G4Step *step) { G4String processName = "None"; if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) - processName = step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); - + processName = + step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); + G4String creatorProcessName = "None"; if (step->GetTrack()->GetCreatorProcess() != 0) - creatorProcessName =step->GetTrack()->GetCreatorProcess()->GetProcessName(); - + creatorProcessName = + step->GetTrack()->GetCreatorProcess()->GetProcessName(); + if ((step->GetTrack()->GetParticleDefinition()->GetParticleName() == "e+") && ((step->GetTrack()->GetTrackStatus() == 1) || (step->GetTrack()->GetTrackStatus() == 2))) { @@ -370,151 +400,154 @@ void GateLastVertexInteractionSplittingActor::FillOfDataTree(G4Step*step){ } G4String annihilFlag = "None"; - if (processName == "annihil"){ - if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0){ - if (processName == step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName()){ + if (processName == "annihil") { + if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) { + if (processName == + step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName()) { annihilFlag = "PostStep"; - } - else if (processName != step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName()){ - annihilFlag ="AtRest"; + } else if (processName != step->GetPostStepPoint() + ->GetProcessDefinedStep() + ->GetProcessName()) { + annihilFlag = "AtRest"; } } } - - - if (fIsFirstStep){ + if (fIsFirstStep) { LastVertexDataContainer newContainer = LastVertexDataContainer(); newContainer.SetTrackID(step->GetTrack()->GetTrackID()); - newContainer.SetParticleName(step->GetTrack()->GetDefinition()->GetParticleName()); + newContainer.SetParticleName( + step->GetTrack()->GetDefinition()->GetParticleName()); newContainer.SetCreationProcessName(creatorProcessName); - - - if (fTree.empty()){ + if (fTree.empty()) { fTree.set_head(newContainer); } - - for (auto it = fTree.begin_post(); it != fTree.end_post(); ++it){ + + for (auto it = fTree.begin_post(); it != fTree.end_post(); ++it) { LastVertexDataContainer container = *it; G4int trackID = container.GetTrackID(); - - if (step->GetTrack()->GetParentID() == trackID){ - newContainer = container.ContainerFromParentInformation(step); - fTree.append_child(it,newContainer); - break; + if (step->GetTrack()->GetParentID() == trackID) { + newContainer = container.ContainerFromParentInformation(step); + fTree.append_child(it, newContainer); + break; } } - for (auto it = fTree.begin_post(); it != fTree.end_post(); ++it){ + for (auto it = fTree.begin_post(); it != fTree.end_post(); ++it) { LastVertexDataContainer container = *it; G4int trackID = container.GetTrackID(); - if (step->GetTrack()->GetTrackID() == trackID){ + if (step->GetTrack()->GetTrackID() == trackID) { fIterator = it; break; } } } - - - LastVertexDataContainer* container = &(*fIterator); + LastVertexDataContainer *container = &(*fIterator); G4int trackID = container->GetTrackID(); - if ((processName != "Transportation") &&(processName !="None") && (processName !="Rayl")){ - if (step->GetTrack()->GetTrackID() == trackID){ + if ((processName != "Transportation") && (processName != "None") && + (processName != "Rayl")) { + if (step->GetTrack()->GetTrackID() == trackID) { G4ThreeVector position = step->GetTrack()->GetPosition(); G4ThreeVector prePosition = step->GetPreStepPoint()->GetPosition(); G4ThreeVector momentum; - if ((processName == "annihil")) + if ((processName == "annihil")) momentum = step->GetPostStepPoint()->GetMomentumDirection(); - else{ + else { momentum = step->GetPreStepPoint()->GetMomentumDirection(); } G4ThreeVector polarization = step->GetPreStepPoint()->GetPolarization(); - G4String particleName = step->GetTrack()->GetDefinition()->GetParticleName(); + G4String particleName = + step->GetTrack()->GetDefinition()->GetParticleName(); G4double energy = step->GetPreStepPoint()->GetKineticEnergy(); G4double weight = step->GetTrack()->GetWeight(); G4int trackStatus = step->GetTrack()->GetTrackStatus(); G4int nbOfSecondaries = step->GetfSecondary()->size(); G4double stepLength = step->GetStepLength(); - if (((processName == "annihil"))){ + if (((processName == "annihil"))) { energy -= (step->GetTotalEnergyDeposit()); } - SimpleContainer containerToSplit = SimpleContainer(processName,energy, momentum, position,polarization,particleName,weight,trackStatus,nbOfSecondaries,annihilFlag,stepLength,prePosition); + SimpleContainer containerToSplit = + SimpleContainer(processName, energy, momentum, position, polarization, + particleName, weight, trackStatus, nbOfSecondaries, + annihilFlag, stepLength, prePosition); container->SetContainerToSplit(containerToSplit); container->PushListOfSplittingParameters(containerToSplit); - } } - - } - - -G4bool GateLastVertexInteractionSplittingActor::IsParticleExitTheBiasedVolume(G4Step*step){ - +G4bool GateLastVertexInteractionSplittingActor::IsParticleExitTheBiasedVolume( + G4Step *step) { if ((step->GetPostStepPoint()->GetStepStatus() == 1)) { G4String logicalVolumeNamePostStep = "None"; if (step->GetPostStepPoint()->GetPhysicalVolume() != 0) - logicalVolumeNamePostStep = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - - if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()){ + logicalVolumeNamePostStep = step->GetPostStepPoint() + ->GetPhysicalVolume() + ->GetLogicalVolume() + ->GetName(); + + if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), + logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { return true; } - /* - else if (std::find(fListOfBiasedVolume.begin(), fListOfBiasedVolume.end(), logicalVolumeNamePostStep) != fListOfBiasedVolume.end()) { - return false; - } - */ + /* + else if (std::find(fListOfBiasedVolume.begin(), fListOfBiasedVolume.end(), + logicalVolumeNamePostStep) != fListOfBiasedVolume.end()) { return false; } + */ + } if (step->GetPostStepPoint()->GetStepStatus() == 0) return true; return false; } - - -G4bool GateLastVertexInteractionSplittingActor::IsTheParticleUndergoesAProcess(G4Step* step){ +G4bool GateLastVertexInteractionSplittingActor::IsTheParticleUndergoesAProcess( + G4Step *step) { G4String processName = "None"; - G4String particleName = step->GetTrack()->GetParticleDefinition()->GetParticleName(); + G4String particleName = + step->GetTrack()->GetParticleDefinition()->GetParticleName(); if (step->GetPostStepPoint()->GetProcessDefinedStep() != 0) - processName = step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); - - if (std::find(fListOfProcessesAccordingParticles[particleName].begin(), fListOfProcessesAccordingParticles[particleName].end(), processName) != fListOfProcessesAccordingParticles[particleName].end()){ + processName = + step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName(); + + if (std::find(fListOfProcessesAccordingParticles[particleName].begin(), + fListOfProcessesAccordingParticles[particleName].end(), + processName) != + fListOfProcessesAccordingParticles[particleName].end()) { return true; } return false; - - } -G4bool GateLastVertexInteractionSplittingActor::IsTheParticleUndergoesALossEnergyProcess(G4Step* step){ - if (step->GetPostStepPoint()->GetKineticEnergy() - step->GetPreStepPoint()->GetKineticEnergy() != 0) +G4bool GateLastVertexInteractionSplittingActor:: + IsTheParticleUndergoesALossEnergyProcess(G4Step *step) { + if (step->GetPostStepPoint()->GetKineticEnergy() - + step->GetPreStepPoint()->GetKineticEnergy() != + 0) return true; return false; - - } - - void GateLastVertexInteractionSplittingActor::BeginOfRunAction( const G4Run *run) { - fListOfProcessesAccordingParticles["gamma"] = {"compt","phot","conv"}; - fListOfProcessesAccordingParticles["e-"] = {"eBrem","eIoni","msc"}; - fListOfProcessesAccordingParticles["e+"] = {"eBrem","eIoni","msc","annihil"}; + fListOfProcessesAccordingParticles["gamma"] = {"compt", "phot", "conv"}; + fListOfProcessesAccordingParticles["e-"] = {"eBrem", "eIoni", "msc"}; + fListOfProcessesAccordingParticles["e+"] = {"eBrem", "eIoni", "msc", + "annihil"}; - std::cout<<fMotherVolumeName<<std::endl; - G4LogicalVolume *biasingVolume = G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + std::cout << fMotherVolumeName << std::endl; + G4LogicalVolume *biasingVolume = + G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); fListOfBiasedVolume.push_back(biasingVolume->GetName()); CreateListOfbiasedVolume(biasingVolume); - auto* source = fSourceManager->FindSourceByName("source_vertex"); - fVertexSource = (GateLastVertexSource* ) source; + auto *source = fSourceManager->FindSourceByName("source_vertex"); + fVertexSource = (GateLastVertexSource *)source; fCosMaxTheta = std::cos(fMaxTheta); fStackManager = G4EventManager::GetEventManager()->GetStackManager(); @@ -532,175 +565,176 @@ void GateLastVertexInteractionSplittingActor::BeginOfEventAction( fEventID = event->GetEventID(); fIsAnnihilAlreadySplit = false; fNbOfBatchForExitingParticle = 0; - if (fEventID%50000 == 0) - std::cout<<"event ID : "<<fEventID<<std::endl; - if (fCopyInitStep != 0){ + if (fEventID % 50000 == 0) + std::cout << "event ID : " << fEventID << std::endl; + if (fCopyInitStep != 0) { delete fCopyInitStep; fCopyInitStep = nullptr; } - fSplitCounter =0; - fNumberOfTrackToSimulate =0; + fSplitCounter = 0; + fNumberOfTrackToSimulate = 0; fKilledBecauseOfProcess = false; - if (fActiveSource == "source_vertex"){ - auto* source = fSourceManager->FindSourceByName(fActiveSource); - GateLastVertexSource *vertexSource = (GateLastVertexSource*) source; + if (fActiveSource == "source_vertex") { + auto *source = fSourceManager->FindSourceByName(fActiveSource); + GateLastVertexSource *vertexSource = (GateLastVertexSource *)source; fContainer = vertexSource->GetLastVertexContainer(); fProcessNameToSplit = vertexSource->GetProcessToSplit(); - if (fProcessToSplit !=0){ + if (fProcessToSplit != 0) { fProcessToSplit = nullptr; } - if (fTrackToSplit !=0){ + if (fTrackToSplit != 0) { delete fTrackToSplit; fTrackToSplit = nullptr; } fTrackToSplit = CreateATrackFromContainer(fContainer); if (fTrackToSplit != 0) - fWeight = fTrackToSplit->GetWeight()/fSplittingFactor; + fWeight = fTrackToSplit->GetWeight() / fSplittingFactor; } - - } void GateLastVertexInteractionSplittingActor::PreUserTrackingAction( const G4Track *track) { - fToSplit =true; - fIsFirstStep = true; - + fToSplit = true; + fIsFirstStep = true; } void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { - - if (fActiveSource != "source_vertex"){ + if (fActiveSource != "source_vertex") { FillOfDataTree(step); - - if (IsParticleExitTheBiasedVolume(step)){ - if ((fAngularKill == false) || ((fAngularKill == true) && (DoesParticleEmittedInSolidAngle(step->GetTrack()->GetMomentumDirection(),fVectorDirector) == true))){ + + if (IsParticleExitTheBiasedVolume(step)) { + if ((fAngularKill == false) || + ((fAngularKill == true) && + (DoesParticleEmittedInSolidAngle( + step->GetTrack()->GetMomentumDirection(), fVectorDirector) == + true))) { fListOfContainer.push_back((*fIterator)); } - + step->GetTrack()->SetTrackStatus(fStopAndKill); } - - } - - - if (fOnlyTree == false){ - if (fActiveSource == "source_vertex"){ + if (fOnlyTree == false) { + if (fActiveSource == "source_vertex") { - if (fIsFirstStep){ + if (fIsFirstStep) { fTrackID = step->GetTrack()->GetTrackID(); fEkin = step->GetPostStepPoint()->GetKineticEnergy(); - } - else{ - if ((fTrackID == step->GetTrack()->GetTrackID()) && (fEkin != step->GetPreStepPoint()->GetKineticEnergy())){ - fToSplit =false; - } - else{ + } else { + if ((fTrackID == step->GetTrack()->GetTrackID()) && + (fEkin != step->GetPreStepPoint()->GetKineticEnergy())) { + fToSplit = false; + } else { fEkin = step->GetPostStepPoint()->GetKineticEnergy(); } - } if (fToSplit) { G4String creatorProcessName = "None"; if (step->GetTrack()->GetCreatorProcess() != 0) - creatorProcessName =step->GetTrack()->GetCreatorProcess()->GetProcessName(); - if (((step->GetTrack()->GetParentID() == 0)&& (step->GetTrack()->GetTrackID() == 1))|| ((creatorProcessName == "annihil") && (step->GetTrack()->GetParentID() == 1))){ - - if ((fProcessNameToSplit != "annihil") || ((fProcessNameToSplit == "annihil")&& (fIsAnnihilAlreadySplit ==false))){ - - //FIXME : list of process which are not splitable yet - if ((fProcessNameToSplit != "msc") && (fProcessNameToSplit != "conv") && (fProcessNameToSplit != "eIoni")) { + creatorProcessName = + step->GetTrack()->GetCreatorProcess()->GetProcessName(); + if (((step->GetTrack()->GetParentID() == 0) && + (step->GetTrack()->GetTrackID() == 1)) || + ((creatorProcessName == "annihil") && + (step->GetTrack()->GetParentID() == 1))) { + + if ((fProcessNameToSplit != "annihil") || + ((fProcessNameToSplit == "annihil") && + (fIsAnnihilAlreadySplit == false))) { + + // FIXME : list of process which are not splitable yet + if ((fProcessNameToSplit != "msc") && + (fProcessNameToSplit != "conv") && + (fProcessNameToSplit != "eIoni")) { fCopyInitStep = new G4Step(*step); - if (fProcessNameToSplit == "eBrem"){ - fCopyInitStep->SetStepLength(fContainer.GetContainerToSplit().GetStepLength()); - fCopyInitStep->GetPreStepPoint()->SetKineticEnergy(fContainer.GetContainerToSplit().GetEnergy()); - + if (fProcessNameToSplit == "eBrem") { + fCopyInitStep->SetStepLength( + fContainer.GetContainerToSplit().GetStepLength()); + fCopyInitStep->GetPreStepPoint()->SetKineticEnergy( + fContainer.GetContainerToSplit().GetEnergy()); } - CreateNewParticleAtTheLastVertex(fCopyInitStep,step,fContainer, fBatchSize); + CreateNewParticleAtTheLastVertex(fCopyInitStep, step, fContainer, + fBatchSize); } step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); - - if (fProcessNameToSplit == "annihil"){ + + if (fProcessNameToSplit == "annihil") { fIsAnnihilAlreadySplit = true; - } } + } - - else if ((fProcessNameToSplit == "annihil")&& (fIsAnnihilAlreadySplit == true)){ + else if ((fProcessNameToSplit == "annihil") && + (fIsAnnihilAlreadySplit == true)) { step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); } - - } - + + } + else { - if (fIsFirstStep){ - fNumberOfTrackToSimulate --; - if (fKilledBecauseOfProcess == false){ + if (fIsFirstStep) { + fNumberOfTrackToSimulate--; + if (fKilledBecauseOfProcess == false) { fSplitCounter += 1; - } - else { + } else { fKilledBecauseOfProcess = false; } - if (fSplitCounter > fSplittingFactor){ + if (fSplitCounter > fSplittingFactor) { step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); fStackManager->clear(); } } - if (IsTheParticleUndergoesALossEnergyProcess(step)){ + if (IsTheParticleUndergoesALossEnergyProcess(step)) { step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); fKilledBecauseOfProcess = true; } - if (fIsFirstStep){ - if (fSplitCounter <= fSplittingFactor){ - if (fNumberOfTrackToSimulate == 0){ - CreateNewParticleAtTheLastVertex(fCopyInitStep,step,fContainer,(fSplittingFactor - fSplitCounter +1)/fSplittingFactor * fBatchSize); - } + if (fIsFirstStep) { + if (fSplitCounter <= fSplittingFactor) { + if (fNumberOfTrackToSimulate == 0) { + CreateNewParticleAtTheLastVertex( + fCopyInitStep, step, fContainer, + (fSplittingFactor - fSplitCounter + 1) / fSplittingFactor * + fBatchSize); + } } } } - } + } } } fIsFirstStep = false; - - - - } - void GateLastVertexInteractionSplittingActor::EndOfEventAction( - const G4Event* event) { - - if (fActiveSource != "source_vertex"){ - - //print_tree(fTree,fTree.begin(),fTree.end()); - fVertexSource->SetNumberOfEventToSimulate(fListOfContainer.size()); - fVertexSource->SetNumberOfGeneratedEvent(0); - fVertexSource->SetListOfVertexToSimulate(fListOfContainer); - fTree.clear(); - fListOfContainer.clear(); - } + const G4Event *event) { - if (fOnlyTree == false){ + if (fActiveSource != "source_vertex") { - auto* source = fSourceManager->FindSourceByName("source_vertex"); - GateLastVertexSource* vertexSource = (GateLastVertexSource*) source; - if (vertexSource->GetNumberOfGeneratedEvent() < vertexSource->GetNumberOfEventToSimulate()){ - fSourceManager->SetActiveSourcebyName("source_vertex"); - } - fActiveSource = fSourceManager->GetActiveSourceName(); - } - + // print_tree(fTree,fTree.begin(),fTree.end()); + fVertexSource->SetNumberOfEventToSimulate(fListOfContainer.size()); + fVertexSource->SetNumberOfGeneratedEvent(0); + fVertexSource->SetListOfVertexToSimulate(fListOfContainer); + fTree.clear(); + fListOfContainer.clear(); + } + + if (fOnlyTree == false) { + + auto *source = fSourceManager->FindSourceByName("source_vertex"); + GateLastVertexSource *vertexSource = (GateLastVertexSource *)source; + if (vertexSource->GetNumberOfGeneratedEvent() < + vertexSource->GetNumberOfEventToSimulate()) { + fSourceManager->SetActiveSourcebyName("source_vertex"); } + fActiveSource = fSourceManager->GetActiveSourceName(); + } +} //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h index dfe0ee850..e5d0b333f 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h @@ -29,18 +29,17 @@ #ifndef GateLastVertexInteractionSplittingActor_h #define GateLastVertexInteractionSplittingActor_h 1 +#include "CLHEP/Vector/ThreeVector.h" #include "G4ParticleChangeForGamma.hh" +#include "G4StackManager.hh" #include "G4VEnergyLossProcess.hh" -#include "GateVActor.h" -#include <iostream> -#include <pybind11/stl.h> +#include "GateLastVertexSource.h" #include "GateLastVertexSplittingDataContainer.h" +#include "GateVActor.h" #include "tree.hh" #include "tree_util.hh" #include <iostream> -#include "GateLastVertexSource.h" -#include "CLHEP/Vector/ThreeVector.h" -#include "G4StackManager.hh" +#include <pybind11/stl.h> using CLHEP::Hep3Vector; namespace py = pybind11; @@ -71,35 +70,31 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { G4double fSplitCounter = 0; G4bool fToSplit = true; G4String fActiveSource = "None"; - G4bool fIsAnnihilAlreadySplit =false; + G4bool fIsAnnihilAlreadySplit = false; G4int fCounter; - G4bool fKilledBecauseOfProcess = false; + G4bool fKilledBecauseOfProcess = false; G4bool fFirstSplittedPart = true; G4bool fOnlyTree = false; G4double fWeight; G4double fBatchSize; G4int fNumberOfTrackToSimulate = 0; - G4int fNbOfBatchForExitingParticle=0; - G4int fTracksCounts=0; - GateLastVertexSource* fVertexSource = nullptr; + G4int fNbOfBatchForExitingParticle = 0; + G4int fTracksCounts = 0; + GateLastVertexSource *fVertexSource = nullptr; tree<LastVertexDataContainer> fTree; tree<LastVertexDataContainer>::post_order_iterator fIterator; std::vector<LastVertexDataContainer> fListOfContainer; - G4StackManager* fStackManager = nullptr; - - + G4StackManager *fStackManager = nullptr; G4Track *fTrackToSplit = nullptr; - G4Step* fCopyInitStep = nullptr; + G4Step *fCopyInitStep = nullptr; G4String fProcessNameToSplit; - G4VProcess* fProcessToSplit; + G4VProcess *fProcessToSplit; LastVertexDataContainer fContainer; std::vector<G4Track> fTracksToPostpone; - std::map<G4String,std::vector<G4String>> fListOfProcessesAccordingParticles; - std::map<G4int,LastVertexDataContainer> fDataMap; - - + std::map<G4String, std::vector<G4String>> fListOfProcessesAccordingParticles; + std::map<G4int, LastVertexDataContainer> fDataMap; std::vector<std::string> fListOfVolumeAncestor; std::vector<std::string> fListOfBiasedVolume; @@ -107,7 +102,7 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { std::vector<G4String> fListOfProcesses = {"compt", "annihil", "eBrem", "conv", "phot"}; - void InitializeUserInput(py::dict &user_info) override; + void InitializeUserInput(py::dict &user_info) override; virtual void SteppingAction(G4Step *) override; virtual void BeginOfEventAction(const G4Event *) override; virtual void EndOfEventAction(const G4Event *) override; @@ -115,23 +110,33 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { virtual void PreUserTrackingAction(const G4Track *track) override; // Pure splitting functions - G4bool DoesParticleEmittedInSolidAngle(G4ThreeVector dir, G4ThreeVector vectorDirector); + G4bool DoesParticleEmittedInSolidAngle(G4ThreeVector dir, + G4ThreeVector vectorDirector); G4Track *CreateComptonTrack(G4ParticleChangeForGamma *, G4Track, G4double); - void ComptonSplitting(G4Step* initStep,G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container, G4double batchSize); - void SecondariesSplitting(G4Step* initStep, G4Step *CurrentStep,G4VProcess *process,LastVertexDataContainer container, G4double batchSize); - - void CreateNewParticleAtTheLastVertex(G4Step*init,G4Step *current, LastVertexDataContainer, G4double batchSize); - G4Track* CreateATrackFromContainer(LastVertexDataContainer container); - G4bool IsTheParticleUndergoesAProcess(G4Step* step); - G4bool IsTheParticleUndergoesALossEnergyProcess(G4Step* step); - G4VProcess* GetProcessFromProcessName(G4String particleName, G4String pName); - G4Track eBremProcessFinalState(G4Track* track, G4Step* step,G4VProcess *process); - + void ComptonSplitting(G4Step *initStep, G4Step *CurrentStep, + G4VProcess *process, LastVertexDataContainer container, + G4double batchSize); + void SecondariesSplitting(G4Step *initStep, G4Step *CurrentStep, + G4VProcess *process, + LastVertexDataContainer container, + G4double batchSize); + + void CreateNewParticleAtTheLastVertex(G4Step *init, G4Step *current, + LastVertexDataContainer, + G4double batchSize); + G4Track *CreateATrackFromContainer(LastVertexDataContainer container); + G4bool IsTheParticleUndergoesAProcess(G4Step *step); + G4bool IsTheParticleUndergoesALossEnergyProcess(G4Step *step); + G4VProcess *GetProcessFromProcessName(G4String particleName, G4String pName); + G4Track eBremProcessFinalState(G4Track *track, G4Step *step, + G4VProcess *process); void FillOfDataTree(G4Step *step); - G4bool IsParticleExitTheBiasedVolume(G4Step*step); + G4bool IsParticleExitTheBiasedVolume(G4Step *step); void CreateListOfbiasedVolume(G4LogicalVolume *volume); - void print_tree(const tree<LastVertexDataContainer>& tr, tree<LastVertexDataContainer>::pre_order_iterator it, tree<LastVertexDataContainer>::pre_order_iterator end); + void print_tree(const tree<LastVertexDataContainer> &tr, + tree<LastVertexDataContainer>::pre_order_iterator it, + tree<LastVertexDataContainer>::pre_order_iterator end); }; #endif diff --git a/core/opengate_core/opengate_lib/GateLastVertexSource.cpp b/core/opengate_core/opengate_lib/GateLastVertexSource.cpp index b903d90c8..c870637a4 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSource.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexSource.cpp @@ -10,40 +10,38 @@ #include "GateHelpersDict.h" #include <G4UnitsTable.hh> -GateLastVertexSource::GateLastVertexSource() : GateVSource() { -} +GateLastVertexSource::GateLastVertexSource() : GateVSource() {} -GateLastVertexSource::~GateLastVertexSource() { -} +GateLastVertexSource::~GateLastVertexSource() {} void GateLastVertexSource::InitializeUserInfo(py::dict &user_info) { GateVSource::InitializeUserInfo(user_info); // get user info about activity or nb of events fN = DictGetInt(user_info, "n"); - } +} double GateLastVertexSource::PrepareNextTime(double current_simulation_time) { /* // If all N events have been generated, we stop (negative time) if (fNumberOfGeneratedEvents >= fN){ - std::cout << "LV: "<< -1<<" "<< fNumberOfGeneratedEvents <<" "<< fN<<std::endl; - return -1; + std::cout << "LV: "<< -1<<" "<< fNumberOfGeneratedEvents <<" "<< + fN<<std::endl; return -1; } if (fListOfContainer.size()==0){ - std::cout << "LV: "<< 1<<" "<<fNumberOfGeneratedEvents <<" "<< fN<<std::endl; - return 1; + std::cout << "LV: "<< 1<<" "<<fNumberOfGeneratedEvents <<" "<< + fN<<std::endl; return 1; } // Else we consider all event with a timestamp equal to the simulation // StartTime - std::cout << "LV: "<< fStartTime<<" "<<fNumberOfGeneratedEvents <<" "<< fN<<std::endl; - return fStartTime; + std::cout << "LV: "<< fStartTime<<" "<<fNumberOfGeneratedEvents <<" "<< + fN<<std::endl; return fStartTime; */ - if (fNumberOfGeneratedEvents >= fN) - return -1; + if (fNumberOfGeneratedEvents >= fN) + return -1; - return fStartTime + 1; + return fStartTime + 1; } void GateLastVertexSource::PrepareNextRun() { @@ -56,34 +54,35 @@ void GateLastVertexSource::PrepareNextRun() { // init the number of generated events (here, for each run) fNumberOfGeneratedEvents = 0; - } -void GateLastVertexSource::GenerateOnePrimary(G4Event* event, double current_simulation_time, G4int idx ){ +void GateLastVertexSource::GenerateOnePrimary(G4Event *event, + double current_simulation_time, + G4int idx) { - if (fNumberOfGeneratedEvents >= fN){ + if (fNumberOfGeneratedEvents >= fN) { auto *particle_table = G4ParticleTable::GetParticleTable(); auto *fParticleDefinition = particle_table->FindParticle("geantino"); auto *particle = new G4PrimaryParticle(fParticleDefinition); particle->SetKineticEnergy(0); - particle->SetMomentumDirection({1,0,0}); + particle->SetMomentumDirection({1, 0, 0}); particle->SetWeight(1); - auto *vertex = new G4PrimaryVertex({0,0,0}, current_simulation_time); + auto *vertex = new G4PrimaryVertex({0, 0, 0}, current_simulation_time); vertex->SetPrimary(particle); event->AddPrimaryVertex(vertex); - } - else { + } else { - SimpleContainer containerToSplit = fListOfContainer[idx].GetContainerToSplit(); + SimpleContainer containerToSplit = + fListOfContainer[idx].GetContainerToSplit(); G4double energy = containerToSplit.GetEnergy(); - if (energy < 0){ + if (energy < 0) { energy = 0; } fContainer = fListOfContainer[idx]; - G4ThreeVector position = containerToSplit.GetVertexPosition(); + G4ThreeVector position = containerToSplit.GetVertexPosition(); G4ThreeVector momentum = containerToSplit.GetMomentum(); G4String particleName = containerToSplit.GetParticleNameToSplit(); - G4double weight =containerToSplit.GetWeight(); + G4double weight = containerToSplit.GetWeight(); fProcessToSplit = containerToSplit.GetProcessNameToSplit(); auto &l = fThreadLocalData.Get(); @@ -97,16 +96,14 @@ void GateLastVertexSource::GenerateOnePrimary(G4Event* event, double current_sim vertex->SetPrimary(particle); event->AddPrimaryVertex(vertex); } - } - void GateLastVertexSource::GeneratePrimaries(G4Event *event, - double current_simulation_time) { - - GenerateOnePrimary(event,current_simulation_time,fNumberOfGeneratedEvents); + double current_simulation_time) { + + GenerateOnePrimary(event, current_simulation_time, fNumberOfGeneratedEvents); fNumberOfGeneratedEvents++; - if (fNumberOfGeneratedEvents == fListOfContainer.size()){ + if (fNumberOfGeneratedEvents == fListOfContainer.size()) { fListOfContainer.clear(); } } diff --git a/core/opengate_core/opengate_lib/GateLastVertexSource.h b/core/opengate_core/opengate_lib/GateLastVertexSource.h index dd23c124d..eebf43e90 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSource.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSource.h @@ -9,9 +9,9 @@ #define GateLastVertexSource_h #include "GateAcceptanceAngleTesterManager.h" +#include "GateLastVertexSplittingDataContainer.h" #include "GateSingleParticleSource.h" #include "GateVSource.h" -#include "GateLastVertexSplittingDataContainer.h" #include <pybind11/stl.h> namespace py = pybind11; @@ -19,9 +19,9 @@ namespace py = pybind11; /* This is NOT a real source type but a template to help writing your own source type. Copy-paste this file with a different name ("MyNewSource.hh") and start - building. You also need to copy : GateLastVertexSource.hh GateLastVertexSource.cpp - pyGateLastVertexSource.cpp - And add the source declaration in opengate_core.cpp + building. You also need to copy : GateLastVertexSource.hh + GateLastVertexSource.cpp pyGateLastVertexSource.cpp And add the source + declaration in opengate_core.cpp */ class GateLastVertexSource : public GateVSource { @@ -39,38 +39,25 @@ class GateLastVertexSource : public GateVSource { void GeneratePrimaries(G4Event *event, double time) override; + void GenerateOnePrimary(G4Event *event, double time, G4int idx); - void GenerateOnePrimary(G4Event *event, double time,G4int idx); - - - void SetListOfVertexToSimulate(std::vector<LastVertexDataContainer> list){ + void SetListOfVertexToSimulate(std::vector<LastVertexDataContainer> list) { fListOfContainer = list; } - void SetNumberOfGeneratedEvent(G4int nbEvent){ + void SetNumberOfGeneratedEvent(G4int nbEvent) { fNumberOfGeneratedEvents = nbEvent; } - void SetNumberOfEventToSimulate(G4int N){ - fN = N; - } - - G4int GetNumberOfEventToSimulate(){ - return fN; - } + void SetNumberOfEventToSimulate(G4int N) { fN = N; } - G4int GetNumberOfGeneratedEvent(){ - return fNumberOfGeneratedEvents; - } + G4int GetNumberOfEventToSimulate() { return fN; } - G4String GetProcessToSplit(){ - return fProcessToSplit; - } + G4int GetNumberOfGeneratedEvent() { return fNumberOfGeneratedEvents; } - LastVertexDataContainer GetLastVertexContainer(){ - return fContainer; - } + G4String GetProcessToSplit() { return fProcessToSplit; } + LastVertexDataContainer GetLastVertexContainer() { return fContainer; } protected: G4int fNumberOfGeneratedEvents = 0; diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h index 474d6cc84..92edcb5f0 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h @@ -27,139 +27,118 @@ #ifndef LastVertexDataContainer_h #define LastVertexDataContainer_h - -#include <iostream> -#include "G4VEnergyLossProcess.hh" +#include "G4Electron.hh" +#include "G4EmBiasingManager.hh" +#include "G4EmParameters.hh" +#include "G4EntanglementAuxInfo.hh" +#include "G4Gamma.hh" +#include "G4MaterialCutsCouple.hh" +#include "G4PhysicalConstants.hh" +#include "G4PhysicsModelCatalog.hh" +#include "G4Positron.hh" #include "G4Track.hh" #include "G4VEmProcess.hh" +#include "G4VEnergyLossProcess.hh" #include "G4VParticleChange.hh" -#include "G4eplusAnnihilation.hh" -#include "G4PhysicalConstants.hh" -#include "G4MaterialCutsCouple.hh" -#include "G4Gamma.hh" -#include "G4Electron.hh" -#include "G4Positron.hh" #include "G4eeToTwoGammaModel.hh" -#include "G4EmBiasingManager.hh" -#include "G4EntanglementAuxInfo.hh" +#include "G4eplusAnnihilation.hh" #include "G4eplusAnnihilationEntanglementClipBoard.hh" -#include "G4EmParameters.hh" -#include "G4PhysicsModelCatalog.hh" #include "GateLastVertexSplittingSimpleContainer.h" +#include <iostream> +class LastVertexDataContainer { -class LastVertexDataContainer{ - -public : - - -LastVertexDataContainer(){} - -~LastVertexDataContainer(){} - - - -void SetTrackID(G4int trackID ){ - fTrackID = trackID; -} - -G4int GetTrackID(){ - return fTrackID; -} - - -void SetParticleName(G4String name){ - fParticleName = name; -} - -G4String GetParticleName(){ - return fParticleName; -} - - -void SetCreationProcessName(G4String creationProcessName){ - fCreationProcessName = creationProcessName; -} - -G4String GetCreationProcessName(){ - return fCreationProcessName; -} - +public: + LastVertexDataContainer() {} -void SetContainerToSplit(SimpleContainer container){ - fContainerToSplit = container; -} + ~LastVertexDataContainer() {} + void SetTrackID(G4int trackID) { fTrackID = trackID; } -SimpleContainer GetContainerToSplit(){ - return fContainerToSplit; -} + G4int GetTrackID() { return fTrackID; } + void SetParticleName(G4String name) { fParticleName = name; } + G4String GetParticleName() { return fParticleName; } -void PushListOfSplittingParameters(SimpleContainer container){ - fVectorOfContainerToSplit.emplace_back(container); -} + void SetCreationProcessName(G4String creationProcessName) { + fCreationProcessName = creationProcessName; + } + G4String GetCreationProcessName() { return fCreationProcessName; } + void SetContainerToSplit(SimpleContainer container) { + fContainerToSplit = container; + } + SimpleContainer GetContainerToSplit() { return fContainerToSplit; } -LastVertexDataContainer ContainerFromParentInformation(G4Step* step){ - LastVertexDataContainer aContainer = LastVertexDataContainer(); + void PushListOfSplittingParameters(SimpleContainer container) { + fVectorOfContainerToSplit.emplace_back(container); + } - aContainer.fTrackID = step->GetTrack()->GetTrackID(); - aContainer.fParticleName = step->GetTrack()->GetDefinition()->GetParticleName(); - if (this->fContainerToSplit.GetProcessNameToSplit() != "None"){ - if (this->fVectorOfContainerToSplit.size() !=0){ - G4ThreeVector vertexPosition = step->GetTrack()->GetVertexPosition(); - for (int i =0;i<this->fVectorOfContainerToSplit.size();i++){ - if (vertexPosition == this->fVectorOfContainerToSplit[i].GetVertexPosition()){ - SimpleContainer tmpContainer = this->fVectorOfContainerToSplit[i]; - //std::cout<<"1 "<<tmpContainer.GetProcessNameToSplit()<<std::endl; - aContainer.fContainerToSplit = SimpleContainer(tmpContainer.GetProcessNameToSplit(),tmpContainer.GetEnergy(),tmpContainer.GetMomentum(),tmpContainer.GetVertexPosition(),tmpContainer.GetPolarization(),tmpContainer.GetParticleNameToSplit(),tmpContainer.GetWeight(),tmpContainer.GetTrackStatus(),tmpContainer.GetNbOfSecondaries(),tmpContainer.GetAnnihilationFlag(),tmpContainer.GetStepLength(), tmpContainer.GetPrePositionToSplit()); - return aContainer; + LastVertexDataContainer ContainerFromParentInformation(G4Step *step) { + LastVertexDataContainer aContainer = LastVertexDataContainer(); + + aContainer.fTrackID = step->GetTrack()->GetTrackID(); + aContainer.fParticleName = + step->GetTrack()->GetDefinition()->GetParticleName(); + if (this->fContainerToSplit.GetProcessNameToSplit() != "None") { + if (this->fVectorOfContainerToSplit.size() != 0) { + G4ThreeVector vertexPosition = step->GetTrack()->GetVertexPosition(); + for (int i = 0; i < this->fVectorOfContainerToSplit.size(); i++) { + if (vertexPosition == + this->fVectorOfContainerToSplit[i].GetVertexPosition()) { + SimpleContainer tmpContainer = this->fVectorOfContainerToSplit[i]; + // std::cout<<"1 "<<tmpContainer.GetProcessNameToSplit()<<std::endl; + aContainer.fContainerToSplit = SimpleContainer( + tmpContainer.GetProcessNameToSplit(), tmpContainer.GetEnergy(), + tmpContainer.GetMomentum(), tmpContainer.GetVertexPosition(), + tmpContainer.GetPolarization(), + tmpContainer.GetParticleNameToSplit(), tmpContainer.GetWeight(), + tmpContainer.GetTrackStatus(), + tmpContainer.GetNbOfSecondaries(), + tmpContainer.GetAnnihilationFlag(), + tmpContainer.GetStepLength(), + tmpContainer.GetPrePositionToSplit()); + return aContainer; + } } + } else { + SimpleContainer tmpContainer = this->fContainerToSplit; + // std::cout<<"2 "<<tmpContainer.GetProcessNameToSplit()<<std::endl; + aContainer.fContainerToSplit = SimpleContainer( + tmpContainer.GetProcessNameToSplit(), tmpContainer.GetEnergy(), + tmpContainer.GetMomentum(), tmpContainer.GetVertexPosition(), + tmpContainer.GetPolarization(), + tmpContainer.GetParticleNameToSplit(), tmpContainer.GetWeight(), + tmpContainer.GetTrackStatus(), tmpContainer.GetNbOfSecondaries(), + tmpContainer.GetAnnihilationFlag(), tmpContainer.GetStepLength(), + tmpContainer.GetPrePositionToSplit()); + return aContainer; } } - else{ - SimpleContainer tmpContainer = this->fContainerToSplit; - //std::cout<<"2 "<<tmpContainer.GetProcessNameToSplit()<<std::endl; - aContainer.fContainerToSplit = SimpleContainer(tmpContainer.GetProcessNameToSplit(),tmpContainer.GetEnergy(),tmpContainer.GetMomentum(),tmpContainer.GetVertexPosition(),tmpContainer.GetPolarization(),tmpContainer.GetParticleNameToSplit(),tmpContainer.GetWeight(),tmpContainer.GetTrackStatus(),tmpContainer.GetNbOfSecondaries(),tmpContainer.GetAnnihilationFlag(),tmpContainer.GetStepLength(),tmpContainer.GetPrePositionToSplit()); - return aContainer; - } + // std::cout<<"3"<<std::endl; + return aContainer; } - //std::cout<<"3"<<std::endl; - return aContainer; -} - - - - - -friend std::ostream& operator<<(std::ostream& os, LastVertexDataContainer& container) { - os <<container.fParticleName<<" ID: "<<container.fTrackID<< " process to split : "<<container.fContainerToSplit.GetProcessNameToSplit()<<" name to split"<<container.fContainerToSplit.GetParticleNameToSplit() ; + friend std::ostream &operator<<(std::ostream &os, + LastVertexDataContainer &container) { + os << container.fParticleName << " ID: " << container.fTrackID + << " process to split : " + << container.fContainerToSplit.GetProcessNameToSplit() + << " name to split" + << container.fContainerToSplit.GetParticleNameToSplit(); return os; -} - - - - -private : - -G4String fParticleName="None"; -G4int fTrackID = 0; -G4String fCreationProcessName ="None"; -SimpleContainer fContainerToSplit; + } -std::vector<SimpleContainer> fVectorOfContainerToSplit; +private: + G4String fParticleName = "None"; + G4int fTrackID = 0; + G4String fCreationProcessName = "None"; + SimpleContainer fContainerToSplit; + std::vector<SimpleContainer> fVectorOfContainerToSplit; }; #endif - - - - - - \ No newline at end of file diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h index 38a9dc2ea..ba7b9b366 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingPostStepDoIt.h @@ -27,90 +27,77 @@ #ifndef GateLastVertexSplittingPostStepDoIt_h #define GateLastVertexSplittingPostStepDoIt_h - -#include "G4VEnergyLossProcess.hh" -#include "G4VEmProcess.hh" -#include "G4VParticleChange.hh" -#include "G4eplusAnnihilation.hh" -#include "G4PhysicalConstants.hh" -#include "G4MaterialCutsCouple.hh" -#include "G4Gamma.hh" #include "G4Electron.hh" -#include "G4Positron.hh" -#include "G4eeToTwoGammaModel.hh" #include "G4EmBiasingManager.hh" -#include "G4EntanglementAuxInfo.hh" -#include "G4eplusAnnihilationEntanglementClipBoard.hh" #include "G4EmParameters.hh" +#include "G4EntanglementAuxInfo.hh" +#include "G4Gamma.hh" +#include "G4MaterialCutsCouple.hh" +#include "G4PhysicalConstants.hh" #include "G4PhysicsModelCatalog.hh" +#include "G4Positron.hh" +#include "G4VEmProcess.hh" +#include "G4VEnergyLossProcess.hh" +#include "G4VParticleChange.hh" +#include "G4eeToTwoGammaModel.hh" +#include "G4eplusAnnihilation.hh" +#include "G4eplusAnnihilationEntanglementClipBoard.hh" #include <iostream> - - - class GateBremPostStepDoIt : public G4VEnergyLossProcess { -public : - -GateBremPostStepDoIt(); - -~ GateBremPostStepDoIt(); - -virtual G4VParticleChange * PostStepDoIt (const G4Track & track, const G4Step & step) override -{ - const G4MaterialCutsCouple* couple = step.GetPreStepPoint()->GetMaterialCutsCouple(); - currentCouple = couple; - G4VParticleChange* particleChange = G4VEnergyLossProcess::PostStepDoIt(track,step); - return particleChange; -} - -virtual G4VParticleChange * AlongStepDoIt (const G4Track & track, const G4Step & step) override -{ - G4VParticleChange* particleChange = G4VEnergyLossProcess::AlongStepDoIt(track,step); - return particleChange; -} - +public: + GateBremPostStepDoIt(); + + ~GateBremPostStepDoIt(); + + virtual G4VParticleChange *PostStepDoIt(const G4Track &track, + const G4Step &step) override { + const G4MaterialCutsCouple *couple = + step.GetPreStepPoint()->GetMaterialCutsCouple(); + currentCouple = couple; + G4VParticleChange *particleChange = + G4VEnergyLossProcess::PostStepDoIt(track, step); + return particleChange; + } + virtual G4VParticleChange *AlongStepDoIt(const G4Track &track, + const G4Step &step) override { + G4VParticleChange *particleChange = + G4VEnergyLossProcess::AlongStepDoIt(track, step); + return particleChange; + } }; - class GateGammaEmPostStepDoIt : public G4VEmProcess { -public : - -GateGammaEmPostStepDoIt(); - -~ GateGammaEmPostStepDoIt(); - -virtual G4VParticleChange * PostStepDoIt(const G4Track & track, const G4Step & step) override -{ - const G4MaterialCutsCouple* couple = step.GetPreStepPoint()->GetMaterialCutsCouple(); - currentCouple = couple; - G4VParticleChange* particleChange = G4VEmProcess::PostStepDoIt(track,step); - return particleChange; -} - - +public: + GateGammaEmPostStepDoIt(); + + ~GateGammaEmPostStepDoIt(); + + virtual G4VParticleChange *PostStepDoIt(const G4Track &track, + const G4Step &step) override { + const G4MaterialCutsCouple *couple = + step.GetPreStepPoint()->GetMaterialCutsCouple(); + currentCouple = couple; + G4VParticleChange *particleChange = G4VEmProcess::PostStepDoIt(track, step); + return particleChange; + } }; class GateplusannihilAtRestDoIt : public G4eplusAnnihilation { -public : - -GateplusannihilAtRestDoIt(); -~ GateplusannihilAtRestDoIt(); +public: + GateplusannihilAtRestDoIt(); + ~GateplusannihilAtRestDoIt(); -virtual G4VParticleChange* AtRestDoIt(const G4Track& track, - const G4Step& step) override -// Performs the e+ e- annihilation when both particles are assumed at rest. + virtual G4VParticleChange *AtRestDoIt(const G4Track &track, + const G4Step &step) override + // Performs the e+ e- annihilation when both particles are assumed at rest. { - G4Track copyTrack = G4Track(track); - copyTrack.SetStep(&step); - G4VParticleChange* particleChange = G4eplusAnnihilation::AtRestDoIt(copyTrack,step); - return particleChange; + G4Track copyTrack = G4Track(track); + copyTrack.SetStep(&step); + G4VParticleChange *particleChange = + G4eplusAnnihilation::AtRestDoIt(copyTrack, step); + return particleChange; } }; #endif - - - - - - \ No newline at end of file diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h index e3069bf2c..f39087b70 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h @@ -27,191 +27,131 @@ #ifndef SimpleContainer_h #define SimpleContainer_h - -#include <iostream> -#include "G4VEnergyLossProcess.hh" +#include "G4Electron.hh" +#include "G4EmBiasingManager.hh" +#include "G4EmParameters.hh" +#include "G4EntanglementAuxInfo.hh" +#include "G4Gamma.hh" +#include "G4MaterialCutsCouple.hh" +#include "G4PhysicalConstants.hh" +#include "G4PhysicsModelCatalog.hh" +#include "G4Positron.hh" #include "G4Track.hh" #include "G4VEmProcess.hh" +#include "G4VEnergyLossProcess.hh" #include "G4VParticleChange.hh" -#include "G4eplusAnnihilation.hh" -#include "G4PhysicalConstants.hh" -#include "G4MaterialCutsCouple.hh" -#include "G4Gamma.hh" -#include "G4Electron.hh" -#include "G4Positron.hh" #include "G4eeToTwoGammaModel.hh" -#include "G4EmBiasingManager.hh" -#include "G4EntanglementAuxInfo.hh" +#include "G4eplusAnnihilation.hh" #include "G4eplusAnnihilationEntanglementClipBoard.hh" -#include "G4EmParameters.hh" -#include "G4PhysicsModelCatalog.hh" - - -class SimpleContainer{ - -public : -SimpleContainer(G4String processName,G4double energy,G4ThreeVector momentum, G4ThreeVector position,G4ThreeVector polarization,G4String name,G4double weight,G4int trackStatus,G4int nbSec,G4String flag,G4double length, G4ThreeVector prePos){ - - fProcessNameToSplit = processName; - fEnergyToSplit = energy; - fMomentumToSplit = momentum; - fPositionToSplit = position; - fPolarizationToSplit = polarization; - fParticleNameToSplit = name; - fWeightToSplit = weight; - fTrackStatusToSplit = trackStatus; - fNumberOfSecondariesToSplit = nbSec; - fAnnihilProcessFlag = flag; - fStepLength = length; - fPrePosition = prePos; - -} - - -SimpleContainer(){} - -~SimpleContainer(){} - -void SetProcessNameToSplit(G4String processName){ - fProcessNameToSplit = processName; -} - -G4String GetProcessNameToSplit(){ - return fProcessNameToSplit; -} - -void SetEnergy(G4double energy){ - fEnergyToSplit =energy; -} - -G4double GetEnergy(){ - return fEnergyToSplit; -} - - -void SetWeight(G4double weight){ - fWeightToSplit =weight; -} - -G4double GetWeight(){ - return fWeightToSplit; -} - -void SetPolarization(G4ThreeVector polarization){ - fPolarizationToSplit = polarization; -} - -G4ThreeVector GetPolarization(){ - return fPolarizationToSplit; -} - -void SetMomentum(G4ThreeVector momentum){ - fMomentumToSplit =momentum; -} - -G4ThreeVector GetMomentum(){ - return fMomentumToSplit; -} +#include <iostream> -void SetVertexPosition(G4ThreeVector position){ - fPositionToSplit = position; -} +class SimpleContainer { -G4ThreeVector GetVertexPosition(){ - return fPositionToSplit; -} +public: + SimpleContainer(G4String processName, G4double energy, G4ThreeVector momentum, + G4ThreeVector position, G4ThreeVector polarization, + G4String name, G4double weight, G4int trackStatus, + G4int nbSec, G4String flag, G4double length, + G4ThreeVector prePos) { -void SetParticleNameToSplit(G4String name){ - fParticleNameToSplit = name; -} + fProcessNameToSplit = processName; + fEnergyToSplit = energy; + fMomentumToSplit = momentum; + fPositionToSplit = position; + fPolarizationToSplit = polarization; + fParticleNameToSplit = name; + fWeightToSplit = weight; + fTrackStatusToSplit = trackStatus; + fNumberOfSecondariesToSplit = nbSec; + fAnnihilProcessFlag = flag; + fStepLength = length; + fPrePosition = prePos; + } -G4String GetParticleNameToSplit(){ - return fParticleNameToSplit; -} + SimpleContainer() {} + ~SimpleContainer() {} -void SetTrackStatus(G4int trackStatus){ - fTrackStatusToSplit = trackStatus; -} + void SetProcessNameToSplit(G4String processName) { + fProcessNameToSplit = processName; + } + G4String GetProcessNameToSplit() { return fProcessNameToSplit; } -G4int GetTrackStatus(){ - return fTrackStatusToSplit; -} + void SetEnergy(G4double energy) { fEnergyToSplit = energy; } + G4double GetEnergy() { return fEnergyToSplit; } -void SetNbOfSecondaries(G4int nbSec){ - fNumberOfSecondariesToSplit = nbSec; -} + void SetWeight(G4double weight) { fWeightToSplit = weight; } -G4int GetNbOfSecondaries(){ - return fNumberOfSecondariesToSplit; -} + G4double GetWeight() { return fWeightToSplit; } -void SetAnnihilationFlag(G4String flag){ - fAnnihilProcessFlag = flag; -} + void SetPolarization(G4ThreeVector polarization) { + fPolarizationToSplit = polarization; + } -G4String GetAnnihilationFlag(){ - return fAnnihilProcessFlag; -} + G4ThreeVector GetPolarization() { return fPolarizationToSplit; } -void SetStepLength(G4double length){ - fStepLength = length; -} + void SetMomentum(G4ThreeVector momentum) { fMomentumToSplit = momentum; } -G4double GetStepLength(){ - return fStepLength; -} + G4ThreeVector GetMomentum() { return fMomentumToSplit; } + void SetVertexPosition(G4ThreeVector position) { + fPositionToSplit = position; + } + G4ThreeVector GetVertexPosition() { return fPositionToSplit; } -void SetPrePositionToSplit(G4ThreeVector prePos){ - fPrePosition = prePos; -} + void SetParticleNameToSplit(G4String name) { fParticleNameToSplit = name; } -G4ThreeVector GetPrePositionToSplit(){ - return fPrePosition; -} + G4String GetParticleNameToSplit() { return fParticleNameToSplit; } + void SetTrackStatus(G4int trackStatus) { fTrackStatusToSplit = trackStatus; } + G4int GetTrackStatus() { return fTrackStatusToSplit; } + void SetNbOfSecondaries(G4int nbSec) { fNumberOfSecondariesToSplit = nbSec; } -void DumpInfoToSplit(){ - std::cout<<"Particle name of the particle to split: "<<fParticleNameToSplit<<std::endl; - std::cout<<"Kinetic Energy of the particle to split: "<<fEnergyToSplit<<std::endl; - std::cout<<"Momentum of the particle to split: "<<fMomentumToSplit<<std::endl; - std::cout<<"Initial position of the particle to split: "<<fPositionToSplit<<std::endl; - std::cout<<"ProcessNameToSplit: "<<fProcessNameToSplit<<std::endl; - std::cout<<" "<<std::endl; + G4int GetNbOfSecondaries() { return fNumberOfSecondariesToSplit; } -} + void SetAnnihilationFlag(G4String flag) { fAnnihilProcessFlag = flag; } + G4String GetAnnihilationFlag() { return fAnnihilProcessFlag; } + void SetStepLength(G4double length) { fStepLength = length; } + G4double GetStepLength() { return fStepLength; } -private : + void SetPrePositionToSplit(G4ThreeVector prePos) { fPrePosition = prePos; } -G4String fParticleNameToSplit="None"; -G4String fProcessNameToSplit ="None"; -G4double fEnergyToSplit = 0; -G4ThreeVector fMomentumToSplit; -G4ThreeVector fPositionToSplit; -G4ThreeVector fPolarizationToSplit; -G4double fWeightToSplit; -G4int fTrackStatusToSplit; -G4int fNumberOfSecondariesToSplit; -G4String fAnnihilProcessFlag; -G4double fStepLength; -G4ThreeVector fPrePosition; + G4ThreeVector GetPrePositionToSplit() { return fPrePosition; } + void DumpInfoToSplit() { + std::cout << "Particle name of the particle to split: " + << fParticleNameToSplit << std::endl; + std::cout << "Kinetic Energy of the particle to split: " << fEnergyToSplit + << std::endl; + std::cout << "Momentum of the particle to split: " << fMomentumToSplit + << std::endl; + std::cout << "Initial position of the particle to split: " + << fPositionToSplit << std::endl; + std::cout << "ProcessNameToSplit: " << fProcessNameToSplit << std::endl; + std::cout << " " << std::endl; + } +private: + G4String fParticleNameToSplit = "None"; + G4String fProcessNameToSplit = "None"; + G4double fEnergyToSplit = 0; + G4ThreeVector fMomentumToSplit; + G4ThreeVector fPositionToSplit; + G4ThreeVector fPolarizationToSplit; + G4double fWeightToSplit; + G4int fTrackStatusToSplit; + G4int fNumberOfSecondariesToSplit; + G4String fAnnihilProcessFlag; + G4double fStepLength; + G4ThreeVector fPrePosition; }; #endif - - - - - - \ No newline at end of file diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp index 990a5629f..a1d281d47 100644 --- a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp +++ b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp @@ -119,11 +119,9 @@ void GateOptnForceFreeFlight ::AlongMoveBy( { G4String processName = callingProcess->GetWrappedProcess()->GetProcessName(); - if (processName != "Rayl"){ - fWeightChange[processName] = - weightChange; - } - else { + if (processName != "Rayl") { + fWeightChange[processName] = weightChange; + } else { fWeightChange[processName] = 1; } } diff --git a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp index ba894cf04..8c22b6399 100644 --- a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp +++ b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp @@ -49,57 +49,63 @@ //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptnVGenericSplitting:: - GateOptnVGenericSplitting(G4String name) +GateOptnVGenericSplitting::GateOptnVGenericSplitting(G4String name) : G4VBiasingOperation(name), fParticleChange() {} //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... -GateOptnVGenericSplitting:: - ~GateOptnVGenericSplitting() {} +GateOptnVGenericSplitting::~GateOptnVGenericSplitting() {} +void GateOptnVGenericSplitting::TrackInitializationChargedParticle( + G4ParticleChange *particleChange, G4VParticleChange *processFinalState, + const G4Track *track, G4double split) { -void GateOptnVGenericSplitting::TrackInitializationChargedParticle(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split) { - - G4ParticleChangeForLoss* processFinalStateForLoss =( G4ParticleChangeForLoss* ) processFinalState ; + G4ParticleChangeForLoss *processFinalStateForLoss = + (G4ParticleChangeForLoss *)processFinalState; particleChange->Initialize(*track); - particleChange->ProposeTrackStatus(processFinalStateForLoss->GetTrackStatus() ); - particleChange->ProposeEnergy(processFinalStateForLoss->GetProposedKineticEnergy() ); - particleChange->ProposeMomentumDirection(processFinalStateForLoss->GetProposedMomentumDirection()); + particleChange->ProposeTrackStatus( + processFinalStateForLoss->GetTrackStatus()); + particleChange->ProposeEnergy( + processFinalStateForLoss->GetProposedKineticEnergy()); + particleChange->ProposeMomentumDirection( + processFinalStateForLoss->GetProposedMomentumDirection()); particleChange->SetNumberOfSecondaries(fSplittingFactor); particleChange->SetSecondaryWeightByProcess(true); processFinalStateForLoss->Clear(); } -void GateOptnVGenericSplitting::TrackInitializationGamma(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split) { - G4ParticleChangeForGamma* processFinalStateForGamma = (G4ParticleChangeForGamma *)processFinalState; +void GateOptnVGenericSplitting::TrackInitializationGamma( + G4ParticleChange *particleChange, G4VParticleChange *processFinalState, + const G4Track *track, G4double split) { + G4ParticleChangeForGamma *processFinalStateForGamma = + (G4ParticleChangeForGamma *)processFinalState; particleChange->Initialize(*track); - particleChange->ProposeTrackStatus(processFinalStateForGamma->GetTrackStatus() ); - particleChange->ProposeEnergy(processFinalStateForGamma->GetProposedKineticEnergy() ); - particleChange->ProposeMomentumDirection(processFinalStateForGamma->GetProposedMomentumDirection() ); + particleChange->ProposeTrackStatus( + processFinalStateForGamma->GetTrackStatus()); + particleChange->ProposeEnergy( + processFinalStateForGamma->GetProposedKineticEnergy()); + particleChange->ProposeMomentumDirection( + processFinalStateForGamma->GetProposedMomentumDirection()); particleChange->SetNumberOfSecondaries(fSplittingFactor); particleChange->SetSecondaryWeightByProcess(true); processFinalStateForGamma->Clear(); - - } -G4double GateOptnVGenericSplitting::RussianRouletteForAngleSurvival(G4ThreeVector dir,G4ThreeVector vectorDirector,G4double maxTheta,G4double split){ -G4double cosTheta =vectorDirector * dir; -G4double theta = std::acos(cosTheta); -G4double weightToApply = 1; -if (theta > maxTheta){ - G4double probability = G4UniformRand(); - if (probability <= 1 / split) { - weightToApply = split; +G4double GateOptnVGenericSplitting::RussianRouletteForAngleSurvival( + G4ThreeVector dir, G4ThreeVector vectorDirector, G4double maxTheta, + G4double split) { + G4double cosTheta = vectorDirector * dir; + G4double theta = std::acos(cosTheta); + G4double weightToApply = 1; + if (theta > maxTheta) { + G4double probability = G4UniformRand(); + if (probability <= 1 / split) { + weightToApply = split; + } else { + weightToApply = 0; + } } - else{ - weightToApply = 0; - } -} -return weightToApply; - + return weightToApply; } - //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h index d7cebd78a..b89f0baee 100644 --- a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h +++ b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h @@ -70,15 +70,20 @@ class GateOptnVGenericSplitting : public G4VBiasingOperation { return 0; } + // ---------------------------------------------- + // -- Methods for the generic splitting + // ---------------------------------------------- -// ---------------------------------------------- -// -- Methods for the generic splitting -// ---------------------------------------------- - -void TrackInitializationChargedParticle(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split); -void TrackInitializationGamma(G4ParticleChange* particleChange,G4VParticleChange* processFinalState, const G4Track* track,G4double split); -static G4double RussianRouletteForAngleSurvival(G4ThreeVector dir,G4ThreeVector vectorDirector,G4double maxTheta,G4double split); - + void TrackInitializationChargedParticle(G4ParticleChange *particleChange, + G4VParticleChange *processFinalState, + const G4Track *track, G4double split); + void TrackInitializationGamma(G4ParticleChange *particleChange, + G4VParticleChange *processFinalState, + const G4Track *track, G4double split); + static G4double RussianRouletteForAngleSurvival(G4ThreeVector dir, + G4ThreeVector vectorDirector, + G4double maxTheta, + G4double split); public: // ---------------------------------------------- diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp index 13c5b3cfb..f973cc4aa 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp @@ -50,12 +50,9 @@ #include "G4eplusAnnihilation.hh" #include "GateOptnPairProdSplitting.h" #include "GateOptnScatteredGammaSplitting.h" +#include "GateOptnVGenericSplitting.h" #include "GateOptneBremSplitting.h" #include "GateOptrComptPseudoTransportationActor.h" -#include "G4UImanager.hh" -#include "G4eplusAnnihilation.hh" -#include "GateOptnVGenericSplitting.h" - //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... @@ -144,9 +141,8 @@ void GateOptrComptPseudoTransportationActor::StartSimulationAction() { fPairProdSplittingOperation->SetSplittingFactor(fSplittingFactor); - fFreeFlightOperation->SetRussianRouletteForWeights(fRussianRouletteForWeights); - - + fFreeFlightOperation->SetRussianRouletteForWeights( + fRussianRouletteForWeights); } void GateOptrComptPseudoTransportationActor::StartRun() { @@ -171,38 +167,47 @@ void GateOptrComptPseudoTransportationActor::StartRun() { void GateOptrComptPseudoTransportationActor::SteppingAction(G4Step *step) { G4String creationProcessName = "None"; - if (step->GetTrack()->GetCreatorProcess() != 0){ - creationProcessName = step->GetTrack()->GetCreatorProcess()->GetProcessName(); - + if (step->GetTrack()->GetCreatorProcess() != 0) { + creationProcessName = + step->GetTrack()->GetCreatorProcess()->GetProcessName(); } - -if ((fIsFirstStep) && (fRussianRouletteForAngle)){ - G4String LogicalVolumeNameOfCreation = step->GetTrack()->GetLogicalVolumeAtVertex()->GetName(); - if (std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeNameOfCreation ) !=fNameOfBiasedLogicalVolume.end()){ - if (creationProcessName == "biasWrapper(annihil)"){ + + if ((fIsFirstStep) && (fRussianRouletteForAngle)) { + G4String LogicalVolumeNameOfCreation = + step->GetTrack()->GetLogicalVolumeAtVertex()->GetName(); + if (std::find(fNameOfBiasedLogicalVolume.begin(), + fNameOfBiasedLogicalVolume.end(), + LogicalVolumeNameOfCreation) != + fNameOfBiasedLogicalVolume.end()) { + if (creationProcessName == "biasWrapper(annihil)") { auto dir = step->GetPreStepPoint()->GetMomentumDirection(); - G4double w = GateOptnVGenericSplitting::RussianRouletteForAngleSurvival(dir,fVectorDirector,fMaxTheta,fSplittingFactor); - if (w == 0) - { + G4double w = GateOptnVGenericSplitting::RussianRouletteForAngleSurvival( + dir, fVectorDirector, fMaxTheta, fSplittingFactor); + if (w == 0) { step->GetTrack()->SetTrackStatus(fStopAndKill); - } - else { + } else { step->GetTrack()->SetWeight(step->GetTrack()->GetWeight() * w); } } + } } -} - -if ((isSplitted == true) && (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary)) { - G4String LogicalVolumeName = step->GetPostStepPoint()->GetPhysicalVolume()->GetLogicalVolume()->GetName(); - if (!(std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeName ) !=fNameOfBiasedLogicalVolume.end()) - && (LogicalVolumeName != fMotherVolumeName)) { + + if ((isSplitted == true) && + (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary)) { + G4String LogicalVolumeName = step->GetPostStepPoint() + ->GetPhysicalVolume() + ->GetLogicalVolume() + ->GetName(); + if (!(std::find(fNameOfBiasedLogicalVolume.begin(), + fNameOfBiasedLogicalVolume.end(), + LogicalVolumeName) != fNameOfBiasedLogicalVolume.end()) && + (LogicalVolumeName != fMotherVolumeName)) { step->GetTrack()->SetTrackStatus(fStopAndKill); isSplitted = false; + } } -} -fIsFirstStep = false; + fIsFirstStep = false; } void GateOptrComptPseudoTransportationActor::BeginOfEventAction( @@ -218,14 +223,17 @@ void GateOptrComptPseudoTransportationActor::StartTracking( const G4Track *track) { G4String creationProcessName = "None"; fIsFirstStep = true; - if (track->GetCreatorProcess() != 0){ + if (track->GetCreatorProcess() != 0) { creationProcessName = track->GetCreatorProcess()->GetProcessName(); } - - if (track->GetParticleDefinition()->GetParticleName() == "gamma"){ - G4String LogicalVolumeNameOfCreation = track->GetLogicalVolumeAtVertex()->GetName(); - if (std::find(fNameOfBiasedLogicalVolume.begin(), fNameOfBiasedLogicalVolume.end(),LogicalVolumeNameOfCreation ) !=fNameOfBiasedLogicalVolume.end()){ + if (track->GetParticleDefinition()->GetParticleName() == "gamma") { + G4String LogicalVolumeNameOfCreation = + track->GetLogicalVolumeAtVertex()->GetName(); + if (std::find(fNameOfBiasedLogicalVolume.begin(), + fNameOfBiasedLogicalVolume.end(), + LogicalVolumeNameOfCreation) != + fNameOfBiasedLogicalVolume.end()) { fInitialWeight = track->GetWeight(); fFreeFlightOperation->SetInitialWeight(fInitialWeight); @@ -299,8 +307,10 @@ GateOptrComptPseudoTransportationActor::ProposeFinalStateBiasingOperation( return feBremSplittingOperation; } - if (!(std::find(fCreationProcessNameList.begin(), fCreationProcessNameList.end(),CreationProcessName) != fCreationProcessNameList.end())){ - if (callingProcess->GetWrappedProcess()->GetProcessName() == "compt"){ + if (!(std::find(fCreationProcessNameList.begin(), + fCreationProcessNameList.end(), + CreationProcessName) != fCreationProcessNameList.end())) { + if (callingProcess->GetWrappedProcess()->GetProcessName() == "compt") { isSplitted = true; return fScatteredGammaSplittingOperation; diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h index 629d31b69..c463a4e2c 100644 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h +++ b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h @@ -85,13 +85,14 @@ class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, G4int ftrackIDAtTheEntrance; G4int fEventID; G4double fEventIDKineticEnergy; - G4bool ftestbool= false; + G4bool ftestbool = false; G4bool fIsFirstStep = false; - const G4VProcess* fAnnihilation =nullptr; + const G4VProcess *fAnnihilation = nullptr; std::vector<G4String> fNameOfBiasedLogicalVolume = {}; std::vector<G4int> v_EventID = {}; - std::vector<G4String> fCreationProcessNameList = {"biasWrapper(compt)", "biasWrapper(eBrem)","biasWrapper(annihil)"}; + std::vector<G4String> fCreationProcessNameList = { + "biasWrapper(compt)", "biasWrapper(eBrem)", "biasWrapper(annihil)"}; // Unused but mandatory diff --git a/core/opengate_core/opengate_lib/GateSourceManager.cpp b/core/opengate_core/opengate_lib/GateSourceManager.cpp index d0772fe15..61dfb8361 100644 --- a/core/opengate_core/opengate_lib/GateSourceManager.cpp +++ b/core/opengate_core/opengate_lib/GateSourceManager.cpp @@ -241,9 +241,6 @@ void GateSourceManager::PrepareRunToStart(int run_id) { : std::to_string(G4Threading::G4GetThreadId())); } - - - void GateSourceManager::PrepareNextSource() { auto &l = fThreadLocalData.Get(); l.fNextActiveSource = nullptr; diff --git a/core/opengate_core/opengate_lib/GateSourceManager.h b/core/opengate_core/opengate_lib/GateSourceManager.h index 1b559a07b..342cbaeab 100644 --- a/core/opengate_core/opengate_lib/GateSourceManager.h +++ b/core/opengate_core/opengate_lib/GateSourceManager.h @@ -64,20 +64,19 @@ class GateSourceManager : public G4VUserPrimaryGeneratorAction { // Return a source GateVSource *FindSourceByName(std::string name) const; - - - G4String GetActiveSourceName(){ - auto &l = fThreadLocalData.Get(); - if (l.fNextActiveSource !=0){ + + G4String GetActiveSourceName() { + auto &l = fThreadLocalData.Get(); + if (l.fNextActiveSource != 0) { G4String name = l.fNextActiveSource->fName; return name; } return "None"; } - void SetActiveSourcebyName(G4String sourceName){ + void SetActiveSourcebyName(G4String sourceName) { auto &l = fThreadLocalData.Get(); - auto* source = FindSourceByName(sourceName); + auto *source = FindSourceByName(sourceName); l.fNextActiveSource = source; } diff --git a/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp b/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp index 35897f33b..3c22c34eb 100644 --- a/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp +++ b/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp @@ -110,7 +110,7 @@ void GateTLEDoseActor::SteppingAction(G4Step *step) { if (fDoseFlag) { ImageAddValue<Image3DType>(cpp_dose_image, index, dose); } - ImageAddValue<Image3DType>(cpp_edep_image, index, edep); + ImageAddValue<Image3DType>(cpp_edep_image, index, edep); if (fEdepSquaredFlag || fDoseSquaredFlag) { if (fEdepSquaredFlag) { @@ -122,6 +122,5 @@ void GateTLEDoseActor::SteppingAction(G4Step *step) { dose, event_id, index); } } - } } diff --git a/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp index d841af3d0..d454f60a4 100644 --- a/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateKillNonInteractingParticleActor.cpp @@ -18,6 +18,7 @@ void init_GateKillNonInteractingParticleActor(py::module &m) { .def_readwrite( "fListOfVolumeAncestor", &GateKillNonInteractingParticleActor::fListOfVolumeAncestor) - .def_readwrite("number_of_killed_particles", &GateKillNonInteractingParticleActor::fNbOfKilledParticles) + .def_readwrite("number_of_killed_particles", + &GateKillNonInteractingParticleActor::fNbOfKilledParticles) .def(py::init<py::dict &>()); } diff --git a/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp index 4a6a8c130..85f22a9ef 100644 --- a/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp @@ -11,8 +11,9 @@ namespace py = pybind11; void init_GateLastVertexInteractionSplittingActor(py::module &m) { - py::class_<GateLastVertexInteractionSplittingActor, GateVActor, - std::unique_ptr<GateLastVertexInteractionSplittingActor, py::nodelete>>( + py::class_< + GateLastVertexInteractionSplittingActor, GateVActor, + std::unique_ptr<GateLastVertexInteractionSplittingActor, py::nodelete>>( m, "GateLastVertexInteractionSplittingActor") .def_readwrite( "fListOfVolumeAncestor", diff --git a/core/opengate_core/opengate_lib/tree.hh b/core/opengate_core/opengate_lib/tree.hh index 4f78d5b8a..ef78833de 100644 --- a/core/opengate_core/opengate_lib/tree.hh +++ b/core/opengate_core/opengate_lib/tree.hh @@ -4,7 +4,7 @@ // Copyright (C) 2001-2024 Kasper Peeters <kasper@phi-sci.com> // Distributed under the GNU General Public License version 3. // -// Special permission to use tree.hh under the conditions of a +// Special permission to use tree.hh under the conditions of a // different license can be requested from the author. /** \mainpage tree.hh @@ -18,1460 +18,1508 @@ nodes. Various types of iterators are provided (post-order, pre-order, and others). Where possible the access methods are compatible with the STL or alternative algorithms are - available. + available. */ - #ifndef tree_hh_ #define tree_hh_ +#include <algorithm> #include <cassert> -#include <memory> -#include <stdexcept> +#include <cstddef> #include <iterator> -#include <set> +#include <memory> #include <queue> -#include <algorithm> -#include <cstddef> +#include <set> +#include <stdexcept> #include <string> -/// A node in the tree, combining links to other nodes as well as the actual data. -template<class T> +/// A node in the tree, combining links to other nodes as well as the actual +/// data. +template <class T> class tree_node_ { // size: 5*4=20 bytes (on 32 bit arch), can be reduced by 8. - public: - tree_node_(); - tree_node_(const T&); - tree_node_(T&&); - - tree_node_<T> *parent; - tree_node_<T> *first_child, *last_child; - tree_node_<T> *prev_sibling, *next_sibling; - T data; -}; - -template<class T> +public: + tree_node_(); + tree_node_(const T &); + tree_node_(T &&); + + tree_node_<T> *parent; + tree_node_<T> *first_child, *last_child; + tree_node_<T> *prev_sibling, *next_sibling; + T data; +}; + +template <class T> tree_node_<T>::tree_node_() - : parent(0), first_child(0), last_child(0), prev_sibling(0), next_sibling(0) - { - } - -template<class T> -tree_node_<T>::tree_node_(const T& val) - : parent(0), first_child(0), last_child(0), prev_sibling(0), next_sibling(0), data(val) - { - } - -template<class T> -tree_node_<T>::tree_node_(T&& val) - : parent(0), first_child(0), last_child(0), prev_sibling(0), next_sibling(0), data(val) - { - } + : parent(0), first_child(0), last_child(0), prev_sibling(0), + next_sibling(0) {} + +template <class T> +tree_node_<T>::tree_node_(const T &val) + : parent(0), first_child(0), last_child(0), prev_sibling(0), + next_sibling(0), data(val) {} + +template <class T> +tree_node_<T>::tree_node_(T &&val) + : parent(0), first_child(0), last_child(0), prev_sibling(0), + next_sibling(0), data(val) {} // Throw an exception with a stacktrace. -//template <class E> -//void throw_with_trace(const E& e) +// template <class E> +// void throw_with_trace(const E& e) // { // throw boost::enable_error_info(e) // << traced(boost::stacktrace::stacktrace()); // } class navigation_error : public std::logic_error { - public: - navigation_error(const std::string& s) : std::logic_error(s) - { -// assert(1==0); -// std::ostringstream str; -// std::cerr << boost::stacktrace::stacktrace() << std::endl; -// str << boost::stacktrace::stacktrace(); -// stacktrace=str.str(); - } - -// virtual const char *what() const noexcept override -// { -// return (std::logic_error::what()+std::string("; ")+stacktrace).c_str(); -// } -// -// std::string stacktrace; +public: + navigation_error(const std::string &s) : std::logic_error(s) { + // assert(1==0); + // std::ostringstream str; + // std::cerr << boost::stacktrace::stacktrace() << + //std::endl; str << boost::stacktrace::stacktrace(); stacktrace=str.str(); + } + + // virtual const char *what() const noexcept override + // { + // return (std::logic_error::what()+std::string("; + //")+stacktrace).c_str(); + // } + // + // std::string stacktrace; }; - -template <class T, class tree_node_allocator = std::allocator<tree_node_<T> > > + +template <class T, class tree_node_allocator = std::allocator<tree_node_<T>>> class tree { - protected: - typedef tree_node_<T> tree_node; - public: - /// Value of the data stored at a node. - typedef T value_type; - - class iterator_base; - class pre_order_iterator; - class post_order_iterator; - class sibling_iterator; - class leaf_iterator; - - tree(); // empty constructor - tree(const T&); // constructor setting given element as head - tree(const iterator_base&); - tree(const tree<T, tree_node_allocator>&); // copy constructor - tree(tree<T, tree_node_allocator>&&); // move constructor - ~tree(); - tree<T,tree_node_allocator>& operator=(const tree<T, tree_node_allocator>&); // copy assignment - tree<T,tree_node_allocator>& operator=(tree<T, tree_node_allocator>&&); // move assignment - - /// Base class for iterators, only pointers stored, no traversal logic. +protected: + typedef tree_node_<T> tree_node; + +public: + /// Value of the data stored at a node. + typedef T value_type; + + class iterator_base; + class pre_order_iterator; + class post_order_iterator; + class sibling_iterator; + class leaf_iterator; + + tree(); // empty constructor + tree(const T &); // constructor setting given element as head + tree(const iterator_base &); + tree(const tree<T, tree_node_allocator> &); // copy constructor + tree(tree<T, tree_node_allocator> &&); // move constructor + ~tree(); + tree<T, tree_node_allocator> & + operator=(const tree<T, tree_node_allocator> &); // copy assignment + tree<T, tree_node_allocator> & + operator=(tree<T, tree_node_allocator> &&); // move assignment + + /// Base class for iterators, only pointers stored, no traversal logic. #ifdef __SGI_STL_PORT - class iterator_base : public stlport::bidirectional_iterator<T, ptrdiff_t> { + class iterator_base : public stlport::bidirectional_iterator<T, ptrdiff_t> { #else - class iterator_base { + class iterator_base { #endif - public: - typedef T value_type; - typedef T* pointer; - typedef T& reference; - typedef size_t size_type; - typedef ptrdiff_t difference_type; - typedef std::bidirectional_iterator_tag iterator_category; - - iterator_base(); - iterator_base(tree_node *); - - T& operator*() const; - T* operator->() const; - - /// When called, the next increment/decrement skips children of this node. - void skip_children(); - void skip_children(bool skip); - /// Number of children of the node pointed to by the iterator. - unsigned int number_of_children() const; - - sibling_iterator begin() const; - sibling_iterator end() const; - - tree_node *node; - protected: - bool skip_current_children_; - }; - - /// Depth-first iterator, first accessing the node, then its children. - class pre_order_iterator : public iterator_base { - public: - pre_order_iterator(); - pre_order_iterator(tree_node *); - pre_order_iterator(const iterator_base&); - pre_order_iterator(const sibling_iterator&); - - bool operator==(const pre_order_iterator&) const; - bool operator!=(const pre_order_iterator&) const; - pre_order_iterator& operator++(); - pre_order_iterator& operator--(); - pre_order_iterator operator++(int); - pre_order_iterator operator--(int); - pre_order_iterator& operator+=(unsigned int); - pre_order_iterator& operator-=(unsigned int); - - pre_order_iterator& next_skip_children(); - }; - - /// Depth-first iterator, first accessing the children, then the node itself. - class post_order_iterator : public iterator_base { - public: - post_order_iterator(); - post_order_iterator(tree_node *); - post_order_iterator(const iterator_base&); - post_order_iterator(const sibling_iterator&); - - bool operator==(const post_order_iterator&) const; - bool operator!=(const post_order_iterator&) const; - post_order_iterator& operator++(); - post_order_iterator& operator--(); - post_order_iterator operator++(int); - post_order_iterator operator--(int); - post_order_iterator& operator+=(unsigned int); - post_order_iterator& operator-=(unsigned int); - - /// Set iterator to the first child as deep as possible down the tree. - void descend_all(); - }; - - /// Breadth-first iterator, using a queue - class breadth_first_queued_iterator : public iterator_base { - public: - breadth_first_queued_iterator(); - breadth_first_queued_iterator(tree_node *); - breadth_first_queued_iterator(const iterator_base&); - - bool operator==(const breadth_first_queued_iterator&) const; - bool operator!=(const breadth_first_queued_iterator&) const; - breadth_first_queued_iterator& operator++(); - breadth_first_queued_iterator operator++(int); - breadth_first_queued_iterator& operator+=(unsigned int); - - private: - std::queue<tree_node *> traversal_queue; - }; - - /// The default iterator types throughout the tree class. - typedef pre_order_iterator iterator; - typedef breadth_first_queued_iterator breadth_first_iterator; - - /// Iterator which traverses only the nodes at a given depth from the root. - class fixed_depth_iterator : public iterator_base { - public: - fixed_depth_iterator(); - fixed_depth_iterator(tree_node *); - fixed_depth_iterator(const iterator_base&); - fixed_depth_iterator(const sibling_iterator&); - fixed_depth_iterator(const fixed_depth_iterator&); - - void swap(fixed_depth_iterator&, fixed_depth_iterator&); - fixed_depth_iterator& operator=(fixed_depth_iterator); - - bool operator==(const fixed_depth_iterator&) const; - bool operator!=(const fixed_depth_iterator&) const; - fixed_depth_iterator& operator++(); - fixed_depth_iterator& operator--(); - fixed_depth_iterator operator++(int); - fixed_depth_iterator operator--(int); - fixed_depth_iterator& operator+=(unsigned int); - fixed_depth_iterator& operator-=(unsigned int); - - tree_node *top_node; - }; - - /// Iterator which traverses only the nodes which are siblings of each other. - class sibling_iterator : public iterator_base { - public: - sibling_iterator(); - sibling_iterator(tree_node *); - sibling_iterator(const sibling_iterator&); - sibling_iterator(const iterator_base&); - - void swap(sibling_iterator&, sibling_iterator&); - sibling_iterator& operator=(sibling_iterator); - - bool operator==(const sibling_iterator&) const; - bool operator!=(const sibling_iterator&) const; - sibling_iterator& operator++(); - sibling_iterator& operator--(); - sibling_iterator operator++(int); - sibling_iterator operator--(int); - sibling_iterator& operator+=(unsigned int); - sibling_iterator& operator-=(unsigned int); - - tree_node *range_first() const; - tree_node *range_last() const; - tree_node *parent_; - private: - void set_parent_(); - }; - - /// Iterator which traverses only the leaves. - class leaf_iterator : public iterator_base { - public: - leaf_iterator(); - leaf_iterator(tree_node *, tree_node *top=0); - leaf_iterator(const sibling_iterator&); - leaf_iterator(const iterator_base&); - - bool operator==(const leaf_iterator&) const; - bool operator!=(const leaf_iterator&) const; - leaf_iterator& operator++(); - leaf_iterator& operator--(); - leaf_iterator operator++(int); - leaf_iterator operator--(int); - leaf_iterator& operator+=(unsigned int); - leaf_iterator& operator-=(unsigned int); - private: - tree_node *top_node; - }; - - /// Return iterator to the beginning of the tree. - inline pre_order_iterator begin() const; - /// Return iterator to the end of the tree. - inline pre_order_iterator end() const; - /// Return post-order iterator to the beginning of the tree. - post_order_iterator begin_post() const; - /// Return post-order end iterator of the tree. - post_order_iterator end_post() const; - /// Return fixed-depth iterator to the first node at a given depth from the given iterator. - /// If 'walk_back=true', a depth=0 iterator will be taken from the beginning of the sibling - /// range, not the current node. - fixed_depth_iterator begin_fixed(const iterator_base&, unsigned int, bool walk_back=true) const; - /// Return fixed-depth end iterator. - fixed_depth_iterator end_fixed(const iterator_base&, unsigned int) const; - /// Return breadth-first iterator to the first node at a given depth. - breadth_first_queued_iterator begin_breadth_first() const; - /// Return breadth-first end iterator. - breadth_first_queued_iterator end_breadth_first() const; - /// Return sibling iterator to the first child of given node. - static sibling_iterator begin(const iterator_base&); - /// Return sibling end iterator for children of given node. - static sibling_iterator end(const iterator_base&); - /// Return leaf iterator to the first leaf of the tree. - leaf_iterator begin_leaf() const; - /// Return leaf end iterator for entire tree. - leaf_iterator end_leaf() const; - /// Return leaf iterator to the first leaf of the subtree at the given node. - leaf_iterator begin_leaf(const iterator_base& top) const; - /// Return leaf end iterator for the subtree at the given node. - leaf_iterator end_leaf(const iterator_base& top) const; - - typedef std::vector<int> path_t; - /// Return a path (to be taken from the 'top' node) corresponding to a node in the tree. - /// The first integer in path_t is the number of steps you need to go 'right' in the sibling - /// chain (so 0 if we go straight to the children). - path_t path_from_iterator(const iterator_base& iter, const iterator_base& top) const; - /// Return an iterator given a path from the 'top' node. - iterator iterator_from_path(const path_t&, const iterator_base& top) const; - - /// Return iterator to the parent of a node. Throws a `navigation_error` if the node - /// does not have a parent. - template<typename iter> static iter parent(iter); - /// Return iterator to the previous sibling of a node. - template<typename iter> static iter previous_sibling(iter); - /// Return iterator to the next sibling of a node. - template<typename iter> static iter next_sibling(iter); - /// Return iterator to the next node at a given depth. - template<typename iter> iter next_at_same_depth(iter) const; - - /// Erase all nodes of the tree. - void clear(); - /// Erase element at position pointed to by iterator, return incremented iterator. - template<typename iter> iter erase(iter); - /// Erase all children of the node pointed to by iterator. - void erase_children(const iterator_base&); - /// Erase all siblings to the right of the iterator. - void erase_right_siblings(const iterator_base&); - /// Erase all siblings to the left of the iterator. - void erase_left_siblings(const iterator_base&); - - /// Insert empty node as last/first child of node pointed to by position. - template<typename iter> iter append_child(iter position); - template<typename iter> iter prepend_child(iter position); - /// Insert node as last/first child of node pointed to by position. - template<typename iter> iter append_child(iter position, const T& x); - template<typename iter> iter append_child(iter position, T&& x); - template<typename iter> iter prepend_child(iter position, const T& x); - template<typename iter> iter prepend_child(iter position, T&& x); - /// Append the node (plus its children) at other_position as last/first child of position. - template<typename iter> iter append_child(iter position, iter other_position); - template<typename iter> iter prepend_child(iter position, iter other_position); - /// Append the nodes in the from-to range (plus their children) as last/first children of position. - template<typename iter> iter append_children(iter position, sibling_iterator from, sibling_iterator to); - template<typename iter> iter prepend_children(iter position, sibling_iterator from, sibling_iterator to); - - /// Short-hand to insert topmost node in otherwise empty tree. - pre_order_iterator set_head(const T& x); - pre_order_iterator set_head(T&& x); - /// Insert node as previous sibling of node pointed to by position. - template<typename iter> iter insert(iter position, const T& x); - template<typename iter> iter insert(iter position, T&& x); - /// Specialisation of previous member. - sibling_iterator insert(sibling_iterator position, const T& x); - sibling_iterator insert(sibling_iterator position, T&& x); - /// Insert node (with children) pointed to by subtree as previous sibling of node pointed to by position. - /// Does not change the subtree itself (use move_in or move_in_below for that). - template<typename iter> iter insert_subtree(iter position, const iterator_base& subtree); - /// Insert node as next sibling of node pointed to by position. - template<typename iter> iter insert_after(iter position, const T& x); - template<typename iter> iter insert_after(iter position, T&& x); - /// Insert node (with children) pointed to by subtree as next sibling of node pointed to by position. - template<typename iter> iter insert_subtree_after(iter position, const iterator_base& subtree); - - /// Replace node at 'position' with other node (keeping same children); 'position' becomes invalid. - template<typename iter> iter replace(iter position, const T& x); - /// Replace node at 'position' with subtree starting at 'from' (do not erase subtree at 'from'); see above. - template<typename iter> iter replace(iter position, const iterator_base& from); - /// Replace string of siblings (plus their children) with copy of a new string (with children); see above - sibling_iterator replace(sibling_iterator orig_begin, sibling_iterator orig_end, - sibling_iterator new_begin, sibling_iterator new_end); - - /// Move all children of node at 'position' to be siblings, returns position. - template<typename iter> iter flatten(iter position); - /// Move nodes in range to be children of 'position'. - template<typename iter> iter reparent(iter position, sibling_iterator begin, sibling_iterator end); - /// Move all child nodes of 'from' to be children of 'position'. - template<typename iter> iter reparent(iter position, iter from); - - /// Replace node with a new node, making the old node (plus subtree) a child of the new node. - template<typename iter> iter wrap(iter position, const T& x); - /// Replace the range of sibling nodes (plus subtrees), making these children of the new node. - template<typename iter> iter wrap(iter from, iter to, const T& x); - - /// Move 'source' node (plus its children) to become the next sibling of 'target'. - template<typename iter> iter move_after(iter target, iter source); - /// Move 'source' node (plus its children) to become the previous sibling of 'target'. - template<typename iter> iter move_before(iter target, iter source); - sibling_iterator move_before(sibling_iterator target, sibling_iterator source); - /// Move 'source' node (plus its children) to become the node at 'target' (erasing the node at 'target'). - template<typename iter> iter move_ontop(iter target, iter source); - - /// Extract the subtree starting at the indicated node, removing it from the original tree. - tree move_out(iterator); - /// Inverse of take_out: inserts the given tree as previous sibling of indicated node by a - /// move operation, that is, the given tree becomes empty. Returns iterator to the top node. - template<typename iter> iter move_in(iter, tree&); - /// As above, but now make the tree the last child of the indicated node. - template<typename iter> iter move_in_below(iter, tree&); - /// As above, but now make the tree the nth child of the indicated node (if possible). - template<typename iter> iter move_in_as_nth_child(iter, size_t, tree&); - - /// Merge with other tree, creating new branches and leaves only if they are not already present. - void merge(sibling_iterator, sibling_iterator, sibling_iterator, sibling_iterator, - bool duplicate_leaves=false); - /// As above, but using two trees with a single top node at the 'to' and 'from' positions. - void merge(iterator to, iterator from, bool duplicate_leaves); - /// Sort (std::sort only moves values of nodes, this one moves children as well). - void sort(sibling_iterator from, sibling_iterator to, bool deep=false); - template<class StrictWeakOrdering> - void sort(sibling_iterator from, sibling_iterator to, StrictWeakOrdering comp, bool deep=false); - /// Compare two ranges of nodes (compares nodes as well as tree structure). - template<typename iter> - bool equal(const iter& one, const iter& two, const iter& three) const; - template<typename iter, class BinaryPredicate> - bool equal(const iter& one, const iter& two, const iter& three, BinaryPredicate) const; - template<typename iter> - bool equal_subtree(const iter& one, const iter& two) const; - template<typename iter, class BinaryPredicate> - bool equal_subtree(const iter& one, const iter& two, BinaryPredicate) const; - /// Extract a new tree formed by the range of siblings plus all their children. - tree subtree(sibling_iterator from, sibling_iterator to) const; - void subtree(tree&, sibling_iterator from, sibling_iterator to) const; - /// Exchange the node (plus subtree) with its sibling node (do nothing if no sibling present). - void swap(sibling_iterator it); - /// Exchange two nodes (plus subtrees). The iterators will remain valid and keep - /// pointing to the same nodes, which now sit at different locations in the tree. - void swap(iterator, iterator); - - /// Count the total number of nodes. - size_t size() const; - /// Count the total number of nodes below the indicated node (plus one). - size_t size(const iterator_base&) const; - /// Check if tree is empty. - bool empty() const; - /// Compute the depth to the root or to a fixed other iterator. - static int depth(const iterator_base&); - static int depth(const iterator_base&, const iterator_base&); - /// Compute the depth to the root, counting all levels for which predicate returns true. - template<class Predicate> - static int depth(const iterator_base&, Predicate p); - /// Compute the depth distance between two nodes, counting all levels for which predicate returns true. - template<class Predicate> - static int distance(const iterator_base& top, const iterator_base& bottom, Predicate p); - /// Determine the maximal depth of the tree. An empty tree has max_depth=-1. - int max_depth() const; - /// Determine the maximal depth of the tree with top node at the given position. - int max_depth(const iterator_base&) const; - /// Count the number of children of node at position. - static unsigned int number_of_children(const iterator_base&); - /// Count the number of siblings (left and right) of node at iterator. Total nodes at this level is +1. - unsigned int number_of_siblings(const iterator_base&) const; - /// Determine whether node at position is in the subtrees with indicated top node. - bool is_in_subtree(const iterator_base& position, const iterator_base& top) const; - /// Determine whether node at position is in the subtrees with root in the range. - bool is_in_subtree(const iterator_base& position, const iterator_base& begin, - const iterator_base& end) const; - /// Determine whether the iterator is an 'end' iterator and thus not actually pointing to a node. - bool is_valid(const iterator_base&) const; - /// Determine whether the iterator is one of the 'head' nodes at the top level, i.e. has no parent. - static bool is_head(const iterator_base&); - /// Find the lowest common ancestor of two nodes, that is, the deepest node such that - /// both nodes are descendants of it. - iterator lowest_common_ancestor(const iterator_base&, const iterator_base &) const; - - /// Determine the index of a node in the range of siblings to which it belongs. - unsigned int index(sibling_iterator it) const; - /// Inverse of 'index': return the n-th child of the node at position. - static sibling_iterator child(const iterator_base& position, unsigned int); - /// Return iterator to the sibling indicated by index - sibling_iterator sibling(const iterator_base& position, unsigned int) const; - - /// For debugging only: verify internal consistency by inspecting all pointers in the tree - /// (which will also trigger a valgrind error in case something got corrupted). - void debug_verify_consistency() const; - - /// Comparator class for iterators (compares pointer values; why doesn't this work automatically?) - class iterator_base_less { - public: - bool operator()(const typename tree<T, tree_node_allocator>::iterator_base& one, - const typename tree<T, tree_node_allocator>::iterator_base& two) const - { - return one.node < two.node; - } - }; - tree_node *head, *feet; // head/feet are always dummy; if an iterator points to them it is invalid - private: - tree_node_allocator alloc_; - void head_initialise_(); - void copy_(const tree<T, tree_node_allocator>& other); - - /// Comparator class for two nodes of a tree (used for sorting and searching). - template<class StrictWeakOrdering> - class compare_nodes { - public: - compare_nodes(StrictWeakOrdering comp) : comp_(comp) {} - - bool operator()(const tree_node *a, const tree_node *b) const - { - return comp_(a->data, b->data); - } - private: - StrictWeakOrdering comp_; - }; - }; - -//template <class T, class tree_node_allocator> -//class iterator_base_less { + public: + typedef T value_type; + typedef T *pointer; + typedef T &reference; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + typedef std::bidirectional_iterator_tag iterator_category; + + iterator_base(); + iterator_base(tree_node *); + + T &operator*() const; + T *operator->() const; + + /// When called, the next increment/decrement skips children of this node. + void skip_children(); + void skip_children(bool skip); + /// Number of children of the node pointed to by the iterator. + unsigned int number_of_children() const; + + sibling_iterator begin() const; + sibling_iterator end() const; + + tree_node *node; + + protected: + bool skip_current_children_; + }; + + /// Depth-first iterator, first accessing the node, then its children. + class pre_order_iterator : public iterator_base { + public: + pre_order_iterator(); + pre_order_iterator(tree_node *); + pre_order_iterator(const iterator_base &); + pre_order_iterator(const sibling_iterator &); + + bool operator==(const pre_order_iterator &) const; + bool operator!=(const pre_order_iterator &) const; + pre_order_iterator &operator++(); + pre_order_iterator &operator--(); + pre_order_iterator operator++(int); + pre_order_iterator operator--(int); + pre_order_iterator &operator+=(unsigned int); + pre_order_iterator &operator-=(unsigned int); + + pre_order_iterator &next_skip_children(); + }; + + /// Depth-first iterator, first accessing the children, then the node itself. + class post_order_iterator : public iterator_base { + public: + post_order_iterator(); + post_order_iterator(tree_node *); + post_order_iterator(const iterator_base &); + post_order_iterator(const sibling_iterator &); + + bool operator==(const post_order_iterator &) const; + bool operator!=(const post_order_iterator &) const; + post_order_iterator &operator++(); + post_order_iterator &operator--(); + post_order_iterator operator++(int); + post_order_iterator operator--(int); + post_order_iterator &operator+=(unsigned int); + post_order_iterator &operator-=(unsigned int); + + /// Set iterator to the first child as deep as possible down the tree. + void descend_all(); + }; + + /// Breadth-first iterator, using a queue + class breadth_first_queued_iterator : public iterator_base { + public: + breadth_first_queued_iterator(); + breadth_first_queued_iterator(tree_node *); + breadth_first_queued_iterator(const iterator_base &); + + bool operator==(const breadth_first_queued_iterator &) const; + bool operator!=(const breadth_first_queued_iterator &) const; + breadth_first_queued_iterator &operator++(); + breadth_first_queued_iterator operator++(int); + breadth_first_queued_iterator &operator+=(unsigned int); + + private: + std::queue<tree_node *> traversal_queue; + }; + + /// The default iterator types throughout the tree class. + typedef pre_order_iterator iterator; + typedef breadth_first_queued_iterator breadth_first_iterator; + + /// Iterator which traverses only the nodes at a given depth from the root. + class fixed_depth_iterator : public iterator_base { + public: + fixed_depth_iterator(); + fixed_depth_iterator(tree_node *); + fixed_depth_iterator(const iterator_base &); + fixed_depth_iterator(const sibling_iterator &); + fixed_depth_iterator(const fixed_depth_iterator &); + + void swap(fixed_depth_iterator &, fixed_depth_iterator &); + fixed_depth_iterator &operator=(fixed_depth_iterator); + + bool operator==(const fixed_depth_iterator &) const; + bool operator!=(const fixed_depth_iterator &) const; + fixed_depth_iterator &operator++(); + fixed_depth_iterator &operator--(); + fixed_depth_iterator operator++(int); + fixed_depth_iterator operator--(int); + fixed_depth_iterator &operator+=(unsigned int); + fixed_depth_iterator &operator-=(unsigned int); + + tree_node *top_node; + }; + + /// Iterator which traverses only the nodes which are siblings of each other. + class sibling_iterator : public iterator_base { + public: + sibling_iterator(); + sibling_iterator(tree_node *); + sibling_iterator(const sibling_iterator &); + sibling_iterator(const iterator_base &); + + void swap(sibling_iterator &, sibling_iterator &); + sibling_iterator &operator=(sibling_iterator); + + bool operator==(const sibling_iterator &) const; + bool operator!=(const sibling_iterator &) const; + sibling_iterator &operator++(); + sibling_iterator &operator--(); + sibling_iterator operator++(int); + sibling_iterator operator--(int); + sibling_iterator &operator+=(unsigned int); + sibling_iterator &operator-=(unsigned int); + + tree_node *range_first() const; + tree_node *range_last() const; + tree_node *parent_; + + private: + void set_parent_(); + }; + + /// Iterator which traverses only the leaves. + class leaf_iterator : public iterator_base { + public: + leaf_iterator(); + leaf_iterator(tree_node *, tree_node *top = 0); + leaf_iterator(const sibling_iterator &); + leaf_iterator(const iterator_base &); + + bool operator==(const leaf_iterator &) const; + bool operator!=(const leaf_iterator &) const; + leaf_iterator &operator++(); + leaf_iterator &operator--(); + leaf_iterator operator++(int); + leaf_iterator operator--(int); + leaf_iterator &operator+=(unsigned int); + leaf_iterator &operator-=(unsigned int); + + private: + tree_node *top_node; + }; + + /// Return iterator to the beginning of the tree. + inline pre_order_iterator begin() const; + /// Return iterator to the end of the tree. + inline pre_order_iterator end() const; + /// Return post-order iterator to the beginning of the tree. + post_order_iterator begin_post() const; + /// Return post-order end iterator of the tree. + post_order_iterator end_post() const; + /// Return fixed-depth iterator to the first node at a given depth from the + /// given iterator. If 'walk_back=true', a depth=0 iterator will be taken from + /// the beginning of the sibling range, not the current node. + fixed_depth_iterator begin_fixed(const iterator_base &, unsigned int, + bool walk_back = true) const; + /// Return fixed-depth end iterator. + fixed_depth_iterator end_fixed(const iterator_base &, unsigned int) const; + /// Return breadth-first iterator to the first node at a given depth. + breadth_first_queued_iterator begin_breadth_first() const; + /// Return breadth-first end iterator. + breadth_first_queued_iterator end_breadth_first() const; + /// Return sibling iterator to the first child of given node. + static sibling_iterator begin(const iterator_base &); + /// Return sibling end iterator for children of given node. + static sibling_iterator end(const iterator_base &); + /// Return leaf iterator to the first leaf of the tree. + leaf_iterator begin_leaf() const; + /// Return leaf end iterator for entire tree. + leaf_iterator end_leaf() const; + /// Return leaf iterator to the first leaf of the subtree at the given node. + leaf_iterator begin_leaf(const iterator_base &top) const; + /// Return leaf end iterator for the subtree at the given node. + leaf_iterator end_leaf(const iterator_base &top) const; + + typedef std::vector<int> path_t; + /// Return a path (to be taken from the 'top' node) corresponding to a node in + /// the tree. The first integer in path_t is the number of steps you need to + /// go 'right' in the sibling chain (so 0 if we go straight to the children). + path_t path_from_iterator(const iterator_base &iter, + const iterator_base &top) const; + /// Return an iterator given a path from the 'top' node. + iterator iterator_from_path(const path_t &, const iterator_base &top) const; + + /// Return iterator to the parent of a node. Throws a `navigation_error` if + /// the node does not have a parent. + template <typename iter> static iter parent(iter); + /// Return iterator to the previous sibling of a node. + template <typename iter> static iter previous_sibling(iter); + /// Return iterator to the next sibling of a node. + template <typename iter> static iter next_sibling(iter); + /// Return iterator to the next node at a given depth. + template <typename iter> iter next_at_same_depth(iter) const; + + /// Erase all nodes of the tree. + void clear(); + /// Erase element at position pointed to by iterator, return incremented + /// iterator. + template <typename iter> iter erase(iter); + /// Erase all children of the node pointed to by iterator. + void erase_children(const iterator_base &); + /// Erase all siblings to the right of the iterator. + void erase_right_siblings(const iterator_base &); + /// Erase all siblings to the left of the iterator. + void erase_left_siblings(const iterator_base &); + + /// Insert empty node as last/first child of node pointed to by position. + template <typename iter> iter append_child(iter position); + template <typename iter> iter prepend_child(iter position); + /// Insert node as last/first child of node pointed to by position. + template <typename iter> iter append_child(iter position, const T &x); + template <typename iter> iter append_child(iter position, T &&x); + template <typename iter> iter prepend_child(iter position, const T &x); + template <typename iter> iter prepend_child(iter position, T &&x); + /// Append the node (plus its children) at other_position as last/first child + /// of position. + template <typename iter> + iter append_child(iter position, iter other_position); + template <typename iter> + iter prepend_child(iter position, iter other_position); + /// Append the nodes in the from-to range (plus their children) as last/first + /// children of position. + template <typename iter> + iter append_children(iter position, sibling_iterator from, + sibling_iterator to); + template <typename iter> + iter prepend_children(iter position, sibling_iterator from, + sibling_iterator to); + + /// Short-hand to insert topmost node in otherwise empty tree. + pre_order_iterator set_head(const T &x); + pre_order_iterator set_head(T &&x); + /// Insert node as previous sibling of node pointed to by position. + template <typename iter> iter insert(iter position, const T &x); + template <typename iter> iter insert(iter position, T &&x); + /// Specialisation of previous member. + sibling_iterator insert(sibling_iterator position, const T &x); + sibling_iterator insert(sibling_iterator position, T &&x); + /// Insert node (with children) pointed to by subtree as previous sibling of + /// node pointed to by position. Does not change the subtree itself (use + /// move_in or move_in_below for that). + template <typename iter> + iter insert_subtree(iter position, const iterator_base &subtree); + /// Insert node as next sibling of node pointed to by position. + template <typename iter> iter insert_after(iter position, const T &x); + template <typename iter> iter insert_after(iter position, T &&x); + /// Insert node (with children) pointed to by subtree as next sibling of node + /// pointed to by position. + template <typename iter> + iter insert_subtree_after(iter position, const iterator_base &subtree); + + /// Replace node at 'position' with other node (keeping same children); + /// 'position' becomes invalid. + template <typename iter> iter replace(iter position, const T &x); + /// Replace node at 'position' with subtree starting at 'from' (do not erase + /// subtree at 'from'); see above. + template <typename iter> + iter replace(iter position, const iterator_base &from); + /// Replace string of siblings (plus their children) with copy of a new string + /// (with children); see above + sibling_iterator replace(sibling_iterator orig_begin, + sibling_iterator orig_end, + sibling_iterator new_begin, + sibling_iterator new_end); + + /// Move all children of node at 'position' to be siblings, returns position. + template <typename iter> iter flatten(iter position); + /// Move nodes in range to be children of 'position'. + template <typename iter> + iter reparent(iter position, sibling_iterator begin, sibling_iterator end); + /// Move all child nodes of 'from' to be children of 'position'. + template <typename iter> iter reparent(iter position, iter from); + + /// Replace node with a new node, making the old node (plus subtree) a child + /// of the new node. + template <typename iter> iter wrap(iter position, const T &x); + /// Replace the range of sibling nodes (plus subtrees), making these children + /// of the new node. + template <typename iter> iter wrap(iter from, iter to, const T &x); + + /// Move 'source' node (plus its children) to become the next sibling of + /// 'target'. + template <typename iter> iter move_after(iter target, iter source); + /// Move 'source' node (plus its children) to become the previous sibling of + /// 'target'. + template <typename iter> iter move_before(iter target, iter source); + sibling_iterator move_before(sibling_iterator target, + sibling_iterator source); + /// Move 'source' node (plus its children) to become the node at 'target' + /// (erasing the node at 'target'). + template <typename iter> iter move_ontop(iter target, iter source); + + /// Extract the subtree starting at the indicated node, removing it from the + /// original tree. + tree move_out(iterator); + /// Inverse of take_out: inserts the given tree as previous sibling of + /// indicated node by a move operation, that is, the given tree becomes empty. + /// Returns iterator to the top node. + template <typename iter> iter move_in(iter, tree &); + /// As above, but now make the tree the last child of the indicated node. + template <typename iter> iter move_in_below(iter, tree &); + /// As above, but now make the tree the nth child of the indicated node (if + /// possible). + template <typename iter> iter move_in_as_nth_child(iter, size_t, tree &); + + /// Merge with other tree, creating new branches and leaves only if they are + /// not already present. + void merge(sibling_iterator, sibling_iterator, sibling_iterator, + sibling_iterator, bool duplicate_leaves = false); + /// As above, but using two trees with a single top node at the 'to' and + /// 'from' positions. + void merge(iterator to, iterator from, bool duplicate_leaves); + /// Sort (std::sort only moves values of nodes, this one moves children as + /// well). + void sort(sibling_iterator from, sibling_iterator to, bool deep = false); + template <class StrictWeakOrdering> + void sort(sibling_iterator from, sibling_iterator to, StrictWeakOrdering comp, + bool deep = false); + /// Compare two ranges of nodes (compares nodes as well as tree structure). + template <typename iter> + bool equal(const iter &one, const iter &two, const iter &three) const; + template <typename iter, class BinaryPredicate> + bool equal(const iter &one, const iter &two, const iter &three, + BinaryPredicate) const; + template <typename iter> + bool equal_subtree(const iter &one, const iter &two) const; + template <typename iter, class BinaryPredicate> + bool equal_subtree(const iter &one, const iter &two, BinaryPredicate) const; + /// Extract a new tree formed by the range of siblings plus all their + /// children. + tree subtree(sibling_iterator from, sibling_iterator to) const; + void subtree(tree &, sibling_iterator from, sibling_iterator to) const; + /// Exchange the node (plus subtree) with its sibling node (do nothing if no + /// sibling present). + void swap(sibling_iterator it); + /// Exchange two nodes (plus subtrees). The iterators will remain valid and + /// keep pointing to the same nodes, which now sit at different locations in + /// the tree. + void swap(iterator, iterator); + + /// Count the total number of nodes. + size_t size() const; + /// Count the total number of nodes below the indicated node (plus one). + size_t size(const iterator_base &) const; + /// Check if tree is empty. + bool empty() const; + /// Compute the depth to the root or to a fixed other iterator. + static int depth(const iterator_base &); + static int depth(const iterator_base &, const iterator_base &); + /// Compute the depth to the root, counting all levels for which predicate + /// returns true. + template <class Predicate> + static int depth(const iterator_base &, Predicate p); + /// Compute the depth distance between two nodes, counting all levels for + /// which predicate returns true. + template <class Predicate> + static int distance(const iterator_base &top, const iterator_base &bottom, + Predicate p); + /// Determine the maximal depth of the tree. An empty tree has max_depth=-1. + int max_depth() const; + /// Determine the maximal depth of the tree with top node at the given + /// position. + int max_depth(const iterator_base &) const; + /// Count the number of children of node at position. + static unsigned int number_of_children(const iterator_base &); + /// Count the number of siblings (left and right) of node at iterator. Total + /// nodes at this level is +1. + unsigned int number_of_siblings(const iterator_base &) const; + /// Determine whether node at position is in the subtrees with indicated top + /// node. + bool is_in_subtree(const iterator_base &position, + const iterator_base &top) const; + /// Determine whether node at position is in the subtrees with root in the + /// range. + bool is_in_subtree(const iterator_base &position, const iterator_base &begin, + const iterator_base &end) const; + /// Determine whether the iterator is an 'end' iterator and thus not actually + /// pointing to a node. + bool is_valid(const iterator_base &) const; + /// Determine whether the iterator is one of the 'head' nodes at the top + /// level, i.e. has no parent. + static bool is_head(const iterator_base &); + /// Find the lowest common ancestor of two nodes, that is, the deepest node + /// such that both nodes are descendants of it. + iterator lowest_common_ancestor(const iterator_base &, + const iterator_base &) const; + + /// Determine the index of a node in the range of siblings to which it + /// belongs. + unsigned int index(sibling_iterator it) const; + /// Inverse of 'index': return the n-th child of the node at position. + static sibling_iterator child(const iterator_base &position, unsigned int); + /// Return iterator to the sibling indicated by index + sibling_iterator sibling(const iterator_base &position, unsigned int) const; + + /// For debugging only: verify internal consistency by inspecting all pointers + /// in the tree (which will also trigger a valgrind error in case something + /// got corrupted). + void debug_verify_consistency() const; + + /// Comparator class for iterators (compares pointer values; why doesn't this + /// work automatically?) + class iterator_base_less { + public: + bool operator()( + const typename tree<T, tree_node_allocator>::iterator_base &one, + const typename tree<T, tree_node_allocator>::iterator_base &two) const { + return one.node < two.node; + } + }; + tree_node *head, *feet; // head/feet are always dummy; if an iterator points + // to them it is invalid +private: + tree_node_allocator alloc_; + void head_initialise_(); + void copy_(const tree<T, tree_node_allocator> &other); + + /// Comparator class for two nodes of a tree (used for sorting and searching). + template <class StrictWeakOrdering> class compare_nodes { + public: + compare_nodes(StrictWeakOrdering comp) : comp_(comp) {} + + bool operator()(const tree_node *a, const tree_node *b) const { + return comp_(a->data, b->data); + } + + private: + StrictWeakOrdering comp_; + }; +}; + +// template <class T, class tree_node_allocator> +// class iterator_base_less { // public: -// bool operator()(const typename tree<T, tree_node_allocator>::iterator_base& one, -// const typename tree<T, tree_node_allocator>::iterator_base& two) const +// bool operator()(const typename tree<T, +//tree_node_allocator>::iterator_base& one, const typename tree<T, +//tree_node_allocator>::iterator_base& two) const // { -// txtout << "operatorclass<" << one.node < two.node << std::endl; -// return one.node < two.node; +// txtout << "operatorclass<" << one.node < two.node << +//std::endl; return one.node < two.node; // } -//}; +// }; // template <class T, class tree_node_allocator> // bool operator<(const typename tree<T, tree_node_allocator>::iterator& one, -// const typename tree<T, tree_node_allocator>::iterator& two) +// const typename tree<T, +// tree_node_allocator>::iterator& two) // { // txtout << "operator< " << one.node < two.node << std::endl; // if(one.node < two.node) return true; // return false; // } -// +// // template <class T, class tree_node_allocator> // bool operator==(const typename tree<T, tree_node_allocator>::iterator& one, -// const typename tree<T, tree_node_allocator>::iterator& two) +// const typename tree<T, +// tree_node_allocator>::iterator& two) // { // txtout << "operator== " << one.node == two.node << std::endl; // if(one.node == two.node) return true; // return false; // } -// +// // template <class T, class tree_node_allocator> -// bool operator>(const typename tree<T, tree_node_allocator>::iterator_base& one, -// const typename tree<T, tree_node_allocator>::iterator_base& two) +// bool operator>(const typename tree<T, tree_node_allocator>::iterator_base& +// one, const typename tree<T, tree_node_allocator>::iterator_base& two) // { // txtout << "operator> " << one.node < two.node << std::endl; // if(one.node > two.node) return true; // return false; // } +// Tree +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree() { + head_initialise_(); +} -// Tree +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree(const T &x) { + head_initialise_(); + set_head(x); +} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree(tree<T, tree_node_allocator> &&x) { + head_initialise_(); + if (x.head->next_sibling != x.feet) { // move tree if non-empty only + head->next_sibling = x.head->next_sibling; + feet->prev_sibling = x.feet->prev_sibling; + x.head->next_sibling->prev_sibling = head; + x.feet->prev_sibling->next_sibling = feet; + x.head->next_sibling = x.feet; + x.feet->prev_sibling = x.head; + } +} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree(const iterator_base &other) { + head_initialise_(); + set_head((*other)); + replace(begin(), other); +} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::~tree() { + clear(); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, head); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, feet); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, head, 1); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, feet, 1); +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::head_initialise_() { + head = std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + feet = std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, head, + tree_node_<T>()); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, feet, + tree_node_<T>()); + + head->parent = 0; + head->first_child = 0; + head->last_child = 0; + head->prev_sibling = 0; // head; + head->next_sibling = feet; // head; + + feet->parent = 0; + feet->first_child = 0; + feet->last_child = 0; + feet->prev_sibling = head; + feet->next_sibling = 0; +} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator> &tree<T, tree_node_allocator>::operator=( + const tree<T, tree_node_allocator> &other) { + if (this != &other) + copy_(other); + return *this; +} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator> & +tree<T, tree_node_allocator>::operator=(tree<T, tree_node_allocator> &&x) { + if (this != &x) { + clear(); // clear any existing data. + + head->next_sibling = x.head->next_sibling; + feet->prev_sibling = x.feet->prev_sibling; + x.head->next_sibling->prev_sibling = head; + x.feet->prev_sibling->next_sibling = feet; + x.head->next_sibling = x.feet; + x.feet->prev_sibling = x.head; + } + return *this; +} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::tree(const tree<T, tree_node_allocator> &other) { + head_initialise_(); + copy_(other); +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::copy_( + const tree<T, tree_node_allocator> &other) { + clear(); + pre_order_iterator it = other.begin(), to = begin(); + while (it != other.end()) { + to = insert(to, (*it)); + it.skip_children(); + ++it; + } + to = begin(); + it = other.begin(); + while (it != other.end()) { + to = replace(to, it); + to.skip_children(); + it.skip_children(); + ++to; + ++it; + } +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::clear() { + if (head) + while (head->next_sibling != feet) + erase(pre_order_iterator(head->next_sibling)); +} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::tree() - { - head_initialise_(); - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::tree(const T& x) - { - head_initialise_(); - set_head(x); - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::tree(tree<T, tree_node_allocator>&& x) - { - head_initialise_(); - if(x.head->next_sibling!=x.feet) { // move tree if non-empty only - head->next_sibling=x.head->next_sibling; - feet->prev_sibling=x.feet->prev_sibling; - x.head->next_sibling->prev_sibling=head; - x.feet->prev_sibling->next_sibling=feet; - x.head->next_sibling=x.feet; - x.feet->prev_sibling=x.head; - } - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::tree(const iterator_base& other) - { - head_initialise_(); - set_head((*other)); - replace(begin(), other); - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::~tree() - { - clear(); - std::allocator_traits<decltype(alloc_)>::destroy(alloc_, head); - std::allocator_traits<decltype(alloc_)>::destroy(alloc_, feet); - std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, head, 1); - std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, feet, 1); - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::head_initialise_() - { - head = std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - feet = std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, head, tree_node_<T>()); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, feet, tree_node_<T>()); - - head->parent=0; - head->first_child=0; - head->last_child=0; - head->prev_sibling=0; //head; - head->next_sibling=feet; //head; - - feet->parent=0; - feet->first_child=0; - feet->last_child=0; - feet->prev_sibling=head; - feet->next_sibling=0; - } - -template <class T, class tree_node_allocator> -tree<T,tree_node_allocator>& tree<T, tree_node_allocator>::operator=(const tree<T, tree_node_allocator>& other) - { - if(this != &other) - copy_(other); - return *this; - } - -template <class T, class tree_node_allocator> -tree<T,tree_node_allocator>& tree<T, tree_node_allocator>::operator=(tree<T, tree_node_allocator>&& x) - { - if(this != &x) { - clear(); // clear any existing data. - - head->next_sibling=x.head->next_sibling; - feet->prev_sibling=x.feet->prev_sibling; - x.head->next_sibling->prev_sibling=head; - x.feet->prev_sibling->next_sibling=feet; - x.head->next_sibling=x.feet; - x.feet->prev_sibling=x.head; - } - return *this; - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::tree(const tree<T, tree_node_allocator>& other) - { - head_initialise_(); - copy_(other); - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::copy_(const tree<T, tree_node_allocator>& other) - { - clear(); - pre_order_iterator it=other.begin(), to=begin(); - while(it!=other.end()) { - to=insert(to, (*it)); - it.skip_children(); - ++it; - } - to=begin(); - it=other.begin(); - while(it!=other.end()) { - to=replace(to, it); - to.skip_children(); - it.skip_children(); - ++to; - ++it; - } - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::clear() - { - if(head) - while(head->next_sibling!=feet) - erase(pre_order_iterator(head->next_sibling)); - } - -template<class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::erase_children(const iterator_base& it) - { -// std::cout << "erase_children " << it.node << std::endl; - if(it.node==0) return; - - tree_node *cur=it.node->first_child; - tree_node *prev=0; - - while(cur!=0) { - prev=cur; - cur=cur->next_sibling; - erase_children(pre_order_iterator(prev)); - std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); - std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); - } - it.node->first_child=0; - it.node->last_child=0; -// std::cout << "exit" << std::endl; - } - -template<class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::erase_right_siblings(const iterator_base& it) - { - if(it.node==0) return; - - tree_node *cur=it.node->next_sibling; - tree_node *prev=0; - - while(cur!=0) { - prev=cur; - cur=cur->next_sibling; - erase_children(pre_order_iterator(prev)); - std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); - std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); - } - it.node->next_sibling=0; - if(it.node->parent!=0) - it.node->parent->last_child=it.node; - } - -template<class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::erase_left_siblings(const iterator_base& it) - { - if(it.node==0) return; - - tree_node *cur=it.node->prev_sibling; - tree_node *prev=0; - - while(cur!=0) { - prev=cur; - cur=cur->prev_sibling; - erase_children(pre_order_iterator(prev)); - std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); - std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); - } - it.node->prev_sibling=0; - if(it.node->parent!=0) - it.node->parent->first_child=it.node; - } - -template<class T, class tree_node_allocator> -template<class iter> -iter tree<T, tree_node_allocator>::erase(iter it) - { - tree_node *cur=it.node; - assert(cur!=head); - iter ret=it; - ret.skip_children(); - ++ret; - erase_children(it); - if(cur->prev_sibling==0) { - cur->parent->first_child=cur->next_sibling; - } - else { - cur->prev_sibling->next_sibling=cur->next_sibling; - } - if(cur->next_sibling==0) { - cur->parent->last_child=cur->prev_sibling; - } - else { - cur->next_sibling->prev_sibling=cur->prev_sibling; - } - - std::allocator_traits<decltype(alloc_)>::destroy(alloc_, cur); - std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, cur, 1); - return ret; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::begin() const - { - return pre_order_iterator(head->next_sibling); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::end() const - { - return pre_order_iterator(feet); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::breadth_first_queued_iterator tree<T, tree_node_allocator>::begin_breadth_first() const - { - return breadth_first_queued_iterator(head->next_sibling); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::breadth_first_queued_iterator tree<T, tree_node_allocator>::end_breadth_first() const - { - return breadth_first_queued_iterator(); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::begin_post() const - { - tree_node *tmp=head->next_sibling; - if(tmp!=feet) { - while(tmp->first_child) - tmp=tmp->first_child; - } - return post_order_iterator(tmp); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::end_post() const - { - return post_order_iterator(feet); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::begin_fixed(const iterator_base& pos, unsigned int dp, bool walk_back) const - { - typename tree<T, tree_node_allocator>::fixed_depth_iterator ret; - ret.top_node=pos.node; - - tree_node *tmp=pos.node; - unsigned int curdepth=0; - while(curdepth<dp) { // go down one level - while(tmp->first_child==0) { - if(tmp->next_sibling==0) { - // try to walk up and then right again - do { - if(tmp==ret.top_node) - throw std::range_error("tree: begin_fixed out of range"); - tmp=tmp->parent; - if(tmp==0) - throw std::range_error("tree: begin_fixed out of range"); - --curdepth; - } while(tmp->next_sibling==0); - } - tmp=tmp->next_sibling; - } - tmp=tmp->first_child; - ++curdepth; - } - - // Now walk back to the first sibling in this range. - if(walk_back) - while(tmp->prev_sibling!=0) - tmp=tmp->prev_sibling; - - ret.node=tmp; - return ret; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::end_fixed(const iterator_base& pos, unsigned int dp) const - { - assert(1==0); // FIXME: not correct yet: use is_valid() as a temporary workaround - tree_node *tmp=pos.node; - unsigned int curdepth=1; - while(curdepth<dp) { // go down one level - while(tmp->first_child==0) { - tmp=tmp->next_sibling; - if(tmp==0) - throw std::range_error("tree: end_fixed out of range"); - } - tmp=tmp->first_child; - ++curdepth; - } - return tmp; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::begin(const iterator_base& pos) - { - assert(pos.node!=0); - if(pos.node->first_child==0) { - return end(pos); - } - return pos.node->first_child; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::end(const iterator_base& pos) - { - sibling_iterator ret(0); - ret.parent_=pos.node; - return ret; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::begin_leaf() const - { - tree_node *tmp=head->next_sibling; - if(tmp!=feet) { - while(tmp->first_child) - tmp=tmp->first_child; +void tree<T, tree_node_allocator>::erase_children(const iterator_base &it) { + // std::cout << "erase_children " << it.node << std::endl; + if (it.node == 0) + return; + + tree_node *cur = it.node->first_child; + tree_node *prev = 0; + + while (cur != 0) { + prev = cur; + cur = cur->next_sibling; + erase_children(pre_order_iterator(prev)); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); + } + it.node->first_child = 0; + it.node->last_child = 0; + // std::cout << "exit" << std::endl; +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::erase_right_siblings( + const iterator_base &it) { + if (it.node == 0) + return; + + tree_node *cur = it.node->next_sibling; + tree_node *prev = 0; + + while (cur != 0) { + prev = cur; + cur = cur->next_sibling; + erase_children(pre_order_iterator(prev)); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); + } + it.node->next_sibling = 0; + if (it.node->parent != 0) + it.node->parent->last_child = it.node; +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::erase_left_siblings( + const iterator_base &it) { + if (it.node == 0) + return; + + tree_node *cur = it.node->prev_sibling; + tree_node *prev = 0; + + while (cur != 0) { + prev = cur; + cur = cur->prev_sibling; + erase_children(pre_order_iterator(prev)); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, prev); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, prev, 1); + } + it.node->prev_sibling = 0; + if (it.node->parent != 0) + it.node->parent->first_child = it.node; +} + +template <class T, class tree_node_allocator> +template <class iter> +iter tree<T, tree_node_allocator>::erase(iter it) { + tree_node *cur = it.node; + assert(cur != head); + iter ret = it; + ret.skip_children(); + ++ret; + erase_children(it); + if (cur->prev_sibling == 0) { + cur->parent->first_child = cur->next_sibling; + } else { + cur->prev_sibling->next_sibling = cur->next_sibling; + } + if (cur->next_sibling == 0) { + cur->parent->last_child = cur->prev_sibling; + } else { + cur->next_sibling->prev_sibling = cur->prev_sibling; + } + + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, cur); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, cur, 1); + return ret; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator +tree<T, tree_node_allocator>::begin() const { + return pre_order_iterator(head->next_sibling); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator +tree<T, tree_node_allocator>::end() const { + return pre_order_iterator(feet); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator +tree<T, tree_node_allocator>::begin_breadth_first() const { + return breadth_first_queued_iterator(head->next_sibling); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator +tree<T, tree_node_allocator>::end_breadth_first() const { + return breadth_first_queued_iterator(); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator +tree<T, tree_node_allocator>::begin_post() const { + tree_node *tmp = head->next_sibling; + if (tmp != feet) { + while (tmp->first_child) + tmp = tmp->first_child; + } + return post_order_iterator(tmp); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator +tree<T, tree_node_allocator>::end_post() const { + return post_order_iterator(feet); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator +tree<T, tree_node_allocator>::begin_fixed(const iterator_base &pos, + unsigned int dp, + bool walk_back) const { + typename tree<T, tree_node_allocator>::fixed_depth_iterator ret; + ret.top_node = pos.node; + + tree_node *tmp = pos.node; + unsigned int curdepth = 0; + while (curdepth < dp) { // go down one level + while (tmp->first_child == 0) { + if (tmp->next_sibling == 0) { + // try to walk up and then right again + do { + if (tmp == ret.top_node) + throw std::range_error("tree: begin_fixed out of range"); + tmp = tmp->parent; + if (tmp == 0) + throw std::range_error("tree: begin_fixed out of range"); + --curdepth; + } while (tmp->next_sibling == 0); } - return leaf_iterator(tmp); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::end_leaf() const - { - return leaf_iterator(feet); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::path_t tree<T, tree_node_allocator>::path_from_iterator(const iterator_base& iter, const iterator_base& top) const - { - path_t path; - tree_node *walk=iter.node; - - do { - if(path.size()>0) - walk=walk->parent; - int num=0; - while(walk!=top.node && walk->prev_sibling!=0 && walk->prev_sibling!=head) { - ++num; - walk=walk->prev_sibling; - } - path.push_back(num); - } - while(walk->parent!=0 && walk!=top.node); - - std::reverse(path.begin(), path.end()); - return path; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::iterator tree<T, tree_node_allocator>::iterator_from_path(const path_t& path, const iterator_base& top) const - { - iterator it=top; - tree_node *walk=it.node; - - for(size_t step=0; step<path.size(); ++step) { - if(step>0) - walk=walk->first_child; - if(walk==0) - throw std::range_error("tree::iterator_from_path: no more nodes at step "+std::to_string(step)); - - for(int i=0; i<path[step]; ++i) { - walk=walk->next_sibling; - if(walk==0) - throw std::range_error("tree::iterator_from_path: out of siblings at step "+std::to_string(step)); - } - } - it.node=walk; - return it; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::begin_leaf(const iterator_base& top) const - { - tree_node *tmp=top.node; - while(tmp->first_child) - tmp=tmp->first_child; - return leaf_iterator(tmp, top.node); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::end_leaf(const iterator_base& top) const - { - return leaf_iterator(top.node, top.node); - } + tmp = tmp->next_sibling; + } + tmp = tmp->first_child; + ++curdepth; + } + + // Now walk back to the first sibling in this range. + if (walk_back) + while (tmp->prev_sibling != 0) + tmp = tmp->prev_sibling; + + ret.node = tmp; + return ret; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator +tree<T, tree_node_allocator>::end_fixed(const iterator_base &pos, + unsigned int dp) const { + assert(1 == + 0); // FIXME: not correct yet: use is_valid() as a temporary workaround + tree_node *tmp = pos.node; + unsigned int curdepth = 1; + while (curdepth < dp) { // go down one level + while (tmp->first_child == 0) { + tmp = tmp->next_sibling; + if (tmp == 0) + throw std::range_error("tree: end_fixed out of range"); + } + tmp = tmp->first_child; + ++curdepth; + } + return tmp; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::begin(const iterator_base &pos) { + assert(pos.node != 0); + if (pos.node->first_child == 0) { + return end(pos); + } + return pos.node->first_child; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::end(const iterator_base &pos) { + sibling_iterator ret(0); + ret.parent_ = pos.node; + return ret; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator +tree<T, tree_node_allocator>::begin_leaf() const { + tree_node *tmp = head->next_sibling; + if (tmp != feet) { + while (tmp->first_child) + tmp = tmp->first_child; + } + return leaf_iterator(tmp); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator +tree<T, tree_node_allocator>::end_leaf() const { + return leaf_iterator(feet); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::path_t +tree<T, tree_node_allocator>::path_from_iterator( + const iterator_base &iter, const iterator_base &top) const { + path_t path; + tree_node *walk = iter.node; + + do { + if (path.size() > 0) + walk = walk->parent; + int num = 0; + while (walk != top.node && walk->prev_sibling != 0 && + walk->prev_sibling != head) { + ++num; + walk = walk->prev_sibling; + } + path.push_back(num); + } while (walk->parent != 0 && walk != top.node); + + std::reverse(path.begin(), path.end()); + return path; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::iterator +tree<T, tree_node_allocator>::iterator_from_path( + const path_t &path, const iterator_base &top) const { + iterator it = top; + tree_node *walk = it.node; + + for (size_t step = 0; step < path.size(); ++step) { + if (step > 0) + walk = walk->first_child; + if (walk == 0) + throw std::range_error( + "tree::iterator_from_path: no more nodes at step " + + std::to_string(step)); + + for (int i = 0; i < path[step]; ++i) { + walk = walk->next_sibling; + if (walk == 0) + throw std::range_error( + "tree::iterator_from_path: out of siblings at step " + + std::to_string(step)); + } + } + it.node = walk; + return it; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator +tree<T, tree_node_allocator>::begin_leaf(const iterator_base &top) const { + tree_node *tmp = top.node; + while (tmp->first_child) + tmp = tmp->first_child; + return leaf_iterator(tmp, top.node); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator +tree<T, tree_node_allocator>::end_leaf(const iterator_base &top) const { + return leaf_iterator(top.node, top.node); +} template <class T, class tree_node_allocator> template <typename iter> -iter tree<T, tree_node_allocator>::parent(iter position) - { - if(position.node==0) - throw navigation_error("tree: attempt to navigate from null iterator."); - - if(position.node->parent==0) - throw navigation_error("tree: attempt to navigate up past head node."); +iter tree<T, tree_node_allocator>::parent(iter position) { + if (position.node == 0) + throw navigation_error("tree: attempt to navigate from null iterator."); - return iter(position.node->parent); - } + if (position.node->parent == 0) + throw navigation_error("tree: attempt to navigate up past head node."); + + return iter(position.node->parent); +} template <class T, class tree_node_allocator> template <typename iter> -iter tree<T, tree_node_allocator>::previous_sibling(iter position) - { - assert(position.node!=0); - iter ret(position); - ret.node=position.node->prev_sibling; - return ret; - } +iter tree<T, tree_node_allocator>::previous_sibling(iter position) { + assert(position.node != 0); + iter ret(position); + ret.node = position.node->prev_sibling; + return ret; +} template <class T, class tree_node_allocator> template <typename iter> -iter tree<T, tree_node_allocator>::next_sibling(iter position) - { - assert(position.node!=0); - iter ret(position); - ret.node=position.node->next_sibling; - return ret; - } +iter tree<T, tree_node_allocator>::next_sibling(iter position) { + assert(position.node != 0); + iter ret(position); + ret.node = position.node->next_sibling; + return ret; +} template <class T, class tree_node_allocator> template <typename iter> -iter tree<T, tree_node_allocator>::next_at_same_depth(iter position) const - { - // We make use of a temporary fixed_depth iterator to implement this. - - typename tree<T, tree_node_allocator>::fixed_depth_iterator tmp(position.node); - - ++tmp; - return iter(tmp); - - // assert(position.node!=0); - // iter ret(position); - // - // if(position.node->next_sibling) { - // ret.node=position.node->next_sibling; - // } - // else { - // int relative_depth=0; - // upper: - // do { - // ret.node=ret.node->parent; - // if(ret.node==0) return ret; - // --relative_depth; - // } while(ret.node->next_sibling==0); - // lower: - // ret.node=ret.node->next_sibling; - // while(ret.node->first_child==0) { - // if(ret.node->next_sibling==0) - // goto upper; - // ret.node=ret.node->next_sibling; - // if(ret.node==0) return ret; - // } - // while(relative_depth<0 && ret.node->first_child!=0) { - // ret.node=ret.node->first_child; - // ++relative_depth; - // } - // if(relative_depth<0) { - // if(ret.node->next_sibling==0) goto upper; - // else goto lower; - // } - // } - // return ret; - } +iter tree<T, tree_node_allocator>::next_at_same_depth(iter position) const { + // We make use of a temporary fixed_depth iterator to implement this. + + typename tree<T, tree_node_allocator>::fixed_depth_iterator tmp( + position.node); + + ++tmp; + return iter(tmp); + + // assert(position.node!=0); + // iter ret(position); + // + // if(position.node->next_sibling) { + // ret.node=position.node->next_sibling; + // } + // else { + // int relative_depth=0; + // upper: + // do { + // ret.node=ret.node->parent; + // if(ret.node==0) return ret; + // --relative_depth; + // } while(ret.node->next_sibling==0); + // lower: + // ret.node=ret.node->next_sibling; + // while(ret.node->first_child==0) { + // if(ret.node->next_sibling==0) + // goto upper; + // ret.node=ret.node->next_sibling; + // if(ret.node==0) return ret; + // } + // while(relative_depth<0 && ret.node->first_child!=0) { + // ret.node=ret.node->first_child; + // ++relative_depth; + // } + // if(relative_depth<0) { + // if(ret.node->next_sibling==0) goto upper; + // else goto lower; + // } + // } + // return ret; +} template <class T, class tree_node_allocator> template <typename iter> -iter tree<T, tree_node_allocator>::append_child(iter position) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); - - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, tree_node_<T>()); - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node; - if(position.node->last_child!=0) { - position.node->last_child->next_sibling=tmp; - } - else { - position.node->first_child=tmp; - } - tmp->prev_sibling=position.node->last_child; - position.node->last_child=tmp; - tmp->next_sibling=0; - return tmp; - } +iter tree<T, tree_node_allocator>::append_child(iter position) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); + + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, + tree_node_<T>()); + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node; + if (position.node->last_child != 0) { + position.node->last_child->next_sibling = tmp; + } else { + position.node->first_child = tmp; + } + tmp->prev_sibling = position.node->last_child; + position.node->last_child = tmp; + tmp->next_sibling = 0; + return tmp; +} template <class T, class tree_node_allocator> template <typename iter> -iter tree<T, tree_node_allocator>::prepend_child(iter position) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); - - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, tree_node_<T>()); - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node; - if(position.node->first_child!=0) { - position.node->first_child->prev_sibling=tmp; - } - else { - position.node->last_child=tmp; - } - tmp->next_sibling=position.node->first_child; - position.node->prev_child=tmp; - tmp->prev_sibling=0; - return tmp; - } +iter tree<T, tree_node_allocator>::prepend_child(iter position) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); + + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, + tree_node_<T>()); + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node; + if (position.node->first_child != 0) { + position.node->first_child->prev_sibling = tmp; + } else { + position.node->last_child = tmp; + } + tmp->next_sibling = position.node->first_child; + position.node->prev_child = tmp; + tmp->prev_sibling = 0; + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::append_child(iter position, const T& x) - { - // If your program fails here you probably used 'append_child' to add the top - // node to an empty tree. From version 1.45 the top element should be added - // using 'insert'. See the documentation for further information, and sorry about - // the API change. - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); - - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node; - if(position.node->last_child!=0) { - position.node->last_child->next_sibling=tmp; - } - else { - position.node->first_child=tmp; - } - tmp->prev_sibling=position.node->last_child; - position.node->last_child=tmp; - tmp->next_sibling=0; - return tmp; - } +iter tree<T, tree_node_allocator>::append_child(iter position, const T &x) { + // If your program fails here you probably used 'append_child' to add the top + // node to an empty tree. From version 1.45 the top element should be added + // using 'insert'. See the documentation for further information, and sorry + // about the API change. + assert(position.node != head); + assert(position.node != feet); + assert(position.node); + + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node; + if (position.node->last_child != 0) { + position.node->last_child->next_sibling = tmp; + } else { + position.node->first_child = tmp; + } + tmp->prev_sibling = position.node->last_child; + position.node->last_child = tmp; + tmp->next_sibling = 0; + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::append_child(iter position, T&& x) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); - - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); // Here is where the move semantics kick in - std::swap(tmp->data, x); - - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node; - if(position.node->last_child!=0) { - position.node->last_child->next_sibling=tmp; - } - else { - position.node->first_child=tmp; - } - tmp->prev_sibling=position.node->last_child; - position.node->last_child=tmp; - tmp->next_sibling=0; - return tmp; - } +iter tree<T, tree_node_allocator>::append_child(iter position, T &&x) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); + + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct( + alloc_, tmp); // Here is where the move semantics kick in + std::swap(tmp->data, x); + + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node; + if (position.node->last_child != 0) { + position.node->last_child->next_sibling = tmp; + } else { + position.node->first_child = tmp; + } + tmp->prev_sibling = position.node->last_child; + position.node->last_child = tmp; + tmp->next_sibling = 0; + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::prepend_child(iter position, const T& x) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); - - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node; - if(position.node->first_child!=0) { - position.node->first_child->prev_sibling=tmp; - } - else { - position.node->last_child=tmp; - } - tmp->next_sibling=position.node->first_child; - position.node->first_child=tmp; - tmp->prev_sibling=0; - return tmp; - } +iter tree<T, tree_node_allocator>::prepend_child(iter position, const T &x) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); + + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node; + if (position.node->first_child != 0) { + position.node->first_child->prev_sibling = tmp; + } else { + position.node->last_child = tmp; + } + tmp->next_sibling = position.node->first_child; + position.node->first_child = tmp; + tmp->prev_sibling = 0; + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::prepend_child(iter position, T&& x) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); - - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); - std::swap(tmp->data, x); - - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node; - if(position.node->first_child!=0) { - position.node->first_child->prev_sibling=tmp; - } - else { - position.node->last_child=tmp; - } - tmp->next_sibling=position.node->first_child; - position.node->first_child=tmp; - tmp->prev_sibling=0; - return tmp; - } +iter tree<T, tree_node_allocator>::prepend_child(iter position, T &&x) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); + + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); + std::swap(tmp->data, x); + + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node; + if (position.node->first_child != 0) { + position.node->first_child->prev_sibling = tmp; + } else { + position.node->last_child = tmp; + } + tmp->next_sibling = position.node->first_child; + position.node->first_child = tmp; + tmp->prev_sibling = 0; + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::append_child(iter position, iter other) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); +iter tree<T, tree_node_allocator>::append_child(iter position, iter other) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); - sibling_iterator aargh=append_child(position, value_type()); - return replace(aargh, other); - } + sibling_iterator aargh = append_child(position, value_type()); + return replace(aargh, other); +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::prepend_child(iter position, iter other) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); +iter tree<T, tree_node_allocator>::prepend_child(iter position, iter other) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); - sibling_iterator aargh=prepend_child(position, value_type()); - return replace(aargh, other); - } + sibling_iterator aargh = prepend_child(position, value_type()); + return replace(aargh, other); +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::append_children(iter position, sibling_iterator from, sibling_iterator to) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); - - iter ret=from; - - while(from!=to) { - insert_subtree(position.end(), from); - ++from; - } - return ret; - } +iter tree<T, tree_node_allocator>::append_children(iter position, + sibling_iterator from, + sibling_iterator to) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); + + iter ret = from; + + while (from != to) { + insert_subtree(position.end(), from); + ++from; + } + return ret; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::prepend_children(iter position, sibling_iterator from, sibling_iterator to) - { - assert(position.node!=head); - assert(position.node!=feet); - assert(position.node); - - if(from==to) return from; // should return end of tree? - - iter ret; - do { - --to; - ret=insert_subtree(position.begin(), to); - } - while(to!=from); - - return ret; - } +iter tree<T, tree_node_allocator>::prepend_children(iter position, + sibling_iterator from, + sibling_iterator to) { + assert(position.node != head); + assert(position.node != feet); + assert(position.node); + + if (from == to) + return from; // should return end of tree? + + iter ret; + do { + --to; + ret = insert_subtree(position.begin(), to); + } while (to != from); + + return ret; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::set_head(const T& x) - { - assert(head->next_sibling==feet); - return insert(iterator(feet), x); - } +typename tree<T, tree_node_allocator>::pre_order_iterator +tree<T, tree_node_allocator>::set_head(const T &x) { + assert(head->next_sibling == feet); + return insert(iterator(feet), x); +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::set_head(T&& x) - { - assert(head->next_sibling==feet); - return insert(iterator(feet), x); - } +typename tree<T, tree_node_allocator>::pre_order_iterator +tree<T, tree_node_allocator>::set_head(T &&x) { + assert(head->next_sibling == feet); + return insert(iterator(feet), x); +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::insert(iter position, const T& x) - { - if(position.node==0) { - position.node=feet; // Backward compatibility: when calling insert on a null node, - // insert before the feet. - } - assert(position.node!=head); // Cannot insert before head. - - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node->parent; - tmp->next_sibling=position.node; - tmp->prev_sibling=position.node->prev_sibling; - position.node->prev_sibling=tmp; - - if(tmp->prev_sibling==0) { - if(tmp->parent) // when inserting nodes at the head, there is no parent - tmp->parent->first_child=tmp; - } - else - tmp->prev_sibling->next_sibling=tmp; - return tmp; - } +iter tree<T, tree_node_allocator>::insert(iter position, const T &x) { + if (position.node == 0) { + position.node = feet; // Backward compatibility: when calling insert on a + // null node, insert before the feet. + } + assert(position.node != head); // Cannot insert before head. + + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node->parent; + tmp->next_sibling = position.node; + tmp->prev_sibling = position.node->prev_sibling; + position.node->prev_sibling = tmp; + + if (tmp->prev_sibling == 0) { + if (tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->first_child = tmp; + } else + tmp->prev_sibling->next_sibling = tmp; + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::insert(iter position, T&& x) - { - if(position.node==0) { - position.node=feet; // Backward compatibility: when calling insert on a null node, - // insert before the feet. - } - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); - std::swap(tmp->data, x); // Move semantics - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node->parent; - tmp->next_sibling=position.node; - tmp->prev_sibling=position.node->prev_sibling; - position.node->prev_sibling=tmp; - - if(tmp->prev_sibling==0) { - if(tmp->parent) // when inserting nodes at the head, there is no parent - tmp->parent->first_child=tmp; - } - else - tmp->prev_sibling->next_sibling=tmp; - return tmp; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::insert(sibling_iterator position, const T& x) - { - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); - tmp->first_child=0; - tmp->last_child=0; - - tmp->next_sibling=position.node; - if(position.node==0) { // iterator points to end of a subtree - tmp->parent=position.parent_; - tmp->prev_sibling=position.range_last(); - tmp->parent->last_child=tmp; - } - else { - tmp->parent=position.node->parent; - tmp->prev_sibling=position.node->prev_sibling; - position.node->prev_sibling=tmp; - } - - if(tmp->prev_sibling==0) { - if(tmp->parent) // when inserting nodes at the head, there is no parent - tmp->parent->first_child=tmp; - } - else - tmp->prev_sibling->next_sibling=tmp; - return tmp; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::insert(sibling_iterator position, T&& x) - { - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); - std::swap(tmp->data, x); // Move semantics - - tmp->first_child=0; - tmp->last_child=0; - - tmp->next_sibling=position.node; - if(position.node==0) { // iterator points to end of a subtree - tmp->parent=position.parent_; - tmp->prev_sibling=position.range_last(); - tmp->parent->last_child=tmp; - } - else { - tmp->parent=position.node->parent; - tmp->prev_sibling=position.node->prev_sibling; - position.node->prev_sibling=tmp; - } - - if(tmp->prev_sibling==0) { - if(tmp->parent) // when inserting nodes at the head, there is no parent - tmp->parent->first_child=tmp; - } - else - tmp->prev_sibling->next_sibling=tmp; - return tmp; - } +iter tree<T, tree_node_allocator>::insert(iter position, T &&x) { + if (position.node == 0) { + position.node = feet; // Backward compatibility: when calling insert on a + // null node, insert before the feet. + } + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); + std::swap(tmp->data, x); // Move semantics + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node->parent; + tmp->next_sibling = position.node; + tmp->prev_sibling = position.node->prev_sibling; + position.node->prev_sibling = tmp; + + if (tmp->prev_sibling == 0) { + if (tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->first_child = tmp; + } else + tmp->prev_sibling->next_sibling = tmp; + return tmp; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::insert(sibling_iterator position, const T &x) { + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->next_sibling = position.node; + if (position.node == 0) { // iterator points to end of a subtree + tmp->parent = position.parent_; + tmp->prev_sibling = position.range_last(); + tmp->parent->last_child = tmp; + } else { + tmp->parent = position.node->parent; + tmp->prev_sibling = position.node->prev_sibling; + position.node->prev_sibling = tmp; + } + + if (tmp->prev_sibling == 0) { + if (tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->first_child = tmp; + } else + tmp->prev_sibling->next_sibling = tmp; + return tmp; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::insert(sibling_iterator position, T &&x) { + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); + std::swap(tmp->data, x); // Move semantics + + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->next_sibling = position.node; + if (position.node == 0) { // iterator points to end of a subtree + tmp->parent = position.parent_; + tmp->prev_sibling = position.range_last(); + tmp->parent->last_child = tmp; + } else { + tmp->parent = position.node->parent; + tmp->prev_sibling = position.node->prev_sibling; + position.node->prev_sibling = tmp; + } + + if (tmp->prev_sibling == 0) { + if (tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->first_child = tmp; + } else + tmp->prev_sibling->next_sibling = tmp; + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::insert_after(iter position, const T& x) - { - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node->parent; - tmp->prev_sibling=position.node; - tmp->next_sibling=position.node->next_sibling; - position.node->next_sibling=tmp; - - if(tmp->next_sibling==0) { - if(tmp->parent) // when inserting nodes at the head, there is no parent - tmp->parent->last_child=tmp; - } - else { - tmp->next_sibling->prev_sibling=tmp; - } - return tmp; - } +iter tree<T, tree_node_allocator>::insert_after(iter position, const T &x) { + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, x); + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node->parent; + tmp->prev_sibling = position.node; + tmp->next_sibling = position.node->next_sibling; + position.node->next_sibling = tmp; + + if (tmp->next_sibling == 0) { + if (tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->last_child = tmp; + } else { + tmp->next_sibling->prev_sibling = tmp; + } + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::insert_after(iter position, T&& x) - { - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); - std::swap(tmp->data, x); // move semantics - tmp->first_child=0; - tmp->last_child=0; - - tmp->parent=position.node->parent; - tmp->prev_sibling=position.node; - tmp->next_sibling=position.node->next_sibling; - position.node->next_sibling=tmp; - - if(tmp->next_sibling==0) { - if(tmp->parent) // when inserting nodes at the head, there is no parent - tmp->parent->last_child=tmp; - } - else { - tmp->next_sibling->prev_sibling=tmp; - } - return tmp; - } +iter tree<T, tree_node_allocator>::insert_after(iter position, T &&x) { + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp); + std::swap(tmp->data, x); // move semantics + tmp->first_child = 0; + tmp->last_child = 0; + + tmp->parent = position.node->parent; + tmp->prev_sibling = position.node; + tmp->next_sibling = position.node->next_sibling; + position.node->next_sibling = tmp; + + if (tmp->next_sibling == 0) { + if (tmp->parent) // when inserting nodes at the head, there is no parent + tmp->parent->last_child = tmp; + } else { + tmp->next_sibling->prev_sibling = tmp; + } + return tmp; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::insert_subtree(iter position, const iterator_base& subtree) - { - // insert dummy - iter it=insert(position, value_type()); - // replace dummy with subtree - return replace(it, subtree); - } +iter tree<T, tree_node_allocator>::insert_subtree( + iter position, const iterator_base &subtree) { + // insert dummy + iter it = insert(position, value_type()); + // replace dummy with subtree + return replace(it, subtree); +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::insert_subtree_after(iter position, const iterator_base& subtree) - { - // insert dummy - iter it=insert_after(position, value_type()); - // replace dummy with subtree - return replace(it, subtree); - } +iter tree<T, tree_node_allocator>::insert_subtree_after( + iter position, const iterator_base &subtree) { + // insert dummy + iter it = insert_after(position, value_type()); + // replace dummy with subtree + return replace(it, subtree); +} // template <class T, class tree_node_allocator> // template <class iter> -// iter tree<T, tree_node_allocator>::insert_subtree(sibling_iterator position, iter subtree) +// iter tree<T, tree_node_allocator>::insert_subtree(sibling_iterator position, +// iter subtree) // { // // insert dummy // iter it(insert(position, value_type())); @@ -1481,1929 +1529,1957 @@ iter tree<T, tree_node_allocator>::insert_subtree_after(iter position, const ite template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::replace(iter position, const T& x) - { -// kp::destructor(&position.node->data); -// kp::constructor(&position.node->data, x); - position.node->data=x; -// alloc_.destroy(position.node); -// alloc_.construct(position.node, x); - return position; - } +iter tree<T, tree_node_allocator>::replace(iter position, const T &x) { + // kp::destructor(&position.node->data); + // kp::constructor(&position.node->data, x); + position.node->data = x; + // alloc_.destroy(position.node); + // alloc_.construct(position.node, x); + return position; +} template <class T, class tree_node_allocator> template <class iter> -iter tree<T, tree_node_allocator>::replace(iter position, const iterator_base& from) - { - assert(position.node!=head); - tree_node *current_from=from.node; - tree_node *start_from=from.node; - tree_node *current_to =position.node; - - // replace the node at position with head of the replacement tree at from -// std::cout << "warning!" << position.node << std::endl; - erase_children(position); -// std::cout << "no warning!" << std::endl; - tree_node *tmp=std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); - std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, (*from)); - tmp->first_child=0; - tmp->last_child=0; - if(current_to->prev_sibling==0) { - if(current_to->parent!=0) - current_to->parent->first_child=tmp; - } - else { - current_to->prev_sibling->next_sibling=tmp; - } - tmp->prev_sibling=current_to->prev_sibling; - if(current_to->next_sibling==0) { - if(current_to->parent!=0) - current_to->parent->last_child=tmp; - } - else { - current_to->next_sibling->prev_sibling=tmp; - } - tmp->next_sibling=current_to->next_sibling; - tmp->parent=current_to->parent; -// kp::destructor(¤t_to->data); - std::allocator_traits<decltype(alloc_)>::destroy(alloc_, current_to); - std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, current_to, 1); - current_to=tmp; - - // only at this stage can we fix 'last' - tree_node *last=from.node->next_sibling; - - pre_order_iterator toit=tmp; - // copy all children - do { - assert(current_from!=0); - if(current_from->first_child != 0) { - current_from=current_from->first_child; - toit=append_child(toit, current_from->data); - } - else { - while(current_from->next_sibling==0 && current_from!=start_from) { - current_from=current_from->parent; - toit=parent(toit); - assert(current_from!=0); - } - current_from=current_from->next_sibling; - if(current_from!=last) { - toit=append_child(parent(toit), current_from->data); - } - } - } - while(current_from!=last); - - return current_to; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::replace( - sibling_iterator orig_begin, - sibling_iterator orig_end, - sibling_iterator new_begin, - sibling_iterator new_end) - { - tree_node *orig_first=orig_begin.node; - tree_node *new_first=new_begin.node; - tree_node *orig_last=orig_first; - while((++orig_begin)!=orig_end) - orig_last=orig_last->next_sibling; - tree_node *new_last=new_first; - while((++new_begin)!=new_end) - new_last=new_last->next_sibling; - - // insert all siblings in new_first..new_last before orig_first - bool first=true; - pre_order_iterator ret; - while(1==1) { - pre_order_iterator tt=insert_subtree(pre_order_iterator(orig_first), pre_order_iterator(new_first)); - if(first) { - ret=tt; - first=false; - } - if(new_first==new_last) - break; - new_first=new_first->next_sibling; - } - - // erase old range of siblings - bool last=false; - tree_node *next=orig_first; - while(1==1) { - if(next==orig_last) - last=true; - next=next->next_sibling; - erase((pre_order_iterator)orig_first); - if(last) - break; - orig_first=next; - } - return ret; - } +iter tree<T, tree_node_allocator>::replace(iter position, + const iterator_base &from) { + assert(position.node != head); + tree_node *current_from = from.node; + tree_node *start_from = from.node; + tree_node *current_to = position.node; + + // replace the node at position with head of the replacement tree at from + // std::cout << "warning!" << position.node << std::endl; + erase_children(position); + // std::cout << "no warning!" << std::endl; + tree_node *tmp = + std::allocator_traits<decltype(alloc_)>::allocate(alloc_, 1, 0); + std::allocator_traits<decltype(alloc_)>::construct(alloc_, tmp, (*from)); + tmp->first_child = 0; + tmp->last_child = 0; + if (current_to->prev_sibling == 0) { + if (current_to->parent != 0) + current_to->parent->first_child = tmp; + } else { + current_to->prev_sibling->next_sibling = tmp; + } + tmp->prev_sibling = current_to->prev_sibling; + if (current_to->next_sibling == 0) { + if (current_to->parent != 0) + current_to->parent->last_child = tmp; + } else { + current_to->next_sibling->prev_sibling = tmp; + } + tmp->next_sibling = current_to->next_sibling; + tmp->parent = current_to->parent; + // kp::destructor(¤t_to->data); + std::allocator_traits<decltype(alloc_)>::destroy(alloc_, current_to); + std::allocator_traits<decltype(alloc_)>::deallocate(alloc_, current_to, 1); + current_to = tmp; + + // only at this stage can we fix 'last' + tree_node *last = from.node->next_sibling; + + pre_order_iterator toit = tmp; + // copy all children + do { + assert(current_from != 0); + if (current_from->first_child != 0) { + current_from = current_from->first_child; + toit = append_child(toit, current_from->data); + } else { + while (current_from->next_sibling == 0 && current_from != start_from) { + current_from = current_from->parent; + toit = parent(toit); + assert(current_from != 0); + } + current_from = current_from->next_sibling; + if (current_from != last) { + toit = append_child(parent(toit), current_from->data); + } + } + } while (current_from != last); + + return current_to; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::replace(sibling_iterator orig_begin, + sibling_iterator orig_end, + sibling_iterator new_begin, + sibling_iterator new_end) { + tree_node *orig_first = orig_begin.node; + tree_node *new_first = new_begin.node; + tree_node *orig_last = orig_first; + while ((++orig_begin) != orig_end) + orig_last = orig_last->next_sibling; + tree_node *new_last = new_first; + while ((++new_begin) != new_end) + new_last = new_last->next_sibling; + + // insert all siblings in new_first..new_last before orig_first + bool first = true; + pre_order_iterator ret; + while (1 == 1) { + pre_order_iterator tt = insert_subtree(pre_order_iterator(orig_first), + pre_order_iterator(new_first)); + if (first) { + ret = tt; + first = false; + } + if (new_first == new_last) + break; + new_first = new_first->next_sibling; + } + + // erase old range of siblings + bool last = false; + tree_node *next = orig_first; + while (1 == 1) { + if (next == orig_last) + last = true; + next = next->next_sibling; + erase((pre_order_iterator)orig_first); + if (last) + break; + orig_first = next; + } + return ret; +} + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::flatten(iter position) { + if (position.node->first_child == 0) + return position; + + tree_node *tmp = position.node->first_child; + while (tmp) { + tmp->parent = position.node->parent; + tmp = tmp->next_sibling; + } + if (position.node->next_sibling) { + position.node->last_child->next_sibling = position.node->next_sibling; + position.node->next_sibling->prev_sibling = position.node->last_child; + } else { + position.node->parent->last_child = position.node->last_child; + } + position.node->next_sibling = position.node->first_child; + position.node->next_sibling->prev_sibling = position.node; + position.node->first_child = 0; + position.node->last_child = 0; + + return position; +} + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::reparent(iter position, + sibling_iterator begin, + sibling_iterator end) { + tree_node *first = begin.node; + tree_node *last = first; + + assert(first != position.node); + + if (begin == end) + return begin; + // determine last node + while ((++begin) != end) { + last = last->next_sibling; + } + // move subtree + if (first->prev_sibling == 0) { + first->parent->first_child = last->next_sibling; + } else { + first->prev_sibling->next_sibling = last->next_sibling; + } + if (last->next_sibling == 0) { + last->parent->last_child = first->prev_sibling; + } else { + last->next_sibling->prev_sibling = first->prev_sibling; + } + if (position.node->first_child == 0) { + position.node->first_child = first; + position.node->last_child = last; + first->prev_sibling = 0; + } else { + position.node->last_child->next_sibling = first; + first->prev_sibling = position.node->last_child; + position.node->last_child = last; + } + last->next_sibling = 0; + + tree_node *pos = first; + for (;;) { + pos->parent = position.node; + if (pos == last) + break; + pos = pos->next_sibling; + } + + return first; +} + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::reparent(iter position, iter from) { + if (from.node->first_child == 0) + return position; + return reparent(position, from.node->first_child, end(from)); +} + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::wrap(iter position, const T &x) { + assert(position.node != 0); + sibling_iterator fr = position, to = position; + ++to; + iter ret = insert(position, x); + reparent(ret, fr, to); + return ret; +} template <class T, class tree_node_allocator> template <typename iter> -iter tree<T, tree_node_allocator>::flatten(iter position) - { - if(position.node->first_child==0) - return position; - - tree_node *tmp=position.node->first_child; - while(tmp) { - tmp->parent=position.node->parent; - tmp=tmp->next_sibling; - } - if(position.node->next_sibling) { - position.node->last_child->next_sibling=position.node->next_sibling; - position.node->next_sibling->prev_sibling=position.node->last_child; - } - else { - position.node->parent->last_child=position.node->last_child; - } - position.node->next_sibling=position.node->first_child; - position.node->next_sibling->prev_sibling=position.node; - position.node->first_child=0; - position.node->last_child=0; - - return position; - } +iter tree<T, tree_node_allocator>::wrap(iter from, iter to, const T &x) { + assert(from.node != 0); + iter ret = insert(from, x); + reparent(ret, from, to); + return ret; +} +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::move_after(iter target, iter source) { + tree_node *dst = target.node; + tree_node *src = source.node; + assert(dst); + assert(src); + + if (dst == src) + return source; + if (dst->next_sibling) + if (dst->next_sibling == src) // already in the right spot + return source; + + // take src out of the tree + if (src->prev_sibling != 0) + src->prev_sibling->next_sibling = src->next_sibling; + else + src->parent->first_child = src->next_sibling; + if (src->next_sibling != 0) + src->next_sibling->prev_sibling = src->prev_sibling; + else + src->parent->last_child = src->prev_sibling; + + // connect it to the new point + if (dst->next_sibling != 0) + dst->next_sibling->prev_sibling = src; + else + dst->parent->last_child = src; + src->next_sibling = dst->next_sibling; + dst->next_sibling = src; + src->prev_sibling = dst; + src->parent = dst->parent; + return src; +} template <class T, class tree_node_allocator> template <typename iter> -iter tree<T, tree_node_allocator>::reparent(iter position, sibling_iterator begin, sibling_iterator end) - { - tree_node *first=begin.node; - tree_node *last=first; - - assert(first!=position.node); - - if(begin==end) return begin; - // determine last node - while((++begin)!=end) { - last=last->next_sibling; - } - // move subtree - if(first->prev_sibling==0) { - first->parent->first_child=last->next_sibling; - } - else { - first->prev_sibling->next_sibling=last->next_sibling; - } - if(last->next_sibling==0) { - last->parent->last_child=first->prev_sibling; - } - else { - last->next_sibling->prev_sibling=first->prev_sibling; - } - if(position.node->first_child==0) { - position.node->first_child=first; - position.node->last_child=last; - first->prev_sibling=0; - } - else { - position.node->last_child->next_sibling=first; - first->prev_sibling=position.node->last_child; - position.node->last_child=last; - } - last->next_sibling=0; - - tree_node *pos=first; - for(;;) { - pos->parent=position.node; - if(pos==last) break; - pos=pos->next_sibling; - } - - return first; - } - -template <class T, class tree_node_allocator> -template <typename iter> iter tree<T, tree_node_allocator>::reparent(iter position, iter from) - { - if(from.node->first_child==0) return position; - return reparent(position, from.node->first_child, end(from)); - } - -template <class T, class tree_node_allocator> -template <typename iter> iter tree<T, tree_node_allocator>::wrap(iter position, const T& x) - { - assert(position.node!=0); - sibling_iterator fr=position, to=position; - ++to; - iter ret = insert(position, x); - reparent(ret, fr, to); - return ret; - } - -template <class T, class tree_node_allocator> -template <typename iter> iter tree<T, tree_node_allocator>::wrap(iter from, iter to, const T& x) - { - assert(from.node!=0); - iter ret = insert(from, x); - reparent(ret, from, to); - return ret; - } - -template <class T, class tree_node_allocator> -template <typename iter> iter tree<T, tree_node_allocator>::move_after(iter target, iter source) - { - tree_node *dst=target.node; - tree_node *src=source.node; - assert(dst); - assert(src); - - if(dst==src) return source; - if(dst->next_sibling) - if(dst->next_sibling==src) // already in the right spot - return source; - - // take src out of the tree - if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; - else src->parent->first_child=src->next_sibling; - if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; - else src->parent->last_child=src->prev_sibling; - - // connect it to the new point - if(dst->next_sibling!=0) dst->next_sibling->prev_sibling=src; - else dst->parent->last_child=src; - src->next_sibling=dst->next_sibling; - dst->next_sibling=src; - src->prev_sibling=dst; - src->parent=dst->parent; - return src; - } - -template <class T, class tree_node_allocator> -template <typename iter> iter tree<T, tree_node_allocator>::move_before(iter target, iter source) - { - tree_node *dst=target.node; - tree_node *src=source.node; - assert(dst); - assert(src); - - if(dst==src) return source; - if(dst->prev_sibling) - if(dst->prev_sibling==src) // already in the right spot - return source; - - // take src out of the tree - if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; - else src->parent->first_child=src->next_sibling; - if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; - else src->parent->last_child=src->prev_sibling; - - // connect it to the new point - if(dst->prev_sibling!=0) dst->prev_sibling->next_sibling=src; - else dst->parent->first_child=src; - src->prev_sibling=dst->prev_sibling; - dst->prev_sibling=src; - src->next_sibling=dst; - src->parent=dst->parent; - return src; - } +iter tree<T, tree_node_allocator>::move_before(iter target, iter source) { + tree_node *dst = target.node; + tree_node *src = source.node; + assert(dst); + assert(src); + + if (dst == src) + return source; + if (dst->prev_sibling) + if (dst->prev_sibling == src) // already in the right spot + return source; + + // take src out of the tree + if (src->prev_sibling != 0) + src->prev_sibling->next_sibling = src->next_sibling; + else + src->parent->first_child = src->next_sibling; + if (src->next_sibling != 0) + src->next_sibling->prev_sibling = src->prev_sibling; + else + src->parent->last_child = src->prev_sibling; + + // connect it to the new point + if (dst->prev_sibling != 0) + dst->prev_sibling->next_sibling = src; + else + dst->parent->first_child = src; + src->prev_sibling = dst->prev_sibling; + dst->prev_sibling = src; + src->next_sibling = dst; + src->parent = dst->parent; + return src; +} // specialisation for sibling_iterators template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::move_before(sibling_iterator target, - sibling_iterator source) - { - tree_node *dst=target.node; - tree_node *src=source.node; - tree_node *dst_prev_sibling; - if(dst==0) { // must then be an end iterator - dst_prev_sibling=target.parent_->last_child; - assert(dst_prev_sibling); - } - else dst_prev_sibling=dst->prev_sibling; - assert(src); - - if(dst==src) return source; - if(dst_prev_sibling) - if(dst_prev_sibling==src) // already in the right spot - return source; - - // take src out of the tree - if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; - else src->parent->first_child=src->next_sibling; - if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; - else src->parent->last_child=src->prev_sibling; - - // connect it to the new point - if(dst_prev_sibling!=0) dst_prev_sibling->next_sibling=src; - else target.parent_->first_child=src; - src->prev_sibling=dst_prev_sibling; - if(dst) { - dst->prev_sibling=src; - src->parent=dst->parent; - } - else { - src->parent=dst_prev_sibling->parent; - } - src->next_sibling=dst; - return src; - } - -template <class T, class tree_node_allocator> -template <typename iter> iter tree<T, tree_node_allocator>::move_ontop(iter target, iter source) - { - tree_node *dst=target.node; - tree_node *src=source.node; - assert(dst); - assert(src); - - if(dst==src) return source; - -// if(dst==src->prev_sibling) { -// -// } - - // remember connection points - tree_node *b_prev_sibling=dst->prev_sibling; - tree_node *b_next_sibling=dst->next_sibling; - tree_node *b_parent=dst->parent; - - // remove target - erase(target); - - // take src out of the tree - if(src->prev_sibling!=0) src->prev_sibling->next_sibling=src->next_sibling; - else { - assert(src->parent!=0); - src->parent->first_child=src->next_sibling; - } - if(src->next_sibling!=0) src->next_sibling->prev_sibling=src->prev_sibling; - else { - assert(src->parent!=0); - src->parent->last_child=src->prev_sibling; - } - - // connect it to the new point - if(b_prev_sibling!=0) b_prev_sibling->next_sibling=src; - else { - assert(b_parent!=0); - b_parent->first_child=src; - } - if(b_next_sibling!=0) b_next_sibling->prev_sibling=src; - else { - assert(b_parent!=0); - b_parent->last_child=src; - } - src->prev_sibling=b_prev_sibling; - src->next_sibling=b_next_sibling; - src->parent=b_parent; - return src; - } - - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator> tree<T, tree_node_allocator>::move_out(iterator source) - { - tree ret; - - // Move source node into the 'ret' tree. - ret.head->next_sibling = source.node; - ret.feet->prev_sibling = source.node; - - // Close the links in the current tree. - if(source.node->prev_sibling!=0) - source.node->prev_sibling->next_sibling = source.node->next_sibling; - - if(source.node->next_sibling!=0) - source.node->next_sibling->prev_sibling = source.node->prev_sibling; - - // If the moved-out node was a first or last child of - // the parent, adjust those links. - if(source.node->parent->first_child==source.node) { - if(source.node->next_sibling!=0) - source.node->parent->first_child=source.node->next_sibling; - else - source.node->parent->first_child=0; - } - if(source.node->parent->last_child==source.node) { - if(source.node->prev_sibling!=0) - source.node->parent->last_child=source.node->prev_sibling; - else - source.node->parent->last_child=0; - } - source.node->parent=0; - - // Fix source prev/next links. - source.node->prev_sibling = ret.head; - source.node->next_sibling = ret.feet; - - return ret; // A good compiler will move this, not copy. - } - -template <class T, class tree_node_allocator> -template<typename iter> iter tree<T, tree_node_allocator>::move_in(iter loc, tree& other) - { - if(other.head->next_sibling==other.feet) return loc; // other tree is empty - - tree_node *other_first_head = other.head->next_sibling; - tree_node *other_last_head = other.feet->prev_sibling; - - sibling_iterator prev(loc); - --prev; - - prev.node->next_sibling = other_first_head; - loc.node->prev_sibling = other_last_head; - other_first_head->prev_sibling = prev.node; - other_last_head->next_sibling = loc.node; - - // Adjust parent pointers. - tree_node *walk=other_first_head; - while(true) { - walk->parent=loc.node->parent; - if(walk==other_last_head) - break; - walk=walk->next_sibling; - } - - // Close other tree. - other.head->next_sibling=other.feet; - other.feet->prev_sibling=other.head; - - return other_first_head; - } - -template <class T, class tree_node_allocator> -template<typename iter> iter tree<T, tree_node_allocator>::move_in_below(iter loc, tree& other) - { - if(other.head->next_sibling==other.feet) return loc; // other tree is empty - - auto n = other.number_of_children(loc); - return move_in_as_nth_child(loc, n, other); - } - -template <class T, class tree_node_allocator> -template<typename iter> iter tree<T, tree_node_allocator>::move_in_as_nth_child(iter loc, size_t n, tree& other) - { - if(other.head->next_sibling==other.feet) return loc; // other tree is empty - - tree_node *other_first_head = other.head->next_sibling; - tree_node *other_last_head = other.feet->prev_sibling; - - if(n==0) { - if(loc.node->first_child==0) { - loc.node->first_child=other_first_head; - loc.node->last_child=other_last_head; - other_last_head->next_sibling=0; - other_first_head->prev_sibling=0; - } - else { - loc.node->first_child->prev_sibling=other_last_head; - other_last_head->next_sibling=loc.node->first_child; - loc.node->first_child=other_first_head; - other_first_head->prev_sibling=0; - } - } - else { - --n; - tree_node *walk = loc.node->first_child; - while(true) { - if(walk==0) - throw std::range_error("tree: move_in_as_nth_child position out of range"); - if(n==0) - break; - --n; - walk = walk->next_sibling; - } - if(walk->next_sibling==0) - loc.node->last_child=other_last_head; - else - walk->next_sibling->prev_sibling=other_last_head; - other_last_head->next_sibling=walk->next_sibling; - walk->next_sibling=other_first_head; - other_first_head->prev_sibling=walk; - } - - // Adjust parent pointers. - tree_node *walk=other_first_head; - while(true) { - walk->parent=loc.node; - if(walk==other_last_head) - break; - walk=walk->next_sibling; - } - - // Close other tree. - other.head->next_sibling=other.feet; - other.feet->prev_sibling=other.head; - - return other_first_head; - } - - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::merge(sibling_iterator to1, sibling_iterator to2, - sibling_iterator from1, sibling_iterator from2, - bool duplicate_leaves) - { - sibling_iterator fnd; - while(from1!=from2) { - if((fnd=std::find(to1, to2, (*from1))) != to2) { // element found - if(from1.begin()==from1.end()) { // full depth reached - if(duplicate_leaves) - append_child(parent(to1), (*from1)); - } - else { // descend further - merge(fnd.begin(), fnd.end(), from1.begin(), from1.end(), duplicate_leaves); - } - } - else { // element missing - insert_subtree(to2, from1); - } - ++from1; - } - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::merge(iterator to, iterator from, bool duplicate_leaves) - { - sibling_iterator to1(to); - sibling_iterator to2=to1; - ++to2; - sibling_iterator from1(from); - sibling_iterator from2=from1; - ++from2; - - merge(to1, to2, from1, from2, duplicate_leaves); - } - - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::sort(sibling_iterator from, sibling_iterator to, bool deep) - { - std::less<T> comp; - sort(from, to, comp, deep); - } +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::move_before(sibling_iterator target, + sibling_iterator source) { + tree_node *dst = target.node; + tree_node *src = source.node; + tree_node *dst_prev_sibling; + if (dst == 0) { // must then be an end iterator + dst_prev_sibling = target.parent_->last_child; + assert(dst_prev_sibling); + } else + dst_prev_sibling = dst->prev_sibling; + assert(src); + + if (dst == src) + return source; + if (dst_prev_sibling) + if (dst_prev_sibling == src) // already in the right spot + return source; + + // take src out of the tree + if (src->prev_sibling != 0) + src->prev_sibling->next_sibling = src->next_sibling; + else + src->parent->first_child = src->next_sibling; + if (src->next_sibling != 0) + src->next_sibling->prev_sibling = src->prev_sibling; + else + src->parent->last_child = src->prev_sibling; + + // connect it to the new point + if (dst_prev_sibling != 0) + dst_prev_sibling->next_sibling = src; + else + target.parent_->first_child = src; + src->prev_sibling = dst_prev_sibling; + if (dst) { + dst->prev_sibling = src; + src->parent = dst->parent; + } else { + src->parent = dst_prev_sibling->parent; + } + src->next_sibling = dst; + return src; +} + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::move_ontop(iter target, iter source) { + tree_node *dst = target.node; + tree_node *src = source.node; + assert(dst); + assert(src); + + if (dst == src) + return source; + + // if(dst==src->prev_sibling) { + // + // } + + // remember connection points + tree_node *b_prev_sibling = dst->prev_sibling; + tree_node *b_next_sibling = dst->next_sibling; + tree_node *b_parent = dst->parent; + + // remove target + erase(target); + + // take src out of the tree + if (src->prev_sibling != 0) + src->prev_sibling->next_sibling = src->next_sibling; + else { + assert(src->parent != 0); + src->parent->first_child = src->next_sibling; + } + if (src->next_sibling != 0) + src->next_sibling->prev_sibling = src->prev_sibling; + else { + assert(src->parent != 0); + src->parent->last_child = src->prev_sibling; + } + + // connect it to the new point + if (b_prev_sibling != 0) + b_prev_sibling->next_sibling = src; + else { + assert(b_parent != 0); + b_parent->first_child = src; + } + if (b_next_sibling != 0) + b_next_sibling->prev_sibling = src; + else { + assert(b_parent != 0); + b_parent->last_child = src; + } + src->prev_sibling = b_prev_sibling; + src->next_sibling = b_next_sibling; + src->parent = b_parent; + return src; +} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator> +tree<T, tree_node_allocator>::move_out(iterator source) { + tree ret; + + // Move source node into the 'ret' tree. + ret.head->next_sibling = source.node; + ret.feet->prev_sibling = source.node; + + // Close the links in the current tree. + if (source.node->prev_sibling != 0) + source.node->prev_sibling->next_sibling = source.node->next_sibling; + + if (source.node->next_sibling != 0) + source.node->next_sibling->prev_sibling = source.node->prev_sibling; + + // If the moved-out node was a first or last child of + // the parent, adjust those links. + if (source.node->parent->first_child == source.node) { + if (source.node->next_sibling != 0) + source.node->parent->first_child = source.node->next_sibling; + else + source.node->parent->first_child = 0; + } + if (source.node->parent->last_child == source.node) { + if (source.node->prev_sibling != 0) + source.node->parent->last_child = source.node->prev_sibling; + else + source.node->parent->last_child = 0; + } + source.node->parent = 0; + + // Fix source prev/next links. + source.node->prev_sibling = ret.head; + source.node->next_sibling = ret.feet; + + return ret; // A good compiler will move this, not copy. +} + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::move_in(iter loc, tree &other) { + if (other.head->next_sibling == other.feet) + return loc; // other tree is empty + + tree_node *other_first_head = other.head->next_sibling; + tree_node *other_last_head = other.feet->prev_sibling; + + sibling_iterator prev(loc); + --prev; + + prev.node->next_sibling = other_first_head; + loc.node->prev_sibling = other_last_head; + other_first_head->prev_sibling = prev.node; + other_last_head->next_sibling = loc.node; + + // Adjust parent pointers. + tree_node *walk = other_first_head; + while (true) { + walk->parent = loc.node->parent; + if (walk == other_last_head) + break; + walk = walk->next_sibling; + } + + // Close other tree. + other.head->next_sibling = other.feet; + other.feet->prev_sibling = other.head; + + return other_first_head; +} + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::move_in_below(iter loc, tree &other) { + if (other.head->next_sibling == other.feet) + return loc; // other tree is empty + + auto n = other.number_of_children(loc); + return move_in_as_nth_child(loc, n, other); +} + +template <class T, class tree_node_allocator> +template <typename iter> +iter tree<T, tree_node_allocator>::move_in_as_nth_child(iter loc, size_t n, + tree &other) { + if (other.head->next_sibling == other.feet) + return loc; // other tree is empty + + tree_node *other_first_head = other.head->next_sibling; + tree_node *other_last_head = other.feet->prev_sibling; + + if (n == 0) { + if (loc.node->first_child == 0) { + loc.node->first_child = other_first_head; + loc.node->last_child = other_last_head; + other_last_head->next_sibling = 0; + other_first_head->prev_sibling = 0; + } else { + loc.node->first_child->prev_sibling = other_last_head; + other_last_head->next_sibling = loc.node->first_child; + loc.node->first_child = other_first_head; + other_first_head->prev_sibling = 0; + } + } else { + --n; + tree_node *walk = loc.node->first_child; + while (true) { + if (walk == 0) + throw std::range_error( + "tree: move_in_as_nth_child position out of range"); + if (n == 0) + break; + --n; + walk = walk->next_sibling; + } + if (walk->next_sibling == 0) + loc.node->last_child = other_last_head; + else + walk->next_sibling->prev_sibling = other_last_head; + other_last_head->next_sibling = walk->next_sibling; + walk->next_sibling = other_first_head; + other_first_head->prev_sibling = walk; + } + + // Adjust parent pointers. + tree_node *walk = other_first_head; + while (true) { + walk->parent = loc.node; + if (walk == other_last_head) + break; + walk = walk->next_sibling; + } + + // Close other tree. + other.head->next_sibling = other.feet; + other.feet->prev_sibling = other.head; + + return other_first_head; +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::merge(sibling_iterator to1, + sibling_iterator to2, + sibling_iterator from1, + sibling_iterator from2, + bool duplicate_leaves) { + sibling_iterator fnd; + while (from1 != from2) { + if ((fnd = std::find(to1, to2, (*from1))) != to2) { // element found + if (from1.begin() == from1.end()) { // full depth reached + if (duplicate_leaves) + append_child(parent(to1), (*from1)); + } else { // descend further + merge(fnd.begin(), fnd.end(), from1.begin(), from1.end(), + duplicate_leaves); + } + } else { // element missing + insert_subtree(to2, from1); + } + ++from1; + } +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::merge(iterator to, iterator from, + bool duplicate_leaves) { + sibling_iterator to1(to); + sibling_iterator to2 = to1; + ++to2; + sibling_iterator from1(from); + sibling_iterator from2 = from1; + ++from2; + + merge(to1, to2, from1, from2, duplicate_leaves); +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::sort(sibling_iterator from, + sibling_iterator to, bool deep) { + std::less<T> comp; + sort(from, to, comp, deep); +} template <class T, class tree_node_allocator> template <class StrictWeakOrdering> -void tree<T, tree_node_allocator>::sort(sibling_iterator from, sibling_iterator to, - StrictWeakOrdering comp, bool deep) - { - if(from==to) return; - // make list of sorted nodes - // CHECK: if multiset stores equivalent nodes in the order in which they - // are inserted, then this routine should be called 'stable_sort'. - std::multiset<tree_node *, compare_nodes<StrictWeakOrdering> > nodes(comp); - sibling_iterator it=from, it2=to; - while(it != to) { - nodes.insert(it.node); - ++it; - } - // reassemble - --it2; - - // prev and next are the nodes before and after the sorted range - tree_node *prev=from.node->prev_sibling; - tree_node *next=it2.node->next_sibling; - typename std::multiset<tree_node *, compare_nodes<StrictWeakOrdering> >::iterator nit=nodes.begin(), eit=nodes.end(); - if(prev==0) { - if((*nit)->parent!=0) // to catch "sorting the head" situations, when there is no parent - (*nit)->parent->first_child=(*nit); - } - else prev->next_sibling=(*nit); - - --eit; - while(nit!=eit) { - (*nit)->prev_sibling=prev; - if(prev) - prev->next_sibling=(*nit); - prev=(*nit); - ++nit; - } - // prev now points to the last-but-one node in the sorted range - if(prev) - prev->next_sibling=(*eit); - - // eit points to the last node in the sorted range. - (*eit)->next_sibling=next; - (*eit)->prev_sibling=prev; // missed in the loop above - if(next==0) { - if((*eit)->parent!=0) // to catch "sorting the head" situations, when there is no parent - (*eit)->parent->last_child=(*eit); - } - else next->prev_sibling=(*eit); - - if(deep) { // sort the children of each node too - sibling_iterator bcs(*nodes.begin()); - sibling_iterator ecs(*eit); - ++ecs; - while(bcs!=ecs) { - sort(begin(bcs), end(bcs), comp, deep); - ++bcs; - } - } - } +void tree<T, tree_node_allocator>::sort(sibling_iterator from, + sibling_iterator to, + StrictWeakOrdering comp, bool deep) { + if (from == to) + return; + // make list of sorted nodes + // CHECK: if multiset stores equivalent nodes in the order in which they + // are inserted, then this routine should be called 'stable_sort'. + std::multiset<tree_node *, compare_nodes<StrictWeakOrdering>> nodes(comp); + sibling_iterator it = from, it2 = to; + while (it != to) { + nodes.insert(it.node); + ++it; + } + // reassemble + --it2; + + // prev and next are the nodes before and after the sorted range + tree_node *prev = from.node->prev_sibling; + tree_node *next = it2.node->next_sibling; + typename std::multiset<tree_node *, + compare_nodes<StrictWeakOrdering>>::iterator + nit = nodes.begin(), + eit = nodes.end(); + if (prev == 0) { + if ((*nit)->parent != + 0) // to catch "sorting the head" situations, when there is no parent + (*nit)->parent->first_child = (*nit); + } else + prev->next_sibling = (*nit); + + --eit; + while (nit != eit) { + (*nit)->prev_sibling = prev; + if (prev) + prev->next_sibling = (*nit); + prev = (*nit); + ++nit; + } + // prev now points to the last-but-one node in the sorted range + if (prev) + prev->next_sibling = (*eit); + + // eit points to the last node in the sorted range. + (*eit)->next_sibling = next; + (*eit)->prev_sibling = prev; // missed in the loop above + if (next == 0) { + if ((*eit)->parent != + 0) // to catch "sorting the head" situations, when there is no parent + (*eit)->parent->last_child = (*eit); + } else + next->prev_sibling = (*eit); + + if (deep) { // sort the children of each node too + sibling_iterator bcs(*nodes.begin()); + sibling_iterator ecs(*eit); + ++ecs; + while (bcs != ecs) { + sort(begin(bcs), end(bcs), comp, deep); + ++bcs; + } + } +} template <class T, class tree_node_allocator> template <typename iter> -bool tree<T, tree_node_allocator>::equal(const iter& one_, const iter& two, const iter& three_) const - { - std::equal_to<T> comp; - return equal(one_, two, three_, comp); - } +bool tree<T, tree_node_allocator>::equal(const iter &one_, const iter &two, + const iter &three_) const { + std::equal_to<T> comp; + return equal(one_, two, three_, comp); +} template <class T, class tree_node_allocator> template <typename iter> -bool tree<T, tree_node_allocator>::equal_subtree(const iter& one_, const iter& two_) const - { - std::equal_to<T> comp; - return equal_subtree(one_, two_, comp); - } +bool tree<T, tree_node_allocator>::equal_subtree(const iter &one_, + const iter &two_) const { + std::equal_to<T> comp; + return equal_subtree(one_, two_, comp); +} template <class T, class tree_node_allocator> template <typename iter, class BinaryPredicate> -bool tree<T, tree_node_allocator>::equal(const iter& one_, const iter& two, const iter& three_, BinaryPredicate fun) const - { - pre_order_iterator one(one_), three(three_); - -// if(one==two && is_valid(three) && three.number_of_children()!=0) -// return false; - while(one!=two && is_valid(three)) { - if(!fun(*one,*three)) - return false; - if(one.number_of_children()!=three.number_of_children()) - return false; - ++one; - ++three; - } - return true; - } +bool tree<T, tree_node_allocator>::equal(const iter &one_, const iter &two, + const iter &three_, + BinaryPredicate fun) const { + pre_order_iterator one(one_), three(three_); + + // if(one==two && is_valid(three) && three.number_of_children()!=0) + // return false; + while (one != two && is_valid(three)) { + if (!fun(*one, *three)) + return false; + if (one.number_of_children() != three.number_of_children()) + return false; + ++one; + ++three; + } + return true; +} template <class T, class tree_node_allocator> template <typename iter, class BinaryPredicate> -bool tree<T, tree_node_allocator>::equal_subtree(const iter& one_, const iter& two_, BinaryPredicate fun) const - { - pre_order_iterator one(one_), two(two_); - - if(!fun(*one,*two)) return false; - if(number_of_children(one)!=number_of_children(two)) return false; - return equal(begin(one),end(one),begin(two),fun); - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator> tree<T, tree_node_allocator>::subtree(sibling_iterator from, sibling_iterator to) const - { - assert(from!=to); // if from==to, the range is empty, hence no tree to return. - - tree tmp; - tmp.set_head(value_type()); - tmp.replace(tmp.begin(), tmp.end(), from, to); - return tmp; - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::subtree(tree& tmp, sibling_iterator from, sibling_iterator to) const - { - assert(from!=to); // if from==to, the range is empty, hence no tree to return. - - tmp.set_head(value_type()); - tmp.replace(tmp.begin(), tmp.end(), from, to); - } - -template <class T, class tree_node_allocator> -size_t tree<T, tree_node_allocator>::size() const - { - size_t i=0; - pre_order_iterator it=begin(), eit=end(); - while(it!=eit) { - ++i; - ++it; - } - return i; - } - -template <class T, class tree_node_allocator> -size_t tree<T, tree_node_allocator>::size(const iterator_base& top) const - { - size_t i=0; - pre_order_iterator it=top, eit=top; - eit.skip_children(); - ++eit; - while(it!=eit) { - ++i; - ++it; - } - return i; - } - -template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::empty() const - { - pre_order_iterator it=begin(), eit=end(); - return (it==eit); - } - -template <class T, class tree_node_allocator> -int tree<T, tree_node_allocator>::depth(const iterator_base& it) - { - tree_node* pos=it.node; - assert(pos!=0); - int ret=0; - while(pos->parent!=0) { - pos=pos->parent; - ++ret; - } - return ret; - } - -template <class T, class tree_node_allocator> -int tree<T, tree_node_allocator>::depth(const iterator_base& it, const iterator_base& root) - { - tree_node* pos=it.node; - assert(pos!=0); - int ret=0; - while(pos->parent!=0 && pos!=root.node) { - pos=pos->parent; - ++ret; - } - return ret; - } +bool tree<T, tree_node_allocator>::equal_subtree(const iter &one_, + const iter &two_, + BinaryPredicate fun) const { + pre_order_iterator one(one_), two(two_); + + if (!fun(*one, *two)) + return false; + if (number_of_children(one) != number_of_children(two)) + return false; + return equal(begin(one), end(one), begin(two), fun); +} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator> +tree<T, tree_node_allocator>::subtree(sibling_iterator from, + sibling_iterator to) const { + assert(from != + to); // if from==to, the range is empty, hence no tree to return. + + tree tmp; + tmp.set_head(value_type()); + tmp.replace(tmp.begin(), tmp.end(), from, to); + return tmp; +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::subtree(tree &tmp, sibling_iterator from, + sibling_iterator to) const { + assert(from != + to); // if from==to, the range is empty, hence no tree to return. + + tmp.set_head(value_type()); + tmp.replace(tmp.begin(), tmp.end(), from, to); +} + +template <class T, class tree_node_allocator> +size_t tree<T, tree_node_allocator>::size() const { + size_t i = 0; + pre_order_iterator it = begin(), eit = end(); + while (it != eit) { + ++i; + ++it; + } + return i; +} + +template <class T, class tree_node_allocator> +size_t tree<T, tree_node_allocator>::size(const iterator_base &top) const { + size_t i = 0; + pre_order_iterator it = top, eit = top; + eit.skip_children(); + ++eit; + while (it != eit) { + ++i; + ++it; + } + return i; +} + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::empty() const { + pre_order_iterator it = begin(), eit = end(); + return (it == eit); +} + +template <class T, class tree_node_allocator> +int tree<T, tree_node_allocator>::depth(const iterator_base &it) { + tree_node *pos = it.node; + assert(pos != 0); + int ret = 0; + while (pos->parent != 0) { + pos = pos->parent; + ++ret; + } + return ret; +} + +template <class T, class tree_node_allocator> +int tree<T, tree_node_allocator>::depth(const iterator_base &it, + const iterator_base &root) { + tree_node *pos = it.node; + assert(pos != 0); + int ret = 0; + while (pos->parent != 0 && pos != root.node) { + pos = pos->parent; + ++ret; + } + return ret; +} template <class T, class tree_node_allocator> template <class Predicate> -int tree<T, tree_node_allocator>::depth(const iterator_base& it, Predicate p) - { - tree_node* pos=it.node; - assert(pos!=0); - int ret=0; - while(pos->parent!=0) { - pos=pos->parent; - if(p(pos)) - ++ret; - } - return ret; - } +int tree<T, tree_node_allocator>::depth(const iterator_base &it, Predicate p) { + tree_node *pos = it.node; + assert(pos != 0); + int ret = 0; + while (pos->parent != 0) { + pos = pos->parent; + if (p(pos)) + ++ret; + } + return ret; +} template <class T, class tree_node_allocator> template <class Predicate> -int tree<T, tree_node_allocator>::distance(const iterator_base& top, const iterator_base& bottom, Predicate p) - { - tree_node* pos=bottom.node; - assert(pos!=0); - int ret=0; - while(pos->parent!=0 && pos!=top.node) { - pos=pos->parent; - if(p(pos)) - ++ret; - } - return ret; - } - -template <class T, class tree_node_allocator> -int tree<T, tree_node_allocator>::max_depth() const - { - int maxd=-1; - for(tree_node *it = head->next_sibling; it!=feet; it=it->next_sibling) - maxd=std::max(maxd, max_depth(it)); - - return maxd; - } - - -template <class T, class tree_node_allocator> -int tree<T, tree_node_allocator>::max_depth(const iterator_base& pos) const - { - tree_node *tmp=pos.node; - - if(tmp==0 || tmp==head || tmp==feet) return -1; - - int curdepth=0, maxdepth=0; - while(true) { // try to walk the bottom of the tree - while(tmp->first_child==0) { - if(tmp==pos.node) return maxdepth; - if(tmp->next_sibling==0) { - // try to walk up and then right again - do { - tmp=tmp->parent; - if(tmp==0) return maxdepth; - --curdepth; - } - while(tmp->next_sibling==0); - } - if(tmp==pos.node) return maxdepth; - tmp=tmp->next_sibling; - } - tmp=tmp->first_child; - ++curdepth; - maxdepth=std::max(curdepth, maxdepth); - } - } - -template <class T, class tree_node_allocator> -unsigned int tree<T, tree_node_allocator>::number_of_children(const iterator_base& it) - { - tree_node *pos=it.node->first_child; - if(pos==0) return 0; - - unsigned int ret=1; -// while(pos!=it.node->last_child) { -// ++ret; -// pos=pos->next_sibling; -// } - while((pos=pos->next_sibling)) - ++ret; - return ret; - } - -template <class T, class tree_node_allocator> -unsigned int tree<T, tree_node_allocator>::number_of_siblings(const iterator_base& it) const - { - tree_node *pos=it.node; - unsigned int ret=0; - // count forward - while(pos->next_sibling && - pos->next_sibling!=head && - pos->next_sibling!=feet) { - ++ret; - pos=pos->next_sibling; - } - // count backward - pos=it.node; - while(pos->prev_sibling && - pos->prev_sibling!=head && - pos->prev_sibling!=feet) { - ++ret; - pos=pos->prev_sibling; - } - - return ret; - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::swap(sibling_iterator it) - { - tree_node *nxt=it.node->next_sibling; - if(nxt) { - if(it.node->prev_sibling) - it.node->prev_sibling->next_sibling=nxt; - else - it.node->parent->first_child=nxt; - nxt->prev_sibling=it.node->prev_sibling; - tree_node *nxtnxt=nxt->next_sibling; - if(nxtnxt) - nxtnxt->prev_sibling=it.node; - else - it.node->parent->last_child=it.node; - nxt->next_sibling=it.node; - it.node->prev_sibling=nxt; - it.node->next_sibling=nxtnxt; - } - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::swap(iterator one, iterator two) - { - // if one and two are adjacent siblings, use the sibling swap - if(one.node->next_sibling==two.node) swap(one); - else if(two.node->next_sibling==one.node) swap(two); - else { - tree_node *nxt1=one.node->next_sibling; - tree_node *nxt2=two.node->next_sibling; - tree_node *pre1=one.node->prev_sibling; - tree_node *pre2=two.node->prev_sibling; - tree_node *par1=one.node->parent; - tree_node *par2=two.node->parent; - - // reconnect - one.node->parent=par2; - one.node->next_sibling=nxt2; - if(nxt2) nxt2->prev_sibling=one.node; - else par2->last_child=one.node; - one.node->prev_sibling=pre2; - if(pre2) pre2->next_sibling=one.node; - else par2->first_child=one.node; - - two.node->parent=par1; - two.node->next_sibling=nxt1; - if(nxt1) nxt1->prev_sibling=two.node; - else par1->last_child=two.node; - two.node->prev_sibling=pre1; - if(pre1) pre1->next_sibling=two.node; - else par1->first_child=two.node; - } - } +int tree<T, tree_node_allocator>::distance(const iterator_base &top, + const iterator_base &bottom, + Predicate p) { + tree_node *pos = bottom.node; + assert(pos != 0); + int ret = 0; + while (pos->parent != 0 && pos != top.node) { + pos = pos->parent; + if (p(pos)) + ++ret; + } + return ret; +} + +template <class T, class tree_node_allocator> +int tree<T, tree_node_allocator>::max_depth() const { + int maxd = -1; + for (tree_node *it = head->next_sibling; it != feet; it = it->next_sibling) + maxd = std::max(maxd, max_depth(it)); + + return maxd; +} + +template <class T, class tree_node_allocator> +int tree<T, tree_node_allocator>::max_depth(const iterator_base &pos) const { + tree_node *tmp = pos.node; + + if (tmp == 0 || tmp == head || tmp == feet) + return -1; + + int curdepth = 0, maxdepth = 0; + while (true) { // try to walk the bottom of the tree + while (tmp->first_child == 0) { + if (tmp == pos.node) + return maxdepth; + if (tmp->next_sibling == 0) { + // try to walk up and then right again + do { + tmp = tmp->parent; + if (tmp == 0) + return maxdepth; + --curdepth; + } while (tmp->next_sibling == 0); + } + if (tmp == pos.node) + return maxdepth; + tmp = tmp->next_sibling; + } + tmp = tmp->first_child; + ++curdepth; + maxdepth = std::max(curdepth, maxdepth); + } +} + +template <class T, class tree_node_allocator> +unsigned int +tree<T, tree_node_allocator>::number_of_children(const iterator_base &it) { + tree_node *pos = it.node->first_child; + if (pos == 0) + return 0; + + unsigned int ret = 1; + // while(pos!=it.node->last_child) { + // ++ret; + // pos=pos->next_sibling; + // } + while ((pos = pos->next_sibling)) + ++ret; + return ret; +} + +template <class T, class tree_node_allocator> +unsigned int tree<T, tree_node_allocator>::number_of_siblings( + const iterator_base &it) const { + tree_node *pos = it.node; + unsigned int ret = 0; + // count forward + while (pos->next_sibling && pos->next_sibling != head && + pos->next_sibling != feet) { + ++ret; + pos = pos->next_sibling; + } + // count backward + pos = it.node; + while (pos->prev_sibling && pos->prev_sibling != head && + pos->prev_sibling != feet) { + ++ret; + pos = pos->prev_sibling; + } + + return ret; +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::swap(sibling_iterator it) { + tree_node *nxt = it.node->next_sibling; + if (nxt) { + if (it.node->prev_sibling) + it.node->prev_sibling->next_sibling = nxt; + else + it.node->parent->first_child = nxt; + nxt->prev_sibling = it.node->prev_sibling; + tree_node *nxtnxt = nxt->next_sibling; + if (nxtnxt) + nxtnxt->prev_sibling = it.node; + else + it.node->parent->last_child = it.node; + nxt->next_sibling = it.node; + it.node->prev_sibling = nxt; + it.node->next_sibling = nxtnxt; + } +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::swap(iterator one, iterator two) { + // if one and two are adjacent siblings, use the sibling swap + if (one.node->next_sibling == two.node) + swap(one); + else if (two.node->next_sibling == one.node) + swap(two); + else { + tree_node *nxt1 = one.node->next_sibling; + tree_node *nxt2 = two.node->next_sibling; + tree_node *pre1 = one.node->prev_sibling; + tree_node *pre2 = two.node->prev_sibling; + tree_node *par1 = one.node->parent; + tree_node *par2 = two.node->parent; + + // reconnect + one.node->parent = par2; + one.node->next_sibling = nxt2; + if (nxt2) + nxt2->prev_sibling = one.node; + else + par2->last_child = one.node; + one.node->prev_sibling = pre2; + if (pre2) + pre2->next_sibling = one.node; + else + par2->first_child = one.node; + + two.node->parent = par1; + two.node->next_sibling = nxt1; + if (nxt1) + nxt1->prev_sibling = two.node; + else + par1->last_child = two.node; + two.node->prev_sibling = pre1; + if (pre1) + pre1->next_sibling = two.node; + else + par1->first_child = two.node; + } +} // template <class BinaryPredicate> -// tree<T, tree_node_allocator>::iterator tree<T, tree_node_allocator>::find_subtree( -// sibling_iterator subfrom, sibling_iterator subto, iterator from, iterator to, -// BinaryPredicate fun) const +// tree<T, tree_node_allocator>::iterator tree<T, +// tree_node_allocator>::find_subtree( sibling_iterator subfrom, +// sibling_iterator subto, iterator from, iterator to, BinaryPredicate fun) +// const // { // assert(1==0); // this routine is not finished yet. // while(from!=to) { // if(fun(*subfrom, *from)) { -// +// // } // } // return to; // } template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::is_in_subtree(const iterator_base& it, const iterator_base& top) const - { - sibling_iterator first=top; - sibling_iterator last=first; - ++last; - return is_in_subtree(it, first, last); - } - -template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::is_in_subtree(const iterator_base& it, const iterator_base& begin, - const iterator_base& end) const - { - // FIXME: this should be optimised. - pre_order_iterator tmp=begin; - while(tmp!=end) { - if(tmp==it) return true; - ++tmp; - } - return false; - } - -template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::is_valid(const iterator_base& it) const - { - if(it.node==0 || it.node==feet || it.node==head) return false; - else return true; - } - -template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::is_head(const iterator_base& it) - { - if(it.node->parent==0) return true; - return false; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::iterator tree<T, tree_node_allocator>::lowest_common_ancestor( - const iterator_base& one, const iterator_base& two) const - { - std::set<iterator, iterator_base_less> parents; - - // Walk up from 'one' storing all parents. - iterator walk=one; - do { - walk=parent(walk); - parents.insert(walk); - } - while( walk.node->parent ); - - // Walk up from 'two' until we encounter a node in parents. - walk=two; - do { - walk=parent(walk); - if(parents.find(walk) != parents.end()) break; - } - while( walk.node->parent ); - - return walk; - } - -template <class T, class tree_node_allocator> -unsigned int tree<T, tree_node_allocator>::index(sibling_iterator it) const - { - unsigned int ind=0; - if(it.node->parent==0) { - while(it.node->prev_sibling!=head) { - it.node=it.node->prev_sibling; - ++ind; - } - } - else { - while(it.node->prev_sibling!=0) { - it.node=it.node->prev_sibling; - ++ind; - } - } - return ind; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::sibling(const iterator_base& it, unsigned int num) const - { - tree_node *tmp; - if(it.node->parent==0) { - tmp=head->next_sibling; - while(num) { - tmp = tmp->next_sibling; - --num; - } - } - else { - tmp=it.node->parent->first_child; - while(num) { - assert(tmp!=0); - tmp = tmp->next_sibling; - --num; - } - } - return tmp; - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::debug_verify_consistency() const - { - iterator it=begin(); - while(it!=end()) { - // std::cerr << *it << " (" << it.node << ")" << std::endl; - if(it.node->parent!=0) { - if(it.node->prev_sibling==0) - assert(it.node->parent->first_child==it.node); - else - assert(it.node->prev_sibling->next_sibling==it.node); - if(it.node->next_sibling==0) - assert(it.node->parent->last_child==it.node); - else - assert(it.node->next_sibling->prev_sibling==it.node); - } - ++it; - } - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::child(const iterator_base& it, unsigned int num) - { - tree_node *tmp=it.node->first_child; - while(num--) { - assert(tmp!=0); - tmp=tmp->next_sibling; - } - return tmp; - } +bool tree<T, tree_node_allocator>::is_in_subtree( + const iterator_base &it, const iterator_base &top) const { + sibling_iterator first = top; + sibling_iterator last = first; + ++last; + return is_in_subtree(it, first, last); +} +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::is_in_subtree( + const iterator_base &it, const iterator_base &begin, + const iterator_base &end) const { + // FIXME: this should be optimised. + pre_order_iterator tmp = begin; + while (tmp != end) { + if (tmp == it) + return true; + ++tmp; + } + return false; +} +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::is_valid(const iterator_base &it) const { + if (it.node == 0 || it.node == feet || it.node == head) + return false; + else + return true; +} +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::is_head(const iterator_base &it) { + if (it.node->parent == 0) + return true; + return false; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::iterator +tree<T, tree_node_allocator>::lowest_common_ancestor( + const iterator_base &one, const iterator_base &two) const { + std::set<iterator, iterator_base_less> parents; + + // Walk up from 'one' storing all parents. + iterator walk = one; + do { + walk = parent(walk); + parents.insert(walk); + } while (walk.node->parent); + + // Walk up from 'two' until we encounter a node in parents. + walk = two; + do { + walk = parent(walk); + if (parents.find(walk) != parents.end()) + break; + } while (walk.node->parent); + + return walk; +} + +template <class T, class tree_node_allocator> +unsigned int tree<T, tree_node_allocator>::index(sibling_iterator it) const { + unsigned int ind = 0; + if (it.node->parent == 0) { + while (it.node->prev_sibling != head) { + it.node = it.node->prev_sibling; + ++ind; + } + } else { + while (it.node->prev_sibling != 0) { + it.node = it.node->prev_sibling; + ++ind; + } + } + return ind; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::sibling(const iterator_base &it, + unsigned int num) const { + tree_node *tmp; + if (it.node->parent == 0) { + tmp = head->next_sibling; + while (num) { + tmp = tmp->next_sibling; + --num; + } + } else { + tmp = it.node->parent->first_child; + while (num) { + assert(tmp != 0); + tmp = tmp->next_sibling; + --num; + } + } + return tmp; +} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::debug_verify_consistency() const { + iterator it = begin(); + while (it != end()) { + // std::cerr << *it << " (" << it.node << ")" << std::endl; + if (it.node->parent != 0) { + if (it.node->prev_sibling == 0) + assert(it.node->parent->first_child == it.node); + else + assert(it.node->prev_sibling->next_sibling == it.node); + if (it.node->next_sibling == 0) + assert(it.node->parent->last_child == it.node); + else + assert(it.node->next_sibling->prev_sibling == it.node); + } + ++it; + } +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::child(const iterator_base &it, unsigned int num) { + tree_node *tmp = it.node->first_child; + while (num--) { + assert(tmp != 0); + tmp = tmp->next_sibling; + } + return tmp; +} // Iterator base template <class T, class tree_node_allocator> tree<T, tree_node_allocator>::iterator_base::iterator_base() - : node(0), skip_current_children_(false) - { - } + : node(0), skip_current_children_(false) {} template <class T, class tree_node_allocator> tree<T, tree_node_allocator>::iterator_base::iterator_base(tree_node *tn) - : node(tn), skip_current_children_(false) - { - } + : node(tn), skip_current_children_(false) {} template <class T, class tree_node_allocator> -T& tree<T, tree_node_allocator>::iterator_base::operator*() const - { - return node->data; - } +T &tree<T, tree_node_allocator>::iterator_base::operator*() const { + return node->data; +} template <class T, class tree_node_allocator> -T* tree<T, tree_node_allocator>::iterator_base::operator->() const - { - return &(node->data); - } +T *tree<T, tree_node_allocator>::iterator_base::operator->() const { + return &(node->data); +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::post_order_iterator::operator!=(const post_order_iterator& other) const - { - if(other.node!=this->node) return true; - else return false; - } +bool tree<T, tree_node_allocator>::post_order_iterator::operator!=( + const post_order_iterator &other) const { + if (other.node != this->node) + return true; + else + return false; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::post_order_iterator::operator==(const post_order_iterator& other) const - { - if(other.node==this->node) return true; - else return false; - } +bool tree<T, tree_node_allocator>::post_order_iterator::operator==( + const post_order_iterator &other) const { + if (other.node == this->node) + return true; + else + return false; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::pre_order_iterator::operator!=(const pre_order_iterator& other) const - { - if(other.node!=this->node) return true; - else return false; - } +bool tree<T, tree_node_allocator>::pre_order_iterator::operator!=( + const pre_order_iterator &other) const { + if (other.node != this->node) + return true; + else + return false; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::pre_order_iterator::operator==(const pre_order_iterator& other) const - { - if(other.node==this->node) return true; - else return false; - } +bool tree<T, tree_node_allocator>::pre_order_iterator::operator==( + const pre_order_iterator &other) const { + if (other.node == this->node) + return true; + else + return false; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::sibling_iterator::operator!=(const sibling_iterator& other) const - { - if(other.node!=this->node) return true; - else return false; - } +bool tree<T, tree_node_allocator>::sibling_iterator::operator!=( + const sibling_iterator &other) const { + if (other.node != this->node) + return true; + else + return false; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::sibling_iterator::operator==(const sibling_iterator& other) const - { - if(other.node==this->node) return true; - else return false; - } +bool tree<T, tree_node_allocator>::sibling_iterator::operator==( + const sibling_iterator &other) const { + if (other.node == this->node) + return true; + else + return false; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::leaf_iterator::operator!=(const leaf_iterator& other) const - { - if(other.node!=this->node) return true; - else return false; - } +bool tree<T, tree_node_allocator>::leaf_iterator::operator!=( + const leaf_iterator &other) const { + if (other.node != this->node) + return true; + else + return false; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::leaf_iterator::operator==(const leaf_iterator& other) const - { - if(other.node==this->node && other.top_node==this->top_node) return true; - else return false; - } +bool tree<T, tree_node_allocator>::leaf_iterator::operator==( + const leaf_iterator &other) const { + if (other.node == this->node && other.top_node == this->top_node) + return true; + else + return false; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::iterator_base::begin() const - { - if(node->first_child==0) - return end(); +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::iterator_base::begin() const { + if (node->first_child == 0) + return end(); - sibling_iterator ret(node->first_child); - ret.parent_=this->node; - return ret; - } + sibling_iterator ret(node->first_child); + ret.parent_ = this->node; + return ret; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::iterator_base::end() const - { - sibling_iterator ret(0); - ret.parent_=node; - return ret; - } +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::iterator_base::end() const { + sibling_iterator ret(0); + ret.parent_ = node; + return ret; +} template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::iterator_base::skip_children() - { - skip_current_children_=true; - } +void tree<T, tree_node_allocator>::iterator_base::skip_children() { + skip_current_children_ = true; +} template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::iterator_base::skip_children(bool skip) - { - skip_current_children_=skip; - } +void tree<T, tree_node_allocator>::iterator_base::skip_children(bool skip) { + skip_current_children_ = skip; +} template <class T, class tree_node_allocator> -unsigned int tree<T, tree_node_allocator>::iterator_base::number_of_children() const - { - tree_node *pos=node->first_child; - if(pos==0) return 0; - - unsigned int ret=1; - while(pos!=node->last_child) { - ++ret; - pos=pos->next_sibling; - } - return ret; - } - +unsigned int +tree<T, tree_node_allocator>::iterator_base::number_of_children() const { + tree_node *pos = node->first_child; + if (pos == 0) + return 0; + unsigned int ret = 1; + while (pos != node->last_child) { + ++ret; + pos = pos->next_sibling; + } + return ret; +} // Pre-order iterator template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator() - : iterator_base(0) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator(tree_node *tn) - : iterator_base(tn) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator(const iterator_base &other) - : iterator_base(other.node) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator(const sibling_iterator& other) - : iterator_base(other.node) - { - if(this->node==0) { - if(other.range_last()!=0) - this->node=other.range_last(); - else - this->node=other.parent_; - this->skip_children(); - ++(*this); - } - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator++() - { - assert(this->node!=0); - if(!this->skip_current_children_ && this->node->first_child != 0) { - this->node=this->node->first_child; - } - else { - this->skip_current_children_=false; - while(this->node->next_sibling==0) { - this->node=this->node->parent; - if(this->node==0) - return *this; - } - this->node=this->node->next_sibling; - } - return *this; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator--() - { - assert(this->node!=0); - if(this->node->prev_sibling) { - this->node=this->node->prev_sibling; - while(this->node->last_child) - this->node=this->node->last_child; - } - else { - this->node=this->node->parent; - if(this->node==0) - return *this; - } - return *this; -} - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::pre_order_iterator::operator++(int) - { - pre_order_iterator copy = *this; - ++(*this); - return copy; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::next_skip_children() - { - (*this).skip_children(); - (*this)++; - return *this; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator tree<T, tree_node_allocator>::pre_order_iterator::operator--(int) -{ +tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator() + : iterator_base(0) {} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator( + tree_node *tn) + : iterator_base(tn) {} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator( + const iterator_base &other) + : iterator_base(other.node) {} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::pre_order_iterator::pre_order_iterator( + const sibling_iterator &other) + : iterator_base(other.node) { + if (this->node == 0) { + if (other.range_last() != 0) + this->node = other.range_last(); + else + this->node = other.parent_; + this->skip_children(); + ++(*this); + } +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator & +tree<T, tree_node_allocator>::pre_order_iterator::operator++() { + assert(this->node != 0); + if (!this->skip_current_children_ && this->node->first_child != 0) { + this->node = this->node->first_child; + } else { + this->skip_current_children_ = false; + while (this->node->next_sibling == 0) { + this->node = this->node->parent; + if (this->node == 0) + return *this; + } + this->node = this->node->next_sibling; + } + return *this; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator & +tree<T, tree_node_allocator>::pre_order_iterator::operator--() { + assert(this->node != 0); + if (this->node->prev_sibling) { + this->node = this->node->prev_sibling; + while (this->node->last_child) + this->node = this->node->last_child; + } else { + this->node = this->node->parent; + if (this->node == 0) + return *this; + } + return *this; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator +tree<T, tree_node_allocator>::pre_order_iterator::operator++(int) { pre_order_iterator copy = *this; - --(*this); + ++(*this); return copy; } template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator+=(unsigned int num) - { - while(num>0) { - ++(*this); - --num; - } - return (*this); - } +typename tree<T, tree_node_allocator>::pre_order_iterator & +tree<T, tree_node_allocator>::pre_order_iterator::next_skip_children() { + (*this).skip_children(); + (*this)++; + return *this; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::pre_order_iterator& tree<T, tree_node_allocator>::pre_order_iterator::operator-=(unsigned int num) - { - while(num>0) { - --(*this); - --num; - } - return (*this); - } +typename tree<T, tree_node_allocator>::pre_order_iterator +tree<T, tree_node_allocator>::pre_order_iterator::operator--(int) { + pre_order_iterator copy = *this; + --(*this); + return copy; +} +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator & +tree<T, tree_node_allocator>::pre_order_iterator::operator+=(unsigned int num) { + while (num > 0) { + ++(*this); + --num; + } + return (*this); +} +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::pre_order_iterator & +tree<T, tree_node_allocator>::pre_order_iterator::operator-=(unsigned int num) { + while (num > 0) { + --(*this); + --num; + } + return (*this); +} // Post-order iterator template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator() - : iterator_base(0) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator(tree_node *tn) - : iterator_base(tn) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator(const iterator_base &other) - : iterator_base(other.node) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator(const sibling_iterator& other) - : iterator_base(other.node) - { - if(this->node==0) { - if(other.range_last()!=0) - this->node=other.range_last(); - else - this->node=other.parent_; - this->skip_children(); - ++(*this); - } - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator++() - { - assert(this->node!=0); - if(this->node->next_sibling==0) { - this->node=this->node->parent; - this->skip_current_children_=false; - } - else { - this->node=this->node->next_sibling; - if(this->skip_current_children_) { - this->skip_current_children_=false; - } - else { - while(this->node->first_child) - this->node=this->node->first_child; - } - } - return *this; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator--() - { - assert(this->node!=0); - if(this->skip_current_children_ || this->node->last_child==0) { - this->skip_current_children_=false; - while(this->node->prev_sibling==0) - this->node=this->node->parent; - this->node=this->node->prev_sibling; - } - else { - this->node=this->node->last_child; - } - return *this; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::post_order_iterator::operator++(int) - { - post_order_iterator copy = *this; - ++(*this); - return copy; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::post_order_iterator tree<T, tree_node_allocator>::post_order_iterator::operator--(int) - { - post_order_iterator copy = *this; - --(*this); - return copy; - } - - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator+=(unsigned int num) - { - while(num>0) { - ++(*this); - --num; - } - return (*this); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::post_order_iterator& tree<T, tree_node_allocator>::post_order_iterator::operator-=(unsigned int num) - { - while(num>0) { - --(*this); - --num; - } - return (*this); - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::post_order_iterator::descend_all() - { - assert(this->node!=0); - while(this->node->first_child) - this->node=this->node->first_child; - } +tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator() + : iterator_base(0) {} +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator( + tree_node *tn) + : iterator_base(tn) {} -// Breadth-first iterator +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator( + const iterator_base &other) + : iterator_base(other.node) {} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::breadth_first_queued_iterator::breadth_first_queued_iterator() - : iterator_base() - { - } +tree<T, tree_node_allocator>::post_order_iterator::post_order_iterator( + const sibling_iterator &other) + : iterator_base(other.node) { + if (this->node == 0) { + if (other.range_last() != 0) + this->node = other.range_last(); + else + this->node = other.parent_; + this->skip_children(); + ++(*this); + } +} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::breadth_first_queued_iterator::breadth_first_queued_iterator(tree_node *tn) - : iterator_base(tn) - { - traversal_queue.push(tn); - } +typename tree<T, tree_node_allocator>::post_order_iterator & +tree<T, tree_node_allocator>::post_order_iterator::operator++() { + assert(this->node != 0); + if (this->node->next_sibling == 0) { + this->node = this->node->parent; + this->skip_current_children_ = false; + } else { + this->node = this->node->next_sibling; + if (this->skip_current_children_) { + this->skip_current_children_ = false; + } else { + while (this->node->first_child) + this->node = this->node->first_child; + } + } + return *this; +} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::breadth_first_queued_iterator::breadth_first_queued_iterator(const iterator_base& other) - : iterator_base(other.node) - { - traversal_queue.push(other.node); - } +typename tree<T, tree_node_allocator>::post_order_iterator & +tree<T, tree_node_allocator>::post_order_iterator::operator--() { + assert(this->node != 0); + if (this->skip_current_children_ || this->node->last_child == 0) { + this->skip_current_children_ = false; + while (this->node->prev_sibling == 0) + this->node = this->node->parent; + this->node = this->node->prev_sibling; + } else { + this->node = this->node->last_child; + } + return *this; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator!=(const breadth_first_queued_iterator& other) const - { - if(other.node!=this->node) return true; - else return false; - } +typename tree<T, tree_node_allocator>::post_order_iterator +tree<T, tree_node_allocator>::post_order_iterator::operator++(int) { + post_order_iterator copy = *this; + ++(*this); + return copy; +} template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator==(const breadth_first_queued_iterator& other) const - { - if(other.node==this->node) return true; - else return false; - } +typename tree<T, tree_node_allocator>::post_order_iterator +tree<T, tree_node_allocator>::post_order_iterator::operator--(int) { + post_order_iterator copy = *this; + --(*this); + return copy; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::breadth_first_queued_iterator& tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator++() - { - assert(this->node!=0); +typename tree<T, tree_node_allocator>::post_order_iterator & +tree<T, tree_node_allocator>::post_order_iterator::operator+=( + unsigned int num) { + while (num > 0) { + ++(*this); + --num; + } + return (*this); +} - // Add child nodes and pop current node - sibling_iterator sib=this->begin(); - while(sib!=this->end()) { - traversal_queue.push(sib.node); - ++sib; - } - traversal_queue.pop(); - if(traversal_queue.size()>0) - this->node=traversal_queue.front(); - else - this->node=0; - return (*this); - } +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::post_order_iterator & +tree<T, tree_node_allocator>::post_order_iterator::operator-=( + unsigned int num) { + while (num > 0) { + --(*this); + --num; + } + return (*this); +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::breadth_first_queued_iterator tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator++(int) - { - breadth_first_queued_iterator copy = *this; - ++(*this); - return copy; - } +void tree<T, tree_node_allocator>::post_order_iterator::descend_all() { + assert(this->node != 0); + while (this->node->first_child) + this->node = this->node->first_child; +} + +// Breadth-first iterator template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::breadth_first_queued_iterator& tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator+=(unsigned int num) - { - while(num>0) { - ++(*this); - --num; - } - return (*this); - } +tree<T, tree_node_allocator>::breadth_first_queued_iterator:: + breadth_first_queued_iterator() + : iterator_base() {} +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::breadth_first_queued_iterator:: + breadth_first_queued_iterator(tree_node *tn) + : iterator_base(tn) { + traversal_queue.push(tn); +} +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::breadth_first_queued_iterator:: + breadth_first_queued_iterator(const iterator_base &other) + : iterator_base(other.node) { + traversal_queue.push(other.node); +} + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator!=( + const breadth_first_queued_iterator &other) const { + if (other.node != this->node) + return true; + else + return false; +} + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator==( + const breadth_first_queued_iterator &other) const { + if (other.node == this->node) + return true; + else + return false; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator & +tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator++() { + assert(this->node != 0); + + // Add child nodes and pop current node + sibling_iterator sib = this->begin(); + while (sib != this->end()) { + traversal_queue.push(sib.node); + ++sib; + } + traversal_queue.pop(); + if (traversal_queue.size() > 0) + this->node = traversal_queue.front(); + else + this->node = 0; + return (*this); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator +tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator++(int) { + breadth_first_queued_iterator copy = *this; + ++(*this); + return copy; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::breadth_first_queued_iterator & +tree<T, tree_node_allocator>::breadth_first_queued_iterator::operator+=( + unsigned int num) { + while (num > 0) { + ++(*this); + --num; + } + return (*this); +} // Fixed depth iterator template <class T, class tree_node_allocator> tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator() - : iterator_base() - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(tree_node *tn) - : iterator_base(tn), top_node(0) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(const iterator_base& other) - : iterator_base(other.node), top_node(0) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(const sibling_iterator& other) - : iterator_base(other.node), top_node(0) - { - } - -template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator(const fixed_depth_iterator& other) - : iterator_base(other.node), top_node(other.top_node) - { - } - -template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::fixed_depth_iterator::swap(fixed_depth_iterator& first, fixed_depth_iterator& second) - { - std::swap(first.node, second.node); - std::swap(first.skip_current_children_, second.skip_current_children_); - std::swap(first.top_node, second.top_node); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator=(fixed_depth_iterator other) - { - swap(*this, other); - return *this; - } - -template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::fixed_depth_iterator::operator==(const fixed_depth_iterator& other) const - { - if(other.node==this->node && other.top_node==top_node) return true; - else return false; - } - -template <class T, class tree_node_allocator> -bool tree<T, tree_node_allocator>::fixed_depth_iterator::operator!=(const fixed_depth_iterator& other) const - { - if(other.node!=this->node || other.top_node!=top_node) return true; - else return false; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator++() - { - assert(this->node!=0); - - if(this->node->next_sibling) { - this->node=this->node->next_sibling; - } - else { - int relative_depth=0; - upper: - do { - if(this->node==this->top_node) { - this->node=0; // FIXME: return a proper fixed_depth end iterator once implemented - return *this; - } - this->node=this->node->parent; - if(this->node==0) return *this; - --relative_depth; - } while(this->node->next_sibling==0); - lower: - this->node=this->node->next_sibling; - while(this->node->first_child==0) { - if(this->node->next_sibling==0) - goto upper; - this->node=this->node->next_sibling; - if(this->node==0) return *this; - } - while(relative_depth<0 && this->node->first_child!=0) { - this->node=this->node->first_child; - ++relative_depth; - } - if(relative_depth<0) { - if(this->node->next_sibling==0) goto upper; - else goto lower; - } - } - return *this; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator--() - { - assert(this->node!=0); - - if(this->node->prev_sibling) { - this->node=this->node->prev_sibling; - } - else { - int relative_depth=0; - upper: - do { - if(this->node==this->top_node) { - this->node=0; - return *this; - } - this->node=this->node->parent; - if(this->node==0) return *this; - --relative_depth; - } while(this->node->prev_sibling==0); - lower: - this->node=this->node->prev_sibling; - while(this->node->last_child==0) { - if(this->node->prev_sibling==0) - goto upper; - this->node=this->node->prev_sibling; - if(this->node==0) return *this; - } - while(relative_depth<0 && this->node->last_child!=0) { - this->node=this->node->last_child; - ++relative_depth; - } - if(relative_depth<0) { - if(this->node->prev_sibling==0) goto upper; - else goto lower; - } - } - return *this; + : iterator_base() {} -// -// -// assert(this->node!=0); -// if(this->node->prev_sibling!=0) { -// this->node=this->node->prev_sibling; -// assert(this->node!=0); -// if(this->node->parent==0 && this->node->prev_sibling==0) // head element -// this->node=0; -// } -// else { -// tree_node *par=this->node->parent; -// do { -// par=par->prev_sibling; -// if(par==0) { // FIXME: need to keep track of this! -// this->node=0; -// return *this; -// } -// } while(par->last_child==0); -// this->node=par->last_child; -// } -// return *this; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::fixed_depth_iterator::operator++(int) - { - fixed_depth_iterator copy = *this; - ++(*this); - return copy; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator tree<T, tree_node_allocator>::fixed_depth_iterator::operator--(int) - { - fixed_depth_iterator copy = *this; - --(*this); - return copy; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator-=(unsigned int num) - { - while(num>0) { - --(*this); - --(num); - } - return (*this); - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::fixed_depth_iterator& tree<T, tree_node_allocator>::fixed_depth_iterator::operator+=(unsigned int num) - { - while(num>0) { - ++(*this); - --(num); - } - return *this; - } +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator( + tree_node *tn) + : iterator_base(tn), top_node(0) {} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator( + const iterator_base &other) + : iterator_base(other.node), top_node(0) {} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator( + const sibling_iterator &other) + : iterator_base(other.node), top_node(0) {} + +template <class T, class tree_node_allocator> +tree<T, tree_node_allocator>::fixed_depth_iterator::fixed_depth_iterator( + const fixed_depth_iterator &other) + : iterator_base(other.node), top_node(other.top_node) {} + +template <class T, class tree_node_allocator> +void tree<T, tree_node_allocator>::fixed_depth_iterator::swap( + fixed_depth_iterator &first, fixed_depth_iterator &second) { + std::swap(first.node, second.node); + std::swap(first.skip_current_children_, second.skip_current_children_); + std::swap(first.top_node, second.top_node); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator & +tree<T, tree_node_allocator>::fixed_depth_iterator::operator=( + fixed_depth_iterator other) { + swap(*this, other); + return *this; +} + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::fixed_depth_iterator::operator==( + const fixed_depth_iterator &other) const { + if (other.node == this->node && other.top_node == top_node) + return true; + else + return false; +} + +template <class T, class tree_node_allocator> +bool tree<T, tree_node_allocator>::fixed_depth_iterator::operator!=( + const fixed_depth_iterator &other) const { + if (other.node != this->node || other.top_node != top_node) + return true; + else + return false; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator & +tree<T, tree_node_allocator>::fixed_depth_iterator::operator++() { + assert(this->node != 0); + + if (this->node->next_sibling) { + this->node = this->node->next_sibling; + } else { + int relative_depth = 0; + upper: + do { + if (this->node == this->top_node) { + this->node = 0; // FIXME: return a proper fixed_depth end iterator once + // implemented + return *this; + } + this->node = this->node->parent; + if (this->node == 0) + return *this; + --relative_depth; + } while (this->node->next_sibling == 0); + lower: + this->node = this->node->next_sibling; + while (this->node->first_child == 0) { + if (this->node->next_sibling == 0) + goto upper; + this->node = this->node->next_sibling; + if (this->node == 0) + return *this; + } + while (relative_depth < 0 && this->node->first_child != 0) { + this->node = this->node->first_child; + ++relative_depth; + } + if (relative_depth < 0) { + if (this->node->next_sibling == 0) + goto upper; + else + goto lower; + } + } + return *this; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator & +tree<T, tree_node_allocator>::fixed_depth_iterator::operator--() { + assert(this->node != 0); + + if (this->node->prev_sibling) { + this->node = this->node->prev_sibling; + } else { + int relative_depth = 0; + upper: + do { + if (this->node == this->top_node) { + this->node = 0; + return *this; + } + this->node = this->node->parent; + if (this->node == 0) + return *this; + --relative_depth; + } while (this->node->prev_sibling == 0); + lower: + this->node = this->node->prev_sibling; + while (this->node->last_child == 0) { + if (this->node->prev_sibling == 0) + goto upper; + this->node = this->node->prev_sibling; + if (this->node == 0) + return *this; + } + while (relative_depth < 0 && this->node->last_child != 0) { + this->node = this->node->last_child; + ++relative_depth; + } + if (relative_depth < 0) { + if (this->node->prev_sibling == 0) + goto upper; + else + goto lower; + } + } + return *this; + + // + // + // assert(this->node!=0); + // if(this->node->prev_sibling!=0) { + // this->node=this->node->prev_sibling; + // assert(this->node!=0); + // if(this->node->parent==0 && this->node->prev_sibling==0) // head + //element this->node=0; + // } + // else { + // tree_node *par=this->node->parent; + // do { + // par=par->prev_sibling; + // if(par==0) { // FIXME: need to keep track of this! + // this->node=0; + // return *this; + // } + // } while(par->last_child==0); + // this->node=par->last_child; + // } + // return *this; +} +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator +tree<T, tree_node_allocator>::fixed_depth_iterator::operator++(int) { + fixed_depth_iterator copy = *this; + ++(*this); + return copy; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator +tree<T, tree_node_allocator>::fixed_depth_iterator::operator--(int) { + fixed_depth_iterator copy = *this; + --(*this); + return copy; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator & +tree<T, tree_node_allocator>::fixed_depth_iterator::operator-=( + unsigned int num) { + while (num > 0) { + --(*this); + --(num); + } + return (*this); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::fixed_depth_iterator & +tree<T, tree_node_allocator>::fixed_depth_iterator::operator+=( + unsigned int num) { + while (num > 0) { + ++(*this); + --(num); + } + return *this; +} // Sibling iterator template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator() - : iterator_base() - { - set_parent_(); - } +tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator() + : iterator_base() { + set_parent_(); +} template <class T, class tree_node_allocator> tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator(tree_node *tn) - : iterator_base(tn) - { - set_parent_(); - } + : iterator_base(tn) { + set_parent_(); +} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator(const iterator_base& other) - : iterator_base(other.node) - { - set_parent_(); - } +tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator( + const iterator_base &other) + : iterator_base(other.node) { + set_parent_(); +} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator(const sibling_iterator& other) - : iterator_base(other), parent_(other.parent_) - { - } +tree<T, tree_node_allocator>::sibling_iterator::sibling_iterator( + const sibling_iterator &other) + : iterator_base(other), parent_(other.parent_) {} template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::sibling_iterator::swap(sibling_iterator& first, sibling_iterator& second) - { - std::swap(first.node, second.node); - std::swap(first.skip_current_children_, second.skip_current_children_); - std::swap(first.parent_, second.parent_); - } - +void tree<T, tree_node_allocator>::sibling_iterator::swap( + sibling_iterator &first, sibling_iterator &second) { + std::swap(first.node, second.node); + std::swap(first.skip_current_children_, second.skip_current_children_); + std::swap(first.parent_, second.parent_); +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator=(sibling_iterator other) - { - swap(*this, other); - return *this; - } - +typename tree<T, tree_node_allocator>::sibling_iterator & +tree<T, tree_node_allocator>::sibling_iterator::operator=( + sibling_iterator other) { + swap(*this, other); + return *this; +} + template <class T, class tree_node_allocator> -void tree<T, tree_node_allocator>::sibling_iterator::set_parent_() - { - parent_=0; - if(this->node==0) return; - if(this->node->parent!=0) - parent_=this->node->parent; - } +void tree<T, tree_node_allocator>::sibling_iterator::set_parent_() { + parent_ = 0; + if (this->node == 0) + return; + if (this->node->parent != 0) + parent_ = this->node->parent; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator++() - { - if(this->node) - this->node=this->node->next_sibling; - return *this; - } +typename tree<T, tree_node_allocator>::sibling_iterator & +tree<T, tree_node_allocator>::sibling_iterator::operator++() { + if (this->node) + this->node = this->node->next_sibling; + return *this; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator--() - { - if(this->node) this->node=this->node->prev_sibling; - else { - assert(parent_); - this->node=parent_->last_child; - } - return *this; +typename tree<T, tree_node_allocator>::sibling_iterator & +tree<T, tree_node_allocator>::sibling_iterator::operator--() { + if (this->node) + this->node = this->node->prev_sibling; + else { + assert(parent_); + this->node = parent_->last_child; + } + return *this; } template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::sibling_iterator::operator++(int) - { - sibling_iterator copy = *this; - ++(*this); - return copy; - } +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::sibling_iterator::operator++(int) { + sibling_iterator copy = *this; + ++(*this); + return copy; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator tree<T, tree_node_allocator>::sibling_iterator::operator--(int) - { - sibling_iterator copy = *this; - --(*this); - return copy; - } +typename tree<T, tree_node_allocator>::sibling_iterator +tree<T, tree_node_allocator>::sibling_iterator::operator--(int) { + sibling_iterator copy = *this; + --(*this); + return copy; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator+=(unsigned int num) - { - while(num>0) { - ++(*this); - --num; - } - return (*this); - } +typename tree<T, tree_node_allocator>::sibling_iterator & +tree<T, tree_node_allocator>::sibling_iterator::operator+=(unsigned int num) { + while (num > 0) { + ++(*this); + --num; + } + return (*this); +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::sibling_iterator& tree<T, tree_node_allocator>::sibling_iterator::operator-=(unsigned int num) - { - while(num>0) { - --(*this); - --num; - } - return (*this); - } +typename tree<T, tree_node_allocator>::sibling_iterator & +tree<T, tree_node_allocator>::sibling_iterator::operator-=(unsigned int num) { + while (num > 0) { + --(*this); + --num; + } + return (*this); +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::tree_node *tree<T, tree_node_allocator>::sibling_iterator::range_first() const - { - tree_node *tmp=parent_->first_child; - return tmp; - } +typename tree<T, tree_node_allocator>::tree_node * +tree<T, tree_node_allocator>::sibling_iterator::range_first() const { + tree_node *tmp = parent_->first_child; + return tmp; +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::tree_node *tree<T, tree_node_allocator>::sibling_iterator::range_last() const - { - return parent_->last_child; - } +typename tree<T, tree_node_allocator>::tree_node * +tree<T, tree_node_allocator>::sibling_iterator::range_last() const { + return parent_->last_child; +} // Leaf iterator template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator() - : iterator_base(0), top_node(0) - { - } +tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator() + : iterator_base(0), top_node(0) {} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator(tree_node *tn, tree_node *top) - : iterator_base(tn), top_node(top) - { - } +tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator(tree_node *tn, + tree_node *top) + : iterator_base(tn), top_node(top) {} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator(const iterator_base &other) - : iterator_base(other.node), top_node(0) - { - } +tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator( + const iterator_base &other) + : iterator_base(other.node), top_node(0) {} template <class T, class tree_node_allocator> -tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator(const sibling_iterator& other) - : iterator_base(other.node), top_node(0) - { - if(this->node==0) { - if(other.range_last()!=0) - this->node=other.range_last(); - else - this->node=other.parent_; - ++(*this); - } - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator++() - { - assert(this->node!=0); - if(this->node->first_child!=0) { // current node is no longer leaf (children got added) - while(this->node->first_child) - this->node=this->node->first_child; - } - else { - while(this->node->next_sibling==0) { - if (this->node->parent==0) return *this; - this->node=this->node->parent; - if (top_node != 0 && this->node==top_node) return *this; - } - this->node=this->node->next_sibling; - while(this->node->first_child) - this->node=this->node->first_child; - } - return *this; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator--() - { - assert(this->node!=0); - while (this->node->prev_sibling==0) { - if (this->node->parent==0) return *this; - this->node=this->node->parent; - if (top_node !=0 && this->node==top_node) return *this; - } - this->node=this->node->prev_sibling; - while(this->node->last_child) - this->node=this->node->last_child; - return *this; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::leaf_iterator::operator++(int) - { - leaf_iterator copy = *this; - ++(*this); - return copy; - } - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator tree<T, tree_node_allocator>::leaf_iterator::operator--(int) - { - leaf_iterator copy = *this; - --(*this); - return copy; - } - - -template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator+=(unsigned int num) - { - while(num>0) { - ++(*this); - --num; - } - return (*this); - } +tree<T, tree_node_allocator>::leaf_iterator::leaf_iterator( + const sibling_iterator &other) + : iterator_base(other.node), top_node(0) { + if (this->node == 0) { + if (other.range_last() != 0) + this->node = other.range_last(); + else + this->node = other.parent_; + ++(*this); + } +} template <class T, class tree_node_allocator> -typename tree<T, tree_node_allocator>::leaf_iterator& tree<T, tree_node_allocator>::leaf_iterator::operator-=(unsigned int num) - { - while(num>0) { - --(*this); - --num; - } - return (*this); - } +typename tree<T, tree_node_allocator>::leaf_iterator & +tree<T, tree_node_allocator>::leaf_iterator::operator++() { + assert(this->node != 0); + if (this->node->first_child != + 0) { // current node is no longer leaf (children got added) + while (this->node->first_child) + this->node = this->node->first_child; + } else { + while (this->node->next_sibling == 0) { + if (this->node->parent == 0) + return *this; + this->node = this->node->parent; + if (top_node != 0 && this->node == top_node) + return *this; + } + this->node = this->node->next_sibling; + while (this->node->first_child) + this->node = this->node->first_child; + } + return *this; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator & +tree<T, tree_node_allocator>::leaf_iterator::operator--() { + assert(this->node != 0); + while (this->node->prev_sibling == 0) { + if (this->node->parent == 0) + return *this; + this->node = this->node->parent; + if (top_node != 0 && this->node == top_node) + return *this; + } + this->node = this->node->prev_sibling; + while (this->node->last_child) + this->node = this->node->last_child; + return *this; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator +tree<T, tree_node_allocator>::leaf_iterator::operator++(int) { + leaf_iterator copy = *this; + ++(*this); + return copy; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator +tree<T, tree_node_allocator>::leaf_iterator::operator--(int) { + leaf_iterator copy = *this; + --(*this); + return copy; +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator & +tree<T, tree_node_allocator>::leaf_iterator::operator+=(unsigned int num) { + while (num > 0) { + ++(*this); + --num; + } + return (*this); +} + +template <class T, class tree_node_allocator> +typename tree<T, tree_node_allocator>::leaf_iterator & +tree<T, tree_node_allocator>::leaf_iterator::operator-=(unsigned int num) { + while (num > 0) { + --(*this); + --num; + } + return (*this); +} #endif diff --git a/core/opengate_core/opengate_lib/tree_util.hh b/core/opengate_core/opengate_lib/tree_util.hh index bc69594f0..2706b3e2a 100644 --- a/core/opengate_core/opengate_lib/tree_util.hh +++ b/core/opengate_core/opengate_lib/tree_util.hh @@ -1,13 +1,13 @@ -/* +/* - A collection of miscellaneous utilities that operate on the templated - tree.hh class. + A collection of miscellaneous utilities that operate on the templated + tree.hh class. - Copyright (C) 2001-2009 Kasper Peeters <kasper.peeters@aei.mpg.de> + Copyright (C) 2001-2009 Kasper Peeters <kasper.peeters@aei.mpg.de> - (At the moment this only contains a printing utility, thanks to Linda - Buisman <linda.buisman@studentmail.newcastle.edu.au>) + (At the moment this only contains a printing utility, thanks to Linda + Buisman <linda.buisman@studentmail.newcastle.edu.au>) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -21,72 +21,70 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. - + */ #ifndef tree_util_hh_ #define tree_util_hh_ -#include <iostream> #include "tree.hh" +#include <iostream> namespace kptree { -template<class T> -void print_tree_bracketed(const tree<T>& t, std::ostream& str=std::cout); - -template<class T> -void print_subtree_bracketed(const tree<T>& t, typename tree<T>::iterator iRoot, - std::ostream& str=std::cout); - +template <class T> +void print_tree_bracketed(const tree<T> &t, std::ostream &str = std::cout); +template <class T> +void print_subtree_bracketed(const tree<T> &t, typename tree<T>::iterator iRoot, + std::ostream &str = std::cout); // Iterate over all roots (the head) and print each one on a new line // by calling printSingleRoot. -template<class T> -void print_tree_bracketed(const tree<T>& t, std::ostream& str) - { - int headCount = t.number_of_siblings(t.begin()); - int headNum = 0; - for(typename tree<T>::sibling_iterator iRoots = t.begin(); iRoots != t.end(); ++iRoots, ++headNum) { - print_subtree_bracketed(t,iRoots,str); - if (headNum != headCount) { - str << std::endl; - } - } - } - +template <class T> +void print_tree_bracketed(const tree<T> &t, std::ostream &str) { + int headCount = t.number_of_siblings(t.begin()); + int headNum = 0; + for (typename tree<T>::sibling_iterator iRoots = t.begin(); iRoots != t.end(); + ++iRoots, ++headNum) { + print_subtree_bracketed(t, iRoots, str); + if (headNum != headCount) { + str << std::endl; + } + } +} // Print everything under this root in a flat, bracketed structure. -template<class T> -void print_subtree_bracketed(const tree<T>& t, typename tree<T>::iterator iRoot, std::ostream& str) - { - if(t.empty()) return; - if (t.number_of_children(iRoot) == 0) { - str << *iRoot; - } - else { - // parent - str << *iRoot; - str << "("; - // child1, ..., childn - int siblingCount = t.number_of_siblings(t.begin(iRoot)); - int siblingNum; - typename tree<T>::sibling_iterator iChildren; - for (iChildren = t.begin(iRoot), siblingNum = 0; iChildren != t.end(iRoot); ++iChildren, ++siblingNum) { - // recursively print child - print_subtree_bracketed(t,iChildren,str); - // comma after every child except the last one - if (siblingNum != siblingCount ) { - str << ", "; - } - } - str << ")"; - } - } - +template <class T> +void print_subtree_bracketed(const tree<T> &t, typename tree<T>::iterator iRoot, + std::ostream &str) { + if (t.empty()) + return; + if (t.number_of_children(iRoot) == 0) { + str << *iRoot; + } else { + // parent + str << *iRoot; + str << "("; + // child1, ..., childn + int siblingCount = t.number_of_siblings(t.begin(iRoot)); + int siblingNum; + typename tree<T>::sibling_iterator iChildren; + for (iChildren = t.begin(iRoot), siblingNum = 0; iChildren != t.end(iRoot); + ++iChildren, ++siblingNum) { + // recursively print child + print_subtree_bracketed(t, iChildren, str); + // comma after every child except the last one + if (siblingNum != siblingCount) { + str << ", "; + } + } + str << ")"; + } } +} // namespace kptree + #endif diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 3b3cb2fdf..65b21d88b 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -610,8 +610,8 @@ class LastVertexInteractionSplittingActor(ActorBase,g4.GateLastVertexInteraction "batch_size": ( 1, { - "doc": "Defines a batch of number of processes to regenerate, calculated as batch_size * splitting_factor. The optimal value depends on the collimation setup; for example, a batch_size of 10 works well for LINAC - + "doc": "Defines a batch of number of processes to regenerate, calculated as batch_size * splitting_factor. The optimal value depends on the collimation setup; for example, a batch_size of 10 works well for LINAC + configurations.", }, ), @@ -634,7 +634,7 @@ def __initcpp__(self): "PostUserTrackingAction", "EndOfEventAction"}) - + def initialize(self): ActorBase.initialize(self) self.InitializeUserInput(self.user_info) diff --git a/opengate/managers.py b/opengate/managers.py index 4650fe84d..f2511c7e9 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -111,7 +111,7 @@ "SimulationStatisticsActor": SimulationStatisticsActor, "KillActor": KillActor, "KillAccordingProcessesActor": KillAccordingProcessesActor, - "KillNonInteractingParticleActor":KillNonInteractingParticleActor, + "KillNonInteractingParticleActor": KillNonInteractingParticleActor, "BremSplittingActor": BremSplittingActor, "ComptSplittingActor": ComptSplittingActor, "DigitizerAdderActor": DigitizerAdderActor, @@ -123,7 +123,7 @@ "DigitizerEnergyWindowsActor": DigitizerEnergyWindowsActor, "DigitizerHitsCollectionActor": DigitizerHitsCollectionActor, "PhaseSpaceActor": PhaseSpaceActor, - "LastVertexInteractionSplittingActor":LastVertexInteractionSplittingActor, + "LastVertexInteractionSplittingActor": LastVertexInteractionSplittingActor, } @@ -1189,32 +1189,30 @@ def dump_volume_tree(self): # FIXME: pre should be used directly but cannot be encoded correctly in Windows s += len(pre) * " " + f"{node.name}\n" return s - + def get_volume_tree(self): - return self.volume_tree_root + return self.volume_tree_root def print_volume_tree(self): print(self.dump_volume_tree()) - def dump_volume_types(self): s = "" for vt in self.volume_types: s += f"{vt} " return s - + def get_volume_tree(self): return self.volume_tree_root - - + def print_volume_types(self): print(self.dump_volume_types()) def dump_material_database_names(self): return list(self.material_database.filenames) - + def get_volume_tree(self): - return (self.volume_tree_root) + return self.volume_tree_root def get_volume_tree(self): return self.volume_tree_root diff --git a/opengate/tests/src/test077_kill_interacting_particles.py b/opengate/tests/src/test077_kill_interacting_particles.py index b36776d8f..fc342cdf5 100644 --- a/opengate/tests/src/test077_kill_interacting_particles.py +++ b/opengate/tests/src/test077_kill_interacting_particles.py @@ -11,8 +11,6 @@ def test077_test(entry_data, exit_data_1, exit_data_2): - - liste_ekin = [] liste_evtID = [] liste_trackID = [] @@ -29,7 +27,6 @@ def test077_test(entry_data, exit_data_1, exit_data_2): Ekin_entry = entry_data["KineticEnergy"][i] Ekin_exit = exit_data_1["KineticEnergy"][j] - if (TID_entry != TID_exit) or (Ekin_exit != Ekin_entry): liste_ekin.append(exit_data_1["KineticEnergy"][j]) liste_evtID.append(exit_data_1["EventID"][j]) diff --git a/opengate/tests/src/test082_kill_non_interacting_particles.py b/opengate/tests/src/test082_kill_non_interacting_particles.py index cf433209e..73c439179 100644 --- a/opengate/tests/src/test082_kill_non_interacting_particles.py +++ b/opengate/tests/src/test082_kill_non_interacting_particles.py @@ -131,7 +131,7 @@ def test082_test(entry_data, exit_data_1, exit_data_2): entry_phase_space.mother = actor_box.name entry_phase_space.size = [0.6 * m, 0.6 * m, 1 * nm] entry_phase_space.material = "G4_Galactic" - entry_phase_space.translation = [0, 0, 0.255* m] + entry_phase_space.translation = [0, 0, 0.255 * m] entry_phase_space.color = [0.5, 0.9, 0.3, 1] exit_phase_space_1 = sim.add_volume("Box", "exit_phase_space_1") diff --git a/opengate/tests/src/test083_kill_according_processes.py b/opengate/tests/src/test083_kill_according_processes.py index 8cab3494e..83cdcb7c8 100644 --- a/opengate/tests/src/test083_kill_according_processes.py +++ b/opengate/tests/src/test083_kill_according_processes.py @@ -11,25 +11,30 @@ """ The kill actor according processes is able to kill if a process occurred or if a process did not occured. -To verify it here, I shot 6 MeV gamma with high cut on electrons in a tungsten target. It means that I have 4 types +To verify it here, I shot 6 MeV gamma with high cut on electrons in a tungsten target. It means that I have 4 types of photons : without any interaction, just compton photons, conv and therefore compton, and brem (with or without cmpton) With a interdiction for photons to exit the volume if they did not undergo a conv, combined with a kill if eBrem occured. -I normally just have photons created by annihilation which undergo or not a compton. The test is now straightforward: -if the number of detected photons is equal to the number of detected photons and created by conv, and if in average, the +I normally just have photons created by annihilation which undergo or not a compton. The test is now straightforward: +if the number of detected photons is equal to the number of detected photons and created by conv, and if in average, the energy at the vertex is different of the measured energy, the actor is working. """ + + def test083_test(df): - photon_array = df[df["PDGCode"] ==22] - if len(photon_array) == len(photon_array[photon_array["TrackCreatorProcess"] == "annihil"]): - if np.mean(photon_array["KineticEnergy"]) != np.mean(photon_array["TrackVertexKineticEnergy"]): + photon_array = df[df["PDGCode"] == 22] + if len(photon_array) == len( + photon_array[photon_array["TrackCreatorProcess"] == "annihil"] + ): + if np.mean(photon_array["KineticEnergy"]) != np.mean( + photon_array["TrackVertexKineticEnergy"] + ): return True return False - if __name__ == "__main__": paths = utility.get_default_test_paths(__file__) output_path = paths.output @@ -76,8 +81,8 @@ def test083_test(df): source.particle = "gamma" source.position.type = "box" source.mother = world.name - source.position.size = [1*nm,1*nm,1*nm] - source.position.translation = [0, 0, 10*cm + 1 * mm] + source.position.size = [1 * nm, 1 * nm, 1 * nm] + source.position.translation = [0, 0, 10 * cm + 1 * mm] source.direction.type = "momentum" source.direction_relative_to_attached_volume = True # source1.direction.focus_point = [0*cm, 0*cm, -5 *cm] @@ -87,36 +92,41 @@ def test083_test(df): source.n = 100000 tungsten = sim.add_volume("Box", "tungsten_box") - tungsten.size = [4 * cm, 4 * cm, 20 * cm] + tungsten.size = [4 * cm, 4 * cm, 20 * cm] tungsten.material = "Tungsten" tungsten.mother = world.name tungsten.color = [0.5, 0.9, 0.3, 1] kill_proc_act = sim.add_actor("KillAccordingProcessesActor", "kill_proc_act") kill_proc_act.attached_to = tungsten.name - kill_proc_act.processes_to_kill_if_occurence=["eBrem"] + kill_proc_act.processes_to_kill_if_occurence = ["eBrem"] kill_proc_act.processes_to_kill_if_no_occurence = ["conv"] - kill_plan = sim.add_volume("Box", "plan") - kill_plan.size = [4 * cm, 4 * cm, 1*nm] - kill_plan.translation = [0,0, - tungsten.size[2]/2 - kill_plan.size[2]] - + kill_plan = sim.add_volume("Box", "plan") + kill_plan.size = [4 * cm, 4 * cm, 1 * nm] + kill_plan.translation = [0, 0, -tungsten.size[2] / 2 - kill_plan.size[2]] kill_actor = sim.add_actor("KillActor", "kill_actor_plan") kill_actor.attached_to = kill_plan.name phsp_sphere = sim.add_volume("Sphere", "phsp_sphere") - phsp_sphere.mother =world.name - phsp_sphere.rmin = 15 *cm - phsp_sphere.rmax = 15 * cm +1*nm - + phsp_sphere.mother = world.name + phsp_sphere.rmin = 15 * cm + phsp_sphere.rmax = 15 * cm + 1 * nm sim.output_dir = output_path phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace") phsp.attached_to = phsp_sphere.name - phsp.attributes = ["EventID", "TrackID", "KineticEnergy","TrackVertexKineticEnergy","TrackCreatorProcess","PDGCode"] + phsp.attributes = [ + "EventID", + "TrackID", + "KineticEnergy", + "TrackVertexKineticEnergy", + "TrackCreatorProcess", + "PDGCode", + ] name_phsp = "test083_" + phsp.name + ".root" - phsp.output_filename= name_phsp + phsp.output_filename = name_phsp sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" sim.physics_manager.enable_decay = False @@ -127,10 +137,7 @@ def test083_test(df): sim.g4_commands_before_init.append(s) sim.run() # - phsp = uproot.open( - str(output_path) - + "/test083_PhaseSpace.root" - + ":PhaseSpace") + phsp = uproot.open(str(output_path) + "/test083_PhaseSpace.root" + ":PhaseSpace") df = phsp.arrays() is_ok = test083_test(df) diff --git a/opengate/tests/src/test084_last_vertex_splittting.py b/opengate/tests/src/test084_last_vertex_splittting.py index 956520b59..390c5d1ae 100755 --- a/opengate/tests/src/test084_last_vertex_splittting.py +++ b/opengate/tests/src/test084_last_vertex_splittting.py @@ -8,63 +8,81 @@ from opengate.tests import utility - def validation_test(arr_ref, arr_data, nb_split): arr_ref = arr_ref[arr_ref["ParticleName"] == "gamma"] arr_data = arr_data[arr_data["ParticleName"] == "gamma"] - weight_data = np.round(np.mean(arr_data["Weight"]),4) + weight_data = np.round(np.mean(arr_data["Weight"]), 4) bool_weight = False - print("Weight mean is equal to",weight_data, "and need to be equal to",1/nb_split) - if weight_data == 1/nb_split: + print( + "Weight mean is equal to", weight_data, "and need to be equal to", 1 / nb_split + ) + if weight_data == 1 / nb_split: bool_weight = True - bool_events = False - sigma = np.sqrt((len(arr_data["KineticEnergy"])/nb_split))*nb_split + sigma = np.sqrt((len(arr_data["KineticEnergy"]) / nb_split)) * nb_split nb_events_ref = len(arr_ref["KineticEnergy"]) nb_events_data = len(arr_data["KineticEnergy"]) - print("Reference counts number:",nb_events_ref) + print("Reference counts number:", nb_events_ref) print("Biased counts number:", nb_events_data) - if nb_events_data - 4*sigma <= nb_events_ref <= nb_events_data + 4*sigma: - bool_events =True - - keys = ["KineticEnergy", "PreDirection_X","PreDirection_Y","PreDirection_Z","PrePosition_X","PrePosition_Y"] + if nb_events_data - 4 * sigma <= nb_events_ref <= nb_events_data + 4 * sigma: + bool_events = True + + keys = [ + "KineticEnergy", + "PreDirection_X", + "PreDirection_Y", + "PreDirection_Z", + "PrePosition_X", + "PrePosition_Y", + ] bool_distrib = True - for key in keys : + for key in keys: ref = arr_ref[key] data = arr_data[key] mean_ref = np.mean(ref) mean_data = np.mean(data) - std_dev_ref = np.std(ref,ddof =1) - std_dev_data = np.std(data,ddof =1) - - std_err_ref = std_dev_ref/np.sqrt(len(ref)) - std_err_data= std_dev_data/(nb_split * np.sqrt(len(data)/nb_split)) + std_dev_ref = np.std(ref, ddof=1) + std_dev_data = np.std(data, ddof=1) - print(key,"mean ref value:", np.round(mean_ref,3),"+-",np.round(std_err_ref,3)) - print(key,"mean data value:", np.round(mean_data,3),"+-",np.round(std_err_data,3)) + std_err_ref = std_dev_ref / np.sqrt(len(ref)) + std_err_data = std_dev_data / (nb_split * np.sqrt(len(data) / nb_split)) + print( + key, + "mean ref value:", + np.round(mean_ref, 3), + "+-", + np.round(std_err_ref, 3), + ) + print( + key, + "mean data value:", + np.round(mean_data, 3), + "+-", + np.round(std_err_data, 3), + ) - if (mean_data - 4 * np.sqrt(std_err_data**2 + std_err_ref**2) > mean_ref) or (mean_data + 4 * np.sqrt(std_err_data**2 + std_err_ref**2) <mean_ref): + if ( + mean_data - 4 * np.sqrt(std_err_data**2 + std_err_ref**2) > mean_ref + ) or (mean_data + 4 * np.sqrt(std_err_data**2 + std_err_ref**2) < mean_ref): bool_distrib = False if bool_distrib and bool_events and bool_weight: return True - else : + else: return False - - if __name__ == "__main__": for i in range(2): - if i == 0 : + if i == 0: bias = False - else : + else: bias = True paths = utility.get_default_test_paths( __file__, "test084_last_vertex_splitting", output_folder="test084" @@ -113,7 +131,7 @@ def validation_test(arr_ref, arr_data, nb_split): W_tubs.mother = world.name W_tubs.rmin = 0 - W_tubs.rmax = 0.4*cm + W_tubs.rmax = 0.4 * cm W_tubs.dz = 0.05 * m W_tubs.color = [0.8, 0.2, 0.1, 1] angle_x = 45 @@ -125,31 +143,30 @@ def validation_test(arr_ref, arr_data, nb_split): ).as_matrix() W_tubs.rotation = rotation - if bias : - ###### Last vertex Splitting ACTOR ######### + if bias: + ###### Last vertex Splitting ACTOR ######### nb_split = 10 - vertex_splitting_actor = sim.add_actor("LastVertexInteractionSplittingActor", "vertexSplittingW") + vertex_splitting_actor = sim.add_actor( + "LastVertexInteractionSplittingActor", "vertexSplittingW" + ) vertex_splitting_actor.attached_to = W_tubs.name vertex_splitting_actor.splitting_factor = nb_split vertex_splitting_actor.angular_kill = True - vertex_splitting_actor.vector_director = [0,0,-1] - vertex_splitting_actor.max_theta = 90*deg + vertex_splitting_actor.vector_director = [0, 0, -1] + vertex_splitting_actor.max_theta = 90 * deg vertex_splitting_actor.batch_size = 10 - plan = sim.add_volume("Box", "plan_phsp") plan.material = "G4_Galactic" - plan.size = [5*cm,5*cm,1*nm] - plan.translation = [0,0,-1*cm] - + plan.size = [5 * cm, 5 * cm, 1 * nm] + plan.translation = [0, 0, -1 * cm] ####### gamma source ########### source = sim.add_source("GenericSource", "source1") source.particle = "gamma" source.n = 100000 - if bias : - source.n = source.n/nb_split - + if bias: + source.n = source.n / nb_split source.position.type = "sphere" source.position.radius = 1 * nm @@ -157,15 +174,15 @@ def validation_test(arr_ref, arr_data, nb_split): # source.direction.momentum = [0,0,-1] source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) source.energy.type = "mono" - source.energy.mono = 4 * MeV + source.energy.mono = 4 * MeV # ###### LastVertexSource ############# - if bias : + if bias: source_0 = sim.add_source("LastVertexSource", "source_vertex") source_0.n = 1 ####### PHASE SPACE ACTOR ############## - sim.output_dir =paths.output + sim.output_dir = paths.output phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") phsp_actor.attached_to = plan.name phsp_actor.attributes = [ @@ -178,12 +195,11 @@ def validation_test(arr_ref, arr_data, nb_split): "PrePosition", "TrackCreatorProcess", ] - if bias : - phsp_actor.output_filename ="test084_output_data_last_vertex_biased.root" - else : + if bias: + phsp_actor.output_filename = "test084_output_data_last_vertex_biased.root" + else: phsp_actor.output_filename = "test084_output_data_last_vertex_ref.root" - s = sim.add_actor("SimulationStatisticsActor", "Stats") s.track_types_flag = True sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" @@ -197,9 +213,6 @@ def validation_test(arr_ref, arr_data, nb_split): output = sim.run(True) print(s) - - - f_data = uproot.open(paths.output / "test084_output_data_last_vertex_biased.root") f_ref_data = uproot.open(paths.output / "test084_output_data_last_vertex_ref.root") arr_data = f_data["PhaseSpace"].arrays() @@ -207,4 +220,3 @@ def validation_test(arr_ref, arr_data, nb_split): # # is_ok = validation_test(arr_ref_data, arr_data, nb_split) utility.test_ok(is_ok) - diff --git a/opengate/tests/src/test084_last_vertex_splittting_angular_kill.py b/opengate/tests/src/test084_last_vertex_splittting_angular_kill.py index c9f058618..d1fa56cf2 100755 --- a/opengate/tests/src/test084_last_vertex_splittting_angular_kill.py +++ b/opengate/tests/src/test084_last_vertex_splittting_angular_kill.py @@ -8,28 +8,29 @@ from opengate.tests import utility - -def validation_test(arr_data,vector_director,max_theta): +def validation_test(arr_data, vector_director, max_theta): arr_data = arr_data[arr_data["ParticleName"] == "gamma"] - qt_mvt_data = arr_data[["PreDirection_X","PreDirection_Y","PreDirection_Z"]] - mom_data = np.zeros((len(qt_mvt_data["PreDirection_X"]),3)) - mom_data[:,0] += np.array(qt_mvt_data["PreDirection_X"]) + qt_mvt_data = arr_data[["PreDirection_X", "PreDirection_Y", "PreDirection_Z"]] + mom_data = np.zeros((len(qt_mvt_data["PreDirection_X"]), 3)) + mom_data[:, 0] += np.array(qt_mvt_data["PreDirection_X"]) mom_data[:, 1] += np.array(qt_mvt_data["PreDirection_Y"]) mom_data[:, 2] += np.array(qt_mvt_data["PreDirection_Z"]) - l_theta = np.zeros(len(mom_data[:,0])) + l_theta = np.zeros(len(mom_data[:, 0])) for i in range(len(l_theta)): theta = np.arccos(mom_data[i].dot(vector_director)) l_theta[i] = theta - print('Number of particles with an angle higher than max_theta:',len(l_theta[l_theta > max_theta])) + print( + "Number of particles with an angle higher than max_theta:", + len(l_theta[l_theta > max_theta]), + ) if len(l_theta[l_theta > max_theta]) == 0: return True else: return False - if __name__ == "__main__": bias = True paths = utility.get_default_test_paths( @@ -79,7 +80,7 @@ def validation_test(arr_data,vector_director,max_theta): W_tubs.mother = world.name W_tubs.rmin = 0 - W_tubs.rmax = 0.4*cm + W_tubs.rmax = 0.4 * cm W_tubs.dz = 0.05 * m W_tubs.color = [0.8, 0.2, 0.1, 1] angle_x = 45 @@ -91,36 +92,33 @@ def validation_test(arr_data,vector_director,max_theta): ).as_matrix() W_tubs.rotation = rotation - if bias : - ###### Last vertex Splitting ACTOR ######### + if bias: + ###### Last vertex Splitting ACTOR ######### nb_split = 10 - vertex_splitting_actor = sim.add_actor("LastVertexInteractionSplittingActor", "vertexSplittingW") + vertex_splitting_actor = sim.add_actor( + "LastVertexInteractionSplittingActor", "vertexSplittingW" + ) vertex_splitting_actor.attached_to = W_tubs.name vertex_splitting_actor.splitting_factor = nb_split vertex_splitting_actor.angular_kill = True - vertex_splitting_actor.vector_director = [0,0,-1] - vertex_splitting_actor.max_theta = 15*deg + vertex_splitting_actor.vector_director = [0, 0, -1] + vertex_splitting_actor.max_theta = 15 * deg vertex_splitting_actor.batch_size = 10 - plan = sim.add_volume("Box", "plan_phsp") plan.material = "G4_Galactic" - plan.size = [5*cm,5*cm,1*nm] - plan.translation = [0,0,-1*cm] + plan.size = [5 * cm, 5 * cm, 1 * nm] + plan.translation = [0, 0, -1 * cm] - if bias : + if bias: vector_director = np.array(vertex_splitting_actor.vector_director) - - - ####### gamma source ########### source = sim.add_source("GenericSource", "source1") source.particle = "gamma" source.n = 100000 - if bias : - source.n = source.n/nb_split - + if bias: + source.n = source.n / nb_split source.position.type = "sphere" source.position.radius = 1 * nm @@ -128,15 +126,15 @@ def validation_test(arr_data,vector_director,max_theta): # source.direction.momentum = [0,0,-1] source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) source.energy.type = "mono" - source.energy.mono = 4 * MeV + source.energy.mono = 4 * MeV ###### LastVertexSource ############# - if bias : + if bias: source_0 = sim.add_source("LastVertexSource", "source_vertex") source_0.n = 1 ####### PHASE SPACE ACTOR ############## - sim.output_dir =paths.output + sim.output_dir = paths.output phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") phsp_actor.attached_to = plan.name phsp_actor.attributes = [ @@ -149,8 +147,8 @@ def validation_test(arr_data,vector_director,max_theta): "PrePosition", "TrackCreatorProcess", ] - if bias : - phsp_actor.output_filename ="test084_output_data_last_vertex_angular_kill.root" + if bias: + phsp_actor.output_filename = "test084_output_data_last_vertex_angular_kill.root" s = sim.add_actor("SimulationStatisticsActor", "Stats") s.track_types_flag = True @@ -165,8 +163,9 @@ def validation_test(arr_data,vector_director,max_theta): output = sim.run() print(s) - f_data = uproot.open(paths.output / "test084_output_data_last_vertex_angular_kill.root") + f_data = uproot.open( + paths.output / "test084_output_data_last_vertex_angular_kill.root" + ) arr_data = f_data["PhaseSpace"].arrays() - is_ok = validation_test(arr_data, vector_director,vertex_splitting_actor.max_theta) + is_ok = validation_test(arr_data, vector_director, vertex_splitting_actor.max_theta) utility.test_ok(is_ok) - From d0c5a7203044d778ffe0654a636f286d256d3b9e Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Sun, 17 Nov 2024 22:15:15 +0100 Subject: [PATCH 54/82] TLE correction --- core/opengate_core/opengate_lib/GateTLEDoseActor.cpp | 5 +++-- opengate/actors/miscactors.py | 4 +--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp b/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp index 3c22c34eb..9dfe775ea 100644 --- a/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp +++ b/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp @@ -39,11 +39,12 @@ void GateTLEDoseActor::InitializeUserInput(py::dict &user_info) { void GateTLEDoseActor::PreUserTrackingAction(const G4Track *track) { auto &l = fThreadLocalData.Get(); + auto track_id = track->GetTrackID(); if (track->GetDefinition()->GetParticleName() == "gamma") { l.fIsTLEGamma = false; - l.fLastTrackId = 1; + //l.fLastTrackId = 1; + l.fLastTrackId = track_id; } else { - auto track_id = track->GetTrackID(); if (track_id < l.fLastTrackId) { // if the track_id is lower than the lastTrack, it means all the following // tracks will be without TLE mode (tracks are processed LIFO), so we diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 65b21d88b..86b314650 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -610,9 +610,7 @@ class LastVertexInteractionSplittingActor(ActorBase,g4.GateLastVertexInteraction "batch_size": ( 1, { - "doc": "Defines a batch of number of processes to regenerate, calculated as batch_size * splitting_factor. The optimal value depends on the collimation setup; for example, a batch_size of 10 works well for LINAC - - configurations.", + "doc": "Defines a batch of number of processes to regenerate, calculated as batch_size * splitting_factor. The optimal value depends on the collimation setup; for example, a batch_size of 10 works well for LINAC configurations.", }, ), From 12eaa31f8fafebb8e81bd1ee67dfc938f8c6c6f2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 17 Nov 2024 21:15:39 +0000 Subject: [PATCH 55/82] [pre-commit.ci] Automatic python and c++ formatting --- .../opengate_lib/GateTLEDoseActor.cpp | 2 +- core/opengate_core/opengate_lib/tree.hh | 14 +-- opengate/actors/miscactors.py | 100 ++++++++++-------- 3 files changed, 63 insertions(+), 53 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp b/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp index 9dfe775ea..1c5f73ba5 100644 --- a/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp +++ b/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp @@ -42,7 +42,7 @@ void GateTLEDoseActor::PreUserTrackingAction(const G4Track *track) { auto track_id = track->GetTrackID(); if (track->GetDefinition()->GetParticleName() == "gamma") { l.fIsTLEGamma = false; - //l.fLastTrackId = 1; + // l.fLastTrackId = 1; l.fLastTrackId = track_id; } else { if (track_id < l.fLastTrackId) { diff --git a/core/opengate_core/opengate_lib/tree.hh b/core/opengate_core/opengate_lib/tree.hh index ef78833de..17e501f48 100644 --- a/core/opengate_core/opengate_lib/tree.hh +++ b/core/opengate_core/opengate_lib/tree.hh @@ -79,7 +79,8 @@ public: // assert(1==0); // std::ostringstream str; // std::cerr << boost::stacktrace::stacktrace() << - //std::endl; str << boost::stacktrace::stacktrace(); stacktrace=str.str(); + // std::endl; str << boost::stacktrace::stacktrace(); + // stacktrace=str.str(); } // virtual const char *what() const noexcept override @@ -579,11 +580,11 @@ private: // class iterator_base_less { // public: // bool operator()(const typename tree<T, -//tree_node_allocator>::iterator_base& one, const typename tree<T, -//tree_node_allocator>::iterator_base& two) const +// tree_node_allocator>::iterator_base& one, +// const typename tree<T, tree_node_allocator>::iterator_base& two) const // { // txtout << "operatorclass<" << one.node < two.node << -//std::endl; return one.node < two.node; +// std::endl; return one.node < two.node; // } // }; @@ -609,7 +610,8 @@ private: // // template <class T, class tree_node_allocator> // bool operator>(const typename tree<T, tree_node_allocator>::iterator_base& -// one, const typename tree<T, tree_node_allocator>::iterator_base& two) +// one, const typename tree<T, +// tree_node_allocator>::iterator_base& two) // { // txtout << "operator> " << one.node < two.node << std::endl; // if(one.node > two.node) return true; @@ -3202,7 +3204,7 @@ tree<T, tree_node_allocator>::fixed_depth_iterator::operator--() { // this->node=this->node->prev_sibling; // assert(this->node!=0); // if(this->node->parent==0 && this->node->prev_sibling==0) // head - //element this->node=0; + // element this->node=0; // } // else { // tree_node *par=this->node->parent; diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 86b314650..a430e6324 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -11,6 +11,7 @@ from ..base import process_cls from anytree import Node, RenderTree + def _setter_hook_stats_actor_output_filename(self, output_filename): # By default, write_to_disk is False. # However, if user actively sets the output_filename @@ -264,7 +265,6 @@ def EndSimulationAction(self): self.user_output.stats.write_data_if_requested() - """ It is feasible to get callback every Run, Event, Track, Step in the python side. However, it is VERY time consuming. For SteppingAction, expect large performance drop. @@ -277,13 +277,14 @@ def SteppingAction(self, step, touchable): g4.GateSimulationStatisticsActor.SteppingAction(self, step, touchable) do_something() """ + + class ActorOutputKillAccordingProcessesActor(ActorOutputBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.number_of_killed_particles = 0 - def get_processed_output(self): d = {} d["particles killed"] = self.number_of_killed_particles @@ -294,8 +295,7 @@ def __str__(self): for k, v in self.get_processed_output().items(): s = k + ": " + str(v) s += "\n" - return(s) - + return s class KillAccordingProcessesActor(ActorBase, g4.GateKillAccordingProcessesActor): @@ -311,7 +311,7 @@ class KillAccordingProcessesActor(ActorBase, g4.GateKillAccordingProcessesActor) { "doc": "If a particle succeded to exit the volume where the actor is attached without undergoing one the processes on the list, the particles and its potential secondaries are killed", }, - ) + ), } """ @@ -324,7 +324,9 @@ def __init__(self, *args, **kwargs): ActorBase.__init__(self, *args, **kwargs) - self._add_user_output(ActorOutputKillAccordingProcessesActor, "kill_interacting_particles") + self._add_user_output( + ActorOutputKillAccordingProcessesActor, "kill_interacting_particles" + ) self.__initcpp__() self.list_of_volume_name = [] self.number_of_killed_particles = 0 @@ -332,7 +334,12 @@ def __init__(self, *args, **kwargs): def __initcpp__(self): g4.GateKillAccordingProcessesActor.__init__(self, self.user_info) self.AddActions( - {"BeginOfRunAction","PreUserTrackingAction", "SteppingAction","EndSimulationAction"} + { + "BeginOfRunAction", + "PreUserTrackingAction", + "SteppingAction", + "EndSimulationAction", + } ) def initialize(self): @@ -349,25 +356,22 @@ def initialize(self): volume_name = node.mother self.list_of_volume_name.append(volume_name) self.fListOfVolumeAncestor = self.list_of_volume_name - common_elements = set(self.processes_to_kill_if_no_occurence).intersection(self.processes_to_kill_if_occurence) + common_elements = set(self.processes_to_kill_if_no_occurence).intersection( + self.processes_to_kill_if_occurence + ) if len(common_elements) > 0: - fatal( - "You put the same process on both lists !" - ) - - - - - + fatal("You put the same process on both lists !") def EndSimulationAction(self): - self.user_output.kill_interacting_particles.number_of_killed_particles = self.number_of_killed_particles - + self.user_output.kill_interacting_particles.number_of_killed_particles = ( + self.number_of_killed_particles + ) def __str__(self): s = self.user_output["kill_non_interacting_particles"].__str__() return s + class KillActor(ActorBase, g4.GateKillActor): def __init__(self, *args, **kwargs): @@ -396,7 +400,6 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.number_of_killed_particles = 0 - def get_processed_output(self): d = {} d["particles killed"] = self.number_of_killed_particles @@ -407,12 +410,12 @@ def __str__(self): for k, v in self.get_processed_output().items(): s = k + ": " + str(v) s += "\n" - return(s) - - + return s -class KillNonInteractingParticleActor(ActorBase, g4.GateKillNonInteractingParticleActor): +class KillNonInteractingParticleActor( + ActorBase, g4.GateKillNonInteractingParticleActor +): """ If a particle, not generated or generated within the volume at which our actor is attached, crosses the volume without interaction, the particle is killed. Warning : this actor being based on energy measurement, Rayleigh photon @@ -421,7 +424,9 @@ class KillNonInteractingParticleActor(ActorBase, g4.GateKillNonInteractingPartic def __init__(self, *args, **kwargs): ActorBase.__init__(self, *args, **kwargs) - self._add_user_output(ActorOutputKillNonInteractingParticleActor, "kill_non_interacting_particles") + self._add_user_output( + ActorOutputKillNonInteractingParticleActor, "kill_non_interacting_particles" + ) self.__initcpp__() self.list_of_volume_name = [] self.number_of_killed_particles = 0 @@ -429,7 +434,12 @@ def __init__(self, *args, **kwargs): def __initcpp__(self): g4.GateKillNonInteractingParticleActor.__init__(self, self.user_info) self.AddActions( - {"StartSimulationAction","PreUserTrackingAction", "SteppingAction","EndOfSimulationAction"} + { + "StartSimulationAction", + "PreUserTrackingAction", + "SteppingAction", + "EndOfSimulationAction", + } ) def initialize(self): @@ -447,10 +457,10 @@ def initialize(self): self.list_of_volume_name.append(volume_name) self.fListOfVolumeAncestor = self.list_of_volume_name - def EndSimulationAction(self): - self.user_output.kill_non_interacting_particles.number_of_killed_particles = self.number_of_killed_particles - + self.user_output.kill_non_interacting_particles.number_of_killed_particles = ( + self.number_of_killed_particles + ) def __str__(self): s = self.user_output["kill_non_interacting_particles"].__str__() @@ -464,7 +474,6 @@ def _setter_hook_particles(self, value): return list(value) - class SplittingActorBase(ActorBase): # hints for IDE splitting_factor: int @@ -566,10 +575,12 @@ def initialize(self): self.InitializeCpp() -class LastVertexInteractionSplittingActor(ActorBase,g4.GateLastVertexInteractionSplittingActor): +class LastVertexInteractionSplittingActor( + ActorBase, g4.GateLastVertexInteractionSplittingActor +): """This splitting actor proposes an interaction splitting at the last particle vertex before the exit - of the biased volume. This actor can be usefull for application where collimation are important, - such as in medical LINAC (Linear Accelerator) simulations or radiation shielding. + of the biased volume. This actor can be usefull for application where collimation are important, + such as in medical LINAC (Linear Accelerator) simulations or radiation shielding. """ user_info_defaults = { @@ -579,22 +590,18 @@ class LastVertexInteractionSplittingActor(ActorBase,g4.GateLastVertexInteraction "doc": "Defines the number of particles exiting at each split process. Unlike other split actors, this splitting factor counts particles that actually exit, not just those generated.", }, ), - "angular_kill": ( False, { "doc": "If enabled, particles with momentum outside a specified angular range are killed.", }, ), - "max_theta": ( 90 * g4_units.deg, { "doc": "Defines the maximum angle (in degrees) from a central axis within which particles are retained. Particles with momentum beyond this angle are removed. The angular range spans from 0 to max_theta, measured from the vector_director", }, ), - - "vector_director": ( [0, 0, 1], { @@ -613,11 +620,8 @@ class LastVertexInteractionSplittingActor(ActorBase,g4.GateLastVertexInteraction "doc": "Defines a batch of number of processes to regenerate, calculated as batch_size * splitting_factor. The optimal value depends on the collimation setup; for example, a batch_size of 10 works well for LINAC configurations.", }, ), - - } - def __init__(self, *args, **kwargs): ActorBase.__init__(self, *args, **kwargs) self.__initcpp__() @@ -625,13 +629,16 @@ def __init__(self, *args, **kwargs): def __initcpp__(self): g4.GateLastVertexInteractionSplittingActor.__init__(self, {"name": self.name}) - self.AddActions({"BeginOfRunAction", - "BeginOfEventAction", - "PreUserTrackingAction", - "SteppingAction", - "PostUserTrackingAction", - "EndOfEventAction"}) - + self.AddActions( + { + "BeginOfRunAction", + "BeginOfEventAction", + "PreUserTrackingAction", + "SteppingAction", + "PostUserTrackingAction", + "EndOfEventAction", + } + ) def initialize(self): ActorBase.initialize(self) @@ -648,6 +655,7 @@ def initialize(self): self.list_of_volume_name.append(volume_name) self.fListOfVolumeAncestor = self.list_of_volume_name + class BremSplittingActor(SplittingActorBase, g4.GateBOptrBremSplittingActor): # hints for IDE processes: list From ed907cdb6fa9761def60f16c4b0b74c1372c19a5 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 19 Nov 2024 10:09:57 +0100 Subject: [PATCH 56/82] actor adaptation regarding latest modif --- .../GateLastVertexInteractionSplittingActor.cpp | 11 +++++------ .../GateLastVertexInteractionSplittingActor.h | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index 23f24c1d1..5a2c73d09 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -56,10 +56,10 @@ GateLastVertexInteractionSplittingActor:: GateLastVertexInteractionSplittingActor(py::dict &user_info) : GateVActor(user_info, false) {} -void GateLastVertexInteractionSplittingActor::InitializeUserInput( +void GateLastVertexInteractionSplittingActor::InitializeUserInfo( py::dict &user_info) { - GateVActor::InitializeUserInput(user_info); - fMotherVolumeName = DictGetStr(user_info, "attached_to"); + GateVActor::InitializeUserInfo(user_info); + fAttachedToVolumeName = DictGetStr(user_info, "attached_to"); fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); fAngularKill = DictGetBool(user_info, "angular_kill"); @@ -540,9 +540,8 @@ void GateLastVertexInteractionSplittingActor::BeginOfRunAction( fListOfProcessesAccordingParticles["e+"] = {"eBrem", "eIoni", "msc", "annihil"}; - std::cout << fMotherVolumeName << std::endl; G4LogicalVolume *biasingVolume = - G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + G4LogicalVolumeStore::GetInstance()->GetVolume(fAttachedToVolumeName); fListOfBiasedVolume.push_back(biasingVolume->GetName()); CreateListOfbiasedVolume(biasingVolume); @@ -554,7 +553,7 @@ void GateLastVertexInteractionSplittingActor::BeginOfRunAction( if (fRotationVectorDirector) { G4VPhysicalVolume *physBiasingVolume = - G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); + G4PhysicalVolumeStore::GetInstance()->GetVolume(fAttachedToVolumeName); auto rot = physBiasingVolume->GetObjectRotationValue(); fVectorDirector = rot * fVectorDirector; } diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h index e5d0b333f..90be6e135 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h @@ -102,7 +102,7 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { std::vector<G4String> fListOfProcesses = {"compt", "annihil", "eBrem", "conv", "phot"}; - void InitializeUserInput(py::dict &user_info) override; + void InitializeUserInfo(py::dict &user_info) override; virtual void SteppingAction(G4Step *) override; virtual void BeginOfEventAction(const G4Event *) override; virtual void EndOfEventAction(const G4Event *) override; From f57492fbf1e1b019ef398a92d5950ee55254a1b9 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 19 Nov 2024 10:47:47 +0100 Subject: [PATCH 57/82] old files deletion --- core/opengate_core/opengate_core.cpp | 3 - .../opengate_lib/GateOptnForceFreeFlight.cpp | 127 ------- .../opengate_lib/GateOptnForceFreeFlight.h | 122 ------- .../GateOptnPairProdSplitting.cpp | 97 ------ .../opengate_lib/GateOptnPairProdSplitting.h | 52 --- .../GateOptnScatteredGammaSplitting.cpp | 165 --------- .../GateOptnScatteredGammaSplitting.h | 53 --- .../GateOptnVGenericSplitting.cpp | 111 ------ .../opengate_lib/GateOptnVGenericSplitting.h | 127 ------- .../opengate_lib/GateOptneBremSplitting.cpp | 112 ------ .../opengate_lib/GateOptneBremSplitting.h | 54 --- ...GateOptrComptPseudoTransportationActor.cpp | 329 ------------------ .../GateOptrComptPseudoTransportationActor.h | 138 -------- ...GateOptrComptPseudoTransportationActor.cpp | 20 -- .../tests/src/test072_pseudo_transport_RR.py | 231 ------------ .../src/test072_pseudo_transportation.py | 194 ----------- .../test074_kill_non_interacting_particles.py | 212 ----------- ...75_geometrical_splitting_volume_surface.py | 185 ---------- .../src/test077_kill_interacting_particles.py | 213 ------------ .../src/test084_last_vertex_splittting.py | 222 ------------ ...084_last_vertex_splittting_angular_kill.py | 171 --------- 21 files changed, 2938 deletions(-) delete mode 100644 core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp delete mode 100644 core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h delete mode 100644 core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp delete mode 100644 core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h delete mode 100644 core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp delete mode 100644 core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h delete mode 100644 core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp delete mode 100644 core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h delete mode 100644 core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp delete mode 100644 core/opengate_core/opengate_lib/GateOptneBremSplitting.h delete mode 100644 core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp delete mode 100644 core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h delete mode 100644 core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp delete mode 100644 opengate/tests/src/test072_pseudo_transport_RR.py delete mode 100644 opengate/tests/src/test072_pseudo_transportation.py delete mode 100644 opengate/tests/src/test074_kill_non_interacting_particles.py delete mode 100644 opengate/tests/src/test075_geometrical_splitting_volume_surface.py delete mode 100644 opengate/tests/src/test077_kill_interacting_particles.py delete mode 100755 opengate/tests/src/test084_last_vertex_splittting.py delete mode 100755 opengate/tests/src/test084_last_vertex_splittting_angular_kill.py diff --git a/core/opengate_core/opengate_core.cpp b/core/opengate_core/opengate_core.cpp index 72672fed0..84c25d76f 100644 --- a/core/opengate_core/opengate_core.cpp +++ b/core/opengate_core/opengate_core.cpp @@ -347,8 +347,6 @@ void init_GateOptrComptSplittingActor(py::module &m); void init_GateLastVertexInteractionSplittingActor(py::module &m); -void init_GateOptrComptPseudoTransportationActor(py::module &m); - void init_GateBOptrBremSplittingActor(py::module &m); void init_G4VBiasingOperator(py::module &m); @@ -576,7 +574,6 @@ PYBIND11_MODULE(opengate_core, m) { init_GateSimulationStatisticsActor(m); init_GatePhaseSpaceActor(m); init_GateBOptrBremSplittingActor(m); - init_GateOptrComptPseudoTransportationActor(m); init_GateOptrComptSplittingActor(m); init_GateLastVertexInteractionSplittingActor(m); init_GateHitsCollectionActor(m); diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp deleted file mode 100644 index a1d281d47..000000000 --- a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp +++ /dev/null @@ -1,127 +0,0 @@ -// -// ******************************************************************** -// * 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. * -// ******************************************************************** -// -#include "GateOptnForceFreeFlight.h" -#include "G4BiasingProcessInterface.hh" -#include "G4ILawForceFreeFlight.hh" -#include "G4Step.hh" - -// This operator is used to transport the particle without interaction, and then -// correct the weight of the particle -// according to the probablity for the photon to not interact within the matter. -// To do that, we use a GEANT4 modifed Interaction Law (G4ILawForceFreeFlight), -// which modify, for the biased process the probability for the interaction to -// occur : Never. This occurence is called during the tracking for each step. -// Here the step is the largest possible, and one step correspond to the path of -// particle in the media. - -GateOptnForceFreeFlight ::GateOptnForceFreeFlight(G4String name) - : G4VBiasingOperation(name), fOperationComplete(true) { - fForceFreeFlightInteractionLaw = - new G4ILawForceFreeFlight("LawForOperation" + name); -} - -GateOptnForceFreeFlight ::~GateOptnForceFreeFlight() { - if (fForceFreeFlightInteractionLaw) - delete fForceFreeFlightInteractionLaw; -} - -const G4VBiasingInteractionLaw * -GateOptnForceFreeFlight ::ProvideOccurenceBiasingInteractionLaw( - const G4BiasingProcessInterface *, - G4ForceCondition &proposeForceCondition) { - fOperationComplete = false; - proposeForceCondition = Forced; - return fForceFreeFlightInteractionLaw; -} - -G4VParticleChange *GateOptnForceFreeFlight ::ApplyFinalStateBiasing( - const G4BiasingProcessInterface *callingProcess, const G4Track *track, - const G4Step *step, G4bool &forceFinalState) { - - fParticleChange.Initialize(*track); - forceFinalState = true; - fCountProcess++; - - fProposedWeight *= - fWeightChange[callingProcess->GetWrappedProcess()->GetProcessName()]; - - if (fRussianRouletteForWeights) { - G4int nbOfOrderOfMagnitude = std::log10(fInitialWeight / fMinWeight); - - if ((fProposedWeight < fMinWeight) || - ((fProposedWeight < 0.1 * fInitialWeight) && - (fNbOfRussianRoulette == nbOfOrderOfMagnitude - 1))) { - fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); - fNbOfRussianRoulette = 0; - return &fParticleChange; - } - - if ((fProposedWeight < 0.1 * fInitialWeight) && (fCountProcess == 4)) { - G4double probability = G4UniformRand(); - for (int i = 2; i <= nbOfOrderOfMagnitude; i++) { - G4double RRprobability = 1 / std::pow(10, i); - if (fProposedWeight * 1 / std::pow(10, fNbOfRussianRoulette) < - 10 * RRprobability * fMinWeight) { - fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); - fNbOfRussianRoulette = 0; - return &fParticleChange; - } - if ((fProposedWeight >= RRprobability * fInitialWeight) && - (fProposedWeight < 10 * RRprobability * fInitialWeight)) { - if (probability > 10 * RRprobability) { - fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); - fNbOfRussianRoulette = 0; - return &fParticleChange; - } else { - fProposedWeight = fProposedWeight / (10 * RRprobability); - fNbOfRussianRoulette = fNbOfRussianRoulette + i - 1; - } - } - } - } - } else { - if (fProposedWeight < fMinWeight) { - fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); - return &fParticleChange; - } - } - fParticleChange.ProposeWeight(fProposedWeight); - fOperationComplete = true; - return &fParticleChange; -} - -void GateOptnForceFreeFlight ::AlongMoveBy( - const G4BiasingProcessInterface *callingProcess, const G4Step *, - G4double weightChange) - -{ - G4String processName = callingProcess->GetWrappedProcess()->GetProcessName(); - if (processName != "Rayl") { - fWeightChange[processName] = weightChange; - } else { - fWeightChange[processName] = 1; - } -} diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h deleted file mode 100644 index 398f27851..000000000 --- a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h +++ /dev/null @@ -1,122 +0,0 @@ -// -// ******************************************************************** -// * 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. * -// ******************************************************************** -// -// -// -//--------------------------------------------------------------- -// -// GateOptnForceFreeFlight -// -// Class Description: -// A G4VBiasingOperation physics-based biasing operation. -// If forces the physics process to not act on the track. -// In this implementation (meant for the ForceCollision -// operator) the free flight is done under zero weight for -// the track, and the action is meant to accumulate the weight -// change for making this uninteracting flight, -// cumulatedWeightChange. -// When the track reaches the current volume boundary, its -// weight is restored with value : -// initialWeight * cumulatedWeightChange -// -//--------------------------------------------------------------- -// Initial version Nov. 2013 M. Verderi -#ifndef GateOptnForceFreeFlight_h -#define GateOptnForceFreeFlight_h 1 - -#include "G4ForceCondition.hh" -#include "G4ParticleChange.hh" // -- ยงยง should add a dedicated "weight change only" particle change -#include "G4VBiasingOperation.hh" -class G4ILawForceFreeFlight; - -class GateOptnForceFreeFlight : public G4VBiasingOperation { -public: - // -- Constructor : - GateOptnForceFreeFlight(G4String name); - // -- destructor: - virtual ~GateOptnForceFreeFlight(); - -public: - // -- Methods from G4VBiasingOperation interface: - // ------------------------------------------- - // -- Used: - virtual const G4VBiasingInteractionLaw * - ProvideOccurenceBiasingInteractionLaw(const G4BiasingProcessInterface *, - G4ForceCondition &); - virtual void AlongMoveBy(const G4BiasingProcessInterface *, const G4Step *, - G4double); - virtual G4VParticleChange * - ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, - const G4Step *, G4bool &); - // -- Unused: - virtual G4double DistanceToApplyOperation(const G4Track *, G4double, - G4ForceCondition *) { - return DBL_MAX; - } - virtual G4VParticleChange *GenerateBiasingFinalState(const G4Track *, - const G4Step *) { - return 0; - } - -public: - // -- Additional methods, specific to this class: - // ---------------------------------------------- - // -- return concrete type of interaction law: - G4ILawForceFreeFlight *GetForceFreeFlightLaw() { - return fForceFreeFlightInteractionLaw; - } - // -- initialization for weight: - // void ResetInitialTrackWeight(G4double w) {fInitialTrackWeight = w; - // fCumulatedWeightChange = 1.0;} - - void SetMinWeight(G4double w) { fMinWeight = w; } - void SetInitialWeight(G4double w) { fInitialWeight = w; } - G4double GetTrackWeight() { return fProposedWeight; } - void SetTrackWeight(G4double w) { fProposedWeight = w; } - void SetRussianRouletteProbability(G4double p) { - fRussianRouletteProbability = p; - } - void SetCountProcess(G4int N) { fCountProcess = N; } - void SetSurvivedToRR(G4bool b) { fSurvivedToRR = b; } - void SetRussianRouletteForWeights(G4bool rr) { - fRussianRouletteForWeights = rr; - } - G4bool GetSurvivedToRR() { return fSurvivedToRR; } - G4bool OperationComplete() const { return fOperationComplete; } - -private: - G4ILawForceFreeFlight *fForceFreeFlightInteractionLaw; - std::map<G4String, G4double> fWeightChange; - G4bool fRussianRouletteForWeights; - G4double fMinWeight, fRussianRouletteProbability, fInitialWeight; - G4ParticleChange fParticleChange; - G4bool fOperationComplete; - G4double fProposedWeight; - G4int fCountProcess; - G4bool fSurvivedToRR; - G4int fNbOfRussianRoulette; -}; - -#endif diff --git a/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp deleted file mode 100644 index cf30c3a7d..000000000 --- a/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptnPairProdSplitting.cc -/// \brief Implementation of the GateOptnPairProdSplitting class - -#include "GateOptnPairProdSplitting.h" -#include "G4BiasingProcessInterface.hh" - -#include "G4ComptonScattering.hh" -#include "G4DynamicParticle.hh" -#include "G4Exception.hh" -#include "G4Gamma.hh" -#include "G4GammaConversion.hh" -#include "G4ParticleChange.hh" -#include "G4ParticleChangeForGamma.hh" -#include "G4ParticleChangeForLoss.hh" -#include "G4PhotoElectricEffect.hh" -#include "G4ProcessType.hh" -#include "G4RayleighScattering.hh" -#include "G4SystemOfUnits.hh" -#include "G4TrackStatus.hh" -#include "G4TrackingManager.hh" -#include "G4VEmProcess.hh" -#include <memory> - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptnPairProdSplitting::GateOptnPairProdSplitting(G4String name) - : GateOptnVGenericSplitting(name), fParticleChange() {} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptnPairProdSplitting::~GateOptnPairProdSplitting() {} - -G4VParticleChange *GateOptnPairProdSplitting::ApplyFinalStateBiasing( - const G4BiasingProcessInterface *callingProcess, const G4Track *track, - const G4Step *step, G4bool &) { - - G4int splittingFactor = ceil(fSplittingFactor); - G4double survivalProbabilitySplitting = - 1 - (splittingFactor - fSplittingFactor) / splittingFactor; - G4double particleWeight = 0; - - G4VParticleChange *processFinalState = - callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - - if (fSplittingFactor == 1 && fRussianRouletteForAngle == false) - return processFinalState; - TrackInitializationGamma(&fParticleChange, processFinalState, track, - fSplittingFactor); - - processFinalState->Clear(); - - G4int nCalls = 1; - while (nCalls <= splittingFactor) { - G4double splittingProbability = G4UniformRand(); - if (splittingProbability <= survivalProbabilitySplitting || - survivalProbabilitySplitting == 1) { - particleWeight = track->GetWeight() / fSplittingFactor; - G4VParticleChange *processFinalState = - callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - if (processFinalState->GetNumberOfSecondaries() >= 1) { - for (int i = 0; i < processFinalState->GetNumberOfSecondaries(); i++) { - G4Track *SecondaryTrack = processFinalState->GetSecondary(i); - SecondaryTrack->SetWeight(particleWeight); - fParticleChange.AddSecondary(SecondaryTrack); - } - } - } - nCalls++; - } - return &fParticleChange; -} diff --git a/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h b/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h deleted file mode 100644 index ef4128a81..000000000 --- a/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h +++ /dev/null @@ -1,52 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptnPairProdSplitting.h -/// \brief Definition of the GateOptnPairProdSplitting class -// - -#ifndef GateOptnPairProdSplitting_h -#define GateOptnPairProdSplitting_h 1 - -#include "G4ParticleChange.hh" -#include "GateOptnVGenericSplitting.h" - -class GateOptnPairProdSplitting : public GateOptnVGenericSplitting { -public: - // -- Constructor : - GateOptnPairProdSplitting(G4String name); - - // -- destructor: - virtual ~GateOptnPairProdSplitting(); - - virtual G4VParticleChange * - ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, - const G4Step *, G4bool &); - - G4ParticleChange fParticleChange; -}; - -#endif diff --git a/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp deleted file mode 100644 index bbfe8acff..000000000 --- a/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp +++ /dev/null @@ -1,165 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptnScatteredGammaSplitting.cc -/// \brief Implementation of the GateOptnScatteredGammaSplitting class - -#include "GateOptnScatteredGammaSplitting.h" -#include "G4BiasingProcessInterface.hh" - -#include "G4ComptonScattering.hh" -#include "G4DynamicParticle.hh" -#include "G4Exception.hh" -#include "G4Gamma.hh" -#include "G4GammaConversion.hh" -#include "G4ParticleChange.hh" -#include "G4ParticleChangeForGamma.hh" -#include "G4PhotoElectricEffect.hh" -#include "G4ProcessType.hh" -#include "G4RayleighScattering.hh" -#include "G4SystemOfUnits.hh" -#include "G4TrackStatus.hh" -#include "G4TrackingManager.hh" -#include "G4VEmProcess.hh" -#include "GateOptnVGenericSplitting.h" -#include <memory> - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptnScatteredGammaSplitting::GateOptnScatteredGammaSplitting(G4String name) - : GateOptnVGenericSplitting(name), fParticleChange() {} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptnScatteredGammaSplitting::~GateOptnScatteredGammaSplitting() {} - -G4VParticleChange *GateOptnScatteredGammaSplitting::ApplyFinalStateBiasing( - const G4BiasingProcessInterface *callingProcess, const G4Track *track, - const G4Step *step, G4bool &) { - - // Here we generate for the first the compton process, given that this - // function (ApplyFinalStateBiasing) is called when there is a compton - // interaction Then the interaction location of the compton process will - // always be the same - - const G4ThreeVector position = step->GetPostStepPoint()->GetPosition(); - G4int splittingFactor = ceil(fSplittingFactor); - G4double survivalProbabilitySplitting = - 1 - (splittingFactor - fSplittingFactor) / splittingFactor; - G4double gammaWeight = 0; - - G4VParticleChange *processFinalState = - callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - // In case we don't want to split (a bit faster) i.e no biaising or no - // splitting low weights particles. - - if (fSplittingFactor == 1 && fRussianRouletteForAngle == false) - return processFinalState; - - TrackInitializationGamma(&fParticleChange, processFinalState, track, - fSplittingFactor); - processFinalState->Clear(); - - // There is here the biasing process : - // Since G4VParticleChange class does not allow to retrieve scattered gamma - // information, we need to cast the type G4ParticleChangeForGamma to the - // G4VParticleChange object. We then call the process (biasWrapper(compt)) - // fSplittingFactor times (Here, the difference with the other version of - // splitting is the primary particle will be killed and its weight does not - // count) to generate, at last, fSplittingFactor gamma according to the - // compton interaction process. If the gamma track is ok regarding the - // russian roulette algorithm (no russian roulette - //, or within the acceptance angle, or not killed by the RR process), we add - // it to the primary track. - // If an electron is generated (above the range cut), we also generate it. - // A tremendous advantage is there is no need to use by ourself Klein-Nishina - // formula or other. So, if the physics list used takes into account the - // doppler broadening or other fine effects, this will be also taken into - // account by the MC simulation. PS : The first gamma is then the primary - // particle, but all the other splitted particle (electron of course AND - // gamma) must be considered as secondary particles, even though generated - // gamma will not be cut here by the applied cut. - - G4int nCalls = 1; - while (nCalls <= splittingFactor) { - gammaWeight = track->GetWeight() / fSplittingFactor; - G4VParticleChange *processGammaSplittedFinalState = - callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - G4ParticleChangeForGamma *castedProcessGammaSplittedFinalState = - (G4ParticleChangeForGamma *)processGammaSplittedFinalState; - const G4ThreeVector momentum = - castedProcessGammaSplittedFinalState->GetProposedMomentumDirection(); - G4double energy = - castedProcessGammaSplittedFinalState->GetProposedKineticEnergy(); - G4double splittingProbability = G4UniformRand(); - - if (splittingProbability <= survivalProbabilitySplitting || - survivalProbabilitySplitting == 1) { - if (fRussianRouletteForAngle == true) { - G4double weightToApply = RussianRouletteForAngleSurvival( - castedProcessGammaSplittedFinalState - ->GetProposedMomentumDirection(), - fVectorDirector, fMaxTheta, fSplittingFactor); - if (weightToApply != 0) { - gammaWeight = gammaWeight * weightToApply; - G4Track *gammaTrack = new G4Track(*track); - gammaTrack->SetWeight(gammaWeight); - gammaTrack->SetKineticEnergy(energy); - gammaTrack->SetMomentumDirection(momentum); - gammaTrack->SetPosition(position); - fParticleChange.AddSecondary(gammaTrack); - if (processGammaSplittedFinalState->GetNumberOfSecondaries() == 1) { - G4Track *electronTrack = - processGammaSplittedFinalState->GetSecondary(0); - electronTrack->SetWeight(gammaWeight); - fParticleChange.AddSecondary(electronTrack); - } - } - } - - else { - G4Track *gammaTrack = new G4Track(*track); - gammaTrack->SetWeight(gammaWeight); - gammaTrack->SetKineticEnergy(energy); - gammaTrack->SetMomentumDirection(momentum); - gammaTrack->SetPosition(position); - fParticleChange.AddSecondary(gammaTrack); - if (processGammaSplittedFinalState->GetNumberOfSecondaries() == 1) { - G4Track *electronTrack = - processGammaSplittedFinalState->GetSecondary(0); - electronTrack->SetWeight(gammaWeight); - fParticleChange.AddSecondary(electronTrack); - } - } - } - nCalls++; - processGammaSplittedFinalState->Clear(); - castedProcessGammaSplittedFinalState->Clear(); - } - return &fParticleChange; -} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h b/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h deleted file mode 100644 index ff0a6b623..000000000 --- a/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h +++ /dev/null @@ -1,53 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptnScatteredGammaSplitting.h -/// \brief Definition of the GateOptnScatteredGammaSplitting class -// - -#ifndef GateOptnScatteredGammaSplitting_h -#define GateOptnScatteredGammaSplitting_h 1 - -#include "G4ParticleChange.hh" -#include "G4VBiasingOperation.hh" -#include "GateOptnVGenericSplitting.h" - -class GateOptnScatteredGammaSplitting : public GateOptnVGenericSplitting { -public: - // -- Constructor : - GateOptnScatteredGammaSplitting(G4String name); - - // -- destructor: - virtual ~GateOptnScatteredGammaSplitting(); - - virtual G4VParticleChange * - ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, - const G4Step *, G4bool &); - - G4ParticleChange fParticleChange; -}; - -#endif diff --git a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp deleted file mode 100644 index 8c22b6399..000000000 --- a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp +++ /dev/null @@ -1,111 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptnVGenericSplitting.cc -/// \brief Implementation of the GateOptnVGenericSplitting class - -#include "GateOptnVGenericSplitting.h" -#include "G4BiasingProcessInterface.hh" - -#include "G4ComptonScattering.hh" -#include "G4DynamicParticle.hh" -#include "G4Exception.hh" -#include "G4Gamma.hh" -#include "G4GammaConversion.hh" -#include "G4ParticleChange.hh" -#include "G4ParticleChangeForGamma.hh" -#include "G4ParticleChangeForLoss.hh" -#include "G4PhotoElectricEffect.hh" -#include "G4ProcessType.hh" -#include "G4RayleighScattering.hh" -#include "G4SystemOfUnits.hh" -#include "G4TrackStatus.hh" -#include "G4TrackingManager.hh" -#include "G4VEmProcess.hh" -#include <memory> - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptnVGenericSplitting::GateOptnVGenericSplitting(G4String name) - : G4VBiasingOperation(name), fParticleChange() {} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptnVGenericSplitting::~GateOptnVGenericSplitting() {} - -void GateOptnVGenericSplitting::TrackInitializationChargedParticle( - G4ParticleChange *particleChange, G4VParticleChange *processFinalState, - const G4Track *track, G4double split) { - - G4ParticleChangeForLoss *processFinalStateForLoss = - (G4ParticleChangeForLoss *)processFinalState; - particleChange->Initialize(*track); - particleChange->ProposeTrackStatus( - processFinalStateForLoss->GetTrackStatus()); - particleChange->ProposeEnergy( - processFinalStateForLoss->GetProposedKineticEnergy()); - particleChange->ProposeMomentumDirection( - processFinalStateForLoss->GetProposedMomentumDirection()); - particleChange->SetNumberOfSecondaries(fSplittingFactor); - particleChange->SetSecondaryWeightByProcess(true); - processFinalStateForLoss->Clear(); -} - -void GateOptnVGenericSplitting::TrackInitializationGamma( - G4ParticleChange *particleChange, G4VParticleChange *processFinalState, - const G4Track *track, G4double split) { - G4ParticleChangeForGamma *processFinalStateForGamma = - (G4ParticleChangeForGamma *)processFinalState; - particleChange->Initialize(*track); - particleChange->ProposeTrackStatus( - processFinalStateForGamma->GetTrackStatus()); - particleChange->ProposeEnergy( - processFinalStateForGamma->GetProposedKineticEnergy()); - particleChange->ProposeMomentumDirection( - processFinalStateForGamma->GetProposedMomentumDirection()); - particleChange->SetNumberOfSecondaries(fSplittingFactor); - particleChange->SetSecondaryWeightByProcess(true); - processFinalStateForGamma->Clear(); -} - -G4double GateOptnVGenericSplitting::RussianRouletteForAngleSurvival( - G4ThreeVector dir, G4ThreeVector vectorDirector, G4double maxTheta, - G4double split) { - G4double cosTheta = vectorDirector * dir; - G4double theta = std::acos(cosTheta); - G4double weightToApply = 1; - if (theta > maxTheta) { - G4double probability = G4UniformRand(); - if (probability <= 1 / split) { - weightToApply = split; - } else { - weightToApply = 0; - } - } - return weightToApply; -} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h deleted file mode 100644 index b89f0baee..000000000 --- a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h +++ /dev/null @@ -1,127 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptnVGenericSplitting.h -/// \brief Definition of the GateOptnVGenericSplitting class -// - -#ifndef GateOptnVGenericSplitting_h -#define GateOptnVGenericSplitting_h 1 - -#include "G4ParticleChange.hh" -#include "G4VBiasingOperation.hh" - -class GateOptnVGenericSplitting : public G4VBiasingOperation { -public: - // -- Constructor : - GateOptnVGenericSplitting(G4String name); - - // -- destructor: - virtual ~GateOptnVGenericSplitting(); - -public: - // ---------------------------------------------- - // -- Methods from G4VBiasingOperation interface: - // ---------------------------------------------- - // -- Unused: - virtual const G4VBiasingInteractionLaw * - ProvideOccurenceBiasingInteractionLaw(const G4BiasingProcessInterface *, - G4ForceCondition &) { - return 0; - } - - // --Used: - virtual G4VParticleChange * - ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, - const G4Step *, G4bool &) { - return 0; - }; - - // -- Unsued: - virtual G4double DistanceToApplyOperation(const G4Track *, G4double, - G4ForceCondition *) { - return DBL_MAX; - } - virtual G4VParticleChange *GenerateBiasingFinalState(const G4Track *, - const G4Step *) { - return 0; - } - - // ---------------------------------------------- - // -- Methods for the generic splitting - // ---------------------------------------------- - - void TrackInitializationChargedParticle(G4ParticleChange *particleChange, - G4VParticleChange *processFinalState, - const G4Track *track, G4double split); - void TrackInitializationGamma(G4ParticleChange *particleChange, - G4VParticleChange *processFinalState, - const G4Track *track, G4double split); - static G4double RussianRouletteForAngleSurvival(G4ThreeVector dir, - G4ThreeVector vectorDirector, - G4double maxTheta, - G4double split); - -public: - // ---------------------------------------------- - // -- Additional methods, specific to this class: - // ---------------------------------------------- - // -- Splitting factor: - void SetSplittingFactor(G4double splittingFactor) { - fSplittingFactor = splittingFactor; - } - G4double GetSplittingFactor() const { return fSplittingFactor; } - - void SetRussianRouletteForAngle(G4bool russianRoulette) { - fRussianRouletteForAngle = russianRoulette; - } - - void SetVectorDirector(G4ThreeVector vectorDirector) { - fVectorDirector = vectorDirector; - } - - void SetRotationMatrix(G4RotationMatrix rot) { fRot = rot; } - - G4ThreeVector GetVectorDirector() const { return fVectorDirector; } - - void SetMaxTheta(G4double maxTheta) { fMaxTheta = maxTheta; } - - G4double GetMaxTheta() const { return fMaxTheta; } - - G4VParticleChange *GetParticleChange() { - G4VParticleChange *particleChange = &fParticleChange; - return particleChange; - } - - G4double fSplittingFactor; - G4ParticleChange fParticleChange; - G4bool fRussianRouletteForAngle; - G4ThreeVector fVectorDirector; - G4double fMaxTheta; - G4RotationMatrix fRot; -}; - -#endif diff --git a/core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp b/core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp deleted file mode 100644 index ce9ff2324..000000000 --- a/core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp +++ /dev/null @@ -1,112 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptneBremSplitting.cc -/// \brief Implementation of the GateOptneBremSplitting class - -#include "GateOptneBremSplitting.h" -#include "G4BiasingProcessInterface.hh" - -#include "G4ComptonScattering.hh" -#include "G4DynamicParticle.hh" -#include "G4Exception.hh" -#include "G4Gamma.hh" -#include "G4GammaConversion.hh" -#include "G4ParticleChange.hh" -#include "G4ParticleChangeForGamma.hh" -#include "G4ParticleChangeForLoss.hh" -#include "G4PhotoElectricEffect.hh" -#include "G4ProcessType.hh" -#include "G4RayleighScattering.hh" -#include "G4SystemOfUnits.hh" -#include "G4TrackStatus.hh" -#include "G4TrackingManager.hh" -#include "G4VEmProcess.hh" -#include "GateOptnVGenericSplitting.h" -#include <memory> - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptneBremSplitting::GateOptneBremSplitting(G4String name) - : GateOptnVGenericSplitting(name), fParticleChange() {} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptneBremSplitting::~GateOptneBremSplitting() {} - -G4VParticleChange *GateOptneBremSplitting::ApplyFinalStateBiasing( - const G4BiasingProcessInterface *callingProcess, const G4Track *track, - const G4Step *step, G4bool &) { - - G4int splittingFactor = ceil(fSplittingFactor); - G4double survivalProbabilitySplitting = - 1 - (splittingFactor - fSplittingFactor) / splittingFactor; - G4VParticleChange *processFinalState = - callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - if (fSplittingFactor == 1) - return processFinalState; - if (processFinalState->GetNumberOfSecondaries() == 0) - return processFinalState; - - TrackInitializationChargedParticle(&fParticleChange, processFinalState, track, - fSplittingFactor); - - processFinalState->Clear(); - - G4int nCalls = 1; - while (nCalls <= fSplittingFactor) { - G4double splittingProbability = G4UniformRand(); - if (splittingProbability <= survivalProbabilitySplitting || - survivalProbabilitySplitting == 1) { - processFinalState = - callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - if (processFinalState->GetNumberOfSecondaries() >= 1) { - for (int i = 0; i < processFinalState->GetNumberOfSecondaries(); i++) { - G4double gammaWeight = track->GetWeight() / fSplittingFactor; - G4Track *gammaTrack = processFinalState->GetSecondary(i); - if (fRussianRouletteForAngle == true) { - G4double weightToApply = RussianRouletteForAngleSurvival( - gammaTrack->GetMomentumDirection(), fVectorDirector, fMaxTheta, - fSplittingFactor); - if (weightToApply != 0) { - gammaWeight = gammaWeight * weightToApply; - gammaTrack->SetWeight(gammaWeight); - fParticleChange.AddSecondary(gammaTrack); - } - } else { - gammaTrack->SetWeight(gammaWeight); - fParticleChange.AddSecondary(gammaTrack); - } - } - } - processFinalState->Clear(); - } - nCalls++; - } - return &fParticleChange; -} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptneBremSplitting.h b/core/opengate_core/opengate_lib/GateOptneBremSplitting.h deleted file mode 100644 index 47319e536..000000000 --- a/core/opengate_core/opengate_lib/GateOptneBremSplitting.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptneBremSplitting.h -/// \brief Definition of the GateOptneBremSplitting class -// - -#ifndef GateOptneBremSplitting_h -#define GateOptneBremSplitting_h 1 - -#include "G4ParticleChange.hh" -#include "G4VBiasingOperation.hh" -#include "GateOptnVGenericSplitting.h" - -class GateOptneBremSplitting : public GateOptnVGenericSplitting { -public: - // -- Constructor : - GateOptneBremSplitting(G4String name); - - // -- destructor: - virtual ~GateOptneBremSplitting(); - -public: - virtual G4VParticleChange * - ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, - const G4Step *, G4bool &); - - G4ParticleChange fParticleChange; -}; - -#endif diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp deleted file mode 100644 index f973cc4aa..000000000 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp +++ /dev/null @@ -1,329 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptrComptPseudoTransportationActor.cc -/// \brief Implementation of the GateOptrComptPseudoTransportationActor class - -#include "GateHelpersDict.h" -#include "GateHelpersImage.h" - -#include "CLHEP/Units/SystemOfUnits.h" -#include "G4BiasingProcessInterface.hh" -#include "G4Electron.hh" -#include "G4EmCalculator.hh" -#include "G4Exception.hh" -#include "G4Gamma.hh" -#include "G4LogicalVolumeStore.hh" -#include "G4ParticleTable.hh" -#include "G4PhysicalVolumeStore.hh" -#include "G4Positron.hh" -#include "G4ProcessManager.hh" -#include "G4ProcessVector.hh" -#include "G4RunManager.hh" -#include "G4TrackStatus.hh" -#include "G4UImanager.hh" -#include "G4UserTrackingAction.hh" -#include "G4VEmProcess.hh" -#include "G4eplusAnnihilation.hh" -#include "GateOptnPairProdSplitting.h" -#include "GateOptnScatteredGammaSplitting.h" -#include "GateOptnVGenericSplitting.h" -#include "GateOptneBremSplitting.h" -#include "GateOptrComptPseudoTransportationActor.h" - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptrComptPseudoTransportationActor::GateOptrComptPseudoTransportationActor( - py::dict &user_info) - : G4VBiasingOperator("ComptSplittingOperator"), - GateVActor(user_info, false) { - fMotherVolumeName = DictGetStr(user_info, "mother"); - fAttachToLogicalHolder = DictGetBool(user_info, "attach_to_logical_holder"); - fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); - fRelativeMinWeightOfParticle = - DictGetDouble(user_info, "relative_min_weight_of_particle"); - // Since the russian roulette uses as a probability 1/splitting, we need to - // have a double, but the splitting factor provided by the user is logically - // an int, so we need to change the type. - fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); - fRussianRouletteForAngle = - DictGetBool(user_info, "russian_roulette_for_angle"); - fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); - fRussianRouletteForWeights = - DictGetBool(user_info, "russian_roulette_for_weights"); - fMaxTheta = DictGetDouble(user_info, "max_theta"); - fFreeFlightOperation = new GateOptnForceFreeFlight("freeFlightOperation"); - fScatteredGammaSplittingOperation = - new GateOptnScatteredGammaSplitting("comptSplittingOperation"); - feBremSplittingOperation = - new GateOptneBremSplitting("eBremSplittingOperation"); - fPairProdSplittingOperation = - new GateOptnPairProdSplitting("PairProdSplittingOperation"); - fActions.insert("StartSimulationAction"); - fActions.insert("SteppingAction"); - fActions.insert("BeginOfEventAction"); - isSplitted = false; -} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -void GateOptrComptPseudoTransportationActor::AttachAllLogicalDaughtersVolumes( - G4LogicalVolume *volume) { - if (fAttachToLogicalHolder == false) { - if (volume->GetName() != G4LogicalVolumeStore::GetInstance() - ->GetVolume(fMotherVolumeName) - ->GetName()) { - AttachTo(volume); - } - } - if (fAttachToLogicalHolder == true) { - AttachTo(volume); - } - G4int nbOfDaughters = volume->GetNoDaughters(); - if (nbOfDaughters > 0) { - for (int i = 0; i < nbOfDaughters; i++) { - G4String LogicalVolumeName = - volume->GetDaughter(i)->GetLogicalVolume()->GetName(); - G4LogicalVolume *logicalDaughtersVolume = - volume->GetDaughter(i)->GetLogicalVolume(); - AttachAllLogicalDaughtersVolumes(logicalDaughtersVolume); - if (!(std::find(fNameOfBiasedLogicalVolume.begin(), - fNameOfBiasedLogicalVolume.end(), - LogicalVolumeName) != fNameOfBiasedLogicalVolume.end())) - fNameOfBiasedLogicalVolume.push_back( - volume->GetDaughter(i)->GetLogicalVolume()->GetName()); - } - } -} - -void GateOptrComptPseudoTransportationActor::StartSimulationAction() { - G4LogicalVolume *biasingVolume = - G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); - - // Here we need to attach all the daughters and daughters of daughters (...) - // to the biasing operator. To do that, I use the function - // AttachAllLogicalDaughtersVolumes. - AttachAllLogicalDaughtersVolumes(biasingVolume); - - fScatteredGammaSplittingOperation->SetSplittingFactor(fSplittingFactor); - - fScatteredGammaSplittingOperation->SetMaxTheta(fMaxTheta); - fScatteredGammaSplittingOperation->SetRussianRouletteForAngle( - fRussianRouletteForAngle); - - feBremSplittingOperation->SetSplittingFactor(fSplittingFactor); - feBremSplittingOperation->SetMaxTheta(fMaxTheta); - feBremSplittingOperation->SetRussianRouletteForAngle( - fRussianRouletteForAngle); - - fPairProdSplittingOperation->SetSplittingFactor(fSplittingFactor); - - fFreeFlightOperation->SetRussianRouletteForWeights( - fRussianRouletteForWeights); -} - -void GateOptrComptPseudoTransportationActor::StartRun() { - - // The way to behave of the russian roulette is the following : - // we provide a vector director and the theta angle acceptance, where theta = - // 0 is a vector colinear to the vector director Then if the track generated - // is on the acceptance angle, we add it to the primary track, and if it's not - // the case, we launch the russian roulette - if (fRotationVectorDirector) { - G4VPhysicalVolume *physBiasingVolume = - G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); - auto rot = physBiasingVolume->GetObjectRotationValue(); - fVectorDirector = rot * fVectorDirector; - fScatteredGammaSplittingOperation->SetRotationMatrix(rot); - feBremSplittingOperation->SetRotationMatrix(rot); - } - - fScatteredGammaSplittingOperation->SetVectorDirector(fVectorDirector); - feBremSplittingOperation->SetVectorDirector(fVectorDirector); -} - -void GateOptrComptPseudoTransportationActor::SteppingAction(G4Step *step) { - G4String creationProcessName = "None"; - if (step->GetTrack()->GetCreatorProcess() != 0) { - creationProcessName = - step->GetTrack()->GetCreatorProcess()->GetProcessName(); - } - - if ((fIsFirstStep) && (fRussianRouletteForAngle)) { - G4String LogicalVolumeNameOfCreation = - step->GetTrack()->GetLogicalVolumeAtVertex()->GetName(); - if (std::find(fNameOfBiasedLogicalVolume.begin(), - fNameOfBiasedLogicalVolume.end(), - LogicalVolumeNameOfCreation) != - fNameOfBiasedLogicalVolume.end()) { - if (creationProcessName == "biasWrapper(annihil)") { - auto dir = step->GetPreStepPoint()->GetMomentumDirection(); - G4double w = GateOptnVGenericSplitting::RussianRouletteForAngleSurvival( - dir, fVectorDirector, fMaxTheta, fSplittingFactor); - if (w == 0) { - step->GetTrack()->SetTrackStatus(fStopAndKill); - } else { - step->GetTrack()->SetWeight(step->GetTrack()->GetWeight() * w); - } - } - } - } - - if ((isSplitted == true) && - (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary)) { - G4String LogicalVolumeName = step->GetPostStepPoint() - ->GetPhysicalVolume() - ->GetLogicalVolume() - ->GetName(); - if (!(std::find(fNameOfBiasedLogicalVolume.begin(), - fNameOfBiasedLogicalVolume.end(), - LogicalVolumeName) != fNameOfBiasedLogicalVolume.end()) && - (LogicalVolumeName != fMotherVolumeName)) { - step->GetTrack()->SetTrackStatus(fStopAndKill); - isSplitted = false; - } - } - - fIsFirstStep = false; -} - -void GateOptrComptPseudoTransportationActor::BeginOfEventAction( - const G4Event *event) { - fKillOthersParticles = false; - fPassedByABiasedVolume = false; - fEventID = event->GetEventID(); - fEventIDKineticEnergy = - event->GetPrimaryVertex(0)->GetPrimary(0)->GetKineticEnergy(); -} - -void GateOptrComptPseudoTransportationActor::StartTracking( - const G4Track *track) { - G4String creationProcessName = "None"; - fIsFirstStep = true; - if (track->GetCreatorProcess() != 0) { - creationProcessName = track->GetCreatorProcess()->GetProcessName(); - } - - if (track->GetParticleDefinition()->GetParticleName() == "gamma") { - G4String LogicalVolumeNameOfCreation = - track->GetLogicalVolumeAtVertex()->GetName(); - if (std::find(fNameOfBiasedLogicalVolume.begin(), - fNameOfBiasedLogicalVolume.end(), - LogicalVolumeNameOfCreation) != - fNameOfBiasedLogicalVolume.end()) { - - fInitialWeight = track->GetWeight(); - fFreeFlightOperation->SetInitialWeight(fInitialWeight); - } - } -} - -// For the following operation the idea is the following : -// All the potential photon processes are biased. If a particle undergoes a -// compton interaction, we splitted it (ComptonSplittingForTransportation -// operation) and the particle generated are pseudo-transported with the -// ForceFreeFLight operation -// Since the occurence Biaising operation is called at the beginning of each -// track, and propose a different way to track the particle -//(with modified physics), it here returns other thing than 0 if we want to -// pseudo-transport the particle, so if its creatorProcess is the modified -// compton interaction - -G4VBiasingOperation * -GateOptrComptPseudoTransportationActor::ProposeOccurenceBiasingOperation( - const G4Track *track, const G4BiasingProcessInterface *callingProcess) { - - if (track->GetParticleDefinition()->GetParticleName() == "gamma") { - G4String LogicalVolumeNameOfCreation = - track->GetLogicalVolumeAtVertex()->GetName(); - if (std::find(fNameOfBiasedLogicalVolume.begin(), - fNameOfBiasedLogicalVolume.end(), - LogicalVolumeNameOfCreation) != - fNameOfBiasedLogicalVolume.end()) { - fFreeFlightOperation->SetMinWeight(fInitialWeight / - fRelativeMinWeightOfParticle); - fFreeFlightOperation->SetTrackWeight(track->GetWeight()); - fFreeFlightOperation->SetCountProcess(0); - return fFreeFlightOperation; - } - } - return 0; -} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -// Here we call the final state biasing operation called if one of the biased -// interaction (all photon interaction here) occurs. -// That's why we need here to apply some conditions to just split the initial -// track. - -G4VBiasingOperation * -GateOptrComptPseudoTransportationActor::ProposeFinalStateBiasingOperation( - const G4Track *track, const G4BiasingProcessInterface *callingProcess) { - G4String particleName = track->GetParticleDefinition()->GetParticleName(); - - G4String LogicalVolumeNameOfCreation = - track->GetLogicalVolumeAtVertex()->GetName(); - G4String CreationProcessName = ""; - if (track->GetCreatorProcess() != 0) { - CreationProcessName = track->GetCreatorProcess()->GetProcessName(); - } - - if (track->GetParticleDefinition()->GetParticleName() == "gamma") { - if (std::find(fNameOfBiasedLogicalVolume.begin(), - fNameOfBiasedLogicalVolume.end(), - LogicalVolumeNameOfCreation) != - fNameOfBiasedLogicalVolume.end()) { - return callingProcess->GetCurrentOccurenceBiasingOperation(); - } - } - - if (((CreationProcessName != "biasWrapper(conv)") && - (CreationProcessName != "biasWrapper(compt)")) && - (callingProcess->GetWrappedProcess()->GetProcessName() == "eBrem")) { - return feBremSplittingOperation; - } - - if (!(std::find(fCreationProcessNameList.begin(), - fCreationProcessNameList.end(), - CreationProcessName) != fCreationProcessNameList.end())) { - if (callingProcess->GetWrappedProcess()->GetProcessName() == "compt") { - - isSplitted = true; - return fScatteredGammaSplittingOperation; - } - if ((callingProcess->GetWrappedProcess()->GetProcessName() == "conv")) { - return fPairProdSplittingOperation; - } - } - return 0; -} - -void GateOptrComptPseudoTransportationActor::EndTracking() { - isSplitted = false; -} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h deleted file mode 100644 index c463a4e2c..000000000 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h +++ /dev/null @@ -1,138 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptrComptPseudoTransportationActor.h -/// \brief Definition of the GateOptrComptPseudoTransportationActor class -#ifndef GateOptrComptPseudoTransportationActor_h -#define GateOptrComptPseudoTransportationActor_h 1 - -#include "G4EmCalculator.hh" -#include "G4VBiasingOperator.hh" -#include "GateOptnForceFreeFlight.h" -#include "GateOptnPairProdSplitting.h" -#include "GateOptneBremSplitting.h" - -#include "GateVActor.h" -#include <iostream> -#include <pybind11/stl.h> -namespace py = pybind11; - -class GateOptnScatteredGammaSplitting; - -class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, - public GateVActor { -public: - GateOptrComptPseudoTransportationActor(py::dict &user_info); - virtual ~GateOptrComptPseudoTransportationActor() {} - -public: - // ------------------------- - // Optional from base class: - // ------------------------- - // -- Call at run start: - // virtual void BeginOfRunAction(const G4Run *run); - - // virtual void SteppingAction(G4Step* step); - - // -- Call at each track starting: - // virtual void PreUserTrackingAction( const G4Track* track ); - - G4double fSplittingFactor; - G4double fInitialWeight; - G4double fRelativeMinWeightOfParticle; - G4double fWeightThreshold; - G4bool fBiasPrimaryOnly; - G4bool fBiasOnlyOnce; - G4int fNInteractions = 0; - G4bool fRussianRouletteForAngle; - G4bool fRussianRouletteForWeights; - G4bool fRotationVectorDirector; - G4ThreeVector fVectorDirector; - G4double fMaxTheta; - G4bool isSplitted; - G4int NbOfTrack = 0; - G4int NbOfProbe = 1; - G4double weight = 0; - G4bool fKillOthersParticles = false; - G4bool fUseProbes = false; - G4bool fSurvivedRR = false; - G4bool fAttachToLogicalHolder = true; - G4bool fPassedByABiasedVolume = false; - G4double fKineticEnergyAtTheEntrance; - G4int ftrackIDAtTheEntrance; - G4int fEventID; - G4double fEventIDKineticEnergy; - G4bool ftestbool = false; - G4bool fIsFirstStep = false; - const G4VProcess *fAnnihilation = nullptr; - - std::vector<G4String> fNameOfBiasedLogicalVolume = {}; - std::vector<G4int> v_EventID = {}; - std::vector<G4String> fCreationProcessNameList = { - "biasWrapper(compt)", "biasWrapper(eBrem)", "biasWrapper(annihil)"}; - - // Unused but mandatory - - virtual void StartSimulationAction(); - virtual void StartRun(); - virtual void StartTracking(const G4Track *); - virtual void SteppingAction(G4Step *); - virtual void BeginOfEventAction(const G4Event *); - virtual void EndTracking(); - -protected: - // ----------------------------- - // -- Mandatory from base class: - // ----------------------------- - // -- Unused: - void AttachAllLogicalDaughtersVolumes(G4LogicalVolume *); - virtual G4VBiasingOperation *ProposeNonPhysicsBiasingOperation( - const G4Track * /* track */, - const G4BiasingProcessInterface * /* callingProcess */) { - return 0; - } - - // -- Used: - virtual G4VBiasingOperation *ProposeOccurenceBiasingOperation( - const G4Track * /* track */, - const G4BiasingProcessInterface * /* callingProcess */); - - virtual G4VBiasingOperation *ProposeFinalStateBiasingOperation( - const G4Track *track, const G4BiasingProcessInterface *callingProcess); - -private: - // -- Avoid compiler complaining for (wrong) method shadowing, - // -- this is because other virtual method with same name exists. - using G4VBiasingOperator::OperationApplied; - -private: - GateOptnForceFreeFlight *fFreeFlightOperation; - GateOptnScatteredGammaSplitting *fScatteredGammaSplittingOperation; - GateOptneBremSplitting *feBremSplittingOperation; - GateOptnPairProdSplitting *fPairProdSplittingOperation; -}; - -#endif diff --git a/core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp deleted file mode 100644 index a5cb86c35..000000000 --- a/core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/* -------------------------------------------------- - Copyright (C): OpenGATE Collaboration - This software is distributed under the terms - of the GNU Lesser General Public Licence (LGPL) - See LICENSE.md for further details - -------------------------------------------------- */ -#include <pybind11/pybind11.h> - -namespace py = pybind11; -#include "G4VBiasingOperator.hh" -#include "GateOptrComptPseudoTransportationActor.h" - -void init_GateOptrComptPseudoTransportationActor(py::module &m) { - - py::class_< - GateOptrComptPseudoTransportationActor, G4VBiasingOperator, GateVActor, - std::unique_ptr<GateOptrComptPseudoTransportationActor, py::nodelete>>( - m, "GateOptrComptPseudoTransportationActor") - .def(py::init<py::dict &>()); -} diff --git a/opengate/tests/src/test072_pseudo_transport_RR.py b/opengate/tests/src/test072_pseudo_transport_RR.py deleted file mode 100644 index 3ed0038e4..000000000 --- a/opengate/tests/src/test072_pseudo_transport_RR.py +++ /dev/null @@ -1,231 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import opengate as gate -import uproot -import numpy as np -import matplotlib.pyplot as plt -import matplotlib as mpl -import scipy -from scipy.spatial.transform import Rotation -from opengate.tests import utility - - -def Ekin_per_event(weight, EventID, Ekin): - l_edep = [] - Edep_per_event = 0 - last_EventID = 0 - for i in range(len(weight)): - if i > 0: - last_EventID = EventID[i - 1] - if EventID[i] != last_EventID and i > 0: - l_edep.append(Edep_per_event) - Edep_per_event = 0 - Edep_per_event += Ekin[i] * weight[i] - return np.asarray(l_edep) - - -def validation_test(arr_no_RR, arr_RR, nb_event, splitting_factor, tol=0.15): - - Tracks_no_RR = arr_no_RR[ - (arr_no_RR["TrackCreatorProcess"] == "biasWrapper(compt)") - & (arr_no_RR["ParticleName"] == "gamma") - ] - - Tracks_RR = arr_RR[ - (arr_RR["TrackCreatorProcess"] == "biasWrapper(compt)") - & (arr_RR["ParticleName"] == "gamma") - ] - - Ekin_no_RR = Tracks_no_RR["KineticEnergy"] - w_no_RR = Tracks_no_RR["Weight"] - Event_ID_no_RR = Tracks_no_RR["EventID"] - - Ekin_per_event_no_RR = Ekin_per_event(w_no_RR, Event_ID_no_RR, Ekin_no_RR) - - Ekin_RR = Tracks_RR["KineticEnergy"] - w_RR = Tracks_RR["Weight"] - Event_ID_RR = Tracks_RR["EventID"] - - bool_RR = (np.min(w_RR) > 0.1 * 1 / splitting_factor) & ( - np.max(w_RR) < 1 / splitting_factor - ) - - Ekin_per_event_RR = Ekin_per_event(w_RR, Event_ID_RR, Ekin_RR) - - mean_ekin_event_no_RR = np.sum(Ekin_per_event_no_RR) / nb_event - mean_ekin_event_RR = np.sum(Ekin_per_event_RR) / nb_event - diff = (mean_ekin_event_RR - mean_ekin_event_no_RR) / mean_ekin_event_RR - print( - "Mean kinetic energy per event without russian roulette :", - np.round(1000 * mean_ekin_event_no_RR, 1), - "keV", - ) - print( - "Mean kinetic energy per event with russian roulette :", - np.round(1000 * mean_ekin_event_RR, 1), - "keV", - ) - print("Percentage of difference :", diff * 100, "%") - - return (abs(diff) < tol) & (bool_RR) - # mean_energy_per_history_no_RR = np.sum(Tracks_no_RR["KineticEnergy"]*Tracks_no_RR["Weight"])/nb_event - # mean_energy_per_history_RR = np.sum(Tracks_RR["KineticEnergy"] * Tracks_RR["Weight"]) / nb_event - - # print(mean_energy_per_history_no_RR,mean_energy_per_history_RR) - - -if __name__ == "__main__": - - for j in range(2): - paths = utility.get_default_test_paths( - __file__, "test072_pseudo_transportation", output_folder="test072" - ) - - # create the simulation - sim = gate.Simulation() - - # main options - ui = sim.user_info - ui.g4_verbose = False - - # ui.visu = True - # ui.visu_type = "vrml" - ui.check_volumes_overlap = False - # ui.running_verbose_level = gate.EVENT - ui.number_of_threads = 1 - ui.random_seed = "auto" - - # units - m = gate.g4_units.m - km = gate.g4_units.km - mm = gate.g4_units.mm - um = gate.g4_units.um - cm = gate.g4_units.cm - nm = gate.g4_units.nm - Bq = gate.g4_units.Bq - MeV = gate.g4_units.MeV - keV = gate.g4_units.keV - gcm3 = gate.g4_units.g / gate.g4_units.cm3 - deg = gate.g4_units.deg - - # adapt world size - world = sim.world - world.size = [1.2 * m, 1.2 * m, 2 * m] - world.material = "G4_Galactic" - - ####### GEOMETRY TO IRRADIATE ############# - sim.volume_manager.material_database.add_material_weights( - "Tungsten", - ["W"], - [1], - 19.3 * gcm3, - ) - simple_collimation = sim.add_volume("Box", "colli_box") - simple_collimation.material = "G4_Galactic" - simple_collimation.mother = world.name - simple_collimation.size = [1 * m, 1 * m, 40 * cm] - simple_collimation.color = [0.3, 0.1, 0.8, 1] - - W_leaf = sim.add_volume("Box", "W_leaf") - W_leaf.mother = simple_collimation.name - W_leaf.size = [1 * m, 1 * m, 0.5 * cm] - W_leaf.material = "Tungsten" - leaf_translation = [] - for i in range(10): - leaf_translation.append( - [ - 0, - 0, - -0.5 * simple_collimation.size[2] - + 0.5 * W_leaf.size[2] - + i * W_leaf.size[2], - ] - ) - W_leaf.translation = leaf_translation - W_leaf.color = [0.8, 0.2, 0.1, 1] - - ######## pseudo_transportation ACTOR ######### - nb_split = 5 - pseudo_transportation_actor = sim.add_actor( - "ComptPseudoTransportationActor", "pseudo_transportation_actor" - ) - pseudo_transportation_actor.mother = simple_collimation.name - pseudo_transportation_actor.splitting_factor = nb_split - pseudo_transportation_actor.relative_min_weight_of_particle = 10000 - if j == 0: - pseudo_transportation_actor.russian_roulette_for_weights = False - if j == 1: - pseudo_transportation_actor.russian_roulette_for_weights = True - list_processes_to_bias = pseudo_transportation_actor.gamma_processes - - ##### PHASE SPACE plan ######" - plan = sim.add_volume("Box", "phsp") - plan.material = "G4_Galactic" - plan.mother = world.name - plan.size = [1 * m, 1 * m, 1 * nm] - plan.color = [0.2, 1, 0.8, 1] - plan.translation = [0, 0, -20 * cm - 1 * nm] - - ####### gamma source ########### - nb_event = 20000 - source = sim.add_source("GenericSource", "source1") - source.particle = "gamma" - source.n = nb_event - source.position.type = "sphere" - source.position.radius = 1 * nm - source.direction.type = "momentum" - source.direction.momentum = [0, 0, -1] - source.energy.type = "mono" - source.energy.mono = 6 * MeV - source.position.translation = [0, 0, 18 * cm] - - ####### PHASE SPACE ACTOR ############## - - phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") - phsp_actor.mother = plan.name - phsp_actor.attributes = [ - "EventID", - "TrackCreatorProcess", - "TrackVertexPosition", - "PrePosition", - "Weight", - "KineticEnergy", - "ParentID", - "ParticleName", - ] - data_name = "test072_output_data_RR_" + str(j) + ".root" - phsp_actor.output = paths.output / data_name - - ##### MODIFIED PHYSICS LIST ############### - - s = sim.add_actor("SimulationStatisticsActor", "Stats") - s.track_types_flag = True - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" - ### Perhaps avoid the user to call the below boolean function ? ### - sim.physics_manager.special_physics_constructors.G4GenericBiasingPhysics = True - sim.physics_manager.processes_to_bias.gamma = list_processes_to_bias - s = f"/process/em/UseGeneralProcess false" - sim.add_g4_command_before_init(s) - - sim.physics_manager.global_production_cuts.gamma = 1 * mm - sim.physics_manager.global_production_cuts.electron = 1 * km - sim.physics_manager.global_production_cuts.positron = 1 * km - - output = sim.run(True) - - # - # # print results - stats = sim.output.get_actor("Stats") - h = sim.output.get_actor("PhaseSpace") - print(stats) - # - - f_1 = uproot.open(paths.output / "test072_output_data_RR_0.root") - f_2 = uproot.open(paths.output / "test072_output_data_RR_1.root") - - arr_no_RR = f_1["PhaseSpace"].arrays() - arr_RR = f_2["PhaseSpace"].arrays() - - is_ok = validation_test(arr_no_RR, arr_RR, nb_event, nb_split) - utility.test_ok(is_ok) diff --git a/opengate/tests/src/test072_pseudo_transportation.py b/opengate/tests/src/test072_pseudo_transportation.py deleted file mode 100644 index 64f8e822f..000000000 --- a/opengate/tests/src/test072_pseudo_transportation.py +++ /dev/null @@ -1,194 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import opengate as gate -import uproot -import numpy as np -import matplotlib.pyplot as plt -import matplotlib as mpl -import scipy -from scipy.spatial.transform import Rotation -from opengate.tests import utility - - -def validation_test(arr, NIST_data, nb_split, tol=0.01): - tab_ekin = NIST_data[:, 0] - mu_att = NIST_data[:, -2] - - log_ekin = np.log(tab_ekin) - log_mu = np.log(mu_att) - - f_mu = scipy.interpolate.interp1d(log_ekin, log_mu, kind="cubic") - # print(arr["TrackCreatorProcess"]) - Tracks = arr[ - (arr["TrackCreatorProcess"] == "biasWrapper(compt)") - & (arr["KineticEnergy"] > 0.1) - & (arr["ParticleName"] == "gamma") - & (arr["Weight"] > 10 ** (-20)) - ] - ekin_tracks = Tracks["KineticEnergy"] - x_vertex = Tracks["TrackVertexPosition_X"] - y_vertex = Tracks["TrackVertexPosition_Y"] - z_vertex = Tracks["TrackVertexPosition_Z"] - x = Tracks["PrePosition_X"] - y = Tracks["PrePosition_Y"] - z = Tracks["PrePosition_Z"] - weights = Tracks["Weight"] * nb_split - dist = np.sqrt((x - x_vertex) ** 2 + (y - y_vertex) ** 2 + (z - z_vertex) ** 2) - - G4_mu = -np.log(weights) / (0.1 * dist * 19.3) - X_com_mu = np.exp(f_mu(np.log(ekin_tracks))) - diff = (G4_mu - X_com_mu) / G4_mu - print( - "Median difference between mu calculated from XCOM database and from GEANT4 free flight operation:", - np.round(100 * np.median(diff), 1), - "%", - ) - return np.median(diff) < tol - - -if __name__ == "__main__": - paths = utility.get_default_test_paths( - __file__, "test072_pseudo_transportation", output_folder="test072" - ) - - # create the simulation - sim = gate.Simulation() - - # main options - ui = sim.user_info - ui.g4_verbose = False - - # ui.visu = True - # ui.visu_type = "vrml" - ui.check_volumes_overlap = False - # ui.running_verbose_level = gate.EVENT - ui.number_of_threads = 1 - ui.random_seed = "auto" - - # units - m = gate.g4_units.m - km = gate.g4_units.km - mm = gate.g4_units.mm - um = gate.g4_units.um - cm = gate.g4_units.cm - nm = gate.g4_units.nm - Bq = gate.g4_units.Bq - MeV = gate.g4_units.MeV - keV = gate.g4_units.keV - gcm3 = gate.g4_units.g / gate.g4_units.cm3 - deg = gate.g4_units.deg - - # adapt world size - world = sim.world - world.size = [1.2 * m, 1.2 * m, 2 * m] - world.material = "G4_Galactic" - - ####### GEOMETRY TO IRRADIATE ############# - sim.volume_manager.material_database.add_material_weights( - "Tungsten", - ["W"], - [1], - 19.3 * gcm3, - ) - simple_collimation = sim.add_volume("Box", "colli_box") - simple_collimation.material = "G4_Galactic" - simple_collimation.mother = world.name - simple_collimation.size = [1 * m, 1 * m, 40 * cm] - simple_collimation.color = [0.3, 0.1, 0.8, 1] - - W_leaf = sim.add_volume("Box", "W_leaf") - W_leaf.mother = simple_collimation.name - W_leaf.size = [1 * m, 1 * m, 2 * cm] - W_leaf.material = "Tungsten" - leaf_translation = [] - for i in range(10): - leaf_translation.append( - [ - 0, - 0, - -0.5 * simple_collimation.size[2] - + 0.5 * W_leaf.size[2] - + i * W_leaf.size[2], - ] - ) - print(leaf_translation) - W_leaf.translation = leaf_translation - W_leaf.color = [0.8, 0.2, 0.1, 1] - - ######## pseudo_transportation ACTOR ######### - nb_split = 5 - pseudo_transportation_actor = sim.add_actor( - "ComptPseudoTransportationActor", "pseudo_transportation_actor" - ) - pseudo_transportation_actor.mother = simple_collimation.name - pseudo_transportation_actor.splitting_factor = nb_split - pseudo_transportation_actor.relative_min_weight_of_particle = np.inf - list_processes_to_bias = pseudo_transportation_actor.gamma_processes - - ##### PHASE SPACE plan ######" - plan = sim.add_volume("Box", "phsp") - plan.material = "G4_Galactic" - plan.mother = world.name - plan.size = [1 * m, 1 * m, 1 * nm] - plan.color = [0.2, 1, 0.8, 1] - plan.translation = [0, 0, -20 * cm - 1 * nm] - - ####### gamma source ########### - source = sim.add_source("GenericSource", "source1") - source.particle = "gamma" - source.n = 1000 - source.position.type = "sphere" - source.position.radius = 1 * nm - source.direction.type = "momentum" - source.direction.momentum = [0, 0, -1] - source.energy.type = "mono" - source.energy.mono = 6 * MeV - source.position.translation = [0, 0, 18 * cm] - - ####### PHASE SPACE ACTOR ############## - - phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") - phsp_actor.mother = plan.name - phsp_actor.attributes = [ - "EventID", - "TrackCreatorProcess", - "TrackVertexPosition", - "PrePosition", - "Weight", - "KineticEnergy", - "ParentID", - "ParticleName", - ] - - phsp_actor.output = paths.output / "test072_output_data.root" - - ##### MODIFIED PHYSICS LIST ############### - - s = sim.add_actor("SimulationStatisticsActor", "Stats") - s.track_types_flag = True - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" - ### Perhaps avoid the user to call the below boolean function ? ### - sim.physics_manager.special_physics_constructors.G4GenericBiasingPhysics = True - sim.physics_manager.processes_to_bias.gamma = list_processes_to_bias - s = f"/process/em/UseGeneralProcess false" - sim.add_g4_command_before_init(s) - - sim.physics_manager.global_production_cuts.gamma = 1 * mm - sim.physics_manager.global_production_cuts.electron = 1 * km - sim.physics_manager.global_production_cuts.positron = 1 * km - - output = sim.run() - - # - # # print results - stats = sim.output.get_actor("Stats") - h = sim.output.get_actor("PhaseSpace") - print(stats) - # - f_phsp = uproot.open(paths.output / "test072_output_data.root") - data_NIST_W = np.loadtxt(paths.data / "NIST_W.txt", delimiter="|") - arr = f_phsp["PhaseSpace"].arrays() - # - is_ok = validation_test(arr, data_NIST_W, nb_split) - utility.test_ok(is_ok) diff --git a/opengate/tests/src/test074_kill_non_interacting_particles.py b/opengate/tests/src/test074_kill_non_interacting_particles.py deleted file mode 100644 index 36616f210..000000000 --- a/opengate/tests/src/test074_kill_non_interacting_particles.py +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import opengate as gate -from opengate.tests import utility -from scipy.spatial.transform import Rotation -import numpy as np -from anytree import Node, RenderTree -import uproot - - -def test074_test(entry_data, exit_data_1, exit_data_2): - liste_ekin = [] - liste_evtID = [] - liste_trackID = [] - evt_ID_entry_data = entry_data["EventID"] - j = 0 - i = 0 - while i < len(evt_ID_entry_data): - if ( - j < len(exit_data_1["EventID"]) - and evt_ID_entry_data[i] == exit_data_1["EventID"][j] - ): - TID_entry = entry_data["TrackID"][i] - TID_exit = exit_data_1["TrackID"][j] - Ekin_entry = entry_data["KineticEnergy"][i] - Ekin_exit = exit_data_1["KineticEnergy"][j] - - if (TID_entry == TID_exit) and (Ekin_exit == Ekin_entry): - liste_ekin.append(exit_data_1["KineticEnergy"][j]) - liste_evtID.append(exit_data_1["EventID"][j]) - liste_trackID.append(exit_data_1["TrackID"][j]) - if (j < len(exit_data_1["EventID"]) - 1) and ( - exit_data_1["EventID"][j] == exit_data_1["EventID"][j + 1] - ): - i = i - 1 - j += 1 - i += 1 - liste_ekin = np.asarray(liste_ekin) - print("Number of tracks to kill =", len(liste_ekin)) - print( - "Number of killed tracks =", - (len(exit_data_1["EventID"]) - len(exit_data_2["EventID"])), - ) - - return len(liste_ekin) == ( - len(exit_data_1["EventID"]) - len(exit_data_2["EventID"]) - ) - - -if __name__ == "__main__": - paths = utility.get_default_test_paths(__file__) - output_path = paths.output - - print(output_path) - # create the simulation - sim = gate.Simulation() - - # main options - ui = sim.user_info - ui.g4_verbose = False - # ui.visu = True - ui.visu_type = "vrml" - ui.check_volumes_overlap = False - # ui.running_verbose_level = gate.logger.EVENT - ui.number_of_threads = 1 - ui.random_seed = "auto" - - # units - m = gate.g4_units.m - km = gate.g4_units.km - mm = gate.g4_units.mm - cm = gate.g4_units.cm - nm = gate.g4_units.nm - Bq = gate.g4_units.Bq - MeV = gate.g4_units.MeV - keV = gate.g4_units.keV - sec = gate.g4_units.s - gcm3 = gate.g4_units["g/cm3"] - - sim.volume_manager.material_database.add_material_weights( - "Tungsten", - ["W"], - [1], - 19.3 * gcm3, - ) - - # adapt world size - world = sim.world - world.size = [1 * m, 1 * m, 1 * m] - world.material = "G4_AIR" - - big_box = sim.add_volume("Box", "big_box") - big_box.mother = world.name - big_box.material = "G4_AIR" - big_box.size = [0.8 * m, 0.8 * m, 0.8 * m] - - actor_box = sim.add_volume("Box", "actor_box") - actor_box.mother = big_box.name - actor_box.material = "G4_AIR" - actor_box.size = [0.6 * m, 0.6 * m, 0.6 * m] - actor_box.translation = [0, 0, -0.1 * m] - - source = sim.add_source("GenericSource", "photon_source") - source.particle = "gamma" - source.position.type = "box" - source.mother = world.name - source.position.size = [6 * cm, 6 * cm, 6 * cm] - source.position.translation = [0, 0, 0.3 * m] - source.direction.type = "momentum" - source.force_rotation = True - # source1.direction.focus_point = [0*cm, 0*cm, -5 *cm] - source.direction.momentum = [0, 0, -1] - source.energy.type = "mono" - source.energy.mono = 6 * MeV - source.n = 1000 - - tungsten_leaves = sim.add_volume("Box", "tungsten_leaves") - tungsten_leaves.mother = actor_box - tungsten_leaves.size = [0.6 * m, 0.6 * m, 0.3 * cm] - tungsten_leaves.material = "Tungsten" - liste_translation_W = [] - for i in range(7): - liste_translation_W.append([0, 0, 0.25 * m - i * 6 * cm]) - tungsten_leaves.translation = liste_translation_W - tungsten_leaves.color = [0.9, 0.0, 0.4, 0.8] - - kill_No_int_act = sim.add_actor("KillNonInteractingParticleActor", "killact") - kill_No_int_act.mother = actor_box.name - - entry_phase_space = sim.add_volume("Box", "entry_phase_space") - entry_phase_space.mother = big_box - entry_phase_space.size = [0.8 * m, 0.8 * m, 1 * nm] - entry_phase_space.material = "G4_AIR" - entry_phase_space.translation = [0, 0, 0.21 * m] - entry_phase_space.color = [0.5, 0.9, 0.3, 1] - - exit_phase_space_1 = sim.add_volume("Box", "exit_phase_space_1") - exit_phase_space_1.mother = actor_box - exit_phase_space_1.size = [0.6 * m, 0.6 * m, 1 * nm] - exit_phase_space_1.material = "G4_AIR" - exit_phase_space_1.translation = [0, 0, -0.3 * m + 1 * nm] - exit_phase_space_1.color = [0.5, 0.9, 0.3, 1] - - exit_phase_space_2 = sim.add_volume("Box", "exit_phase_space_2") - exit_phase_space_2.mother = world.name - exit_phase_space_2.size = [0.6 * m, 0.6 * m, 1 * nm] - exit_phase_space_2.material = "G4_AIR" - exit_phase_space_2.translation = [0, 0, -0.4 * m - 1 * nm] - exit_phase_space_2.color = [0.5, 0.9, 0.3, 1] - - # print(sim.volume_manager.dump_volume_tree()) - liste_phase_space_name = [ - entry_phase_space.name, - exit_phase_space_1.name, - exit_phase_space_2.name, - ] - for name in liste_phase_space_name: - - phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_" + name) - phsp.mother = name - phsp.attributes = ["EventID", "TrackID", "KineticEnergy"] - name_phsp = "test074_" + name + ".root" - phsp.output = output_path / name_phsp - - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" - sim.physics_manager.enable_decay = False - sim.physics_manager.global_production_cuts.gamma = 1 * mm - sim.physics_manager.global_production_cuts.electron = 1 * mm - sim.physics_manager.global_production_cuts.positron = 1 * mm - - s = sim.add_actor("SimulationStatisticsActor", "Stats") - s.track_types_flag = True - - # go ! - sim.run() - output = sim.output - stats = sim.output.get_actor("Stats") - print(stats) - - entry_phsp = uproot.open( - str(output_path) - + "/test074_" - + liste_phase_space_name[0] - + ".root" - + ":PhaseSpace_" - + liste_phase_space_name[0] - ) - exit_phase_space_1 = uproot.open( - str(output_path) - + "/test074_" - + liste_phase_space_name[1] - + ".root" - + ":PhaseSpace_" - + liste_phase_space_name[1] - ) - exit_phase_space_2 = uproot.open( - str(output_path) - + "/test074_" - + liste_phase_space_name[2] - + ".root" - + ":PhaseSpace_" - + liste_phase_space_name[2] - ) - - df_entry = entry_phsp.arrays() - df_exit_1 = exit_phase_space_1.arrays() - df_exit_2 = exit_phase_space_2.arrays() - - is_ok = test074_test(df_entry, df_exit_1, df_exit_2) - - utility.test_ok(is_ok) diff --git a/opengate/tests/src/test075_geometrical_splitting_volume_surface.py b/opengate/tests/src/test075_geometrical_splitting_volume_surface.py deleted file mode 100644 index 58af57f28..000000000 --- a/opengate/tests/src/test075_geometrical_splitting_volume_surface.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import opengate as gate -from opengate.tests import utility -from scipy.spatial.transform import Rotation -import numpy as np -from anytree import Node, RenderTree -import uproot - - -def test075(entry_data, exit_data, splitting_factor): - splitted_particle_data_entry = entry_data[ - entry_data["TrackCreatorProcess"] == "none" - ] - - splitted_particle_data_exit = exit_data[exit_data["TrackCreatorProcess"] == "none"] - - array_weight_1 = splitted_particle_data_entry["Weight"] - array_weight_2 = splitted_particle_data_exit["Weight"] - - if (np.round(np.sum(array_weight_1), 3) == 1) and len( - array_weight_1 - ) == splitting_factor: - if (np.round(np.sum(array_weight_2), 3) == 1) and len( - array_weight_2 - ) == splitting_factor: - return True - else: - return False - else: - return False - - -if __name__ == "__main__": - paths = utility.get_default_test_paths(__file__) - output_path = paths.output - - print(output_path) - # create the simulation - sim = gate.Simulation() - - # main options - ui = sim.user_info - ui.g4_verbose = False - # ui.visu = True - ui.visu_type = "vrml" - ui.check_volumes_overlap = False - # ui.running_verbose_level = gate.logger.EVENT - ui.number_of_threads = 1 - ui.random_seed = "auto" - - # units - m = gate.g4_units.m - km = gate.g4_units.km - mm = gate.g4_units.mm - cm = gate.g4_units.cm - nm = gate.g4_units.nm - Bq = gate.g4_units.Bq - MeV = gate.g4_units.MeV - keV = gate.g4_units.keV - sec = gate.g4_units.s - gcm3 = gate.g4_units["g/cm3"] - - # adapt world size - world = sim.world - world.size = [1 * m, 1 * m, 1 * m] - world.material = "G4_Galactic" - - big_box = sim.add_volume("Box", "big_box") - big_box.mother = world.name - big_box.material = "G4_Galactic" - big_box.size = [0.8 * m, 0.8 * m, 0.8 * m] - - actor_box = sim.add_volume("Box", "actor_box") - actor_box.mother = big_box.name - actor_box.material = "G4_Galactic" - actor_box.size = [0.6 * m, 0.6 * m, 0.6 * m] - actor_box.translation = [0, 0, -0.1 * m] - - source_1 = sim.add_source("GenericSource", "elec_source_1") - source_1.particle = "e-" - source_1.position.type = "box" - source_1.mother = big_box.name - source_1.position.size = [1 * cm, 1 * cm, 1 * cm] - source_1.position.translation = [0, 0.35 * m, 0] - source_1.direction.type = "momentum" - source_1.direction.momentum = [0, -1, 0] - source_1.energy.type = "mono" - source_1.energy.mono = 10 * MeV - source_1.n = 1 - - source_2 = sim.add_source("GenericSource", "elec_source_2") - source_2.particle = "e-" - source_2.position.type = "box" - source_2.mother = big_box.name - source_2.position.size = [1 * cm, 1 * cm, 1 * cm] - source_2.position.translation = [0, 0, -0.39 * m] - source_2.direction.type = "momentum" - source_2.direction.momentum = [0, 0, -1] - source_2.energy.type = "mono" - source_2.energy.mono = 10 * MeV - source_2.n = 1 - - geom_splitting = sim.add_actor("SurfaceSplittingActor", "splitting_act") - geom_splitting.mother = actor_box.name - geom_splitting.splitting_factor = 10 - geom_splitting.weight_threshold = 1 - geom_splitting.split_entering_particles = True - geom_splitting.split_exiting_particles = True - - entry_phase_space = sim.add_volume("Box", "entry_phase_space") - entry_phase_space.mother = actor_box - entry_phase_space.size = [0.6 * m, 1 * nm, 0.6 * m] - entry_phase_space.material = "G4_Galactic" - entry_phase_space.translation = [0, 0.3 * m - 0.5 * nm, 0] - entry_phase_space.color = [0.5, 0.9, 0.3, 1] - - exit_phase_space = sim.add_volume("Box", "exit_phase_space") - exit_phase_space.mother = world.name - exit_phase_space.size = [0.6 * m, 0.6 * m, 1 * nm] - exit_phase_space.material = "G4_Galactic" - exit_phase_space.translation = [0, 0, -0.4 * m - 1 * nm] - exit_phase_space.color = [0.5, 0.9, 0.3, 1] - - # # print(sim.volume_manager.dump_volume_tree()) - liste_phase_space_name = [ - entry_phase_space.name, - exit_phase_space.name, - ] - for name in liste_phase_space_name: - - phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_" + name) - phsp.mother = name - phsp.attributes = [ - "EventID", - "TrackID", - "Weight", - "PDGCode", - "TrackCreatorProcess", - ] - name_phsp = "test075_" + name + ".root" - phsp.output = output_path / name_phsp - - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" - sim.physics_manager.enable_decay = False - sim.physics_manager.global_production_cuts.gamma = 1 * mm - sim.physics_manager.global_production_cuts.electron = 1 * mm - sim.physics_manager.global_production_cuts.positron = 1 * mm - - s = sim.add_actor("SimulationStatisticsActor", "Stats") - s.track_types_flag = True - - # go ! - sim.run() - output = sim.output - stats = sim.output.get_actor("Stats") - print(stats) - - # - entry_phsp = uproot.open( - str(output_path) - + "/test075_" - + liste_phase_space_name[0] - + ".root" - + ":PhaseSpace_" - + liste_phase_space_name[0] - ) - exit_phase_space = uproot.open( - str(output_path) - + "/test075_" - + liste_phase_space_name[1] - + ".root" - + ":PhaseSpace_" - + liste_phase_space_name[1] - ) - - # - df_entry = entry_phsp.arrays() - df_exit = exit_phase_space.arrays() - # - - is_ok = test075(df_entry, df_exit, geom_splitting.splitting_factor) - - utility.test_ok(is_ok) diff --git a/opengate/tests/src/test077_kill_interacting_particles.py b/opengate/tests/src/test077_kill_interacting_particles.py deleted file mode 100644 index fc342cdf5..000000000 --- a/opengate/tests/src/test077_kill_interacting_particles.py +++ /dev/null @@ -1,213 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import opengate as gate -from opengate.tests import utility -from scipy.spatial.transform import Rotation -import numpy as np -from anytree import Node, RenderTree -import uproot - - -def test077_test(entry_data, exit_data_1, exit_data_2): - - liste_ekin = [] - liste_evtID = [] - liste_trackID = [] - evt_ID_entry_data = entry_data["EventID"] - j = 0 - i = 0 - while i < len(evt_ID_entry_data): - if ( - j < len(exit_data_1["EventID"]) - and evt_ID_entry_data[i] == exit_data_1["EventID"][j] - ): - TID_entry = entry_data["TrackID"][i] - TID_exit = exit_data_1["TrackID"][j] - Ekin_entry = entry_data["KineticEnergy"][i] - Ekin_exit = exit_data_1["KineticEnergy"][j] - - if (TID_entry != TID_exit) or (Ekin_exit != Ekin_entry): - liste_ekin.append(exit_data_1["KineticEnergy"][j]) - liste_evtID.append(exit_data_1["EventID"][j]) - liste_trackID.append(exit_data_1["TrackID"][j]) - if (j < len(exit_data_1["EventID"]) - 1) and ( - exit_data_1["EventID"][j] == exit_data_1["EventID"][j + 1] - ): - i = i - 1 - j += 1 - i += 1 - liste_ekin = np.asarray(liste_ekin) - print("Number of tracks to kill =", len(liste_ekin)) - print( - "Number of killed tracks =", - (len(exit_data_1["EventID"]) - len(exit_data_2["EventID"])), - ) - - return len(liste_ekin) == ( - len(exit_data_1["EventID"]) - len(exit_data_2["EventID"]) - ) - - -if __name__ == "__main__": - paths = utility.get_default_test_paths(__file__) - output_path = paths.output - - print(output_path) - # create the simulation - sim = gate.Simulation() - - # main options - ui = sim.user_info - ui.g4_verbose = False - # ui.visu = True - ui.visu_type = "vrml" - ui.check_volumes_overlap = False - # ui.running_verbose_level = gate.logger.EVENT - ui.number_of_threads = 1 - ui.random_seed = "auto" - - # units - m = gate.g4_units.m - km = gate.g4_units.km - mm = gate.g4_units.mm - cm = gate.g4_units.cm - nm = gate.g4_units.nm - Bq = gate.g4_units.Bq - MeV = gate.g4_units.MeV - keV = gate.g4_units.keV - sec = gate.g4_units.s - gcm3 = gate.g4_units["g/cm3"] - - sim.volume_manager.material_database.add_material_weights( - "Tungsten", - ["W"], - [1], - 19.3 * gcm3, - ) - - # adapt world size - world = sim.world - world.size = [1 * m, 1 * m, 1 * m] - world.material = "G4_AIR" - - big_box = sim.add_volume("Box", "big_box") - big_box.mother = world.name - big_box.material = "G4_AIR" - big_box.size = [0.8 * m, 0.8 * m, 0.8 * m] - - actor_box = sim.add_volume("Box", "actor_box") - actor_box.mother = big_box.name - actor_box.material = "G4_AIR" - actor_box.size = [0.6 * m, 0.6 * m, 0.6 * m] - actor_box.translation = [0, 0, -0.1 * m] - - source = sim.add_source("GenericSource", "photon_source") - source.particle = "gamma" - source.position.type = "box" - source.mother = world.name - source.position.size = [6 * cm, 6 * cm, 6 * cm] - source.position.translation = [0, 0, 0.3 * m] - source.direction.type = "momentum" - source.force_rotation = True - # source1.direction.focus_point = [0*cm, 0*cm, -5 *cm] - source.direction.momentum = [0, 0, -1] - source.energy.type = "mono" - source.energy.mono = 6 * MeV - source.n = 1000 - - tungsten_leaves = sim.add_volume("Box", "tungsten_leaves") - tungsten_leaves.mother = actor_box - tungsten_leaves.size = [0.6 * m, 0.6 * m, 0.3 * cm] - tungsten_leaves.material = "Tungsten" - liste_translation_W = [] - for i in range(7): - liste_translation_W.append([0, 0, 0.25 * m - i * 6 * cm]) - tungsten_leaves.translation = liste_translation_W - tungsten_leaves.color = [0.9, 0.0, 0.4, 0.8] - - kill_int_act = sim.add_actor("KillInteractingParticleActor", "killact") - kill_int_act.mother = actor_box.name - - entry_phase_space = sim.add_volume("Box", "entry_phase_space") - entry_phase_space.mother = big_box - entry_phase_space.size = [0.8 * m, 0.8 * m, 1 * nm] - entry_phase_space.material = "G4_AIR" - entry_phase_space.translation = [0, 0, 0.21 * m] - entry_phase_space.color = [0.5, 0.9, 0.3, 1] - - exit_phase_space_1 = sim.add_volume("Box", "exit_phase_space_1") - exit_phase_space_1.mother = actor_box - exit_phase_space_1.size = [0.6 * m, 0.6 * m, 1 * nm] - exit_phase_space_1.material = "G4_AIR" - exit_phase_space_1.translation = [0, 0, -0.3 * m + 1 * nm] - exit_phase_space_1.color = [0.5, 0.9, 0.3, 1] - - exit_phase_space_2 = sim.add_volume("Box", "exit_phase_space_2") - exit_phase_space_2.mother = world.name - exit_phase_space_2.size = [0.6 * m, 0.6 * m, 1 * nm] - exit_phase_space_2.material = "G4_AIR" - exit_phase_space_2.translation = [0, 0, -0.4 * m - 1 * nm] - exit_phase_space_2.color = [0.5, 0.9, 0.3, 1] - - # print(sim.volume_manager.dump_volume_tree()) - liste_phase_space_name = [ - entry_phase_space.name, - exit_phase_space_1.name, - exit_phase_space_2.name, - ] - for name in liste_phase_space_name: - - phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace_" + name) - phsp.mother = name - phsp.attributes = ["EventID", "TrackID", "KineticEnergy"] - name_phsp = "test077_" + name + ".root" - phsp.output = output_path / name_phsp - - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" - sim.physics_manager.enable_decay = False - sim.physics_manager.global_production_cuts.gamma = 1 * mm - sim.physics_manager.global_production_cuts.electron = 1 * mm - sim.physics_manager.global_production_cuts.positron = 1 * mm - - s = sim.add_actor("SimulationStatisticsActor", "Stats") - s.track_types_flag = True - - # go ! - sim.run() - output = sim.output - stats = sim.output.get_actor("Stats") - print(stats) - - entry_phsp = uproot.open( - str(output_path) - + "/test077_" - + liste_phase_space_name[0] - + ".root" - + ":PhaseSpace_" - + liste_phase_space_name[0] - ) - exit_phase_space_1 = uproot.open( - str(output_path) - + "/test077_" - + liste_phase_space_name[1] - + ".root" - + ":PhaseSpace_" - + liste_phase_space_name[1] - ) - exit_phase_space_2 = uproot.open( - str(output_path) - + "/test077_" - + liste_phase_space_name[2] - + ".root" - + ":PhaseSpace_" - + liste_phase_space_name[2] - ) - - df_entry = entry_phsp.arrays() - df_exit_1 = exit_phase_space_1.arrays() - df_exit_2 = exit_phase_space_2.arrays() - - is_ok = test077_test(df_entry, df_exit_1, df_exit_2) - - utility.test_ok(is_ok) diff --git a/opengate/tests/src/test084_last_vertex_splittting.py b/opengate/tests/src/test084_last_vertex_splittting.py deleted file mode 100755 index 390c5d1ae..000000000 --- a/opengate/tests/src/test084_last_vertex_splittting.py +++ /dev/null @@ -1,222 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import opengate as gate -import uproot -import numpy as np -from scipy.spatial.transform import Rotation -from opengate.tests import utility - - -def validation_test(arr_ref, arr_data, nb_split): - arr_ref = arr_ref[arr_ref["ParticleName"] == "gamma"] - arr_data = arr_data[arr_data["ParticleName"] == "gamma"] - - weight_data = np.round(np.mean(arr_data["Weight"]), 4) - bool_weight = False - print( - "Weight mean is equal to", weight_data, "and need to be equal to", 1 / nb_split - ) - if weight_data == 1 / nb_split: - bool_weight = True - - bool_events = False - sigma = np.sqrt((len(arr_data["KineticEnergy"]) / nb_split)) * nb_split - nb_events_ref = len(arr_ref["KineticEnergy"]) - nb_events_data = len(arr_data["KineticEnergy"]) - print("Reference counts number:", nb_events_ref) - print("Biased counts number:", nb_events_data) - if nb_events_data - 4 * sigma <= nb_events_ref <= nb_events_data + 4 * sigma: - bool_events = True - - keys = [ - "KineticEnergy", - "PreDirection_X", - "PreDirection_Y", - "PreDirection_Z", - "PrePosition_X", - "PrePosition_Y", - ] - - bool_distrib = True - for key in keys: - ref = arr_ref[key] - data = arr_data[key] - - mean_ref = np.mean(ref) - mean_data = np.mean(data) - - std_dev_ref = np.std(ref, ddof=1) - std_dev_data = np.std(data, ddof=1) - - std_err_ref = std_dev_ref / np.sqrt(len(ref)) - std_err_data = std_dev_data / (nb_split * np.sqrt(len(data) / nb_split)) - - print( - key, - "mean ref value:", - np.round(mean_ref, 3), - "+-", - np.round(std_err_ref, 3), - ) - print( - key, - "mean data value:", - np.round(mean_data, 3), - "+-", - np.round(std_err_data, 3), - ) - - if ( - mean_data - 4 * np.sqrt(std_err_data**2 + std_err_ref**2) > mean_ref - ) or (mean_data + 4 * np.sqrt(std_err_data**2 + std_err_ref**2) < mean_ref): - bool_distrib = False - - if bool_distrib and bool_events and bool_weight: - return True - else: - return False - - -if __name__ == "__main__": - for i in range(2): - if i == 0: - bias = False - else: - bias = True - paths = utility.get_default_test_paths( - __file__, "test084_last_vertex_splitting", output_folder="test084" - ) - - # create the simulation - sim = gate.Simulation() - - # main options - ui = sim.user_info - ui.g4_verbose = False - ui.visu = False - ui.visu_type = "vrml" - ui.check_volumes_overlap = False - ui.number_of_threads = 1 - ui.random_seed = 123456789 - - # units - m = gate.g4_units.m - km = gate.g4_units.km - mm = gate.g4_units.mm - um = gate.g4_units.um - cm = gate.g4_units.cm - nm = gate.g4_units.nm - Bq = gate.g4_units.Bq - MeV = gate.g4_units.MeV - keV = gate.g4_units.keV - deg = gate.g4_units.deg - gcm3 = gate.g4_units.g / gate.g4_units.cm3 - - # adapt world size - world = sim.world - world.size = [0.25 * m, 0.25 * m, 0.25 * m] - world.material = "G4_Galactic" - - ####### GEOMETRY TO IRRADIATE ############# - sim.volume_manager.material_database.add_material_weights( - "Tungsten", - ["W"], - [1], - 19.3 * gcm3, - ) - - W_tubs = sim.add_volume("Tubs", "W_box") - W_tubs.material = "Tungsten" - W_tubs.mother = world.name - - W_tubs.rmin = 0 - W_tubs.rmax = 0.4 * cm - W_tubs.dz = 0.05 * m - W_tubs.color = [0.8, 0.2, 0.1, 1] - angle_x = 45 - angle_y = 70 - angle_z = 80 - - rotation = Rotation.from_euler( - "xyz", [angle_y, angle_y, angle_z], degrees=True - ).as_matrix() - W_tubs.rotation = rotation - - if bias: - ###### Last vertex Splitting ACTOR ######### - nb_split = 10 - vertex_splitting_actor = sim.add_actor( - "LastVertexInteractionSplittingActor", "vertexSplittingW" - ) - vertex_splitting_actor.attached_to = W_tubs.name - vertex_splitting_actor.splitting_factor = nb_split - vertex_splitting_actor.angular_kill = True - vertex_splitting_actor.vector_director = [0, 0, -1] - vertex_splitting_actor.max_theta = 90 * deg - vertex_splitting_actor.batch_size = 10 - - plan = sim.add_volume("Box", "plan_phsp") - plan.material = "G4_Galactic" - plan.size = [5 * cm, 5 * cm, 1 * nm] - plan.translation = [0, 0, -1 * cm] - - ####### gamma source ########### - source = sim.add_source("GenericSource", "source1") - source.particle = "gamma" - source.n = 100000 - if bias: - source.n = source.n / nb_split - - source.position.type = "sphere" - source.position.radius = 1 * nm - source.direction.type = "momentum" - # source.direction.momentum = [0,0,-1] - source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) - source.energy.type = "mono" - source.energy.mono = 4 * MeV - # - ###### LastVertexSource ############# - if bias: - source_0 = sim.add_source("LastVertexSource", "source_vertex") - source_0.n = 1 - - ####### PHASE SPACE ACTOR ############## - sim.output_dir = paths.output - phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") - phsp_actor.attached_to = plan.name - phsp_actor.attributes = [ - "EventID", - "TrackID", - "Weight", - "ParticleName", - "KineticEnergy", - "PreDirection", - "PrePosition", - "TrackCreatorProcess", - ] - if bias: - phsp_actor.output_filename = "test084_output_data_last_vertex_biased.root" - else: - phsp_actor.output_filename = "test084_output_data_last_vertex_ref.root" - - s = sim.add_actor("SimulationStatisticsActor", "Stats") - s.track_types_flag = True - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" - s = f"/process/em/UseGeneralProcess false" - sim.g4_commands_before_init.append(s) - - sim.physics_manager.global_production_cuts.gamma = 1 * mm - sim.physics_manager.global_production_cuts.electron = 1000 * km - sim.physics_manager.global_production_cuts.positron = 1000 * km - - output = sim.run(True) - print(s) - - f_data = uproot.open(paths.output / "test084_output_data_last_vertex_biased.root") - f_ref_data = uproot.open(paths.output / "test084_output_data_last_vertex_ref.root") - arr_data = f_data["PhaseSpace"].arrays() - arr_ref_data = f_ref_data["PhaseSpace"].arrays() - # # - is_ok = validation_test(arr_ref_data, arr_data, nb_split) - utility.test_ok(is_ok) diff --git a/opengate/tests/src/test084_last_vertex_splittting_angular_kill.py b/opengate/tests/src/test084_last_vertex_splittting_angular_kill.py deleted file mode 100755 index d1fa56cf2..000000000 --- a/opengate/tests/src/test084_last_vertex_splittting_angular_kill.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import opengate as gate -import uproot -import numpy as np -from scipy.spatial.transform import Rotation -from opengate.tests import utility - - -def validation_test(arr_data, vector_director, max_theta): - arr_data = arr_data[arr_data["ParticleName"] == "gamma"] - qt_mvt_data = arr_data[["PreDirection_X", "PreDirection_Y", "PreDirection_Z"]] - mom_data = np.zeros((len(qt_mvt_data["PreDirection_X"]), 3)) - mom_data[:, 0] += np.array(qt_mvt_data["PreDirection_X"]) - mom_data[:, 1] += np.array(qt_mvt_data["PreDirection_Y"]) - mom_data[:, 2] += np.array(qt_mvt_data["PreDirection_Z"]) - - l_theta = np.zeros(len(mom_data[:, 0])) - - for i in range(len(l_theta)): - theta = np.arccos(mom_data[i].dot(vector_director)) - l_theta[i] = theta - print( - "Number of particles with an angle higher than max_theta:", - len(l_theta[l_theta > max_theta]), - ) - if len(l_theta[l_theta > max_theta]) == 0: - return True - else: - return False - - -if __name__ == "__main__": - bias = True - paths = utility.get_default_test_paths( - __file__, "test084_last_vertex_splitting", output_folder="test084" - ) - - # create the simulation - sim = gate.Simulation() - - # main options - ui = sim.user_info - ui.g4_verbose = False - ui.visu = False - ui.visu_type = "vrml" - ui.check_volumes_overlap = False - ui.number_of_threads = 1 - ui.random_seed = 123456789 - - # units - m = gate.g4_units.m - km = gate.g4_units.km - mm = gate.g4_units.mm - um = gate.g4_units.um - cm = gate.g4_units.cm - nm = gate.g4_units.nm - Bq = gate.g4_units.Bq - MeV = gate.g4_units.MeV - keV = gate.g4_units.keV - deg = gate.g4_units.deg - gcm3 = gate.g4_units.g / gate.g4_units.cm3 - - # adapt world size - world = sim.world - world.size = [0.25 * m, 0.25 * m, 0.25 * m] - world.material = "G4_Galactic" - - ####### GEOMETRY TO IRRADIATE ############# - sim.volume_manager.material_database.add_material_weights( - "Tungsten", - ["W"], - [1], - 19.3 * gcm3, - ) - - W_tubs = sim.add_volume("Tubs", "W_box") - W_tubs.material = "Tungsten" - W_tubs.mother = world.name - - W_tubs.rmin = 0 - W_tubs.rmax = 0.4 * cm - W_tubs.dz = 0.05 * m - W_tubs.color = [0.8, 0.2, 0.1, 1] - angle_x = 45 - angle_y = 70 - angle_z = 80 - - rotation = Rotation.from_euler( - "xyz", [angle_y, angle_y, angle_z], degrees=True - ).as_matrix() - W_tubs.rotation = rotation - - if bias: - ###### Last vertex Splitting ACTOR ######### - nb_split = 10 - vertex_splitting_actor = sim.add_actor( - "LastVertexInteractionSplittingActor", "vertexSplittingW" - ) - vertex_splitting_actor.attached_to = W_tubs.name - vertex_splitting_actor.splitting_factor = nb_split - vertex_splitting_actor.angular_kill = True - vertex_splitting_actor.vector_director = [0, 0, -1] - vertex_splitting_actor.max_theta = 15 * deg - vertex_splitting_actor.batch_size = 10 - - plan = sim.add_volume("Box", "plan_phsp") - plan.material = "G4_Galactic" - plan.size = [5 * cm, 5 * cm, 1 * nm] - plan.translation = [0, 0, -1 * cm] - - if bias: - vector_director = np.array(vertex_splitting_actor.vector_director) - - ####### gamma source ########### - source = sim.add_source("GenericSource", "source1") - source.particle = "gamma" - source.n = 100000 - if bias: - source.n = source.n / nb_split - - source.position.type = "sphere" - source.position.radius = 1 * nm - source.direction.type = "momentum" - # source.direction.momentum = [0,0,-1] - source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) - source.energy.type = "mono" - source.energy.mono = 4 * MeV - - ###### LastVertexSource ############# - if bias: - source_0 = sim.add_source("LastVertexSource", "source_vertex") - source_0.n = 1 - - ####### PHASE SPACE ACTOR ############## - sim.output_dir = paths.output - phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") - phsp_actor.attached_to = plan.name - phsp_actor.attributes = [ - "EventID", - "TrackID", - "Weight", - "ParticleName", - "KineticEnergy", - "PreDirection", - "PrePosition", - "TrackCreatorProcess", - ] - if bias: - phsp_actor.output_filename = "test084_output_data_last_vertex_angular_kill.root" - - s = sim.add_actor("SimulationStatisticsActor", "Stats") - s.track_types_flag = True - sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" - s = f"/process/em/UseGeneralProcess false" - sim.g4_commands_before_init.append(s) - - sim.physics_manager.global_production_cuts.gamma = 1 * mm - sim.physics_manager.global_production_cuts.electron = 1000 * km - sim.physics_manager.global_production_cuts.positron = 1000 * km - - output = sim.run() - print(s) - - f_data = uproot.open( - paths.output / "test084_output_data_last_vertex_angular_kill.root" - ) - arr_data = f_data["PhaseSpace"].arrays() - is_ok = validation_test(arr_data, vector_director, vertex_splitting_actor.max_theta) - utility.test_ok(is_ok) From 20fa7cbd996f0c839ed3c105011d1bbe0e0d5d19 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 19 Nov 2024 10:49:29 +0100 Subject: [PATCH 58/82] source refactoring according to latest opengate release --- opengate/actors/miscactors.py | 2 +- opengate/managers.py | 2 + opengate/sources/generic.py | 17 -- opengate/sources/lastvertexsources.py | 33 +++ .../src/test082_last_vertex_splittting.py | 222 ++++++++++++++++++ ...082_last_vertex_splittting_angular_kill.py | 171 ++++++++++++++ 6 files changed, 429 insertions(+), 18 deletions(-) create mode 100644 opengate/sources/lastvertexsources.py create mode 100755 opengate/tests/src/test082_last_vertex_splittting.py create mode 100755 opengate/tests/src/test082_last_vertex_splittting_angular_kill.py diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 0902e756d..d43efb423 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -461,7 +461,7 @@ def __initcpp__(self): def initialize(self): ActorBase.initialize(self) - self.InitializeUserInput(self.user_info) + self.InitializeUserInfo(self.user_info) self.InitializeCpp() volume_tree = self.simulation.volume_manager.get_volume_tree() dico_of_volume_tree = {} diff --git a/opengate/managers.py b/opengate/managers.py index 25ceb15f0..71f6bbc2d 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -48,6 +48,7 @@ from .processing import dispatch_to_subprocess from .sources.generic import SourceBase, GenericSource +from .sources.lastvertexsources import LastVertexSource from .sources.phspsources import PhaseSpaceSource from .sources.voxelsources import VoxelSource from .sources.gansources import GANSource, GANPairsSource @@ -56,6 +57,7 @@ source_types = { "GenericSource": GenericSource, + "LastVertexSource":LastVertexSource, "PhaseSpaceSource": PhaseSpaceSource, "VoxelSource": VoxelSource, "GANSource": GANSource, diff --git a/opengate/sources/generic.py b/opengate/sources/generic.py index 1d32b9b57..d74b2c368 100644 --- a/opengate/sources/generic.py +++ b/opengate/sources/generic.py @@ -390,21 +390,4 @@ def initialize(self, run_timing_intervals): # initialize SourceBase.initialize(self, run_timing_intervals) - -class LastVertexSource(SourceBase): - type_name = "LastVertexSource" - - @staticmethod - def set_default_user_info(user_info): - SourceBase.set_default_user_info(user_info) - user_info.n = 0 - - def create_g4_source(self): - return opengate_core.GateLastVertexSource() - - def __init__(self, user_info): - super().__init__(user_info) - - def initialize(self, run_timing_intervals): - SourceBase.initialize(self, run_timing_intervals) process_cls(GenericSource) diff --git a/opengate/sources/lastvertexsources.py b/opengate/sources/lastvertexsources.py new file mode 100644 index 000000000..9578542b4 --- /dev/null +++ b/opengate/sources/lastvertexsources.py @@ -0,0 +1,33 @@ +from box import Box +from scipy.spatial.transform import Rotation + +import opengate_core as g4 +from .base import ( + SourceBase, + all_beta_plus_radionuclides, + read_beta_plus_spectra, + compute_cdf_and_total_yield, +) +from ..base import process_cls +from ..utility import g4_units +from ..exception import fatal, warning + + +class LastVertexSource(SourceBase,g4.GateLastVertexSource): + """ + The source used to replay position, energy, direction and weight of last vertex particles actor + """ + + def __init__(self, *args, **kwargs): + super().__init__(self, *args, **kwargs) + self.__initcpp__() + + def __initcpp__(self): + g4.GateLastVertexSource.__init__(self) + + def initialize(self, run_timing_intervals): + SourceBase.initialize(self, run_timing_intervals) + + +process_cls(LastVertexSource) + diff --git a/opengate/tests/src/test082_last_vertex_splittting.py b/opengate/tests/src/test082_last_vertex_splittting.py new file mode 100755 index 000000000..390c5d1ae --- /dev/null +++ b/opengate/tests/src/test082_last_vertex_splittting.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +import uproot +import numpy as np +from scipy.spatial.transform import Rotation +from opengate.tests import utility + + +def validation_test(arr_ref, arr_data, nb_split): + arr_ref = arr_ref[arr_ref["ParticleName"] == "gamma"] + arr_data = arr_data[arr_data["ParticleName"] == "gamma"] + + weight_data = np.round(np.mean(arr_data["Weight"]), 4) + bool_weight = False + print( + "Weight mean is equal to", weight_data, "and need to be equal to", 1 / nb_split + ) + if weight_data == 1 / nb_split: + bool_weight = True + + bool_events = False + sigma = np.sqrt((len(arr_data["KineticEnergy"]) / nb_split)) * nb_split + nb_events_ref = len(arr_ref["KineticEnergy"]) + nb_events_data = len(arr_data["KineticEnergy"]) + print("Reference counts number:", nb_events_ref) + print("Biased counts number:", nb_events_data) + if nb_events_data - 4 * sigma <= nb_events_ref <= nb_events_data + 4 * sigma: + bool_events = True + + keys = [ + "KineticEnergy", + "PreDirection_X", + "PreDirection_Y", + "PreDirection_Z", + "PrePosition_X", + "PrePosition_Y", + ] + + bool_distrib = True + for key in keys: + ref = arr_ref[key] + data = arr_data[key] + + mean_ref = np.mean(ref) + mean_data = np.mean(data) + + std_dev_ref = np.std(ref, ddof=1) + std_dev_data = np.std(data, ddof=1) + + std_err_ref = std_dev_ref / np.sqrt(len(ref)) + std_err_data = std_dev_data / (nb_split * np.sqrt(len(data) / nb_split)) + + print( + key, + "mean ref value:", + np.round(mean_ref, 3), + "+-", + np.round(std_err_ref, 3), + ) + print( + key, + "mean data value:", + np.round(mean_data, 3), + "+-", + np.round(std_err_data, 3), + ) + + if ( + mean_data - 4 * np.sqrt(std_err_data**2 + std_err_ref**2) > mean_ref + ) or (mean_data + 4 * np.sqrt(std_err_data**2 + std_err_ref**2) < mean_ref): + bool_distrib = False + + if bool_distrib and bool_events and bool_weight: + return True + else: + return False + + +if __name__ == "__main__": + for i in range(2): + if i == 0: + bias = False + else: + bias = True + paths = utility.get_default_test_paths( + __file__, "test084_last_vertex_splitting", output_folder="test084" + ) + + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + ui.visu = False + ui.visu_type = "vrml" + ui.check_volumes_overlap = False + ui.number_of_threads = 1 + ui.random_seed = 123456789 + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + um = gate.g4_units.um + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + deg = gate.g4_units.deg + gcm3 = gate.g4_units.g / gate.g4_units.cm3 + + # adapt world size + world = sim.world + world.size = [0.25 * m, 0.25 * m, 0.25 * m] + world.material = "G4_Galactic" + + ####### GEOMETRY TO IRRADIATE ############# + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + + W_tubs = sim.add_volume("Tubs", "W_box") + W_tubs.material = "Tungsten" + W_tubs.mother = world.name + + W_tubs.rmin = 0 + W_tubs.rmax = 0.4 * cm + W_tubs.dz = 0.05 * m + W_tubs.color = [0.8, 0.2, 0.1, 1] + angle_x = 45 + angle_y = 70 + angle_z = 80 + + rotation = Rotation.from_euler( + "xyz", [angle_y, angle_y, angle_z], degrees=True + ).as_matrix() + W_tubs.rotation = rotation + + if bias: + ###### Last vertex Splitting ACTOR ######### + nb_split = 10 + vertex_splitting_actor = sim.add_actor( + "LastVertexInteractionSplittingActor", "vertexSplittingW" + ) + vertex_splitting_actor.attached_to = W_tubs.name + vertex_splitting_actor.splitting_factor = nb_split + vertex_splitting_actor.angular_kill = True + vertex_splitting_actor.vector_director = [0, 0, -1] + vertex_splitting_actor.max_theta = 90 * deg + vertex_splitting_actor.batch_size = 10 + + plan = sim.add_volume("Box", "plan_phsp") + plan.material = "G4_Galactic" + plan.size = [5 * cm, 5 * cm, 1 * nm] + plan.translation = [0, 0, -1 * cm] + + ####### gamma source ########### + source = sim.add_source("GenericSource", "source1") + source.particle = "gamma" + source.n = 100000 + if bias: + source.n = source.n / nb_split + + source.position.type = "sphere" + source.position.radius = 1 * nm + source.direction.type = "momentum" + # source.direction.momentum = [0,0,-1] + source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) + source.energy.type = "mono" + source.energy.mono = 4 * MeV + # + ###### LastVertexSource ############# + if bias: + source_0 = sim.add_source("LastVertexSource", "source_vertex") + source_0.n = 1 + + ####### PHASE SPACE ACTOR ############## + sim.output_dir = paths.output + phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + phsp_actor.attached_to = plan.name + phsp_actor.attributes = [ + "EventID", + "TrackID", + "Weight", + "ParticleName", + "KineticEnergy", + "PreDirection", + "PrePosition", + "TrackCreatorProcess", + ] + if bias: + phsp_actor.output_filename = "test084_output_data_last_vertex_biased.root" + else: + phsp_actor.output_filename = "test084_output_data_last_vertex_ref.root" + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + s = f"/process/em/UseGeneralProcess false" + sim.g4_commands_before_init.append(s) + + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1000 * km + sim.physics_manager.global_production_cuts.positron = 1000 * km + + output = sim.run(True) + print(s) + + f_data = uproot.open(paths.output / "test084_output_data_last_vertex_biased.root") + f_ref_data = uproot.open(paths.output / "test084_output_data_last_vertex_ref.root") + arr_data = f_data["PhaseSpace"].arrays() + arr_ref_data = f_ref_data["PhaseSpace"].arrays() + # # + is_ok = validation_test(arr_ref_data, arr_data, nb_split) + utility.test_ok(is_ok) diff --git a/opengate/tests/src/test082_last_vertex_splittting_angular_kill.py b/opengate/tests/src/test082_last_vertex_splittting_angular_kill.py new file mode 100755 index 000000000..d1fa56cf2 --- /dev/null +++ b/opengate/tests/src/test082_last_vertex_splittting_angular_kill.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +import uproot +import numpy as np +from scipy.spatial.transform import Rotation +from opengate.tests import utility + + +def validation_test(arr_data, vector_director, max_theta): + arr_data = arr_data[arr_data["ParticleName"] == "gamma"] + qt_mvt_data = arr_data[["PreDirection_X", "PreDirection_Y", "PreDirection_Z"]] + mom_data = np.zeros((len(qt_mvt_data["PreDirection_X"]), 3)) + mom_data[:, 0] += np.array(qt_mvt_data["PreDirection_X"]) + mom_data[:, 1] += np.array(qt_mvt_data["PreDirection_Y"]) + mom_data[:, 2] += np.array(qt_mvt_data["PreDirection_Z"]) + + l_theta = np.zeros(len(mom_data[:, 0])) + + for i in range(len(l_theta)): + theta = np.arccos(mom_data[i].dot(vector_director)) + l_theta[i] = theta + print( + "Number of particles with an angle higher than max_theta:", + len(l_theta[l_theta > max_theta]), + ) + if len(l_theta[l_theta > max_theta]) == 0: + return True + else: + return False + + +if __name__ == "__main__": + bias = True + paths = utility.get_default_test_paths( + __file__, "test084_last_vertex_splitting", output_folder="test084" + ) + + # create the simulation + sim = gate.Simulation() + + # main options + ui = sim.user_info + ui.g4_verbose = False + ui.visu = False + ui.visu_type = "vrml" + ui.check_volumes_overlap = False + ui.number_of_threads = 1 + ui.random_seed = 123456789 + + # units + m = gate.g4_units.m + km = gate.g4_units.km + mm = gate.g4_units.mm + um = gate.g4_units.um + cm = gate.g4_units.cm + nm = gate.g4_units.nm + Bq = gate.g4_units.Bq + MeV = gate.g4_units.MeV + keV = gate.g4_units.keV + deg = gate.g4_units.deg + gcm3 = gate.g4_units.g / gate.g4_units.cm3 + + # adapt world size + world = sim.world + world.size = [0.25 * m, 0.25 * m, 0.25 * m] + world.material = "G4_Galactic" + + ####### GEOMETRY TO IRRADIATE ############# + sim.volume_manager.material_database.add_material_weights( + "Tungsten", + ["W"], + [1], + 19.3 * gcm3, + ) + + W_tubs = sim.add_volume("Tubs", "W_box") + W_tubs.material = "Tungsten" + W_tubs.mother = world.name + + W_tubs.rmin = 0 + W_tubs.rmax = 0.4 * cm + W_tubs.dz = 0.05 * m + W_tubs.color = [0.8, 0.2, 0.1, 1] + angle_x = 45 + angle_y = 70 + angle_z = 80 + + rotation = Rotation.from_euler( + "xyz", [angle_y, angle_y, angle_z], degrees=True + ).as_matrix() + W_tubs.rotation = rotation + + if bias: + ###### Last vertex Splitting ACTOR ######### + nb_split = 10 + vertex_splitting_actor = sim.add_actor( + "LastVertexInteractionSplittingActor", "vertexSplittingW" + ) + vertex_splitting_actor.attached_to = W_tubs.name + vertex_splitting_actor.splitting_factor = nb_split + vertex_splitting_actor.angular_kill = True + vertex_splitting_actor.vector_director = [0, 0, -1] + vertex_splitting_actor.max_theta = 15 * deg + vertex_splitting_actor.batch_size = 10 + + plan = sim.add_volume("Box", "plan_phsp") + plan.material = "G4_Galactic" + plan.size = [5 * cm, 5 * cm, 1 * nm] + plan.translation = [0, 0, -1 * cm] + + if bias: + vector_director = np.array(vertex_splitting_actor.vector_director) + + ####### gamma source ########### + source = sim.add_source("GenericSource", "source1") + source.particle = "gamma" + source.n = 100000 + if bias: + source.n = source.n / nb_split + + source.position.type = "sphere" + source.position.radius = 1 * nm + source.direction.type = "momentum" + # source.direction.momentum = [0,0,-1] + source.direction.momentum = np.dot(rotation, np.array([0, 0, -1])) + source.energy.type = "mono" + source.energy.mono = 4 * MeV + + ###### LastVertexSource ############# + if bias: + source_0 = sim.add_source("LastVertexSource", "source_vertex") + source_0.n = 1 + + ####### PHASE SPACE ACTOR ############## + sim.output_dir = paths.output + phsp_actor = sim.add_actor("PhaseSpaceActor", "PhaseSpace") + phsp_actor.attached_to = plan.name + phsp_actor.attributes = [ + "EventID", + "TrackID", + "Weight", + "ParticleName", + "KineticEnergy", + "PreDirection", + "PrePosition", + "TrackCreatorProcess", + ] + if bias: + phsp_actor.output_filename = "test084_output_data_last_vertex_angular_kill.root" + + s = sim.add_actor("SimulationStatisticsActor", "Stats") + s.track_types_flag = True + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + s = f"/process/em/UseGeneralProcess false" + sim.g4_commands_before_init.append(s) + + sim.physics_manager.global_production_cuts.gamma = 1 * mm + sim.physics_manager.global_production_cuts.electron = 1000 * km + sim.physics_manager.global_production_cuts.positron = 1000 * km + + output = sim.run() + print(s) + + f_data = uproot.open( + paths.output / "test084_output_data_last_vertex_angular_kill.root" + ) + arr_data = f_data["PhaseSpace"].arrays() + is_ok = validation_test(arr_data, vector_director, vertex_splitting_actor.max_theta) + utility.test_ok(is_ok) From 52a8a57e83c708cb47511fbdb594f82c9af014f6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 09:50:13 +0000 Subject: [PATCH 59/82] [pre-commit.ci] Automatic python and c++ formatting --- core/opengate_core/opengate_lib/tree.hh | 14 ++++++++------ opengate/managers.py | 2 +- opengate/sources/generic.py | 2 ++ opengate/sources/lastvertexsources.py | 5 ++--- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/core/opengate_core/opengate_lib/tree.hh b/core/opengate_core/opengate_lib/tree.hh index ef78833de..17e501f48 100644 --- a/core/opengate_core/opengate_lib/tree.hh +++ b/core/opengate_core/opengate_lib/tree.hh @@ -79,7 +79,8 @@ public: // assert(1==0); // std::ostringstream str; // std::cerr << boost::stacktrace::stacktrace() << - //std::endl; str << boost::stacktrace::stacktrace(); stacktrace=str.str(); + // std::endl; str << boost::stacktrace::stacktrace(); + // stacktrace=str.str(); } // virtual const char *what() const noexcept override @@ -579,11 +580,11 @@ private: // class iterator_base_less { // public: // bool operator()(const typename tree<T, -//tree_node_allocator>::iterator_base& one, const typename tree<T, -//tree_node_allocator>::iterator_base& two) const +// tree_node_allocator>::iterator_base& one, +// const typename tree<T, tree_node_allocator>::iterator_base& two) const // { // txtout << "operatorclass<" << one.node < two.node << -//std::endl; return one.node < two.node; +// std::endl; return one.node < two.node; // } // }; @@ -609,7 +610,8 @@ private: // // template <class T, class tree_node_allocator> // bool operator>(const typename tree<T, tree_node_allocator>::iterator_base& -// one, const typename tree<T, tree_node_allocator>::iterator_base& two) +// one, const typename tree<T, +// tree_node_allocator>::iterator_base& two) // { // txtout << "operator> " << one.node < two.node << std::endl; // if(one.node > two.node) return true; @@ -3202,7 +3204,7 @@ tree<T, tree_node_allocator>::fixed_depth_iterator::operator--() { // this->node=this->node->prev_sibling; // assert(this->node!=0); // if(this->node->parent==0 && this->node->prev_sibling==0) // head - //element this->node=0; + // element this->node=0; // } // else { // tree_node *par=this->node->parent; diff --git a/opengate/managers.py b/opengate/managers.py index 71f6bbc2d..fcd7e0e63 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -57,7 +57,7 @@ source_types = { "GenericSource": GenericSource, - "LastVertexSource":LastVertexSource, + "LastVertexSource": LastVertexSource, "PhaseSpaceSource": PhaseSpaceSource, "VoxelSource": VoxelSource, "GANSource": GANSource, diff --git a/opengate/sources/generic.py b/opengate/sources/generic.py index d74b2c368..29f9d96eb 100644 --- a/opengate/sources/generic.py +++ b/opengate/sources/generic.py @@ -354,6 +354,7 @@ def can_predict_number_of_events(self): return False return True + class TemplateSource(SourceBase): """ Source template: to create a new type of source, copy-paste @@ -390,4 +391,5 @@ def initialize(self, run_timing_intervals): # initialize SourceBase.initialize(self, run_timing_intervals) + process_cls(GenericSource) diff --git a/opengate/sources/lastvertexsources.py b/opengate/sources/lastvertexsources.py index 9578542b4..6712cac08 100644 --- a/opengate/sources/lastvertexsources.py +++ b/opengate/sources/lastvertexsources.py @@ -13,7 +13,7 @@ from ..exception import fatal, warning -class LastVertexSource(SourceBase,g4.GateLastVertexSource): +class LastVertexSource(SourceBase, g4.GateLastVertexSource): """ The source used to replay position, energy, direction and weight of last vertex particles actor """ @@ -21,7 +21,7 @@ class LastVertexSource(SourceBase,g4.GateLastVertexSource): def __init__(self, *args, **kwargs): super().__init__(self, *args, **kwargs) self.__initcpp__() - + def __initcpp__(self): g4.GateLastVertexSource.__init__(self) @@ -30,4 +30,3 @@ def initialize(self, run_timing_intervals): process_cls(LastVertexSource) - From 210647aef676cf1a97a26aa59a117d5f1557b52b Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 19 Nov 2024 15:49:27 +0100 Subject: [PATCH 60/82] file deletion --- .../GateKillAccordingProcessesActor.cpp | 8 +- .../GateKillAccordingProcessesActor.h | 2 +- .../GateKillNonInteractingParticleActor.cpp | 6 +- .../opengate_lib/GateOptnForceFreeFlight.cpp | 127 ------- .../opengate_lib/GateOptnForceFreeFlight.h | 122 ------- .../GateOptnPairProdSplitting.cpp | 97 ------ .../opengate_lib/GateOptnPairProdSplitting.h | 52 --- .../GateOptnScatteredGammaSplitting.cpp | 165 --------- .../GateOptnScatteredGammaSplitting.h | 53 --- .../GateOptnVGenericSplitting.cpp | 111 ------ .../opengate_lib/GateOptnVGenericSplitting.h | 127 ------- .../opengate_lib/GateOptneBremSplitting.cpp | 112 ------ .../opengate_lib/GateOptneBremSplitting.h | 54 --- ...GateOptrComptPseudoTransportationActor.cpp | 329 ------------------ .../GateOptrComptPseudoTransportationActor.h | 138 -------- ...GateOptrComptPseudoTransportationActor.cpp | 20 -- 16 files changed, 8 insertions(+), 1515 deletions(-) delete mode 100644 core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp delete mode 100644 core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h delete mode 100644 core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp delete mode 100644 core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h delete mode 100644 core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp delete mode 100644 core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h delete mode 100644 core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp delete mode 100644 core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h delete mode 100644 core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp delete mode 100644 core/opengate_core/opengate_lib/GateOptneBremSplitting.h delete mode 100644 core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp delete mode 100644 core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h delete mode 100644 core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp diff --git a/core/opengate_core/opengate_lib/GateKillAccordingProcessesActor.cpp b/core/opengate_core/opengate_lib/GateKillAccordingProcessesActor.cpp index b80f9363f..5b29db383 100644 --- a/core/opengate_core/opengate_lib/GateKillAccordingProcessesActor.cpp +++ b/core/opengate_core/opengate_lib/GateKillAccordingProcessesActor.cpp @@ -43,8 +43,8 @@ GateKillAccordingProcessesActor::GetListOfPhysicsListProcesses() { return listOfAllProcesses; } -void GateKillAccordingProcessesActor::InitializeUserInput(py::dict &user_info) { - GateVActor::InitializeUserInput(user_info); +void GateKillAccordingProcessesActor::InitializeUserInfo(py::dict &user_info) { + GateVActor::InitializeUserInfo(user_info); fProcessesToKillIfOccurence = DictGetVecStr(user_info, "processes_to_kill_if_occurence"); fProcessesToKillIfNoOccurence = @@ -96,7 +96,7 @@ void GateKillAccordingProcessesActor::PreUserTrackingAction( void GateKillAccordingProcessesActor::SteppingAction(G4Step *step) { G4String logNameMotherVolume = G4LogicalVolumeStore::GetInstance() - ->GetVolume(fMotherVolumeName) + ->GetVolume(fAttachedToVolumeName) ->GetName(); G4String physicalVolumeNamePreStep = "None"; if (step->GetPreStepPoint()->GetPhysicalVolume() != 0) @@ -107,7 +107,7 @@ void GateKillAccordingProcessesActor::SteppingAction(G4Step *step) { (fIsFirstStep)) || ((fIsFirstStep) && (step->GetTrack()->GetParentID() == 0))) { if (((step->GetPreStepPoint()->GetStepStatus() == 1) && - (physicalVolumeNamePreStep == fMotherVolumeName)) || + (physicalVolumeNamePreStep == fAttachedToVolumeName)) || ((fIsFirstStep) && (step->GetTrack()->GetParentID() == 0))) { fKill = true; } diff --git a/core/opengate_core/opengate_lib/GateKillAccordingProcessesActor.h b/core/opengate_core/opengate_lib/GateKillAccordingProcessesActor.h index a10c2170f..99a32ee37 100644 --- a/core/opengate_core/opengate_lib/GateKillAccordingProcessesActor.h +++ b/core/opengate_core/opengate_lib/GateKillAccordingProcessesActor.h @@ -21,7 +21,7 @@ class GateKillAccordingProcessesActor : public GateVActor { GateKillAccordingProcessesActor(py::dict &user_info); std::vector<G4String> GetListOfPhysicsListProcesses(); - void InitializeUserInput(py::dict &user_info) override; + void InitializeUserInfo(py::dict &user_info) override; void BeginOfRunAction(const G4Run *) override; diff --git a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp index 84ce65ac4..98fb66463 100644 --- a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp +++ b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp @@ -34,7 +34,7 @@ void GateKillNonInteractingParticleActor::SteppingAction(G4Step *step) { ->GetNavigatorForTracking(); G4String logNameMotherVolume = G4LogicalVolumeStore::GetInstance() - ->GetVolume(fMotherVolumeName) + ->GetVolume(fAttachedToVolumeName) ->GetName(); G4String physicalVolumeNamePreStep = "None"; if (step->GetPreStepPoint()->GetPhysicalVolume() != 0) @@ -48,11 +48,11 @@ void GateKillNonInteractingParticleActor::SteppingAction(G4Step *step) { (fIsFirstStep)) { // if ((fPassedByTheMotherVolume == false) && // (((step->GetPreStepPoint()->GetStepStatus() == 1) && - // (physicalVolumeNamePreStep == fMotherVolumeName)) || ((fIsFirstStep) && + // (physicalVolumeNamePreStep == fAttachedToVolumeName)) || ((fIsFirstStep) && // (step->GetTrack()->GetParentID() == 0)))) { if ((fPassedByTheMotherVolume == false) && (((step->GetPreStepPoint()->GetStepStatus() == 1) && - (physicalVolumeNamePreStep == fMotherVolumeName)))) { + (physicalVolumeNamePreStep == fAttachedToVolumeName)))) { fPassedByTheMotherVolume = true; fKineticEnergyAtTheEntrance = step->GetPreStepPoint()->GetKineticEnergy(); ftrackIDAtTheEntrance = step->GetTrack()->GetTrackID(); diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp deleted file mode 100644 index a1d281d47..000000000 --- a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.cpp +++ /dev/null @@ -1,127 +0,0 @@ -// -// ******************************************************************** -// * 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. * -// ******************************************************************** -// -#include "GateOptnForceFreeFlight.h" -#include "G4BiasingProcessInterface.hh" -#include "G4ILawForceFreeFlight.hh" -#include "G4Step.hh" - -// This operator is used to transport the particle without interaction, and then -// correct the weight of the particle -// according to the probablity for the photon to not interact within the matter. -// To do that, we use a GEANT4 modifed Interaction Law (G4ILawForceFreeFlight), -// which modify, for the biased process the probability for the interaction to -// occur : Never. This occurence is called during the tracking for each step. -// Here the step is the largest possible, and one step correspond to the path of -// particle in the media. - -GateOptnForceFreeFlight ::GateOptnForceFreeFlight(G4String name) - : G4VBiasingOperation(name), fOperationComplete(true) { - fForceFreeFlightInteractionLaw = - new G4ILawForceFreeFlight("LawForOperation" + name); -} - -GateOptnForceFreeFlight ::~GateOptnForceFreeFlight() { - if (fForceFreeFlightInteractionLaw) - delete fForceFreeFlightInteractionLaw; -} - -const G4VBiasingInteractionLaw * -GateOptnForceFreeFlight ::ProvideOccurenceBiasingInteractionLaw( - const G4BiasingProcessInterface *, - G4ForceCondition &proposeForceCondition) { - fOperationComplete = false; - proposeForceCondition = Forced; - return fForceFreeFlightInteractionLaw; -} - -G4VParticleChange *GateOptnForceFreeFlight ::ApplyFinalStateBiasing( - const G4BiasingProcessInterface *callingProcess, const G4Track *track, - const G4Step *step, G4bool &forceFinalState) { - - fParticleChange.Initialize(*track); - forceFinalState = true; - fCountProcess++; - - fProposedWeight *= - fWeightChange[callingProcess->GetWrappedProcess()->GetProcessName()]; - - if (fRussianRouletteForWeights) { - G4int nbOfOrderOfMagnitude = std::log10(fInitialWeight / fMinWeight); - - if ((fProposedWeight < fMinWeight) || - ((fProposedWeight < 0.1 * fInitialWeight) && - (fNbOfRussianRoulette == nbOfOrderOfMagnitude - 1))) { - fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); - fNbOfRussianRoulette = 0; - return &fParticleChange; - } - - if ((fProposedWeight < 0.1 * fInitialWeight) && (fCountProcess == 4)) { - G4double probability = G4UniformRand(); - for (int i = 2; i <= nbOfOrderOfMagnitude; i++) { - G4double RRprobability = 1 / std::pow(10, i); - if (fProposedWeight * 1 / std::pow(10, fNbOfRussianRoulette) < - 10 * RRprobability * fMinWeight) { - fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); - fNbOfRussianRoulette = 0; - return &fParticleChange; - } - if ((fProposedWeight >= RRprobability * fInitialWeight) && - (fProposedWeight < 10 * RRprobability * fInitialWeight)) { - if (probability > 10 * RRprobability) { - fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); - fNbOfRussianRoulette = 0; - return &fParticleChange; - } else { - fProposedWeight = fProposedWeight / (10 * RRprobability); - fNbOfRussianRoulette = fNbOfRussianRoulette + i - 1; - } - } - } - } - } else { - if (fProposedWeight < fMinWeight) { - fParticleChange.ProposeTrackStatus(G4TrackStatus::fStopAndKill); - return &fParticleChange; - } - } - fParticleChange.ProposeWeight(fProposedWeight); - fOperationComplete = true; - return &fParticleChange; -} - -void GateOptnForceFreeFlight ::AlongMoveBy( - const G4BiasingProcessInterface *callingProcess, const G4Step *, - G4double weightChange) - -{ - G4String processName = callingProcess->GetWrappedProcess()->GetProcessName(); - if (processName != "Rayl") { - fWeightChange[processName] = weightChange; - } else { - fWeightChange[processName] = 1; - } -} diff --git a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h b/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h deleted file mode 100644 index 398f27851..000000000 --- a/core/opengate_core/opengate_lib/GateOptnForceFreeFlight.h +++ /dev/null @@ -1,122 +0,0 @@ -// -// ******************************************************************** -// * 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. * -// ******************************************************************** -// -// -// -//--------------------------------------------------------------- -// -// GateOptnForceFreeFlight -// -// Class Description: -// A G4VBiasingOperation physics-based biasing operation. -// If forces the physics process to not act on the track. -// In this implementation (meant for the ForceCollision -// operator) the free flight is done under zero weight for -// the track, and the action is meant to accumulate the weight -// change for making this uninteracting flight, -// cumulatedWeightChange. -// When the track reaches the current volume boundary, its -// weight is restored with value : -// initialWeight * cumulatedWeightChange -// -//--------------------------------------------------------------- -// Initial version Nov. 2013 M. Verderi -#ifndef GateOptnForceFreeFlight_h -#define GateOptnForceFreeFlight_h 1 - -#include "G4ForceCondition.hh" -#include "G4ParticleChange.hh" // -- ยงยง should add a dedicated "weight change only" particle change -#include "G4VBiasingOperation.hh" -class G4ILawForceFreeFlight; - -class GateOptnForceFreeFlight : public G4VBiasingOperation { -public: - // -- Constructor : - GateOptnForceFreeFlight(G4String name); - // -- destructor: - virtual ~GateOptnForceFreeFlight(); - -public: - // -- Methods from G4VBiasingOperation interface: - // ------------------------------------------- - // -- Used: - virtual const G4VBiasingInteractionLaw * - ProvideOccurenceBiasingInteractionLaw(const G4BiasingProcessInterface *, - G4ForceCondition &); - virtual void AlongMoveBy(const G4BiasingProcessInterface *, const G4Step *, - G4double); - virtual G4VParticleChange * - ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, - const G4Step *, G4bool &); - // -- Unused: - virtual G4double DistanceToApplyOperation(const G4Track *, G4double, - G4ForceCondition *) { - return DBL_MAX; - } - virtual G4VParticleChange *GenerateBiasingFinalState(const G4Track *, - const G4Step *) { - return 0; - } - -public: - // -- Additional methods, specific to this class: - // ---------------------------------------------- - // -- return concrete type of interaction law: - G4ILawForceFreeFlight *GetForceFreeFlightLaw() { - return fForceFreeFlightInteractionLaw; - } - // -- initialization for weight: - // void ResetInitialTrackWeight(G4double w) {fInitialTrackWeight = w; - // fCumulatedWeightChange = 1.0;} - - void SetMinWeight(G4double w) { fMinWeight = w; } - void SetInitialWeight(G4double w) { fInitialWeight = w; } - G4double GetTrackWeight() { return fProposedWeight; } - void SetTrackWeight(G4double w) { fProposedWeight = w; } - void SetRussianRouletteProbability(G4double p) { - fRussianRouletteProbability = p; - } - void SetCountProcess(G4int N) { fCountProcess = N; } - void SetSurvivedToRR(G4bool b) { fSurvivedToRR = b; } - void SetRussianRouletteForWeights(G4bool rr) { - fRussianRouletteForWeights = rr; - } - G4bool GetSurvivedToRR() { return fSurvivedToRR; } - G4bool OperationComplete() const { return fOperationComplete; } - -private: - G4ILawForceFreeFlight *fForceFreeFlightInteractionLaw; - std::map<G4String, G4double> fWeightChange; - G4bool fRussianRouletteForWeights; - G4double fMinWeight, fRussianRouletteProbability, fInitialWeight; - G4ParticleChange fParticleChange; - G4bool fOperationComplete; - G4double fProposedWeight; - G4int fCountProcess; - G4bool fSurvivedToRR; - G4int fNbOfRussianRoulette; -}; - -#endif diff --git a/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp deleted file mode 100644 index cf30c3a7d..000000000 --- a/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptnPairProdSplitting.cc -/// \brief Implementation of the GateOptnPairProdSplitting class - -#include "GateOptnPairProdSplitting.h" -#include "G4BiasingProcessInterface.hh" - -#include "G4ComptonScattering.hh" -#include "G4DynamicParticle.hh" -#include "G4Exception.hh" -#include "G4Gamma.hh" -#include "G4GammaConversion.hh" -#include "G4ParticleChange.hh" -#include "G4ParticleChangeForGamma.hh" -#include "G4ParticleChangeForLoss.hh" -#include "G4PhotoElectricEffect.hh" -#include "G4ProcessType.hh" -#include "G4RayleighScattering.hh" -#include "G4SystemOfUnits.hh" -#include "G4TrackStatus.hh" -#include "G4TrackingManager.hh" -#include "G4VEmProcess.hh" -#include <memory> - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptnPairProdSplitting::GateOptnPairProdSplitting(G4String name) - : GateOptnVGenericSplitting(name), fParticleChange() {} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptnPairProdSplitting::~GateOptnPairProdSplitting() {} - -G4VParticleChange *GateOptnPairProdSplitting::ApplyFinalStateBiasing( - const G4BiasingProcessInterface *callingProcess, const G4Track *track, - const G4Step *step, G4bool &) { - - G4int splittingFactor = ceil(fSplittingFactor); - G4double survivalProbabilitySplitting = - 1 - (splittingFactor - fSplittingFactor) / splittingFactor; - G4double particleWeight = 0; - - G4VParticleChange *processFinalState = - callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - - if (fSplittingFactor == 1 && fRussianRouletteForAngle == false) - return processFinalState; - TrackInitializationGamma(&fParticleChange, processFinalState, track, - fSplittingFactor); - - processFinalState->Clear(); - - G4int nCalls = 1; - while (nCalls <= splittingFactor) { - G4double splittingProbability = G4UniformRand(); - if (splittingProbability <= survivalProbabilitySplitting || - survivalProbabilitySplitting == 1) { - particleWeight = track->GetWeight() / fSplittingFactor; - G4VParticleChange *processFinalState = - callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - if (processFinalState->GetNumberOfSecondaries() >= 1) { - for (int i = 0; i < processFinalState->GetNumberOfSecondaries(); i++) { - G4Track *SecondaryTrack = processFinalState->GetSecondary(i); - SecondaryTrack->SetWeight(particleWeight); - fParticleChange.AddSecondary(SecondaryTrack); - } - } - } - nCalls++; - } - return &fParticleChange; -} diff --git a/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h b/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h deleted file mode 100644 index ef4128a81..000000000 --- a/core/opengate_core/opengate_lib/GateOptnPairProdSplitting.h +++ /dev/null @@ -1,52 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptnPairProdSplitting.h -/// \brief Definition of the GateOptnPairProdSplitting class -// - -#ifndef GateOptnPairProdSplitting_h -#define GateOptnPairProdSplitting_h 1 - -#include "G4ParticleChange.hh" -#include "GateOptnVGenericSplitting.h" - -class GateOptnPairProdSplitting : public GateOptnVGenericSplitting { -public: - // -- Constructor : - GateOptnPairProdSplitting(G4String name); - - // -- destructor: - virtual ~GateOptnPairProdSplitting(); - - virtual G4VParticleChange * - ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, - const G4Step *, G4bool &); - - G4ParticleChange fParticleChange; -}; - -#endif diff --git a/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp deleted file mode 100644 index bbfe8acff..000000000 --- a/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.cpp +++ /dev/null @@ -1,165 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptnScatteredGammaSplitting.cc -/// \brief Implementation of the GateOptnScatteredGammaSplitting class - -#include "GateOptnScatteredGammaSplitting.h" -#include "G4BiasingProcessInterface.hh" - -#include "G4ComptonScattering.hh" -#include "G4DynamicParticle.hh" -#include "G4Exception.hh" -#include "G4Gamma.hh" -#include "G4GammaConversion.hh" -#include "G4ParticleChange.hh" -#include "G4ParticleChangeForGamma.hh" -#include "G4PhotoElectricEffect.hh" -#include "G4ProcessType.hh" -#include "G4RayleighScattering.hh" -#include "G4SystemOfUnits.hh" -#include "G4TrackStatus.hh" -#include "G4TrackingManager.hh" -#include "G4VEmProcess.hh" -#include "GateOptnVGenericSplitting.h" -#include <memory> - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptnScatteredGammaSplitting::GateOptnScatteredGammaSplitting(G4String name) - : GateOptnVGenericSplitting(name), fParticleChange() {} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptnScatteredGammaSplitting::~GateOptnScatteredGammaSplitting() {} - -G4VParticleChange *GateOptnScatteredGammaSplitting::ApplyFinalStateBiasing( - const G4BiasingProcessInterface *callingProcess, const G4Track *track, - const G4Step *step, G4bool &) { - - // Here we generate for the first the compton process, given that this - // function (ApplyFinalStateBiasing) is called when there is a compton - // interaction Then the interaction location of the compton process will - // always be the same - - const G4ThreeVector position = step->GetPostStepPoint()->GetPosition(); - G4int splittingFactor = ceil(fSplittingFactor); - G4double survivalProbabilitySplitting = - 1 - (splittingFactor - fSplittingFactor) / splittingFactor; - G4double gammaWeight = 0; - - G4VParticleChange *processFinalState = - callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - // In case we don't want to split (a bit faster) i.e no biaising or no - // splitting low weights particles. - - if (fSplittingFactor == 1 && fRussianRouletteForAngle == false) - return processFinalState; - - TrackInitializationGamma(&fParticleChange, processFinalState, track, - fSplittingFactor); - processFinalState->Clear(); - - // There is here the biasing process : - // Since G4VParticleChange class does not allow to retrieve scattered gamma - // information, we need to cast the type G4ParticleChangeForGamma to the - // G4VParticleChange object. We then call the process (biasWrapper(compt)) - // fSplittingFactor times (Here, the difference with the other version of - // splitting is the primary particle will be killed and its weight does not - // count) to generate, at last, fSplittingFactor gamma according to the - // compton interaction process. If the gamma track is ok regarding the - // russian roulette algorithm (no russian roulette - //, or within the acceptance angle, or not killed by the RR process), we add - // it to the primary track. - // If an electron is generated (above the range cut), we also generate it. - // A tremendous advantage is there is no need to use by ourself Klein-Nishina - // formula or other. So, if the physics list used takes into account the - // doppler broadening or other fine effects, this will be also taken into - // account by the MC simulation. PS : The first gamma is then the primary - // particle, but all the other splitted particle (electron of course AND - // gamma) must be considered as secondary particles, even though generated - // gamma will not be cut here by the applied cut. - - G4int nCalls = 1; - while (nCalls <= splittingFactor) { - gammaWeight = track->GetWeight() / fSplittingFactor; - G4VParticleChange *processGammaSplittedFinalState = - callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - G4ParticleChangeForGamma *castedProcessGammaSplittedFinalState = - (G4ParticleChangeForGamma *)processGammaSplittedFinalState; - const G4ThreeVector momentum = - castedProcessGammaSplittedFinalState->GetProposedMomentumDirection(); - G4double energy = - castedProcessGammaSplittedFinalState->GetProposedKineticEnergy(); - G4double splittingProbability = G4UniformRand(); - - if (splittingProbability <= survivalProbabilitySplitting || - survivalProbabilitySplitting == 1) { - if (fRussianRouletteForAngle == true) { - G4double weightToApply = RussianRouletteForAngleSurvival( - castedProcessGammaSplittedFinalState - ->GetProposedMomentumDirection(), - fVectorDirector, fMaxTheta, fSplittingFactor); - if (weightToApply != 0) { - gammaWeight = gammaWeight * weightToApply; - G4Track *gammaTrack = new G4Track(*track); - gammaTrack->SetWeight(gammaWeight); - gammaTrack->SetKineticEnergy(energy); - gammaTrack->SetMomentumDirection(momentum); - gammaTrack->SetPosition(position); - fParticleChange.AddSecondary(gammaTrack); - if (processGammaSplittedFinalState->GetNumberOfSecondaries() == 1) { - G4Track *electronTrack = - processGammaSplittedFinalState->GetSecondary(0); - electronTrack->SetWeight(gammaWeight); - fParticleChange.AddSecondary(electronTrack); - } - } - } - - else { - G4Track *gammaTrack = new G4Track(*track); - gammaTrack->SetWeight(gammaWeight); - gammaTrack->SetKineticEnergy(energy); - gammaTrack->SetMomentumDirection(momentum); - gammaTrack->SetPosition(position); - fParticleChange.AddSecondary(gammaTrack); - if (processGammaSplittedFinalState->GetNumberOfSecondaries() == 1) { - G4Track *electronTrack = - processGammaSplittedFinalState->GetSecondary(0); - electronTrack->SetWeight(gammaWeight); - fParticleChange.AddSecondary(electronTrack); - } - } - } - nCalls++; - processGammaSplittedFinalState->Clear(); - castedProcessGammaSplittedFinalState->Clear(); - } - return &fParticleChange; -} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h b/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h deleted file mode 100644 index ff0a6b623..000000000 --- a/core/opengate_core/opengate_lib/GateOptnScatteredGammaSplitting.h +++ /dev/null @@ -1,53 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptnScatteredGammaSplitting.h -/// \brief Definition of the GateOptnScatteredGammaSplitting class -// - -#ifndef GateOptnScatteredGammaSplitting_h -#define GateOptnScatteredGammaSplitting_h 1 - -#include "G4ParticleChange.hh" -#include "G4VBiasingOperation.hh" -#include "GateOptnVGenericSplitting.h" - -class GateOptnScatteredGammaSplitting : public GateOptnVGenericSplitting { -public: - // -- Constructor : - GateOptnScatteredGammaSplitting(G4String name); - - // -- destructor: - virtual ~GateOptnScatteredGammaSplitting(); - - virtual G4VParticleChange * - ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, - const G4Step *, G4bool &); - - G4ParticleChange fParticleChange; -}; - -#endif diff --git a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp deleted file mode 100644 index 8c22b6399..000000000 --- a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.cpp +++ /dev/null @@ -1,111 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptnVGenericSplitting.cc -/// \brief Implementation of the GateOptnVGenericSplitting class - -#include "GateOptnVGenericSplitting.h" -#include "G4BiasingProcessInterface.hh" - -#include "G4ComptonScattering.hh" -#include "G4DynamicParticle.hh" -#include "G4Exception.hh" -#include "G4Gamma.hh" -#include "G4GammaConversion.hh" -#include "G4ParticleChange.hh" -#include "G4ParticleChangeForGamma.hh" -#include "G4ParticleChangeForLoss.hh" -#include "G4PhotoElectricEffect.hh" -#include "G4ProcessType.hh" -#include "G4RayleighScattering.hh" -#include "G4SystemOfUnits.hh" -#include "G4TrackStatus.hh" -#include "G4TrackingManager.hh" -#include "G4VEmProcess.hh" -#include <memory> - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptnVGenericSplitting::GateOptnVGenericSplitting(G4String name) - : G4VBiasingOperation(name), fParticleChange() {} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptnVGenericSplitting::~GateOptnVGenericSplitting() {} - -void GateOptnVGenericSplitting::TrackInitializationChargedParticle( - G4ParticleChange *particleChange, G4VParticleChange *processFinalState, - const G4Track *track, G4double split) { - - G4ParticleChangeForLoss *processFinalStateForLoss = - (G4ParticleChangeForLoss *)processFinalState; - particleChange->Initialize(*track); - particleChange->ProposeTrackStatus( - processFinalStateForLoss->GetTrackStatus()); - particleChange->ProposeEnergy( - processFinalStateForLoss->GetProposedKineticEnergy()); - particleChange->ProposeMomentumDirection( - processFinalStateForLoss->GetProposedMomentumDirection()); - particleChange->SetNumberOfSecondaries(fSplittingFactor); - particleChange->SetSecondaryWeightByProcess(true); - processFinalStateForLoss->Clear(); -} - -void GateOptnVGenericSplitting::TrackInitializationGamma( - G4ParticleChange *particleChange, G4VParticleChange *processFinalState, - const G4Track *track, G4double split) { - G4ParticleChangeForGamma *processFinalStateForGamma = - (G4ParticleChangeForGamma *)processFinalState; - particleChange->Initialize(*track); - particleChange->ProposeTrackStatus( - processFinalStateForGamma->GetTrackStatus()); - particleChange->ProposeEnergy( - processFinalStateForGamma->GetProposedKineticEnergy()); - particleChange->ProposeMomentumDirection( - processFinalStateForGamma->GetProposedMomentumDirection()); - particleChange->SetNumberOfSecondaries(fSplittingFactor); - particleChange->SetSecondaryWeightByProcess(true); - processFinalStateForGamma->Clear(); -} - -G4double GateOptnVGenericSplitting::RussianRouletteForAngleSurvival( - G4ThreeVector dir, G4ThreeVector vectorDirector, G4double maxTheta, - G4double split) { - G4double cosTheta = vectorDirector * dir; - G4double theta = std::acos(cosTheta); - G4double weightToApply = 1; - if (theta > maxTheta) { - G4double probability = G4UniformRand(); - if (probability <= 1 / split) { - weightToApply = split; - } else { - weightToApply = 0; - } - } - return weightToApply; -} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h b/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h deleted file mode 100644 index b89f0baee..000000000 --- a/core/opengate_core/opengate_lib/GateOptnVGenericSplitting.h +++ /dev/null @@ -1,127 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptnVGenericSplitting.h -/// \brief Definition of the GateOptnVGenericSplitting class -// - -#ifndef GateOptnVGenericSplitting_h -#define GateOptnVGenericSplitting_h 1 - -#include "G4ParticleChange.hh" -#include "G4VBiasingOperation.hh" - -class GateOptnVGenericSplitting : public G4VBiasingOperation { -public: - // -- Constructor : - GateOptnVGenericSplitting(G4String name); - - // -- destructor: - virtual ~GateOptnVGenericSplitting(); - -public: - // ---------------------------------------------- - // -- Methods from G4VBiasingOperation interface: - // ---------------------------------------------- - // -- Unused: - virtual const G4VBiasingInteractionLaw * - ProvideOccurenceBiasingInteractionLaw(const G4BiasingProcessInterface *, - G4ForceCondition &) { - return 0; - } - - // --Used: - virtual G4VParticleChange * - ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, - const G4Step *, G4bool &) { - return 0; - }; - - // -- Unsued: - virtual G4double DistanceToApplyOperation(const G4Track *, G4double, - G4ForceCondition *) { - return DBL_MAX; - } - virtual G4VParticleChange *GenerateBiasingFinalState(const G4Track *, - const G4Step *) { - return 0; - } - - // ---------------------------------------------- - // -- Methods for the generic splitting - // ---------------------------------------------- - - void TrackInitializationChargedParticle(G4ParticleChange *particleChange, - G4VParticleChange *processFinalState, - const G4Track *track, G4double split); - void TrackInitializationGamma(G4ParticleChange *particleChange, - G4VParticleChange *processFinalState, - const G4Track *track, G4double split); - static G4double RussianRouletteForAngleSurvival(G4ThreeVector dir, - G4ThreeVector vectorDirector, - G4double maxTheta, - G4double split); - -public: - // ---------------------------------------------- - // -- Additional methods, specific to this class: - // ---------------------------------------------- - // -- Splitting factor: - void SetSplittingFactor(G4double splittingFactor) { - fSplittingFactor = splittingFactor; - } - G4double GetSplittingFactor() const { return fSplittingFactor; } - - void SetRussianRouletteForAngle(G4bool russianRoulette) { - fRussianRouletteForAngle = russianRoulette; - } - - void SetVectorDirector(G4ThreeVector vectorDirector) { - fVectorDirector = vectorDirector; - } - - void SetRotationMatrix(G4RotationMatrix rot) { fRot = rot; } - - G4ThreeVector GetVectorDirector() const { return fVectorDirector; } - - void SetMaxTheta(G4double maxTheta) { fMaxTheta = maxTheta; } - - G4double GetMaxTheta() const { return fMaxTheta; } - - G4VParticleChange *GetParticleChange() { - G4VParticleChange *particleChange = &fParticleChange; - return particleChange; - } - - G4double fSplittingFactor; - G4ParticleChange fParticleChange; - G4bool fRussianRouletteForAngle; - G4ThreeVector fVectorDirector; - G4double fMaxTheta; - G4RotationMatrix fRot; -}; - -#endif diff --git a/core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp b/core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp deleted file mode 100644 index ce9ff2324..000000000 --- a/core/opengate_core/opengate_lib/GateOptneBremSplitting.cpp +++ /dev/null @@ -1,112 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptneBremSplitting.cc -/// \brief Implementation of the GateOptneBremSplitting class - -#include "GateOptneBremSplitting.h" -#include "G4BiasingProcessInterface.hh" - -#include "G4ComptonScattering.hh" -#include "G4DynamicParticle.hh" -#include "G4Exception.hh" -#include "G4Gamma.hh" -#include "G4GammaConversion.hh" -#include "G4ParticleChange.hh" -#include "G4ParticleChangeForGamma.hh" -#include "G4ParticleChangeForLoss.hh" -#include "G4PhotoElectricEffect.hh" -#include "G4ProcessType.hh" -#include "G4RayleighScattering.hh" -#include "G4SystemOfUnits.hh" -#include "G4TrackStatus.hh" -#include "G4TrackingManager.hh" -#include "G4VEmProcess.hh" -#include "GateOptnVGenericSplitting.h" -#include <memory> - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptneBremSplitting::GateOptneBremSplitting(G4String name) - : GateOptnVGenericSplitting(name), fParticleChange() {} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptneBremSplitting::~GateOptneBremSplitting() {} - -G4VParticleChange *GateOptneBremSplitting::ApplyFinalStateBiasing( - const G4BiasingProcessInterface *callingProcess, const G4Track *track, - const G4Step *step, G4bool &) { - - G4int splittingFactor = ceil(fSplittingFactor); - G4double survivalProbabilitySplitting = - 1 - (splittingFactor - fSplittingFactor) / splittingFactor; - G4VParticleChange *processFinalState = - callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - if (fSplittingFactor == 1) - return processFinalState; - if (processFinalState->GetNumberOfSecondaries() == 0) - return processFinalState; - - TrackInitializationChargedParticle(&fParticleChange, processFinalState, track, - fSplittingFactor); - - processFinalState->Clear(); - - G4int nCalls = 1; - while (nCalls <= fSplittingFactor) { - G4double splittingProbability = G4UniformRand(); - if (splittingProbability <= survivalProbabilitySplitting || - survivalProbabilitySplitting == 1) { - processFinalState = - callingProcess->GetWrappedProcess()->PostStepDoIt(*track, *step); - if (processFinalState->GetNumberOfSecondaries() >= 1) { - for (int i = 0; i < processFinalState->GetNumberOfSecondaries(); i++) { - G4double gammaWeight = track->GetWeight() / fSplittingFactor; - G4Track *gammaTrack = processFinalState->GetSecondary(i); - if (fRussianRouletteForAngle == true) { - G4double weightToApply = RussianRouletteForAngleSurvival( - gammaTrack->GetMomentumDirection(), fVectorDirector, fMaxTheta, - fSplittingFactor); - if (weightToApply != 0) { - gammaWeight = gammaWeight * weightToApply; - gammaTrack->SetWeight(gammaWeight); - fParticleChange.AddSecondary(gammaTrack); - } - } else { - gammaTrack->SetWeight(gammaWeight); - fParticleChange.AddSecondary(gammaTrack); - } - } - } - processFinalState->Clear(); - } - nCalls++; - } - return &fParticleChange; -} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptneBremSplitting.h b/core/opengate_core/opengate_lib/GateOptneBremSplitting.h deleted file mode 100644 index 47319e536..000000000 --- a/core/opengate_core/opengate_lib/GateOptneBremSplitting.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptneBremSplitting.h -/// \brief Definition of the GateOptneBremSplitting class -// - -#ifndef GateOptneBremSplitting_h -#define GateOptneBremSplitting_h 1 - -#include "G4ParticleChange.hh" -#include "G4VBiasingOperation.hh" -#include "GateOptnVGenericSplitting.h" - -class GateOptneBremSplitting : public GateOptnVGenericSplitting { -public: - // -- Constructor : - GateOptneBremSplitting(G4String name); - - // -- destructor: - virtual ~GateOptneBremSplitting(); - -public: - virtual G4VParticleChange * - ApplyFinalStateBiasing(const G4BiasingProcessInterface *, const G4Track *, - const G4Step *, G4bool &); - - G4ParticleChange fParticleChange; -}; - -#endif diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp deleted file mode 100644 index f973cc4aa..000000000 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.cpp +++ /dev/null @@ -1,329 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptrComptPseudoTransportationActor.cc -/// \brief Implementation of the GateOptrComptPseudoTransportationActor class - -#include "GateHelpersDict.h" -#include "GateHelpersImage.h" - -#include "CLHEP/Units/SystemOfUnits.h" -#include "G4BiasingProcessInterface.hh" -#include "G4Electron.hh" -#include "G4EmCalculator.hh" -#include "G4Exception.hh" -#include "G4Gamma.hh" -#include "G4LogicalVolumeStore.hh" -#include "G4ParticleTable.hh" -#include "G4PhysicalVolumeStore.hh" -#include "G4Positron.hh" -#include "G4ProcessManager.hh" -#include "G4ProcessVector.hh" -#include "G4RunManager.hh" -#include "G4TrackStatus.hh" -#include "G4UImanager.hh" -#include "G4UserTrackingAction.hh" -#include "G4VEmProcess.hh" -#include "G4eplusAnnihilation.hh" -#include "GateOptnPairProdSplitting.h" -#include "GateOptnScatteredGammaSplitting.h" -#include "GateOptnVGenericSplitting.h" -#include "GateOptneBremSplitting.h" -#include "GateOptrComptPseudoTransportationActor.h" - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -GateOptrComptPseudoTransportationActor::GateOptrComptPseudoTransportationActor( - py::dict &user_info) - : G4VBiasingOperator("ComptSplittingOperator"), - GateVActor(user_info, false) { - fMotherVolumeName = DictGetStr(user_info, "mother"); - fAttachToLogicalHolder = DictGetBool(user_info, "attach_to_logical_holder"); - fSplittingFactor = DictGetDouble(user_info, "splitting_factor"); - fRelativeMinWeightOfParticle = - DictGetDouble(user_info, "relative_min_weight_of_particle"); - // Since the russian roulette uses as a probability 1/splitting, we need to - // have a double, but the splitting factor provided by the user is logically - // an int, so we need to change the type. - fRotationVectorDirector = DictGetBool(user_info, "rotation_vector_director"); - fRussianRouletteForAngle = - DictGetBool(user_info, "russian_roulette_for_angle"); - fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); - fRussianRouletteForWeights = - DictGetBool(user_info, "russian_roulette_for_weights"); - fMaxTheta = DictGetDouble(user_info, "max_theta"); - fFreeFlightOperation = new GateOptnForceFreeFlight("freeFlightOperation"); - fScatteredGammaSplittingOperation = - new GateOptnScatteredGammaSplitting("comptSplittingOperation"); - feBremSplittingOperation = - new GateOptneBremSplitting("eBremSplittingOperation"); - fPairProdSplittingOperation = - new GateOptnPairProdSplitting("PairProdSplittingOperation"); - fActions.insert("StartSimulationAction"); - fActions.insert("SteppingAction"); - fActions.insert("BeginOfEventAction"); - isSplitted = false; -} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -void GateOptrComptPseudoTransportationActor::AttachAllLogicalDaughtersVolumes( - G4LogicalVolume *volume) { - if (fAttachToLogicalHolder == false) { - if (volume->GetName() != G4LogicalVolumeStore::GetInstance() - ->GetVolume(fMotherVolumeName) - ->GetName()) { - AttachTo(volume); - } - } - if (fAttachToLogicalHolder == true) { - AttachTo(volume); - } - G4int nbOfDaughters = volume->GetNoDaughters(); - if (nbOfDaughters > 0) { - for (int i = 0; i < nbOfDaughters; i++) { - G4String LogicalVolumeName = - volume->GetDaughter(i)->GetLogicalVolume()->GetName(); - G4LogicalVolume *logicalDaughtersVolume = - volume->GetDaughter(i)->GetLogicalVolume(); - AttachAllLogicalDaughtersVolumes(logicalDaughtersVolume); - if (!(std::find(fNameOfBiasedLogicalVolume.begin(), - fNameOfBiasedLogicalVolume.end(), - LogicalVolumeName) != fNameOfBiasedLogicalVolume.end())) - fNameOfBiasedLogicalVolume.push_back( - volume->GetDaughter(i)->GetLogicalVolume()->GetName()); - } - } -} - -void GateOptrComptPseudoTransportationActor::StartSimulationAction() { - G4LogicalVolume *biasingVolume = - G4LogicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); - - // Here we need to attach all the daughters and daughters of daughters (...) - // to the biasing operator. To do that, I use the function - // AttachAllLogicalDaughtersVolumes. - AttachAllLogicalDaughtersVolumes(biasingVolume); - - fScatteredGammaSplittingOperation->SetSplittingFactor(fSplittingFactor); - - fScatteredGammaSplittingOperation->SetMaxTheta(fMaxTheta); - fScatteredGammaSplittingOperation->SetRussianRouletteForAngle( - fRussianRouletteForAngle); - - feBremSplittingOperation->SetSplittingFactor(fSplittingFactor); - feBremSplittingOperation->SetMaxTheta(fMaxTheta); - feBremSplittingOperation->SetRussianRouletteForAngle( - fRussianRouletteForAngle); - - fPairProdSplittingOperation->SetSplittingFactor(fSplittingFactor); - - fFreeFlightOperation->SetRussianRouletteForWeights( - fRussianRouletteForWeights); -} - -void GateOptrComptPseudoTransportationActor::StartRun() { - - // The way to behave of the russian roulette is the following : - // we provide a vector director and the theta angle acceptance, where theta = - // 0 is a vector colinear to the vector director Then if the track generated - // is on the acceptance angle, we add it to the primary track, and if it's not - // the case, we launch the russian roulette - if (fRotationVectorDirector) { - G4VPhysicalVolume *physBiasingVolume = - G4PhysicalVolumeStore::GetInstance()->GetVolume(fMotherVolumeName); - auto rot = physBiasingVolume->GetObjectRotationValue(); - fVectorDirector = rot * fVectorDirector; - fScatteredGammaSplittingOperation->SetRotationMatrix(rot); - feBremSplittingOperation->SetRotationMatrix(rot); - } - - fScatteredGammaSplittingOperation->SetVectorDirector(fVectorDirector); - feBremSplittingOperation->SetVectorDirector(fVectorDirector); -} - -void GateOptrComptPseudoTransportationActor::SteppingAction(G4Step *step) { - G4String creationProcessName = "None"; - if (step->GetTrack()->GetCreatorProcess() != 0) { - creationProcessName = - step->GetTrack()->GetCreatorProcess()->GetProcessName(); - } - - if ((fIsFirstStep) && (fRussianRouletteForAngle)) { - G4String LogicalVolumeNameOfCreation = - step->GetTrack()->GetLogicalVolumeAtVertex()->GetName(); - if (std::find(fNameOfBiasedLogicalVolume.begin(), - fNameOfBiasedLogicalVolume.end(), - LogicalVolumeNameOfCreation) != - fNameOfBiasedLogicalVolume.end()) { - if (creationProcessName == "biasWrapper(annihil)") { - auto dir = step->GetPreStepPoint()->GetMomentumDirection(); - G4double w = GateOptnVGenericSplitting::RussianRouletteForAngleSurvival( - dir, fVectorDirector, fMaxTheta, fSplittingFactor); - if (w == 0) { - step->GetTrack()->SetTrackStatus(fStopAndKill); - } else { - step->GetTrack()->SetWeight(step->GetTrack()->GetWeight() * w); - } - } - } - } - - if ((isSplitted == true) && - (step->GetPostStepPoint()->GetStepStatus() != fWorldBoundary)) { - G4String LogicalVolumeName = step->GetPostStepPoint() - ->GetPhysicalVolume() - ->GetLogicalVolume() - ->GetName(); - if (!(std::find(fNameOfBiasedLogicalVolume.begin(), - fNameOfBiasedLogicalVolume.end(), - LogicalVolumeName) != fNameOfBiasedLogicalVolume.end()) && - (LogicalVolumeName != fMotherVolumeName)) { - step->GetTrack()->SetTrackStatus(fStopAndKill); - isSplitted = false; - } - } - - fIsFirstStep = false; -} - -void GateOptrComptPseudoTransportationActor::BeginOfEventAction( - const G4Event *event) { - fKillOthersParticles = false; - fPassedByABiasedVolume = false; - fEventID = event->GetEventID(); - fEventIDKineticEnergy = - event->GetPrimaryVertex(0)->GetPrimary(0)->GetKineticEnergy(); -} - -void GateOptrComptPseudoTransportationActor::StartTracking( - const G4Track *track) { - G4String creationProcessName = "None"; - fIsFirstStep = true; - if (track->GetCreatorProcess() != 0) { - creationProcessName = track->GetCreatorProcess()->GetProcessName(); - } - - if (track->GetParticleDefinition()->GetParticleName() == "gamma") { - G4String LogicalVolumeNameOfCreation = - track->GetLogicalVolumeAtVertex()->GetName(); - if (std::find(fNameOfBiasedLogicalVolume.begin(), - fNameOfBiasedLogicalVolume.end(), - LogicalVolumeNameOfCreation) != - fNameOfBiasedLogicalVolume.end()) { - - fInitialWeight = track->GetWeight(); - fFreeFlightOperation->SetInitialWeight(fInitialWeight); - } - } -} - -// For the following operation the idea is the following : -// All the potential photon processes are biased. If a particle undergoes a -// compton interaction, we splitted it (ComptonSplittingForTransportation -// operation) and the particle generated are pseudo-transported with the -// ForceFreeFLight operation -// Since the occurence Biaising operation is called at the beginning of each -// track, and propose a different way to track the particle -//(with modified physics), it here returns other thing than 0 if we want to -// pseudo-transport the particle, so if its creatorProcess is the modified -// compton interaction - -G4VBiasingOperation * -GateOptrComptPseudoTransportationActor::ProposeOccurenceBiasingOperation( - const G4Track *track, const G4BiasingProcessInterface *callingProcess) { - - if (track->GetParticleDefinition()->GetParticleName() == "gamma") { - G4String LogicalVolumeNameOfCreation = - track->GetLogicalVolumeAtVertex()->GetName(); - if (std::find(fNameOfBiasedLogicalVolume.begin(), - fNameOfBiasedLogicalVolume.end(), - LogicalVolumeNameOfCreation) != - fNameOfBiasedLogicalVolume.end()) { - fFreeFlightOperation->SetMinWeight(fInitialWeight / - fRelativeMinWeightOfParticle); - fFreeFlightOperation->SetTrackWeight(track->GetWeight()); - fFreeFlightOperation->SetCountProcess(0); - return fFreeFlightOperation; - } - } - return 0; -} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... - -// Here we call the final state biasing operation called if one of the biased -// interaction (all photon interaction here) occurs. -// That's why we need here to apply some conditions to just split the initial -// track. - -G4VBiasingOperation * -GateOptrComptPseudoTransportationActor::ProposeFinalStateBiasingOperation( - const G4Track *track, const G4BiasingProcessInterface *callingProcess) { - G4String particleName = track->GetParticleDefinition()->GetParticleName(); - - G4String LogicalVolumeNameOfCreation = - track->GetLogicalVolumeAtVertex()->GetName(); - G4String CreationProcessName = ""; - if (track->GetCreatorProcess() != 0) { - CreationProcessName = track->GetCreatorProcess()->GetProcessName(); - } - - if (track->GetParticleDefinition()->GetParticleName() == "gamma") { - if (std::find(fNameOfBiasedLogicalVolume.begin(), - fNameOfBiasedLogicalVolume.end(), - LogicalVolumeNameOfCreation) != - fNameOfBiasedLogicalVolume.end()) { - return callingProcess->GetCurrentOccurenceBiasingOperation(); - } - } - - if (((CreationProcessName != "biasWrapper(conv)") && - (CreationProcessName != "biasWrapper(compt)")) && - (callingProcess->GetWrappedProcess()->GetProcessName() == "eBrem")) { - return feBremSplittingOperation; - } - - if (!(std::find(fCreationProcessNameList.begin(), - fCreationProcessNameList.end(), - CreationProcessName) != fCreationProcessNameList.end())) { - if (callingProcess->GetWrappedProcess()->GetProcessName() == "compt") { - - isSplitted = true; - return fScatteredGammaSplittingOperation; - } - if ((callingProcess->GetWrappedProcess()->GetProcessName() == "conv")) { - return fPairProdSplittingOperation; - } - } - return 0; -} - -void GateOptrComptPseudoTransportationActor::EndTracking() { - isSplitted = false; -} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h b/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h deleted file mode 100644 index c463a4e2c..000000000 --- a/core/opengate_core/opengate_lib/GateOptrComptPseudoTransportationActor.h +++ /dev/null @@ -1,138 +0,0 @@ -// -// ******************************************************************** -// * 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 GateOptrComptPseudoTransportationActor.h -/// \brief Definition of the GateOptrComptPseudoTransportationActor class -#ifndef GateOptrComptPseudoTransportationActor_h -#define GateOptrComptPseudoTransportationActor_h 1 - -#include "G4EmCalculator.hh" -#include "G4VBiasingOperator.hh" -#include "GateOptnForceFreeFlight.h" -#include "GateOptnPairProdSplitting.h" -#include "GateOptneBremSplitting.h" - -#include "GateVActor.h" -#include <iostream> -#include <pybind11/stl.h> -namespace py = pybind11; - -class GateOptnScatteredGammaSplitting; - -class GateOptrComptPseudoTransportationActor : public G4VBiasingOperator, - public GateVActor { -public: - GateOptrComptPseudoTransportationActor(py::dict &user_info); - virtual ~GateOptrComptPseudoTransportationActor() {} - -public: - // ------------------------- - // Optional from base class: - // ------------------------- - // -- Call at run start: - // virtual void BeginOfRunAction(const G4Run *run); - - // virtual void SteppingAction(G4Step* step); - - // -- Call at each track starting: - // virtual void PreUserTrackingAction( const G4Track* track ); - - G4double fSplittingFactor; - G4double fInitialWeight; - G4double fRelativeMinWeightOfParticle; - G4double fWeightThreshold; - G4bool fBiasPrimaryOnly; - G4bool fBiasOnlyOnce; - G4int fNInteractions = 0; - G4bool fRussianRouletteForAngle; - G4bool fRussianRouletteForWeights; - G4bool fRotationVectorDirector; - G4ThreeVector fVectorDirector; - G4double fMaxTheta; - G4bool isSplitted; - G4int NbOfTrack = 0; - G4int NbOfProbe = 1; - G4double weight = 0; - G4bool fKillOthersParticles = false; - G4bool fUseProbes = false; - G4bool fSurvivedRR = false; - G4bool fAttachToLogicalHolder = true; - G4bool fPassedByABiasedVolume = false; - G4double fKineticEnergyAtTheEntrance; - G4int ftrackIDAtTheEntrance; - G4int fEventID; - G4double fEventIDKineticEnergy; - G4bool ftestbool = false; - G4bool fIsFirstStep = false; - const G4VProcess *fAnnihilation = nullptr; - - std::vector<G4String> fNameOfBiasedLogicalVolume = {}; - std::vector<G4int> v_EventID = {}; - std::vector<G4String> fCreationProcessNameList = { - "biasWrapper(compt)", "biasWrapper(eBrem)", "biasWrapper(annihil)"}; - - // Unused but mandatory - - virtual void StartSimulationAction(); - virtual void StartRun(); - virtual void StartTracking(const G4Track *); - virtual void SteppingAction(G4Step *); - virtual void BeginOfEventAction(const G4Event *); - virtual void EndTracking(); - -protected: - // ----------------------------- - // -- Mandatory from base class: - // ----------------------------- - // -- Unused: - void AttachAllLogicalDaughtersVolumes(G4LogicalVolume *); - virtual G4VBiasingOperation *ProposeNonPhysicsBiasingOperation( - const G4Track * /* track */, - const G4BiasingProcessInterface * /* callingProcess */) { - return 0; - } - - // -- Used: - virtual G4VBiasingOperation *ProposeOccurenceBiasingOperation( - const G4Track * /* track */, - const G4BiasingProcessInterface * /* callingProcess */); - - virtual G4VBiasingOperation *ProposeFinalStateBiasingOperation( - const G4Track *track, const G4BiasingProcessInterface *callingProcess); - -private: - // -- Avoid compiler complaining for (wrong) method shadowing, - // -- this is because other virtual method with same name exists. - using G4VBiasingOperator::OperationApplied; - -private: - GateOptnForceFreeFlight *fFreeFlightOperation; - GateOptnScatteredGammaSplitting *fScatteredGammaSplittingOperation; - GateOptneBremSplitting *feBremSplittingOperation; - GateOptnPairProdSplitting *fPairProdSplittingOperation; -}; - -#endif diff --git a/core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp b/core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp deleted file mode 100644 index a5cb86c35..000000000 --- a/core/opengate_core/opengate_lib/pyGateOptrComptPseudoTransportationActor.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/* -------------------------------------------------- - Copyright (C): OpenGATE Collaboration - This software is distributed under the terms - of the GNU Lesser General Public Licence (LGPL) - See LICENSE.md for further details - -------------------------------------------------- */ -#include <pybind11/pybind11.h> - -namespace py = pybind11; -#include "G4VBiasingOperator.hh" -#include "GateOptrComptPseudoTransportationActor.h" - -void init_GateOptrComptPseudoTransportationActor(py::module &m) { - - py::class_< - GateOptrComptPseudoTransportationActor, G4VBiasingOperator, GateVActor, - std::unique_ptr<GateOptrComptPseudoTransportationActor, py::nodelete>>( - m, "GateOptrComptPseudoTransportationActor") - .def(py::init<py::dict &>()); -} From 922ea8245e498e1ee20115f362cc9c62c6f860a6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 15:07:02 +0000 Subject: [PATCH 61/82] [pre-commit.ci] Automatic python and c++ formatting --- .../GateKillNonInteractingParticleActor.cpp | 4 +- .../opengate_lib/GateTLEDoseActor.cpp | 81 +++++++++---------- .../opengate_lib/GateTLEDoseActor.h | 5 +- opengate/tests/src/test081_tle_1_geom.py | 1 - opengate/tests/src/test081_tle_2_vox.py | 2 - .../tests/src/test081_tle_4_high_energy.py | 4 +- opengate/tests/src/test081_tle_5_db.py | 3 - .../tests/src/test081_tle_6_brem_splitting.py | 38 ++++----- opengate/tests/src/test081_tle_helpers.py | 1 + 9 files changed, 63 insertions(+), 76 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp index 98fb66463..0a0a1ea22 100644 --- a/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp +++ b/core/opengate_core/opengate_lib/GateKillNonInteractingParticleActor.cpp @@ -48,8 +48,8 @@ void GateKillNonInteractingParticleActor::SteppingAction(G4Step *step) { (fIsFirstStep)) { // if ((fPassedByTheMotherVolume == false) && // (((step->GetPreStepPoint()->GetStepStatus() == 1) && - // (physicalVolumeNamePreStep == fAttachedToVolumeName)) || ((fIsFirstStep) && - // (step->GetTrack()->GetParentID() == 0)))) { + // (physicalVolumeNamePreStep == fAttachedToVolumeName)) || ((fIsFirstStep) + // && (step->GetTrack()->GetParentID() == 0)))) { if ((fPassedByTheMotherVolume == false) && (((step->GetPreStepPoint()->GetStepStatus() == 1) && (physicalVolumeNamePreStep == fAttachedToVolumeName)))) { diff --git a/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp b/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp index 89c7ea788..aa0bf084a 100644 --- a/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp +++ b/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp @@ -37,20 +37,18 @@ void GateTLEDoseActor::InitializeUserInfo(py::dict &user_info) { fMaterialMuHandler = GateMaterialMuHandler::GetInstance(database, fEnergyMax); } - -void GateTLEDoseActor::BeginOfEventAction(const G4Event* event) { +void GateTLEDoseActor::BeginOfEventAction(const G4Event *event) { auto &l = fThreadLocalData.Get(); l.fIsTLESecondary = false; l.fSecNbWhichDeposit.clear(); GateDoseActor::BeginOfEventAction(event); - - } void GateTLEDoseActor::PreUserTrackingAction(const G4Track *track) { auto &l = fThreadLocalData.Get(); - // if the particle is a gamma, we associate a map which will associate the gamma TID with the number of secondaries created - // when the particle is in TLE mode + // if the particle is a gamma, we associate a map which will associate the + // gamma TID with the number of secondaries created when the particle is in + // TLE mode if (track->GetDefinition()->GetParticleName() == "gamma") { l.fIsTLEGamma = false; @@ -58,76 +56,77 @@ void GateTLEDoseActor::PreUserTrackingAction(const G4Track *track) { l.fSecNbWhichDeposit[track->GetTrackID()] = 0; } - - //if the particle is not a gamma, we want to associate a secondary boolean to allow or not the energy deposition. - //- If it's a direct secondary of the gamma with the PID inside, which have to not deposit its energy, the boolean is set to false - //- If it's a direct secondary, but the number of remaining secondaries to track is 0, it means that this secondary was not created by a - //TLE gamma, and the boolean is set to false - //- If it's an indirect secondary, created by a secondary generated by a gamma, we do nothing, since the boolean was already fixed. - // If it's a primary or a secondary generated from a primary which is not a gamma, the boolean is set to False at the beginning of the event + // if the particle is not a gamma, we want to associate a secondary boolean to + // allow or not the energy deposition. + //- If it's a direct secondary of the gamma with the PID inside, which have to + //not deposit its energy, the boolean is set to false + //- If it's a direct secondary, but the number of remaining secondaries to + //track is 0, it means that this secondary was not created by a TLE gamma, and + // the boolean is set to false + //- If it's an indirect secondary, created by a secondary generated by a + //gamma, we do nothing, since the boolean was already fixed. + // If it's a primary or a secondary generated from a primary which is not a + // gamma, the boolean is set to False at the beginning of the event else { auto parent_id = track->GetParentID(); - //std::cout<<"begin"<<std::endl; - for (auto it = l.fSecNbWhichDeposit.begin(); it != l.fSecNbWhichDeposit.end(); ++it) { - //std::cout <<it->first<<" "<<it->second<<std::endl; - if (parent_id == it->first){ - if (it->second > 0){ - l.fIsTLESecondary = true; - } - if (it->second == 0){ - l.fSecNbWhichDeposit.erase(it); - l.fIsTLESecondary = false; - break; - } - it->second --; + // std::cout<<"begin"<<std::endl; + for (auto it = l.fSecNbWhichDeposit.begin(); + it != l.fSecNbWhichDeposit.end(); ++it) { + // std::cout <<it->first<<" "<<it->second<<std::endl; + if (parent_id == it->first) { + if (it->second > 0) { + l.fIsTLESecondary = true; + } + if (it->second == 0) { + l.fSecNbWhichDeposit.erase(it); + l.fIsTLESecondary = false; + break; } + it->second--; + } } - //std::cout<<"end"<<std::endl; + // std::cout<<"end"<<std::endl; } } - void GateTLEDoseActor::SteppingAction(G4Step *step) { auto &l = fThreadLocalData.Get(); auto pre_step = step->GetPreStepPoint(); double energy = 0; - if (pre_step !=0) - energy =pre_step->GetKineticEnergy(); + if (pre_step != 0) + energy = pre_step->GetKineticEnergy(); if (step->GetTrack()->GetDefinition()->GetParticleName() == "gamma") { - // For too high energy, no TLE + // For too high energy, no TLE if (energy > fEnergyMax) { l.fIsTLEGamma = false; l.fIsTLESecondary = false; return GateDoseActor::SteppingAction(step); - } - else { + } else { l.fIsTLEGamma = true; } - // Update the number of secondaries if there is secondary in the current non TLE particle - if (l.fIsTLEGamma==true){ + // Update the number of secondaries if there is secondary in the current non + // TLE particle + if (l.fIsTLEGamma == true) { auto nbSec = step->GetSecondaryInCurrentStep()->size(); - if (nbSec > 0){ + if (nbSec > 0) { l.fIsTLESecondary = true; l.fSecNbWhichDeposit[step->GetTrack()->GetTrackID()] += nbSec; } - //l.fLastTrackId += step->GetSecondaryInCurrentStep()->size(); + // l.fLastTrackId += step->GetSecondaryInCurrentStep()->size(); } } - - // For non-gamma particle, no TLE if (step->GetTrack()->GetDefinition()->GetParticleName() != "gamma") { - if (l.fIsTLESecondary == true){ + if (l.fIsTLESecondary == true) { return; } return GateDoseActor::SteppingAction(step); } - auto weight = step->GetTrack()->GetWeight(); auto step_length = step->GetStepLength(); auto density = pre_step->GetMaterial()->GetDensity(); diff --git a/core/opengate_core/opengate_lib/GateTLEDoseActor.h b/core/opengate_core/opengate_lib/GateTLEDoseActor.h index b11afc927..27e3d64e7 100644 --- a/core/opengate_core/opengate_lib/GateTLEDoseActor.h +++ b/core/opengate_core/opengate_lib/GateTLEDoseActor.h @@ -28,7 +28,7 @@ class GateTLEDoseActor : public GateDoseActor { void InitializeUserInfo(py::dict &user_info) override; - void BeginOfEventAction(const G4Event* event) override; + void BeginOfEventAction(const G4Event *event) override; void PreUserTrackingAction(const G4Track *track) override; @@ -45,8 +45,7 @@ class GateTLEDoseActor : public GateDoseActor { // Bool if current track is a TLE gamma or not bool fIsTLEGamma; bool fIsTLESecondary; - std::map<G4int,G4int> fSecNbWhichDeposit; - + std::map<G4int, G4int> fSecNbWhichDeposit; }; G4Cache<threadLocalT> fThreadLocalData; diff --git a/opengate/tests/src/test081_tle_1_geom.py b/opengate/tests/src/test081_tle_1_geom.py index f2dfb6665..f908f6ce2 100755 --- a/opengate/tests/src/test081_tle_1_geom.py +++ b/opengate/tests/src/test081_tle_1_geom.py @@ -76,7 +76,6 @@ def main(argv): print(f"Dose actor spacing : {dose_actor.spacing} mm") print(f"Dose actor size : {waterbox.size} mm") - # add stat actor stats = sim.add_actor("SimulationStatisticsActor", "stats") stats.track_types_flag = True diff --git a/opengate/tests/src/test081_tle_2_vox.py b/opengate/tests/src/test081_tle_2_vox.py index e2ac24b24..5618de9a3 100755 --- a/opengate/tests/src/test081_tle_2_vox.py +++ b/opengate/tests/src/test081_tle_2_vox.py @@ -80,8 +80,6 @@ print(f"Dose actor spacing : {dose_actor.spacing} mm") print(f"Dose actor size : {waterbox_size} mm") - - # add stat actor stats = sim.add_actor("SimulationStatisticsActor", "stats") stats.track_types_flag = True diff --git a/opengate/tests/src/test081_tle_4_high_energy.py b/opengate/tests/src/test081_tle_4_high_energy.py index 4b51569c8..9fa375bed 100755 --- a/opengate/tests/src/test081_tle_4_high_energy.py +++ b/opengate/tests/src/test081_tle_4_high_energy.py @@ -63,7 +63,7 @@ # resulting dose will be biased. The energy threshold depends on the voxels size of the # dose actor. Here the bias is clearly visible if TLE is used above 1.2 MeV. # With the threshold enabled, no acceleration for high enery gamma, but no bias. - tle_dose_actor.energy_max = 0.8* MeV + tle_dose_actor.energy_max = 0.8 * MeV tle_dose_actor.database = "EPDL" print(f"TLE Dose actor pixels : {tle_dose_actor.size}") print(f"TLE Dose actor spacing : {tle_dose_actor.spacing} mm") @@ -81,8 +81,6 @@ print(f"Dose actor spacing : {dose_actor.spacing} mm") print(f"Dose actor size : {waterbox_size} mm") - - # add stat actor stats = sim.add_actor("SimulationStatisticsActor", "stats") stats.track_types_flag = True diff --git a/opengate/tests/src/test081_tle_5_db.py b/opengate/tests/src/test081_tle_5_db.py index 775c4bb66..068e7dd80 100755 --- a/opengate/tests/src/test081_tle_5_db.py +++ b/opengate/tests/src/test081_tle_5_db.py @@ -64,7 +64,6 @@ print(f"TLE Dose actor spacing : {tle_dose_actor.spacing} mm") print(f"TLE Dose actor size : {waterbox_size} mm") - # add conventional dose actor dose_actor = sim.add_actor("TLEDoseActor", "dose_actor") dose_actor.output_filename = "test081_db_epdl.mhd" @@ -78,8 +77,6 @@ print(f"Dose actor spacing : {dose_actor.spacing} mm") print(f"Dose actor size : {waterbox_size} mm") - - # add stat actor stats = sim.add_actor("SimulationStatisticsActor", "stats") stats.track_types_flag = True diff --git a/opengate/tests/src/test081_tle_6_brem_splitting.py b/opengate/tests/src/test081_tle_6_brem_splitting.py index ded1f1f2a..020c714fb 100755 --- a/opengate/tests/src/test081_tle_6_brem_splitting.py +++ b/opengate/tests/src/test081_tle_6_brem_splitting.py @@ -15,11 +15,11 @@ """ To correctly check if the TLE applied to a high number of track per event, a brem splitting of 6 MeV electrons -is applied at a target before the waterbox entrance, with a very low +is applied at a target before the waterbox entrance, with a very low """ -def test(f1,f2,f1_err,f2_err): +def test(f1, f2, f1_err, f2_err): img1 = itk.imread(f1) img2 = itk.imread(f2) img1_err = itk.imread(f1_err) @@ -29,11 +29,11 @@ def test(f1,f2,f1_err,f2_err): img1_err_arr = np.sum(itk.GetArrayFromImage(img1_err)) img2_err_arr = np.sum(itk.GetArrayFromImage(img2_err)) - tot_err = np.sqrt((img1_arr * img1_err_arr)**2 + (img2_arr * img2_err_arr)**2) - print("dose actor:", img1_arr, "+-",img1_err_arr*img1_arr,"Gy") - print("TLE dose actor:", img2_arr, "+-", img2_err_arr*img2_arr, "Gy") - if np.abs(img2_arr - img1_arr) < 4*tot_err : - return(True) + tot_err = np.sqrt((img1_arr * img1_err_arr) ** 2 + (img2_arr * img2_err_arr) ** 2) + print("dose actor:", img1_arr, "+-", img1_err_arr * img1_arr, "Gy") + print("TLE dose actor:", img2_arr, "+-", img2_err_arr * img2_arr, "Gy") + if np.abs(img2_arr - img1_arr) < 4 * tot_err: + return True def main(argv): @@ -59,7 +59,7 @@ def main(argv): keV = gate.g4_units.keV MeV = gate.g4_units.MeV Bq = gate.g4_units.Bq - gcm3 = gate.g4_units.g/gate.g4_units.cm3 + gcm3 = gate.g4_units.g / gate.g4_units.cm3 # change world size world = sim.world @@ -85,15 +85,14 @@ def main(argv): ) # default source for tests - source = add_source(sim, n=2500, energy = 2.5*MeV) + source = add_source(sim, n=2500, energy=2.5 * MeV) source.particle = "e-" - - target = sim.add_volume("Box","target") - target.size = [10*cm,10*cm,2*mm] + target = sim.add_volume("Box", "target") + target.size = [10 * cm, 10 * cm, 2 * mm] target.material = "Tungsten" - target.translation = [0,0, - waterbox.size[2]/2 - 1 * mm] - target.color = [0.6,0.8,0.4,0.9] + target.translation = [0, 0, -waterbox.size[2] / 2 - 1 * mm] + target.color = [0.6, 0.8, 0.4, 0.9] region_linac = sim.physics_manager.add_region(name=f"{target.name}_region") region_linac.associate_volume(target) @@ -107,25 +106,24 @@ def main(argv): tle_dose_actor.attached_to = waterbox tle_dose_actor.dose_uncertainty.active = True tle_dose_actor.dose.active = True - tle_dose_actor.size = [1,1,1] + tle_dose_actor.size = [1, 1, 1] tle_dose_actor.spacing = [x / y for x, y in zip(waterbox.size, tle_dose_actor.size)] tle_dose_actor.energy_max = 2.5 * MeV print(f"TLE Dose actor pixels : {tle_dose_actor.size}") print(f"TLE Dose actor spacing : {tle_dose_actor.spacing} mm") - #add conventional dose actor + # add conventional dose actor dose_actor = sim.add_actor("DoseActor", "dose_actor") dose_actor.output_filename = "test081.mhd" dose_actor.attached_to = waterbox dose_actor.dose_uncertainty.active = True dose_actor.dose.active = True - dose_actor.size = [1,1,1] + dose_actor.size = [1, 1, 1] dose_actor.spacing = [x / y for x, y in zip(waterbox.size, dose_actor.size)] print(f"Dose actor pixels : {dose_actor.size}") print(f"Dose actor spacing : {dose_actor.spacing} mm") print(f"Dose actor size : {waterbox.size} mm") - print(f"TLE Dose actor size : {waterbox.size} mm") # add stat actor @@ -140,14 +138,12 @@ def main(argv): f1_bis = dose_actor.dose_uncertainty.get_output_path() f2_bis = tle_dose_actor.dose_uncertainty.get_output_path() - - is_ok = test(f1,f2,f1_bis,f2_bis) + is_ok = test(f1, f2, f1_bis, f2_bis) utility.test_ok(is_ok) # print results at the end print(stats) - if __name__ == "__main__": main(sys.argv) diff --git a/opengate/tests/src/test081_tle_helpers.py b/opengate/tests/src/test081_tle_helpers.py index 6efb58054..df7df3841 100644 --- a/opengate/tests/src/test081_tle_helpers.py +++ b/opengate/tests/src/test081_tle_helpers.py @@ -38,6 +38,7 @@ def add_waterbox(sim): return waterbox + def add_simple_waterbox(sim): # units cm = gate.g4_units.cm From 4edbfd179d926f032231b516615db0e6c8883db2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 11:03:27 +0000 Subject: [PATCH 62/82] [pre-commit.ci] Automatic python and c++ formatting --- .../GateKillAccordingProcessesActor.cpp | 31 +++++------ .../opengate_lib/GateTLEDoseActor.cpp | 7 ++- opengate/actors/miscactors.py | 18 ++++-- .../test082_kill_according_all_processes.py | 44 ++++++++------- .../src/test082_kill_according_processes.py | 55 +++++++++++-------- 5 files changed, 86 insertions(+), 69 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateKillAccordingProcessesActor.cpp b/core/opengate_core/opengate_lib/GateKillAccordingProcessesActor.cpp index 40aaa515e..011f06bb5 100644 --- a/core/opengate_core/opengate_lib/GateKillAccordingProcessesActor.cpp +++ b/core/opengate_core/opengate_lib/GateKillAccordingProcessesActor.cpp @@ -48,7 +48,8 @@ GateKillAccordingProcessesActor::GetListOfPhysicsListProcesses() { void GateKillAccordingProcessesActor::InitializeUserInfo(py::dict &user_info) { GateVActor::InitializeUserInfo(user_info); fProcessesToKill = DictGetVecStr(user_info, "processes_to_kill"); - fIsRayleighAnInteraction = DictGetBool(user_info, "is_rayleigh_an_interaction"); + fIsRayleighAnInteraction = + DictGetBool(user_info, "is_rayleigh_an_interaction"); } void GateKillAccordingProcessesActor::BeginOfRunAction(const G4Run *run) { @@ -71,15 +72,13 @@ void GateKillAccordingProcessesActor::BeginOfRunAction(const G4Run *run) { errorMessage); } } - if (fProcessesToKill[0] == "all"){ - if (fProcessesToKill.size() == 1){ + if (fProcessesToKill[0] == "all") { + if (fProcessesToKill.size() == 1) { fKillIfAnyInteraction = true; } } - } - void GateKillAccordingProcessesActor::PreUserTrackingAction( const G4Track *track) { fIsFirstStep = true; @@ -96,33 +95,31 @@ void GateKillAccordingProcessesActor::SteppingAction(G4Step *step) { const G4VProcess *process = step->GetPostStepPoint()->GetProcessDefinedStep(); if (process != 0) processName = process->GetProcessName(); - // Positron exception to retrieve the annihilation process, since it's an at // rest process most of the time - if ((step->GetTrack()->GetParticleDefinition()->GetParticleName() == "e+") && (step->GetTrack()->GetTrackStatus() == 1)) + if ((step->GetTrack()->GetParticleDefinition()->GetParticleName() == "e+") && + (step->GetTrack()->GetTrackStatus() == 1)) processName = "annihil"; - - if (fKillIfAnyInteraction){ - if (processName != "Transportation"){ - if (fIsRayleighAnInteraction == true){ + if (fKillIfAnyInteraction) { + if (processName != "Transportation") { + if (fIsRayleighAnInteraction == true) { step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); G4AutoLock mutex(&SetNbKillAccordingProcessesMutex); fNbOfKilledParticles++; - } - else { - if (processName != "Rayl"){ + } else { + if (processName != "Rayl") { step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); G4AutoLock mutex(&SetNbKillAccordingProcessesMutex); fNbOfKilledParticles++; } } } - } - else{ - if (std::find(fProcessesToKill.begin(),fProcessesToKill.end(),processName) != fProcessesToKill.end()) { + } else { + if (std::find(fProcessesToKill.begin(), fProcessesToKill.end(), + processName) != fProcessesToKill.end()) { step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); G4AutoLock mutex(&SetNbKillAccordingProcessesMutex); fNbOfKilledParticles++; diff --git a/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp b/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp index aa0bf084a..4bee2de08 100644 --- a/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp +++ b/core/opengate_core/opengate_lib/GateTLEDoseActor.cpp @@ -59,12 +59,13 @@ void GateTLEDoseActor::PreUserTrackingAction(const G4Track *track) { // if the particle is not a gamma, we want to associate a secondary boolean to // allow or not the energy deposition. //- If it's a direct secondary of the gamma with the PID inside, which have to - //not deposit its energy, the boolean is set to false + // not deposit its energy, the boolean is set to false //- If it's a direct secondary, but the number of remaining secondaries to - //track is 0, it means that this secondary was not created by a TLE gamma, and + // track is 0, it means that this secondary was not created by a TLE gamma, + // and // the boolean is set to false //- If it's an indirect secondary, created by a secondary generated by a - //gamma, we do nothing, since the boolean was already fixed. + // gamma, we do nothing, since the boolean was already fixed. // If it's a primary or a secondary generated from a primary which is not a // gamma, the boolean is set to False at the beginning of the event diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index be96228b1..3446b99ea 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -315,12 +315,12 @@ class KillAccordingProcessesActor(ActorBase, g4.GateKillAccordingProcessesActor) "doc": "If a processes belonging to this list occured, the particle and its potential secondaries are killed. the variable all can be set up to kill a particle if an interaction occured." }, ), - "is_rayleigh_an_interaction":( + "is_rayleigh_an_interaction": ( True, { "doc": "Specific case to be faster. If a user wants to kill all interactions which implies an energy loss, this boolean enables to not account Rayleigh process as an interaction" - } - ) + }, + ), } def __init__(self, *args, **kwargs): @@ -334,17 +334,22 @@ def __init__(self, *args, **kwargs): def __initcpp__(self): g4.GateKillAccordingProcessesActor.__init__(self, self.user_info) self.AddActions( - {"BeginOfRunAction","BeginOfEventAction","PreUserTrackingAction", "SteppingAction","EndSimulationAction"} + { + "BeginOfRunAction", + "BeginOfEventAction", + "PreUserTrackingAction", + "SteppingAction", + "EndSimulationAction", + } ) def initialize(self): ActorBase.initialize(self) self.InitializeUserInfo(self.user_info) self.InitializeCpp() - if len(self.user_info.processes_to_kill)== 0: + if len(self.user_info.processes_to_kill) == 0: fatal("You have to select at least one process ! ") - def EndSimulationAction(self): self.user_output.kill_interacting_particles.number_of_killed_particles = ( self.number_of_killed_particles @@ -354,6 +359,7 @@ def __str__(self): s = self.user_output["kill_non_interacting_particles"].__str__() return s + class KillActor(ActorBase, g4.GateKillActor): """ Actor which kill a particle entering in a volume with the following attached actor. diff --git a/opengate/tests/src/test082_kill_according_all_processes.py b/opengate/tests/src/test082_kill_according_all_processes.py index 87aa76c3e..81f82ad1f 100644 --- a/opengate/tests/src/test082_kill_according_all_processes.py +++ b/opengate/tests/src/test082_kill_according_all_processes.py @@ -10,22 +10,23 @@ def test083_test(df): - df = df[df["PDGCode"] ==22] + df = df[df["PDGCode"] == 22] nb_event = len(df["ParentID"]) nb_event_to_interest = len(df["ParentID"][df["ParentID"] == 0]) tab_vertex_ekin = df["TrackVertexKineticEnergy"] - tab_ekin = df["KineticEnergy"] + tab_ekin = df["KineticEnergy"] dz_diff = df["PreDirection_Z"][df["PreDirection_Z"] != -1] print("Number of photons undergoing at least one rayleigh process", len(dz_diff)) - if (nb_event_to_interest == nb_event) and (np.all(tab_ekin == tab_vertex_ekin) and len(dz_diff >0)): + if (nb_event_to_interest == nb_event) and ( + np.all(tab_ekin == tab_vertex_ekin) and len(dz_diff > 0) + ): return True return False - if __name__ == "__main__": paths = utility.get_default_test_paths(__file__) output_path = paths.output @@ -72,8 +73,8 @@ def test083_test(df): source.particle = "gamma" source.position.type = "box" source.attached_to = world.name - source.position.size = [1*nm,1*nm,1*nm] - source.position.translation = [0, 0, 10*cm + 1 * mm] + source.position.size = [1 * nm, 1 * nm, 1 * nm] + source.position.translation = [0, 0, 10 * cm + 1 * mm] source.direction.type = "momentum" source.direction_relative_to_attached_volume = True # source1.direction.focus_point = [0*cm, 0*cm, -5 *cm] @@ -83,7 +84,7 @@ def test083_test(df): source.n = 100000 tungsten = sim.add_volume("Box", "tungsten_box") - tungsten.size = [3 * cm, 3 * cm, 1 * cm] + tungsten.size = [3 * cm, 3 * cm, 1 * cm] tungsten.material = "Tungsten" tungsten.mother = world.name tungsten.color = [0.5, 0.9, 0.3, 1] @@ -91,22 +92,28 @@ def test083_test(df): kill_proc_act = sim.add_actor("KillAccordingProcessesActor", "kill_proc_act") kill_proc_act.attached_to = tungsten.name kill_proc_act.is_rayleigh_an_interaction = False - kill_proc_act.processes_to_kill=["all"] - + kill_proc_act.processes_to_kill = ["all"] phsp_sphere = sim.add_volume("Sphere", "phsp_sphere") - phsp_sphere.mother =world.name + phsp_sphere.mother = world.name phsp_sphere.material = "G4_Galactic" - phsp_sphere.rmin = 5 *cm - phsp_sphere.rmax = 5 * cm +1*nm - + phsp_sphere.rmin = 5 * cm + phsp_sphere.rmax = 5 * cm + 1 * nm sim.output_dir = output_path phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace") phsp.attached_to = phsp_sphere.name - phsp.attributes = ["ParentID","EventID", "TrackID", "KineticEnergy","TrackVertexKineticEnergy","PreDirection","PDGCode"] + phsp.attributes = [ + "ParentID", + "EventID", + "TrackID", + "KineticEnergy", + "TrackVertexKineticEnergy", + "PreDirection", + "PDGCode", + ] name_phsp = "test083_" + phsp.name + ".root" - phsp.output_filename= name_phsp + phsp.output_filename = name_phsp sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" sim.physics_manager.enable_decay = False @@ -114,7 +121,7 @@ def test083_test(df): sim.physics_manager.global_production_cuts.electron = 1 * km sim.physics_manager.global_production_cuts.positron = 1 * km - #Mandatory for this actor, since gamma processes are encompassed in GammaGeneralProc without. + # Mandatory for this actor, since gamma processes are encompassed in GammaGeneralProc without. s = f"/process/em/UseGeneralProcess false" sim.g4_commands_before_init.append(s) @@ -125,10 +132,7 @@ def test083_test(df): # # go ! sim.run() # - phsp = uproot.open( - str(output_path) - + "/test083_PhaseSpace.root" - + ":PhaseSpace") + phsp = uproot.open(str(output_path) + "/test083_PhaseSpace.root" + ":PhaseSpace") df = phsp.arrays() is_ok = test083_test(df) diff --git a/opengate/tests/src/test082_kill_according_processes.py b/opengate/tests/src/test082_kill_according_processes.py index 8a7a8f21a..aa8981ded 100644 --- a/opengate/tests/src/test082_kill_according_processes.py +++ b/opengate/tests/src/test082_kill_according_processes.py @@ -10,21 +10,27 @@ def test083_test(df): - df = df[df["PDGCode"] ==22] + df = df[df["PDGCode"] == 22] nb_event = len(df["ParentID"]) nb_event_to_interest = len(df["ParentID"][df["ParentID"] == 0]) tab_vertex_ekin = df["TrackVertexKineticEnergy"] - tab_ekin = df["KineticEnergy"] - dz_diff = df["PreDirection_Z"][(df["PreDirection_Z"] != -1) & (df["TrackVertexKineticEnergy"] == df["KineticEnergy"])] - - print("Number of detected photons with an energy/a modified momentum:",len(dz_diff)) - if (nb_event_to_interest == nb_event) and (np.all(tab_ekin == tab_vertex_ekin) and (len(dz_diff)== 0)): + tab_ekin = df["KineticEnergy"] + dz_diff = df["PreDirection_Z"][ + (df["PreDirection_Z"] != -1) + & (df["TrackVertexKineticEnergy"] == df["KineticEnergy"]) + ] + + print( + "Number of detected photons with an energy/a modified momentum:", len(dz_diff) + ) + if (nb_event_to_interest == nb_event) and ( + np.all(tab_ekin == tab_vertex_ekin) and (len(dz_diff) == 0) + ): return True return False - if __name__ == "__main__": paths = utility.get_default_test_paths(__file__) output_path = paths.output @@ -71,8 +77,8 @@ def test083_test(df): source.particle = "gamma" source.position.type = "box" source.attached_to = world.name - source.position.size = [1*nm,1*nm,1*nm] - source.position.translation = [0, 0, 10*cm + 1 * mm] + source.position.size = [1 * nm, 1 * nm, 1 * nm] + source.position.translation = [0, 0, 10 * cm + 1 * mm] source.direction.type = "momentum" source.direction_relative_to_attached_volume = True # source1.direction.focus_point = [0*cm, 0*cm, -5 *cm] @@ -82,29 +88,35 @@ def test083_test(df): source.n = 100000 tungsten = sim.add_volume("Box", "tungsten_box") - tungsten.size = [3 * cm, 3 * cm, 1 * cm] + tungsten.size = [3 * cm, 3 * cm, 1 * cm] tungsten.material = "Tungsten" tungsten.mother = world.name tungsten.color = [0.5, 0.9, 0.3, 1] kill_proc_act = sim.add_actor("KillAccordingProcessesActor", "kill_proc_act") kill_proc_act.attached_to = tungsten.name - kill_proc_act.processes_to_kill=["compt","Rayl"] - + kill_proc_act.processes_to_kill = ["compt", "Rayl"] phsp_sphere = sim.add_volume("Sphere", "phsp_sphere") - phsp_sphere.mother =world.name + phsp_sphere.mother = world.name phsp_sphere.material = "G4_Galactic" - phsp_sphere.rmin = 5 *cm - phsp_sphere.rmax = 5 * cm +1*nm - + phsp_sphere.rmin = 5 * cm + phsp_sphere.rmax = 5 * cm + 1 * nm sim.output_dir = output_path phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace") phsp.attached_to = phsp_sphere.name - phsp.attributes = ["ParentID","EventID", "TrackID", "KineticEnergy","TrackVertexKineticEnergy","PreDirection","PDGCode"] + phsp.attributes = [ + "ParentID", + "EventID", + "TrackID", + "KineticEnergy", + "TrackVertexKineticEnergy", + "PreDirection", + "PDGCode", + ] name_phsp = "test083_" + phsp.name + ".root" - phsp.output_filename= name_phsp + phsp.output_filename = name_phsp sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" sim.physics_manager.enable_decay = False @@ -112,7 +124,7 @@ def test083_test(df): sim.physics_manager.global_production_cuts.electron = 1 * km sim.physics_manager.global_production_cuts.positron = 1 * km - #Mandatory for this actor, since gamma processes are encompassed in GammaGeneralProc without. + # Mandatory for this actor, since gamma processes are encompassed in GammaGeneralProc without. s = f"/process/em/UseGeneralProcess false" sim.g4_commands_before_init.append(s) @@ -123,10 +135,7 @@ def test083_test(df): # # go ! sim.run() # - phsp = uproot.open( - str(output_path) - + "/test083_PhaseSpace.root" - + ":PhaseSpace") + phsp = uproot.open(str(output_path) + "/test083_PhaseSpace.root" + ":PhaseSpace") df = phsp.arrays() is_ok = test083_test(df) From 8b36ea653fa8c1240a889dd60dd42accab923579 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Mon, 25 Nov 2024 13:53:39 +0100 Subject: [PATCH 63/82] Add a user parameter to limit the maximum number of batch to generate --- ...ateLastVertexInteractionSplittingActor.cpp | 3 ++- .../GateLastVertexInteractionSplittingActor.h | 1 + opengate/actors/miscactors.py | 19 ++++++++++++++++++- .../src/test082_last_vertex_splittting.py | 3 ++- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index 5a2c73d09..0220d8477 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -66,6 +66,7 @@ void GateLastVertexInteractionSplittingActor::InitializeUserInfo( fVectorDirector = DictGetG4ThreeVector(user_info, "vector_director"); fMaxTheta = DictGetDouble(user_info, "max_theta"); fBatchSize = DictGetDouble(user_info, "batch_size"); + fNbOfMaxBatchPerEvent = DictGetInt(user_info, "nb_of_max_batch_per_event"); } //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... @@ -357,7 +358,7 @@ void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex( fNumberOfTrackToSimulate = fStackManager->GetNTotalTrack() - nbOfTrackAlreadyInStack; fNbOfBatchForExitingParticle++; - if (fNbOfBatchForExitingParticle > 500) { + if (fNbOfBatchForExitingParticle > fNbOfMaxBatchPerEvent) { fStackManager->clear(); } // stackManager->clear(); diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h index 90be6e135..21bd30e37 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h @@ -85,6 +85,7 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { tree<LastVertexDataContainer>::post_order_iterator fIterator; std::vector<LastVertexDataContainer> fListOfContainer; G4StackManager *fStackManager = nullptr; + G4int fNbOfMaxBatchPerEvent; G4Track *fTrackToSplit = nullptr; G4Step *fCopyInitStep = nullptr; diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 3d4926c6e..29b4e4b5a 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -415,6 +415,17 @@ class LastVertexInteractionSplittingActor( such as in medical LINAC (Linear Accelerator) simulations or radiation shielding. """ + + # hints for IDE + splitting_factor: int + angular_kill: bool + max_theta: float + vector_director: list + rotation_vector_director: bool + batch_size:int + nb_of_max_batch_per_event:int + + user_info_defaults = { "splitting_factor": ( 1, @@ -449,7 +460,13 @@ class LastVertexInteractionSplittingActor( "batch_size": ( 1, { - "doc": "Defines a batch of number of processes to regenerate, calculated as batch_size * splitting_factor. The optimal value depends on the collimation setup; for example, a batch_size of 10 works well for LINAC head configurations.", + "doc": "Defines a batch of number of processes to regenerate. The optimal value depends on the collimation setup; for example, a batch_size of 10 works well for LINAC head configurations.", + }, + ), + "nb_of_max_batch_per_event": ( + 500, + { + "doc": "Defines a maximum number of attempt to enable the particles to exit. Useful to avoid an important loss of time for extremely rare events", }, ), } diff --git a/opengate/tests/src/test082_last_vertex_splittting.py b/opengate/tests/src/test082_last_vertex_splittting.py index 390c5d1ae..3d90d7dd4 100755 --- a/opengate/tests/src/test082_last_vertex_splittting.py +++ b/opengate/tests/src/test082_last_vertex_splittting.py @@ -154,7 +154,8 @@ def validation_test(arr_ref, arr_data, nb_split): vertex_splitting_actor.angular_kill = True vertex_splitting_actor.vector_director = [0, 0, -1] vertex_splitting_actor.max_theta = 90 * deg - vertex_splitting_actor.batch_size = 10 + vertex_splitting_actor.batch_size = 10*nb_split + vertex_splitting_actor.nb_of_max_batch_per_event = 500 plan = sim.add_volume("Box", "plan_phsp") plan.material = "G4_Galactic" From a64a11ecb921a2817f922b0d0ebf69c84676732a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:58:21 +0000 Subject: [PATCH 64/82] [pre-commit.ci] Automatic python and c++ formatting --- opengate/actors/miscactors.py | 6 ++---- opengate/tests/src/test082_last_vertex_splittting.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 7d903d25f..03889c245 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -584,16 +584,14 @@ class LastVertexInteractionSplittingActor( such as in medical LINAC (Linear Accelerator) simulations or radiation shielding. """ - # hints for IDE splitting_factor: int angular_kill: bool max_theta: float vector_director: list rotation_vector_director: bool - batch_size:int - nb_of_max_batch_per_event:int - + batch_size: int + nb_of_max_batch_per_event: int user_info_defaults = { "splitting_factor": ( diff --git a/opengate/tests/src/test082_last_vertex_splittting.py b/opengate/tests/src/test082_last_vertex_splittting.py index 3d90d7dd4..6a40856a6 100755 --- a/opengate/tests/src/test082_last_vertex_splittting.py +++ b/opengate/tests/src/test082_last_vertex_splittting.py @@ -154,7 +154,7 @@ def validation_test(arr_ref, arr_data, nb_split): vertex_splitting_actor.angular_kill = True vertex_splitting_actor.vector_director = [0, 0, -1] vertex_splitting_actor.max_theta = 90 * deg - vertex_splitting_actor.batch_size = 10*nb_split + vertex_splitting_actor.batch_size = 10 * nb_split vertex_splitting_actor.nb_of_max_batch_per_event = 500 plan = sim.add_volume("Box", "plan_phsp") From 9ed46c6d96560086e81d48fe26c64f29d1c2f0c1 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Thu, 28 Nov 2024 09:52:05 +0100 Subject: [PATCH 65/82] modif leaf design --- opengate/contrib/linacs/elektaversa.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/opengate/contrib/linacs/elektaversa.py b/opengate/contrib/linacs/elektaversa.py index 9be3fe88a..1798b4b12 100644 --- a/opengate/contrib/linacs/elektaversa.py +++ b/opengate/contrib/linacs/elektaversa.py @@ -539,7 +539,8 @@ def mlc_leaf(linac_name): interleaf_gap = 0.09 * mm leaf_length = 155 * mm leaf_height = 90 * mm - leaf_mean_width = 1.76 * mm + # leaf_mean_width = 1.76 * mm + leaf_mean_width = 1.69 * mm tongues_length = 0.8 * mm cyl = volumes.TubsVolume(name=f"{linac_name}_cylinder_leaf") @@ -552,8 +553,10 @@ def mlc_leaf(linac_name): trap_leaf = volumes.TrapVolume(name=f"{linac_name}_trap_leaf") dz = leaf_height / 2 dy1 = leaf_length / 2 - dx1 = 1.94 * mm / 2 - dx3 = 1.58 * mm / 2 + # dx1 = 1.94 * mm / 2 + # dx3 = 1.58 * mm / 2 + dx1 = 1.91 * mm / 2 + dx3 = 1.47 * mm / 2 theta = 0 alpha1 = 0 @@ -583,11 +586,13 @@ def mlc_leaf(linac_name): dy1 = leaf_length / 2 ##FIXME I need to remove 2 um to the tongues to avoid an overleap between leaves - dx1 = (interleaf_gap - 2.2 * 10 ** (-3) * mm) / 2 + # dx1 = (interleaf_gap - 2.2 * 10 ** (-3) * mm) / 2 + dx1 = (interleaf_gap - 3.3 * 10 ** (-3) * mm) / 2 dx3 = dx1 alpha1 = 0 alpha2 = alpha1 - theta = np.arctan((1.58 * mm - 1.94 * mm) * 0.5 / leaf_height) + # theta = np.arctan((1.58 * mm - 1.94 * mm) * 0.5 / leaf_height) + theta = np.arctan((1.47 * mm - 1.91 * mm) * 0.5 / leaf_height) phi = 0 dy2 = dy1 dx2 = dx1 @@ -624,14 +629,16 @@ def add_mlc(sim, linac_name): z_linac = linac.size[2] center_mlc = 349.3 * mm + 3.5 * mm interleaf_gap = 0.09 * mm - leaf_width = 1.76 * mm + # leaf_width = 1.76 * mm + leaf_width = 1.69 * mm leaf_lenght = 155 * mm nb_leaf = 160 - rotation_angle = np.arctan((1.94 * mm - 1.58 * mm) * 0.5 / leaf_height) + # rotation_angle = np.arctan((1.94 * mm - 1.58 * mm) * 0.5 / leaf_height) + rotation_angle = np.arctan((1.91 * mm - 1.47 * mm) * 0.5 / leaf_height) mlc = sim.add_volume("Box", f"{linac_name}_mlc") mlc_bank_rotation = Rotation.from_euler( - "X", np.arctan(3.25 / 349.3), degrees=False + "X", - np.arctan(3.25 / 349.3), degrees=False ).as_matrix() mlc.rotation = mlc_bank_rotation mlc.size = [linac.size[0] - 2 * cm, linac.size[1] - 2 * cm, 95 * mm] From 839a185628e18597279ecfb71e8f7c6fa42a07f1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:55:06 +0000 Subject: [PATCH 66/82] [pre-commit.ci] Automatic python and c++ formatting --- opengate/actors/miscactors.py | 1 - opengate/contrib/linacs/elektaversa.py | 2 +- opengate/tests/src/test082_kill_according_processes.py | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 78b580383..5d5b4b635 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -331,7 +331,6 @@ class KillAccordingProcessesActor(ActorBase, g4.GateKillAccordingProcessesActor) ), } - user_output_config = { "kill_according_processes": { "actor_output_class": ActorOutputKillAccordingProcessesActor, diff --git a/opengate/contrib/linacs/elektaversa.py b/opengate/contrib/linacs/elektaversa.py index 1798b4b12..12ec244d4 100644 --- a/opengate/contrib/linacs/elektaversa.py +++ b/opengate/contrib/linacs/elektaversa.py @@ -638,7 +638,7 @@ def add_mlc(sim, linac_name): mlc = sim.add_volume("Box", f"{linac_name}_mlc") mlc_bank_rotation = Rotation.from_euler( - "X", - np.arctan(3.25 / 349.3), degrees=False + "X", -np.arctan(3.25 / 349.3), degrees=False ).as_matrix() mlc.rotation = mlc_bank_rotation mlc.size = [linac.size[0] - 2 * cm, linac.size[1] - 2 * cm, 95 * mm] diff --git a/opengate/tests/src/test082_kill_according_processes.py b/opengate/tests/src/test082_kill_according_processes.py index 8d28378c7..7bc4d45e0 100755 --- a/opengate/tests/src/test082_kill_according_processes.py +++ b/opengate/tests/src/test082_kill_according_processes.py @@ -9,7 +9,6 @@ import uproot - def test082_test(df): df = df[df["PDGCode"] == 22] nb_event = len(df["ParentID"]) From adefc8e084ba4dfd035138251692d4492df81d54 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Wed, 11 Dec 2024 19:14:19 +0100 Subject: [PATCH 67/82] Update of LV according to exitong and non interacting particles --- .../GateLastVertexInteractionSplittingActor.cpp | 7 ++++++- .../opengate_lib/GateLastVertexInteractionSplittingActor.h | 4 ++++ .../pyGateLastVertexInteractionSplittingActor.cpp | 7 ++++++- opengate/actors/miscactors.py | 5 +++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index 0220d8477..a187ae40e 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -360,6 +360,7 @@ void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex( fNbOfBatchForExitingParticle++; if (fNbOfBatchForExitingParticle > fNbOfMaxBatchPerEvent) { fStackManager->clear(); + fRemovedParticle ++; } // stackManager->clear(); } @@ -610,7 +611,10 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { (DoesParticleEmittedInSolidAngle( step->GetTrack()->GetMomentumDirection(), fVectorDirector) == true))) { - fListOfContainer.push_back((*fIterator)); + if ((*fIterator).GetContainerToSplit().GetProcessNameToSplit() != "None"){ + fListOfContainer.push_back((*fIterator)); + fNumberOfReplayedParticle ++; + } } step->GetTrack()->SetTrackStatus(fStopAndKill); @@ -736,6 +740,7 @@ void GateLastVertexInteractionSplittingActor::EndOfEventAction( } } + //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h index 21bd30e37..6cb600839 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h @@ -86,6 +86,8 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { std::vector<LastVertexDataContainer> fListOfContainer; G4StackManager *fStackManager = nullptr; G4int fNbOfMaxBatchPerEvent; + G4int fRemovedParticle = 0; + G4int fNumberOfReplayedParticle =0; G4Track *fTrackToSplit = nullptr; G4Step *fCopyInitStep = nullptr; @@ -138,6 +140,8 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { void print_tree(const tree<LastVertexDataContainer> &tr, tree<LastVertexDataContainer>::pre_order_iterator it, tree<LastVertexDataContainer>::pre_order_iterator end); + inline long GetNumberOfKilledParticles() { return fRemovedParticle;} + inline long GetNumberOfReplayedParticles() { return fNumberOfReplayedParticle;} }; #endif diff --git a/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp index 85f22a9ef..0a1ccddb6 100644 --- a/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp @@ -18,5 +18,10 @@ void init_GateLastVertexInteractionSplittingActor(py::module &m) { .def_readwrite( "fListOfVolumeAncestor", &GateLastVertexInteractionSplittingActor::fListOfVolumeAncestor) - .def(py::init<py::dict &>()); + .def(py::init<py::dict &>()) + .def("GetNumberOfKilledParticles", + &GateLastVertexInteractionSplittingActor::GetNumberOfKilledParticles) + .def("GetNumberOfReplayedParticles", + &GateLastVertexInteractionSplittingActor::GetNumberOfReplayedParticles); } + diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 78b580383..e90960e4a 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -662,6 +662,7 @@ def __initcpp__(self): "SteppingAction", "PostUserTrackingAction", "EndOfEventAction", + "EndSimulationAction", } ) @@ -680,6 +681,10 @@ def initialize(self): self.list_of_volume_name.append(volume_name) self.fListOfVolumeAncestor = self.list_of_volume_name + def EndSimulationAction(self): + print("Number of replayed particles: ", self.GetNumberOfReplayedParticles()) + print("Number of killed particle:", self.GetNumberOfKilledParticles()) + class BremSplittingActor(SplittingActorBase, g4.GateBOptrBremSplittingActor): """ From e8bbd58aa0453d50ecbad939304bda9cc780866f Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Thu, 12 Dec 2024 10:18:25 +0100 Subject: [PATCH 68/82] actor killing particle according its name --- core/opengate_core/opengate_core.cpp | 3 ++ .../GateKillAccordingParticleNameActor.cpp | 53 +++++++++++++++++++ .../GateKillAccordingParticleNameActor.h | 40 ++++++++++++++ .../pyGateKillAccordingParticleNameActor.cpp | 20 +++++++ opengate/actors/miscactors.py | 34 ++++++++++++ opengate/managers.py | 2 + 6 files changed, 152 insertions(+) create mode 100644 core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.cpp create mode 100644 core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.h create mode 100644 core/opengate_core/opengate_lib/pyGateKillAccordingParticleNameActor.cpp diff --git a/core/opengate_core/opengate_core.cpp b/core/opengate_core/opengate_core.cpp index 222fa0df7..7ad3b0cdf 100644 --- a/core/opengate_core/opengate_core.cpp +++ b/core/opengate_core/opengate_core.cpp @@ -318,6 +318,8 @@ void init_GateKillActor(py::module &); void init_GateKillAccordingProcessesActor(py::module &); +void init_GateKillAccordingParticleNameActor(py::module &); + void init_GateAttenuationImageActor(py::module &); void init_itk_image(py::module &); @@ -600,6 +602,7 @@ PYBIND11_MODULE(opengate_core, m) { init_GateARFTrainingDatasetActor(m); init_GateKillActor(m); init_GateKillAccordingProcessesActor(m); + init_GateKillAccordingParticleNameActor(m); init_GateAttenuationImageActor(m); init_GateDigiAttributeManager(m); init_GateVDigiAttribute(m); diff --git a/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.cpp b/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.cpp new file mode 100644 index 000000000..ec1d9d747 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.cpp @@ -0,0 +1,53 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + ------------------------------------ -------------- */ + +#include "GateKillAccordingParticleNameActor.h" +#include "G4LogicalVolumeStore.hh" +#include "G4PhysicalVolumeStore.hh" +#include "G4TransportationManager.hh" +#include "G4ios.hh" +#include "GateHelpers.h" +#include "GateHelpersDict.h" + +G4Mutex SetNbKillAcordingParticleMutex = G4MUTEX_INITIALIZER; + +GateKillAccordingParticleNameActor::GateKillAccordingParticleNameActor( + py::dict &user_info) + : GateVActor(user_info, false) {} + + + +void GateKillAccordingParticleNameActor::PreUserTrackingAction( + const G4Track *track) { + auto &l = fThreadLocalData.Get(); + l.fIsAParticleToKill = false; + + G4String particleName = track->GetParticleDefinition()->GetParticleName(); + if (std::find(fParticlesNameToKill.begin(), fParticlesNameToKill.end(),particleName) != fParticlesNameToKill.end()){ + l.fIsAParticleToKill = true; + } + +} + +void GateKillAccordingParticleNameActor::SteppingAction(G4Step *step) { + + if (step->GetPostStepPoint()->GetStepStatus() == 1) { + G4String logicalVolumeNamePostStep = step->GetPostStepPoint() + ->GetPhysicalVolume() + ->GetLogicalVolume() + ->GetName(); + if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(),logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { + auto &l = fThreadLocalData.Get(); + if (l.fIsAParticleToKill){ + step->GetTrack()->SetTrackStatus(fStopAndKill); + G4AutoLock mutex(&SetNbKillAcordingParticleMutex); + fNbOfKilledParticles++; + } + + } + } +} \ No newline at end of file diff --git a/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.h b/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.h new file mode 100644 index 000000000..70432dcea --- /dev/null +++ b/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.h @@ -0,0 +1,40 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#ifndef GateKillAccordingParticleNameActor_h +#define GateKillAccordingParticleNameActor_h + +#include "G4Cache.hh" +#include "GateVActor.h" +#include <pybind11/stl.h> + +namespace py = pybind11; + +class GateKillAccordingParticleNameActor : public GateVActor { + +public: + // Constructor + GateKillAccordingParticleNameActor(py::dict &user_info); + struct threadLocalT { + G4bool fIsAParticleToKill = false; + }; + G4Cache<threadLocalT> fThreadLocalData; + std::vector<G4String> fParticlesNameToKill; + std::vector<G4String> fListOfVolumeAncestor; + + + // Main function called every step in attached volume + void PreUserTrackingAction(const G4Track *) override; + void SteppingAction(G4Step *) override; + + inline long GetNumberOfKilledParticles() { return fNbOfKilledParticles; } + +private: + long fNbOfKilledParticles = 0; +}; + +#endif diff --git a/core/opengate_core/opengate_lib/pyGateKillAccordingParticleNameActor.cpp b/core/opengate_core/opengate_lib/pyGateKillAccordingParticleNameActor.cpp new file mode 100644 index 000000000..35ea457fb --- /dev/null +++ b/core/opengate_core/opengate_lib/pyGateKillAccordingParticleNameActor.cpp @@ -0,0 +1,20 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include <pybind11/pybind11.h> + +namespace py = pybind11; + +#include "GateKillAccordingParticleNameActor.h" + +void init_GateKillAccordingParticleNameActor(py::module &m) { + py::class_<GateKillAccordingParticleNameActor, std::unique_ptr<GateKillAccordingParticleNameActor, py::nodelete>, + GateVActor>(m, "GateKillAccordingParticleNameActor") + .def(py::init<py::dict &>()) + .def("GetNumberOfKilledParticles", + &GateKillAccordingParticleNameActor::GetNumberOfKilledParticles); +} diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 63f5c7165..fd741c1b2 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -7,6 +7,7 @@ from ..serialization import dump_json from ..exception import fatal, warning from ..base import process_cls +from .physics import translate_particle_name_gate_to_geant4 def _setter_hook_stats_actor_output_filename(self, output_filename): @@ -371,6 +372,39 @@ def __str__(self): s = self.user_output["kill_according_processes"].__str__() return s +class KillAccordingParticleNameActor(ActorBase, g4.GateKillAccordingParticleNameActor): + """Actor which kills a particle according the particle name provied by the user at the exit of the + actorified volume.""" + + particles_name_to_kill: list + + user_info_defaults = { + "particles_name_to_kill": ( + [], + { + "doc": "Put particles name the user wants to kill at the exit of the volume" + }, + ), + } + def __init__(self, *args, **kwargs): + ActorBase.__init__(self, *args, **kwargs) + self.number_of_killed_particles = 0 + self.__initcpp__() + + def __initcpp__(self): + g4.GateKillAccordingParticleNameActor.__init__(self, self.user_info) + self.AddActions( + {"PreUserTrackingAction", "SteppingAction","EndSimulationAction"} + ) + + def initialize(self): + ActorBase.initialize(self) + self.InitializeUserInfo(self.user_info) + self.InitializeCpp() + + def EndSimulationAction(self): + self.number_of_killed_particles = self.GetNumberOfKilledParticles() + class KillActor(ActorBase, g4.GateKillActor): """Actor which kills a particle entering a volume.""" diff --git a/opengate/managers.py b/opengate/managers.py index d64472af8..29f06c665 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -89,6 +89,7 @@ SimulationStatisticsActor, KillActor, KillAccordingProcessesActor, + KillAccordingParticleNameActor, SplittingActorBase, ComptSplittingActor, BremSplittingActor, @@ -126,6 +127,7 @@ "SimulationStatisticsActor": SimulationStatisticsActor, "KillActor": KillActor, "KillAccordingProcessesActor": KillAccordingProcessesActor, + "KillAccordingParticleNameActor": KillAccordingParticleNameActor, "BremSplittingActor": BremSplittingActor, "ComptSplittingActor": ComptSplittingActor, "DigitizerAdderActor": DigitizerAdderActor, From c350f9c847cd5a4df043e668fecfc4d05351556c Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Thu, 12 Dec 2024 10:25:19 +0100 Subject: [PATCH 69/82] update miscactors --- opengate/actors/miscactors.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index fd741c1b2..379c3515b 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -7,7 +7,8 @@ from ..serialization import dump_json from ..exception import fatal, warning from ..base import process_cls -from .physics import translate_particle_name_gate_to_geant4 +from anytree import RenderTree + def _setter_hook_stats_actor_output_filename(self, output_filename): @@ -386,6 +387,8 @@ class KillAccordingParticleNameActor(ActorBase, g4.GateKillAccordingParticleName }, ), } + + list_of_volume_name = [] def __init__(self, *args, **kwargs): ActorBase.__init__(self, *args, **kwargs) self.number_of_killed_particles = 0 @@ -401,6 +404,16 @@ def initialize(self): ActorBase.initialize(self) self.InitializeUserInfo(self.user_info) self.InitializeCpp() + volume_tree = self.simulation.volume_manager.get_volume_tree() + dico_of_volume_tree = {} + for pre, _, node in RenderTree(volume_tree): + dico_of_volume_tree[str(node.name)] = node + volume_name = self.user_info.attached_to + while volume_name != "world": + node = dico_of_volume_tree[volume_name] + volume_name = node.mother + self.list_of_volume_name.append(volume_name) + self.fListOfVolumeAncestor = self.list_of_volume_name def EndSimulationAction(self): self.number_of_killed_particles = self.GetNumberOfKilledParticles() From 590bbcd975cd15bddf97046c59074f964ddc9edd Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Thu, 12 Dec 2024 10:41:27 +0100 Subject: [PATCH 70/82] code update --- .../opengate_lib/GateKillAccordingParticleNameActor.cpp | 6 ++++++ .../opengate_lib/GateKillAccordingParticleNameActor.h | 3 ++- .../opengate_lib/pyGateKillAccordingParticleNameActor.cpp | 3 +++ opengate/actors/miscactors.py | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.cpp b/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.cpp index ec1d9d747..2c14bf6c7 100644 --- a/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.cpp +++ b/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.cpp @@ -20,6 +20,12 @@ GateKillAccordingParticleNameActor::GateKillAccordingParticleNameActor( : GateVActor(user_info, false) {} +void GateKillAccordingParticleNameActor::InitializeUserInfo(py::dict &user_info) { + GateVActor::InitializeUserInfo(user_info); + fParticlesNameToKill = DictGetVecStr(user_info, "particles_name_to_kill"); +} + + void GateKillAccordingParticleNameActor::PreUserTrackingAction( const G4Track *track) { diff --git a/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.h b/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.h index 70432dcea..86bf79470 100644 --- a/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.h +++ b/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.h @@ -23,13 +23,14 @@ class GateKillAccordingParticleNameActor : public GateVActor { G4bool fIsAParticleToKill = false; }; G4Cache<threadLocalT> fThreadLocalData; - std::vector<G4String> fParticlesNameToKill; + std::vector<std::string> fParticlesNameToKill; std::vector<G4String> fListOfVolumeAncestor; // Main function called every step in attached volume void PreUserTrackingAction(const G4Track *) override; void SteppingAction(G4Step *) override; + void InitializeUserInfo(py::dict &user_info) override; inline long GetNumberOfKilledParticles() { return fNbOfKilledParticles; } diff --git a/core/opengate_core/opengate_lib/pyGateKillAccordingParticleNameActor.cpp b/core/opengate_core/opengate_lib/pyGateKillAccordingParticleNameActor.cpp index 35ea457fb..57760973b 100644 --- a/core/opengate_core/opengate_lib/pyGateKillAccordingParticleNameActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateKillAccordingParticleNameActor.cpp @@ -15,6 +15,9 @@ void init_GateKillAccordingParticleNameActor(py::module &m) { py::class_<GateKillAccordingParticleNameActor, std::unique_ptr<GateKillAccordingParticleNameActor, py::nodelete>, GateVActor>(m, "GateKillAccordingParticleNameActor") .def(py::init<py::dict &>()) + .def_readwrite( + "fListOfVolumeAncestor", + &GateKillAccordingParticleNameActor::fListOfVolumeAncestor) .def("GetNumberOfKilledParticles", &GateKillAccordingParticleNameActor::GetNumberOfKilledParticles); } diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 379c3515b..ca63d738b 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -388,11 +388,11 @@ class KillAccordingParticleNameActor(ActorBase, g4.GateKillAccordingParticleName ), } - list_of_volume_name = [] def __init__(self, *args, **kwargs): ActorBase.__init__(self, *args, **kwargs) self.number_of_killed_particles = 0 self.__initcpp__() + self.list_of_volume_name = [] def __initcpp__(self): g4.GateKillAccordingParticleNameActor.__init__(self, self.user_info) From a8b4964e790a4723dc59d376fca1b1b0bc2009cf Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Thu, 12 Dec 2024 13:43:07 +0100 Subject: [PATCH 71/82] condition added to not try split fluo gamma --- .../opengate_lib/GateLastVertexInteractionSplittingActor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index a187ae40e..2d548f7d7 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -652,7 +652,8 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { // FIXME : list of process which are not splitable yet if ((fProcessNameToSplit != "msc") && (fProcessNameToSplit != "conv") && - (fProcessNameToSplit != "eIoni")) { + (fProcessNameToSplit != "eIoni")&& + (!((fProcessNameToSplit == "phot") || (step->GetTrack()->GetParticleDefinition()->GetParticleName() == "gamma" )))) { fCopyInitStep = new G4Step(*step); if (fProcessNameToSplit == "eBrem") { fCopyInitStep->SetStepLength( From 59d539ed33bf197792de5d5b2f3008f01a01038f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 12:44:15 +0000 Subject: [PATCH 72/82] [pre-commit.ci] Automatic python and c++ formatting --- .../GateKillAccordingParticleNameActor.cpp | 34 +++++++++---------- .../GateKillAccordingParticleNameActor.h | 3 +- ...ateLastVertexInteractionSplittingActor.cpp | 19 ++++++----- .../GateLastVertexInteractionSplittingActor.h | 8 +++-- .../pyGateKillAccordingParticleNameActor.cpp | 8 ++--- ...ateLastVertexInteractionSplittingActor.cpp | 4 +-- opengate/actors/miscactors.py | 4 +-- 7 files changed, 41 insertions(+), 39 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.cpp b/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.cpp index 2c14bf6c7..f4c295245 100644 --- a/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.cpp +++ b/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.cpp @@ -19,41 +19,39 @@ GateKillAccordingParticleNameActor::GateKillAccordingParticleNameActor( py::dict &user_info) : GateVActor(user_info, false) {} - -void GateKillAccordingParticleNameActor::InitializeUserInfo(py::dict &user_info) { +void GateKillAccordingParticleNameActor::InitializeUserInfo( + py::dict &user_info) { GateVActor::InitializeUserInfo(user_info); fParticlesNameToKill = DictGetVecStr(user_info, "particles_name_to_kill"); } - - void GateKillAccordingParticleNameActor::PreUserTrackingAction( const G4Track *track) { auto &l = fThreadLocalData.Get(); l.fIsAParticleToKill = false; G4String particleName = track->GetParticleDefinition()->GetParticleName(); - if (std::find(fParticlesNameToKill.begin(), fParticlesNameToKill.end(),particleName) != fParticlesNameToKill.end()){ + if (std::find(fParticlesNameToKill.begin(), fParticlesNameToKill.end(), + particleName) != fParticlesNameToKill.end()) { l.fIsAParticleToKill = true; } - } void GateKillAccordingParticleNameActor::SteppingAction(G4Step *step) { - + if (step->GetPostStepPoint()->GetStepStatus() == 1) { G4String logicalVolumeNamePostStep = step->GetPostStepPoint() - ->GetPhysicalVolume() - ->GetLogicalVolume() - ->GetName(); - if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(),logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { - auto &l = fThreadLocalData.Get(); - if (l.fIsAParticleToKill){ - step->GetTrack()->SetTrackStatus(fStopAndKill); - G4AutoLock mutex(&SetNbKillAcordingParticleMutex); - fNbOfKilledParticles++; - } - + ->GetPhysicalVolume() + ->GetLogicalVolume() + ->GetName(); + if (std::find(fListOfVolumeAncestor.begin(), fListOfVolumeAncestor.end(), + logicalVolumeNamePostStep) != fListOfVolumeAncestor.end()) { + auto &l = fThreadLocalData.Get(); + if (l.fIsAParticleToKill) { + step->GetTrack()->SetTrackStatus(fStopAndKill); + G4AutoLock mutex(&SetNbKillAcordingParticleMutex); + fNbOfKilledParticles++; } } + } } \ No newline at end of file diff --git a/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.h b/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.h index 86bf79470..4da5a246a 100644 --- a/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.h +++ b/core/opengate_core/opengate_lib/GateKillAccordingParticleNameActor.h @@ -19,14 +19,13 @@ class GateKillAccordingParticleNameActor : public GateVActor { public: // Constructor GateKillAccordingParticleNameActor(py::dict &user_info); - struct threadLocalT { + struct threadLocalT { G4bool fIsAParticleToKill = false; }; G4Cache<threadLocalT> fThreadLocalData; std::vector<std::string> fParticlesNameToKill; std::vector<G4String> fListOfVolumeAncestor; - // Main function called every step in attached volume void PreUserTrackingAction(const G4Track *) override; void SteppingAction(G4Step *) override; diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index 2d548f7d7..1efe71ea7 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -360,7 +360,7 @@ void GateLastVertexInteractionSplittingActor::CreateNewParticleAtTheLastVertex( fNbOfBatchForExitingParticle++; if (fNbOfBatchForExitingParticle > fNbOfMaxBatchPerEvent) { fStackManager->clear(); - fRemovedParticle ++; + fRemovedParticle++; } // stackManager->clear(); } @@ -611,10 +611,11 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { (DoesParticleEmittedInSolidAngle( step->GetTrack()->GetMomentumDirection(), fVectorDirector) == true))) { - if ((*fIterator).GetContainerToSplit().GetProcessNameToSplit() != "None"){ - fListOfContainer.push_back((*fIterator)); - fNumberOfReplayedParticle ++; - } + if ((*fIterator).GetContainerToSplit().GetProcessNameToSplit() != + "None") { + fListOfContainer.push_back((*fIterator)); + fNumberOfReplayedParticle++; + } } step->GetTrack()->SetTrackStatus(fStopAndKill); @@ -652,8 +653,11 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { // FIXME : list of process which are not splitable yet if ((fProcessNameToSplit != "msc") && (fProcessNameToSplit != "conv") && - (fProcessNameToSplit != "eIoni")&& - (!((fProcessNameToSplit == "phot") || (step->GetTrack()->GetParticleDefinition()->GetParticleName() == "gamma" )))) { + (fProcessNameToSplit != "eIoni") && + (!((fProcessNameToSplit == "phot") || + (step->GetTrack() + ->GetParticleDefinition() + ->GetParticleName() == "gamma")))) { fCopyInitStep = new G4Step(*step); if (fProcessNameToSplit == "eBrem") { fCopyInitStep->SetStepLength( @@ -741,7 +745,6 @@ void GateLastVertexInteractionSplittingActor::EndOfEventAction( } } - //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h index 6cb600839..d1a551dcb 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.h @@ -87,7 +87,7 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { G4StackManager *fStackManager = nullptr; G4int fNbOfMaxBatchPerEvent; G4int fRemovedParticle = 0; - G4int fNumberOfReplayedParticle =0; + G4int fNumberOfReplayedParticle = 0; G4Track *fTrackToSplit = nullptr; G4Step *fCopyInitStep = nullptr; @@ -140,8 +140,10 @@ class GateLastVertexInteractionSplittingActor : public GateVActor { void print_tree(const tree<LastVertexDataContainer> &tr, tree<LastVertexDataContainer>::pre_order_iterator it, tree<LastVertexDataContainer>::pre_order_iterator end); - inline long GetNumberOfKilledParticles() { return fRemovedParticle;} - inline long GetNumberOfReplayedParticles() { return fNumberOfReplayedParticle;} + inline long GetNumberOfKilledParticles() { return fRemovedParticle; } + inline long GetNumberOfReplayedParticles() { + return fNumberOfReplayedParticle; + } }; #endif diff --git a/core/opengate_core/opengate_lib/pyGateKillAccordingParticleNameActor.cpp b/core/opengate_core/opengate_lib/pyGateKillAccordingParticleNameActor.cpp index 57760973b..364261cec 100644 --- a/core/opengate_core/opengate_lib/pyGateKillAccordingParticleNameActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateKillAccordingParticleNameActor.cpp @@ -12,12 +12,12 @@ namespace py = pybind11; #include "GateKillAccordingParticleNameActor.h" void init_GateKillAccordingParticleNameActor(py::module &m) { - py::class_<GateKillAccordingParticleNameActor, std::unique_ptr<GateKillAccordingParticleNameActor, py::nodelete>, + py::class_<GateKillAccordingParticleNameActor, + std::unique_ptr<GateKillAccordingParticleNameActor, py::nodelete>, GateVActor>(m, "GateKillAccordingParticleNameActor") .def(py::init<py::dict &>()) - .def_readwrite( - "fListOfVolumeAncestor", - &GateKillAccordingParticleNameActor::fListOfVolumeAncestor) + .def_readwrite("fListOfVolumeAncestor", + &GateKillAccordingParticleNameActor::fListOfVolumeAncestor) .def("GetNumberOfKilledParticles", &GateKillAccordingParticleNameActor::GetNumberOfKilledParticles); } diff --git a/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp index 0a1ccddb6..9ac022e2a 100644 --- a/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/pyGateLastVertexInteractionSplittingActor.cpp @@ -22,6 +22,6 @@ void init_GateLastVertexInteractionSplittingActor(py::module &m) { .def("GetNumberOfKilledParticles", &GateLastVertexInteractionSplittingActor::GetNumberOfKilledParticles) .def("GetNumberOfReplayedParticles", - &GateLastVertexInteractionSplittingActor::GetNumberOfReplayedParticles); + &GateLastVertexInteractionSplittingActor:: + GetNumberOfReplayedParticles); } - diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 865a91a27..616574df1 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -12,7 +12,6 @@ from anytree import RenderTree - def _setter_hook_stats_actor_output_filename(self, output_filename): # By default, write_to_disk is False. # However, if user actively sets the output_filename @@ -371,6 +370,7 @@ def __str__(self): s = self.user_output["kill_according_processes"].__str__() return s + class KillAccordingParticleNameActor(ActorBase, g4.GateKillAccordingParticleNameActor): """Actor which kills a particle according the particle name provied by the user at the exit of the actorified volume.""" @@ -395,7 +395,7 @@ def __init__(self, *args, **kwargs): def __initcpp__(self): g4.GateKillAccordingParticleNameActor.__init__(self, self.user_info) self.AddActions( - {"PreUserTrackingAction", "SteppingAction","EndSimulationAction"} + {"PreUserTrackingAction", "SteppingAction", "EndSimulationAction"} ) def initialize(self): From 12e35a094f000f914daafd37e08a05285020c573 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Thu, 12 Dec 2024 14:57:34 +0100 Subject: [PATCH 73/82] correction on the phot exclusion mechanism --- ...ateLastVertexInteractionSplittingActor.cpp | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index 2d548f7d7..64d563bae 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -650,20 +650,23 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { (fIsAnnihilAlreadySplit == false))) { // FIXME : list of process which are not splitable yet - if ((fProcessNameToSplit != "msc") && - (fProcessNameToSplit != "conv") && - (fProcessNameToSplit != "eIoni")&& - (!((fProcessNameToSplit == "phot") || (step->GetTrack()->GetParticleDefinition()->GetParticleName() == "gamma" )))) { - fCopyInitStep = new G4Step(*step); - if (fProcessNameToSplit == "eBrem") { - fCopyInitStep->SetStepLength( - fContainer.GetContainerToSplit().GetStepLength()); - fCopyInitStep->GetPreStepPoint()->SetKineticEnergy( - fContainer.GetContainerToSplit().GetEnergy()); + + if ((fProcessNameToSplit != "msc") && + (fProcessNameToSplit != "conv") && + (fProcessNameToSplit != "eIoni")&& + (((fProcessNameToSplit != "phot") || ((fProcessNameToSplit == "phot")&& (step->GetTrack()->GetParticleDefinition()->GetParticleName() !="gamma")))) + ) { + fCopyInitStep = new G4Step(*step); + if (fProcessNameToSplit == "eBrem") { + fCopyInitStep->SetStepLength( + fContainer.GetContainerToSplit().GetStepLength()); + fCopyInitStep->GetPreStepPoint()->SetKineticEnergy( + fContainer.GetContainerToSplit().GetEnergy()); + } + CreateNewParticleAtTheLastVertex(fCopyInitStep, step, fContainer, + fBatchSize); } - CreateNewParticleAtTheLastVertex(fCopyInitStep, step, fContainer, - fBatchSize); - } + step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); if (fProcessNameToSplit == "annihil") { From 56b02503a11d99c642d8ed107e70ffab6b16f019 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 13:59:17 +0000 Subject: [PATCH 74/82] [pre-commit.ci] Automatic python and c++ formatting --- ...ateLastVertexInteractionSplittingActor.cpp | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index d996d96ea..a33e7c97d 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -651,22 +651,25 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { (fIsAnnihilAlreadySplit == false))) { // FIXME : list of process which are not splitable yet - - if ((fProcessNameToSplit != "msc") && - (fProcessNameToSplit != "conv") && - (fProcessNameToSplit != "eIoni")&& - (((fProcessNameToSplit != "phot") || ((fProcessNameToSplit == "phot")&& (step->GetTrack()->GetParticleDefinition()->GetParticleName() !="gamma")))) - ) { - fCopyInitStep = new G4Step(*step); - if (fProcessNameToSplit == "eBrem") { - fCopyInitStep->SetStepLength( - fContainer.GetContainerToSplit().GetStepLength()); - fCopyInitStep->GetPreStepPoint()->SetKineticEnergy( - fContainer.GetContainerToSplit().GetEnergy()); - } - CreateNewParticleAtTheLastVertex(fCopyInitStep, step, fContainer, - fBatchSize); + + if ((fProcessNameToSplit != "msc") && + (fProcessNameToSplit != "conv") && + (fProcessNameToSplit != "eIoni") && + (((fProcessNameToSplit != "phot") || + ((fProcessNameToSplit == "phot") && + (step->GetTrack() + ->GetParticleDefinition() + ->GetParticleName() != "gamma"))))) { + fCopyInitStep = new G4Step(*step); + if (fProcessNameToSplit == "eBrem") { + fCopyInitStep->SetStepLength( + fContainer.GetContainerToSplit().GetStepLength()); + fCopyInitStep->GetPreStepPoint()->SetKineticEnergy( + fContainer.GetContainerToSplit().GetEnergy()); } + CreateNewParticleAtTheLastVertex(fCopyInitStep, step, fContainer, + fBatchSize); + } step->GetTrack()->SetTrackStatus(fKillTrackAndSecondaries); From 9468792bf7d848e6025e97ee4215e191498a6177 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 17 Dec 2024 18:23:59 +0100 Subject: [PATCH 75/82] slight modif of batch method --- .../GateLastVertexInteractionSplittingActor.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index d996d96ea..0ce15ac34 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -705,10 +705,12 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { if (fIsFirstStep) { if (fSplitCounter <= fSplittingFactor) { if (fNumberOfTrackToSimulate == 0) { - CreateNewParticleAtTheLastVertex( - fCopyInitStep, step, fContainer, - (fSplittingFactor - fSplitCounter + 1) / fSplittingFactor * - fBatchSize); + //CreateNewParticleAtTheLastVertex( + // fCopyInitStep, step, fContainer, + //(fSplittingFactor - fSplitCounter + 1) / fSplittingFactor * + // fBatchSize); + CreateNewParticleAtTheLastVertex( + fCopyInitStep, step, fContainer,fBatchSize); } } } From 82ea3256b0d2e32e40e10962334f2b3fca8ef669 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 17 Dec 2024 17:24:49 +0000 Subject: [PATCH 76/82] [pre-commit.ci] Automatic python and c++ formatting --- .../GateLastVertexInteractionSplittingActor.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index 93f64eaae..b216736c3 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -708,12 +708,12 @@ void GateLastVertexInteractionSplittingActor::SteppingAction(G4Step *step) { if (fIsFirstStep) { if (fSplitCounter <= fSplittingFactor) { if (fNumberOfTrackToSimulate == 0) { - //CreateNewParticleAtTheLastVertex( - // fCopyInitStep, step, fContainer, - //(fSplittingFactor - fSplitCounter + 1) / fSplittingFactor * - // fBatchSize); - CreateNewParticleAtTheLastVertex( - fCopyInitStep, step, fContainer,fBatchSize); + // CreateNewParticleAtTheLastVertex( + // fCopyInitStep, step, fContainer, + //(fSplittingFactor - fSplitCounter + 1) / fSplittingFactor * + // fBatchSize); + CreateNewParticleAtTheLastVertex(fCopyInitStep, step, + fContainer, fBatchSize); } } } From 18fdac71f7f2af865a04f50fe00fcfdb6c3dc609 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Fri, 20 Dec 2024 17:39:11 +0100 Subject: [PATCH 77/82] created bug with G4 new version correction --- .../GateLastVertexInteractionSplittingActor.cpp | 5 ++++- .../opengate_lib/GateLastVertexSplittingDataContainer.h | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index b216736c3..6599b1720 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -134,6 +134,7 @@ G4Track *GateLastVertexInteractionSplittingActor::CreateATrackFromContainer( new G4DynamicParticle(particleDefinition, momentum, energy); G4double time = 0; G4Track *aTrack = new G4Track(dynamicParticle, time, position); + aTrack->SetTouchableHandle(container.GetTouchableHandle()); aTrack->SetPolarization(polarization); if (trackStatus == 0) { aTrack->SetTrackStatus(fAlive); @@ -147,6 +148,7 @@ G4Track *GateLastVertexInteractionSplittingActor::CreateATrackFromContainer( aTrack->SetWeight(container.GetWeight()); return aTrack; } + return nullptr; } @@ -468,13 +470,14 @@ void GateLastVertexInteractionSplittingActor::FillOfDataTree(G4Step *step) { G4int trackStatus = step->GetTrack()->GetTrackStatus(); G4int nbOfSecondaries = step->GetfSecondary()->size(); G4double stepLength = step->GetStepLength(); + G4TouchableHandle tch = step->GetTrack()->GetTouchableHandle(); if (((processName == "annihil"))) { energy -= (step->GetTotalEnergyDeposit()); } SimpleContainer containerToSplit = SimpleContainer(processName, energy, momentum, position, polarization, particleName, weight, trackStatus, nbOfSecondaries, - annihilFlag, stepLength, prePosition); + annihilFlag, stepLength, prePosition,tch); container->SetContainerToSplit(containerToSplit); container->PushListOfSplittingParameters(containerToSplit); } diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h index 92edcb5f0..4264766d8 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingDataContainer.h @@ -100,7 +100,8 @@ class LastVertexDataContainer { tmpContainer.GetNbOfSecondaries(), tmpContainer.GetAnnihilationFlag(), tmpContainer.GetStepLength(), - tmpContainer.GetPrePositionToSplit()); + tmpContainer.GetPrePositionToSplit(), + tmpContainer.GetTouchableHandle()); return aContainer; } } @@ -114,7 +115,8 @@ class LastVertexDataContainer { tmpContainer.GetParticleNameToSplit(), tmpContainer.GetWeight(), tmpContainer.GetTrackStatus(), tmpContainer.GetNbOfSecondaries(), tmpContainer.GetAnnihilationFlag(), tmpContainer.GetStepLength(), - tmpContainer.GetPrePositionToSplit()); + tmpContainer.GetPrePositionToSplit(), + tmpContainer.GetTouchableHandle()); return aContainer; } } From 399252ee8b8519ee0dd2f173dbfa656e0d7ee047 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:40:27 +0000 Subject: [PATCH 78/82] [pre-commit.ci] Automatic python and c++ formatting --- .../opengate_lib/GateLastVertexInteractionSplittingActor.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp index 6599b1720..26f0a8a3c 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp +++ b/core/opengate_core/opengate_lib/GateLastVertexInteractionSplittingActor.cpp @@ -148,7 +148,6 @@ G4Track *GateLastVertexInteractionSplittingActor::CreateATrackFromContainer( aTrack->SetWeight(container.GetWeight()); return aTrack; } - return nullptr; } @@ -477,7 +476,7 @@ void GateLastVertexInteractionSplittingActor::FillOfDataTree(G4Step *step) { SimpleContainer containerToSplit = SimpleContainer(processName, energy, momentum, position, polarization, particleName, weight, trackStatus, nbOfSecondaries, - annihilFlag, stepLength, prePosition,tch); + annihilFlag, stepLength, prePosition, tch); container->SetContainerToSplit(containerToSplit); container->PushListOfSplittingParameters(containerToSplit); } From 64d2fd448a933eb87d02feeaa39e175550de0971 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Mon, 6 Jan 2025 10:27:23 +0100 Subject: [PATCH 79/82] subsequent header modif of the container class --- .../GateLastVertexSplittingSimpleContainer.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h index f39087b70..ccdcf9aff 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h @@ -42,6 +42,7 @@ #include "G4VParticleChange.hh" #include "G4eeToTwoGammaModel.hh" #include "G4eplusAnnihilation.hh" +#include "G4TouchableHandle.hh" #include "G4eplusAnnihilationEntanglementClipBoard.hh" #include <iostream> @@ -52,7 +53,7 @@ class SimpleContainer { G4ThreeVector position, G4ThreeVector polarization, G4String name, G4double weight, G4int trackStatus, G4int nbSec, G4String flag, G4double length, - G4ThreeVector prePos) { + G4ThreeVector prePos,G4TouchableHandle aTouchable) { fProcessNameToSplit = processName; fEnergyToSplit = energy; @@ -66,6 +67,7 @@ class SimpleContainer { fAnnihilProcessFlag = flag; fStepLength = length; fPrePosition = prePos; + *fpTouchable = aTouchable; } SimpleContainer() {} @@ -126,6 +128,8 @@ class SimpleContainer { G4ThreeVector GetPrePositionToSplit() { return fPrePosition; } + G4TouchableHandle GetTouchableHandle() {return *fpTouchable;} + void DumpInfoToSplit() { std::cout << "Particle name of the particle to split: " << fParticleNameToSplit << std::endl; @@ -139,6 +143,8 @@ class SimpleContainer { std::cout << " " << std::endl; } +//G4TouchableHandle fpTouchable; + private: G4String fParticleNameToSplit = "None"; G4String fProcessNameToSplit = "None"; @@ -152,6 +158,9 @@ class SimpleContainer { G4String fAnnihilProcessFlag; G4double fStepLength; G4ThreeVector fPrePosition; + G4TouchableHandle* fpTouchable = new G4TouchableHandle; + + }; #endif From 878d1186dbe4aaecedfffd46546de7bf22c7dac7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 09:28:58 +0000 Subject: [PATCH 80/82] [pre-commit.ci] Automatic python and c++ formatting --- .../GateLastVertexSplittingSimpleContainer.h | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h index ccdcf9aff..8d866b342 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h @@ -36,13 +36,13 @@ #include "G4PhysicalConstants.hh" #include "G4PhysicsModelCatalog.hh" #include "G4Positron.hh" +#include "G4TouchableHandle.hh" #include "G4Track.hh" #include "G4VEmProcess.hh" #include "G4VEnergyLossProcess.hh" #include "G4VParticleChange.hh" #include "G4eeToTwoGammaModel.hh" #include "G4eplusAnnihilation.hh" -#include "G4TouchableHandle.hh" #include "G4eplusAnnihilationEntanglementClipBoard.hh" #include <iostream> @@ -53,7 +53,7 @@ class SimpleContainer { G4ThreeVector position, G4ThreeVector polarization, G4String name, G4double weight, G4int trackStatus, G4int nbSec, G4String flag, G4double length, - G4ThreeVector prePos,G4TouchableHandle aTouchable) { + G4ThreeVector prePos, G4TouchableHandle aTouchable) { fProcessNameToSplit = processName; fEnergyToSplit = energy; @@ -128,7 +128,7 @@ class SimpleContainer { G4ThreeVector GetPrePositionToSplit() { return fPrePosition; } - G4TouchableHandle GetTouchableHandle() {return *fpTouchable;} + G4TouchableHandle GetTouchableHandle() { return *fpTouchable; } void DumpInfoToSplit() { std::cout << "Particle name of the particle to split: " @@ -143,7 +143,7 @@ class SimpleContainer { std::cout << " " << std::endl; } -//G4TouchableHandle fpTouchable; + // G4TouchableHandle fpTouchable; private: G4String fParticleNameToSplit = "None"; @@ -158,9 +158,7 @@ class SimpleContainer { G4String fAnnihilProcessFlag; G4double fStepLength; G4ThreeVector fPrePosition; - G4TouchableHandle* fpTouchable = new G4TouchableHandle; - - + G4TouchableHandle *fpTouchable = new G4TouchableHandle; }; #endif From d54ea854db29d1a26358b0b941810141048849c8 Mon Sep 17 00:00:00 2001 From: majacquet <maxime.jacquet@creatis.insa-lyon.fr> Date: Tue, 7 Jan 2025 18:51:53 +0100 Subject: [PATCH 81/82] emory leak correction --- .../GateLastVertexSplittingSimpleContainer.h | 27 ++++++++++++++++--- opengate/contrib/linacs/elektaversa.py | 2 +- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h index ccdcf9aff..f51e510d8 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h @@ -43,8 +43,26 @@ #include "G4eeToTwoGammaModel.hh" #include "G4eplusAnnihilation.hh" #include "G4TouchableHandle.hh" +#include "G4ReferenceCountedHandle.hh" #include "G4eplusAnnihilationEntanglementClipBoard.hh" #include <iostream> +#include <memory> +#include "globals.hh" // Include from 'global' +#include <cmath> // Include from 'system' +#include "G4ThreeVector.hh" // Include from 'geometry' +#include "G4LogicalVolume.hh" // Include from 'geometry' +#include "G4VPhysicalVolume.hh" // Include from 'geometry' +#include "G4Allocator.hh" // Include from 'particle+matter' +#include "G4DynamicParticle.hh" // Include from 'particle+matter' +#include "G4TrackStatus.hh" // Include from 'tracking' +#include "G4TouchableHandle.hh" // Include from 'geometry' +#include "G4VUserTrackInformation.hh" + +#include "G4Material.hh" + +class G4Step; // Forward declaration +class G4MaterialCutsCouple; +class G4VelocityTable; class SimpleContainer { @@ -67,12 +85,13 @@ class SimpleContainer { fAnnihilProcessFlag = flag; fStepLength = length; fPrePosition = prePos; - *fpTouchable = aTouchable; + fpTouchable.push_back(aTouchable); } SimpleContainer() {} - ~SimpleContainer() {} + ~SimpleContainer() { + } void SetProcessNameToSplit(G4String processName) { fProcessNameToSplit = processName; @@ -128,7 +147,7 @@ class SimpleContainer { G4ThreeVector GetPrePositionToSplit() { return fPrePosition; } - G4TouchableHandle GetTouchableHandle() {return *fpTouchable;} + G4TouchableHandle GetTouchableHandle() {return fpTouchable[0];} void DumpInfoToSplit() { std::cout << "Particle name of the particle to split: " @@ -158,7 +177,7 @@ class SimpleContainer { G4String fAnnihilProcessFlag; G4double fStepLength; G4ThreeVector fPrePosition; - G4TouchableHandle* fpTouchable = new G4TouchableHandle; + std::vector<G4TouchableHandle> fpTouchable; }; diff --git a/opengate/contrib/linacs/elektaversa.py b/opengate/contrib/linacs/elektaversa.py index 12ec244d4..f9066c3a5 100644 --- a/opengate/contrib/linacs/elektaversa.py +++ b/opengate/contrib/linacs/elektaversa.py @@ -39,7 +39,7 @@ def add_empty_linac_box(sim, linac_name, sad=1000): linac.material = "G4_AIR" linac.size = [1 * m, 1 * m, 0.52 * m] translation_linac_box = np.array([0 * mm, 0, sad - linac.size[2] / 2 + 3.5 * mm]) - # Isocenter begin at the end of the target, That's why 3.5 mm is added. + # Isocenter start from the end of the target, That's why 3.5 mm is added. linac.translation = translation_linac_box linac.color = [1, 1, 1, 0] return linac From c9401ed433ed297540ff9ce1941481725caee933 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Jan 2025 18:06:53 +0000 Subject: [PATCH 82/82] [pre-commit.ci] Automatic python and c++ formatting --- .../GateLastVertexSplittingSimpleContainer.h | 9 +++------ opengate/actors/miscactors.py | 1 + 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h b/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h index 8ebf67848..3980916de 100644 --- a/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h +++ b/core/opengate_core/opengate_lib/GateLastVertexSplittingSimpleContainer.h @@ -36,8 +36,8 @@ #include "G4PhysicalConstants.hh" #include "G4PhysicsModelCatalog.hh" #include "G4Positron.hh" -#include "G4TouchableHandle.hh" #include "G4ReferenceCountedHandle.hh" +#include "G4TouchableHandle.hh" #include "G4Track.hh" #include "G4VEmProcess.hh" #include "G4VEnergyLossProcess.hh" @@ -71,8 +71,7 @@ class SimpleContainer { SimpleContainer() {} - ~SimpleContainer() { - } + ~SimpleContainer() {} void SetProcessNameToSplit(G4String processName) { fProcessNameToSplit = processName; @@ -128,7 +127,7 @@ class SimpleContainer { G4ThreeVector GetPrePositionToSplit() { return fPrePosition; } - G4TouchableHandle GetTouchableHandle() {return fpTouchable[0];} + G4TouchableHandle GetTouchableHandle() { return fpTouchable[0]; } void DumpInfoToSplit() { std::cout << "Particle name of the particle to split: " @@ -159,8 +158,6 @@ class SimpleContainer { G4double fStepLength; G4ThreeVector fPrePosition; std::vector<G4TouchableHandle> fpTouchable; - - }; #endif diff --git a/opengate/actors/miscactors.py b/opengate/actors/miscactors.py index 9941e2d58..e2fe2c16e 100644 --- a/opengate/actors/miscactors.py +++ b/opengate/actors/miscactors.py @@ -438,6 +438,7 @@ def initialize(self): def EndSimulationAction(self): self.number_of_killed_particles = self.GetNumberOfKilledParticles() + class ActorOutputKillNonInteractingParticleActor(ActorOutputBase): def __init__(self, *args, **kwargs):