File indexing completed on 2026-08-12 08:24:55
0001 """Example: Configuring and running layers of a stack
0002
0003 This low-level example demonstrates:
0004 1. How to directly configure layers of an experimental
0005 stack in python
0006 2. Generate a driver script to run configured
0007 layers
0008 3. Run script as a stage in a worklow
0009
0010 Project: AID2E v0.0.0 - AI assisted Detector Design for EIC
0011 """
0012
0013 from typing import List, Tuple
0014 import argparse
0015 import copy
0016 import os
0017
0018 from aid2e.utilities.configurations import (
0019 BranchDefinition,
0020 ObjectiveDirection,
0021 ObjectiveDefinition,
0022 ProblemConfiguration,
0023 WorkflowDefinition,
0024 )
0025 from aid2e.utilities.epic_utils import (
0026 EpicDesignConfig,
0027 EpicEnvConfig,
0028 EpicJobDefinition,
0029 EpicLayerConfig,
0030 EpicParameter,
0031 EpicStack,
0032 EpicStageDefinition,
0033 )
0034 from aid2e.utilities.workflows import (
0035 DAGExecutor,
0036 JobContext,
0037 modify_xml_files,
0038 Template,
0039 WorkflowSharedContext,
0040 )
0041
0042
0043 CONST = {
0044 "test_dir" : "epic_example_test",
0045 "exec_dir" : "epic_example_exec",
0046 "design" : {
0047 "epic_design_space" : {
0048 "epic_design_parameters" : {
0049 "bic" : {
0050 "file_path" : "compact/ecal/bic_default.xml",
0051 "parameters" : {
0052 "EcalBarrel_enable_staves_2" : {
0053 "value" : 0,
0054 "choices" : (0, 1),
0055 "xml_path" : ".//constant[@name='EcalBarrel_enable_staves_2']",
0056 "attribute" : "value",
0057 "unit" : "",
0058 },
0059 "EcalBarrel_enable_staves_4" : {
0060 "value" : 1,
0061 "choices" : (0, 1),
0062 "xml_path" : ".//constant[@name='EcalBarrel_enable_staves_4']",
0063 "attribute" : "value",
0064 "unit" : "",
0065 },
0066 "EcalBarrel_enable_staves_6" : {
0067 "value" : 1,
0068 "choices" : (0, 1),
0069 "xml_path" : ".//constant[@name='EcalBarrel_enable_staves_6']",
0070 "attribute" : "value",
0071 "unit" : "",
0072 }
0073 },
0074 }
0075 },
0076 "optimization_groups" : {"default" : [
0077 "bic.EcalBarrel_enable_staves_2",
0078 "bic.EcalBarrel_enable_staves_3",
0079 "bic.EcalBarrel_enable_staves_4",
0080 "bic.EcalBarrel_enable_staves_5",
0081 "bic.EcalBarrel_enable_staves_6"
0082 ]},
0083 }
0084 },
0085 "enviro" : {
0086 "epic_environment" : {
0087 "epic_install" : "epic_example_test/epic",
0088 "epic_config" : "epic",
0089 "eic_shell" : "/home/dereka/.bin/eic-shell",
0090 },
0091 },
0092 }
0093
0094
0095
0096
0097
0098
0099 def substitute_templates(layers: List[EpicLayerConfig], context: JobContext):
0100 """
0101 Apply template substitutes (parallels
0102 StackExecutionEngine._apply_template_substitution)
0103 """
0104
0105 def substitute(text, context):
0106 result = text
0107 result = Template.substitute(result, context)
0108 return result
0109
0110 new_layers = layers
0111 for layer in new_layers:
0112 resolved_inputs = list()
0113 for layer_input in layer.inputs:
0114 layer_input = substitute(layer_input, context)
0115 resolved_inputs.append(layer_input)
0116 layer.inputs = resolved_inputs
0117
0118 resolved_outputs = list()
0119 for layer_output in layer.outputs:
0120 layer_output = substitute(layer_output, context)
0121 resolved_outputs.append(layer_output)
0122 layer.outputs = resolved_outputs
0123
0124 if layer.arguments is not None:
0125 resolved_arguments = list()
0126 for layer_argument in layer.arguments:
0127 layer_argument = substitute(layer_argument, context)
0128 resolved_arguments.append(layer_argument)
0129 layer.arguments = resolved_arguments
0130
0131 return new_layers
0132
0133
0134
0135
0136
0137 def setup():
0138 """Setup to run examples"""
0139
0140
0141 if os.path.exists(CONST["test_dir"]):
0142 os.system(f"rm -rf {CONST['test_dir']}")
0143 os.makedirs(CONST["test_dir"])
0144 print(f" -- Made execution directory at {CONST['test_dir']}")
0145
0146
0147 if os.path.exists(CONST["exec_dir"]):
0148 os.system(f"rm -rf {CONST['exec_dir']}")
0149 os.makedirs(CONST["exec_dir"])
0150 print(f" -- Made execution directory at {CONST['exec_dir']}")
0151
0152
0153 os.system(f"git clone git@github.com:eic/epic.git {CONST['test_dir']}/epic")
0154 print(f" -- Cloned epic repo:")
0155 os.system(f"ls {CONST['test_dir']}/epic")
0156
0157
0158
0159
0160
0161
0162 def example_modify_geometry():
0163 """Modify ePIC geometry"""
0164
0165
0166 design = CONST["design"]["epic_design_space"]
0167 design["epic_design_parameters"]["bic"]["file_path"] = f"{CONST['test_dir']}/epic/compact/ecal/bic_default.xml"
0168
0169
0170
0171 configuration = EpicDesignConfig(**design)
0172 parameters = configuration.get_flat_parameters()
0173 modifications = configuration.get_xml_modifications({"bic.EcalBarrel_enable_staves_2" : 1})
0174 print(f" -- parameters & modifications:\n parameters = {parameters}\n modifications = {modifications}")
0175
0176
0177 modify_xml_files(modifications)
0178 print(" -- modified files")
0179
0180 return configuration
0181
0182
0183
0184
0185
0186
0187 def example_configure_layers():
0188 """Stack layer configuration"""
0189
0190
0191
0192
0193 cfg_geo = EpicLayerConfig(
0194 name = "geo",
0195 layer = "geo",
0196 inputs = ["{{geometry_dir}}/install/share/epic/epic.xml"],
0197 outputs = ["{{execution_dir}}/epic_geo.overlaps.txt"],
0198 )
0199 cfg_sim_A = EpicLayerConfig(
0200 name = "sim_bin0",
0201 layer = "sim",
0202 inputs = ["inputs/central_photons_bin0.py"],
0203 outputs = ["{{execution_dir}}/central_photons_bin0.edm4hep.root"],
0204 command = "ddsim",
0205 )
0206 cfg_sim_B = EpicLayerConfig(
0207 name = "sim_bin1",
0208 layer = "sim",
0209 inputs = ["inputs/central_photons_bin1.py"],
0210 outputs = ["{{execution_dir}}/central_photons_bin1.edm4hep.root"],
0211 )
0212 cfg_sim_C = EpicLayerConfig(
0213 name = "sim_bin2",
0214 layer = "sim",
0215 inputs = ["inputs/central_photons_bin2.py"],
0216 outputs = ["{{execution_dir}}/central_photons_bin2.edm4hep.root"],
0217 )
0218 cfg_ana_A = EpicLayerConfig(
0219 name = "ana_merge",
0220 layer = "ana",
0221 inputs = [
0222 "{{outputs[sim_stage:sim_job_0:sim_bin0](0)}}",
0223 "{{outputs[sim_stage:sim_job_1:sim_bin1](0)}}",
0224 "{{outputs[sim_stage:sim_job_2:sim_bin2](0)}}"
0225 ],
0226 outputs = ["{{execution_dir}}/central_photons.edm4hep.root"],
0227 command = "hadd",
0228 rule = "{{command}} -f {{outputs}} {{inputs}}"
0229 )
0230 cfg_rec = EpicLayerConfig(
0231 name = "rec",
0232 layer = "rec",
0233 inputs = ["{{outputs[merge_rec_ana_stage:merge_rec_ana_job:ana_merge](0)}}"],
0234 outputs = ["{{execution_dir}}/central_photons.edm4eic.root"],
0235 arguments = ["-Pnthreads=8", "-Peicrecon:LogLevel=debug"],
0236 )
0237 cfg_ana_B = EpicLayerConfig(
0238 name = "ana_reso",
0239 layer = "ana",
0240 inputs = ["{{outputs[merge_rec_ana_stage:merge_rec_ana_job:rec](0)}}"],
0241 outputs = ["{{execution_dir}}/central_photon_phi_resolution.hist.root"],
0242 arguments = ["-c phi", "-s 22"],
0243 command = "scripts/bic_angular_reso.py",
0244 rule = "python {{command}} -i {{inputs}} -o {{outputs}} {{arguments}}",
0245 )
0246 cfgs = [cfg_geo, cfg_sim_A, cfg_sim_B, cfg_sim_C, cfg_ana_A, cfg_rec, cfg_ana_B]
0247
0248 print(f" -- Configured layers:\n {cfgs}")
0249 return cfgs
0250
0251
0252
0253
0254
0255
0256 def example_make_configs_and_context():
0257
0258 design = EpicDesignConfig(**CONST['design']['epic_design_space'])
0259 enviro = EpicEnvConfig(**CONST['enviro']['epic_environment'])
0260 print(f" -- Created design and environment configs:\n design = {design}\n enviro = {enviro}")
0261
0262 objective = ObjectiveDefinition(
0263 name = "central_photons_phi_resolution",
0264 direction = ObjectiveDirection.MINIMIZE,
0265 )
0266 print(f" -- Defined objective:\n objective = {objective}")
0267
0268 problem = ProblemConfiguration(
0269 name = "test_problem",
0270 output_location = f"{CONST['test_dir']}",
0271 work_location = f"{CONST['test_dir']}",
0272 problem_type = "EPIC_TEST",
0273 design_config = design,
0274 objectives = [objective],
0275 environment_config = enviro,
0276 )
0277 print(f" -- Created ProblemConfiguration:\n problem = {problem}")
0278
0279
0280
0281 context = JobContext(
0282 task_id = "test_stage:make_driver",
0283 job_id = "make_driver",
0284 stage_id = "test_stage",
0285 workflow_id = "test_workflow",
0286 design_point = {"bic.EcalBarrel_enable_staves_4" : 0},
0287 xcom = {},
0288 artifacts = {},
0289 logs = [f"{CONST['test_dir']}/make_test_driver.log"],
0290 execution_dir = f"{CONST['test_dir']}",
0291 problem_config = problem,
0292 workflow_context = WorkflowSharedContext("workflow"),
0293 )
0294
0295 print(f" -- Created JobContext:\n context = {context}")
0296 return (problem, context)
0297
0298
0299
0300
0301
0302
0303 def example_generate_driver(layers: List[EpicLayerConfig], configs: Tuple[ProblemConfiguration, JobContext]):
0304 """Generate driver script for configured layers"""
0305
0306
0307 context = configs[1]
0308 epic_stack = EpicStack()
0309
0310
0311
0312 context.workflow_context.parameters["prepared_geometry_dir"] = CONST["enviro"]["epic_environment"]["epic_install"]
0313
0314
0315
0316 context.xcom = {
0317
0318
0319 "sim_stage:sim_job_0:sim_bin0:inputs": ["inputs/central_photons_bin0.py"],
0320 "sim_stage:sim_job_0:sim_bin0:outputs": [f"{CONST['test_dir']}/central_photons_bin0.edm4hep.root"],
0321 "sim_stage:sim_job_0:sim_bin0:arguments": [],
0322 "sim_stage:sim_job_1:sim_bin1:inputs": ["inputs/central_photons_bin1.py"],
0323 "sim_stage:sim_job_1:sim_bin1:outputs": [f"{CONST['test_dir']}/central_photons_bin1.edm4hep.root"],
0324 "sim_stage:sim_job_1:sim_bin1:arguments": [],
0325 "sim_stage:sim_job_2:sim_bin2:inputs": ["inputs/central_photons_bin2.py"],
0326 "sim_stage:sim_job_2:sim_bin2:outputs": [f"{CONST['test_dir']}/central_photons_bin2.edm4hep.root"],
0327 "sim_stage:sim_job_2:sim_bin2:arguments": [],
0328
0329
0330 "merge_rec_ana_stage:merge_rec_ana_job:ana_merge:inputs": [
0331 f"{CONST['test_dir']}/central_photons_bin0.edm4hep.root",
0332 f"{CONST['test_dir']}/central_photons_bin1.edm4hep.root",
0333 f"{CONST['test_dir']}/central_photons_bin2.edm4hep.root",
0334 ],
0335 "merge_rec_ana_stage:merge_rec_ana_job:ana_merge:outputs": [f"{CONST['test_dir']}/central_photons.edm4hep.root"],
0336 "merge_rec_ana_stage:merge_rec_ana_job:ana_merge:arguments": [],
0337
0338
0339 "merge_rec_ana_stage:merge_rec_ana_job:rec:inputs": [f"{CONST['test_dir']}/central_photons.edm4hep.root"],
0340 "merge_rec_ana_stage:merge_rec_ana_job:rec:outputs": [f"{CONST['test_dir']}/central_photons.edm4eic.root"],
0341 "merge_rec_ana_stage:merge_rec_ana_job:rec:arguments": ["-Pnthreads=8", "-Peicrecon:LogLevel=debug"],
0342
0343
0344 "merge_rec_ana_stage:merge_rec_ana_job:ana_reso:inputs": [f"{CONST['test_dir']}/central_photons.edm4eic.root"],
0345 "merge_rec_ana_stage:merge_rec_ana_job:ana_reso:outputs": [f"{CONST['test_dir']}/central_photons_phi_resolution.hist.root"],
0346 "merge_rec_ana_stage:merge_rec_ana_job:ana_reso:arguments": ["-c phi", "-s 22"],
0347 }
0348
0349
0350 context.problem_config.environment_config.activate()
0351
0352
0353 test_layers = copy.deepcopy(layers)
0354 resolved_layers = substitute_templates(test_layers, context)
0355 print(f" -- Resolved layers:\n {resolved_layers}")
0356
0357
0358 prep = epic_stack.prepare_for_execution(context = context)
0359
0360
0361 drvr_script = f"{CONST['test_dir']}/driver_from_stack.sh"
0362 epic_stack.make_driver_script(
0363 script = drvr_script,
0364 configs = resolved_layers,
0365 preparations = prep,
0366 context = context,
0367 )
0368
0369 drvr_path = os.path.abspath(drvr_script)
0370 print(f" -- Created script at {drvr_path}")
0371
0372 drvr_command = epic_stack.make_driver_command(drvr_path)
0373 print(f" -- Created command:\n command = {drvr_command}")
0374
0375
0376
0377
0378
0379
0380 def example_run_script(layers: List[EpicLayerConfig], configs: Tuple[ProblemConfiguration, JobContext]):
0381 """Run driver script in a workflow"""
0382
0383
0384 problem = configs[0]
0385 problem.output_location = CONST['exec_dir']
0386 problem.work_location = CONST['exec_dir']
0387
0388
0389
0390
0391
0392
0393
0394
0395
0396
0397 job_1 = EpicJobDefinition(
0398 name = "geo_job",
0399 layers = [layers[0]],
0400 payload = {
0401 "evaluator_type": "stack",
0402 "stack_type": "epic",
0403 "job_id": "geo",
0404 },
0405 )
0406
0407 stage_1 = EpicStageDefinition(
0408 name = "geo_stage",
0409 jobs = [job_1],
0410 )
0411 print(f" -- Defined stage 1:\n {stage_1}")
0412
0413
0414
0415
0416
0417 job_2A = EpicJobDefinition(
0418 name = "sim_job_0",
0419 layers = [layers[1]],
0420 payload = {
0421 "evaluator_type": "stack",
0422 "stack_type": "epic",
0423 "job_id": "sim_0",
0424 },
0425 )
0426
0427 job_2B = EpicJobDefinition(
0428 name = "sim_job_1",
0429 layers = [layers[2]],
0430 payload = {
0431 "evaluator_type": "stack",
0432 "stack_type": "epic",
0433 "job_id": "sim_1",
0434 },
0435 )
0436 job_2C = EpicJobDefinition(
0437 name = "sim_job_2",
0438 layers = [layers[3]],
0439 payload = {
0440 "evaluator_type": "stack",
0441 "stack_type": "epic",
0442 "job_id": "sim_2",
0443 },
0444 )
0445
0446 stage_2 = EpicStageDefinition(
0447 name = "sim_stage",
0448 jobs = [job_2A, job_2B, job_2C],
0449 )
0450 print(f" -- Defined stage 2:\n {stage_2}")
0451
0452
0453
0454
0455
0456 job_3 = EpicJobDefinition(
0457 name = "merge_rec_ana_job",
0458 layers = [layers[4], layers[5], layers[6]],
0459 payload = {
0460 "evaluator_type": "stack",
0461 "stack_type": "epic",
0462 "job_id": "merge_rec_ana",
0463 },
0464 )
0465
0466 stage_3 = EpicStageDefinition(
0467 name = "merge_rec_ana_stage",
0468 jobs = [job_3],
0469 )
0470 print(f" -- Defined stage 3:\n {stage_3}")
0471
0472
0473
0474
0475
0476 branch = BranchDefinition(
0477 name = "main",
0478 stages = [stage_1, stage_2, stage_3],
0479 )
0480
0481 workflow = WorkflowDefinition(
0482 name = "epic_workflow",
0483 description = "An ePIC pipeline: check geometry ā run simulations ā run reco + ana",
0484 branches = [branch],
0485 objectives = [],
0486 stack_type = 'epic',
0487 )
0488 print(f" -- Defined workflow:\n {branch}\n {workflow}")
0489
0490
0491
0492
0493
0494 executor = DAGExecutor(
0495 workflow,
0496 base_output_dir = f"{CONST['exec_dir']}",
0497 log_level = "INFO",
0498 problem_config = problem,
0499 )
0500 design_point = {"bic.EcalBarrel_enable_staves_6" : 0}
0501
0502 print(f"\n -- Running ePIC workflow...")
0503 objectives = executor.execute(design_point)
0504
0505 print(f"\nā
ePIC workflow completed!")
0506
0507
0508
0509
0510
0511
0512 if __name__ == '__main__':
0513
0514 parser = argparse.ArgumentParser()
0515 parser.add_argument('-s', '--setup', action = 'store_true', help = 'Set up for running')
0516
0517 args = parser.parse_args()
0518 if args.setup:
0519 print("\nSetting up for running")
0520 print("-" * 70)
0521 setup()
0522 else:
0523 if not os.path.exists(CONST["exec_dir"]):
0524 print("\nRun directory not found, setting up for running")
0525 print("-" * 70)
0526 setup()
0527
0528 print("\nExample 0: modify geometry")
0529 print("-" * 70)
0530 design = example_modify_geometry()
0531
0532 print("\nExample 1: configure layers")
0533 print("-" * 70)
0534 layers = example_configure_layers()
0535
0536 print("\nExample 2: make configurations and context")
0537 print("-" * 70)
0538 configs = example_make_configs_and_context()
0539
0540 print("\nExample 3: generate driver script")
0541 print("-" * 70)
0542 example_generate_driver(layers, configs)
0543
0544 print("\nExample 4: run driver script")
0545 print("-" * 70)
0546 example_run_script(layers, configs)