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