@@ -43,6 +43,7 @@ std::mutex PrimaryGeneratorAction::fPrimaryGenerationMutex;
4343TF1* PrimaryGeneratorAction::fEnergyDistributionFunction = nullptr ;
4444TF1* PrimaryGeneratorAction::fAngularDistributionFunction = nullptr ;
4545TF2* PrimaryGeneratorAction::fEnergyAndAngularDistributionFunction = nullptr ;
46+ TH2D* energyAndAngularDistributionHistogram = nullptr ;
4647
4748PrimaryGeneratorAction::PrimaryGeneratorAction (SimulationManager* simulationManager)
4849 : G4VUserPrimaryGeneratorAction(), fSimulationManager(simulationManager) {
@@ -230,6 +231,30 @@ void PrimaryGeneratorAction::SetGeneratorSpatialDensity(TString str) {
230231 fGeneratorSpatialDensityFunction = new TF3 (" GeneratorDistFunc" , str);
231232}
232233
234+ TVector2 PointOnUnitDisk () {
235+ double r = TMath::Sqrt (G4UniformRand ());
236+ double theta = G4UniformRand () * 2 * TMath::Pi ();
237+ return {r * TMath::Cos (theta), r * TMath::Sin (theta)};
238+ }
239+
240+ pair<bool , TVector3> IntersectionLineSphere (const TVector3& lineOrigin, const TVector3& lineDirection) {
241+ // sphere origin is always (0,0)
242+ // return the first intersection point
243+ // https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection
244+ const double a = lineDirection.Dot (lineDirection);
245+ const double b = 2 * lineDirection.Dot (lineOrigin);
246+ const double c = lineOrigin.Dot (lineOrigin) - 1 ;
247+
248+ const double discriminant = b * b - 4 * a * c;
249+ if (discriminant < 0 ) {
250+ return {false , {0 , 0 , 0 }};
251+ }
252+ const double t1 = (-b + sqrt (discriminant)) / (2 * a);
253+ const double t2 = (-b - sqrt (discriminant)) / (2 * a);
254+ const double t = min (t1, t2);
255+ return {true , lineOrigin + t * lineDirection};
256+ }
257+
233258void PrimaryGeneratorAction::GeneratePrimaries (G4Event* event) {
234259 lock_guard<mutex> lock (fPrimaryGenerationMutex );
235260 auto simulationManager = fSimulationManager ;
@@ -261,29 +286,61 @@ void PrimaryGeneratorAction::GeneratePrimaries(G4Event* event) {
261286 G4ParticleTable::GetParticleTable ()->FindParticle (particle.GetParticleName ().Data ()));
262287
263288 if (fCosmicCircumscribedSphereRadius == 0 .) {
264- // radius in mm
265289 fCosmicCircumscribedSphereRadius = fSimulationManager ->GetRestMetadata ()
266290 ->GetGeant4PrimaryGeneratorInfo ()
267- .GetSpatialGeneratorCosmicRadius ();
291+ .GetSpatialGeneratorCosmicRadius (); // radius in mm
292+ }
293+ const G4ThreeVector referenceDirection = {0 , -1 , 0 };
294+ const auto & direction = particle.GetMomentumDirection ();
295+ const double zenith = TMath::ACos (
296+ direction.Dot ({referenceDirection.x (), referenceDirection.y (), referenceDirection.z ()}));
297+
298+ const TVector2 positionOnDisk = PointOnUnitDisk ();
299+ const TVector2 positionOnEllipse = {positionOnDisk.X () / TMath::Cos (zenith) + TMath::Tan (zenith),
300+ positionOnDisk.Y ()};
301+
302+ double phi = TVector2 (direction.X (), direction.Z ()).Phi ();
303+ const TVector2 positionOnEllipseRotated = {
304+ positionOnEllipse.X () * TMath::Cos (phi) - positionOnEllipse.Y () * TMath::Sin (phi),
305+ positionOnEllipse.X () * TMath::Sin (phi) + positionOnEllipse.Y () * TMath::Cos (phi),
306+ };
307+ const TVector3 positionOrigin = {
308+ // this may be wrong, or how phi is derived from direction
309+ -1 * positionOnEllipseRotated.X (),
310+ 1.0 ,
311+ -1 * positionOnEllipseRotated.Y (),
312+ };
313+ const auto [intersectionFlag, intersection] = IntersectionLineSphere (positionOrigin, direction);
314+ if (!intersectionFlag) {
315+ cerr << " PrimaryGeneratorAction: cosmic generator intersection not found (this should never "
316+ " happen)"
317+ << endl;
318+ exit (1 );
268319 }
269320
270- const auto position = ComputeCosmicPosition (fParticleGun .GetParticleMomentumDirection (),
271- fCosmicCircumscribedSphereRadius );
272-
273- fParticleGun .SetParticlePosition (position);
274321 fParticleGun .SetParticleEnergy (particle.GetEnergy () * keV);
275- fParticleGun .SetParticleMomentumDirection ({particle.GetMomentumDirection ().X (),
276- particle.GetMomentumDirection ().Y (),
277- particle.GetMomentumDirection ().Z ()});
322+
323+ G4ThreeVector particlePosition = {
324+ intersection.X () * fCosmicCircumscribedSphereRadius ,
325+ intersection.Y () * fCosmicCircumscribedSphereRadius ,
326+ intersection.Z () * fCosmicCircumscribedSphereRadius ,
327+ };
328+ G4ThreeVector sourceDirection = {source->GetDirection ().X (), source->GetDirection ().Y (),
329+ source->GetDirection ().Z ()};
330+ G4ThreeVector particleDirection = {direction.X (), direction.Y (), direction.Z ()};
331+
332+ const double angleBetweenDirections = sourceDirection.angle (referenceDirection);
333+ if (angleBetweenDirections > 0 ) {
334+ const G4ThreeVector cross = referenceDirection.cross (sourceDirection);
335+ particleDirection.rotate (angleBetweenDirections, cross);
336+ particlePosition.rotate (angleBetweenDirections, cross);
337+ }
338+
339+ fParticleGun .SetParticleMomentumDirection (particleDirection);
340+ fParticleGun .SetParticlePosition (particlePosition);
341+
278342 fParticleGun .GeneratePrimaryVertex (event);
279343
280- /*
281- cout << "PrimaryGeneratorAction - INFO: cosmic particle generated: "
282- << fParticleGun.GetParticleDefinition()->GetParticleName()
283- << " energy: " << fParticleGun.GetParticleEnergy() / keV << " keV"
284- << " position: " << fParticleGun.GetParticlePosition() / mm << " mm"
285- << " direction: " << fParticleGun.GetParticleMomentumDirection() << endl;
286- */
287344 return ;
288345 }
289346
@@ -317,21 +374,17 @@ void PrimaryGeneratorAction::GeneratePrimaries(G4Event* event) {
317374
318375 for (int i = 0 ; i < restG4Metadata->GetNumberOfSources (); i++) {
319376 vector<TRestGeant4Particle> particles = restG4Metadata->GetParticleSource (i)->GetParticles ();
320- // std::cout << "Source : " << i << std::endl;
321377 for (const auto & p : particles) {
322- // ParticleDefinition should be always declared first (after position).
323378 SetParticleDefinition (i, p);
324379 SetParticleEnergyAndDirection (i, p);
325380
326- // p.Print();
327-
328381 if (spatialGeneratorTypeEnum == SpatialGeneratorTypes::COSMIC) {
329382 const auto position = ComputeCosmicPosition (fParticleGun .GetParticleMomentumDirection (),
330383 fCosmicCircumscribedSphereRadius );
331384 fParticleGun .SetParticlePosition (position);
332385 }
333386
334- if (spatialGeneratorTypeEnum == SpatialGeneratorTypes::SOURCE) {
387+ else if (spatialGeneratorTypeEnum == SpatialGeneratorTypes::SOURCE) {
335388 G4ThreeVector position = {p.GetOrigin ().X (), p.GetOrigin ().Y (), p.GetOrigin ().Z ()};
336389 fParticleGun .SetParticlePosition (position);
337390 }
@@ -822,37 +875,44 @@ void PrimaryGeneratorAction::SetParticleEnergyAndDirection(Int_t particleSourceI
822875 restG4Metadata->GetParticleSource (particleSourceIndex)->GetEnergyDistributionType ().Data ();
823876 const auto energyDistTypeEnum = StringToEnergyDistributionTypes (energyDistTypeName);
824877
825- if (energyDistTypeEnum != EnergyDistributionTypes::FORMULA2 &&
826- angularDistTypeEnum != AngularDistributionTypes::FORMULA2) {
827- // when not using 'FORMULA2'
878+ if ((energyDistTypeEnum != EnergyDistributionTypes::FORMULA2 &&
879+ angularDistTypeEnum != AngularDistributionTypes::FORMULA2) &&
880+ (energyDistTypeEnum != EnergyDistributionTypes::TH2D &&
881+ angularDistTypeEnum != AngularDistributionTypes::TH2D)) {
828882 SetParticleEnergy (particleSourceIndex, particle);
829883 SetParticleDirection (particleSourceIndex, particle);
830884 return ;
831885 }
832886
833- // this function should only be invoked when both energy and angular dist are "formula2"
834- if (energyDistTypeEnum != EnergyDistributionTypes::FORMULA2 ||
835- angularDistTypeEnum != AngularDistributionTypes::FORMULA2) {
836- cout << " PrimaryGeneratorAction::SetParticleEnergyAndDirection - this method should only invoked "
837- " when both angular and energy dist are 'formula2'"
887+ double energy, angle;
888+ if (energyDistTypeEnum == EnergyDistributionTypes::FORMULA2 &&
889+ angularDistTypeEnum == AngularDistributionTypes::FORMULA2) {
890+ {
891+ lock_guard<mutex> lock (fDistributionFormulaMutex );
892+ fEnergyAndAngularDistributionFunction ->GetRandom2 (energy, angle, fRandom );
893+ energy *= keV;
894+ }
895+ } else if (energyDistTypeEnum == EnergyDistributionTypes::TH2D &&
896+ angularDistTypeEnum == AngularDistributionTypes::TH2D) {
897+ if (energyAndAngularDistributionHistogram == nullptr ) {
898+ const auto filename = source->GetEnergyDistributionFilename ();
899+ const auto name = source->GetEnergyDistributionNameInFile ();
900+ cout << " Loading energy and angular distribution from file " << filename << " with name " << name
901+ << endl;
902+ TFile* file = TFile::Open (filename);
903+ energyAndAngularDistributionHistogram =
904+ file->Get <TH2D>(name); // it's the same for both angular and energy
905+ }
906+ energyAndAngularDistributionHistogram->GetRandom2 (energy, angle);
907+ energy *= MeV; // energy in these histograms is in MeV. TODO: parse energy from axis label
908+ angle *= TMath::DegToRad ();
909+ } else {
910+ cout << " Energy/Angular distribution type 'formula2' or 'TH2D' should be used on both energy and "
911+ " angular"
838912 << endl;
839913 exit (1 );
840914 }
841915
842- if (fEnergyAndAngularDistributionFunction == nullptr ) {
843- RESTError << " PrimaryGeneratorAction::SetParticleEnergyAndDirection - energy and angular "
844- " distribution function is not set"
845- << RESTendl;
846- exit (1 );
847- }
848-
849- double energy, angle;
850- {
851- lock_guard<mutex> lock (fDistributionFormulaMutex );
852- fEnergyAndAngularDistributionFunction ->GetRandom2 (energy, angle, fRandom );
853- }
854- energy *= keV;
855-
856916 G4ThreeVector direction = {source->GetDirection ().X (), source->GetDirection ().Y (),
857917 source->GetDirection ().Z ()};
858918
0 commit comments