File indexing completed on 2026-10-26 08:47:15
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010 import os
0011 import argparse
0012 from pathlib import Path
0013
0014 import acts
0015 import acts.mille
0016 import acts.examples
0017
0018 from alignment_visualisation import plotAlignImpacts
0019 import numpy as np
0020
0021 from acts.examples import (
0022 Sequencer,
0023 )
0024 from acts.mille import (
0025 readMillePedeResult,
0026 runMillePedeSolver,
0027 generateMillePedeSteeringFile,
0028 MillePedeEqualityConstraint,
0029 MillePedeParameterSeed,
0030 MillePedeSolutionStrategy,
0031 MillePedeSteeringConfig,
0032 MillePedeSolverConfig,
0033 )
0034
0035 from millepede_alignment import (
0036 addAlignmentSandbox,
0037 addMisalignmentDeco,
0038 addTrackingSequence,
0039 getTelescopeDetector,
0040 )
0041
0042 from telescope_alignment_visualiser import plot_all_modes
0043
0044
0045
0046 class mpEigenVec:
0047 def __init__(
0048 self,
0049 index: int,
0050 EV: float,
0051 labels=None,
0052 components=None,
0053 component_dic: dict = {},
0054 ):
0055 self.index = index
0056 self.EV = EV
0057 if labels is not None and components is not None:
0058 self.labels = labels
0059 self.components = components
0060 elif len(component_dic) > 0:
0061 self.labels = np.array([l for l in component_dic.keys()], dtype=int)
0062 self.components = np.array([v for v in component_dic.values()], dtype=float)
0063
0064
0065
0066 def prune(self, threshold=0.01):
0067 relevant = np.where(np.abs(self.components) > threshold)
0068 return mpEigenVec(
0069 self.index,
0070 self.EV,
0071 self.labels[relevant],
0072 self.components[relevant]
0073 / np.linalg.vector_norm(self.components[relevant]),
0074 )
0075
0076
0077 def print(self):
0078 print(
0079 f"EV # {self.index} with EV {self.EV:.2f} has labels \n {self.labels}\n and components \n {np.round(self.components,3)}\n "
0080 )
0081
0082
0083
0084 def readEvFile(fname):
0085
0086
0087 with open(fname, "r") as fin:
0088 evs = []
0089 theIndex = None
0090 theVal = None
0091 elements = {}
0092 for li in fin.readlines():
0093 spl = li.split()
0094
0095 if "Eigenvector" in li:
0096 if theIndex is not None and theVal is not None:
0097 evs.append(mpEigenVec(theIndex, theVal, component_dic=elements))
0098 elements.clear()
0099 theIndex = int(spl[1])
0100 theVal = float(spl[4])
0101
0102 elif len(spl) > 1:
0103 elements.update(
0104 {
0105 int(spl[2 * k]): float(spl[2 * k + 1])
0106 for k in range(int(len(spl) / 2))
0107 }
0108 )
0109 return evs
0110
0111
0112
0113
0114 def drawResult(
0115 paramsMP2,
0116 constraints,
0117 truthInjected: dict,
0118 fname="AlignmentResults",
0119 title=None,
0120 xLim=None,
0121 ):
0122
0123 injectedVals = np.zeros(54, dtype=float)
0124 for label, value in truthInjected.items():
0125 injectedVals[label - 1] = value
0126
0127 if len(constraints) > 0:
0128
0129
0130
0131 blindSpace = np.zeros(shape=(54, len(constraints)), dtype=float)
0132
0133
0134 for i, constraint in enumerate(constraints):
0135 for label, value in constraint.labelsAndWeights:
0136 blindSpace[label - 1, i] = value
0137
0138 injectedVals = injectedVals - blindSpace @ np.linalg.solve(
0139 blindSpace.T @ blindSpace, blindSpace.T @ injectedVals
0140 )
0141
0142 mp2Info = np.array([[p.label, p.val, p.sigma] for p in paramsMP2], dtype=float)
0143
0144 mp2Info = mp2Info[np.argsort(mp2Info[:, 0])]
0145
0146
0147 labels = mp2Info[:, 0]
0148 central = np.zeros(shape=(2, len(labels)), dtype=float)
0149 sigma = np.zeros(shape=(2, len(labels)), dtype=float)
0150
0151
0152 central[0] = mp2Info[:, 1] * -1
0153
0154
0155 if truthInjected is not None:
0156 central[1, :] = injectedVals
0157
0158
0159 sigmaToUpdate = np.where(np.isfinite(mp2Info[:, 2])) and np.where(mp2Info[:, 2] > 0)
0160 sigma[0, sigmaToUpdate] = mp2Info[sigmaToUpdate, 2]
0161
0162
0163 formatDict = [
0164 {
0165 "label": "Millepede-II",
0166 "fmt": "o",
0167 "color": "black",
0168 "markersize": 5,
0169 },
0170 {
0171 "label": "Injected" + ", Projected" if len(constraints) > 0 else "",
0172 "marker": "s",
0173 "markeredgecolor": "#f31d1d",
0174 "markerfacecolor": "none",
0175 "linestyle": "none",
0176 "markersize": 11,
0177 "markeredgewidth": 1.6,
0178 },
0179 ]
0180
0181
0182 plotAlignImpacts(
0183 labels,
0184 central,
0185 sigma,
0186 formatDict,
0187 fname=fname,
0188 title=title,
0189 xLim=xLim,
0190 )
0191
0192
0193 def main():
0194
0195 logger = acts.getDefaultLogger("MillePedeResultReader", acts.logging.INFO)
0196 parser = argparse.ArgumentParser(
0197 description="MillePede alignment demo with the Telescope Detector and equality constraints"
0198 )
0199 parser.add_argument(
0200 "--output",
0201 "-o",
0202 help="Output directory",
0203 type=Path,
0204 default=Path.cwd() / "mpali_output",
0205 )
0206 parser.add_argument(
0207 "--events", "-n", help="Number of events", type=int, default=2000
0208 )
0209 parser.add_argument("--skip", "-s", help="Number of events", type=int, default=0)
0210
0211 args = parser.parse_args()
0212
0213
0214 outputDir = args.output
0215 os.makedirs(outputDir, exist_ok=True)
0216
0217
0218 milleBinaryName = outputDir / "MilleBinary.root"
0219 millePedeSolution = outputDir / "MillePedeAlignment.txt"
0220 millePedeSolutionSoftMode = outputDir / "MillePedeAlignment.SoftModeCut.txt"
0221 millePedeEigenvectors = outputDir / "MillePedeEV.txt"
0222
0223
0224 s = Sequencer(
0225 events=args.events,
0226 skip=args.skip,
0227 numThreads=8,
0228 outputDir=str(outputDir),
0229 )
0230
0231
0232 field = acts.ConstantBField(acts.Vector3(0, 0, 2 * acts.UnitConstants.T))
0233
0234
0235 detector = getTelescopeDetector()
0236 trackingGeometry = detector.trackingGeometry()
0237
0238
0239
0240 addMisalignmentDeco(detector, trackingGeometry, s)
0241
0242
0243
0244 addTrackingSequence(s, field, detector)
0245
0246
0247 addAlignmentSandbox(
0248 s,
0249 trackingGeometry,
0250 field,
0251 fixModules=set(),
0252 milleOutput=str(milleBinaryName),
0253 )
0254
0255
0256
0257
0258
0259
0260 s.run()
0261
0262
0263
0264
0265
0266
0267
0268
0269
0270 print("\n=== Running initial fit to discover singular modes === ")
0271
0272
0273
0274
0275
0276 constraintsStep1 = []
0277 for j in range(6):
0278
0279
0280
0281 eqc = MillePedeEqualityConstraint()
0282 eqc.labelsAndWeights = [(1 + 6 * k + j, 1) for k in range(9)]
0283 eqc.constraint = 0.0
0284 constraintsStep1.append(eqc)
0285
0286
0287
0288
0289 steerCfgStep1 = MillePedeSteeringConfig(
0290 inputFiles=[str(milleBinaryName)],
0291
0292
0293 strategy=MillePedeSolutionStrategy.Diagonalization,
0294
0295
0296 extraLines=["subido"],
0297 constraints=constraintsStep1,
0298 )
0299
0300 fnameSteerStep1 = "steerStep1.txt"
0301 generateMillePedeSteeringFile(fnameSteerStep1, steerCfgStep1, logger)
0302
0303
0304
0305
0306
0307 solverCfgStep1 = MillePedeSolverConfig(
0308 steeringFile=fnameSteerStep1,
0309 workDir="mpTmpConstraints/Step1",
0310 evFileName=str(millePedeEigenvectors),
0311 redirectStdout=str(outputDir / "MillepedeAlignmentStep1.log"),
0312 )
0313
0314 runMillePedeSolver(solverCfgStep1, logger)
0315
0316
0317 theEVset = readEvFile(millePedeEigenvectors)
0318
0319
0320
0321
0322
0323
0324 theFilteredEv = [e for e in theEVset if e.EV < 500.0]
0325 print("\n=== Found the following weak / singular modes: ===")
0326 for thing in theFilteredEv:
0327 thing.prune(0.03).print()
0328
0329
0330 plot_all_modes(theFilteredEv)
0331 print("Please see also 'alignment_mode_XYZ.png' in your working dir")
0332
0333
0334
0335
0336
0337
0338
0339
0340
0341
0342
0343
0344
0345 constraints = []
0346 for EV in theFilteredEv:
0347 constraints.append(MillePedeEqualityConstraint())
0348
0349
0350 constraints[-1].labelsAndWeights = [
0351 (EV.labels[i], EV.components[i]) for i in range(len(EV.components))
0352 ]
0353 constraints[-1].constraint = 0.0
0354
0355
0356
0357 print("\n=== Running second fit to perform the alignment with constraints === ")
0358
0359
0360 steerCfgStep2 = MillePedeSteeringConfig(
0361 inputFiles=[str(milleBinaryName)],
0362 strategy=MillePedeSolutionStrategy.Inversion,
0363 constraints=constraints + constraintsStep1,
0364 )
0365
0366 fnameSteerStep2 = "steerStep2.txt"
0367 generateMillePedeSteeringFile(fnameSteerStep2, steerCfgStep2, logger)
0368
0369
0370 solverCfgStep2 = MillePedeSolverConfig(
0371 steeringFile=fnameSteerStep2,
0372 workDir="mpTmpConstraints/Step2",
0373 resFileName=str(millePedeSolution),
0374 redirectStdout=str(outputDir / "MillepedeAlignmentStep2.log"),
0375 )
0376
0377 runMillePedeSolver(solverCfgStep2, logger)
0378
0379
0380
0381
0382
0383
0384
0385
0386
0387
0388 print("\n=== Running alignment with soft mode cut === ")
0389
0390
0391
0392
0393 preSigmas = []
0394 for label in range(54):
0395 s = MillePedeParameterSeed()
0396 s.label = 1 + label
0397
0398 s.preSigma = 0.1 if label % 6 < 3 else 0.002
0399 preSigmas.append(s)
0400
0401
0402
0403
0404 steerCfgSoftModeCut = MillePedeSteeringConfig(
0405 inputFiles=[str(milleBinaryName)],
0406 strategy=MillePedeSolutionStrategy.Inversion,
0407
0408
0409
0410
0411
0412
0413
0414 extraLines=["regularisation 10 1."],
0415 seeds=preSigmas,
0416 )
0417
0418 fnameSteerSoftModeCut = "steerSoftModeCut.txt"
0419 generateMillePedeSteeringFile(fnameSteerSoftModeCut, steerCfgSoftModeCut, logger)
0420
0421
0422 solverCfgSoftMode = MillePedeSolverConfig(
0423 steeringFile=fnameSteerSoftModeCut,
0424 workDir="mpTmpConstraints/SoftModeCut",
0425 resFileName=str(millePedeSolutionSoftMode),
0426 redirectStdout=str(outputDir / "MillepedeAlignmentSoftMode.log"),
0427 )
0428
0429 runMillePedeSolver(solverCfgSoftMode, logger)
0430
0431
0432
0433 res_MPII_constraints = readMillePedeResult(str(millePedeSolution), logger)
0434 res_MPII_softMode = readMillePedeResult(str(millePedeSolutionSoftMode), logger)
0435
0436
0437 drawResult(
0438 res_MPII_constraints,
0439 constraints + constraintsStep1,
0440 {9: 0.05},
0441 xLim=(-0.2, 0.2),
0442 fname="AlignmentWithConstraints",
0443 )
0444 drawResult(
0445 res_MPII_softMode,
0446 constraintsStep1,
0447 {9: 0.05},
0448 xLim=(-0.2, 0.2),
0449 fname="AlignmentWithSoftMode",
0450 )
0451
0452
0453 if __name__ == "__main__":
0454 main()