Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-01-09 09:26:48

0001 import os
0002 import multiprocessing
0003 from pathlib import Path
0004 import math
0005 import tempfile
0006 import shutil
0007 import pytest
0008 
0009 from helpers import (
0010     edm4hepEnabled,
0011     podioEnabled,
0012     AssertCollectionExistsAlg,
0013 )
0014 
0015 import acts
0016 from acts import UnitConstants as u
0017 
0018 from acts.examples import (
0019     Sequencer,
0020     GenericDetector,
0021 )
0022 
0023 from acts.examples.odd import getOpenDataDetector, getOpenDataDetectorDirectory
0024 
0025 
0026 def assert_podio(
0027     target_file: Path,
0028     category: str,
0029     collections: set[str] | None = None,
0030     nevents: int | None = None,
0031 ):
0032     assert target_file.exists(), f"File {target_file} does not exist"
0033     from podio.root_io import Reader
0034     import cppyy
0035 
0036     reader = Reader(str(target_file))
0037     assert (
0038         category in reader.categories
0039     ), f"Category {category} not found in {target_file} ({reader.categories})"
0040 
0041     if nevents is not None:
0042         assert (
0043             len(reader.get(category)) == nevents
0044         ), f"Expected {nevents} events in {target_file} ({category}) but got {len(reader.get(category))}"
0045 
0046     if collections is not None:
0047         for frame in reader.get(category):
0048             assert (
0049                 set(frame.getAvailableCollections()) == collections
0050             ), f"Expected collections {collections} in {target_file} ({category}) but got {frame.getAvailableCollections()}"
0051             break
0052 
0053 
0054 @pytest.mark.edm4hep
0055 @pytest.mark.skipif(not edm4hepEnabled, reason="EDM4hep is not set up")
0056 def test_edm4hep_measurement_writer(tmp_path, fatras):
0057     from acts.examples.edm4hep import EDM4hepMeasurementOutputConverter, PodioWriter
0058 
0059     s = Sequencer(numThreads=1, events=10)
0060     _, simAlg, digiAlg = fatras(s)
0061 
0062     out = tmp_path / "measurements_edm4hep.root"
0063 
0064     converter = EDM4hepMeasurementOutputConverter(
0065         level=acts.logging.VERBOSE,
0066         inputMeasurements=digiAlg.config.outputMeasurements,
0067         inputClusters=digiAlg.config.outputClusters,
0068         outputTrackerHitsPlane="tracker_hits_plane",
0069         outputTrackerHitsRaw="tracker_hits_raw",
0070     )
0071     s.addAlgorithm(converter)
0072 
0073     s.addWriter(
0074         PodioWriter(
0075             level=acts.logging.VERBOSE,
0076             outputPath=str(out),
0077             category="events",
0078             collections=converter.collections,
0079         )
0080     )
0081 
0082     s.run()
0083 
0084     assert os.path.isfile(out)
0085     assert os.stat(out).st_size > 10
0086 
0087     assert_podio(
0088         out,
0089         "events",
0090         collections=set(["tracker_hits_plane", "tracker_hits_raw"]),
0091         nevents=10,
0092     )
0093 
0094 
0095 @pytest.mark.edm4hep
0096 @pytest.mark.skipif(not edm4hepEnabled, reason="EDM4hep is not set up")
0097 def test_edm4hep_simhit_writer(tmp_path, fatras, conf_const):
0098     from acts.examples.edm4hep import EDM4hepSimHitOutputConverter, PodioWriter
0099 
0100     s = Sequencer(numThreads=1, events=10)
0101     _, simAlg, _ = fatras(s)
0102 
0103     out = tmp_path / "simhits_edm4hep.root"
0104 
0105     converter = EDM4hepSimHitOutputConverter(
0106         level=acts.logging.INFO,
0107         inputSimHits=simAlg.config.outputSimHits,
0108         outputSimTrackerHits="sim_tracker_hits",
0109     )
0110     s.addAlgorithm(converter)
0111 
0112     s.addWriter(
0113         PodioWriter(
0114             level=acts.logging.INFO,
0115             outputPath=str(out),
0116             category="events",
0117             collections=converter.collections,
0118         )
0119     )
0120     s.run()
0121 
0122     assert os.path.isfile(out)
0123     assert os.stat(out).st_size > 200
0124 
0125 
0126 @pytest.mark.edm4hep
0127 @pytest.mark.skipif(not edm4hepEnabled, reason="EDM4hep is not set up")
0128 def test_edm4hep_particle_writer(tmp_path, ptcl_gun):
0129     from acts.examples.edm4hep import EDM4hepParticleOutputConverter, PodioWriter
0130 
0131     s = Sequencer(numThreads=1, events=10)
0132     _, h3conv = ptcl_gun(s)
0133 
0134     out = tmp_path / "particles_edm4hep.root"
0135 
0136     out.mkdir()
0137 
0138     converter = EDM4hepParticleOutputConverter(
0139         acts.logging.INFO,
0140         inputParticles=h3conv.config.outputParticles,
0141         outputParticles="MCParticles",
0142     )
0143     s.addAlgorithm(converter)
0144 
0145     s.addWriter(
0146         PodioWriter(
0147             level=acts.logging.INFO,
0148             outputPath=str(out),
0149             category="events",
0150             collections=converter.collections,
0151         )
0152     )
0153 
0154     s.run()
0155 
0156     assert os.path.isfile(out)
0157     assert os.stat(out).st_size > 200
0158 
0159     assert_podio(out, "events", collections=set(["MCParticles"]), nevents=10)
0160 
0161 
0162 @pytest.mark.edm4hep
0163 @pytest.mark.skipif(not edm4hepEnabled, reason="EDM4hep is not set up")
0164 def test_edm4hep_particle_writer_separate_files(tmp_path, ptcl_gun):
0165     from acts.examples.edm4hep import EDM4hepParticleOutputConverter, PodioWriter
0166 
0167     threads = 2
0168     events = 6
0169 
0170     s = Sequencer(numThreads=threads, events=events)
0171     _, h3conv = ptcl_gun(s)
0172 
0173     out = tmp_path / "particles_edm4hep.root"
0174 
0175     converter = EDM4hepParticleOutputConverter(
0176         acts.logging.INFO,
0177         inputParticles=h3conv.config.outputParticles,
0178         outputParticles="MCParticles",
0179     )
0180     s.addAlgorithm(converter)
0181 
0182     s.addWriter(
0183         PodioWriter(
0184             level=acts.logging.INFO,
0185             outputPath=str(out),
0186             category="events",
0187             collections=converter.collections,
0188             separateFilesPerThread=True,
0189         )
0190     )
0191 
0192     s.run()
0193 
0194     produced_files = sorted(tmp_path.glob("particles_edm4hep_thread*.root"))
0195     assert 1 < len(produced_files) <= threads
0196 
0197     total_events = 0
0198 
0199     from podio.root_io import Reader
0200 
0201     for produced in produced_files:
0202         assert produced.exists()
0203         assert produced.stat().st_size > 200
0204         assert_podio(
0205             produced,
0206             "events",
0207             collections=set(["MCParticles"]),
0208             nevents=None,
0209         )
0210         reader = Reader(str(produced))
0211         total_events += len(reader.get("events"))
0212 
0213     assert total_events == events
0214     assert not out.exists()
0215 
0216 
0217 @pytest.mark.edm4hep
0218 @pytest.mark.skipif(not edm4hepEnabled, reason="EDM4hep is not set up")
0219 def test_edm4hep_multitrajectory_writer(tmp_path):
0220     from acts.examples.edm4hep import EDM4hepMultiTrajectoryOutputConverter, PodioWriter
0221 
0222     detector = GenericDetector()
0223     trackingGeometry = detector.trackingGeometry()
0224     field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T))
0225 
0226     from truth_tracking_kalman import runTruthTrackingKalman
0227 
0228     s = Sequencer(numThreads=1, events=10)
0229     runTruthTrackingKalman(
0230         trackingGeometry,
0231         field,
0232         digiConfigFile=Path(
0233             str(
0234                 Path(__file__).parent.parent.parent.parent
0235                 / "Examples/Configs/generic-digi-smearing-config.json"
0236             )
0237         ),
0238         outputDir=tmp_path,
0239         s=s,
0240     )
0241 
0242     s.addAlgorithm(
0243         acts.examples.TracksToTrajectories(
0244             level=acts.logging.INFO,
0245             inputTracks="tracks",
0246             outputTrajectories="trajectories",
0247         )
0248     )
0249 
0250     out = tmp_path / "trajectories_edm4hep.root"
0251 
0252     converter = EDM4hepMultiTrajectoryOutputConverter(
0253         level=acts.logging.VERBOSE,
0254         inputTrajectories="trajectories",
0255         inputMeasurementParticlesMap="measurement_particles_map",
0256         outputTracks="ActsTrajectories",
0257     )
0258     s.addAlgorithm(converter)
0259 
0260     s.addWriter(
0261         PodioWriter(
0262             level=acts.logging.VERBOSE,
0263             outputPath=str(out),
0264             category="events",
0265             collections=converter.collections,
0266         )
0267     )
0268     s.run()
0269 
0270     assert os.path.isfile(out)
0271     assert os.stat(out).st_size > 200
0272 
0273 
0274 @pytest.mark.edm4hep
0275 @pytest.mark.skipif(not edm4hepEnabled, reason="EDM4hep is not set up")
0276 def test_edm4hep_tracks_writer(tmp_path):
0277     from acts.examples.edm4hep import EDM4hepTrackOutputConverter, PodioWriter
0278 
0279     detector = GenericDetector()
0280     trackingGeometry = detector.trackingGeometry()
0281     field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T))
0282 
0283     from truth_tracking_kalman import runTruthTrackingKalman
0284 
0285     s = Sequencer(numThreads=1, events=10)
0286     runTruthTrackingKalman(
0287         trackingGeometry,
0288         field,
0289         digiConfigFile=Path(
0290             str(
0291                 Path(__file__).parent.parent.parent.parent
0292                 / "Examples/Configs/generic-digi-smearing-config.json"
0293             )
0294         ),
0295         outputDir=tmp_path,
0296         s=s,
0297     )
0298 
0299     out = tmp_path / "tracks_edm4hep.root"
0300 
0301     converter = EDM4hepTrackOutputConverter(
0302         level=acts.logging.VERBOSE,
0303         inputTracks="kf_tracks",
0304         outputTracks="ActsTracks",
0305         Bz=2 * u.T,
0306     )
0307     s.addAlgorithm(converter)
0308 
0309     s.addWriter(
0310         PodioWriter(
0311             level=acts.logging.VERBOSE,
0312             outputPath=str(out),
0313             category="events",
0314             collections=converter.collections,
0315         )
0316     )
0317     s.run()
0318 
0319     assert os.path.isfile(out), f"File {out} does not exist"
0320     assert os.stat(out).st_size > 200, f"File {out} is too small"
0321 
0322     if not podioEnabled:
0323         import warnings
0324 
0325         warnings.warn(
0326             "edm4hep output checks were skipped, because podio was not on the python path"
0327         )
0328         return
0329 
0330     from podio.root_io import Reader
0331     import cppyy
0332 
0333     reader = Reader(str(out))
0334 
0335     actual = []
0336 
0337     for frame in reader.get("events"):
0338         tracks = frame.get("ActsTracks")
0339         for track in tracks:
0340             actual.append(
0341                 (track.getChi2(), track.getNdf(), len(track.getTrackStates()))
0342             )
0343 
0344             locs = []
0345 
0346             perigee = None
0347             for ts in track.getTrackStates():
0348                 if ts.location == cppyy.gbl.edm4hep.TrackState.AtIP:
0349                     perigee = ts
0350                     continue
0351                 locs.append(ts.location)
0352 
0353                 rp = ts.referencePoint
0354                 r = math.sqrt(rp.x**2 + rp.y**2)
0355                 assert r > 25
0356 
0357             assert locs[0] == cppyy.gbl.edm4hep.TrackState.AtLastHit
0358             assert locs[-1] == cppyy.gbl.edm4hep.TrackState.AtFirstHit
0359 
0360             assert perigee is not None
0361             rp = perigee.referencePoint
0362             assert rp.x == 0.0
0363             assert rp.y == 0.0
0364             assert rp.z == 0.0
0365             assert abs(perigee.D0) < 1e0
0366             assert abs(perigee.Z0) < 1e1
0367 
0368 
0369 def generate_input_test_edm4hep_simhit_reader(input, output, particle_type):
0370     from DDSim.DD4hepSimulation import DD4hepSimulation
0371 
0372     ddsim = DD4hepSimulation()
0373 
0374     ddsim.random.seed = 37
0375 
0376     if isinstance(ddsim.compactFile, list):
0377         ddsim.compactFile = [input]
0378     else:
0379         ddsim.compactFile = input
0380     ddsim.enableGun = True
0381     ddsim.gun.direction = (1, 0, 0)
0382     ddsim.gun.particle = particle_type
0383     ddsim.gun.distribution = "eta"
0384     ddsim.gun.etaMin = -3.0
0385     ddsim.gun.etaMax = 3.0
0386     ddsim.numberOfEvents = 10
0387     ddsim.outputFile = output
0388     ddsim.outputConfig.forceEDM4HEP = True
0389     ddsim.run()
0390 
0391 
0392 # Session scoped fixture that uses a temp folder
0393 @pytest.fixture(scope="session", params=["mu-", "pi-"])
0394 def ddsim_input_session(request):
0395     particle_type = request.param
0396     with tempfile.TemporaryDirectory() as tmp_dir:
0397         odd_xml_file = str(
0398             getOpenDataDetectorDirectory() / "xml" / "OpenDataDetector.xml"
0399         )
0400 
0401         output_file = str(Path(tmp_dir) / "output_edm4hep.root")
0402 
0403         if not os.path.exists(output_file):
0404             # explicitly ask for "spawn" as CI failures were observed with "fork"
0405             spawn_context = multiprocessing.get_context("spawn")
0406             p = spawn_context.Process(
0407                 target=generate_input_test_edm4hep_simhit_reader,
0408                 args=(odd_xml_file, output_file, particle_type),
0409             )
0410             p.start()
0411             p.join()
0412             if p.exitcode != 0:
0413                 raise RuntimeError("ddsim process failed")
0414 
0415             assert os.path.exists(output_file)
0416 
0417         yield output_file
0418 
0419 
0420 # Function scoped fixture that uses a temp folder
0421 @pytest.fixture(scope="function")
0422 def ddsim_input(ddsim_input_session, tmp_path):
0423     tmp_file = str(tmp_path / "output_edm4hep.root")
0424     shutil.copy(ddsim_input_session, tmp_file)
0425     return tmp_file
0426 
0427 
0428 @pytest.mark.slow
0429 @pytest.mark.edm4hep
0430 @pytest.mark.skipif(not edm4hepEnabled, reason="EDM4hep is not set up")
0431 def test_edm4hep_simhit_particle_reader(tmp_path, ddsim_input):
0432     from acts.examples.edm4hep import EDM4hepSimInputConverter
0433     from acts.examples.edm4hep import PodioReader
0434 
0435     s = Sequencer(numThreads=1)
0436 
0437     with getOpenDataDetector() as detector:
0438         trackingGeometry = detector.trackingGeometry()
0439 
0440         s.addReader(
0441             PodioReader(
0442                 level=acts.logging.VERBOSE,
0443                 inputPath=ddsim_input,
0444                 outputFrame="events",
0445                 category="events",
0446             )
0447         )
0448 
0449         s.addAlgorithm(
0450             EDM4hepSimInputConverter(
0451                 level=acts.logging.DEBUG,
0452                 inputFrame="events",
0453                 inputSimHits=[
0454                     "PixelBarrelReadout",
0455                     "PixelEndcapReadout",
0456                     "ShortStripBarrelReadout",
0457                     "ShortStripEndcapReadout",
0458                     "LongStripBarrelReadout",
0459                     "LongStripEndcapReadout",
0460                 ],
0461                 outputParticlesGenerator="particles_generated",
0462                 outputParticlesSimulation="particles_simulated",
0463                 outputSimHits="simhits",
0464                 outputSimVertices="simvertices",
0465                 dd4hepDetector=detector,
0466                 trackingGeometry=trackingGeometry,
0467             )
0468         )
0469 
0470         alg = AssertCollectionExistsAlg(
0471             ["simhits", "simvertices", "particles_generated", "particles_simulated"],
0472             "check_alg",
0473             acts.logging.WARNING,
0474         )
0475         s.addAlgorithm(alg)
0476 
0477         alg = AssertCollectionExistsAlg(
0478             "particles_generated", "check_alg", acts.logging.WARNING
0479         )
0480         s.addAlgorithm(alg)
0481 
0482         s.run()
0483 
0484     assert alg.events_seen == 10
0485 
0486 
0487 @pytest.mark.edm4hep
0488 @pytest.mark.skipif(not edm4hepEnabled, reason="EDM4hep is not set up")
0489 def test_edm4hep_measurement_reader(tmp_path, fatras):
0490     from acts.examples.edm4hep import (
0491         EDM4hepMeasurementOutputConverter,
0492         EDM4hepMeasurementInputConverter,
0493     )
0494     from acts.examples.edm4hep import PodioWriter, PodioReader
0495 
0496     s = Sequencer(numThreads=1, events=10)
0497     _, simAlg, digiAlg = fatras(s)
0498 
0499     out = tmp_path / "measurements_edm4hep.root"
0500 
0501     converter = EDM4hepMeasurementOutputConverter(
0502         level=acts.logging.INFO,
0503         inputMeasurements=digiAlg.config.outputMeasurements,
0504         inputClusters=digiAlg.config.outputClusters,
0505     )
0506     s.addAlgorithm(converter)
0507     s.addWriter(
0508         PodioWriter(
0509             level=acts.logging.INFO,
0510             outputPath=str(out),
0511             category="events",
0512             collections=converter.collections,
0513         )
0514     )
0515     s.run()
0516 
0517     # read back in
0518     s = Sequencer(numThreads=1)
0519 
0520     s.addReader(
0521         PodioReader(
0522             level=acts.logging.WARNING,
0523             inputPath=str(out),
0524             outputFrame="events",
0525             category="events",
0526         )
0527     )
0528     s.addAlgorithm(
0529         EDM4hepMeasurementInputConverter(
0530             level=acts.logging.WARNING,
0531             inputFrame="events",
0532             outputMeasurements="measurements",
0533             outputMeasurementSimHitsMap="simhitsmap",
0534         )
0535     )
0536 
0537     alg = AssertCollectionExistsAlg(
0538         ["measurements", "simhitsmap"], "check_alg", acts.logging.WARNING
0539     )
0540     s.addAlgorithm(alg)
0541 
0542     s.run()
0543 
0544     assert alg.events_seen == 10
0545 
0546 
0547 @pytest.mark.edm4hep
0548 @pytest.mark.skipif(not edm4hepEnabled, reason="EDM4hep is not set up")
0549 def test_edm4hep_tracks_reader(tmp_path):
0550     from acts.examples.edm4hep import (
0551         EDM4hepTrackOutputConverter,
0552         EDM4hepTrackInputConverter,
0553     )
0554     from acts.examples.edm4hep import PodioWriter, PodioReader
0555 
0556     detector = acts.examples.GenericDetector()
0557     trackingGeometry = detector.trackingGeometry()
0558 
0559     field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T))
0560 
0561     from truth_tracking_kalman import runTruthTrackingKalman
0562 
0563     s = Sequencer(numThreads=1, events=10)
0564     runTruthTrackingKalman(
0565         trackingGeometry,
0566         field,
0567         digiConfigFile=Path(
0568             str(
0569                 Path(__file__).parent.parent.parent.parent
0570                 / "Examples/Configs/generic-digi-smearing-config.json"
0571             )
0572         ),
0573         outputDir=tmp_path,
0574         s=s,
0575     )
0576 
0577     out = tmp_path / "tracks_edm4hep.root"
0578 
0579     converter = EDM4hepTrackOutputConverter(
0580         level=acts.logging.VERBOSE,
0581         inputTracks="kf_tracks",
0582         Bz=2 * u.T,
0583     )
0584     s.addAlgorithm(converter)
0585 
0586     s.addWriter(
0587         PodioWriter(
0588             level=acts.logging.VERBOSE,
0589             outputPath=str(out),
0590             category="events",
0591             collections=converter.collections,
0592         )
0593     )
0594     s.run()
0595 
0596     s = Sequencer(numThreads=1)
0597 
0598     s.addReader(
0599         PodioReader(
0600             level=acts.logging.VERBOSE,
0601             inputPath=str(out),
0602             outputFrame="events",
0603             category="events",
0604         )
0605     )
0606 
0607     s.addAlgorithm(
0608         EDM4hepTrackInputConverter(
0609             level=acts.logging.VERBOSE,
0610             inputFrame="events",
0611             inputTracks="kf_tracks",
0612             outputTracks="kf_tracks",
0613             Bz=2 * u.T,
0614         )
0615     )
0616 
0617     s.run()
0618 
0619 
0620 @pytest.mark.edm4hep
0621 @pytest.mark.skipif(not edm4hepEnabled, reason="EDM4hep is not set up")
0622 def test_edm4hep_reader(ddsim_input):
0623     from acts.examples.edm4hep import PodioReader
0624 
0625     s = Sequencer(numThreads=1)
0626     s.addReader(
0627         PodioReader(
0628             level=acts.logging.VERBOSE,
0629             inputPath=ddsim_input,
0630             outputFrame="frame",
0631         )
0632     )
0633 
0634     alg = AssertCollectionExistsAlg("frame", "check_alg", acts.logging.WARNING)
0635     s.addAlgorithm(alg)
0636 
0637     s.run()
0638 
0639 
0640 @pytest.mark.edm4hep
0641 @pytest.mark.skipif(not edm4hepEnabled, reason="EDM4hep is not set up")
0642 def test_edm4hep_writer_copy(ddsim_input, tmp_path):
0643     from acts.examples.edm4hep import PodioWriter, PodioReader
0644 
0645     target_file = tmp_path / "rewritten_edm4hep.root"
0646 
0647     s = Sequencer(numThreads=1)
0648     s.addReader(
0649         PodioReader(
0650             level=acts.logging.VERBOSE,
0651             inputPath=ddsim_input,
0652             outputFrame="myframe",
0653             category="events",
0654         )
0655     )
0656     s.addWriter(
0657         PodioWriter(
0658             level=acts.logging.VERBOSE,
0659             inputFrame="myframe",
0660             outputPath=str(target_file),
0661             category="myevents",
0662         )
0663     )
0664 
0665     s.run()
0666 
0667     assert_podio(
0668         target_file,
0669         "myevents",
0670         nevents=10,
0671         collections=set(
0672             [
0673                 "ShortStripEndcapReadout",
0674                 "MCParticles",
0675                 "LongStripBarrelReadout",
0676                 "PixelEndcapReadout",
0677                 "HCalEndcapCollectionContributions",
0678                 "HCalEndcapCollection",
0679                 "HCalBarrelCollectionContributions",
0680                 "ECalBarrelCollectionContributions",
0681                 "LongStripEndcapReadout",
0682                 "EventHeader",
0683                 "ECalEndcapCollectionContributions",
0684                 "ShortStripBarrelReadout",
0685                 "PixelBarrelReadout",
0686                 "ECalEndcapCollection",
0687                 "HCalBarrelCollection",
0688                 "ECalBarrelCollection",
0689             ]
0690         ),
0691     )