File indexing completed on 2026-11-04 09:07:11
0001
0002
0003 import os
0004 import argparse
0005 import pathlib
0006
0007 import acts
0008 import acts.examples
0009 from acts.examples.simulation import (
0010 MomentumConfig,
0011 EtaConfig,
0012 PhiConfig,
0013 ParticleConfig,
0014 ParticleSelectorConfig,
0015 addParticleGun,
0016 addPythia8,
0017 addGenParticleSelection,
0018 addFatras,
0019 addGeant4,
0020 addSimParticleSelection,
0021 addDigitization,
0022 addDigiParticleSelection,
0023 )
0024 from acts.examples.reconstruction import (
0025 addSeeding,
0026 SeedingAlgorithm,
0027 GbtsConfigArg,
0028 CkfConfig,
0029 addCKFTracks,
0030 TrackSelectorConfig,
0031 addAmbiguityResolution,
0032 AmbiguityResolutionConfig,
0033 addAmbiguityResolutionML,
0034 AmbiguityResolutionMLConfig,
0035 addScoreBasedAmbiguityResolution,
0036 ScoreBasedAmbiguityResolutionConfig,
0037 addVertexFitting,
0038 VertexFinder,
0039 addSeedFilterML,
0040 SeedFilterMLDBScanConfig,
0041 )
0042 from acts.examples.odd import getOpenDataDetector, getOpenDataDetectorDirectory
0043
0044 u = acts.UnitConstants
0045
0046
0047 parser = argparse.ArgumentParser(description="Full chain with the OpenDataDetector")
0048 parser.add_argument(
0049 "--output",
0050 "-o",
0051 help="Output directory",
0052 type=pathlib.Path,
0053 default=pathlib.Path.cwd() / "odd_output",
0054 )
0055 parser.add_argument("--events", "-n", help="Number of events", type=int, default=100)
0056 parser.add_argument("--skip", "-s", help="Number of events", type=int, default=0)
0057 parser.add_argument(
0058 "--jobs",
0059 "-j",
0060 help="Number of worker threads (-1 uses all cores)",
0061 type=int,
0062 default=None,
0063 )
0064 parser.add_argument("--edm4hep", help="Use edm4hep inputs", type=pathlib.Path)
0065 parser.add_argument(
0066 "--geant4", help="Use Geant4 instead of fatras", action="store_true"
0067 )
0068 parser.add_argument(
0069 "--ttbar",
0070 help="Use Pythia8 (ttbar, pile-up 200) instead of particle gun",
0071 action="store_true",
0072 )
0073 parser.add_argument(
0074 "--ttbar-pu",
0075 help="Number of pile-up events for ttbar",
0076 type=int,
0077 default=200,
0078 )
0079 parser.add_argument(
0080 "--gun-particles",
0081 help="Multiplicity (no. of particles) of the particle gun",
0082 type=int,
0083 default=4,
0084 )
0085 parser.add_argument(
0086 "--gun-particle-type",
0087 help="Particle type of the particle gun; options: muon(default), electron, pion, kaon, proton",
0088 type=str,
0089 default="muon",
0090 )
0091 parser.add_argument(
0092 "--gun-multiplicity",
0093 help="Multiplicity (no. of vertices) of the particle gun",
0094 type=int,
0095 default=200,
0096 )
0097 parser.add_argument(
0098 "--gun-eta-range",
0099 nargs=2,
0100 help="Eta range of the particle gun",
0101 type=float,
0102 default=[-3.0, 3.0],
0103 )
0104 parser.add_argument(
0105 "--gun-pt-range",
0106 nargs=2,
0107 help="Pt range of the particle gun (GeV)",
0108 type=float,
0109 default=[1.0 * u.GeV, 10.0 * u.GeV],
0110 )
0111 parser.add_argument(
0112 "--digi-config", help="Digitization configuration file", type=pathlib.Path
0113 )
0114 parser.add_argument(
0115 "--material-config", help="Material map configuration file", type=pathlib.Path
0116 )
0117 parser.add_argument(
0118 "--ambi-solver",
0119 help="Set which ambiguity solver to use, default is the classical one",
0120 type=str,
0121 choices=["greedy", "scoring", "ML"],
0122 default="greedy",
0123 )
0124 parser.add_argument(
0125 "--ambi-config",
0126 help="Set the configuration file for the Score Based ambiguity resolution",
0127 type=pathlib.Path,
0128 default=pathlib.Path.cwd() / "ambi_config.json",
0129 )
0130
0131 parser.add_argument(
0132 "--MLSeedFilter",
0133 help="Use the Ml seed filter to select seed after the seeding step",
0134 action="store_true",
0135 )
0136 parser.add_argument(
0137 "--reco",
0138 help="Switch reco on/off",
0139 default=True,
0140 action=argparse.BooleanOptionalAction,
0141 )
0142 parser.add_argument(
0143 "--use-gbts",
0144 help="Use the Graph Based Track Seeding (GBTS) instead of triplet seeding",
0145 action="store_true",
0146 )
0147 parser.add_argument(
0148 "--gbts-layer-map",
0149 help="GBTS layer map configuration file",
0150 type=pathlib.Path,
0151 )
0152 parser.add_argument(
0153 "--gbts-connection-table",
0154 help="GBTS layer connection table configuration file",
0155 type=pathlib.Path,
0156 )
0157 parser.add_argument(
0158 "--output-root",
0159 help="Switch root output on/off",
0160 default=False,
0161 action=argparse.BooleanOptionalAction,
0162 )
0163 parser.add_argument(
0164 "--output-csv",
0165 help="Switch csv output on/off",
0166 default=False,
0167 action=argparse.BooleanOptionalAction,
0168 )
0169 parser.add_argument(
0170 "--output-obj",
0171 help="Switch obj output on/off",
0172 default=False,
0173 action=argparse.BooleanOptionalAction,
0174 )
0175 parser.add_argument(
0176 "--output-parquet",
0177 help="Switch parquet output on/off (requires ACTS_BUILD_EXAMPLES_PARQUET=ON)",
0178 default=False,
0179 action=argparse.BooleanOptionalAction,
0180 )
0181
0182 args = parser.parse_args()
0183
0184 outputDir = args.output
0185 ambi_ML = args.ambi_solver == "ML"
0186 ambi_scoring = args.ambi_solver == "scoring"
0187 ambi_config = args.ambi_config
0188 seedFilter_ML = args.MLSeedFilter
0189 geoDir = getOpenDataDetectorDirectory()
0190 actsDir = pathlib.Path(__file__).parent.parent.parent.parent
0191
0192
0193 oddMaterialMap = (
0194 args.material_config
0195 if args.material_config
0196 else geoDir / "data/odd-material-maps.root"
0197 )
0198
0199 oddDigiConfig = (
0200 args.digi_config
0201 if args.digi_config
0202 else actsDir / "Examples/Configs/odd-digi-smearing-config.json"
0203 )
0204
0205 oddGbtsLayerMap = (
0206 args.gbts_layer_map
0207 if args.gbts_layer_map
0208 else actsDir / "Examples/Configs/odd-gbts-layer-map.json"
0209 )
0210 oddGbtsConnectionTable = (
0211 args.gbts_connection_table
0212 if args.gbts_connection_table
0213 else actsDir / "Examples/Configs/odd-gbts-connection-table.json"
0214 )
0215
0216 oddSeedingSel = actsDir / "Examples/Configs/odd-seeding-config.json"
0217 oddMaterialDeco = acts.IMaterialDecorator.fromFile(oddMaterialMap)
0218
0219 detector = getOpenDataDetector(odd_dir=geoDir, materialDecorator=oddMaterialDeco)
0220 trackingGeometry = detector.trackingGeometry()
0221 decorators = detector.contextDecorators()
0222 field = acts.ConstantBField(acts.Vector3(0.0, 0.0, 2.0 * u.T))
0223 rnd = acts.examples.RandomNumbers(seed=42)
0224
0225 s = acts.examples.Sequencer(
0226 events=args.events,
0227 skip=args.skip,
0228 numThreads=args.jobs if args.jobs is not None else (1 if args.geant4 else -1),
0229 outputDir=str(outputDir),
0230 )
0231
0232 if args.edm4hep:
0233 import acts.examples.edm4hep
0234 from acts.examples.edm4hep import PodioReader
0235
0236 s.addReader(
0237 PodioReader(
0238 level=acts.logging.DEBUG,
0239 inputPath=str(args.edm4hep),
0240 outputFrame="events",
0241 category="events",
0242 )
0243 )
0244
0245 edm4hepReader = acts.examples.edm4hep.EDM4hepSimInputConverter(
0246 inputFrame="events",
0247 inputSimHits=[
0248 "PixelBarrelReadout",
0249 "PixelEndcapReadout",
0250 "ShortStripBarrelReadout",
0251 "ShortStripEndcapReadout",
0252 "LongStripBarrelReadout",
0253 "LongStripEndcapReadout",
0254 ],
0255 outputParticlesGenerator="particles_generated",
0256 outputParticlesSimulation="particles_simulated",
0257 outputSimHits="simhits",
0258 outputSimVertices="vertices_truth",
0259 dd4hepDetector=detector,
0260 trackingGeometry=trackingGeometry,
0261 sortSimHitsInTime=False,
0262 particleRMax=1080 * u.mm,
0263 particleZ=(-3030 * u.mm, 3030 * u.mm),
0264 particlePtMin=150 * u.MeV,
0265 level=acts.logging.DEBUG,
0266 )
0267 s.addAlgorithm(edm4hepReader)
0268
0269 s.addWhiteboardAlias("particles", edm4hepReader.config.outputParticlesSimulation)
0270
0271 addSimParticleSelection(
0272 s,
0273 ParticleSelectorConfig(
0274 rho=(0.0, 24 * u.mm),
0275 absZ=(0.0, 1.0 * u.m),
0276 eta=(-3.0, 3.0),
0277 removeNeutral=True,
0278 ),
0279 )
0280 else:
0281 if not args.ttbar:
0282 particleType = {
0283 "muon": acts.PdgParticle.eMuon,
0284 "electron": acts.PdgParticle.eElectron,
0285 "pion": acts.PdgParticle.ePionPlus,
0286 "kaon": acts.PdgParticle.eKaonPlus,
0287 "proton": acts.PdgParticle.eProton,
0288 }[args.gun_particle_type]
0289 addParticleGun(
0290 s,
0291 MomentumConfig(
0292 args.gun_pt_range[0] * u.GeV,
0293 args.gun_pt_range[1] * u.GeV,
0294 transverse=True,
0295 ),
0296 EtaConfig(args.gun_eta_range[0], args.gun_eta_range[1]),
0297 PhiConfig(0.0, 360.0 * u.degree),
0298 ParticleConfig(args.gun_particles, particleType, randomizeCharge=True),
0299 vtxGen=acts.examples.GaussianVertexGenerator(
0300 mean=acts.Vector4(0, 0, 0, 0),
0301 stddev=acts.Vector4(
0302 0.0125 * u.mm, 0.0125 * u.mm, 55.5 * u.mm, 1.0 * u.ns
0303 ),
0304 ),
0305 multiplicity=args.gun_multiplicity,
0306 rnd=rnd,
0307 )
0308 else:
0309 addPythia8(
0310 s,
0311 hardProcess=["Top:qqbar2ttbar=on"],
0312 npileup=args.ttbar_pu,
0313 vtxGen=acts.examples.GaussianVertexGenerator(
0314 mean=acts.Vector4(0, 0, 0, 0),
0315 stddev=acts.Vector4(
0316 0.0125 * u.mm, 0.0125 * u.mm, 55.5 * u.mm, 5.0 * u.ns
0317 ),
0318 ),
0319 rnd=rnd,
0320 outputDirRoot=outputDir if args.output_root else None,
0321 outputDirCsv=outputDir if args.output_csv else None,
0322 )
0323
0324 addGenParticleSelection(
0325 s,
0326 ParticleSelectorConfig(
0327 rho=(0.0, 24 * u.mm),
0328 absZ=(0.0, 1.0 * u.m),
0329 eta=(-3.0, 3.0),
0330 pt=(150 * u.MeV, None),
0331 ),
0332 )
0333
0334 if args.geant4:
0335 if s.config.numThreads != 1:
0336 raise ValueError("Geant 4 simulation does not support multi-threading")
0337
0338
0339
0340
0341 addGeant4(
0342 s,
0343 detector,
0344 trackingGeometry,
0345 field,
0346 outputDirRoot=outputDir if args.output_root else None,
0347 outputDirCsv=outputDir if args.output_csv else None,
0348 outputDirObj=outputDir if args.output_obj else None,
0349 rnd=rnd,
0350 killVolume=trackingGeometry.highestTrackingVolume,
0351 killAfterTime=25 * u.ns,
0352 )
0353 else:
0354 addFatras(
0355 s,
0356 trackingGeometry,
0357 field,
0358 enableInteractions=True,
0359 outputDirRoot=outputDir if args.output_root else None,
0360 outputDirCsv=outputDir if args.output_csv else None,
0361 outputDirObj=outputDir if args.output_obj else None,
0362 rnd=rnd,
0363 )
0364
0365 addDigitization(
0366 s,
0367 trackingGeometry,
0368 field,
0369 digiConfigFile=oddDigiConfig,
0370 outputDirRoot=outputDir if args.output_root else None,
0371 outputDirCsv=outputDir if args.output_csv else None,
0372 rnd=rnd,
0373 )
0374
0375 addDigiParticleSelection(
0376 s,
0377 ParticleSelectorConfig(
0378 pt=(1.0 * u.GeV, None),
0379 eta=(-3.0, 3.0),
0380 measurements=(9, None),
0381 removeNeutral=True,
0382 ),
0383 )
0384
0385 if args.reco:
0386 addSeeding(
0387 s,
0388 trackingGeometry,
0389 field,
0390 **(
0391 dict(
0392 seedingAlgorithm=SeedingAlgorithm.Gbts,
0393 layerMappingConfigFile=oddGbtsLayerMap,
0394 connectorInputConfigFile=oddGbtsConnectionTable,
0395
0396
0397 gbtsConfigArg=GbtsConfigArg(
0398 seederConfig=acts.examples.GraphBasedSeedingConfig(
0399 minSeedLevel=2,
0400 ),
0401 graphConfig=acts.examples.GbtsGraphBuilderConfig(
0402 minPt=0.9 * u.GeV,
0403 z0HistogramMaxBarrelOrder=-1,
0404 ),
0405 useAdaptiveCuts=False,
0406 ),
0407 gbtsSeedParameters=True,
0408
0409 paramEstimationSpacePoints=acts.examples.SeedSpacePointSelection.SpreadTriplet,
0410 )
0411 if args.use_gbts
0412 else {}
0413 ),
0414 initialSigmas=[
0415 1 * u.mm,
0416 1 * u.mm,
0417 1 * u.degree,
0418 1 * u.degree,
0419 0 * u.e / u.GeV,
0420 1 * u.ns,
0421 ],
0422 initialSigmaQoverPt=0.1 * u.e / u.GeV,
0423 initialSigmaPtRel=0.1,
0424 initialVarInflation=[1.0] * 6,
0425 particleHypothesis=acts.ParticleHypothesis.muon,
0426 geoSelectionConfigFile=oddSeedingSel,
0427 outputDirRoot=outputDir if args.output_root else None,
0428 outputDirCsv=outputDir if args.output_csv else None,
0429 )
0430
0431 if seedFilter_ML:
0432 addSeedFilterML(
0433 s,
0434 SeedFilterMLDBScanConfig(
0435 epsilonDBScan=0.03, minPointsDBScan=2, minSeedScore=0.1
0436 ),
0437 onnxModelFile=os.path.dirname(__file__)
0438 + "/MLAmbiguityResolution/seedDuplicateClassifier.onnx",
0439 outputDirRoot=outputDir if args.output_root else None,
0440 outputDirCsv=outputDir if args.output_csv else None,
0441 )
0442
0443 addCKFTracks(
0444 s,
0445 trackingGeometry,
0446 field,
0447 TrackSelectorConfig(
0448 pt=(1.0 * u.GeV if args.ttbar else 0.0, None),
0449 absEta=(None, 3.0),
0450 loc0=(-4.0 * u.mm, 4.0 * u.mm),
0451 nMeasurementsMin=7,
0452 maxHoles=2,
0453 maxOutliers=2,
0454 ),
0455 CkfConfig(
0456 chi2CutOffMeasurement=15.0,
0457 chi2CutOffOutlier=25.0,
0458 numMeasurementsCutOff=2,
0459 seedDeduplication=True,
0460 stayOnSeed=True,
0461 pixelVolumes=[16, 17, 18],
0462 stripVolumes=[23, 24, 25],
0463 maxPixelHoles=1,
0464 maxStripHoles=2,
0465 constrainToVolumes=[
0466 2,
0467 32,
0468 4,
0469 16,
0470 17,
0471 18,
0472 20,
0473 23,
0474 24,
0475 25,
0476 26,
0477 8,
0478 28,
0479 29,
0480 30,
0481 ],
0482 ),
0483 outputDirRoot=outputDir if args.output_root else None,
0484 outputDirCsv=outputDir if args.output_csv else None,
0485 writeCovMat=True,
0486 )
0487
0488 if ambi_ML:
0489 addAmbiguityResolutionML(
0490 s,
0491 AmbiguityResolutionMLConfig(
0492 maximumSharedHits=3, maximumIterations=1000000, nMeasurementsMin=7
0493 ),
0494 outputDirRoot=outputDir if args.output_root else None,
0495 outputDirCsv=outputDir if args.output_csv else None,
0496 onnxModelFile=os.path.dirname(__file__)
0497 + "/MLAmbiguityResolution/duplicateClassifier.onnx",
0498 )
0499
0500 elif ambi_scoring:
0501 addScoreBasedAmbiguityResolution(
0502 s,
0503 ScoreBasedAmbiguityResolutionConfig(
0504 minScore=0,
0505 minScoreSharedTracks=1,
0506 maxShared=2,
0507 minUnshared=3,
0508 maxSharedTracksPerMeasurement=2,
0509 useAmbiguityScoring=False,
0510 ),
0511 outputDirRoot=outputDir if args.output_root else None,
0512 outputDirCsv=outputDir if args.output_csv else None,
0513 ambiVolumeFile=ambi_config,
0514 writeCovMat=True,
0515 )
0516 else:
0517 addAmbiguityResolution(
0518 s,
0519 AmbiguityResolutionConfig(
0520 maximumSharedHits=3, maximumIterations=1000000, nMeasurementsMin=7
0521 ),
0522 outputDirRoot=outputDir if args.output_root else None,
0523 outputDirCsv=outputDir if args.output_csv else None,
0524 writeCovMat=True,
0525 )
0526
0527 addVertexFitting(
0528 s,
0529 field,
0530 vertexFinder=VertexFinder.AMVF,
0531 outputDirRoot=outputDir if args.output_root else None,
0532 outputDirCsv=outputDir if args.output_csv else None,
0533 )
0534
0535 if args.output_parquet:
0536 try:
0537 from acts.arrow import (
0538 particleSchema,
0539 simHitSchema,
0540 trackSchema,
0541 )
0542 from acts.examples.arrow import (
0543 ArrowParticleOutputConverter,
0544 ArrowSimHitOutputConverter,
0545 ArrowTrackOutputConverter,
0546 makeVolumeIdDetectorResolver,
0547 ParquetWriter,
0548 )
0549 except ImportError as e:
0550 raise RuntimeError(
0551 "Parquet output requested but acts.examples.arrow is not available; "
0552 "rebuild with ACTS_BUILD_EXAMPLES_PARQUET=ON."
0553 ) from e
0554
0555
0556
0557 _odd_detector_resolver = makeVolumeIdDetectorResolver(
0558 {
0559 7: 0,
0560 8: 1,
0561 9: 2,
0562 12: 3,
0563 13: 4,
0564 14: 5,
0565 16: 6,
0566 17: 7,
0567 18: 8,
0568 },
0569 255,
0570 )
0571
0572
0573
0574 arrParticleConv = ArrowParticleOutputConverter(
0575 level=acts.logging.INFO,
0576 inputParticles="particles_simulated",
0577 outputTable="particles_arrow",
0578 )
0579 s.addAlgorithm(arrParticleConv)
0580
0581 arrSimHitConv = ArrowSimHitOutputConverter(
0582 level=acts.logging.INFO,
0583 inputSimHits="simhits",
0584 inputParticles="particles_simulated",
0585 inputClusters="clusters",
0586 inputSimHitMeasurementsMap="simhit_measurements_map",
0587 outputTable="simhits_arrow",
0588 detectorResolver=_odd_detector_resolver,
0589 )
0590 s.addAlgorithm(arrSimHitConv)
0591
0592 if args.reco:
0593 arrTrackConv = ArrowTrackOutputConverter(
0594 level=acts.logging.INFO,
0595 inputTracks="tracks",
0596 inputTrackParticleMatching="track_particle_matching",
0597 inputParticles="particles_simulated",
0598 inputMeasurementSimHitsMap="measurement_simhits_map",
0599 outputTable="tracks_arrow",
0600 )
0601 s.addAlgorithm(arrTrackConv)
0602
0603 s.addWriter(
0604 ParquetWriter(
0605 level=acts.logging.INFO,
0606 outputDir=str(outputDir),
0607 collections={
0608 arrSimHitConv.config.outputTable: "simhits",
0609 arrTrackConv.config.outputTable: "tracks",
0610 arrParticleConv.config.outputTable: "particles",
0611 },
0612 expectedSchemas={
0613 arrSimHitConv.config.outputTable: simHitSchema(),
0614 arrTrackConv.config.outputTable: trackSchema(),
0615 arrParticleConv.config.outputTable: particleSchema(),
0616 },
0617 )
0618 )
0619
0620 s.run()