Belle II Software  release-08-01-10
SensitiveDetectorBase.cc
1 /**************************************************************************
2  * basf2 (Belle II Analysis Software Framework) *
3  * Author: The Belle II Collaboration *
4  * *
5  * See git log for contributors and copyright holders. *
6  * This file is licensed under LGPL-3.0, see LICENSE.md. *
7  **************************************************************************/
8 
9 #include <simulation/kernel/UserInfo.h>
10 #include <vxd/simulation/SensitiveDetectorBase.h>
11 #include <vxd/dataobjects/VXDElectronDeposit.h>
12 #include <framework/gearbox/Const.h>
13 #include <framework/gearbox/Unit.h>
14 
15 #ifdef VXD_SENSITIVEDETECTOR_DEBUG
16 #include <vxd/simulation/SensitiveDetectorDebugHelper.h>
17 #endif
18 
19 #include <G4Step.hh>
20 
21 namespace Belle2 {
26  namespace VXD {
27 
28  bool SensitiveDetectorBase::step(G4Step* step, G4TouchableHistory*)
29  {
30  // Get track
31  const G4Track& track = *step->GetTrack();
32  // Get particle PDG code
33  const int pdgCode = track.GetDefinition()->GetPDGEncoding();
34  // Get particle charge (only keep charged tracks, photons and magnetic monopoles)
35  const bool isNeutral = track.GetDefinition()->GetPDGCharge() == 0;
36  const bool isAllowedNeutral = (pdgCode == Const::photon.getPDGCode()) || (m_seeNeutrons
37  && (abs(pdgCode) == Const::neutron.getPDGCode())) || (abs(pdgCode) == 99666);
38  //Not interested in neutral particles except photons, magnetic monopoles and maybe neutrons
39  if (isNeutral && !isAllowedNeutral) return false;
40 
41  // Get track ID
42  const int trackID = track.GetTrackID();
43  //Get deposited energy
44  const double electrons = step->GetTotalEnergyDeposit() * Unit::MeV / Const::ehEnergy;
45  // Get step information
46  const G4StepPoint& postStep = *step->GetPostStepPoint();
47  const G4StepPoint& preStep = *step->GetPreStepPoint();
48  const G4AffineTransform& topTransform = preStep.GetTouchableHandle()->GetHistory()->GetTopTransform();
49  const G4ThreeVector postStepPos = topTransform.TransformPoint(postStep.GetPosition()) * Unit::mm;
50  const G4ThreeVector postStepMom = topTransform.TransformAxis(postStep.GetMomentum()) * Unit::MeV;
51 
52  //Get the current track info
53  //if none present or not matching current trackID create one first
54  //if trackID is 0 we can reuse the existing top
55  if (m_tracks.empty() || (m_tracks.top().getTrackID() > 0 && m_tracks.top().getTrackID() != trackID)) {
56  m_tracks.push(SensorTraversal());
57  }
58  SensorTraversal& traversal = m_tracks.top();
59  if (traversal.getTrackID() == 0) {
60  bool isPrimary = Simulation::TrackInfo::getInfo(track).hasStatus(MCParticle::c_PrimaryParticle);
61  //If new track, remember values
62  traversal.setInitial(trackID, pdgCode, isPrimary);
63  //Remember if the track came from the outside
64  if (preStep.GetStepStatus() == fGeomBoundary) traversal.hasEntered();
65  //Add start position
66  const G4ThreeVector preStepPos = topTransform.TransformPoint(preStep.GetPosition()) * Unit::mm;
67  const G4ThreeVector preStepMom = topTransform.TransformAxis(preStep.GetMomentum()) * Unit::MeV;
68  traversal.add(preStepPos, preStepMom, 0, preStep.GetGlobalTime() * Unit::ns, 0);
69  }
70  //Add new track
71  traversal.add(postStepPos, postStepMom, electrons,
72  postStep.GetGlobalTime() * Unit::ns,
73  step->GetStepLength() * Unit::mm);
74  //check if we are leaving the volume
75  bool isLeaving = (postStep.GetStepStatus() == fGeomBoundary);
76  //remember that in the track info
77  if (isLeaving) traversal.hasLeft();
78  //If this step is to the boundary or track gets killed, save hits and
79  //return whether or not we saved something
80  if (isLeaving || track.GetTrackStatus() >= fStopAndKill) {
81  bool saved = finishTrack();
82  //If we saved at least one simhit and the track is not contained inside
83  //the sensor volume: keep MCParticle
84  if (saved && !traversal.isContained()) {
85  Simulation::TrackInfo::getInfo(track).setIgnore(false);
86  }
87  return saved;
88  }
89  //Track not finished so we do not save anything, let's return false for
90  //now
91  return false;
92  }
93 
95  {
96  SensorTraversal& traversal = m_tracks.top();
97 #ifdef VXD_SENSITIVEDETECTOR_DEBUG
99  debug.startTraversal(getSensorID(), traversal);
100 #endif
101  bool save = traversal.getElectrons() >= m_minimumElectrons || (m_seeNeutrons
102  && (abs(traversal.getPDGCode()) == Const::neutron.getPDGCode()));
103  if (save) {
104  int trueHitIndex = -1;
105  if (!m_onlyPrimaryTrueHits || traversal.isPrimary()) {
106  trueHitIndex = saveTrueHit(traversal);
107  }
108  std::vector<std::pair<unsigned int, float>> simhits = createSimHits();
109  saveRelations(traversal, trueHitIndex, simhits);
110  }
111 #ifdef VXD_SENSITIVEDETECTOR_DEBUG
112  debug.finishTraversal();
113 #endif
114  //No we just need to take care of the stack of traversals
115  if (m_tracks.size() == 1) {
116  //reuse traversal to keep memory if this is the first one
117  traversal.reset();
118  } else {
119  //this should only happen if the parent track got suspended. As this
120  //rarely happens in PXD we do not care for re-usability here
121  m_tracks.pop();
122  }
123  return save;
124  }
125 
126  std::vector<std::pair<unsigned int, float>> SensitiveDetectorBase::createSimHits()
127  {
128  SensorTraversal& traversal = m_tracks.top();
129  //List of created simhit indices to be able to create relations
130  std::vector<std::pair<unsigned int, float>> simhits;
131 
132  //We need to check how close the steps would be to a linear approximation
133  //of the traversal and split the track if they are too far away. To do
134  //this we check the whole track and split it at the point with the
135  //largest distance recursively until the largest distance is inside the
136  //tolerance. For that we need a stack of segments and check each segment
137  //in turn until they fulfill the criteria.
138  static std::stack<SensorTraversal::range> stack;
139  //Lets push the full segment, first step point to last step point, on the
140  //stack. We use inclusive range, so the second iterator is still included
141  stack.push(make_pair(traversal.begin(), traversal.end() - 1));
142  //Iterators needed for checking
143  SensorTraversal::iterator firstPoint, finalPoint, splitPoint;
144 
145  //Check all segments ...
146  while (!stack.empty()) {
147  //Get first and last point
148  std::tie(firstPoint, finalPoint) = stack.top();
149  //Remove segment from stack
150  stack.pop();
151  //Direction of the segment
152  const G4ThreeVector n = (finalPoint->position - firstPoint->position).unit();
153  //find largest distance to segment by looping over all intermediate points
154  double maxDistance(0);
155  for (auto nextPoint = firstPoint + 1; nextPoint != finalPoint; ++nextPoint) {
156  //Calculate distances between point p and line segment,
157  //x = a + t*n, where a is a point on the line and n is the unit vector
158  //pointing in line direction. distance = ||(p-a) - ((p-a)*n)*n||
159  const G4ThreeVector pa = nextPoint->position - firstPoint->position;
160  const double dist = (pa - (pa * n) * n).mag();
161  //Update splitpoint if distance is larger then previously known
162  if (dist > maxDistance) {
163  splitPoint = nextPoint;
164  maxDistance = dist;
165  }
166  }
167  //If we are above the tolerance we split the track
168  if (maxDistance > m_distanceTolerance) {
169  //If we split in this order, all created simhits will be in correct
170  //order. That is not a requirement but a nice side effect.
171  stack.push(make_pair(splitPoint, finalPoint));
172  stack.push(make_pair(firstPoint, splitPoint));
173  continue;
174  }
175  //Otherwise we create a SimHit
176  //FIXME: check for m_minimumElectrons?
177  int simHitIndex = saveSimHit(traversal, std::make_pair(firstPoint, finalPoint));
178  simhits.push_back(std::make_pair(simHitIndex, finalPoint->electrons - firstPoint->electrons));
179  }
180  return simhits;
181  }
182 
184  {
185  //At the end we want a list of points indication how many electrons where
186  //deposited so far along the track, e.g. [(0.5,100), (1.0, 2000)] would
187  //indicate that after half the step length 100 electrons were deposited
188  //and at the end of the step we have 2000 electrons. To save some memory
189  //we encode this information as unsigned int using ElectronDeposit later
190  //on.
191  std::vector<unsigned int> electronProfile;
192 
193  //We need an iterator to the first and last point in a segment
194  SensorTraversal::iterator firstPoint, finalPoint;
195  //for now let's extract the full segment
196  std::tie(firstPoint, finalPoint) = points;
197  //If this is not the first SimHit for a sensor traversal we need to
198  //subtract the electrons already taken care of
199  const double electronsOffset = (firstPoint->electrons);
200  //We also need the length of the step
201  const double length = finalPoint->length - firstPoint->length;
202  //And the start length
203  const double lengthOffset = firstPoint->length;
204 
205  //We need to keep track of sub segments and where they should insert a
206  //midpoint if required. So we need a tuple of three iterators: insert
207  //position, first point and last point of the segment. We store those
208  //in a stack which we declare static to avoid some relocations if
209  //possible.
210  static std::stack <SensorTraversal::range> stack;
211  //And we push the full segment on the stack
212  stack.push(points);
213 
214  //Now we check all segments we encounter until none exceed the maximum
215  //tolerance anymore
216  while (!stack.empty()) {
217  //Get next segment to be checked
218  std::tie(firstPoint, finalPoint) = stack.top();
219  //And remove it from the stack
220  stack.pop();
221 
222  //Some variables we need
223  const double startElectrons = firstPoint->electrons;
224  const double startLength = firstPoint->length;
225  const double segmentLength = finalPoint->length - startLength;
226  const double segmentElectrons = finalPoint->electrons - startElectrons;
227  //We want to give the tolerance in electrons so we need to convert the
228  //step length into electrons. We do this by using the average number of
229  //electrons created per micrometer for a minimum ionizing particle.
230  const double lengthScale = 1. / Unit::um * 80;
231  //we need the slope for the linear approximation:
232  //electrons= slope*(length-startLength)*lengthScale+startElectrons;
233  const double slope = segmentElectrons / segmentLength / lengthScale;
234  //Distance between point x0,y0 and line a*x+b*y+c=0 is given as
235  //abs(a*x0+b*y0+c)/sqrt(a^2+b^2). In our case:
236  //x0=(length-startLength)*lengthScale, y0=electrons
237  //a=slope, b=-1, c=startElectrons.
238  //Nominator is independent of the actual point so we calculate it now
239  const double distanceConstant = std::sqrt(slope * slope + 1);
240 
241  //Variable to remember maximum distance from linear approximation
242  double maxDistance(0);
243  //and also the point with the largest distance
244  SensorTraversal::iterator splitPoint;
245 
246  //No go through all intermediate points
247  for (auto nextPoint = firstPoint + 1; nextPoint != finalPoint; ++nextPoint) {
248  //and check their distance from the linear approximation
249  const double x = (nextPoint->length - startLength) * lengthScale;
250  const double dist = fabs(x * slope - nextPoint->electrons + startElectrons) / distanceConstant;
251  //and remember if it is the largest distance
252  if (dist > maxDistance) {
253  splitPoint = nextPoint;
254  maxDistance = dist;
255  }
256  }
257  //if the largest distance is above the tolerance we split the segment
258  if (maxDistance > m_electronTolerance) {
259  //And add the two sub segments to the stack in the correcto order to
260  //ensure sorted processing: We always add the segments at the front
261  //last so they will be processed first
262  stack.push(make_pair(splitPoint, finalPoint));
263  stack.push(make_pair(firstPoint, splitPoint));
264  continue;
265  }
266  //otherwise we add the endpoint of the current segment to the list of points.
267  const double fraction = (finalPoint->length - lengthOffset) / length;
268  const double electrons = (finalPoint->electrons - electronsOffset);
269  electronProfile.push_back(VXDElectronDeposit(fraction, electrons));
270  }
271  return electronProfile;
272  }
273 
275  {
276  //We want the middle of the track so we need to find the two steps
277  //which enclose this
278  const double midLength = traversal.getLength() * 0.5;
279  auto after = traversal.begin();
280  while (after->length < midLength) ++after;
281  //Now we have an iterator after the half length. We know that the first
282  //step contains length 0 so we can savely subtract one to get the one
283  //before
284  auto before = after - 1;
285  //the midpoint is in between so calculate the fractions from both sides
286  const double fl = (after->length - midLength) / (after->length - before->length);
287  const double fr = (1 - fl);
288  //we calculate the time and electrons using weighted average
289  const double midTime = fl * before->time + fr * after->time;
290  const double midElectrons = fl * before->electrons + fr * after->electrons;
291  //now we use 3rd order bezier curve to approximate mid position and momentum.
292  //The two base points are easy ...
293  const G4ThreeVector& p0 = before->position;
294  const G4ThreeVector& p3 = after->position;
295  //And the two control points are the base points +- the appropriately
296  //scaled momenta
297  const double momentumScale = (p3 - p0).mag() / before->momentum.mag() / 3;
298  const G4ThreeVector p1 = p0 + momentumScale * before->momentum;
299  const G4ThreeVector p2 = p3 - momentumScale * after->momentum;
300  // The curve is B(t) = (1-t)^3*P0 + 3*(1-t)^2*t*P1 + 3*(1-t)*t^2*P2 + t^3*P3
301  const G4ThreeVector midPos = (
302  fl * fl * fl * p0
303  + 3 * fl * fl * fr * p1
304  + 3 * fl * fr * fr * p2
305  + fr * fr * fr * p3
306  );
307  // The derivative is dB(t)/dt = 3*[(1-t)^2*(P1-P0)+2*(1-t)*t*(P2-P1)+t^2*(P3-P2)]
308  const G4ThreeVector midMom = 1.0 / momentumScale * (
309  fl * fl * (p1 - p0)
310  + 2 * fl * fr * (p2 - p1)
311  + fr * fr * (p3 - p2)
312  );
313  //Okay, we now have everything
314  return StepInformation(midPos, midMom, midElectrons, midTime, midLength);
315  }
316 
317  } //VXD namespace
319 } //Belle2 namespace
int getPDGCode() const
PDG code.
Definition: Const.h:464
static const ParticleType neutron
neutron particle
Definition: Const.h:666
static const double ehEnergy
Energy needed to create an electron-hole pair in Si at std.
Definition: Const.h:688
static const ParticleType photon
photon particle
Definition: Const.h:664
@ c_PrimaryParticle
bit 0: Particle is primary particle.
Definition: MCParticle.h:47
Class to keep track of the traversal of the sensitive volume for one track.
int getPDGCode() const
get PDG code of the particle
double getElectrons() const
get total number of deposited electrons so far
void setInitial(int trackID, int pdgCode, bool primary)
set initial values for a new track
bool isPrimary() const
return whether the track belongs to a primary particle
void hasLeft()
indicate that the track left the current volume
int getTrackID() const
get Geant4 trackID
void add(const G4ThreeVector &position, const G4ThreeVector &momentum, double electrons, double time, double length)
add a new step
void reset()
reset to be used again
void hasEntered()
indicate that the track originated outisde the current volume
bool isContained() const
return whether the track was contained in the volume so far
std::pair< iterator, iterator > range
Iterator pair for a set of points.
double getLength() const
get flight length so far
static Payload getInfo(Carrier &obj)
Static function to just return UserInformation attached to the obj of type Carrier.
Definition: UserInfo.h:100
static const double mm
[millimeters]
Definition: Unit.h:70
static const double um
[micrometers]
Definition: Unit.h:71
static const double MeV
[megaelectronvolt]
Definition: Unit.h:114
static const double ns
Standard of [time].
Definition: Unit.h:48
Packed class to represent energy deposit along a path in electrons.
float m_minimumElectrons
minimum number of electrons a track must deposit for SimHit/TrueHits to be created
StepInformation findMidPoint(const SensorTraversal &traversal)
Find the mid-point of the track traversal.
bool finishTrack()
Process a track once all steps are known.
float m_distanceTolerance
maximum distance between step point and linear interpolation of sensor traversal before a new simhit ...
virtual int saveSimHit(const SensorTraversal &traversal, const SensorTraversal::range &points)=0
Save a SimHit for this track including the given points.
bool m_onlyPrimaryTrueHits
only create TrueHits for primary particles
std::stack< SensorTraversal > m_tracks
stack of SensorTraversal information for all tracks not finished so far
std::vector< std::pair< unsigned int, float > > createSimHits()
Determine which SimHits to create.
float m_electronTolerance
maximum relative difference between electron density of two steps where they can be considered simila...
VxdID getSensorID() const
Return the VxdID belonging to this sensitive detector.
virtual int saveTrueHit(const SensorTraversal &traversal)=0
Save the actual TrueHit for this sensor traversal.
bool m_seeNeutrons
also create SimHit/TrueHit objects for neutrons (or charged particles which deposit less than m_minim...
std::vector< unsigned int > simplifyEnergyDeposit(const SensorTraversal::range &points)
Simplify the energy deposition profile using Douglas-Peuker-Algorithm We normally force a Geant4 step...
virtual void saveRelations(const SensorTraversal &traversal, int trueHitIndex, std::vector< std::pair< unsigned int, float >> simHitIndices)=0
Save the relations between MCParticle, TrueHit and SimHits.
bool step(G4Step *step, G4TouchableHistory *) override
Process a single Geant4 Step.
Small helper class to facilitate debugging of VXD::SensitiveDetector implementation.
static SensitiveDetectorDebugHelper & getInstance()
Singleton class: get instance.
double sqrt(double a)
sqrt for double
Definition: beamHelpers.h:28
Abstract base class for different kinds of events.
Simple struct to keep information about steps in the sensitive detector.