Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:12:08

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