LArSoft  v10_04_05
Liquid Argon Software toolkit - https://larsoft.org/
LArVoxelReadoutGeometry.cxx
Go to the documentation of this file.
1 
9 
10 // C/C++ libraries
11 #include <cmath>
12 #include <map>
13 #include <memory> // std::unique_ptr()
14 #include <sstream> // std::ostringstream
15 #include <typeinfo>
16 
17 // Framework includes
19 #include "cetlib_except/demangle.h"
21 
22 // LArSoft includes
27 
28 // G4 includes
29 #include "Geant4/G4Box.hh"
30 #include "Geant4/G4LogicalVolume.hh"
31 #include "Geant4/G4PVReplica.hh"
32 #include "Geant4/G4Point3D.hh"
33 #include "Geant4/G4RotationMatrix.hh"
34 #include "Geant4/G4SDManager.hh"
35 #include "Geant4/G4ThreeVector.hh"
36 #include "Geant4/G4Tubs.hh"
37 #include "Geant4/G4VSolid.hh"
38 #include "Geant4/G4VisAttributes.hh"
39 
40 constexpr bool DisableVoxelCaching = false;
41 
42 namespace {
43  template <typename T>
44  std::string demangle_cxx_symbol(const T& obj)
45  {
46  return cet::demangle_symbol(typeid(obj).name());
47  }
48 
49  template <class STREAM>
50  void DumpPhysicalVolume(STREAM& out, const G4VPhysicalVolume& PV, std::string indentstr = "")
51  {
52  const G4ThreeVector& pos = PV.GetTranslation();
53  const G4LogicalVolume* LV = PV.GetLogicalVolume();
54 
55  out << indentstr << PV.GetName() << " [" << demangle_cxx_symbol(PV) << "]"
56  << " at (" << pos.x() << ", " << pos.y() << ", " << pos.z() << ")";
57 
58  if (!LV) {
59  out << " with no logical volume (!?)\n";
60  return;
61  }
62 
63  out << ", a " << LV->GetName();
64 
65  const G4VSolid* Solid = LV->GetSolid();
66  if (Solid) {
67  out << " shaped as " << Solid->GetName();
68  if (auto pBox = dynamic_cast<const G4Box*>(Solid)) {
69  out << ", a (" << (2. * pBox->GetXHalfLength()) << " x " << (2. * pBox->GetYHalfLength())
70  << " x " << (2. * pBox->GetZHalfLength()) << ") cm box";
71  }
72  else {
73  out << ", a " << demangle_cxx_symbol(*Solid);
74  }
75  }
76  else {
77  out << " with no shape (!?!)";
78  }
79 
80  G4int nDaughters = LV->GetNoDaughters();
81  if (nDaughters > 0) {
82  out << " with " << nDaughters << " subvolumes:\n";
83  for (G4int i = 0; i < nDaughters; ++i) {
84  DumpPhysicalVolume(out, *LV->GetDaughter(i), indentstr + " ");
85  }
86  }
87  else
88  out << '\n';
89  } // DumpPhysicalVolume()
90 
91 }
92 
93 namespace larg4 {
94 
95  // Constructor and destructor.
96  LArVoxelReadoutGeometry::LArVoxelReadoutGeometry(const G4String& name, Setup_t const& setupData)
97  : G4VUserParallelWorld(name)
98  , fGeom{setupData.geometry}
99  , fWireReadoutGeom{setupData.wireReadoutGeom}
100  , fLgp{setupData.larG4Params}
101  , fReadoutSetupData(setupData.readoutSetup)
102  {
104  fStepLimit = std::make_unique<G4UserLimits>(ios->StepSizeLimit());
105  }
106 
109  {
110  // With a "parallel geometry", Geant4 has already created a clone of the world
111  // physical and logical volumes. We want to place the LAr TPC, and only the LAr TPC,
112  // within this cloned world.
113 
114  // Get the parallel world physical volume.
115  G4VPhysicalVolume* parallelPhysical = GetWorld();
116 
117  // Now we want to place a parallel LAr TPC volume within this parallel world volume.
118  // We only want to duplicate the LAr TPC volume; any other volumes in the "official"
119  // geometry are going to be ignored. Our parallel world will consist only of the
120  // world volume and the LAr TPC volume.
121  G4Transform3D worldTransform;
122 
123  // first get the volDetEnclosure
124  std::string daughterName("volDetEnclosure");
125  G4Transform3D detEnclosureTransform;
126  G4VPhysicalVolume* detEnclosureVolume = FindNestedVolume(g4b::DetectorConstruction::GetWorld(),
127  worldTransform,
128  detEnclosureTransform,
129  daughterName,
130  0);
131 
132  // we keep track of all the voxel boxes with different geometries we create
133  struct VoxelSpecs_t {
134  double w, h, d;
135  unsigned int nw, nh, nd;
136 
138  bool operator<(const VoxelSpecs_t& vs) const
139  {
140  if (w < vs.w) return true;
141  if (w > vs.w) return false;
142  if (h < vs.h) return true;
143  if (h > vs.h) return false;
144  if (d < vs.d) return true;
145  if (d > vs.d) return false;
146  if (nw < vs.nw) return true;
147  if (nw > vs.nw) return false;
148  if (nh < vs.nh) return true;
149  if (nh > vs.nh) return false;
150  if (nd < vs.nd) return true;
151  return false;
152  } // operator<
153  }; // VoxelSpecs_t
154 
155  // TODO we don't need to cache the cell any more: a simple map will suffice
156  struct VoxelVolumes_t {
157  G4LogicalVolume* pBox;
158  G4LogicalVolume* pCell;
159 
160  VoxelVolumes_t(G4LogicalVolume* box = nullptr, G4LogicalVolume* cell = nullptr)
161  : pBox(box), pCell(cell)
162  {}
163  }; // VoxelVolumes_t
164 
165  // Define the sensitive detector for the voxel readout. This class routines will be
166  // called every time a particle deposits energy in a voxel that overlaps the LAr TPC.
169  if ((fGeom->Ncryostats() == 1) && (fGeom->Cryostat().NTPC() == 1))
170  flarVoxelReadout->SetSingleTPC(0, 0); // just one TPC in the detector...
171 
172  // Tell Geant4's sensitive-detector manager that the voxel SD class exists.
173  G4SDManager* sdManager = G4SDManager::GetSDMpointer();
174  sdManager->AddNewDetector(flarVoxelReadout);
175 
176  // hope hashing doubles is reliable...
177  using VoxelCache_t = std::map<VoxelSpecs_t, VoxelVolumes_t>;
178  VoxelCache_t VoxelCache;
179 
180  for (unsigned int c = 0; c < fGeom->Ncryostats(); ++c) {
181 
182  // next get the cryostat
183  daughterName = "volCryostat";
184  G4Transform3D cryostatTransform;
185  G4VPhysicalVolume* cryostatVolume = FindNestedVolume(
186  detEnclosureVolume, detEnclosureTransform, cryostatTransform, daughterName, c);
187 
188  for (unsigned int t = 0; t < fGeom->Cryostat(geo::CryostatID(c)).NTPC(); ++t) {
189 
190  // now for the TPC
191  daughterName = "volTPC";
192  G4Transform3D tpcTransform;
193  G4VPhysicalVolume* tpcVolume =
194  FindNestedVolume(cryostatVolume, cryostatTransform, tpcTransform, daughterName, t);
195 
196  daughterName = "volTPCActive";
197  G4Transform3D transform;
198  G4VPhysicalVolume* larTPCPhysical =
199  FindNestedVolume(tpcVolume, tpcTransform, transform, daughterName, t);
200 
201  // Get the LAr TPC volume, and its shape.
202  G4LogicalVolume* larTPCLogical = larTPCPhysical->GetLogicalVolume();
203  larTPCLogical->SetUserLimits(fStepLimit.get());
204 
205  G4VSolid* larTPCShape = larTPCLogical->GetSolid();
206 
207  // We're not going to exactly duplicate the LAr TPC in our parallel world. We're
208  // going to construct a box of voxels. What should the size and position of that
209  // box be?
210 
211  // To get our first hints, we need the overall dimensions of a "bounding box" that
212  // contains the shape. For now, we'll allow two possible shapes: a box (by the
213  // far the most likely) and a cylinder (for bizarre future detectors that I know
214  // nothing about).
215 
216  G4double larTPCHalfXLength = 0;
217  G4double larTPCHalfYLength = 0;
218  G4double larTPCHalfZLength = 0;
219  G4Box* tpcBox = dynamic_cast<G4Box*>(larTPCShape);
220  if (tpcBox != 0) {
221  larTPCHalfXLength = tpcBox->GetXHalfLength();
222  larTPCHalfYLength = tpcBox->GetYHalfLength();
223  larTPCHalfZLength = tpcBox->GetZHalfLength();
224  }
225  else {
226  // It's not a box. Try a cylinder.
227  G4Tubs* tube = dynamic_cast<G4Tubs*>(larTPCShape);
228  if (tube != 0) {
229  larTPCHalfXLength = tube->GetOuterRadius();
230  larTPCHalfYLength = tube->GetOuterRadius();
231  larTPCHalfZLength = tube->GetZHalfLength();
232  }
233  else {
234  throw cet::exception("LArVoxelReadoutGeometry")
235  << "Unknown shape in readout geometry"
236  << "The LAr TPC volume is not a box or a tube. "
237  << "This routine can't convert any other shapes.\n";
238  }
239  }
240 
241  MF_LOG_DEBUG("LArVoxelReadoutGeometry") << ": larTPCHalfXLength=" << larTPCHalfXLength
242  << ": larTPCHalfYLength=" << larTPCHalfYLength
243  << ": larTPCHalfZLength=" << larTPCHalfZLength;
244 
245  // Get some constants from the LAr voxel information object. N.B. ROOT uses cm.
247  G4double voxelSizeX = lvc->VoxelSizeX() * CLHEP::cm;
248  G4double voxelSizeY = lvc->VoxelSizeY() * CLHEP::cm;
249  G4double voxelSizeZ = lvc->VoxelSizeZ() * CLHEP::cm;
250  G4double voxelOffsetX = lvc->VoxelOffsetX() * CLHEP::cm;
251  G4double voxelOffsetY = lvc->VoxelOffsetY() * CLHEP::cm;
252  G4double voxelOffsetZ = lvc->VoxelOffsetZ() * CLHEP::cm;
253 
254  MF_LOG_DEBUG("LArVoxelReadoutGeometry")
255  << ": voxelSizeX=" << voxelSizeX << ", voxelSizeY=" << voxelSizeY
256  << ", voxelSizeZ=" << voxelSizeZ;
257 
258  MF_LOG_DEBUG("LArVoxelReadoutGeometry")
259  << ": voxelOffsetX=" << voxelOffsetX << ", voxelOffsetY=" << voxelOffsetY
260  << ", voxelOffsetZ=" << voxelOffsetZ;
261 
262  // We want our voxelization region to be an integer multiple of the voxel sizes in
263  // all directions; if we didn't do this, we might get into trouble when we start
264  // playing with replicas. Compute the the dimensions of our voxelization to be
265  // about the size of the LAr TPC region, adjusted to be an integer number of
266  // voxels in all directions.
267 
268  G4double numberXvoxels = 2. * larTPCHalfXLength / voxelSizeX;
269  G4double numberYvoxels = 2. * larTPCHalfYLength / voxelSizeY;
270  G4double numberZvoxels = 2. * larTPCHalfZLength / voxelSizeZ;
271  numberXvoxels = trunc(numberXvoxels) + 1.;
272  numberYvoxels = trunc(numberYvoxels) + 1.;
273  numberZvoxels = trunc(numberZvoxels) + 1.;
274 
275  MF_LOG_DEBUG("LArVoxelReadoutGeometry")
276  << "Active volume of cryo #" << c << " TPC #" << t << " will be split in "
277  << numberXvoxels << " x " << numberYvoxels << " x " << numberZvoxels << " = "
278  << (numberXvoxels * numberYvoxels * numberZvoxels) << " voxels of size " << voxelSizeX
279  << " x " << voxelSizeY << " x " << voxelSizeZ << " cm";
280 
281  VoxelSpecs_t VoxelSpecs{/* w */ 2. * larTPCHalfXLength,
282  /* h */ 2. * larTPCHalfYLength,
283  /* d */ 2. * larTPCHalfZLength,
284  /* nw */ (unsigned int)numberXvoxels,
285  /* nh */ (unsigned int)numberYvoxels,
286  /* nd */ (unsigned int)numberZvoxels};
287 
288  VoxelCache_t::iterator iVoxelVol =
289  DisableVoxelCaching ? VoxelCache.end() : VoxelCache.find(VoxelSpecs);
290  if (iVoxelVol == VoxelCache.end()) {
291  MF_LOG_DEBUG("LArVoxelReadoutGeometry")
292  << "Creating a new voxel volume " << VoxelSpecs.w << " x " << VoxelSpecs.h << " x "
293  << VoxelSpecs.d << " cm, " << VoxelSpecs.nw << " x " << VoxelSpecs.nh << " x "
294  << VoxelSpecs.nd << " voxels";
295 
296  G4double voxelBoxHalfX = numberXvoxels * voxelSizeX / 2.;
297  G4double voxelBoxHalfY = numberYvoxels * voxelSizeY / 2.;
298  G4double voxelBoxHalfZ = numberZvoxels * voxelSizeZ / 2.;
299 
300  MF_LOG_DEBUG("LArVoxelReadoutGeometry")
301  << ": voxelBoxHalfX=" << voxelBoxHalfX << ", voxelBoxHalfY=" << voxelBoxHalfY
302  << ", voxelBoxHalfZ=" << voxelBoxHalfZ;
303 
304  // Now we have a box that will include an integer number of voxels in each
305  // direction. Note that the material is irrelevant for a "parallel world."
306  auto voxelBox = new G4Box("VoxelBox", voxelBoxHalfX, voxelBoxHalfY, voxelBoxHalfZ);
307  auto voxelBoxLogical = new G4LogicalVolume(voxelBox, 0, "VoxelizationLogicalVolume");
308 
309  // If we generate an event display within Geant4, we won't want to see this box.
310  auto invisible = new G4VisAttributes();
311  invisible->SetVisibility(false);
312  voxelBoxLogical->SetVisAttributes(invisible);
313 
314  // Now we've fill our "box of voxels" with the voxels themselves. We'll do this
315  // by sub-dividing the volume in x, then y, then z.
316 
317  // Create an "x-slice".
318  G4Box* xSlice = new G4Box("xSlice", voxelSizeX / 2., voxelBoxHalfY, voxelBoxHalfZ);
319  G4LogicalVolume* xSliceLogical = new G4LogicalVolume(xSlice, 0, "xLArVoxelSlice");
320  xSliceLogical->SetVisAttributes(invisible);
321 
322  // Use replication to slice up the "box of voxels" along the x-axis.
323  new G4PVReplica("VoxelSlicesInX",
324  xSliceLogical,
325  voxelBoxLogical,
326  kXAxis,
327  G4int(numberXvoxels),
328  voxelSizeX);
329 
330  // Now do the same thing, dividing that x-slice along the y-axis.
331  auto ySlice = new G4Box("ySlice", voxelSizeX / 2., voxelSizeY / 2., voxelBoxHalfZ);
332  auto ySliceLogical = new G4LogicalVolume(ySlice, 0, "yLArVoxelSlice");
333  ySliceLogical->SetVisAttributes(invisible);
334  new G4PVReplica("VoxelSlicesInY",
335  ySliceLogical,
336  xSliceLogical,
337  kYAxis,
338  G4int(numberYvoxels),
339  voxelSizeY);
340 
341  // Now divide the y-slice along the z-axis, giving us our actual voxels.
342  auto zSlice = new G4Box("zSlice", voxelSizeX / 2., voxelSizeY / 2., voxelSizeZ / 2.);
343  auto voxelLogical = new G4LogicalVolume(zSlice, 0, "LArVoxel");
344  voxelLogical->SetVisAttributes(invisible);
345  new G4PVReplica(
346  "LArVoxel", voxelLogical, ySliceLogical, kZAxis, G4int(numberZvoxels), voxelSizeZ);
347 
348  iVoxelVol = VoxelCache.emplace(VoxelSpecs, VoxelVolumes_t(voxelBoxLogical, voxelLogical))
349  .first; // iterator to the inserted element
350 
351  // Set the sensitive detector of this LAr TPC (logical) volume to be the voxel
352  // readout.
353  voxelLogical->SetSensitiveDetector(flarVoxelReadout);
354 
355  } // if not cached yet
356  G4LogicalVolume* voxelBoxLogical = iVoxelVol->second.pBox;
357 
358  // We have a "box of voxels" that's the right dimensions, but we have to know
359  // exactly where to put it. The user has the option to offset the voxel
360  // co-ordinate system. We want to place our box so the edges of our voxels align
361  // with that co-ordinate system. In effect, we want to offset our "box of voxels"
362  // by the user's offsets, modulo the size of the voxel in each direction.
363 
364  G4double offsetInVoxelsX = voxelOffsetX / voxelSizeX;
365  G4double offsetInVoxelsY = voxelOffsetY / voxelSizeY;
366  G4double offsetInVoxelsZ = voxelOffsetZ / voxelSizeZ;
367  G4double fractionOffsetX = offsetInVoxelsX - trunc(offsetInVoxelsX);
368  G4double fractionOffsetY = offsetInVoxelsY - trunc(offsetInVoxelsY);
369  G4double fractionOffsetZ = offsetInVoxelsZ - trunc(offsetInVoxelsZ);
370  G4double offsetX = fractionOffsetX * voxelSizeX;
371  G4double offsetY = fractionOffsetY * voxelSizeY;
372  G4double offsetZ = fractionOffsetZ * voxelSizeZ;
373 
374  // Now we know how much to offset the "box of voxels". Include that in the
375  // transformation of the co-ordinates from world volume to LAr TPC volume.
376  transform = G4Translate3D(offsetX, offsetY, offsetZ) * transform;
377 
378  MF_LOG_DEBUG("LArVoxelReadoutGeometry")
379  << ": offsetX=" << offsetX << ", offsetY=" << offsetY << ", offsetZ=" << offsetZ;
380 
381  // suffix representing this TPC
382  std::ostringstream nums;
383  nums << "_Cryostat" << c << "_TPC" << t;
384 
385  // Place the box of voxels, with the accumulated transformations computed above.
386  new G4PVPlacementInTPC(transform,
387  "VoxelizationPhysicalVolume" + nums.str(),
388  voxelBoxLogical,
389  parallelPhysical,
390  false, // Only one volume
391  0, // Copy number
392  false, // No surface check
393  {(unsigned short int)c, (unsigned short int)t} // TPC ID
394  );
395 
396  } // end loop over tpcs
397  } // end loop over cryostats
398 
399  if (mf::isDebugEnabled()) {
400  MF_LOG_DEBUG("LArVoxelReadoutGeometryDump") << "Dump of voxelized volume";
401  {
402  mf::LogDebug log("LArVoxelReadoutGeometryDump");
403  DumpPhysicalVolume(log, *parallelPhysical);
404  }
405  MF_LOG_DEBUG("LArVoxelReadoutGeometryDump") << "End of dump of voxelized volume";
406  }
407  }
408 
409  //---------------------------------------------------------------
410  // We know the ordering of the volumes in the Geometry, see
411  // https://cdcvs.fnal.gov/redmine/projects/larsoftsvn/wiki/Geometry Make use of that
412  // knowledge to efficiently get the desired volumes and their total transforms. the
413  // daughterTransform is the total transform to the world coordinate system
414  G4VPhysicalVolume* LArVoxelReadoutGeometry::FindNestedVolume(G4VPhysicalVolume* mother,
415  G4Transform3D& motherTransform,
416  G4Transform3D& daughterTransform,
417  std::string const& daughterName,
418  unsigned int expectedNum)
419  {
420  G4LogicalVolume* logicalVolume = mother->GetLogicalVolume();
421  G4int numberDaughters = logicalVolume->GetNoDaughters();
422  for (G4int i = 0; i != numberDaughters; ++i) {
423  G4VPhysicalVolume* d = logicalVolume->GetDaughter(i);
424 
425  if (d->GetName().contains(daughterName)) {
426 
427  // check that this cryostat is the requested one using fCryostat
428  G4ThreeVector translation = d->GetObjectTranslation();
429  G4RotationMatrix rotation = d->GetObjectRotationValue();
430  G4Transform3D transform(rotation, translation);
431  daughterTransform = motherTransform * transform;
432 
433  // take the origin of the volume and transform it to world coordinated
434  G4Point3D local(0., 0., 0.);
435  G4Point3D world = daughterTransform * local;
436 
437  MF_LOG_DEBUG("LArVoxelReadoutGeometry")
438  << "current " << daughterName << " origin is at (" << world.x() / CLHEP::cm << ","
439  << world.y() / CLHEP::cm << "," << world.z() / CLHEP::cm << ")";
440 
441  // we don't bother with the cryostat number when calling Geometry::PositionToTPC
442  // because we know we have already started off with the correct cryostat volume G4
443  // uses mm, we want cm
444  geo::Point_t const worldPos{
445  world.x() / CLHEP::cm, world.y() / CLHEP::cm, world.z() / CLHEP::cm};
446  unsigned int daughterNum = 0;
447  if (daughterName.compare("volCryostat") == 0)
448  daughterNum = fGeom->PositionToCryostatID(worldPos).Cryostat;
449  else if (daughterName.compare("volTPC") == 0)
450  daughterNum = fGeom->PositionToTPCID(worldPos).TPC;
451  else if (daughterName.compare("volTPCActive") == 0 ||
452  daughterName.compare("volDetEnclosure") == 0) {
453  // for either of these volumes, we know there is only 1 in the mother volume
454  MF_LOG_DEBUG("LArVoxelReadoutGeometry") << "found the desired " << daughterName;
455  return d;
456  }
457 
458  // if we found the desired volume, stop looking
459  if (daughterNum == expectedNum) {
460  MF_LOG_DEBUG("LArVoxelReadoutGeometry") << "found the desired " << daughterName;
461  return d;
462  }
463  } // end if the volume has the right name
464  } // end loop over volumes
465 
466  throw cet::exception("LArVoxelReadoutGeometry")
467  << "could not find the desired " << daughterName << " to make LArVoxelReadoutGeometry\n";
468  }
469 
470 } // namespace larg4
Build Geant4 geometry from GDML.
intermediate_table::iterator iterator
Collection of all it takes to set up this object.
double VoxelSizeX() const
Access to voxel dimensions and offsets.
larg4::LArVoxelReadout::Setup_t fReadoutSetupData
Encapsulates calculation of LArVoxelID and LArVoxel parameters.
G4VPhysicalVolume * FindNestedVolume(G4VPhysicalVolume *mother, G4Transform3D &motherTransform, G4Transform3D &daughterTransform, std::string const &daughterName, unsigned int expectedNum)
constexpr bool operator<(CryostatID const &a, CryostatID const &b)
Order cryostats with increasing ID.
Definition: geo_types.h:543
static G4VPhysicalVolume * GetWorld()
Geant4 interface.
CryostatID_t Cryostat
Index of cryostat.
Definition: geo_types.h:195
void Setup(Setup_t const &setupData)
Reads all the configuration elements from setupData
larg4::LArVoxelReadout * flarVoxelReadout
Data for LArVoxelReadout setup.
unsigned int Ncryostats() const
Returns the number of cryostats in the detector.
Definition: GeometryCore.h:303
Define the "parallel" geometry that&#39;s seen by the LAr Voxels.
geo::WireReadoutGeom const * fWireReadoutGeom
Float_t voxelSizeY
Definition: plot.C:39
void SetSingleTPC(unsigned int cryostat, unsigned int tpc)
Associates this readout to one specific TPC.
std::unique_ptr< G4UserLimits > fStepLimit
constexpr bool DisableVoxelCaching
Float_t voxelSizeZ
Definition: plot.C:39
static IonizationAndScintillation * Instance()
sim::LArG4Parameters const * fLgp
Float_t d
Definition: plot.C:235
unsigned int NTPC() const
Number of TPCs in this cryostat.
Definition: CryostatGeo.h:171
G4PVPlacementWithID< TPCID_t > G4PVPlacementInTPC
A physical volume with a TPC ID.
geo::GeometryCore const * geometry
Set up data for LArVoxelReadout.
bool isDebugEnabled()
double VoxelOffsetX() const
CryostatGeo const & Cryostat(CryostatID const &cryoid=details::cryostat_zero) const
Returns the specified cryostat.
Float_t voxelSizeX
Definition: plot.C:39
double VoxelOffsetZ() const
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
A Geant4 sensitive detector that accumulates voxel information.
Singleton to access a unified treatment of ionization and scintillation in LAr.
#define MF_LOG_DEBUG(id)
Transports energy depositions from GEANT4 to TPC channels.
TPCID_t TPC
Index of the TPC within its cryostat.
Definition: geo_types.h:315
Float_t w
Definition: plot.C:20
TPCID PositionToTPCID(Point_t const &point) const
Returns the ID of the TPC at specified location.
double VoxelOffsetY() const
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
The data type to uniquely identify a cryostat.
Definition: geo_types.h:187