DriftElectronstoPlane_module.cc
Go to the documentation of this file.
1 /**
2  * @file SimDriftElectrons_module.cxx
3  *
4  * @brief Transports energy depositions in the LAr TPC to the TPC
5  * channels.
6  *
7  * author:
8  * This module was prepared by William Seligman (me), based on code
9  * that had been in
10  * `LArG4::LArVoxelReadout::DriftIonizationElectrons`. However, though
11  * I wrote the original LArVoxelReadout code, I have no idea who added
12  * DriftIonizationElectrons. I probably will not be able to answer any
13  * questions about how this code works.
14  *
15  * This module acts on sim::SimEnergyDeposit, the single energy
16  * depositions from the detector simulation (LArG4), and simulates the
17  * transport of the ensuing ionization electrons to the readout
18  * channels:
19  *
20  * 1. the number of ionisation electrons is read from the current
21  * `larg4::IonizationAndScintillation` instance
22  * 2. space charge displacement is optionally applied
23  * 3. lifetime correction is applied
24  * 4. charge is split in small electron clusters
25  * 5. each cluster is subject to longitudinal and transverse diffusion
26  * 6. each cluster is assigned to one TPC channel for each wire plane
27  * 7. optionally, charge is forced to stay on the planes; otherwise charge
28  * drifting outside the plane is lost
29  *
30  * For each energy deposition, entries on the appropriate
31  * `sim::SimChannel` are added, with the information of the position
32  * where the energy deposit happened (in global coordinates,
33  * centimeters), the ID of the track in the detector simulation which
34  * produced the deposition, and the quantized time of arrival to the
35  * channel (in global TDC tick units). At most one entry is added for
36  * each electron cluster, but entries from the same energy deposit can
37  * be compacted if falling on the same TDC tick.
38  *
39  * Options
40  * --------
41  *
42  * A few optional behaviours are supported:
43  *
44  * * lead off-plane charge to the planes: regulated by
45  * `RecoverOffPlaneDeposit()`, if charge which reaches a wire plane
46  * is actually off it by less than the chosen margin, it's accounted for by
47  * that plane; by default the margin is 0 and all the charge off the plane
48  * is lost (with a warning)
49  *
50  * Update:
51  * Christoph Alt, September 2018 (christoph.alt@cern.ch)
52  * Break hardcoded charge drift in x to support charge drift in y and z.
53  */
54 
55 // LArSoft includes
63 
64 // Framework includes
70 #include "fhiclcpp/ParameterSet.h"
72 #include "nurandom/RandomUtils/NuRandomService.h"
73 
74 // External libraries
75 #include "CLHEP/Random/RandGauss.h"
76 #include "TMath.h"
77 
78 namespace detsim {
79 
80  // Base class for creation of raw signals on wires.
82  public:
83  explicit DriftElectronstoPlane(fhicl::ParameterSet const& pset);
84 
85  // Methods that that are available for a module derived from
86  // art::EDProducer.
87  void produce(art::Event& evt) override;
88  void beginJob() override;
89 
90  private:
91  // The label of the module that created the sim::SimEnergyDeposit
92  // objects (as of Oct-2017, this is probably "largeant").
94 
95  CLHEP::RandGauss fRandGauss;
96 
103  double fRecombA;
104  double fRecombk;
105  double fModBoxA;
106  double fModBoxB;
108 
111  double fLDiff_const;
112  double fTDiff_const;
113  double fRecipDriftVel[3];
114 
115  // Save the number of cryostats, and the number of TPCs within
116  // each cryostat.
117  size_t fNCryostats;
118  std::vector<size_t> fNTPCs;
119 
120  // Per-cluster information.
121  std::vector<double> fLongDiff;
122  std::vector<double> fTransDiff1;
123  std::vector<double> fTransDiff2;
124  std::vector<double> fnElDiff;
125  std::vector<double> fnEnDiff;
126 
127  double fDriftClusterPos[3];
128 
129  art::ServiceHandle<geo::Geometry const> fGeometry; ///< Handle to the Geometry service
130 
131  //IS calculationg
133 
134  }; // class DriftElectronstoPlane
135 
136  //-------------------------------------------------
138  : art::EDProducer{pset}
139  , fSimModuleLabel{pset.get<art::InputTag>("SimulationLabel")}
140  // create a default random engine; obtain the random seed from
141  // NuRandomService, unless overridden in configuration with key
142  // "Seed"
143  , fRandGauss{art::ServiceHandle<rndm::NuRandomService>{}->createEngine(*this, pset, "Seed")}
144  , fStoreDriftedElectronClusters{pset.get<bool>("StoreDriftedElectronClusters", true)}
145  , fLongitudinalDiffusion{pset.get<double>("LongitudinalDiffusion", 6.2e-9)}
146  , fTransverseDiffusion{pset.get<double>("TransverseDiffusion", 16.3e-9)}
147  , fElectronClusterSize{pset.get<double>("ElectronClusterSize", 600.0)}
148  , fMinNumberOfElCluster{pset.get<int>("MinNumberOfElCluster", 0)}
149  , fGeVToElectrons{pset.get<double>("GeVToElectrons", 4.237e+07)}
150  , fRecombA{pset.get<double>("RecombA", 0.800)}
151  , fRecombk{pset.get<double>("Recombk", 0.0486)}
152  , fModBoxA{pset.get<double>("ModBoxA", 0.930)}
153  , fModBoxB{pset.get<double>("ModBoxB", 0.212)}
154  , fUseModBoxRecomb{pset.get<bool>("UseModBoxRecomb", true)}
155  , fISAlg{pset}
156  {
157  if (fStoreDriftedElectronClusters) { produces<std::vector<sim::SimDriftedElectronCluster>>(); }
158  }
159 
160  //-------------------------------------------------
161  void
163  {
164  // Define the physical constants we'll use.
165 
166  auto const detProp =
169  detProp
170  .ElectronLifetime(); // Electron lifetime as returned by the DetectorProperties service assumed to be in us;
171  for (int i = 0; i < 3; ++i) {
172  double driftVelocity =
173  detProp.DriftVelocity(detProp.Efield(i),
174  detProp.Temperature()) *
175  1.e-3; // Drift velocity as returned by the DetectorProperties service assumed to be in cm/us. Multiply by 1.e-3 to convert into LArSoft standard velocity units, cm/ns;
176 
177  fRecipDriftVel[i] = 1. / driftVelocity;
178  }
179  MF_LOG_DEBUG("DriftElectronstoPlane")
180  << " e lifetime (ns): " << fElectronLifetime
181  << "\n Temperature (K): " << detProp.Temperature()
182  << "\n Drift velocity (cm/ns): " << 1. / fRecipDriftVel[0] << " " << 1. / fRecipDriftVel[1]
183  << " " << 1. / fRecipDriftVel[2];
184 
185  // Opposite of lifetime. Convert from us to standard LArSoft time units, ns;
187  fLDiff_const = std::sqrt(2. * fLongitudinalDiffusion);
188  fTDiff_const = std::sqrt(2. * fTransverseDiffusion);
189 
190  // For this detector's geometry, save the number of cryostats and
191  // the number of TPCs within each cryostat.
193  fNTPCs.resize(fNCryostats);
194  for (size_t n = 0; n < fNCryostats; ++n)
195  fNTPCs[n] = fGeometry->NTPC(n);
196  }
197 
198  //-------------------------------------------------
199  void
201  {
202  // Fetch the SimEnergyDeposit objects for this event.
203  typedef art::Handle<std::vector<sim::SimEnergyDeposit>> energyDepositHandle_t;
204  energyDepositHandle_t energyDepositHandle;
205  // If there aren't any energy deposits for this event, don't
206  // panic. It's possible someone is doing a study with events
207  // outside the TPC, or where there are only non-ionizing
208  // particles, or something like that.
209  if (!event.getByLabel(fSimModuleLabel, energyDepositHandle)) return;
210  // Container for the SimDriftedElectronCluster objects
211  std::unique_ptr<std::vector<sim::SimDriftedElectronCluster>>
212  SimDriftedElectronClusterCollection(new std::vector<sim::SimDriftedElectronCluster>);
213 
214  // We're going through the input vector by index, rather than by
215  // iterator, because we need the index number to compute the
216  // associations near the end of this method.
217  auto const& energyDeposits = *energyDepositHandle;
218  auto energyDepositsSize = energyDeposits.size();
219 
220  auto const detProp =
222  // For each energy deposit in this event
223  for (size_t edIndex = 0; edIndex < energyDepositsSize; ++edIndex) {
224  auto const& energyDeposit = energyDeposits[edIndex];
225 
226  // "xyz" is the position of the energy deposit in world
227  // coordinates. Note that the units of distance in
228  // sim::SimEnergyDeposit are supposed to be cm.
229  auto const mp = energyDeposit.MidPoint();
230  double const xyz[3] = {mp.X(), mp.Y(), mp.Z()};
231 
232  // From the position in world coordinates, determine the
233  // cryostat and tpc. If somehow the step is outside a tpc
234  // (e.g., cosmic rays in rock) just move on to the next one.
235  unsigned int cryostat = 0;
236  unsigned int tpc = 0;
237  const geo::TPCGeo& tpcGeo = fGeometry->TPC(tpc, cryostat);
238 
239  // The drift direction can be either in the positive
240  // or negative direction in any coordinate x, y or z.
241  // Charge drift in ...
242  // +x: tpcGeo.DetectDriftDirection()==1
243  // -x: tpcGeo.DetectDriftDirection()==-1
244  // +y: tpcGeo.DetectDriftDirection()==2
245  // -y tpcGeo.DetectDriftDirection()==-2
246  // +z: tpcGeo.DetectDriftDirection()==3
247  // -z: tpcGeo.DetectDriftDirection()==-3
248 
249  //Define charge drift direction: driftcoordinate (x, y or z) and driftsign (positive or negative). Also define coordinates perpendicular to drift direction.
250  int driftcoordinate = std::abs(tpcGeo.DetectDriftDirection()) - 1; //x:0, y:1, z:2
251 
252  int transversecoordinate1 = 0;
253  int transversecoordinate2 = 0;
254  if (driftcoordinate == 0) {
255  transversecoordinate1 = 1;
256  transversecoordinate2 = 2;
257  }
258  else if (driftcoordinate == 1) {
259  transversecoordinate1 = 0;
260  transversecoordinate2 = 2;
261  }
262  else if (driftcoordinate == 2) {
263  transversecoordinate1 = 0;
264  transversecoordinate2 = 1;
265  }
266 
267  if (transversecoordinate1 == transversecoordinate2)
268  continue; //this is the case when driftcoordinate != 0, 1 or 2
269 
270  int driftsign = 0; //1: +x, +y or +z, -1: -x, -y or -z
271  if (tpcGeo.DetectDriftDirection() > 0)
272  driftsign = 1;
273  else
274  driftsign = -1;
275 
276  //Check for charge deposits behind charge readout planes
277  if (driftsign == 1 && tpcGeo.PlaneLocation(0)[driftcoordinate] < xyz[driftcoordinate])
278  continue;
279  if (driftsign == -1 && tpcGeo.PlaneLocation(0)[driftcoordinate] > xyz[driftcoordinate])
280  continue;
281 
282  /// \todo think about effects of drift between planes.
283  // Center of plane is also returned in cm units
284  double DriftDistance =
285  std::abs(xyz[driftcoordinate] - tpcGeo.PlaneLocation(0)[driftcoordinate]);
286 
287  // Space-charge effect (SCE): Get SCE {x,y,z} offsets for
288  // particular location in TPC
289  geo::Vector_t posOffsets{0.0, 0.0, 0.0};
290  double posOffsetxyz[3] = {
291  0.0, 0.0, 0.0}; //need this array for the driftcoordinate and transversecoordinates
292  auto const* SCE = lar::providerFrom<spacecharge::SpaceChargeService>();
293  if (SCE->EnableSimSpatialSCE() == true) {
294  posOffsets = SCE->GetPosOffsets(mp);
295  if (larsim::Utils::SCE::out_of_bounds(posOffsets)) continue;
296  posOffsetxyz[0] = posOffsets.X();
297  posOffsetxyz[1] = posOffsets.Y();
298  posOffsetxyz[2] = posOffsets.Z();
299  }
300 
301  double avegagetransversePos1 = 0.;
302  double avegagetransversePos2 = 0.;
303 
304  DriftDistance += -1. * posOffsetxyz[driftcoordinate];
305  avegagetransversePos1 = xyz[transversecoordinate1] + posOffsetxyz[transversecoordinate1];
306  avegagetransversePos2 = xyz[transversecoordinate2] + posOffsetxyz[transversecoordinate2];
307 
308  // Space charge distortion could push the energy deposit beyond the wire
309  // plane (see issue #15131). Given that we don't have any subtlety in the
310  // simulation of this region, bringing the deposit exactly on the plane
311  // should be enough for the time being.
312  if (DriftDistance < 0.) DriftDistance = 0.;
313 
314  // Drift time in ns
315  double TDrift = DriftDistance * fRecipDriftVel[0];
316 
317  if (
318  tpcGeo.Nplanes() == 2 &&
319  driftcoordinate ==
320  0) { // special case for ArgoNeuT (Nplanes = 2 and drift direction = x): plane 0 is the second wire plane
321  TDrift = ((DriftDistance - tpcGeo.PlanePitch(0, 1)) * fRecipDriftVel[0] +
322  tpcGeo.PlanePitch(0, 1) * fRecipDriftVel[1]);
323  }
324  const int nIonizedElectrons =
326 
327  const double lifetimecorrection = TMath::Exp(TDrift / fLifetimeCorr_const);
328  const double energy = energyDeposit.Energy();
329 
330  // if we have no electrons (too small energy or too large recombination)
331  // we are done already here
332  if (nIonizedElectrons <= 0) {
333  MF_LOG_DEBUG("DriftElectronstoPlane")
334  << "step " // << energyDeposit << "\n"
335  << "No electrons drifted to readout, " << energy << " MeV lost.";
336  continue;
337  }
338 
339  // includes the effect of lifetime: lifetimecorrection = exp[-tdrift/tau]
340  const double nElectrons = nIonizedElectrons * lifetimecorrection;
341 
342  // Longitudinal & transverse diffusion sigma (cm)
343  double SqrtT = std::sqrt(TDrift);
344  double LDiffSig = SqrtT * fLDiff_const;
345  double TDiffSig = SqrtT * fTDiff_const;
346  double electronclsize = fElectronClusterSize;
347 
348  // Number of electron clusters.
349  int nClus = (int)std::ceil(nElectrons / electronclsize);
350  if (nClus < fMinNumberOfElCluster) {
351  electronclsize = nElectrons / fMinNumberOfElCluster;
352  if (electronclsize < 1.0) { electronclsize = 1.0; }
353  nClus = (int)std::ceil(nElectrons / electronclsize);
354  }
355 
356  // Empty and resize the electron-cluster vectors.
357  fLongDiff.clear();
358  fTransDiff1.clear();
359  fTransDiff2.clear();
360  fnElDiff.clear();
361  fnEnDiff.clear();
362  fLongDiff.resize(nClus);
363  fTransDiff1.resize(nClus);
364  fTransDiff2.resize(nClus);
365  fnElDiff.resize(nClus, electronclsize);
366  fnEnDiff.resize(nClus);
367 
368  // fix the number of electrons in the last cluster, that has a smaller size
369  fnElDiff.back() = nElectrons - (nClus - 1) * electronclsize;
370 
371  for (size_t xx = 0; xx < fnElDiff.size(); ++xx) {
372  if (nElectrons > 0)
373  fnEnDiff[xx] = energy / nElectrons * fnElDiff[xx];
374  else
375  fnEnDiff[xx] = 0.;
376  }
377 
378  // Smear drift times by longitudinal diffusion
379  if (LDiffSig > 0.0)
380  fRandGauss.fireArray(nClus, &fLongDiff[0], 0., LDiffSig);
381  else
382  fLongDiff.assign(nClus, 0.0);
383 
384  if (TDiffSig > 0.0) {
385  // Smear the coordinates in plane perpendicular to drift direction by the transverse diffusion
386  fRandGauss.fireArray(nClus, &fTransDiff1[0], avegagetransversePos1, TDiffSig);
387  fRandGauss.fireArray(nClus, &fTransDiff2[0], avegagetransversePos2, TDiffSig);
388  }
389  else {
390  fTransDiff1.assign(nClus, avegagetransversePos1);
391  fTransDiff2.assign(nClus, avegagetransversePos2);
392  }
393 
394  // make a collection of electrons for each plane
395  for (size_t p = 0; p < tpcGeo.Nplanes(); ++p) {
396 
397  fDriftClusterPos[driftcoordinate] = tpcGeo.PlaneLocation(p)[driftcoordinate];
398 
399  // Drift nClus electron clusters to the induction plane
400  for (int k = 0; k < nClus; ++k) {
401 
402  // Correct drift time for longitudinal diffusion and plane
403  double TDiff = TDrift + fLongDiff[k] * fRecipDriftVel[0];
404 
405  // Take into account different Efields between planes
406  // Also take into account special case for ArgoNeuT (Nplanes = 2 and drift direction = x): plane 0 is the second wire plane
407  for (size_t ip = 0; ip < p; ++ip) {
408  TDiff +=
409  (tpcGeo.PlaneLocation(ip + 1)[driftcoordinate] -
410  tpcGeo.PlaneLocation(ip)[driftcoordinate]) *
411  fRecipDriftVel[(tpcGeo.Nplanes() == 2 && driftcoordinate == 0) ? ip + 2 : ip + 1];
412  }
413 
414  fDriftClusterPos[transversecoordinate1] = fTransDiff1[k];
415  fDriftClusterPos[transversecoordinate2] = fTransDiff2[k];
416  auto const simTime = energyDeposit.Time();
417  /// \todo think about effects of drift between planes
418  SimDriftedElectronClusterCollection->emplace_back(
419  fnElDiff[k],
420  TDiff + simTime, // timing
421  geo::Point_t{mp.X(), mp.Y(), mp.Z()}, // mean position of the deposited energy
423  fDriftClusterPos[1],
424  fDriftClusterPos[2]}, // final position of the drifted cluster
425  geo::Point_t{
426  LDiffSig, TDiffSig, TDiffSig}, // Longitudinal (X) and transverse (Y,Z) diffusion
427  fnEnDiff[k], //deposited energy that originated this cluster
428  energyDeposit.TrackID());
429 
430  } // end loop over clusters
431  } // end loop over planes
432  } // for each sim::SimEnergyDeposit
433 
434  if (fStoreDriftedElectronClusters) event.put(std::move(SimDriftedElectronClusterCollection));
435  }
436 } // namespace detsim
437 
double PlanePitch(unsigned int p1=0, unsigned int p2=1) const
Definition: TPCGeo.cxx:388
contains objects relating to SimDriftedElectronCluster
bool out_of_bounds(geo::Vector_t const &offset)
unsigned int Nplanes() const
Number of planes in this tpc.
Definition: TPCGeo.h:165
art::ServiceHandle< geo::Geometry const > fGeometry
Handle to the Geometry service.
EDProducer(fhicl::ParameterSet const &pset)
Definition: EDProducer.h:20
Geometry information for a single TPC.
Definition: TPCGeo.h:38
Detector simulation of raw signals on wires.
ROOT::Math::DisplacementVector3D< ROOT::Math::Cartesian3D< double >, ROOT::Math::GlobalCoordinateSystemTag > Vector_t
Type for representation of momenta in 3D space.
Definition: geo_vectors.h:164
unsigned int Ncryostats() const
Returns the number of cryostats in the detector.
art framework interface to geometry description
T abs(T value)
bool getByLabel(std::string const &label, std::string const &instance, Handle< PROD > &result) const
Definition: DataViewImpl.h:633
const double e
#define DEFINE_ART_MODULE(klass)
Definition: ModuleMacros.h:67
Utility function for testing if Space Charge offsets are out of bounds.
std::void_t< T > n
def move(depos, offset)
Definition: depos.py:107
p
Definition: test.py:223
Data CalculateIonizationAndScintillation(detinfo::DetectorPropertiesData const &detProp, sim::SimEnergyDeposit const &edep) const
unsigned int NTPC(unsigned int cstat=0) const
Returns the total number of TPCs in the specified cryostat.
ROOT::Math::PositionVector3D< ROOT::Math::Cartesian3D< double >, ROOT::Math::GlobalCoordinateSystemTag > Point_t
Type for representation of position in physical 3D space.
Definition: geo_vectors.h:184
void produce(art::Event &evt) override
short int DetectDriftDirection() const
Returns the expected drift direction based on geometry.
Definition: TPCGeo.cxx:157
contains information for a single step in the detector simulation
DriftElectronstoPlane(fhicl::ParameterSet const &pset)
#define MF_LOG_DEBUG(id)
TPCGeo const & TPC(unsigned int const tpc=0, unsigned int const cstat=0) const
Returns the specified TPC.
TCEvent evt
Definition: DataStructs.cxx:7
const double * PlaneLocation(unsigned int p) const
Definition: TPCGeo.cxx:382
Event finding and building.