File indexing completed on 2025-12-16 09:24:05
0001
0002
0003 import os
0004 import argparse
0005 import time
0006 from datetime import datetime
0007
0008 from orion.client import build_experiment
0009 from orion.storage.base import get_storage
0010
0011 import acts
0012 from acts import (
0013 SurfaceMaterialMapper,
0014 VolumeMaterialMapper,
0015 Navigator,
0016 Propagator,
0017 StraightLineStepper,
0018 )
0019
0020 from acts.json import MaterialMapJsonConverter
0021
0022 from acts.examples import (
0023 Sequencer,
0024 WhiteBoard,
0025 AlgorithmContext,
0026 ProcessCode,
0027 MaterialMapping,
0028 )
0029
0030 from acts.examples.root import (
0031 RootMaterialTrackReader,
0032 RootMaterialTrackWriter,
0033 )
0034
0035 from acts.examples.json import (
0036 JsonMaterialWriter,
0037 JsonFormat,
0038 )
0039
0040 from acts.examples.odd import getOpenDataDetector
0041
0042
0043 def runMaterialMappingNoTrack(
0044 trackingGeometry,
0045 decorators,
0046 outputDir,
0047 inputDir,
0048 mapName="material-map",
0049 mapSurface=True,
0050 mapVolume=True,
0051 format=JsonFormat.Json,
0052 readCachedSurfaceInformation=False,
0053 s=None,
0054 ):
0055 """
0056 Implementation of the material mapping that doesn't write the material tracks.
0057 Used to create the material map that will then be used to compute the material variance.
0058
0059 trackingGeometry : The tracking geometry
0060 decorators : The decorators for the tracking geometry
0061 outputDir : Output directory for the material map
0062 inputDir : Input directory containing the Material track
0063 mapName : Name of the material map
0064 mapSurface : Is material being mapped onto surfaces ?
0065 mapVolume : Is material being mapped onto volumes ?
0066 format : Json format used to write the material map (json, cbor, ...)
0067 readCachedSurfaceInformation : If set to true it will be assumed that the surface has already been associated with each material interaction in the input file.
0068 """
0069
0070 s = s or Sequencer(numThreads=1)
0071 for decorator in decorators:
0072 s.addContextDecorator(decorator)
0073 wb = WhiteBoard(acts.logging.INFO)
0074 context = AlgorithmContext(0, 0, wb)
0075 for decorator in decorators:
0076 assert decorator.decorate(context) == ProcessCode.SUCCESS
0077
0078
0079 s.addReader(
0080 RootMaterialTrackReader(
0081 level=acts.logging.INFO,
0082 outputMaterialTracks="material-tracks",
0083 fileList=[
0084 os.path.join(
0085 inputDir,
0086 (
0087 "optimised-material-map_tracks.root"
0088 if readCachedSurfaceInformation
0089 else "geant4_material_tracks.root"
0090 ),
0091 )
0092 ],
0093 readCachedSurfaceInformation=readCachedSurfaceInformation,
0094 )
0095 )
0096
0097 stepper = StraightLineStepper()
0098 mmAlgCfg = MaterialMapping.Config(context.geoContext, context.magFieldContext)
0099 mmAlgCfg.trackingGeometry = trackingGeometry
0100 mmAlgCfg.inputMaterialTracks = "material-tracks"
0101
0102 if mapSurface:
0103 navigator = Navigator(
0104 trackingGeometry=trackingGeometry,
0105 resolveSensitive=True,
0106 resolveMaterial=True,
0107 resolvePassive=True,
0108 )
0109 propagator = Propagator(stepper, navigator)
0110 mapper = SurfaceMaterialMapper(level=acts.logging.INFO, propagator=propagator)
0111 mmAlgCfg.materialSurfaceMapper = mapper
0112
0113 if mapVolume:
0114 navigator = Navigator(
0115 trackingGeometry=trackingGeometry,
0116 )
0117 propagator = Propagator(stepper, navigator)
0118 mapper = VolumeMaterialMapper(
0119 level=acts.logging.INFO, propagator=propagator, mappingStep=999
0120 )
0121 mmAlgCfg.materialVolumeMapper = mapper
0122
0123 jmConverterCfg = MaterialMapJsonConverter.Config(
0124 processSensitives=True,
0125 processApproaches=True,
0126 processRepresenting=True,
0127 processBoundaries=True,
0128 processVolumes=True,
0129 context=context.geoContext,
0130 )
0131
0132 jmw = JsonMaterialWriter(
0133 level=acts.logging.VERBOSE,
0134 converterCfg=jmConverterCfg,
0135 fileName=os.path.join(outputDir, mapName),
0136 writeFormat=format,
0137 )
0138
0139 mmAlgCfg.materialWriters = [jmw]
0140
0141 s.addAlgorithm(MaterialMapping(level=acts.logging.INFO, config=mmAlgCfg))
0142
0143 return s
0144
0145
0146 def runMaterialMappingVariance(
0147 binMap,
0148 events,
0149 job,
0150 inputPath,
0151 pathExp,
0152 pipeResult,
0153 readCachedSurfaceInformation=False,
0154 ):
0155 """
0156 Run the material mapping and compute the variance for each bin of each surface
0157 Return a dict with the GeometryId value of the surface as a key that stores
0158 a list of pairs corresponding to the variance and number of tracks associated with each bin of the surface
0159
0160 binMap : Map containing the binning for each surface
0161 events : Number of event to use in the mapping
0162 job : ID of the job
0163 inputPath : Directory containing the input geantino track and the json geometry
0164 pathExp : Material mapping optimisation path
0165 pipeResult : Pipe to send back the score to the main python instance
0166 readCachedSurfaceInformation : Are surface information stored in the material track. Switch to true if the mapping was already performed to improve the speed.
0167 """
0168 print(
0169 datetime.now().strftime("%H:%M:%S") + " Start mapping for job " + str(job),
0170 flush=True,
0171 )
0172 mapName = "material-map-" + str(job)
0173 mapSurface = True
0174 mapVolume = False
0175
0176
0177 matDeco = acts.IMaterialDecorator.fromFile(
0178 str(os.path.join(inputPath, "geometry-map.json"))
0179 )
0180 detectorTemp = getOpenDataDetector(matDeco)
0181 trackingGeometryTemp = detectorTemp.trackingGeometry()
0182 matMapDeco = acts.examples.MappingMaterialDecorator(
0183 tGeometry=trackingGeometryTemp, level=acts.logging.ERROR
0184 )
0185
0186 matMapDeco.setBinningMap(binMap)
0187
0188
0189 detector = getOpenDataDetector(matMapDeco)
0190 trackingGeometry = detector.trackingGeometry()
0191 decorators = detector.contextDecorators()
0192
0193
0194 sMap = acts.examples.Sequencer(
0195 events=events, numThreads=1, logLevel=acts.logging.INFO
0196 )
0197
0198
0199 runMaterialMappingNoTrack(
0200 trackingGeometry,
0201 decorators,
0202 outputDir=pathExp,
0203 inputDir=inputPath,
0204 mapName=mapName,
0205 format=JsonFormat.Cbor,
0206 mapVolume=mapVolume,
0207 readCachedSurfaceInformation=readCachedSurfaceInformation,
0208 s=sMap,
0209 )
0210 sMap.run()
0211
0212
0213 print(
0214 datetime.now().strftime("%H:%M:%S")
0215 + " Job "
0216 + str(job)
0217 + ": second pass to compute the variance",
0218 flush=True,
0219 )
0220
0221 cborMap = os.path.join(pathExp, (mapName + ".cbor"))
0222 matDecoVar = acts.IMaterialDecorator.fromFile(cborMap)
0223 detectorVar = getOpenDataDetector(matDecoVar)
0224 trackingGeometryVar = detectorVar.trackingGeometry()
0225 decoratorsVar = detectorVar.contextDecorators()
0226 s = acts.examples.Sequencer(events=events, numThreads=1, logLevel=acts.logging.INFO)
0227 for decorator in decoratorsVar:
0228 s.addContextDecorator(decorator)
0229 wb = WhiteBoard(acts.logging.ERROR)
0230 context = AlgorithmContext(0, 0, wb)
0231 for decorator in decoratorsVar:
0232 assert decorator.decorate(context) == ProcessCode.SUCCESS
0233
0234
0235 reader = RootMaterialTrackReader(
0236 level=acts.logging.ERROR,
0237 outputMaterialTracks="material-tracks",
0238 fileList=[
0239 os.path.join(
0240 inputPath,
0241 (
0242 "optimised-material-map_tracks.root"
0243 if readCachedSurfaceInformation
0244 else "geant4_material_tracks.root"
0245 ),
0246 )
0247 ],
0248 readCachedSurfaceInformation=readCachedSurfaceInformation,
0249 )
0250 s.addReader(reader)
0251 stepper = StraightLineStepper()
0252 mmAlgCfg = MaterialMapping.Config(context.geoContext, context.magFieldContext)
0253 mmAlgCfg.trackingGeometry = trackingGeometryVar
0254 mmAlgCfg.inputMaterialTracks = "material-tracks"
0255
0256 if mapSurface:
0257 navigator = Navigator(
0258 trackingGeometry=trackingGeometryVar,
0259 resolveSensitive=True,
0260 resolveMaterial=True,
0261 resolvePassive=True,
0262 )
0263 propagator = Propagator(stepper, navigator)
0264 surfaceCfg = SurfaceMaterialMapper.Config(
0265 computeVariance=True
0266 )
0267 mapper = SurfaceMaterialMapper(
0268 config=surfaceCfg, level=acts.logging.ERROR, propagator=propagator
0269 )
0270 mmAlgCfg.materialSurfaceMapper = mapper
0271
0272 if mapVolume:
0273 navigator = Navigator(
0274 trackingGeometry=trackingGeometryVar,
0275 )
0276 propagator = Propagator(stepper, navigator)
0277 mapper = VolumeMaterialMapper(
0278 level=acts.logging.ERROR, propagator=propagator, mappingStep=999
0279 )
0280 mmAlgCfg.materialVolumeMapper = mapper
0281
0282 mapping = MaterialMapping(level=acts.logging.ERROR, config=mmAlgCfg)
0283 s.addAlgorithm(mapping)
0284 s.run()
0285
0286
0287 score = dict()
0288 for key in binMap:
0289 objective = 0
0290 nonZero = 0
0291 binParameters = mapping.scoringParameters(key)
0292
0293
0294 for parameters in binParameters:
0295 if parameters[1] != 0:
0296 objective += parameters[0] * (1.0 + 1.0 / parameters[1])
0297 nonZero += 1
0298 if nonZero != 0:
0299 objective = objective / nonZero
0300 score[key] = [dict(name="surface_score", type="objective", value=objective)]
0301 print(
0302 datetime.now().strftime("%H:%M:%S")
0303 + " Mapping over for job "
0304 + str(job)
0305 + " : now sending score",
0306 flush=True,
0307 )
0308 pipeResult.send(score)
0309
0310 os.remove(cborMap)
0311
0312
0313 def surfaceExperiment(key, nbJobs, pathDB, pathResult, pipeBin, pipeResult, doPloting):
0314 """
0315 This function create an experiment for a given single surface
0316 Due to how Orion is implemented only one DB can exist per job, this thus need to be call using pythons multiprocessing to circumvent the issue.
0317
0318 key : Id of the surface corresponding to this experiment
0319 nbJobs : Total number of jobs to be executed simultaneously
0320 pathDB : Path to the databases
0321 pathResult : Path to write the result of the optimisation
0322 pipeBin : Pipe use to send the experiment binning to the main python instance
0323 pipeResult : Pipe to receive the result of the optimisation
0324 doPloting : true if we want to plot the result of the optimisation and obtain the optimal material map
0325 """
0326
0327 storage = {
0328 "database": {
0329 "name": "database_" + str(key),
0330 "type": "pickleddb",
0331 "host": os.path.join(pathDB, "database_" + str(key) + ".pkl"),
0332 "timeout": 43200,
0333 },
0334 }
0335
0336
0337
0338 space = {
0339 "x": "uniform(1, 240, discrete=True)",
0340 "y": "uniform(1, 240, discrete=True)",
0341 }
0342
0343 experiments = build_experiment(
0344 "s_" + str(key),
0345 version="1",
0346 space=space,
0347 algorithms="random",
0348 storage=storage,
0349 max_idle_time=43200,
0350 )
0351
0352 store = get_storage()
0353 store.delete_trials(uid=experiments.id, where={"status": {"$ne": "completed"}})
0354 store.release_algorithm_lock(uid=experiments.id)
0355
0356
0357 trials = dict()
0358 binMap = dict()
0359 for job in range(nbJobs):
0360 trials[job] = experiments.suggest()
0361 binMap[job] = (trials[job].params["x"], trials[job].params["y"])
0362 print(
0363 datetime.now().strftime("%H:%M:%S")
0364 + " Binning for job "
0365 + str(job)
0366 + " and surface "
0367 + str(key)
0368 + " has been selected",
0369 flush=True,
0370 )
0371 pipeBin.send(binMap[job])
0372 print(
0373 datetime.now().strftime("%H:%M:%S")
0374 + " All binning for surface "
0375 + str(key)
0376 + " has been sent",
0377 flush=True,
0378 )
0379
0380 for job in range(nbJobs):
0381 score = pipeResult.recv()
0382 print(
0383 datetime.now().strftime("%H:%M:%S")
0384 + " Received score for job "
0385 + str(job)
0386 + " and surface "
0387 + str(key),
0388 flush=True,
0389 )
0390 experiments.observe(trials[job], score)
0391 print(
0392 datetime.now().strftime("%H:%M:%S")
0393 + " Score for job "
0394 + str(job)
0395 + " and surface "
0396 + str(key)
0397 + " has been written",
0398 flush=True,
0399 )
0400
0401
0402 if doPloting:
0403 print(
0404 datetime.now().strftime("%H:%M:%S")
0405 + " All the jobs are over. Now creating the optimisation plots",
0406 flush=True,
0407 )
0408
0409 pathExpSurface = os.path.join(pathResult, "b_" + str(key))
0410 if not os.path.isdir(pathExpSurface):
0411 os.makedirs(pathExpSurface)
0412
0413 regret = experiments.plot.regret()
0414 regret.write_html(pathExpSurface + "/regret.html")
0415
0416 parallel_coordinates = experiments.plot.parallel_coordinates()
0417 parallel_coordinates.write_html(pathExpSurface + "/parallel_coordinates.html")
0418
0419 lpi = experiments.plot.lpi()
0420 lpi.write_html(pathExpSurface + "/lpi.html")
0421
0422 partial_dependencies = experiments.plot.partial_dependencies()
0423 partial_dependencies.write_html(pathExpSurface + "/partial_dependencies.html")
0424
0425
0426 df = experiments.to_pandas()
0427 best = df.iloc[df.objective.idxmin()]
0428 print(
0429 datetime.now().strftime("%H:%M:%S")
0430 + " Best score for surface "
0431 + str(key)
0432 + " : "
0433 + str(best),
0434 flush=True,
0435 )
0436 resultBinMap = (best.x, best.y)
0437 pipeBin.send(resultBinMap)
0438
0439
0440 if "__main__" == __name__:
0441 print(datetime.now().strftime("%H:%M:%S") + " Starting")
0442
0443 parser = argparse.ArgumentParser()
0444 parser.add_argument(
0445 "--numberOfJobs", nargs="?", default=2, type=int
0446 )
0447 parser.add_argument(
0448 "--topNumberOfEvents", nargs="?", default=100, type=int
0449 )
0450 parser.add_argument(
0451 "--inputPath", nargs="?", default=os.getcwd(), type=str
0452 )
0453 parser.add_argument(
0454 "--outputPath", nargs="?", default="", type=str
0455 )
0456 parser.add_argument(
0457 "--doPloting", action="store_true"
0458 )
0459 parser.add_argument(
0460 "--readCachedSurfaceInformation", action="store_true"
0461 )
0462 parser.set_defaults(doPloting=False)
0463 parser.set_defaults(readCachedSurfaceInformation=False)
0464 args = parser.parse_args()
0465
0466
0467 pathExp = os.path.join(args.outputPath, "Mapping")
0468 pathDB = os.path.join(pathExp, "Database")
0469 pathResult = os.path.join(pathExp, "Result")
0470 if not os.path.isdir(pathExp):
0471 os.makedirs(pathExp)
0472 if not os.path.isdir(pathDB):
0473 os.makedirs(pathDB)
0474 if not os.path.isdir(pathResult):
0475 os.makedirs(pathResult)
0476
0477
0478 matDeco = acts.IMaterialDecorator.fromFile(
0479 str(os.path.join(args.inputPath, "geometry-map.json"))
0480 )
0481 detector = getOpenDataDetector(matDeco)
0482 trackingGeometry = detector.trackingGeometry()
0483 decorators = detector.contextDecorators()
0484
0485
0486 matMapDeco = acts.examples.MappingMaterialDecorator(
0487 tGeometry=trackingGeometry, level=acts.logging.WARNING
0488 )
0489 binDict = matMapDeco.binningMap()
0490
0491
0492 from multiprocessing import Process, Pipe
0493
0494 binPipes_child = dict()
0495 resultPipes_child = dict()
0496 scorePipes_child = dict()
0497
0498 binPipes_parent = dict()
0499 resultPipes_parent = dict()
0500 scorePipes_parent = dict()
0501
0502 expJob = dict()
0503 OptiJob = dict()
0504
0505
0506
0507
0508 for key in binDict:
0509 binPipes_parent[key], binPipes_child[key] = Pipe()
0510 scorePipes_parent[key], scorePipes_child[key] = Pipe()
0511 expJob[key] = Process(
0512 target=surfaceExperiment,
0513 args=(
0514 key,
0515 args.numberOfJobs,
0516 pathDB,
0517 pathResult,
0518 binPipes_child[key],
0519 scorePipes_child[key],
0520 args.doPloting,
0521 ),
0522 )
0523 expJob[key].start()
0524
0525
0526 for job in range(args.numberOfJobs):
0527 resultPipes_parent[job], resultPipes_child[job] = Pipe()
0528 binMap = dict()
0529
0530 for key in binDict:
0531 binMap[key] = binPipes_parent[key].recv()
0532 print(
0533 datetime.now().strftime("%H:%M:%S")
0534 + " Binning for job "
0535 + str(job)
0536 + " have been selected, now running the mapping",
0537 flush=True,
0538 )
0539
0540 OptiJob[job] = Process(
0541 target=runMaterialMappingVariance,
0542 args=(
0543 binMap,
0544 args.topNumberOfEvents,
0545 job,
0546 args.inputPath,
0547 pathExp,
0548 resultPipes_child[job],
0549 args.readCachedSurfaceInformation,
0550 ),
0551 )
0552 OptiJob[job].start()
0553
0554
0555 for job in range(args.numberOfJobs):
0556 scores = resultPipes_parent[job].recv()
0557 print(
0558 datetime.now().strftime("%H:%M:%S")
0559 + " Retried score for job "
0560 + str(job),
0561 flush=True,
0562 )
0563 for key in binDict:
0564 score = scores[key]
0565 scorePipes_parent[key].send(score)
0566 print(
0567 datetime.now().strftime("%H:%M:%S")
0568 + " Job number "
0569 + str(job)
0570 + " is over",
0571 flush=True,
0572 )
0573
0574 if args.doPloting:
0575
0576
0577 print(
0578 datetime.now().strftime("%H:%M:%S")
0579 + " Running the material mapping to obtain the optimised material map",
0580 flush=True,
0581 )
0582 resultBinMap = dict()
0583 for key in binDict:
0584 resultBinMap[key] = binPipes_parent[key].recv()
0585 matMapDeco.setBinningMap(resultBinMap)
0586
0587
0588 resultDetector, resultTrackingGeometry, resultDecorators = getOpenDataDetector(
0589 matMapDeco
0590 )
0591
0592
0593 rMap = acts.examples.Sequencer(
0594 events=args.topNumberOfEvents, numThreads=1, logLevel=acts.logging.INFO
0595 )
0596
0597
0598 from material_mapping import runMaterialMapping
0599
0600 runMaterialMapping(
0601 resultTrackingGeometry,
0602 resultDecorators,
0603 outputDir=args.outputPath,
0604 inputDir=args.inputPath,
0605 mapName="optimised-material-map",
0606 format=JsonFormat.Json,
0607 mapVolume=False,
0608 s=rMap,
0609 )
0610 rMap.run()
0611 print(
0612 datetime.now().strftime("%H:%M:%S")
0613 + " Waiting for all the score to have been stored",
0614 flush=True,
0615 )
0616
0617
0618 deadline = time.time() + 600
0619
0620 for key in binDict:
0621 timeout = max(deadline - time.time(), 0)
0622 expJob[key].join(timeout=timeout)
0623 expJob[key].terminate()
0624 print(
0625 datetime.now().strftime("%H:%M:%S")
0626 + " Experiment for surface "
0627 + str(key)
0628 + " is over",
0629 flush=True,
0630 )