Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-15 07:41:49

0001 #!/usr/bin/env python3
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     # Batch mode
0050     geant4.setupUI(typ='tcsh', vis=False, ui=False)
0051 
0052     # Particle gun: e- at 10 MeV starting inside water box (50mm half-size)
0053     # Low energy so electron stops inside the water volume
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     # Register sensitive detector -- creates DD4hep hit collection for Raindrop.
0065     # Add EnergyDepositMinimumCut filter to block G4Step hits from charged
0066     # particles; only GPU optical photon hits injected by OpticsEvent pass.
0067     seq, act = geant4.setupTracker('Raindrop')
0068     filt = DDG4.Filter(kernel, 'EnergyDepositMinimumCut/OpticalOnly')
0069     filt.Cut = 1e12  # 1 TeV -- effectively blocks all G4Step hits
0070     seq.adopt(filt)
0071 
0072     # Physics: QGSP_BERT + standard G4OpticalPhysics (G4Cerenkov + G4Scintillation)
0073     geant4.setupPhysics('QGSP_BERT')
0074     ph = DDG4.PhysicsList(kernel, 'Geant4PhysicsList/OpticalPhys')
0075     ph.addPhysicsConstructor(str('G4OpticalPhysics'))
0076     kernel.physicsList().adopt(ph)
0077 
0078     # --- eic-opticks GPU plugins ---
0079 
0080     # SteppingAction: intercepts G4Cerenkov/G4Scintillation and collects gensteps
0081     stepping = DDG4.SteppingAction(kernel, 'OpticsSteppingAction/OpticsStep1')
0082     stepping.Verbose = 1
0083     kernel.steppingAction().adopt(stepping)
0084 
0085     # RunAction: initializes/finalizes G4CXOpticks geometry
0086     run_action = DDG4.RunAction(kernel, 'OpticsRun/OpticsRun1')
0087     run_action.SaveGeometry = False
0088     kernel.runAction().adopt(run_action)
0089 
0090     # EventAction: triggers GPU simulation, injects hits
0091     evt_action = DDG4.EventAction(kernel, 'OpticsEvent/OpticsEvt1')
0092     evt_action.Verbose = 1
0093     kernel.eventAction().adopt(evt_action)
0094 
0095     # Save hits to ROOT file via DD4hep's standard output mechanism
0096     output_file = "/tmp/raindrop_hits.root"
0097     geant4.setupROOTOutput('RootOutput', output_file)
0098 
0099     # Run 2 events
0100     kernel.NumEvents = 2
0101     kernel.configure()
0102     kernel.initialize()
0103     kernel.run()
0104     kernel.terminate()
0105 
0106     # Verify: read back hits from ROOT file
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     # List branches
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                 # Print first 3 hits
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()