LArSoft  v10_04_05
Liquid Argon Software toolkit - https://larsoft.org/
SimDriftElectrons_module.cc
Go to the documentation of this file.
1 
48 // LArSoft includes
57 
58 #include "larcoreobj/SimpleTypesAndConstants/RawTypes.h" // raw::ChannelID_t
63 
64 // Framework includes
70 #include "fhiclcpp/ParameterSet.h"
73 
74 // External libraries
75 #include "CLHEP/Random/RandGauss.h"
76 #include "TMath.h"
77 
78 // C++ includes
79 #include <algorithm> // std::find
80 #include <cmath>
81 #include <memory>
82 #include <unordered_map>
83 #include <utility>
84 #include <vector>
85 
86 namespace detsim {
87 
88  // Base class for creation of raw signals on wires.
90  public:
91  explicit SimDriftElectrons(fhicl::ParameterSet const& pset);
92 
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 objects (as of
98  // Oct-2017, this is probably "largeant").
100 
101  CLHEP::RandGauss fRandGauss;
102 
108 
110  double fLDiff_const;
111  double fTDiff_const;
112  double fRecipDriftVel[3];
113 
115 
116  // In order to create the associations, for each channel we create we have to keep
117  // track of its index in the output vector, and the indexes of all the steps that
118  // contributed to it.
120  size_t channelIndex;
121  std::vector<size_t> stepList;
122  };
123 
124  // Define type: channel -> sim::SimChannel's bookkeeping.
125  typedef std::unordered_map<raw::ChannelID_t, ChannelBookKeeping> ChannelMap_t;
126 
127  // Array of maps of channel data indexed by [cryostat,tpc]
128  std::vector<ChannelMap_t> fChannelMaps;
129  // The above ensemble may be thought of as a 3D array of ChannelBookKeepings: e.g.,
130  // SimChannel[cryostat,tpc,channel ID].
131 
132  // Save the number of cryostats, and the number of TPCs within each cryostat.
133  size_t fNCryostats;
134  std::vector<size_t> fNTPCs;
135 
136  // Per-cluster information.
137  std::vector<double> fLongDiff;
138  std::vector<double> fTransDiff1;
139  std::vector<double> fTransDiff2;
140  std::vector<double> fnElDiff;
141  std::vector<double> fnEnDiff;
142 
143  double fDriftClusterPos[3];
144 
146 
147  }; // class SimDriftElectrons
148 
149  //-------------------------------------------------
151  : art::EDProducer{pset}
152  , fSimModuleLabel{pset.get<art::InputTag>("SimulationLabel")}
153  // create a default random engine; obtain the random seed from
154  // NuRandomService, unless overridden in configuration with key
155  // "Seed"
157  -> registerAndSeedEngine(createEngine(0), pset, "Seed")}
158  , fStoreDriftedElectronClusters{pset.get<bool>("StoreDriftedElectronClusters", false)}
159  {
160  produces<std::vector<sim::SimChannel>>();
161  if (fStoreDriftedElectronClusters) { produces<std::vector<sim::SimDriftedElectronCluster>>(); }
162  }
163 
164  //-------------------------------------------------
166  {
167  // Define the physical constants we'll use.
168 
169  auto const detProp =
171  fElectronLifetime = detProp.ElectronLifetime(); // Electron lifetime as returned by the
172  // DetectorProperties service assumed to be in us;
173  for (int i = 0; i < 3; ++i) {
174  double driftVelocity = detProp.DriftVelocity(detProp.Efield(i),
175  detProp.Temperature()) *
176  1.e-3; // Drift velocity as returned by the DetectorProperties service
177  // assumed to be in cm/us. Multiply by 1.e-3 to convert into
178  // LArSoft standard velocity units, cm/ns;
179 
180  fRecipDriftVel[i] = 1. / driftVelocity;
181  }
182 
183  // To-do: Move the parameters we fetch from "LArG4" to detector properties.
185  fElectronClusterSize = paramHandle->ElectronClusterSize();
187  fLongitudinalDiffusion = paramHandle->LongitudinalDiffusion(); // cm^2/ns units
188  fTransverseDiffusion = paramHandle->TransverseDiffusion(); // cm^2/ns units
189 
190  MF_LOG_DEBUG("SimDriftElectrons")
191  << " e lifetime (ns): " << fElectronLifetime
192  << "\n Temperature (K): " << detProp.Temperature()
193  << "\n Drift velocity (cm/ns): " << 1. / fRecipDriftVel[0] << " " << 1. / fRecipDriftVel[1]
194  << " " << 1. / fRecipDriftVel[2];
195 
196  // Opposite of lifetime. Convert from us to standard LArSoft time units, ns;
198  fLDiff_const = std::sqrt(2. * fLongitudinalDiffusion);
199  fTDiff_const = std::sqrt(2. * fTransverseDiffusion);
200 
201  // For this detector's geometry, save the number of cryostats and the number of TPCs
202  // within each cryostat.
204  fNTPCs.resize(fNCryostats);
205  for (size_t n = 0; n < fNCryostats; ++n)
207  }
208 
209  //-------------------------------------------------
211  {
212  // Fetch the SimEnergyDeposit objects for this event.
213  typedef art::Handle<std::vector<sim::SimEnergyDeposit>> energyDepositHandle_t;
214  energyDepositHandle_t energyDepositHandle;
215  // If there aren't any energy deposits for this event, don't panic. It's possible
216  // someone is doing a study with events outside the TPC, or where there are only
217  // non-ionizing particles, or something like that.
218  if (!event.getByLabel(fSimModuleLabel, energyDepositHandle)) return;
219 
220  // Define the container for the SimChannel objects that will be transferred to the
221  // art::Event after the put statement below.
222  std::unique_ptr<std::vector<sim::SimChannel>> channels(new std::vector<sim::SimChannel>);
223  // Container for the SimDriftedElectronCluster objects
224  std::unique_ptr<std::vector<sim::SimDriftedElectronCluster>>
225  SimDriftedElectronClusterCollection(new std::vector<sim::SimDriftedElectronCluster>);
226 
227  // Clear the channel maps from the last event. Remember, fChannelMaps is an
228  // array[cryo][tpc] of maps.
229  size_t cryo = 0;
230  fChannelMaps.resize(fNCryostats);
231  for (auto& cryoData : fChannelMaps) { // each, a vector of maps
232  cryoData.clear();
233  }
234 
235  auto const clockData =
237  auto const& tpcClock = clockData.TPCClock();
238 
239  auto const detProp =
241  // We're going through the input vector by index, rather than by iterator, because we
242  // need the index number to compute the associations near the end of this method.
243  auto const& energyDeposits = *energyDepositHandle;
244  auto energyDepositsSize = energyDeposits.size();
245 
246  // For each energy deposit in this event
247  auto const& wireReadoutGeom = art::ServiceHandle<geo::WireReadout const>()->Get();
248  for (size_t edIndex = 0; edIndex < energyDepositsSize; ++edIndex) {
249  auto const& energyDeposit = energyDeposits[edIndex];
250 
251  // "xyz" is the position of the energy deposit in world coordinates. Note that the
252  // units of distance in sim::SimEnergyDeposit are supposed to be cm.
253  auto const mp = energyDeposit.MidPoint();
254  std::array<double, 3> xyz;
255  mp.GetCoordinates(data(xyz));
256 
257  // From the position in world coordinates, determine the cryostat and tpc. If
258  // somehow the step is outside a tpc (e.g., cosmic rays in rock) just move on to the
259  // next one.
260  unsigned int cryostat = 0;
261  cryostat = fGeometry->PositionToCryostatID(mp).Cryostat;
262  if (cryostat == geo::CryostatID::getInvalidID()) {
263  mf::LogWarning("SimDriftElectrons") << "step " // << energyDeposit << "\n"
264  << "cannot be found in a cryostat\n";
265  continue;
266  }
267 
268  geo::TPCID tpcid;
269  tpcid = fGeometry->PositionToTPCID(mp);
270  if (tpcid.TPC == geo::TPCID::getInvalidID()) {
271  mf::LogWarning("SimDriftElectrons") << "step " // << energyDeposit << "\n"
272  << "cannot be found in a TPC\n";
273  continue;
274  }
275 
276  geo::TPCGeo const& tpcGeo = fGeometry->TPC(tpcid);
277  unsigned int const nplanes = wireReadoutGeom.Nplanes(tpcid);
278 
279  // The drift direction can be either in the positive or negative direction in any
280  // coordinate x, y or z.
281 
282  // Define charge drift direction: driftcoordinate (x, y or z) and driftsign
283  // (positive or negative). Also define coordinates perpendicular to drift direction.
284 
285  auto const [axis, sign] = tpcGeo.DriftAxisWithSign();
286 
287  // For axis == geo::Coordinate::X
288  int driftcoordinate = 0;
289  int transversecoordinate1 = 1;
290  int transversecoordinate2 = 2;
291  if (axis == geo::Coordinate::Y) {
292  transversecoordinate1 = 0;
293  driftcoordinate = 1;
294  transversecoordinate2 = 2;
295  }
296  else if (axis == geo::Coordinate::Z) {
297  transversecoordinate1 = 0;
298  transversecoordinate2 = 1;
299  driftcoordinate = 2;
300  }
301 
302  int const driftsign = to_int(sign); // 1: +x, +y or +z, -1: -x, -y or -z
303 
304  // Check for charge deposits behind charge readout planes
305  auto const& plane_location = wireReadoutGeom.FirstPlane(tpcid).GetCenter();
306  auto const plane_location_coord = geo::vect::coord(plane_location, driftcoordinate);
307  if (driftsign == 1 && plane_location_coord < xyz[driftcoordinate]) continue;
308  if (driftsign == -1 && plane_location_coord > xyz[driftcoordinate]) continue;
309 
311  // Center of plane is also returned in cm units
312  double DriftDistance = std::abs(xyz[driftcoordinate] - plane_location_coord);
313 
314  // Space-charge effect (SCE): Get SCE {x,y,z} offsets for particular location in TPC
315  geo::Vector_t posOffsets{0.0, 0.0, 0.0};
316  double posOffsetxyz[3] = {0.0, 0.0, 0.0}; // need this array for the driftcoordinate and
317  // transversecoordinates
318  auto const* SCE = lar::providerFrom<spacecharge::SpaceChargeService>();
319  if (SCE->EnableSimSpatialSCE() == true) {
320  posOffsets = SCE->GetPosOffsets(mp);
321  if (larsim::Utils::SCE::out_of_bounds(posOffsets)) { continue; }
322  posOffsetxyz[0] = posOffsets.X();
323  posOffsetxyz[1] = posOffsets.Y();
324  posOffsetxyz[2] = posOffsets.Z();
325  }
326 
327  double avegagetransversePos1 = 0.;
328  double avegagetransversePos2 = 0.;
329 
330  DriftDistance += -1. * posOffsetxyz[driftcoordinate];
331  avegagetransversePos1 = xyz[transversecoordinate1] + posOffsetxyz[transversecoordinate1];
332  avegagetransversePos2 = xyz[transversecoordinate2] + posOffsetxyz[transversecoordinate2];
333 
334  // Space charge distortion could push the energy deposit beyond the wire plane (see
335  // issue #15131). Given that we don't have any subtlety in the simulation of this
336  // region, bringing the deposit exactly on the plane should be enough for the time
337  // being.
338  if (DriftDistance < 0.) DriftDistance = 0.;
339 
340  // Drift time in ns
341  double TDrift = DriftDistance * fRecipDriftVel[0];
342 
343  if (nplanes == 2 &&
344  driftcoordinate == 0) { // special case for ArgoNeuT (Nplanes = 2 and drift direction =
345  // x): plane 0 is the second wire plane
346  double const pitch = wireReadoutGeom.PlanePitch(tpcid, 0, 1);
347  TDrift = ((DriftDistance - pitch) * fRecipDriftVel[0] + pitch * fRecipDriftVel[1]);
348  }
349 
350  const int nIonizedElectrons = energyDeposit.NumElectrons();
351  const double lifetimecorrection = TMath::Exp(TDrift / fLifetimeCorr_const);
352  const double energy = energyDeposit.Energy();
353 
354  // if we have no electrons (too small energy or too large recombination)
355  // we are done already here
356  if (nIonizedElectrons <= 0) {
357  MF_LOG_DEBUG("SimDriftElectrons")
358  << "step " // << energyDeposit << "\n"
359  << "No electrons drifted to readout, " << energy << " MeV lost.";
360  continue;
361  }
362 
363  // includes the effect of lifetime: lifetimecorrection = exp[-tdrift/tau]
364  const double nElectrons = nIonizedElectrons * lifetimecorrection;
365 
366  // Longitudinal & transverse diffusion sigma (cm)
367  double SqrtT = std::sqrt(TDrift);
368  double LDiffSig = SqrtT * fLDiff_const;
369  double TDiffSig = SqrtT * fTDiff_const;
370  double electronclsize = fElectronClusterSize;
371 
372  // Number of electron clusters.
373  int nClus = (int)std::ceil(nElectrons / electronclsize);
374  if (nClus < fMinNumberOfElCluster) {
375  electronclsize = nElectrons / fMinNumberOfElCluster;
376  if (electronclsize < 1.0) { electronclsize = 1.0; }
377  nClus = (int)std::ceil(nElectrons / electronclsize);
378  }
379 
380  // Empty and resize the electron-cluster vectors.
381  fLongDiff.clear();
382  fTransDiff1.clear();
383  fTransDiff2.clear();
384  fnElDiff.clear();
385  fnEnDiff.clear();
386  fLongDiff.resize(nClus);
387  fTransDiff1.resize(nClus);
388  fTransDiff2.resize(nClus);
389  fnElDiff.resize(nClus, electronclsize);
390  fnEnDiff.resize(nClus);
391 
392  // fix the number of electrons in the last cluster, that has a smaller size
393  fnElDiff.back() = nElectrons - (nClus - 1) * electronclsize;
394 
395  for (size_t xx = 0; xx < fnElDiff.size(); ++xx) {
396  if (nElectrons > 0)
397  fnEnDiff[xx] = energy / nElectrons * fnElDiff[xx];
398  else
399  fnEnDiff[xx] = 0.;
400  }
401 
402  // Smear drift times by longitudinal diffusion
403  if (LDiffSig > 0.0)
404  fRandGauss.fireArray(nClus, &fLongDiff[0], 0., LDiffSig);
405  else
406  fLongDiff.assign(nClus, 0.0);
407 
408  if (TDiffSig > 0.0) {
409  // Smear the coordinates in plane perpendicular to drift direction by the transverse diffusion
410  fRandGauss.fireArray(nClus, &fTransDiff1[0], avegagetransversePos1, TDiffSig);
411  fRandGauss.fireArray(nClus, &fTransDiff2[0], avegagetransversePos2, TDiffSig);
412  }
413  else {
414  fTransDiff1.assign(nClus, avegagetransversePos1);
415  fTransDiff2.assign(nClus, avegagetransversePos2);
416  }
417 
418  // make a collection of electrons for each plane
419  for (unsigned int p = 0; p < nplanes; ++p) {
420  // FIXME (KJK): Shouldn't this be the location for each plane we're iterating
421  // through? Right now it uses whatever value of plane_location_coord was set
422  // above.
423  fDriftClusterPos[driftcoordinate] = plane_location_coord;
424 
425  // Drift nClus electron clusters to the induction plane
426  for (int k = 0; k < nClus; ++k) {
427 
428  // Correct drift time for longitudinal diffusion and plane
429  double TDiff = TDrift + fLongDiff[k] * fRecipDriftVel[0];
430 
431  // Take into account different Efields between planes. Also take into account
432  // special case for ArgoNeuT (Nplanes = 2 and drift direction = x): plane 0 is
433  // the second wire plane
434  for (unsigned int ip = 0; ip < p; ++ip) {
435  TDiff += wireReadoutGeom.PlanePitch(tpcid, ip + 1, ip) *
436  fRecipDriftVel[(nplanes == 2 && driftcoordinate == 0) ? ip + 2 : ip + 1];
437  }
438 
439  fDriftClusterPos[transversecoordinate1] = fTransDiff1[k];
440  fDriftClusterPos[transversecoordinate2] = fTransDiff2[k];
441 
443 
444  // grab the nearest channel to the fDriftClusterPos position
445  try {
446  raw::ChannelID_t channel = wireReadoutGeom.NearestChannel(
447  geo::vect::toPoint(fDriftClusterPos), geo::PlaneID{tpcid, p});
448 
451  // Add potential decay/capture/etc delay effect, simTime.
452  auto const simTime = energyDeposit.Time();
453  unsigned int tdc = tpcClock.Ticks(clockData.G4ToElecTime(TDiff + simTime));
454 
455  // Find whether we already have this channel in our map.
456  ChannelMap_t& channelDataMap = fChannelMaps[cryostat];
457  auto search = channelDataMap.find(channel);
458 
459  // We will find (or create) the pointer to a sim::SimChannel.
460  size_t channelIndex = 0;
461 
462  // Have we created the sim::SimChannel corresponding to channel ID?
463  if (search == channelDataMap.end()) {
464  // We haven't. Initialize the bookkeeping information for this channel.
465  ChannelBookKeeping bookKeeping;
466 
467  // Add a new channel to the end of the list we'll write out after we've
468  // processed this event.
469  bookKeeping.channelIndex = channels->size();
470  channels->emplace_back(channel);
471  channelIndex = bookKeeping.channelIndex;
472 
473  // Initialize a vector with the index of the step that created this channel.
474  bookKeeping.stepList.push_back(edIndex);
475 
476  // Save the bookkeeping information for this channel.
477  channelDataMap[channel] = bookKeeping;
478  }
479  else {
480  // We've created this SimChannel for a previous energy deposit. Get its
481  // address.
482 
483  auto& bookKeeping = search->second;
484  channelIndex = bookKeeping.channelIndex;
485 
486  // Has this step contributed to this channel before?
487  auto& stepList = bookKeeping.stepList;
488  if (!std::binary_search(stepList.begin(), stepList.end(), edIndex)) {
489  // No, so add this step's index to the list.
490  stepList.push_back(edIndex);
491  }
492  }
493 
494  sim::SimChannel* channelPtr = &(channels->at(channelIndex));
495 
496  // Add the electron clusters and energy to the
497  // sim::SimChannel
498  channelPtr->AddIonizationElectrons(energyDeposit.TrackID(),
499  tdc,
500  fnElDiff[k],
501  data(xyz),
502  fnEnDiff[k],
503  energyDeposit.OrigTrackID());
505  SimDriftedElectronClusterCollection->emplace_back(
506  fnElDiff[k],
507  TDiff + simTime, // timing
508  geo::Point_t{mp.X(), mp.Y(), mp.Z()}, // mean position of the deposited energy
509  geo::Point_t{fDriftClusterPos[0],
510  fDriftClusterPos[1],
511  fDriftClusterPos[2]}, // final position of the drifted cluster
512  geo::Point_t{
513  LDiffSig, TDiffSig, TDiffSig}, // Longitudinal (X) and transverse (Y,Z) diffusion
514  fnEnDiff[k], // deposited energy that originated this cluster
515  energyDeposit.TrackID());
516  }
517  catch (cet::exception& e) {
518  mf::LogDebug("SimDriftElectrons")
519  << "unable to drift electrons from point (" << xyz[0] << "," << xyz[1] << ","
520  << xyz[2] << ") with exception " << e;
521  } // end try to determine channel
522  } // end loop over clusters
523  } // end loop over planes
524  } // for each sim::SimEnergyDeposit
525 
526  // Write the sim::SimChannel collection.
527  event.put(std::move(channels));
528  if (fStoreDriftedElectronClusters) event.put(std::move(SimDriftedElectronClusterCollection));
529  }
530 
531 } // namespace detsim
532 
base_engine_t & createEngine(seed_t seed)
Store parameters for running LArG4.
Double_t xx
Definition: macro.C:12
Utilities related to art service access.
Energy deposited on a readout channel by simulated tracks.
Definition: SimChannel.h:136
contains objects relating to SimDriftedElectronCluster
ROOT::Math::DisplacementVector3D< ROOT::Math::Cartesian3D< double >, ROOT::Math::GlobalCoordinateSystemTag > Vector_t
Type for representation of momenta in 3D space.
Definition: geo_vectors.h:160
bool out_of_bounds(geo::Vector_t const &offset)
auto coord(Vector &v, unsigned int n) noexcept
Returns an object to manage the coordinate n of a vector.
unsigned int NTPC(CryostatID const &cryoid=details::cryostat_zero) const
Returns the total number of TPCs in the specified cryostat.
Definition: GeometryCore.h:416
EDProducer(fhicl::ParameterSet const &pset)
Definition: EDProducer.cc:6
The data type to uniquely identify a Plane.
Definition: geo_types.h:364
Geometry information for a single TPC.
Definition: TPCGeo.h:33
Detector simulation of raw signals on wires.
constexpr auto abs(T v)
Returns the absolute value of the argument.
CryostatID_t Cryostat
Index of cryostat.
Definition: geo_types.h:195
cout<< "Opened file "<< fin<< " ixs= "<< ixs<< endl;if(ixs==0) hhh=(TH1F *) fff-> Get("h1")
Definition: AddMC.C:8
unsigned int Ncryostats() const
Returns the number of cryostats in the detector.
Definition: GeometryCore.h:303
DriftAxis DriftAxisWithSign() const
Returns the expected drift direction based on geometry.
Definition: TPCGeo.h:78
art::ServiceHandle< geo::Geometry const > fGeometry
Handle to the Geometry service.
static constexpr CryostatID_t getInvalidID()
Return the value of the invalid ID as a r-value.
Definition: geo_types.h:244
double TransverseDiffusion() const
static constexpr TPCID_t getInvalidID()
Return the value of the invalid TPC ID as a r-value.
Definition: geo_types.h:359
#define DEFINE_ART_MODULE(klass)
Definition: ModuleMacros.h:65
Utility function for testing if Space Charge offsets are out of bounds.
double ElectronClusterSize() const
double energy
Definition: plottest35.C:25
constexpr int to_int(Coordinate const coord) noexcept
Enumerate the possible plane projections.
Definition: geo_types.h:124
Point_t toPoint(Point const &p)
Convert the specified point into a geo::Point_t.
void produce(art::Event &evt) override
The data type to uniquely identify a TPC.
Definition: geo_types.h:306
An art service to assist in the distribution of guaranteed unique seeds to all engines within an art ...
std::vector< ChannelMap_t > fChannelMaps
int sign(double val)
Definition: UtilFunc.cxx:104
CryostatID PositionToCryostatID(Point_t const &point) const
Returns the ID of the cryostat at specified location.
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:180
bool getByLabel(std::string const &label, std::string const &instance, Handle< PROD > &result) const
int MinNumberOfElCluster() const
MaybeLogger_< ELseverityLevel::ELsev_success, false > LogDebug
contains information for a single step in the detector simulation
MaybeLogger_< ELseverityLevel::ELsev_warning, false > LogWarning
#define MF_LOG_DEBUG(id)
object containing MC truth information necessary for making RawDigits and doing back tracking ...
Definition: MVAAlg.h:12
Char_t n[5]
void AddIonizationElectrons(TrackID_t trackID, TDC_t tdc, double numberElectrons, double const *xyz, double energy, TrackID_t origTrackID=util::kBogusI)
Add ionization electrons and energy to this channel.
Definition: SimChannel.cxx:49
double LongitudinalDiffusion() const
std::unordered_map< raw::ChannelID_t, ChannelBookKeeping > ChannelMap_t
TCEvent evt
Definition: DataStructs.cxx:8
unsigned int ChannelID_t
Type representing the ID of a readout channel.
Definition: RawTypes.h:28
TPCID_t TPC
Index of the TPC within its cryostat.
Definition: geo_types.h:315
TPCGeo const & TPC(TPCID const &tpcid=details::tpc_zero) const
Returns the specified TPC.
Definition: GeometryCore.h:448
Float_t e
Definition: plot.C:35
TPCID PositionToTPCID(Point_t const &point) const
Returns the ID of the TPC at specified location.
art framework interface to geometry description
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
SimDriftElectrons(fhicl::ParameterSet const &pset)
Event finding and building.
The data type to uniquely identify a cryostat.
Definition: geo_types.h:187