File indexing completed on 2026-10-30 08:28:14
0001
0002
0003 import sys, os, argparse, pathlib
0004
0005
0006 def parse_args():
0007 from acts.examples.reconstruction import SeedingAlgorithm
0008
0009 parser = argparse.ArgumentParser(description="""
0010 Script to test the full chain ACTS simulation and reconstruction.
0011
0012 This script is provided for interactive developer testing only.
0013 It is not intended (and not supported) for end user use, automated testing,
0014 and certainly should never be called in production. The Python API is the
0015 proper way to access the ActsExamples from scripts. The other Examples/Scripts
0016 are much better examples of how to do that. physmon in the CI is the proper
0017 way to do automated integration tests. This script is only for the case of
0018 interactive testing with one-off configuration specified by command-line options.
0019 """)
0020 parser.add_argument(
0021 "-G",
0022 "--generic-detector",
0023 action="store_true",
0024 help="Use generic detector geometry and config",
0025 )
0026 parser.add_argument(
0027 "--odd",
0028 default=True,
0029 action=argparse.BooleanOptionalAction,
0030 help="Use Open Data Detector geometry and config (default unless overridden by -G or -A). Requires ACTS_BUILD_ODD.",
0031 )
0032 parser.add_argument(
0033 "-A",
0034 "--itk",
0035 action="store_true",
0036 help="Use ATLAS ITk geometry and config. Requires acts-itk/ in current directory.",
0037 )
0038 parser.add_argument(
0039 "-g",
0040 "--geant4",
0041 action="store_true",
0042 help="Use Geant4 instead of Fatras for detector simulation",
0043 )
0044 parser.add_argument(
0045 "--edm4hep",
0046 type=pathlib.Path,
0047 help="Use edm4hep inputs",
0048 )
0049 parser.add_argument(
0050 "-b",
0051 "--bf-constant",
0052 action="store_true",
0053 help="Use constant 2T B-field also for ITk; and don't include material map",
0054 )
0055 parser.add_argument(
0056 "-j",
0057 "--threads",
0058 type=int,
0059 default=-1,
0060 help="Number of parallel threads, negative for automatic (default).",
0061 )
0062 parser.add_argument(
0063 "-o",
0064 "--output-dir",
0065 "--output",
0066 default=None,
0067 type=pathlib.Path,
0068 help="Directory to write outputs to",
0069 )
0070 parser.add_argument(
0071 "-O",
0072 "--output-detail",
0073 action="count",
0074 default=0,
0075 help="fewer output files. Use -OO for more output files. Use -OOO to disable all output.",
0076 )
0077 parser.add_argument(
0078 "-c",
0079 "--output-csv",
0080 action="count",
0081 default=0,
0082 help="Use CSV output instead of ROOT. Specify -cc to output all formats (ROOT, CSV, and obj).",
0083 )
0084 parser.add_argument(
0085 "--output-obj",
0086 action="store_true",
0087 help="Enable obj output",
0088 )
0089 parser.add_argument(
0090 "-n",
0091 "--events",
0092 type=int,
0093 default=100,
0094 help="The number of events to process (default=%(default)d).",
0095 )
0096 parser.add_argument(
0097 "-s",
0098 "--skip",
0099 type=int,
0100 default=0,
0101 help="Number of events to skip (default=%(default)d)",
0102 )
0103
0104
0105 parser.add_argument(
0106 "-N",
0107 "--gen-nparticles",
0108 "--gun-particles",
0109 type=int,
0110 default=4,
0111 help="Number of generated particles per vertex from the particle gun (default=%(default)d).",
0112 )
0113 parser.add_argument(
0114 "-M",
0115 "--gen-nvertices",
0116 "--gun-multiplicity",
0117 "--ttbar-pu",
0118 type=int,
0119 default=200,
0120 help="Number of vertices per event (multiplicity) from the particle gun; or number of pileup events (default=%(default)d)",
0121 )
0122 parser.add_argument(
0123 "-t",
0124 "--ttbar-pu200",
0125 "--ttbar",
0126 action="store_true",
0127 help="Generate ttbar + mu=200 pile-up using Pythia8",
0128 )
0129 parser.add_argument(
0130 "-p",
0131 "--gen-pt-range",
0132 "--gun-pt-range",
0133 default="1:10",
0134 help="transverse momentum (pT) range (min:max) of the particle gun in GeV (default=%(default)s)",
0135 )
0136 parser.add_argument(
0137 "--gen-eta-range",
0138 "--gun-eta-range",
0139 help="Eta range (min:max) of the particle gun (default -2:2 (Generic), -3:3 (ODD), -4:4 (ITk))",
0140 )
0141 parser.add_argument(
0142 "--gen-cos-theta",
0143 action="store_true",
0144 help="Sample eta as cos(theta) and not uniform",
0145 )
0146 parser.add_argument(
0147 "-r",
0148 "--random-seed",
0149 type=int,
0150 default=42,
0151 help="Random number seed (default=%(default)d)",
0152 )
0153 parser.add_argument(
0154 "-F",
0155 "--disable-fpemon",
0156 action="store_true",
0157 help="sets ACTS_SEQUENCER_DISABLE_FPEMON=1",
0158 )
0159 parser.add_argument(
0160 "-l",
0161 "--loglevel",
0162 type=int,
0163 default=2,
0164 help="The output log level. Please set the wished number (0 = VERBOSE, 1 = DEBUG, 2 = INFO (default), 3 = WARNING, 4 = ERROR, 5 = FATAL).",
0165 )
0166 parser.add_argument(
0167 "-d",
0168 "--dump-args-calls",
0169 action="store_true",
0170 help="Show pybind function call details",
0171 )
0172 parser.add_argument(
0173 "--digi-config",
0174 type=pathlib.Path,
0175 help="Digitization configuration file",
0176 )
0177 parser.add_argument(
0178 "--material-config",
0179 type=pathlib.Path,
0180 help="Material map configuration file",
0181 )
0182 parser.add_argument(
0183 "-S",
0184 "--seeding-algorithm",
0185 action=EnumAction,
0186 enum=SeedingAlgorithm,
0187 default=SeedingAlgorithm.GridTriplet,
0188 help="Select the seeding algorithm to use",
0189 )
0190 parser.add_argument(
0191 "--gbts-layer-map",
0192 type=pathlib.Path,
0193 help="GBTS layer map file (default: the detector's shipped one)",
0194 )
0195 parser.add_argument(
0196 "--gbts-connection-table",
0197 type=pathlib.Path,
0198 help="GBTS layer connection table file (default: the detector's shipped one)",
0199 )
0200 parser.add_argument(
0201 "--ckf",
0202 default=True,
0203 action=argparse.BooleanOptionalAction,
0204 help="Switch CKF on/off",
0205 )
0206 parser.add_argument(
0207 "--reco",
0208 default=True,
0209 action=argparse.BooleanOptionalAction,
0210 help="Switch reco on/off",
0211 )
0212 parser.add_argument(
0213 "--vertexing",
0214 default=True,
0215 action=argparse.BooleanOptionalAction,
0216 help="Switch vertexing on/off",
0217 )
0218 parser.add_argument(
0219 "--MLSeedFilter",
0220 action="store_true",
0221 help="Use the ML seed filter to select seed after the seeding step",
0222 )
0223 parser.add_argument(
0224 "--ambi-solver",
0225 type=str,
0226 choices=["greedy", "scoring", "ML", "none"],
0227 default="greedy",
0228 help="Set which ambiguity solver to use (default=%(default)s)",
0229 )
0230 parser.add_argument(
0231 "--ambi-config",
0232 type=pathlib.Path,
0233 default=pathlib.Path.cwd() / "ambi_config.json",
0234 help="Set the configuration file for the Score Based ambiguity resolution (default=%(default)s)",
0235 )
0236 return parser.parse_args()
0237
0238
0239 def full_chain(args):
0240 import acts, acts.root
0241
0242
0243 global detector, trackingGeometry, decorators, field, rnd
0244 global logger
0245
0246 if args.disable_fpemon:
0247 os.environ["ACTS_SEQUENCER_DISABLE_FPEMON"] = "1"
0248
0249 if args.dump_args_calls:
0250 acts.examples.dump_args_calls()
0251
0252 logger = acts.getDefaultLogger("full_chain_test", acts.logging.Level(args.loglevel))
0253
0254 nDetArgs = [args.generic_detector, args.odd, args.itk].count(True)
0255 if nDetArgs == 0:
0256 args.generic_detector = True
0257 elif nDetArgs == 2:
0258 args.odd = False
0259 nDetArgs = [args.generic_detector, args.odd, args.itk].count(True)
0260 if nDetArgs != 1:
0261 logger.fatal("require exactly one of: --generic-detector --odd --itk")
0262 sys.exit(2)
0263 if args.generic_detector:
0264 detname = "gen"
0265 elif args.itk:
0266 detname = "itk"
0267 elif args.odd:
0268 detname = "odd"
0269
0270 u = acts.UnitConstants
0271
0272 if args.output_detail == 3:
0273 outputDirLess = None
0274 elif args.output_dir is None:
0275 outputDirLess = pathlib.Path.cwd() / f"{detname}_output"
0276 else:
0277 outputDirLess = args.output_dir
0278
0279 outputDir = None if args.output_detail == 1 else outputDirLess
0280 outputDirMore = None if args.output_detail in (0, 1) else outputDirLess
0281
0282 outputDirRoot = outputDir if args.output_csv != 1 else None
0283 outputDirLessRoot = outputDirLess if args.output_csv != 1 else None
0284 outputDirMoreRoot = outputDirMore if args.output_csv != 1 else None
0285 outputDirCsv = outputDir if args.output_csv != 0 else None
0286 outputDirLessCsv = outputDirLess if args.output_csv != 0 else None
0287 outputDirMoreCsv = outputDirMore if args.output_csv != 0 else None
0288 outputDirObj = (
0289 outputDirLess
0290 if args.output_obj
0291 else outputDir if args.output_csv == 2 else None
0292 )
0293
0294 actsDir = pathlib.Path(__file__).resolve().parent.parent.parent.parent
0295
0296
0297 if args.generic_detector:
0298 etaRange = (-2.0, 2.0)
0299 ptMin = 0.5 * u.GeV
0300 rhoMax = 24.0 * u.mm
0301 if args.loglevel <= 2:
0302 logger.info(f"Load Generic Detector from {actsDir}")
0303 if args.digi_config is None:
0304 args.digi_config = actsDir / "Examples/Configs/generic-digi-smearing-config.json"
0305 seedingConfigFile = actsDir / "Examples/Configs/generic-seeding-config.json"
0306 if args.gbts_layer_map is None:
0307 args.gbts_layer_map = actsDir / "Examples/Configs/generic-gbts-layer-map.json"
0308 if args.gbts_connection_table is None:
0309 args.gbts_connection_table = actsDir / "Examples/Configs/generic-gbts-connection-table.json"
0310 args.bf_constant = True
0311 detector = acts.examples.GenericDetector()
0312 trackingGeometry = detector.trackingGeometry()
0313 decorators = detector.contextDecorators()
0314 elif args.odd:
0315 import acts.examples.odd
0316 etaRange = (-3.0, 3.0)
0317 ptMin = 1.0 * u.GeV
0318 rhoMax = 24.0 * u.mm
0319 geoDir = acts.examples.odd.getOpenDataDetectorDirectory()
0320 if args.loglevel <= 2:
0321 logger.info(f"Load Open Data Detector from {geoDir.resolve()}")
0322 if args.digi_config is None:
0323 args.digi_config = actsDir / "Examples/Configs/odd-digi-smearing-config.json"
0324 seedingConfigFile = actsDir / "Examples/Configs/odd-seeding-config.json"
0325 if args.gbts_layer_map is None:
0326 args.gbts_layer_map = actsDir / "Examples/Configs/odd-gbts-layer-map.json"
0327 if args.gbts_connection_table is None:
0328 args.gbts_connection_table = actsDir / "Examples/Configs/odd-gbts-connection-table.json"
0329 if args.material_config is None:
0330 args.material_config = geoDir / "data/odd-material-maps.root"
0331 args.bf_constant = True
0332 detector = acts.examples.odd.getOpenDataDetector(
0333 odd_dir=geoDir,
0334 materialDecorator=acts.IMaterialDecorator.fromFile(args.material_config),
0335 )
0336 trackingGeometry = detector.trackingGeometry()
0337 decorators = detector.contextDecorators()
0338 elif args.itk:
0339 import acts.examples.itk as itk
0340 etaRange = (-4.0, 4.0)
0341 ptMin = 1.0 * u.GeV
0342 rhoMax = 28.0 * u.mm
0343 geoDir = pathlib.Path("acts-itk")
0344 if args.loglevel <= 2:
0345 logger.info(f"Load ATLAS ITk from {geoDir.resolve()}")
0346 if args.digi_config is None:
0347 args.digi_config = geoDir / "itk-hgtd/itk-smearing-config.json"
0348 seedingConfigFile = geoDir / "itk-hgtd/geoSelection-ITk.json"
0349 if args.gbts_layer_map is None:
0350 args.gbts_layer_map = geoDir / "itk-hgtd/gbts_layers.json"
0351 if args.gbts_connection_table is None:
0352 args.gbts_connection_table = geoDir / "itk-hgtd/gbts_connections.json"
0353
0354 bFieldFile = geoDir / "bfield/ATLAS-BField-xyz.root"
0355 detector = itk.buildITkGeometry(
0356 geoDir,
0357 customMaterialFile=args.material_config,
0358 material=not args.bf_constant,
0359 logLevel=acts.logging.Level(args.loglevel),
0360 )
0361 trackingGeometry = detector.trackingGeometry()
0362 decorators = detector.contextDecorators()
0363
0364
0365 if args.bf_constant:
0366 field = acts.ConstantBField(acts.Vector3(0.0, 0.0, 2.0 * u.T))
0367 else:
0368 logger.info("Create magnetic field map from {}", bFieldFile)
0369 field = acts.root.MagneticFieldMapXyz(str(bFieldFile))
0370 rnd = acts.examples.RandomNumbers(seed=args.random_seed)
0371
0372 from acts.examples.simulation import (
0373 MomentumConfig,
0374 EtaConfig,
0375 PhiConfig,
0376 ParticleConfig,
0377 ParticleSelectorConfig,
0378 addDigitization,
0379 addGenParticleSelection,
0380 addSimParticleSelection,
0381 addDigiParticleSelection,
0382 )
0383
0384 s = acts.examples.Sequencer(
0385 events=args.events,
0386 skip=args.skip,
0387 numThreads=args.threads if not (args.geant4 and args.threads == -1) else 1,
0388 logLevel=acts.logging.Level(args.loglevel),
0389 outputDir="" if outputDirLess is None else str(outputDirLess),
0390 )
0391
0392
0393 for d in decorators:
0394 s.addContextDecorator(d)
0395
0396 if args.edm4hep:
0397 import acts.examples.edm4hep
0398
0399 s.addReader(
0400 acts.examples.edm4hep.PodioReader(
0401 level=acts.logging.DEBUG,
0402 inputPath=str(args.edm4hep),
0403 outputFrame="events",
0404 category="events",
0405 )
0406 )
0407
0408 edm4hepReader = acts.examples.edm4hep.EDM4hepSimInputConverter(
0409 inputFrame="events",
0410 inputSimHits=[
0411 "PixelBarrelReadout",
0412 "PixelEndcapReadout",
0413 "ShortStripBarrelReadout",
0414 "ShortStripEndcapReadout",
0415 "LongStripBarrelReadout",
0416 "LongStripEndcapReadout",
0417 ],
0418 outputParticlesGenerator="particles_generated",
0419 outputParticlesSimulation="particles_simulated",
0420 outputSimHits="simhits",
0421 outputSimVertices="vertices_truth",
0422 dd4hepDetector=detector,
0423 trackingGeometry=trackingGeometry,
0424 sortSimHitsInTime=False,
0425 particleRMax=1080 * u.mm,
0426 particleZ=(-3030 * u.mm, 3030 * u.mm),
0427 particlePtMin=150 * u.MeV,
0428 level=acts.logging.DEBUG,
0429 )
0430 s.addAlgorithm(edm4hepReader)
0431
0432 s.addWhiteboardAlias(
0433 "particles", edm4hepReader.config.outputParticlesSimulation
0434 )
0435
0436 addSimParticleSelection(
0437 s,
0438 ParticleSelectorConfig(
0439 rho=(0.0 * u.mm, rhoMax),
0440 absZ=(0.0 * u.mm, 1.0 * u.m),
0441 eta=etaRange,
0442 removeNeutral=True,
0443 ),
0444 )
0445
0446 else:
0447
0448 if not args.ttbar_pu200:
0449 from acts.examples.simulation import addParticleGun
0450
0451 addParticleGun(
0452 s,
0453 MomentumConfig(
0454 *strToRange(args.gen_pt_range, "--gen-pt-range", u.GeV),
0455 transverse=True,
0456 ),
0457 EtaConfig(
0458 *(
0459 strToRange(args.gen_eta_range, "--gen-eta-range")
0460 if args.gen_eta_range
0461 else etaRange
0462 ),
0463 uniform=(
0464 not args.gen_cos_theta
0465 if args.gen_cos_theta or not args.odd
0466 else None
0467 ),
0468 ),
0469 PhiConfig(0.0, 360.0 * u.degree) if not args.itk else PhiConfig(),
0470 ParticleConfig(
0471 args.gen_nparticles, acts.PdgParticle.eMuon, randomizeCharge=True
0472 ),
0473 vtxGen=(
0474 acts.examples.GaussianVertexGenerator(
0475 mean=acts.Vector4(0, 0, 0, 0),
0476 stddev=acts.Vector4(
0477 0.0125 * u.mm, 0.0125 * u.mm, 55.5 * u.mm, 1.0 * u.ns
0478 ),
0479 )
0480 if args.odd
0481 else None
0482 ),
0483 multiplicity=args.gen_nvertices,
0484 rnd=rnd,
0485 outputDirRoot=outputDirMoreRoot,
0486 outputDirCsv=outputDirMoreCsv,
0487 )
0488 else:
0489 from acts.examples.simulation import addPythia8
0490
0491 addPythia8(
0492 s,
0493 hardProcess=["Top:qqbar2ttbar=on"],
0494 npileup=args.gen_nvertices,
0495 vtxGen=acts.examples.GaussianVertexGenerator(
0496 stddev=acts.Vector4(
0497 0.0125 * u.mm, 0.0125 * u.mm, 55.5 * u.mm, 5.0 * u.ns
0498 ),
0499 mean=acts.Vector4(0, 0, 0, 0),
0500 ),
0501 rnd=rnd,
0502 outputDirRoot=outputDirRoot,
0503 outputDirCsv=outputDirCsv,
0504 )
0505 addGenParticleSelection(
0506 s,
0507 ParticleSelectorConfig(
0508 rho=(0.0 * u.mm, rhoMax),
0509 absZ=(0.0 * u.mm, 1.0 * u.m),
0510 eta=etaRange,
0511 pt=(150 * u.MeV, None),
0512 ),
0513 )
0514
0515 if not args.geant4:
0516 from acts.examples.simulation import addFatras
0517
0518 addFatras(
0519 s,
0520 trackingGeometry,
0521 field,
0522 rnd=rnd,
0523 outputDirRoot=outputDirRoot,
0524 outputDirCsv=outputDirCsv,
0525 outputDirObj=outputDirObj,
0526 )
0527 else:
0528 if s.config.numThreads != 1:
0529 logger.fatal(
0530 f"Geant 4 simulation does not support multi-threading (threads={s.config.numThreads})"
0531 )
0532 sys.exit(2)
0533
0534 from acts.examples.simulation import addGeant4
0535
0536
0537
0538
0539 addGeant4(
0540 s,
0541 detector,
0542 trackingGeometry,
0543 field,
0544 rnd=rnd,
0545 killVolume=trackingGeometry.highestTrackingVolume,
0546 killAfterTime=25 * u.ns,
0547 outputDirRoot=outputDirRoot,
0548 outputDirCsv=outputDirCsv,
0549 outputDirObj=outputDirObj,
0550 )
0551
0552 addDigitization(
0553 s,
0554 trackingGeometry,
0555 field,
0556 digiConfigFile=args.digi_config,
0557 rnd=rnd,
0558 outputDirRoot=outputDirRoot,
0559 outputDirCsv=outputDirCsv,
0560 )
0561
0562 addDigiParticleSelection(
0563 s,
0564 ParticleSelectorConfig(
0565 pt=(ptMin, None),
0566 eta=etaRange if not args.generic_detector else (None, None),
0567 measurements=(9, None),
0568 removeNeutral=True,
0569 ),
0570 )
0571
0572 if not args.reco:
0573 return s
0574
0575 from acts.examples.reconstruction import (
0576 addSeeding,
0577 TrackSmearingSigmas,
0578 addCKFTracks,
0579 CkfConfig,
0580 SeedingAlgorithm,
0581 TrackSelectorConfig,
0582 addAmbiguityResolution,
0583 AmbiguityResolutionConfig,
0584 addVertexFitting,
0585 VertexFinder,
0586 )
0587
0588 if args.seeding_algorithm == SeedingAlgorithm.Gbts and (
0589 pathlib.Path(args.gbts_layer_map).is_file() == False
0590 or pathlib.Path(args.gbts_connection_table).is_file() == False
0591 ):
0592 logger.fatal(
0593 "GBTS seeding needs --gbts-layer-map and --gbts-connection-table "
0594 "for this detector"
0595 )
0596 sys.exit(2)
0597
0598 if args.itk and args.seeding_algorithm in (
0599 SeedingAlgorithm.GridTriplet,
0600 SeedingAlgorithm.Gbts,
0601 ):
0602 seedingAlgConfig = itk.itkSeedingAlgConfig(
0603 itk.InputSpacePointsType.PixelSpacePoints
0604 )
0605 else:
0606 seedingAlgConfig = []
0607
0608 addSeeding(
0609 s,
0610 trackingGeometry,
0611 field,
0612 *seedingAlgConfig,
0613 seedingAlgorithm=args.seeding_algorithm,
0614 **(
0615 dict(
0616 trackSmearingSigmas=TrackSmearingSigmas(ptRel=0.01),
0617 rnd=rnd,
0618 )
0619 if args.seeding_algorithm == SeedingAlgorithm.TruthSmeared
0620 else (
0621 dict(
0622 layerMappingConfigFile=args.gbts_layer_map,
0623 connectorInputConfigFile=args.gbts_connection_table,
0624 )
0625 if args.seeding_algorithm == SeedingAlgorithm.Gbts
0626 else {}
0627 )
0628 ),
0629 initialSigmas=[
0630 1 * u.mm,
0631 1 * u.mm,
0632 1 * u.degree,
0633 1 * u.degree,
0634 0 * u.e / u.GeV,
0635 1 * u.ns,
0636 ],
0637 initialSigmaQoverPt=0.1 * u.e / u.GeV,
0638 initialSigmaPtRel=0.1,
0639 initialVarInflation=[1.0] * 6,
0640 geoSelectionConfigFile=seedingConfigFile,
0641 outputDirRoot=outputDirLessRoot,
0642 outputDirCsv=outputDirLessCsv,
0643 )
0644
0645 if args.MLSeedFilter:
0646 from acts.examples.reconstruction import (
0647 addSeedFilterML,
0648 SeedFilterMLDBScanConfig,
0649 )
0650
0651 addSeedFilterML(
0652 s,
0653 SeedFilterMLDBScanConfig(
0654 epsilonDBScan=0.03, minPointsDBScan=2, minSeedScore=0.1
0655 ),
0656 onnxModelFile=str(
0657 actsDir
0658 / "Examples/Scripts/Python/MLAmbiguityResolution/seedDuplicateClassifier.onnx"
0659 ),
0660 outputDirRoot=outputDirLessRoot,
0661 outputDirCsv=outputDirLessCsv,
0662 )
0663
0664 if not args.ckf:
0665 return s
0666
0667 if args.seeding_algorithm != SeedingAlgorithm.TruthSmeared:
0668 ckfConfig = CkfConfig(
0669 seedDeduplication=True,
0670 stayOnSeed=True,
0671 )
0672 else:
0673 ckfConfig = CkfConfig()
0674
0675 if not args.itk:
0676 trackSelectorConfig = TrackSelectorConfig(
0677 pt=(ptMin if args.ttbar_pu200 else 0.0, None),
0678 absEta=(None, 3.0),
0679 loc0=(-4.0 * u.mm, 4.0 * u.mm),
0680 nMeasurementsMin=7,
0681 maxHoles=2,
0682 maxOutliers=2,
0683 )
0684 ckfConfig = ckfConfig._replace(
0685 chi2CutOffMeasurement=15.0,
0686 chi2CutOffOutlier=25.0,
0687 numMeasurementsCutOff=2,
0688 )
0689 else:
0690
0691 trackSelectorConfig = (
0692 TrackSelectorConfig(absEta=(None, 2.0), pt=(0.9 * u.GeV, None), nMeasurementsMin=9, maxHoles=2, maxOutliers=2, maxSharedHits=2),
0693 TrackSelectorConfig(absEta=(None, 2.6), pt=(0.4 * u.GeV, None), nMeasurementsMin=8, maxHoles=2, maxOutliers=2, maxSharedHits=2),
0694 TrackSelectorConfig(absEta=(None, 4.0), pt=(0.4 * u.GeV, None), nMeasurementsMin=7, maxHoles=2, maxOutliers=2, maxSharedHits=2),
0695 )
0696
0697
0698 if args.odd:
0699 ckfConfig = ckfConfig._replace(
0700 pixelVolumes=[16, 17, 18],
0701 stripVolumes=[23, 24, 25],
0702 maxPixelHoles=1,
0703 maxStripHoles=2,
0704 constrainToVolumes=[
0705 2,
0706 32,
0707 4,
0708 16,
0709 17,
0710 18,
0711 20,
0712 23,
0713 24,
0714 25,
0715 26,
0716 8,
0717 28,
0718 29,
0719 30,
0720 ],
0721 )
0722 elif args.itk:
0723 ckfConfig = ckfConfig._replace(
0724
0725 pixelVolumes=[8, 9, 10, 13, 14, 15, 16, 18, 19, 20],
0726 stripVolumes=[22, 23, 24],
0727 maxPixelHoles=1,
0728 maxStripHoles=2,
0729 )
0730
0731 if args.output_detail == 1:
0732 writeDetail = dict(writeTrackSummary=False)
0733 elif args.output_detail == 2:
0734 writeDetail = dict(writeTrackStates=True)
0735 else:
0736 writeDetail = {}
0737
0738 if args.odd and args.output_detail != 1:
0739 writeCovMat = dict(writeCovMat=True)
0740 else:
0741 writeCovMat = {}
0742
0743 addCKFTracks(
0744 s,
0745 trackingGeometry,
0746 field,
0747 trackSelectorConfig=trackSelectorConfig,
0748 ckfConfig=ckfConfig,
0749 **writeDetail,
0750 **writeCovMat,
0751 outputDirRoot=outputDirLessRoot,
0752 outputDirCsv=outputDirLessCsv,
0753 )
0754
0755 if args.ambi_solver == "ML":
0756
0757 from acts.examples.reconstruction import (
0758 addAmbiguityResolutionML,
0759 AmbiguityResolutionMLConfig,
0760 )
0761
0762 addAmbiguityResolutionML(
0763 s,
0764 AmbiguityResolutionMLConfig(
0765 maximumSharedHits=3, maximumIterations=1000000, nMeasurementsMin=7
0766 ),
0767 onnxModelFile=str(
0768 actsDir
0769 / "Examples/Scripts/Python/MLAmbiguityResolution/duplicateClassifier.onnx"
0770 ),
0771 outputDirRoot=outputDirLessRoot,
0772 outputDirCsv=outputDirLessCsv,
0773 )
0774
0775 elif args.ambi_solver == "scoring":
0776
0777 from acts.examples.reconstruction import (
0778 addScoreBasedAmbiguityResolution,
0779 ScoreBasedAmbiguityResolutionConfig,
0780 )
0781
0782 addScoreBasedAmbiguityResolution(
0783 s,
0784 ScoreBasedAmbiguityResolutionConfig(
0785 minScore=0,
0786 minScoreSharedTracks=1,
0787 maxShared=2,
0788 minUnshared=3,
0789 maxSharedTracksPerMeasurement=2,
0790 useAmbiguityScoring=False,
0791 ),
0792 ambiVolumeFile=args.ambi_config,
0793 **writeCovMat,
0794 outputDirRoot=outputDirLessRoot,
0795 outputDirCsv=outputDirLessCsv,
0796 )
0797
0798 elif args.ambi_solver == "greedy":
0799
0800 addAmbiguityResolution(
0801 s,
0802 AmbiguityResolutionConfig(
0803 maximumSharedHits=3,
0804 maximumIterations=10000 if args.itk else 1000000,
0805 nMeasurementsMin=6 if args.itk else 7,
0806 ),
0807 **writeDetail,
0808 **writeCovMat,
0809 outputDirRoot=outputDirLessRoot,
0810 outputDirCsv=outputDirLessCsv,
0811 )
0812
0813 if args.vertexing:
0814 addVertexFitting(
0815 s,
0816 field,
0817 vertexFinder=VertexFinder.AMVF,
0818 outputDirRoot=outputDirLessRoot,
0819 outputDirCsv=outputDirLessCsv,
0820 )
0821
0822 return s
0823
0824
0825 def strToRange(s: str, optName: str, unit: float = 1.0):
0826 global logger
0827 try:
0828 range = [float(e) * unit if e != "" else None for e in s.split(":")]
0829 except ValueError:
0830 range = []
0831 if len(range) == 1:
0832 range.append(range[0])
0833 if len(range) != 2:
0834 logger.fatal(f"bad option value: {optName} {s}")
0835 sys.exit(2)
0836 return range
0837
0838
0839
0840 class EnumAction(argparse.Action):
0841 """
0842 Argparse action for handling Enums
0843 """
0844
0845 def __init__(self, **kwargs):
0846 import enum
0847
0848
0849 enum_type = kwargs.pop("enum", None)
0850
0851
0852 if enum_type is None:
0853 raise ValueError("type must be assigned an Enum when using EnumAction")
0854 if not issubclass(enum_type, enum.Enum):
0855 raise TypeError("type must be an Enum when using EnumAction")
0856
0857
0858 kwargs.setdefault("choices", tuple(e.name for e in enum_type))
0859
0860 super(EnumAction, self).__init__(**kwargs)
0861
0862 self._enum = enum_type
0863
0864 def __call__(self, parser, namespace, values, option_string=None):
0865 for e in self._enum:
0866 if e.name == values:
0867 setattr(namespace, self.dest, e)
0868 break
0869 else:
0870 raise ValueError("%s is not a validly enumerated algorithm." % values)
0871
0872
0873
0874 full_chain(parse_args()).run()