8 #include "nug4/G4Base/DetectorConstruction.h"
18 #include "art/Framework/Services/Registry/ServiceHandle.h"
19 #include "cetlib_except/demangle.h"
20 #include "messagefacility/MessageLogger/MessageLogger.h"
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"
45 demangle_cxx_symbol(
const T& obj)
47 return cet::demangle_symbol(
typeid(obj).
name());
50 template <
class STREAM>
52 DumpPhysicalVolume(STREAM& out,
const G4VPhysicalVolume& PV, std::string indentstr =
"")
54 const G4ThreeVector& pos = PV.GetTranslation();
55 const G4LogicalVolume* LV = PV.GetLogicalVolume();
58 out << indentstr << PV.GetName() <<
" [" << demangle_cxx_symbol(PV) <<
"]"
59 <<
" at (" << pos.x() <<
", " << pos.y() <<
", " << pos.z() <<
")";
61 out <<
", a " << LV->GetName();
63 const G4VSolid* Solid = LV->GetSolid();
65 out <<
" shaped as " << Solid->GetName();
69 pBox =
dynamic_cast<const G4Box*
>(Solid);
71 catch (std::bad_cast&) {
76 out <<
", a (" << (2. * pBox->GetXHalfLength()) <<
" x " << (2. * pBox->GetYHalfLength())
77 <<
" x " << (2. * pBox->GetZHalfLength()) <<
") cm box";
80 out <<
", a " << demangle_cxx_symbol(*Solid);
84 out <<
" with no shape (!?!)";
87 G4int nDaughters = LV->GetNoDaughters();
89 out <<
" with " << nDaughters <<
" subvolumes:\n";
90 for (G4int i = 0; i < nDaughters; ++i) {
91 count += DumpPhysicalVolume(out, *LV->GetDaughter(i), indentstr +
" ");
98 out <<
" with no logical volume (!?)\n";
109 : G4VUserParallelWorld(name), fReadoutSetupData(setupData.readoutSetup)
124 G4VPhysicalVolume* parallelPhysical = GetWorld();
132 G4Transform3D worldTransform;
135 std::string daughterName(
"volDetEnclosure");
136 G4Transform3D detEnclosureTransform;
137 G4VPhysicalVolume* detEnclosureVolume =
140 detEnclosureTransform,
145 struct VoxelSpecs_t {
147 unsigned int nw, nh, nd;
153 if (
w < vs.w)
return true;
154 if (
w > vs.w)
return false;
155 if (
h < vs.h)
return true;
156 if (
h > vs.h)
return false;
157 if (d < vs.d)
return true;
158 if (d > vs.d)
return false;
159 if (nw < vs.nw)
return true;
160 if (nw > vs.nw)
return false;
161 if (nh < vs.nh)
return true;
162 if (nh > vs.nh)
return false;
163 if (nd < vs.nd)
return true;
169 struct VoxelVolumes_t {
170 G4LogicalVolume* pBox;
171 G4LogicalVolume* pCell;
173 VoxelVolumes_t(G4LogicalVolume* box =
nullptr, G4LogicalVolume* cell =
nullptr)
174 : pBox(box), pCell(cell)
183 if ((
fGeo->Ncryostats() == 1) && (
fGeo->Cryostat(0).NTPC() == 1))
188 G4SDManager* sdManager = G4SDManager::GetSDMpointer();
192 using VoxelCache_t = std::map<VoxelSpecs_t, VoxelVolumes_t>;
193 VoxelCache_t VoxelCache;
195 for (
unsigned int c = 0; c <
fGeo->Ncryostats(); ++c) {
198 daughterName =
"volCryostat";
199 G4Transform3D cryostatTransform;
201 detEnclosureVolume, detEnclosureTransform, cryostatTransform, daughterName, c);
203 for (
unsigned int t = 0; t <
fGeo->Cryostat(c).NTPC(); ++t) {
206 daughterName =
"volTPC";
207 G4Transform3D tpcTransform;
208 G4VPhysicalVolume* tpcVolume =
209 this->
FindNestedVolume(cryostatVolume, cryostatTransform, tpcTransform, daughterName, t);
211 daughterName =
"volTPCActive";
213 G4VPhysicalVolume* larTPCPhysical =
214 this->
FindNestedVolume(tpcVolume, tpcTransform, transform, daughterName, t);
217 G4LogicalVolume* larTPCLogical = larTPCPhysical->GetLogicalVolume();
218 larTPCLogical->SetUserLimits(
fStepLimit.get());
220 G4VSolid* larTPCShape = larTPCLogical->GetSolid();
232 G4double larTPCHalfXLength = 0;
233 G4double larTPCHalfYLength = 0;
234 G4double larTPCHalfZLength = 0;
235 G4Box* tpcBox =
dynamic_cast<G4Box*
>(larTPCShape);
237 larTPCHalfXLength = tpcBox->GetXHalfLength();
238 larTPCHalfYLength = tpcBox->GetYHalfLength();
239 larTPCHalfZLength = tpcBox->GetZHalfLength();
243 G4Tubs* tube =
dynamic_cast<G4Tubs*
>(larTPCShape);
245 larTPCHalfXLength = tube->GetOuterRadius();
246 larTPCHalfYLength = tube->GetOuterRadius();
247 larTPCHalfZLength = tube->GetZHalfLength();
250 throw cet::exception(
"LArVoxelReadoutGeometry")
251 <<
"Unknown shape in readout geometry"
252 <<
"The LAr TPC volume is not a box or a tube. "
253 <<
"This routine can't convert any other shapes.\n";
257 MF_LOG_DEBUG(
"LArVoxelReadoutGeometry") <<
": larTPCHalfXLength=" << larTPCHalfXLength
258 <<
": larTPCHalfYLength=" << larTPCHalfYLength
259 <<
": larTPCHalfZLength=" << larTPCHalfZLength;
263 art::ServiceHandle<sim::LArVoxelCalculator const> lvc;
264 G4double voxelSizeX = lvc->VoxelSizeX() * CLHEP::cm;
265 G4double voxelSizeY = lvc->VoxelSizeY() * CLHEP::cm;
266 G4double voxelSizeZ = lvc->VoxelSizeZ() * CLHEP::cm;
267 G4double voxelOffsetX = lvc->VoxelOffsetX() * CLHEP::cm;
268 G4double voxelOffsetY = lvc->VoxelOffsetY() * CLHEP::cm;
269 G4double voxelOffsetZ = lvc->VoxelOffsetZ() * CLHEP::cm;
271 MF_LOG_DEBUG(
"LArVoxelReadoutGeometry")
272 <<
": voxelSizeX=" << voxelSizeX <<
", voxelSizeY=" << voxelSizeY
273 <<
", voxelSizeZ=" << voxelSizeZ;
275 MF_LOG_DEBUG(
"LArVoxelReadoutGeometry")
276 <<
": voxelOffsetX=" << voxelOffsetX <<
", voxelOffsetY=" << voxelOffsetY
277 <<
", voxelOffsetZ=" << voxelOffsetZ;
286 G4double numberXvoxels = 2. * larTPCHalfXLength / voxelSizeX;
287 G4double numberYvoxels = 2. * larTPCHalfYLength / voxelSizeY;
288 G4double numberZvoxels = 2. * larTPCHalfZLength / voxelSizeZ;
289 numberXvoxels = trunc(numberXvoxels) + 1.;
290 numberYvoxels = trunc(numberYvoxels) + 1.;
291 numberZvoxels = trunc(numberZvoxels) + 1.;
293 MF_LOG_DEBUG(
"LArVoxelReadoutGeometry")
294 <<
"Active volume of cryo #" << c <<
" TPC #" << t <<
" will be split in "
295 << numberXvoxels <<
" x " << numberYvoxels <<
" x " << numberZvoxels <<
" = "
296 << (numberXvoxels * numberYvoxels * numberZvoxels) <<
" voxels of size " << voxelSizeX
297 <<
" x " << voxelSizeY <<
" x " << voxelSizeZ <<
" cm";
299 VoxelSpecs_t VoxelSpecs{ 2. * larTPCHalfXLength,
300 2. * larTPCHalfYLength,
301 2. * larTPCHalfZLength,
302 (
unsigned int)numberXvoxels,
303 (
unsigned int)numberYvoxels,
304 (
unsigned int)numberZvoxels};
306 VoxelCache_t::iterator iVoxelVol =
308 if (iVoxelVol == VoxelCache.end()) {
309 MF_LOG_DEBUG(
"LArVoxelReadoutGeometry")
310 <<
"Creating a new voxel volume " << VoxelSpecs.w <<
" x " << VoxelSpecs.h <<
" x "
311 << VoxelSpecs.d <<
" cm, " << VoxelSpecs.nw <<
" x " << VoxelSpecs.nh <<
" x "
312 << VoxelSpecs.nd <<
" voxels";
314 G4double voxelBoxHalfX = numberXvoxels * voxelSizeX / 2.;
315 G4double voxelBoxHalfY = numberYvoxels * voxelSizeY / 2.;
316 G4double voxelBoxHalfZ = numberZvoxels * voxelSizeZ / 2.;
318 MF_LOG_DEBUG(
"LArVoxelReadoutGeometry")
319 <<
": voxelBoxHalfX=" << voxelBoxHalfX <<
", voxelBoxHalfY=" << voxelBoxHalfY
320 <<
", voxelBoxHalfZ=" << voxelBoxHalfZ;
325 auto voxelBox =
new G4Box(
"VoxelBox", voxelBoxHalfX, voxelBoxHalfY, voxelBoxHalfZ);
326 auto voxelBoxLogical =
new G4LogicalVolume(voxelBox, 0,
"VoxelizationLogicalVolume");
330 auto invisible =
new G4VisAttributes();
331 invisible->SetVisibility(
false);
332 voxelBoxLogical->SetVisAttributes(invisible);
338 G4Box* xSlice =
new G4Box(
"xSlice", voxelSizeX / 2., voxelBoxHalfY, voxelBoxHalfZ);
339 G4LogicalVolume* xSliceLogical =
new G4LogicalVolume(xSlice, 0,
"xLArVoxelSlice");
340 xSliceLogical->SetVisAttributes(invisible);
343 new G4PVReplica(
"VoxelSlicesInX",
347 G4int(numberXvoxels),
351 auto ySlice =
new G4Box(
"ySlice", voxelSizeX / 2., voxelSizeY / 2., voxelBoxHalfZ);
352 auto ySliceLogical =
new G4LogicalVolume(ySlice, 0,
"yLArVoxelSlice");
353 ySliceLogical->SetVisAttributes(invisible);
354 new G4PVReplica(
"VoxelSlicesInY",
358 G4int(numberYvoxels),
363 auto zSlice =
new G4Box(
"zSlice", voxelSizeX / 2., voxelSizeY / 2., voxelSizeZ / 2.);
364 auto voxelLogical =
new G4LogicalVolume(zSlice, 0,
"LArVoxel");
365 voxelLogical->SetVisAttributes(invisible);
367 "LArVoxel", voxelLogical, ySliceLogical, kZAxis, G4int(numberZvoxels), voxelSizeZ);
369 iVoxelVol = VoxelCache.emplace(VoxelSpecs, VoxelVolumes_t(voxelBoxLogical, voxelLogical))
377 G4LogicalVolume* voxelBoxLogical = iVoxelVol->second.pBox;
387 G4double offsetInVoxelsX = voxelOffsetX / voxelSizeX;
388 G4double offsetInVoxelsY = voxelOffsetY / voxelSizeY;
389 G4double offsetInVoxelsZ = voxelOffsetZ / voxelSizeZ;
390 G4double fractionOffsetX = offsetInVoxelsX - trunc(offsetInVoxelsX);
391 G4double fractionOffsetY = offsetInVoxelsY - trunc(offsetInVoxelsY);
392 G4double fractionOffsetZ = offsetInVoxelsZ - trunc(offsetInVoxelsZ);
393 G4double offsetX = fractionOffsetX * voxelSizeX;
394 G4double offsetY = fractionOffsetY * voxelSizeY;
395 G4double offsetZ = fractionOffsetZ * voxelSizeZ;
400 transform = G4Translate3D(offsetX, offsetY, offsetZ) *
transform;
402 MF_LOG_DEBUG(
"LArVoxelReadoutGeometry")
403 <<
": offsetX=" << offsetX <<
", offsetY=" << offsetY <<
", offsetZ=" << offsetZ;
406 std::ostringstream nums;
407 nums <<
"_Cryostat" << c <<
"_TPC" << t;
412 "VoxelizationPhysicalVolume" + nums.str(),
418 {(
unsigned short int)c, (
unsigned short int)t}
424 if (mf::isDebugEnabled()) {
425 MF_LOG_DEBUG(
"LArVoxelReadoutGeometryDump") <<
"Dump of voxelized volume";
427 mf::LogDebug log(
"LArVoxelReadoutGeometryDump");
428 DumpPhysicalVolume(log, *parallelPhysical);
430 MF_LOG_DEBUG(
"LArVoxelReadoutGeometryDump") <<
"End of dump of voxelized volume";
443 G4Transform3D& motherTransform,
444 G4Transform3D& daughterTransform,
445 std::string& daughterName,
446 unsigned int expectedNum)
448 G4LogicalVolume* logicalVolume = mother->GetLogicalVolume();
449 G4int numberDaughters = logicalVolume->GetNoDaughters();
450 for (G4int i = 0; i != numberDaughters; ++i) {
451 G4VPhysicalVolume* d = logicalVolume->GetDaughter(i);
453 if (d->GetName().contains(daughterName)) {
456 G4ThreeVector translation = d->GetObjectTranslation();
457 G4RotationMatrix rotation = d->GetObjectRotationValue();
458 G4Transform3D
transform(rotation, translation);
459 daughterTransform = motherTransform *
transform;
463 G4Point3D
local(0., 0., 0.);
464 G4Point3D world = daughterTransform *
local;
466 MF_LOG_DEBUG(
"LArVoxelReadoutGeometry")
467 <<
"current " << daughterName <<
" origin is at (" << world.x() / CLHEP::cm <<
","
468 << world.y() / CLHEP::cm <<
"," << world.z() / CLHEP::cm <<
")";
473 double worldPos[3] = {world.x() / CLHEP::cm, world.y() / CLHEP::cm, world.z() / CLHEP::cm};
474 unsigned int daughterNum = 0;
475 unsigned int extra = 0;
476 if (daughterName.compare(
"volCryostat") == 0)
477 fGeo->PositionToCryostat(worldPos, daughterNum);
478 else if (daughterName.compare(
"volTPC") == 0)
479 fGeo->PositionToTPC(worldPos, daughterNum, extra);
480 else if (daughterName.compare(
"volTPCActive") == 0 ||
481 daughterName.compare(
"volDetEnclosure") == 0) {
483 MF_LOG_DEBUG(
"LArVoxelReadoutGeometry") <<
"found the desired " << daughterName;
488 if (daughterNum == expectedNum) {
489 MF_LOG_DEBUG(
"LArVoxelReadoutGeometry") <<
"found the desired " << daughterName;
495 throw cet::exception(
"LArVoxelReadoutGeometry")
496 <<
"could not find the desired " << daughterName <<
" to make LArVoxelReadoutGeometry\n";
Collection of all it takes to set up this object.
larg4::LArVoxelReadout::Setup_t fReadoutSetupData
Encapsulates calculation of LArVoxelID and LArVoxel parameters.
constexpr bool operator<(CryostatID const &a, CryostatID const &b)
Order cryostats with increasing ID.
void Setup(Setup_t const &setupData)
Reads all the configuration elements from setupData
larg4::LArVoxelReadout * flarVoxelReadout
Data for LArVoxelReadout setup.
Define the "parallel" geometry that's seen by the LAr Voxels.
void SetSingleTPC(unsigned int cryostat, unsigned int tpc)
Associates this readout to one specific TPC.
std::unique_ptr< G4UserLimits > fStepLimit
constexpr bool DisableVoxelCaching
LArVoxelReadoutGeometry(const G4String name, Setup_t const &setupData)
Constructor: sets up all its LArVoxelReadout instances.
static IonizationAndScintillation * Instance()
G4PVPlacementWithID< TPCID_t > G4PVPlacementInTPC
A physical volume with a TPC ID.
art::ServiceHandle< geo::Geometry const > fGeo
Handle to the geometry.
G4VPhysicalVolume * FindNestedVolume(G4VPhysicalVolume *mother, G4Transform3D &motherTransform, G4Transform3D &daughterTransform, std::string &daughterName, unsigned int expectedNum)
A Geant4 sensitive detector that accumulates voxel information.
void Construct() override
Singleton to access a unified treatment of ionization and scintillation in LAr.
Transports energy depositions from GEANT4 to TPC channels.
std::size_t count(Cont const &cont)
double StepSizeLimit() const