File indexing completed on 2026-05-15 07:41:49
0001
0002 """
0003 Test eic-opticks DD4hep plugins with the raindrop geometry.
0004
0005 Geometry: Vacuum world > Lead(220mm) > Air(200mm) > Water(100mm)
0006 - Water has RINDEX=1.333 and scintillation properties
0007 - Air has RINDEX=1.0
0008 - One border surface between water drop and air medium
0009 - Very short volume names (Ct, Md, Dr) -- avoids eic-opticks buffer overflow
0010
0011 This uses the same geometry as eic-opticks's own raindrop test,
0012 but expressed as DD4hep compact XML instead of GDML.
0013
0014 Approach: standard G4OpticalPhysics (G4Cerenkov + G4Scintillation) runs on CPU.
0015 OpticsSteppingAction intercepts the processes and collects gensteps.
0016 OpticsEvent triggers GPU simulation and injects hits into DD4hep collections.
0017
0018 Prerequisites:
0019 - Spack environment activated (ROOT, DD4hep, eic-opticks on PYTHONPATH/LD_LIBRARY_PATH)
0020 - DD4hepINSTALL set (for elements.xml lookup)
0021 - libddeicopticks.so and libRaindropGeo.so on DD4HEP_LIBRARY_PATH
0022 """
0023 import os
0024 import sys
0025
0026 import DDG4
0027 from g4units import GeV, MeV, mm
0028
0029 _SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
0030
0031 def run():
0032 kernel = DDG4.Kernel()
0033
0034 compact_file = os.path.join(_SCRIPT_DIR, "geometry", "raindrop_dd4hep.xml")
0035 if not os.path.exists(compact_file):
0036 print(f"ERROR: Compact file not found: {compact_file}")
0037 sys.exit(1)
0038
0039 if "DD4hepINSTALL" not in os.environ:
0040 print("ERROR: DD4hepINSTALL not set. Activate the spack environment first.")
0041 sys.exit(1)
0042
0043 print(f"Loading geometry: {compact_file}")
0044 kernel.loadGeometry(str("file:" + compact_file))
0045
0046 geant4 = DDG4.Geant4(kernel)
0047 geant4.printDetectors()
0048
0049
0050 geant4.setupUI(typ='tcsh', vis=False, ui=False)
0051
0052
0053
0054 gun = geant4.setupGun(
0055 "Gun",
0056 particle='e-',
0057 energy=10 * MeV,
0058 position=(0, 0, 0),
0059 isotrop=False,
0060 direction=(1.0, 0.0, 0.0),
0061 multiplicity=1,
0062 )
0063
0064
0065
0066
0067 seq, act = geant4.setupTracker('Raindrop')
0068 filt = DDG4.Filter(kernel, 'EnergyDepositMinimumCut/OpticalOnly')
0069 filt.Cut = 1e12
0070 seq.adopt(filt)
0071
0072
0073 geant4.setupPhysics('QGSP_BERT')
0074 ph = DDG4.PhysicsList(kernel, 'Geant4PhysicsList/OpticalPhys')
0075 ph.addPhysicsConstructor(str('G4OpticalPhysics'))
0076 kernel.physicsList().adopt(ph)
0077
0078
0079
0080
0081 stepping = DDG4.SteppingAction(kernel, 'OpticsSteppingAction/OpticsStep1')
0082 stepping.Verbose = 1
0083 kernel.steppingAction().adopt(stepping)
0084
0085
0086 run_action = DDG4.RunAction(kernel, 'OpticsRun/OpticsRun1')
0087 run_action.SaveGeometry = False
0088 kernel.runAction().adopt(run_action)
0089
0090
0091 evt_action = DDG4.EventAction(kernel, 'OpticsEvent/OpticsEvt1')
0092 evt_action.Verbose = 1
0093 kernel.eventAction().adopt(evt_action)
0094
0095
0096 output_file = "/tmp/raindrop_hits.root"
0097 geant4.setupROOTOutput('RootOutput', output_file)
0098
0099
0100 kernel.NumEvents = 2
0101 kernel.configure()
0102 kernel.initialize()
0103 kernel.run()
0104 kernel.terminate()
0105
0106
0107 verify_hits(output_file)
0108
0109 def verify_hits(root_file):
0110 """Read back hits from the ROOT file and print summary."""
0111 import ROOT
0112 f = ROOT.TFile.Open(root_file)
0113 if not f or f.IsZombie():
0114 print(f"ERROR: Cannot open {root_file}")
0115 return
0116
0117 tree = f.Get("EVENT")
0118 if not tree:
0119 print("ERROR: No EVENT tree in ROOT file")
0120 f.Close()
0121 return
0122
0123 print(f"\n{'='*60}")
0124 print(f" Hit verification from {root_file}")
0125 print(f"{'='*60}")
0126 print(f" Events in file: {tree.GetEntries()}")
0127
0128
0129 branches = [b.GetName() for b in tree.GetListOfBranches()]
0130 print(f" Branches: {branches}")
0131
0132 hit_branches = [b for b in branches if 'hit' in b.lower() or 'Hit' in b]
0133 if not hit_branches:
0134 hit_branches = [b for b in branches if b not in ('RunNumber', 'EventNumber')]
0135
0136 for evt_idx in range(tree.GetEntries()):
0137 tree.GetEntry(evt_idx)
0138 print(f"\n Event {evt_idx}:")
0139 for bname in hit_branches:
0140 branch = tree.GetBranch(bname)
0141 leaf = branch.GetLeaf(bname)
0142 if hasattr(tree, bname):
0143 hits = getattr(tree, bname)
0144 nhits = hits.size() if hasattr(hits, 'size') else 0
0145 print(f" {bname}: {nhits} hits")
0146
0147 for i in range(min(3, nhits)):
0148 h = hits[i]
0149 pos = h.position
0150 mom = h.momentum
0151 print(f" [{i}] pos=({pos.x():.1f}, {pos.y():.1f}, {pos.z():.1f}) "
0152 f"mom=({mom.x():.3f}, {mom.y():.3f}, {mom.z():.3f}) "
0153 f"wavelength={h.length:.1f}nm "
0154 f"time={h.truth.time:.2f}ns")
0155
0156 f.Close()
0157 print(f"\n PASS: Hits successfully stored and retrieved via DD4hep")
0158 print(f"{'='*60}\n")
0159
0160 if __name__ == "__main__":
0161 run()