All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
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
64 
65 // Framework includes
66 #include "art/Framework/Core/EDProducer.h"
67 #include "art/Framework/Core/ModuleMacros.h"
68 #include "art/Framework/Principal/Event.h"
69 #include "art/Framework/Principal/Handle.h"
70 #include "art/Framework/Services/Registry/ServiceHandle.h"
71 #include "fhiclcpp/ParameterSet.h"
72 #include "messagefacility/MessageLogger/MessageLogger.h"
73 #include "nurandom/RandomUtils/NuRandomService.h"
74 
75 // External libraries
76 #include "CLHEP/Random/RandGauss.h"
77 #include "TMath.h"
78 
79 #include <cmath>
80 #include <memory>
81 #include <utility>
82 #include <vector>
83 
84 namespace detsim {
85 
86  // Base class for creation of raw signals on wires.
87  class DriftElectronstoPlane : public art::EDProducer {
88  public:
89  explicit DriftElectronstoPlane(fhicl::ParameterSet const& pset);
90 
91  // Methods that that are available for a module derived from
92  // art::EDProducer.
93  void produce(art::Event& evt) override;
94  void beginJob() override;
95 
96  private:
97  // The label of the module that created the sim::SimEnergyDeposit
98  // objects (as of Oct-2017, this is probably "largeant").
99  art::InputTag fSimModuleLabel;
100 
101  CLHEP::RandGauss fRandGauss;
102 
109  double fRecombA;
110  double fRecombk;
111  double fModBoxA;
112  double fModBoxB;
114 
117  double fLDiff_const;
118  double fTDiff_const;
119  double fRecipDriftVel[3];
120 
121  // Save the number of cryostats, and the number of TPCs within
122  // each cryostat.
123  size_t fNCryostats;
124  std::vector<size_t> fNTPCs;
125 
126  // Per-cluster information.
127  std::vector<double> fLongDiff;
128  std::vector<double> fTransDiff1;
129  std::vector<double> fTransDiff2;
130  std::vector<double> fnElDiff;
131  std::vector<double> fnEnDiff;
132 
133  double fDriftClusterPos[3];
134 
135  art::ServiceHandle<geo::Geometry const> fGeometry; ///< Handle to the Geometry service
136 
137  //IS calculationg
139 
140  }; // class DriftElectronstoPlane
141 
142  //-------------------------------------------------
143  DriftElectronstoPlane::DriftElectronstoPlane(fhicl::ParameterSet const& pset)
144  : art::EDProducer{pset}
145  , fSimModuleLabel{pset.get<art::InputTag>("SimulationLabel")}
146  // create a default random engine; obtain the random seed from
147  // NuRandomService, unless overridden in configuration with key
148  // "Seed"
149  , fRandGauss{art::ServiceHandle<rndm::NuRandomService>{}->createEngine(*this, pset, "Seed")}
150  , fStoreDriftedElectronClusters{pset.get<bool>("StoreDriftedElectronClusters", true)}
151  , fLongitudinalDiffusion{pset.get<double>("LongitudinalDiffusion", 6.2e-9)}
152  , fTransverseDiffusion{pset.get<double>("TransverseDiffusion", 16.3e-9)}
153  , fElectronClusterSize{pset.get<double>("ElectronClusterSize", 600.0)}
154  , fMinNumberOfElCluster{pset.get<int>("MinNumberOfElCluster", 0)}
155  , fGeVToElectrons{pset.get<double>("GeVToElectrons", 4.237e+07)}
156  , fRecombA{pset.get<double>("RecombA", 0.800)}
157  , fRecombk{pset.get<double>("Recombk", 0.0486)}
158  , fModBoxA{pset.get<double>("ModBoxA", 0.930)}
159  , fModBoxB{pset.get<double>("ModBoxB", 0.212)}
160  , fUseModBoxRecomb{pset.get<bool>("UseModBoxRecomb", true)}
161  , fISAlg{pset}
162  {
163  if (fStoreDriftedElectronClusters) { produces<std::vector<sim::SimDriftedElectronCluster>>(); }
164  }
165 
166  //-------------------------------------------------
167  void
169  {
170  // Define the physical constants we'll use.
171 
172  auto const detProp =
173  art::ServiceHandle<detinfo::DetectorPropertiesService const>()->DataForJob();
175  detProp
176  .ElectronLifetime(); // Electron lifetime as returned by the DetectorProperties service assumed to be in us;
177  for (int i = 0; i < 3; ++i) {
178  double driftVelocity =
179  detProp.DriftVelocity(detProp.Efield(i),
180  detProp.Temperature()) *
181  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;
182 
183  fRecipDriftVel[i] = 1. / driftVelocity;
184  }
185  MF_LOG_DEBUG("DriftElectronstoPlane")
186  << " e lifetime (ns): " << fElectronLifetime
187  << "\n Temperature (K): " << detProp.Temperature()
188  << "\n Drift velocity (cm/ns): " << 1. / fRecipDriftVel[0] << " " << 1. / fRecipDriftVel[1]
189  << " " << 1. / fRecipDriftVel[2];
190 
191  // Opposite of lifetime. Convert from us to standard LArSoft time units, ns;
193  fLDiff_const = std::sqrt(2. * fLongitudinalDiffusion);
194  fTDiff_const = std::sqrt(2. * fTransverseDiffusion);
195 
196  // For this detector's geometry, save the number of cryostats and
197  // the number of TPCs within each cryostat.
198  fNCryostats = fGeometry->Ncryostats();
199  fNTPCs.resize(fNCryostats);
200  for (size_t n = 0; n < fNCryostats; ++n)
201  fNTPCs[n] = fGeometry->NTPC(n);
202  }
203 
204  //-------------------------------------------------
205  void
206  DriftElectronstoPlane::produce(art::Event& event)
207  {
208  // Fetch the SimEnergyDeposit objects for this event.
209  typedef art::Handle<std::vector<sim::SimEnergyDeposit>> energyDepositHandle_t;
210  energyDepositHandle_t energyDepositHandle;
211  // If there aren't any energy deposits for this event, don't
212  // panic. It's possible someone is doing a study with events
213  // outside the TPC, or where there are only non-ionizing
214  // particles, or something like that.
215  if (!event.getByLabel(fSimModuleLabel, energyDepositHandle)) return;
216  // Container for the SimDriftedElectronCluster objects
217  std::unique_ptr<std::vector<sim::SimDriftedElectronCluster>>
218  SimDriftedElectronClusterCollection(new std::vector<sim::SimDriftedElectronCluster>);
219 
220  // We're going through the input vector by index, rather than by
221  // iterator, because we need the index number to compute the
222  // associations near the end of this method.
223  auto const& energyDeposits = *energyDepositHandle;
224  auto energyDepositsSize = energyDeposits.size();
225 
226  auto const detProp =
227  art::ServiceHandle<detinfo::DetectorPropertiesService const>()->DataFor(event);
228  // For each energy deposit in this event
229  for (size_t edIndex = 0; edIndex < energyDepositsSize; ++edIndex) {
230  auto const& energyDeposit = energyDeposits[edIndex];
231 
232  // "xyz" is the position of the energy deposit in world
233  // coordinates. Note that the units of distance in
234  // sim::SimEnergyDeposit are supposed to be cm.
235  auto const mp = energyDeposit.MidPoint();
236  double const xyz[3] = {mp.X(), mp.Y(), mp.Z()};
237 
238  // From the position in world coordinates, determine the
239  // cryostat and tpc. If somehow the step is outside a tpc
240  // (e.g., cosmic rays in rock) just move on to the next one.
241  unsigned int cryostat = 0;
242  unsigned int tpc = 0;
243  const geo::TPCGeo& tpcGeo = fGeometry->TPC(tpc, cryostat);
244 
245  // The drift direction can be either in the positive
246  // or negative direction in any coordinate x, y or z.
247  // Charge drift in ...
248  // +x: tpcGeo.DetectDriftDirection()==1
249  // -x: tpcGeo.DetectDriftDirection()==-1
250  // +y: tpcGeo.DetectDriftDirection()==2
251  // -y tpcGeo.DetectDriftDirection()==-2
252  // +z: tpcGeo.DetectDriftDirection()==3
253  // -z: tpcGeo.DetectDriftDirection()==-3
254 
255  //Define charge drift direction: driftcoordinate (x, y or z) and driftsign (positive or negative). Also define coordinates perpendicular to drift direction.
256  int driftcoordinate = std::abs(tpcGeo.DetectDriftDirection()) - 1; //x:0, y:1, z:2
257 
258  int transversecoordinate1 = 0;
259  int transversecoordinate2 = 0;
260  if (driftcoordinate == 0) {
261  transversecoordinate1 = 1;
262  transversecoordinate2 = 2;
263  }
264  else if (driftcoordinate == 1) {
265  transversecoordinate1 = 0;
266  transversecoordinate2 = 2;
267  }
268  else if (driftcoordinate == 2) {
269  transversecoordinate1 = 0;
270  transversecoordinate2 = 1;
271  }
272 
273  if (transversecoordinate1 == transversecoordinate2)
274  continue; //this is the case when driftcoordinate != 0, 1 or 2
275 
276  int driftsign = 0; //1: +x, +y or +z, -1: -x, -y or -z
277  if (tpcGeo.DetectDriftDirection() > 0)
278  driftsign = 1;
279  else
280  driftsign = -1;
281 
282  //Check for charge deposits behind charge readout planes
283  if (driftsign == 1 && tpcGeo.PlaneLocation(0)[driftcoordinate] < xyz[driftcoordinate])
284  continue;
285  if (driftsign == -1 && tpcGeo.PlaneLocation(0)[driftcoordinate] > xyz[driftcoordinate])
286  continue;
287 
288  /// \todo think about effects of drift between planes.
289  // Center of plane is also returned in cm units
290  double DriftDistance =
291  std::abs(xyz[driftcoordinate] - tpcGeo.PlaneLocation(0)[driftcoordinate]);
292 
293  // Space-charge effect (SCE): Get SCE {x,y,z} offsets for
294  // particular location in TPC
295  geo::Vector_t posOffsets{0.0, 0.0, 0.0};
296  double posOffsetxyz[3] = {
297  0.0, 0.0, 0.0}; //need this array for the driftcoordinate and transversecoordinates
298  auto const* SCE = lar::providerFrom<spacecharge::SpaceChargeService>();
299  if (SCE->EnableSimSpatialSCE() == true) {
300  posOffsets = SCE->GetPosOffsets(mp);
301  if (larsim::Utils::SCE::out_of_bounds(posOffsets)) continue;
302  posOffsetxyz[0] = posOffsets.X();
303  posOffsetxyz[1] = posOffsets.Y();
304  posOffsetxyz[2] = posOffsets.Z();
305  }
306 
307  double avegagetransversePos1 = 0.;
308  double avegagetransversePos2 = 0.;
309 
310  DriftDistance += -1. * posOffsetxyz[driftcoordinate];
311  avegagetransversePos1 = xyz[transversecoordinate1] + posOffsetxyz[transversecoordinate1];
312  avegagetransversePos2 = xyz[transversecoordinate2] + posOffsetxyz[transversecoordinate2];
313 
314  // Space charge distortion could push the energy deposit beyond the wire
315  // plane (see issue #15131). Given that we don't have any subtlety in the
316  // simulation of this region, bringing the deposit exactly on the plane
317  // should be enough for the time being.
318  if (DriftDistance < 0.) DriftDistance = 0.;
319 
320  // Drift time in ns
321  double TDrift = DriftDistance * fRecipDriftVel[0];
322 
323  if (
324  tpcGeo.Nplanes() == 2 &&
325  driftcoordinate ==
326  0) { // special case for ArgoNeuT (Nplanes = 2 and drift direction = x): plane 0 is the second wire plane
327  TDrift = ((DriftDistance - tpcGeo.PlanePitch(0, 1)) * fRecipDriftVel[0] +
328  tpcGeo.PlanePitch(0, 1) * fRecipDriftVel[1]);
329  }
330  const int nIonizedElectrons =
332 
333  const double lifetimecorrection = TMath::Exp(TDrift / fLifetimeCorr_const);
334  const double energy = energyDeposit.Energy();
335 
336  // if we have no electrons (too small energy or too large recombination)
337  // we are done already here
338  if (nIonizedElectrons <= 0) {
339  MF_LOG_DEBUG("DriftElectronstoPlane")
340  << "step " // << energyDeposit << "\n"
341  << "No electrons drifted to readout, " << energy << " MeV lost.";
342  continue;
343  }
344 
345  // includes the effect of lifetime: lifetimecorrection = exp[-tdrift/tau]
346  const double nElectrons = nIonizedElectrons * lifetimecorrection;
347 
348  // Longitudinal & transverse diffusion sigma (cm)
349  double SqrtT = std::sqrt(TDrift);
350  double LDiffSig = SqrtT * fLDiff_const;
351  double TDiffSig = SqrtT * fTDiff_const;
352  double electronclsize = fElectronClusterSize;
353 
354  // Number of electron clusters.
355  int nClus = (int)std::ceil(nElectrons / electronclsize);
356  if (nClus < fMinNumberOfElCluster) {
357  electronclsize = nElectrons / fMinNumberOfElCluster;
358  if (electronclsize < 1.0) { electronclsize = 1.0; }
359  nClus = (int)std::ceil(nElectrons / electronclsize);
360  }
361 
362  // Empty and resize the electron-cluster vectors.
363  fLongDiff.clear();
364  fTransDiff1.clear();
365  fTransDiff2.clear();
366  fnElDiff.clear();
367  fnEnDiff.clear();
368  fLongDiff.resize(nClus);
369  fTransDiff1.resize(nClus);
370  fTransDiff2.resize(nClus);
371  fnElDiff.resize(nClus, electronclsize);
372  fnEnDiff.resize(nClus);
373 
374  // fix the number of electrons in the last cluster, that has a smaller size
375  fnElDiff.back() = nElectrons - (nClus - 1) * electronclsize;
376 
377  for (size_t xx = 0; xx < fnElDiff.size(); ++xx) {
378  if (nElectrons > 0)
379  fnEnDiff[xx] = energy / nElectrons * fnElDiff[xx];
380  else
381  fnEnDiff[xx] = 0.;
382  }
383 
384  // Smear drift times by longitudinal diffusion
385  if (LDiffSig > 0.0)
386  fRandGauss.fireArray(nClus, &fLongDiff[0], 0., LDiffSig);
387  else
388  fLongDiff.assign(nClus, 0.0);
389 
390  if (TDiffSig > 0.0) {
391  // Smear the coordinates in plane perpendicular to drift direction by the transverse diffusion
392  fRandGauss.fireArray(nClus, &fTransDiff1[0], avegagetransversePos1, TDiffSig);
393  fRandGauss.fireArray(nClus, &fTransDiff2[0], avegagetransversePos2, TDiffSig);
394  }
395  else {
396  fTransDiff1.assign(nClus, avegagetransversePos1);
397  fTransDiff2.assign(nClus, avegagetransversePos2);
398  }
399 
400  // make a collection of electrons for each plane
401  for (size_t p = 0; p < tpcGeo.Nplanes(); ++p) {
402 
403  fDriftClusterPos[driftcoordinate] = tpcGeo.PlaneLocation(p)[driftcoordinate];
404 
405  // Drift nClus electron clusters to the induction plane
406  for (int k = 0; k < nClus; ++k) {
407 
408  // Correct drift time for longitudinal diffusion and plane
409  double TDiff = TDrift + fLongDiff[k] * fRecipDriftVel[0];
410 
411  // Take into account different Efields between planes
412  // Also take into account special case for ArgoNeuT (Nplanes = 2 and drift direction = x): plane 0 is the second wire plane
413  for (size_t ip = 0; ip < p; ++ip) {
414  TDiff +=
415  (tpcGeo.PlaneLocation(ip + 1)[driftcoordinate] -
416  tpcGeo.PlaneLocation(ip)[driftcoordinate]) *
417  fRecipDriftVel[(tpcGeo.Nplanes() == 2 && driftcoordinate == 0) ? ip + 2 : ip + 1];
418  }
419 
420  fDriftClusterPos[transversecoordinate1] = fTransDiff1[k];
421  fDriftClusterPos[transversecoordinate2] = fTransDiff2[k];
422  auto const simTime = energyDeposit.Time();
423  /// \todo think about effects of drift between planes
424  SimDriftedElectronClusterCollection->emplace_back(
425  fnElDiff[k],
426  TDiff + simTime, // timing
427  geo::Point_t{mp.X(), mp.Y(), mp.Z()}, // mean position of the deposited energy
429  fDriftClusterPos[1],
430  fDriftClusterPos[2]}, // final position of the drifted cluster
431  geo::Point_t{
432  LDiffSig, TDiffSig, TDiffSig}, // Longitudinal (X) and transverse (Y,Z) diffusion
433  fnEnDiff[k], //deposited energy that originated this cluster
434  energyDeposit.TrackID());
435 
436  } // end loop over clusters
437  } // end loop over planes
438  } // for each sim::SimEnergyDeposit
439 
440  if (fStoreDriftedElectronClusters) event.put(std::move(SimDriftedElectronClusterCollection));
441  }
442 } // namespace detsim
443 
444 DEFINE_ART_MODULE(detsim::DriftElectronstoPlane)
createEngine fTransverseDiffusion
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
double PlanePitch(unsigned int p1=0, unsigned int p2=1) const
Definition: TPCGeo.cxx:388
Utilities related to art service access.
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.
pdgs p
Definition: selectors.fcl:22
Geometry information for a single TPC.
Definition: TPCGeo.h:38
createEngine fElectronClusterSize
T abs(T value)
createEngine fStoreDriftedElectronClusters
createEngine fLongitudinalDiffusion
Utility function for testing if Space Charge offsets are out of bounds.
Data CalculateIonizationAndScintillation(detinfo::DetectorPropertiesData const &detProp, sim::SimEnergyDeposit const &edep) const
createEngine fGeVToElectrons
void produce(art::Event &evt) override
createEngine fMinNumberOfElCluster
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)
createEngine fUseModBoxRecomb
do i e
TCEvent evt
Definition: DataStructs.cxx:8
pdgs k
Definition: selectors.fcl:22
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
const double * PlaneLocation(unsigned int p) const
Definition: TPCGeo.cxx:382
art framework interface to geometry description
auto const detProp