Belle II Software development
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
21namespace 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)) {
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:473
static const ParticleType neutron
neutron particle
Definition: Const.h:675
static const double ehEnergy
Energy needed to create an electron-hole pair in Si at std.
Definition: Const.h:697
static const ParticleType photon
photon particle
Definition: Const.h:673
@ 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.
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.
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...
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.
Abstract base class for different kinds of events.
Simple struct to keep information about steps in the sensitive detector.