Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-22 07:47:22

0001 #!/usr/bin/env python3
0002 
0003 import os
0004 import argparse
0005 import pathlib
0006 from pathlib import Path
0007 
0008 import acts
0009 import acts.examples
0010 
0011 from acts.examples import (
0012     TelescopeDetector,
0013     Sequencer,
0014     StructureSelector,
0015     RandomNumbers,
0016     GaussianVertexGenerator,
0017 )
0018 
0019 from acts.examples.alignment import (
0020     AlignmentDecorator,
0021     GeoIdAlignmentStore,
0022     AlignmentGeneratorGlobalShift,
0023 )
0024 from acts.examples.alignmentmillepede import (
0025     MillePedeAlignmentSandbox,
0026     ActsSolverFromMille,
0027 )
0028 
0029 from acts.examples.simulation import (
0030     MomentumConfig,
0031     EtaConfig,
0032     PhiConfig,
0033     ParticleConfig,
0034     ParticleSelectorConfig,
0035     addParticleGun,
0036     addFatras,
0037     addDigitization,
0038     addDigiParticleSelection,
0039 )
0040 from acts.examples.reconstruction import (
0041     addSeeding,
0042     CkfConfig,
0043     addCKFTracks,
0044     TrackSelectorConfig,
0045     SeedingAlgorithm,
0046     TrackSelectorConfig,
0047     addSeeding,
0048     SeedingAlgorithm,
0049     SeedFinderConfigArg,
0050     SeedFinderOptionsArg,
0051     SeedingAlgorithm,
0052     CkfConfig,
0053     addCKFTracks,
0054     TrackSelectorConfig,
0055 )
0056 
0057 
0058 # Helper to instantiate a telescope detector.
0059 # The square sensors are oriented in the global
0060 # y-direction and cover the x-z plane.
0061 # You can change the number of layers by resizing
0062 # the "bounds", "stereos" and "positions" arrays accordingly.
0063 #
0064 # By default, will be digitised as 25 x 100 pixel grid.
0065 #
0066 # The "layer" field of the geo ID of this detector
0067 # will move in steps of 2 for each module (2,4,6,..,18 for 9 layers).
0068 # Everything is located in volume "1".
0069 #
0070 # In the alignment, at least 4 layers are expected (layer ID 8 will
0071 # be fixed as the alignment reference).
0072 def getTelescopeDetector():
0073     bounds = [200, 200]
0074     positions = [30, 60, 90, 120, 150, 180, 210, 240, 270]
0075     stereos = [0] * len(positions)
0076     detector = TelescopeDetector(
0077         bounds=bounds, positions=positions, stereos=stereos, binValue=1
0078     )
0079 
0080     return detector
0081 
0082 
0083 # Add the alignment sandbox algorithm
0084 def addAlignmentSandbox(
0085     s: Sequencer,
0086     trackingGeometry: acts.TrackingGeometry,
0087     magField: acts.MagneticFieldProvider,
0088     fixModules: set,
0089     inputMeasurements: str = "measurements",
0090     inputTracks: str = "ckf_tracks",
0091     logLevel: acts.logging.Level = acts.logging.INFO,
0092     milleOutput: str = "MilleBinary.root",
0093 ):
0094     sandbox = MillePedeAlignmentSandbox(
0095         level=logLevel,
0096         milleOutput=milleOutput,
0097         inputMeasurements=inputMeasurements,
0098         inputTracks=inputTracks,
0099         trackingGeometry=trackingGeometry,
0100         magneticField=magField,
0101         fixModules=fixModules,
0102     )
0103     s.addAlgorithm(sandbox)
0104     return s
0105 
0106 
0107 def addSolverFromMille(
0108     s: Sequencer,
0109     trackingGeometry: acts.TrackingGeometry,
0110     magField: acts.MagneticFieldProvider,
0111     fixModules: set,
0112     logLevel: acts.logging.Level = acts.logging.INFO,
0113     milleInput: str = "MilleBinary.root",
0114 ):
0115 
0116     solver = ActsSolverFromMille(
0117         level=logLevel,
0118         milleInput=milleInput,
0119         trackingGeometry=trackingGeometry,
0120         magneticField=magField,
0121         fixModules=fixModules,
0122     )
0123     s.addAlgorithm(solver)
0124     return s
0125 
0126 
0127 u = acts.UnitConstants
0128 
0129 # Can also use zero-field - but not healthy for track covariance.
0130 # field = acts.NullBField()
0131 field = acts.ConstantBField(acts.Vector3(0, 0, 2 * acts.UnitConstants.T))
0132 
0133 
0134 parser = argparse.ArgumentParser(
0135     description="MillePede alignment demo with the Telescope Detector"
0136 )
0137 parser.add_argument(
0138     "--output",
0139     "-o",
0140     help="Output directory",
0141     type=pathlib.Path,
0142     default=pathlib.Path.cwd() / "mpali_output",
0143 )
0144 parser.add_argument("--events", "-n", help="Number of events", type=int, default=2000)
0145 parser.add_argument("--skip", "-s", help="Number of events", type=int, default=0)
0146 
0147 
0148 args = parser.parse_args()
0149 
0150 outputDir = args.output
0151 # ensure out output dir exists
0152 os.makedirs(outputDir, exist_ok=True)
0153 
0154 # Instantiate the telescope detector - with alignment enabled
0155 detector = getTelescopeDetector()
0156 trackingGeometry = detector.trackingGeometry()
0157 decorators = detector.contextDecorators()
0158 
0159 # inject a known misalignment.
0160 
0161 # Misalign the second tracking layer (ID = 4).
0162 layerToBump = acts.GeometryIdentifier(layer=4, volume=1, sensitive=1)
0163 
0164 # shift this layer by 200 microns in the global Z direction
0165 leShift = AlignmentGeneratorGlobalShift()
0166 leShift.shift = acts.Vector3(0, 0, 200.0e-3)
0167 
0168 # now add some boilerplate code to make this happen
0169 alignDecoConfig = AlignmentDecorator.Config()
0170 alignDecoConfig.nominalStore = GeoIdAlignmentStore(
0171     StructureSelector(trackingGeometry).selectedTransforms(
0172         acts.GeometryContext.dangerouslyDefaultConstruct(), layerToBump
0173     )
0174 )
0175 alignDecoConfig.iovGenerators = [((0, 10000000), leShift)]
0176 alignDeco = AlignmentDecorator(alignDecoConfig, acts.logging.WARNING)
0177 contextDecorators = [alignDeco]
0178 
0179 # decide on at least on detector module to fix in place
0180 # as a reference for the alignment.
0181 # By default, fix the innermost layer.
0182 fixModules = {acts.GeometryIdentifier(layer=2, volume=1, sensitive=1)}
0183 
0184 
0185 # More Boilerplate code - for setting up the sequence
0186 
0187 rnd = RandomNumbers(seed=42)
0188 s = Sequencer(
0189     events=args.events,
0190     skip=args.skip,
0191     numThreads=1,
0192     outputDir=str(outputDir),
0193 )
0194 
0195 # Add a context with the alignment shift - sim, digi and
0196 # initial reco will "see" the distorted detector
0197 s.addContextDecorator(alignDeco)
0198 
0199 # Run particle gun and fire some muons at our telescope
0200 addParticleGun(
0201     s,
0202     MomentumConfig(
0203         10 * u.GeV,
0204         100 * u.GeV,
0205         transverse=True,
0206     ),
0207     EtaConfig(-0.3, 0.3),
0208     # aim roughly along +Y...
0209     PhiConfig(60 * u.degree, 120 * u.degree),
0210     ParticleConfig(1, acts.PdgParticle.eMuon, randomizeCharge=True),
0211     vtxGen=GaussianVertexGenerator(
0212         mean=acts.Vector4(0, 0, 0, 0),
0213         stddev=acts.Vector4(5.0 * u.mm, 0.0 * u.mm, 5.0 * u.mm, 0.0 * u.ns),
0214     ),
0215     multiplicity=1,
0216     rnd=rnd,
0217 )
0218 # fast sim
0219 addFatras(
0220     s,
0221     trackingGeometry,
0222     field,
0223     enableInteractions=True,
0224     outputDirRoot=None,
0225     outputDirCsv=None,
0226     outputDirObj=None,
0227     rnd=rnd,
0228 )
0229 
0230 # digitise with the default 25x100 pixel config
0231 srcdir = Path(__file__).resolve().parent.parent.parent.parent
0232 addDigitization(
0233     s,
0234     trackingGeometry,
0235     field,
0236     digiConfigFile=srcdir / "Examples/Configs/telescope-digi-smearing-config.json",
0237     outputDirRoot=None,
0238     outputDirCsv=None,
0239     rnd=rnd,
0240 )
0241 addDigiParticleSelection(
0242     s,
0243     ParticleSelectorConfig(
0244         measurements=(3, None),
0245         removeNeutral=True,
0246     ),
0247 )
0248 
0249 # Run grid seeder
0250 addSeeding(
0251     s,
0252     trackingGeometry,
0253     field,
0254     # Settings copied from existing snippet, slightly adapted
0255     seedFinderConfigArg=SeedFinderConfigArg(
0256         r=(20 * u.mm, 200 * u.mm),
0257         deltaR=(1 * u.mm, 300 * u.mm),
0258         collisionRegion=(-250 * u.mm, 250 * u.mm),
0259         z=(-100 * u.mm, 100 * u.mm),
0260         maxSeedsPerSpM=1,
0261         sigmaScattering=5,
0262         radLengthPerSeed=0.1,
0263         minPt=0.5 * u.GeV,
0264         impactMax=3 * u.mm,
0265     ),
0266     # why do we need to specify this here again? Not taken from event context?
0267     seedFinderOptionsArg=SeedFinderOptionsArg(bFieldInZ=2 * u.T),
0268     seedingAlgorithm=SeedingAlgorithm.GridTriplet,
0269     initialSigmas=[
0270         3 * u.mm,
0271         3 * u.mm,
0272         1 * u.degree,
0273         1 * u.degree,
0274         0 * u.e / u.GeV,
0275         1 * u.ns,
0276     ],
0277     initialSigmaQoverPt=0.1 * u.e / u.GeV,
0278     initialSigmaPtRel=0.1,
0279     initialVarInflation=[1.0] * 6,
0280     # This file should be adapted if you add layers and want to include
0281     # them in the seeding.
0282     geoSelectionConfigFile=srcdir / "Examples/Configs/telescope-seeding-config.json",
0283     outputDirRoot=None,
0284 )
0285 
0286 # Add CKF track finding
0287 addCKFTracks(
0288     s,
0289     trackingGeometry,
0290     field,
0291     TrackSelectorConfig(),
0292     CkfConfig(
0293         chi2CutOffMeasurement=150.0,
0294         chi2CutOffOutlier=250.0,
0295         numMeasurementsCutOff=50,
0296         seedDeduplication=(True),
0297         stayOnSeed=True,
0298     ),
0299     outputDirRoot=None,
0300     writePerformance=False,
0301     writeTrackSummary=False,
0302 )
0303 
0304 # And add our alignment sandbox
0305 addAlignmentSandbox(
0306     s, trackingGeometry, field, fixModules, milleOutput=outputDir / "MyBinary.root"
0307 )
0308 # And finally read back and solve in ACTS
0309 addSolverFromMille(
0310     s, trackingGeometry, field, fixModules, milleInput=outputDir / "MyBinary.root"
0311 )
0312 
0313 s.run()