LArG4_module.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 /// \file LArG4_module.cc
3 /// \brief Use Geant4 to run the LArSoft detector simulation
4 ///
5 /// \author seligman@nevis.columbia.edu
6 ////////////////////////////////////////////////////////////////////////
7 
8 /// This a module. It has the following functions:
9 ///
10 /// - Initialize Geant4 physics, detector geometry, and other
11 /// processing.
12 ///
13 /// - Accept sim::MCTruth objects from the MC branch of the FMWK
14 /// Event structure.
15 ///
16 /// - Pass the primary particles to the Geant4 simulation to calculate
17 /// "truth" information for the detector response.
18 ///
19 /// - Pass the truth information to the DetSim branch of the FMWK event.
20 
21 #include "nug4/G4Base/G4Helper.h"
22 
23 // C++ Includes
24 #include <cassert>
25 #include <map>
26 #include <set>
27 #include <sstream>
28 #include <sys/stat.h>
29 
30 // Framework includes
40 #include "cetlib/search_path.h"
41 #include "cetlib_except/exception.h"
42 #include "fhiclcpp/ParameterSet.h"
44 
45 // art extensions
46 #include "nurandom/RandomUtils/NuRandomService.h"
47 
48 // LArSoft Includes
50 #include "larcorealg/CoreUtils/ParticleFilters.h" // util::PositionInVolumeFilter
53 #include "lardataalg/MCDumpers/MCDumpers.h" // sim::dump namespace
73 #include "nug4/G4Base/UserActionManager.h"
74 #include "nug4/ParticleNavigation/ParticleList.h"
76 
77 // G4 Includes
78 #include "Geant4/G4LogicalVolumeStore.hh"
79 #include "Geant4/G4RunManager.hh"
80 #include "Geant4/G4SDManager.hh"
81 #include "Geant4/G4VSensitiveDetector.hh"
82 #include "Geant4/G4VUserDetectorConstruction.hh"
83 
84 //For energy depositions
86 
87 // Boost includes
88 #include "boost/algorithm/string.hpp"
89 
90 ///Geant4 interface
91 namespace larg4 {
92 
93  // Forward declarations within namespace.
94  class LArVoxelListAction;
95 
96  /**
97  * @brief Runs Geant4 simulation and propagation of electrons and photons to readout
98  *
99  * This module collects generated particles from one or more generators and
100  * processes them through Geant4.
101  *
102  * Input
103  * ------
104  *
105  * The module reads the particles to process from `simb::MCTruth` records.
106  * Each particle generator is required to produce a vector of such records:
107  * `std::vector<simb::MCTruth>`.
108  *
109  * The module allows two operation modes:
110  * -# process specific generators: the label of the generator modules to be
111  * processed is specified explicitly in `LArG4` configuration
112  * -# process all truth information generated so far: no generator is specified
113  * in the `LArG4` module configuration, and the module will process all
114  * data products of type `std::vector<simb::MCTruth>`, in a non-specified
115  * order
116  *
117  * For each `simb::MCTruth`, a Geant4 run is started.
118  * The interface with Geant4 is via a helper class provided by _nug4_.
119  * Only the particles in the truth record which have status code
120  * (`simb::MCParticle::StatusCode()`) equal to `1` are processed.
121  * These particles are called, in `LArG4` jargon, _primaries_.
122  *
123  *
124  * Output
125  * -------
126  *
127  * The `LArG4` module produces:
128  * * a collection of `sim::SimChannel`: each `sim::SimChannel` represents the
129  * set of energy depositions in liquid argon which drifted and were observed
130  * on a certain channel; it includes physics effects like attenuation,
131  * diffusion, electric field distortion, etc. Information of the generating
132  * Geant4 "track" is retained;
133  * * a collection of `sim::SimPhotons` or `sim::SimPhotonsLite`: each
134  * `sim::SimPhotons` represents the set of individual photons reaching a
135  * channel of the optical detector; it includes physics effects as well as
136  * quantum efficiency of the detector (to reduce data size early in the
137  * process); `sim::SimPhotonsLite` drops the information of the single
138  * photons and stores only collective information (e.g. their number).
139  * * a collection of `sim::OpDetBacktrackerRecord` (to be documented)
140  * * a collection of `sim::AuxDetSimChannel` (to be documented)
141  * * a collection of `simb::MCParticle`: the particles generated in the
142  * interaction of the primary particles with the material in the world
143  * are stored, but minor filtering by geometry and by physics is possible.
144  * An association of them with the originating `simb::MCTruth` object is
145  * also produced.
146  *
147  *
148  * Notes on the conventions
149  * -------------------------
150  *
151  * * all and the particles in the truth record (`simb::MCTruth`) which have
152  * status code (`simb::MCParticle::StatusCode()`) equal to `1` are passed
153  * to Geant4. These particles are called, in `LArG4` jargon, _primaries_.
154  * The interface with Geant4 is via a helper class provided by _nug4_.
155  * * normally, information about each particle that Geant4 propagates (which
156  * Geant4 calls _tracks_), primary or not, is saved as an individual
157  * `simb::MCParticle` object into the output particle list. Each
158  * `simb::MCParticle` includes a Geant4-like track ID which is also recorded
159  * into each `sim::IDE` deposited by that particle. This information can be
160  * used to track all the deposition from a particle, or to backtrack the
161  * particle responsible of a deposition (but see below...).
162  * Note that the stored track ID may be different than the one Geant4 used
163  * (and, in particular, it's guaranteed to be unique within a `sim::LArG4`
164  * instance output).
165  * * there are options (some set in `sim::LArG4Parameters` service) which
166  * allow for Geant4 tracks not to be saved as `simb::MCParticle` (e.g.
167  * `ParticleKineticEnergyCut`, `KeepEMShowerDaughters`). When these
168  * particles have deposited energy, their `sim::IDE` will report the ID of
169  * the first parent Geant4 track which is saved in the `simb::MCParticle`
170  * list, but _with its sign flipped_. Therefore, when tracking or
171  * backtracking (see above), comparisons should be performed using the
172  * absolute value of the `sim::IDE` (e.g. `std::abs(ide.trackID)`).
173  *
174  *
175  * Timing
176  * -------
177  *
178  * The `LArG4` module produces `sim::SimChannel` objects from generated
179  * `simb::MCParticle`. Each particle ("primary") is assigned the time taken
180  * from its vertex (a 4-vector), which is expected to be represented in
181  * nanoseconds.
182  * The `sim::SimChannel` object is a collection of `sim::IDE` in time. The
183  * position in the `sim::IDE` is the location where some ionization occurred.
184  * The time associated to a `sim::IDE` is stored in tick units. The time it
185  * represents is the time when the ionization happened, which is the time of
186  * the primary particle plus the propagation time to the ionization location,
187  * plus the drift time, which the ionized electrons take to reach the anode
188  * wire. This time is then shifted to the frame of the electronics time
189  * via `detinfo::DetectorClocks::G4ToElecTime()`, which adds a configurable
190  * time offset. The time is converted into ticks via
191  * `detinfo::DetectorClocks::TPCClock()`, and this is the final value
192  * associated to the `sim::IDE`. For a more complete overview, see
193  * https://cdcvs.fnal.gov/redmine/projects/larsoft/wiki/Simulation#Simulation-Timing
194  *
195  *
196  * Randomness
197  * -----------
198  *
199  * The random number generators used by this process are:
200  * - 'GEANT' instance: used by Geant4
201  * - 'propagation' instance: used in electron propagation
202  *
203  *
204  * Configuration parameters
205  * -------------------------
206  *
207  * - *G4PhysListName* (string, default: `"larg4::PhysicsList"`):
208  * whether to use the G4 overlap checker, which catches different issues than ROOT
209  * - *CheckOverlaps* (bool, default: `false`):
210  * whether to use the G4 overlap checker
211  * - *DumpParticleList* (bool, default: `false`):
212  * whether to print all MCParticles tracked;
213  * requires `MakeMCParticles` being `true`
214  * - *DumpSimChannels* (bool, default: `false`):
215  * whether to print all depositions on each SimChannel
216  * - *SmartStacking* (int, default: `0`):
217  * whether to use class to dictate how tracks are put on stack (nonzero is on)
218  * - *MakeMCParticles* (flag, default: `true`): keep a list of the particles
219  * seen in the detector, and eventually save it; you almost always want this on
220  * - *KeepParticlesInVolumes* (list of strings, default: _empty_):
221  * list of volumes in which to keep `simb::MCParticle` objects (empty keeps all);
222  * requires `MakeMCParticles` being `true`
223  * - *GeantCommandFile* (string, _required_):
224  * G4 macro file to pass to `G4Helper` for setting G4 command
225  * - *Seed* (integer, not defined by default): if defined, override the seed for
226  * random number generator used in Geant4 simulation (which is obtained from
227  * `NuRandomService` by default)
228  * - *PropagationSeed* (integer, not defined by default): if defined,
229  * override the seed for the random generator used for electrons propagation
230  * to the wire planes (obtained from the `NuRandomService` by default)
231  * - *InputLabels* (list of strings, default: process all truth):
232  * optional list of generator labels whose produced `simb::MCTruth` will
233  * be simulated; if not specified, all `simb::MCTruth` vector data
234  * products are simulated
235  * - *ChargeRecoveryMargin* (double, default: `0`): sets the maximum
236  * distance from a plane for the wire charge recovery to occur, in
237  * centimeters; for details on how it works, see
238  * `larg4::LArVoxelReadout::SetOffPlaneChargeRecoveryMargin()`. A value of
239  * `0` effectively disables this feature. All TPCs will have the same
240  * margin applied.
241  *
242  *
243  * Simulation details
244  * ===================
245  *
246  * Source of the operational parameters
247  * -------------------------------------
248  *
249  * @anchor LArG4_MaterialProperties
250  *
251  * Some of the physical properties have their values set in FHiCL
252  * configuration (e.g. `detinfo::LArParameters`). Then, GEANT4 is informed
253  * of them via `larg4::MaterialPropertyLoader`. The material property table
254  * in GEANT4 is then used by other LArSoft components to discover the
255  * parameter values.
256  *
257  * Among the parameters registered to GEANT4, the scintillation yields, i.e.
258  * how many scintillation photons are produced on average by 1 MeV of
259  * deposited energy, are also stored by type of ioniziong particle.
260  * These scintillation yields _do include a prescale factor_ (that may
261  * include, for example, the photomultiplier quantum efficiency), from the
262  * `ScintPreScale` parameter of `detinfo::LArPropertiesStandard`
263  * or equivalent.
264  *
265  *
266  * Reflectivity to optical photons
267  * --------------------------------
268  *
269  * Two models are supported for the simulation of (scintillation) light
270  * crossing detector surfaces:
271  * -# the standard one from GEANT4, implemented in `G4OpBoundaryProcess`
272  * -# a simplified one, implemented in `larg4::OpBoundaryProcessSimple`
273  *
274  * The model is chosen according to the value of
275  * `detinfo::DetectorProperties::SimpleBoundary()`, and the choice is
276  * currently exerted by `larg4::OpticalPhysics`.
277  *
278  * The simplified model is faster and simpler: it only deals with absorption
279  * and reflection (both specular and diffues).
280  * This is the "default" model used in most contexts.
281  *
282  * GEANT4 model is more complete and slower. It may take some art to fully
283  * configure all the properties of the materials at the sides of the surfaces.
284  * The price is a detailed simulation that includes among others refraction
285  * and wavelength shifting.
286  *
287  *
288  * Scintillation
289  * --------------
290  *
291  * When using the fast optical simulation, which is the "standard" running
292  * mode, energy depositions from GEANT4 are "converted" into a number of
293  * scintillation photons by the global `larg4::IonizationAndScintillation`
294  * object instance, which internally utilizes the algorithm set up via
295  * configuration parameter `IonAndScintCalculator` in `LArG4Parameters`
296  * service (at the time of writing, `"Separate"` is supported and `"NEST"` is
297  * accepted too).
298  * The number of scintillation photons per energy unit is read from GEANT4
299  * @ref LArG4_MaterialProperties "material properties table". It includes
300  * already quantum efficiency ("prescale") and it may depend on the type of
301  * ionizing particle, depending on the configuration (`LArPropertiesStandard`
302  * parameter `ScintByParticleType`). This value ("yield") is used as
303  * the average of a Poisson distribution from which the actual number of
304  * scintillation photons is extracted case by case.
305  * The implementation `larg4::ISCalculationSeparate` may also include medium
306  * saturation effects as well, if configured, but only if the scintillation
307  * yield is set not to depend on the type of ionizing particle.
308  * The number of scintillation photons is then distributed between the fast
309  * and slow component by a yield ratio also set in the material parameters,
310  * and the single photons are distributed in time accordingly to their
311  * component.
312  *
313  */
314  class LArG4 : public art::EDProducer {
315  public:
316  explicit LArG4(fhicl::ParameterSet const& pset);
317 
318  private:
319  /// The main routine of this module: Fetch the primary particles
320  /// from the event, simulate their evolution in the detector, and
321  /// produce the detector response.
322  void produce(art::Event& evt) override;
323  void beginJob() override;
324  void beginRun(art::Run& run) override;
325 
326  std::unique_ptr<g4b::G4Helper> fG4Help{nullptr}; ///< G4 interface object
328  nullptr}; ///< Geant4 user action to particle information.
329 
330  std::string fG4PhysListName; ///< predefined physics list to use if not making a custom one
331  std::string fG4MacroPath; ///< directory path for Geant4 macro file to be
332  ///< executed before main MC processing.
333  bool fCheckOverlaps; ///< Whether to use the G4 overlap checker
334  bool fMakeMCParticles; ///< Whether to keep a `sim::MCParticle` list
335  bool fdumpParticleList; ///< Whether each event's sim::ParticleList will be displayed.
336  bool fdumpSimChannels; ///< Whether each event's sim::Channel will be displayed.
338  bool fStoreReflected{false};
339  int fSmartStacking; ///< Whether to instantiate and use class to
340  double fOffPlaneMargin = 0.; ///< Off-plane charge recovery margin
341  ///< dictate how tracks are put on stack.
342  std::vector<std::string> fInputLabels;
343  std::vector<std::string>
344  fKeepParticlesInVolumes; ///<Only write particles that have trajectories through these volumes
345 
346  bool fSparsifyTrajectories; ///< Sparsify MCParticle Trajectories
347 
348  CLHEP::HepRandomEngine& fEngine; ///< Random-number engine for IonizationAndScintillation
349  ///< initialization
350 
351  detinfo::DetectorPropertiesData fDetProp; ///< Must outlive fAllPhysicsLists!
354  nullptr}; /// Pointer used for correctly updating the clock data state.
355 
356  /// Configures and returns a particle filter
357  std::unique_ptr<util::PositionInVolumeFilter> CreateParticleVolumeFilter(
358  std::set<std::string> const& vol_names) const;
359  };
360 
361 } // namespace LArG4
362 
363 namespace {
364 
365  // ---------------------------------------------------------------------------
366  /**
367  * @brief Moves data from `source` to the end of `dest`.
368  * @tparam T type of values in the containers
369  * @param dest collection to append data to
370  * @param source collection to take data from
371  * @return a reference to `dest`
372  *
373  * The data contained in `source` is moved (appended) to the end of `dest`.
374  *
375  * The data is moved from source element by element; as an exception, if
376  * `dest` is still empty, the data is moved in a block.
377  *
378  * The `source` collection is always returned depleted as after being "moved
379  * away" (i.e. after `auto temp = std::move(source);`): that means that the
380  * only thing that can be done with it according to C++ standard is to
381  * destruct it.
382  *
383  */
384  template <typename T>
385  std::vector<T>&
386  append(std::vector<T>& dest, std::vector<T>&& source)
387  {
388  if (empty(dest))
389  dest = std::move(source);
390  else {
391  dest.insert(dest.end(), std::move_iterator{begin(source)}, std::move_iterator{end(source)});
392  source = std::vector<T>{}; // ensure the old memory is released
393  }
394  return dest;
395  }
396  // ---------------------------------------------------------------------------
397 
398 } // local namespace
399 
400 namespace larg4 {
401 
402  //----------------------------------------------------------------------
403  // Constructor
405  : art::EDProducer{pset}
406  , fG4PhysListName(pset.get<std::string>("G4PhysListName", "larg4::PhysicsList"))
407  , fCheckOverlaps(pset.get<bool>("CheckOverlaps", false))
408  , fMakeMCParticles(pset.get<bool>("MakeMCParticles", true))
409  , fdumpParticleList(pset.get<bool>("DumpParticleList", false))
410  , fdumpSimChannels(pset.get<bool>("DumpSimChannels", false))
411  , fSmartStacking(pset.get<int>("SmartStacking", 0))
412  , fOffPlaneMargin(pset.get<double>("ChargeRecoveryMargin", 0.0))
413  , fKeepParticlesInVolumes(pset.get<std::vector<std::string>>("KeepParticlesInVolumes", {}))
414  , fSparsifyTrajectories(pset.get<bool>("SparsifyTrajectories", false))
416  ->createEngine(*this, "HepJamesRandom", "propagation", pset, "PropagationSeed"))
419  {
420  MF_LOG_DEBUG("LArG4") << "Debug: LArG4()";
422 
423  if (!fMakeMCParticles) { // configuration option consistency
424  if (fdumpParticleList) {
426  << "Option `DumpParticleList` can't be set if `MakeMCParticles` is unset.\n";
427  }
428  if (!fKeepParticlesInVolumes.empty()) {
430  << "Option `KeepParticlesInVolumes` can't be set if `MakeMCParticles` is unset.\n";
431  }
432  } // if
433 
434  if (pset.has_key("Seed")) {
436  << "The configuration of LArG4 module has the discontinued 'Seed' parameter.\n"
437  "Seeds are now controlled by two parameters: 'GEANTSeed' and 'PropagationSeed'.";
438  }
439  // setup the random number service for Geant4, the "G4Engine" label is a
440  // special tag setting up a global engine for use by Geant4/CLHEP;
441  // obtain the random seed from NuRandomService,
442  // unless overridden in configuration with key "Seed" or "GEANTSeed"
443  // FIXME: THIS APPEARS TO BE A NO-OP; IS IT NEEDED?
444  (void)art::ServiceHandle<rndm::NuRandomService>()->createEngine(
445  *this, "G4Engine", "GEANT", pset, "GEANTSeed");
446 
447  //get a list of generators to use, otherwise, we'll end up looking for anything that's
448  //made an MCTruth object
449  bool useInputLabels =
450  pset.get_if_present<std::vector<std::string>>("InputLabels", fInputLabels);
451  if (!useInputLabels) fInputLabels.resize(0);
452 
455 
456  if (!lgp->NoPhotonPropagation()) {
457  try {
460  }
461  catch (art::Exception const& e) {
462  // If the service is not configured, then just keep the default
463  // false for reflected light. If reflected photons are simulated
464  // without PVS they will show up in the regular SimPhotons collection
465  if (e.categoryCode() != art::errors::ServiceNotFound) throw;
466  }
467 
468  if (!fUseLitePhotons) {
469  produces<std::vector<sim::SimPhotons>>();
470  if (fStoreReflected) { produces<std::vector<sim::SimPhotons>>("Reflected"); }
471  }
472  else {
473  produces<std::vector<sim::SimPhotonsLite>>();
474  produces<std::vector<sim::OpDetBacktrackerRecord>>();
475  if (fStoreReflected) {
476  produces<std::vector<sim::SimPhotonsLite>>("Reflected");
477  produces<std::vector<sim::OpDetBacktrackerRecord>>("Reflected");
478  }
479  }
480  }
481 
482  if (lgp->FillSimEnergyDeposits()) {
483  produces<std::vector<sim::SimEnergyDeposit>>("TPCActive");
484  produces<std::vector<sim::SimEnergyDeposit>>("Other");
485  }
486 
487  if (fMakeMCParticles) {
488  produces<std::vector<simb::MCParticle>>();
489  produces<art::Assns<simb::MCTruth, simb::MCParticle, sim::GeneratedParticleInfo>>();
490  }
491  if (!lgp->NoElectronPropagation()) produces<std::vector<sim::SimChannel>>();
492  produces<std::vector<sim::AuxDetSimChannel>>();
493 
494  // constructor decides if initialized value is a path or an environment variable
495  cet::search_path sp("FW_SEARCH_PATH");
496 
497  sp.find_file(pset.get<std::string>("GeantCommandFile"), fG4MacroPath);
498  struct stat sb;
499  if (fG4MacroPath.empty() || stat(fG4MacroPath.c_str(), &sb) != 0)
500  // failed to resolve the file name
501  throw cet::exception("NoG4Macro") << "G4 macro file " << fG4MacroPath << " not found!\n";
502  }
503 
504  //----------------------------------------------------------------------
505  void
507  {
508  fG4Help = std::make_unique<g4b::G4Helper>(fG4MacroPath, fG4PhysListName);
509 
510  if (fCheckOverlaps) fG4Help->SetOverlapCheck(true);
511 
513  fG4Help->ConstructDetector(geom->GDMLFile());
514 
515  // Get the logical volume store and assign material properties
517  auto const detProp =
519  mpl.GetPropertiesFromServices(detProp);
520  mpl.UpdateGeometry(G4LogicalVolumeStore::GetInstance());
521 
522  // Tell the detector about the parallel LAr voxel geometry.
523  std::vector<G4VUserParallelWorld*> pworlds;
524 
525  // Intialize G4 physics and primary generator action
526  fG4Help->InitPhysics();
527 
528  // create the ionization and scintillation calculator;
529  // this is a singleton (!) so it does not make sense
530  // to create it in LArVoxelReadoutGeometry
532 
533  // make a parallel world for each TPC in the detector
534  LArVoxelReadoutGeometry::Setup_t readoutGeomSetupData;
535  readoutGeomSetupData.readoutSetup.offPlaneMargin = fOffPlaneMargin;
536  readoutGeomSetupData.readoutSetup.propGen = &fEngine;
537 
539  new LArVoxelReadoutGeometry("LArVoxelReadoutGeometry", readoutGeomSetupData);
540  pworlds.push_back(fVoxelReadoutGeometry);
541  pworlds.push_back(
542  new OpDetReadoutGeometry(geom->OpDetGeoName(), "OpDetReadoutGeometry", fUseLitePhotons));
543  pworlds.push_back(new AuxDetReadoutGeometry("AuxDetReadoutGeometry"));
544 
545  fG4Help->SetParallelWorlds(pworlds);
546 
547  // moved up
548  // Intialize G4 physics and primary generator action
549  fG4Help->InitPhysics();
550 
551  // Use the UserActionManager to handle all the Geant4 user hooks.
552  g4b::UserActionManager* uaManager = g4b::UserActionManager::Instance();
553 
554  // User-action class for accumulating LAr voxels.
556 
557  // User-action class for accumulating particles and trajectories
558  // produced in the detector.
560  lgp->StoreTrajectories(),
561  lgp->KeepEMShowerDaughters(),
563  uaManager->AddAndAdoptAction(fparticleListAction);
564 
565  // UserActionManager is now configured so continue G4 initialization
566  fG4Help->SetUserAction();
567 
568  // With an enormous detector with lots of rock ala LAr34 (nee LAr20)
569  // we need to be smarter about stacking.
570  if (fSmartStacking > 0) {
571  G4UserStackingAction* stacking_action = new LArStackingAction(fSmartStacking);
572  fG4Help->GetRunManager()->SetUserAction(stacking_action);
573  }
574  }
575 
576  void
578  {
579  // prepare the filter object (null if no filtering)
580  std::set<std::string> volnameset(fKeepParticlesInVolumes.begin(),
583  }
584 
585  std::unique_ptr<util::PositionInVolumeFilter>
586  LArG4::CreateParticleVolumeFilter(std::set<std::string> const& vol_names) const
587  {
588  // if we don't have favourite volumes, don't even bother creating a filter
589  if (empty(vol_names)) return {};
590 
591  auto const& geom = *art::ServiceHandle<geo::Geometry const>();
592 
593  std::vector<std::vector<TGeoNode const*>> node_paths = geom.FindAllVolumePaths(vol_names);
594 
595  // collection of interesting volumes
597  GeoVolumePairs.reserve(node_paths.size()); // because we are obsessed
598 
599  //for each interesting volume, follow the node path and collect
600  //total rotations and translations
601  for (size_t iVolume = 0; iVolume < node_paths.size(); ++iVolume) {
602  std::vector<TGeoNode const*> path = node_paths[iVolume];
603 
604  auto pTransl = new TGeoTranslation(0., 0., 0.);
605  auto pRot = new TGeoRotation();
606  for (TGeoNode const* node : path) {
607  TGeoTranslation thistranslate(*node->GetMatrix());
608  TGeoRotation thisrotate(*node->GetMatrix());
609  pTransl->Add(&thistranslate);
610  *pRot = *pRot * thisrotate;
611  }
612 
613  // for some reason, pRot and pTransl don't have tr and rot bits set
614  // correctly make new translations and rotations so bits are set correctly
615  auto pTransl2 = new TGeoTranslation(
616  pTransl->GetTranslation()[0], pTransl->GetTranslation()[1], pTransl->GetTranslation()[2]);
617  double phi = 0., theta = 0., psi = 0.;
618  pRot->GetAngles(phi, theta, psi);
619  auto pRot2 = new TGeoRotation();
620  pRot2->SetAngles(phi, theta, psi);
621 
622  auto pTransf = new TGeoCombiTrans(*pTransl2, *pRot2);
623  GeoVolumePairs.emplace_back(node_paths[iVolume].back()->GetVolume(), pTransf);
624  }
625 
626  return std::make_unique<util::PositionInVolumeFilter>(std::move(GeoVolumePairs));
627  } // CreateParticleVolumeFilter()
628 
629  void
631  {
632  MF_LOG_DEBUG("LArG4") << "produce()";
633  auto const clockData = art::ServiceHandle<detinfo::DetectorClocksService const>()->DataFor(evt);
634  auto const detProp =
636  LArVoxelReadoutGeometry::Sentry const set_for_event{fVoxelReadoutGeometry, clockData, detProp};
637 
638  // loop over the lists and put the particles and voxels into the event as
639  // collections
640  auto scCol = std::make_unique<std::vector<sim::SimChannel>>();
641  auto adCol = std::make_unique<std::vector<sim::AuxDetSimChannel>>();
642  auto tpassn =
644  std::make_unique<art::Assns<simb::MCTruth, simb::MCParticle, sim::GeneratedParticleInfo>>() :
645  nullptr;
646  auto partCol =
648  std::make_unique<std::vector<simb::MCParticle>>() :
649  nullptr;
650  auto PhotonCol = std::make_unique<std::vector<sim::SimPhotons>>();
651  auto PhotonColRefl = std::make_unique<std::vector<sim::SimPhotons>>();
652  auto LitePhotonCol = std::make_unique<std::vector<sim::SimPhotonsLite>>();
653  auto LitePhotonColRefl = std::make_unique<std::vector<sim::SimPhotonsLite>>();
654  auto cOpDetBacktrackerRecordCol = std::make_unique<std::vector<sim::OpDetBacktrackerRecord>>();
655  auto cOpDetBacktrackerRecordColRefl =
656  std::make_unique<std::vector<sim::OpDetBacktrackerRecord>>();
657 
658  std::optional<art::PtrMaker<simb::MCParticle>> makeMCPartPtr;
659  if (fMakeMCParticles) makeMCPartPtr.emplace(evt);
660 
661  // for energy deposits
662  auto edepCol_TPCActive = std::make_unique<std::vector<sim::SimEnergyDeposit>>();
663  auto edepCol_Other = std::make_unique<std::vector<sim::SimEnergyDeposit>>();
664 
665  // Fetch the lists of LAr voxels and particles.
668 
669  // Clear the detected photon table
671  if (lgp->FillSimEnergyDeposits()) OpDetPhotonTable::Instance()->ClearEnergyDeposits();
672 
673  // reset the track ID offset as we have a new collection of interactions
675 
676  //look to see if there is any MCTruth information for this
677  //event
678  std::vector<art::Handle<std::vector<simb::MCTruth>>> mclists;
679  if (empty(fInputLabels))
680  //evt.getManyByType(mclists);
681  mclists = evt.getMany<std::vector<simb::MCTruth>>();
682  else {
683  mclists.resize(fInputLabels.size());
684  for (size_t i = 0; i < fInputLabels.size(); i++)
685  evt.getByLabel(fInputLabels[i], mclists[i]);
686  }
687 
688  unsigned int nGeneratedParticles = 0;
689 
690  // Need to process Geant4 simulation for each interaction separately.
691  for (size_t mcl = 0; mcl < mclists.size(); ++mcl) {
692 
693  art::Handle<std::vector<simb::MCTruth>> mclistHandle = mclists[mcl];
694 
695  for (size_t m = 0; m < mclistHandle->size(); ++m) {
696  art::Ptr<simb::MCTruth> mct(mclistHandle, m);
697 
698  MF_LOG_DEBUG("LArG4") << *(mct.get());
699 
700  // The following tells Geant4 to track the particles in this interaction.
701  fG4Help->G4Run(mct);
702 
703  if (!partCol) continue;
704  assert(tpassn);
705 
706  // receive the particle list
707  sim::ParticleList particleList = fparticleListAction->YieldList();
708 
709  for (auto const& partPair : particleList) {
710  simb::MCParticle& p = *(partPair.second);
711  ++nGeneratedParticles;
712 
713  // if the particle has been marked as dropped, we don't save it
714  // (as of LArSoft ~v5.6 this does not ever happen because
715  // ParticleListAction has already taken care of deleting them)
716  if (ParticleListAction::isDropped(&p)) continue;
717 
718  sim::GeneratedParticleInfo const truthInfo{
720  if (!truthInfo.hasGeneratedParticleIndex() && (p.Mother() == 0)) {
721  // this means it's primary but with no information; logic error!!
723  error << "Failed to match primary particle:\n";
724  sim::dump::DumpMCParticle(error, p, " ");
725  error << "\nwith particles from the truth record '"
726  << mclistHandle.provenance()->inputTag() << "':\n";
727  sim::dump::DumpMCTruth(error, *mct, 2U, " "); // 2 points per line
728  error << "\n";
729  throw error;
730  }
731 
733 
734  partCol->push_back(std::move(p));
735 
736  tpassn->addSingle(mct, (*makeMCPartPtr)(partCol->size() - 1), truthInfo);
737 
738  } // for(particleList)
739 
740  // Has the user request a detailed dump of the output objects?
741  if (fdumpParticleList) {
742  mf::LogInfo("LArG4") << "Dump sim::ParticleList; size()=" << particleList.size() << "\n"
743  << particleList;
744  }
745  }
746 
747  } // end loop over interactions
748 
749  // get the electrons from the LArVoxelReadout sensitive detector
750  // Get the sensitive-detector manager.
751  G4SDManager* sdManager = G4SDManager::GetSDMpointer();
752 
753  // Find the sensitive detector with the name "LArVoxelSD".
754  auto theOpDetDet = dynamic_cast<OpDetSensitiveDetector*>(
755  sdManager->FindSensitiveDetector("OpDetSensitiveDetector"));
756 
757  // Store the contents of the detected photon table
758  //
759  if (theOpDetDet) {
760 
761  if (!lgp->NoPhotonPropagation()) {
762 
763  for (int Reflected = 0; Reflected <= 1; Reflected++) {
764  if (Reflected && !fStoreReflected) continue;
765 
766  if (!fUseLitePhotons) {
767  MF_LOG_DEBUG("Optical") << "Storing OpDet Hit Collection in Event";
768  std::vector<sim::SimPhotons>& ThePhotons =
770  if (Reflected)
771  PhotonColRefl->reserve(ThePhotons.size());
772  else
773  PhotonCol->reserve(ThePhotons.size());
774  for (auto& it : ThePhotons) {
775  if (Reflected)
776  PhotonColRefl->push_back(std::move(it));
777  else
778  PhotonCol->push_back(std::move(it));
779  }
780  }
781  else {
782  MF_LOG_DEBUG("Optical") << "Storing OpDet Hit Collection in Event";
783 
784  std::map<int, std::map<int, int>> ThePhotons =
786 
787  if (size(ThePhotons) > 0) {
788  LitePhotonCol->reserve(ThePhotons.size());
789  for (auto const& [opChannel, detectedPhotons] : ThePhotons) {
791  ph.OpChannel = opChannel;
792  ph.DetectedPhotons = detectedPhotons;
793  if (Reflected)
794  LitePhotonColRefl->push_back(std::move(ph));
795  else
796  LitePhotonCol->push_back(std::move(ph));
797  }
798  }
799  }
800  if (Reflected)
801  *cOpDetBacktrackerRecordColRefl =
803  else
804  *cOpDetBacktrackerRecordCol =
806  }
807  } //end if no photon propagation
808 
809  if (lgp->FillSimEnergyDeposits()) {
810  // we steal the only existing copy of the energy deposit map. Oink!
812  for (auto& [volumeName, edepCol] : edepMap) {
813  // note: constant reference to a (smart) pointer to non-const data
814  auto const& destColl =
815  boost::contains(volumeName, "TPCActive") ? edepCol_TPCActive : edepCol_Other;
816  append(*destColl, std::move(edepCol));
817  } // for
818  }
819  } //end if theOpDetDet
820 
821  if (!lgp->NoElectronPropagation()) {
822 
823  // only put the sim::SimChannels into the event once, not once for every
824  // MCTruth in the event
825 
826  std::set<LArVoxelReadout*> ReadoutList; // to be cleared later on
827 
828  for (unsigned int c = 0; c < geom->Ncryostats(); ++c) {
829 
830  // map to keep track of which channels we already have SimChannels for in scCol
831  // remake this map on each cryostat as channels ought not to be shared between
832  // cryostats, just between TPC's
833 
834  std::map<unsigned int, unsigned int> channelToscCol;
835 
836  unsigned int ntpcs = geom->Cryostat(c).NTPC();
837  for (unsigned int t = 0; t < ntpcs; ++t) {
838  std::string name("LArVoxelSD");
839  std::ostringstream sstr;
840  sstr << name << "_Cryostat" << c << "_TPC" << t;
841 
842  // try first to find the sensitive detector specific for this TPC;
843  // do not bother writing on screen if there is none (yet)
844  G4VSensitiveDetector* sd = sdManager->FindSensitiveDetector(sstr.str(), false);
845  // if there is none, catch the general one (called just "LArVoxelSD")
846  if (!sd) sd = sdManager->FindSensitiveDetector(name, false);
847  // If this didn't work, then a sensitive detector with
848  // the name "LArVoxelSD" does not exist.
849  if (!sd) {
850  throw cet::exception("LArG4")
851  << "Sensitive detector for cryostat " << c << " TPC " << t << " not found (neither '"
852  << sstr.str() << "' nor '" << name << "' exist)\n";
853  }
854 
855  // Convert the G4VSensitiveDetector* to a LArVoxelReadout*.
856  auto larVoxelReadout = dynamic_cast<LArVoxelReadout*>(sd);
857 
858  // If this didn't work, there is a "LArVoxelSD" detector, but
859  // it's not a LArVoxelReadout object.
860  if (!larVoxelReadout) {
861  throw cet::exception("LArG4")
862  << "Sensitive detector '" << sd->GetName() << "' is not a LArVoxelReadout object\n";
863  }
864 
865  LArVoxelReadout::ChannelMap_t& channels = larVoxelReadout->GetSimChannelMap(c, t);
866  if (!empty(channels)) {
867  MF_LOG_DEBUG("LArG4") << "now put " << channels.size() << " SimChannels from C=" << c
868  << " T=" << t << " into the event";
869  }
870 
871  for (auto ch_pair : channels) {
872  sim::SimChannel& sc = ch_pair.second;
873 
874  // push sc onto scCol but only if we haven't already put something in scCol for this channel.
875  // if we have, then merge the ionization deposits. Skip the check if we only have one TPC
876 
877  if (ntpcs > 1) {
878  unsigned int ichan = sc.Channel();
879  auto itertest = channelToscCol.find(ichan);
880  if (itertest == channelToscCol.end()) {
881  channelToscCol[ichan] = scCol->size();
882  scCol->emplace_back(std::move(sc));
883  }
884  else {
885  unsigned int idtest = itertest->second;
886  auto const& tdcideMap = sc.TDCIDEMap();
887  for (auto const& tdcide : tdcideMap) {
888  for (auto const& ide : tdcide.second) {
889  double xyz[3] = {ide.x, ide.y, ide.z};
890  scCol->at(idtest).AddIonizationElectrons(
891  ide.trackID, tdcide.first, ide.numElectrons, xyz, ide.energy);
892  } // end loop to add ionization electrons to scCol->at(idtest)
893  } // end loop over tdc to vector<sim::IDE> map
894  } // end if check to see if we've put SimChannels in for ichan yet or not
895  }
896  else {
897  scCol->emplace_back(std::move(sc));
898  } // end of check if we only have one TPC (skips check for multiple simchannels if we have just one TPC)
899  } // end loop over simchannels for this TPC
900 
901  // mark it for clearing
902  ReadoutList.insert(const_cast<LArVoxelReadout*>(larVoxelReadout));
903 
904  } // end loop over tpcs
905  } // end loop over cryostats
906 
907  for (LArVoxelReadout* larVoxelReadout : ReadoutList) {
908  larVoxelReadout->ClearSimChannels();
909  }
910  } //endif electron prop
911 
912  // only put the sim::AuxDetSimChannels into the event once, not once for every
913  // MCTruth in the event
914 
915  adCol->reserve(geom->NAuxDets());
916  for (unsigned int a = 0; a < geom->NAuxDets(); ++a) {
917 
918  // there should always be at least one senstive volume because
919  // we make one for the full aux det if none are specified in the
920  // gdml file - see AuxDetGeo.cxx
921  for (size_t sv = 0; sv < geom->AuxDet(a).NSensitiveVolume(); ++sv) {
922 
923  // N.B. this name convention is used when creating the
924  // AuxDetReadout SD in AuxDetReadoutGeometry
925  std::stringstream name;
926  name << "AuxDetSD_AuxDet" << a << "_" << sv;
927  G4VSensitiveDetector* sd = sdManager->FindSensitiveDetector(name.str().c_str());
928  if (!sd) {
929  throw cet::exception("LArG4")
930  << "Sensitive detector '" << name.str() << "' does not exist\n";
931  }
932 
933  // Convert the G4VSensitiveDetector* to a AuxDetReadout*.
934  larg4::AuxDetReadout* auxDetReadout = dynamic_cast<larg4::AuxDetReadout*>(sd);
935 
936  MF_LOG_DEBUG("LArG4") << "now put the AuxDetSimTracks in the event";
937 
938  const sim::AuxDetSimChannel adsc = auxDetReadout->GetAuxDetSimChannel();
939  adCol->push_back(adsc);
940  auxDetReadout->clear();
941  }
942  } // Loop over AuxDets
943 
944  if (partCol) {
945  mf::LogInfo("LArG4") << "Geant4 simulated " << nGeneratedParticles << " MC particles, we keep "
946  << partCol->size() << " .";
947  }
948 
949  if (fdumpSimChannels) {
950  mf::LogVerbatim("DumpSimChannels")
951  << "Event " << evt.id() << ": " << scCol->size() << " channels with signal";
952  unsigned int nChannels = 0;
953  for (const sim::SimChannel& sc : *scCol) {
954  mf::LogVerbatim out("DumpSimChannels");
955  out << " #" << nChannels << ": ";
956  // dump indenting with " ", but not on the first line
957  sc.Dump(out, " ");
958  ++nChannels;
959  } // for
960  } // if dump SimChannels
961 
962  if (!lgp->NoElectronPropagation()) evt.put(std::move(scCol));
963 
964  evt.put(std::move(adCol));
965  if (partCol) evt.put(std::move(partCol));
966  if (tpassn) evt.put(std::move(tpassn));
967  if (!lgp->NoPhotonPropagation()) {
968  if (!fUseLitePhotons) {
969  evt.put(std::move(PhotonCol));
970  if (fStoreReflected) evt.put(std::move(PhotonColRefl), "Reflected");
971  }
972  else {
973  evt.put(std::move(LitePhotonCol));
974  evt.put(std::move(cOpDetBacktrackerRecordCol));
975  if (fStoreReflected) {
976  evt.put(std::move(LitePhotonColRefl), "Reflected");
977  evt.put(std::move(cOpDetBacktrackerRecordColRefl), "Reflected");
978  }
979  }
980  }
981 
982  if (lgp->FillSimEnergyDeposits()) {
983  evt.put(std::move(edepCol_TPCActive), "TPCActive");
984  evt.put(std::move(edepCol_Other), "Other");
985  }
986  return;
987  } // LArG4::produce()
988 
989 } // namespace LArG4
990 
std::vector< std::string > fInputLabels
static QCString name
Definition: declinfo.cpp:673
end
while True: pbar.update(maxval-len(onlies[E][S])) #print iS, "/", len(onlies[E][S]) found = False for...
std::unique_ptr< g4b::G4Helper > fG4Help
G4 interface object.
CLHEP::HepRandomEngine * propGen
Random engine for charge propagation.
Store parameters for running LArG4.
Collection of all it takes to set up this object.
MaybeLogger_< ELseverityLevel::ELsev_info, true > LogVerbatim
std::vector< sim::OpDetBacktrackerRecord > YieldReflectedOpDetBacktrackerRecords()
bool KeepEMShowerDaughters() const
std::string fG4MacroPath
Energy deposited on a readout channel by simulated tracks.
Definition: SimChannel.h:140
Stores material properties and sends them to GEANT4 geometry.
void beginJob() override
std::string string
Definition: nybbler.cc:12
MaybeLogger_< ELseverityLevel::ELsev_info, false > LogInfo
int Mother() const
Definition: MCParticle.h:213
std::string OpDetGeoName(unsigned int c=0) const
Returns gdml string which gives sensitive opdet name.
void GetPropertiesFromServices(detinfo::DetectorPropertiesData const &detProp)
Imports properties from LArSoft services.
std::vector< VolumeInfo_t > AllVolumeInfo_t
EDProducer(fhicl::ParameterSet const &pset)
Definition: EDProducer.h:20
void produces(std::string const &instanceName={}, Persistable const persistable=Persistable::Yes)
bool fMakeMCParticles
Whether to keep a sim::MCParticle list.
Geant4 interface.
error
Definition: include.cc:26
larg4::LArVoxelReadout::Setup_t readoutSetup
Set up data for LArVoxelReadout.
Runs Geant4 simulation and propagation of electrons and photons to readout.
std::unordered_map< std::string, std::vector< sim::SimEnergyDeposit > > YieldSimEnergyDeposits()
Yields the map of energy deposits by volume name, and resets the internal one.
bool NoPhotonPropagation() const
unsigned int Ncryostats() const
Returns the number of cryostats in the detector.
std::vector< std::string > fKeepParticlesInVolumes
Only write particles that have trajectories through these volumes.
Utility functions to print MC truth information.
art framework interface to geometry description
Definition: Run.h:17
bool StoreTrajectories() const
int TrackId() const
Definition: MCParticle.h:210
std::map< int, int > DetectedPhotons
Number of photons detected at each given time: time tick -> photons.
Definition: SimPhotons.h:117
Define the "parallel" geometry that&#39;s seen by the LAr Voxels.
bool fSparsifyTrajectories
Sparsify MCParticle Trajectories.
int fSmartStacking
Whether to instantiate and use class to.
decltype(auto) constexpr size(T &&obj)
ADL-aware version of std::size.
Definition: StdUtils.h:92
Collection of particles crossing one auxiliary detector cell.
std::string GDMLFile() const
Returns the full directory path to the GDML file source.
bool fdumpSimChannels
Whether each event&#39;s sim::Channel will be displayed.
larg4::ParticleListAction * fparticleListAction
Geant4 user action to particle information.
Simulation objects for optical detectors.
sim::ParticleList && YieldList()
bool getByLabel(std::string const &label, std::string const &instance, Handle< PROD > &result) const
Definition: DataViewImpl.h:633
std::map< unsigned int, sim::SimChannel > ChannelMap_t
Type of map channel -> sim::SimChannel.
std::vector< sim::SimPhotons > & GetPhotons(bool Reflected=false)
const double e
bool NoElectronPropagation() const
#define DEFINE_ART_MODULE(klass)
Definition: ModuleMacros.h:67
static bool isDropped(simb::MCParticle const *p)
returns whether the specified particle has been marked as dropped
Provenance const * provenance() const
Definition: Handle.h:205
LArG4(fhicl::ParameterSet const &pset)
const double a
std::vector< Handle< PROD > > getMany(SelectorBase const &selector=MatchAllSelector{}) const
Definition: DataViewImpl.h:479
def move(depos, offset)
Definition: depos.py:107
virtual void clear()
void DumpMCTruth(Stream &&out, simb::MCTruth const &truth, unsigned int pointsPerLine, std::string indent, std::string firstIndent)
Dumps the content of the specified MC truth in the output stream.
Definition: MCDumpers.h:345
bool FillSimEnergyDeposits() const
int OpChannel
Optical detector channel associated to this data.
Definition: SimPhotons.h:114
p
Definition: test.py:223
unsigned int NTPC() const
Number of TPCs in this cryostat.
Definition: CryostatGeo.h:181
CryostatGeo const & Cryostat(geo::CryostatID const &cryoid) const
Returns the specified cryostat.
ProductID put(std::unique_ptr< PROD > &&edp, std::string const &instance={})
Definition: DataViewImpl.h:686
size_t NSensitiveVolume() const
Definition: AuxDetGeo.h:172
void ParticleFilter(std::unique_ptr< util::PositionInVolumeFilter > &&filter)
Grabs a particle filter.
GeneratedParticleIndex_t GetPrimaryTruthIndex(int trackId) const
Returns the index of primary truth (sim::NoGeneratorIndex if none).
bool fdumpParticleList
Whether each event&#39;s sim::ParticleList will be displayed.
AuxDetGeo const & AuxDet(unsigned int const ad=0) const
Returns the specified auxiliary detector.
unsigned int NOpDets() const
Number of OpDets in the whole detector.
void UpdateGeometry(G4LogicalVolumeStore *lvs)
Updates the material properties with the collected values.
cet::coded_exception< errors::ErrorCodes, ExceptionDetail::translate > Exception
Definition: Exception.h:66
double ParticleKineticEnergyCut() const
raw::ChannelID_t Channel() const
Returns the readout channel this object describes.
Definition: SimChannel.h:329
static OpDetPhotonTable * Instance(bool LitePhotons=false)
A Geant4 sensitive detector that accumulates voxel information.
Define the "parallel" geometry that&#39;s seen by the AuxDet.
Compact representation of photons on a channel.
Definition: SimPhotons.h:103
Contains data associated to particles from detector simulation.
double offPlaneMargin
Margin for charge recovery (see LArVoxelReadout).
std::unique_ptr< util::PositionInVolumeFilter > CreateParticleVolumeFilter(std::set< std::string > const &vol_names) const
Pointer used for correctly updating the clock data state.
bool fUseLitePhotons
std::vector< sim::OpDetBacktrackerRecord > YieldOpDetBacktrackerRecords()
Contains information about a generated particle.
contains information for a single step in the detector simulation
#define MF_LOG_DEBUG(id)
void produce(art::Event &evt) override
static IonizationAndScintillation * CreateInstance(detinfo::DetectorPropertiesData const &detProp, CLHEP::HepRandomEngine &engine)
std::string fG4PhysListName
predefined physics list to use if not making a custom one
bool fCheckOverlaps
Whether to use the G4 overlap checker.
Transports energy depositions from GEANT4 to TPC channels.
TDCIDEs_t const & TDCIDEMap() const
Returns all the deposited energy information as stored.
Definition: SimChannel.h:328
decltype(auto) constexpr begin(T &&obj)
ADL-aware version of std::begin.
Definition: StdUtils.h:72
bool fStoreReflected
AllPhysicsLists fAllPhysicsLists
TCEvent evt
Definition: DataStructs.cxx:7
std::map< int, std::map< int, int > > GetLitePhotons(bool Reflected=false)
detinfo::DetectorPropertiesData fDetProp
Must outlive fAllPhysicsLists!
T const * get() const
Definition: Ptr.h:149
CLHEP::HepRandomEngine & fEngine
void SparsifyTrajectory(double margin=0.1, bool keep_second_to_last=false)
Definition: MCParticle.h:265
double fOffPlaneMargin
void beginRun(art::Run &run) override
static ServiceHandle< RandomNumberGenerator > & rng()
void ClearTable(size_t nch=0)
A Geant4 sensitive detector that accumulates information.
EventID id() const
Definition: Event.cc:34
sim::AuxDetSimChannel const GetAuxDetSimChannel() const
Definition: AuxDetReadout.h:73
void DumpMCParticle(Stream &&out, simb::MCParticle const &particle, std::string indent, std::string firstIndent)
Dumps the content of the specified particle in the output stream.
Definition: MCDumpers.h:227
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
decltype(auto) constexpr empty(T &&obj)
ADL-aware version of std::empty.
Definition: StdUtils.h:97
bool UseLitePhotons() const
LArVoxelReadoutGeometry * fVoxelReadoutGeometry
unsigned int NAuxDets() const
Returns the number of auxiliary detectors.