Back to home page

EIC code displayed by LXR

 
 

    


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 # constants
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 # Do template substitution
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 # Set up for examples
0135 # =============================================================================
0136 
0137 def setup():
0138     """Setup to run examples"""
0139 
0140     # create directories for tests
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     # create directories to run in
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     # clone epic repo
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 # Example 0: Modify ePIC geometry
0160 # =============================================================================
0161 
0162 def example_modify_geometry():
0163     """Modify ePIC geometry"""
0164 
0165     # hard code path to compact file for testing
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     # set up design configruation and generate
0170     # modifications to apply
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     # apply changes to compact files
0177     modify_xml_files(modifications)
0178     print("  -- modified files")
0179 
0180     return configuration
0181 
0182 
0183 # =============================================================================
0184 # Example 1: Configuring Layers Directly
0185 # =============================================================================
0186 
0187 def example_configure_layers():
0188     """Stack layer configuration"""
0189 
0190     # configure desired layers in a stack
0191     #   --> Note that values in {{ }} will be substituted
0192     #       during execution
0193     cfg_geo = EpicLayerConfig(
0194         name = "geo", # NOTE this should be a unique identifier for THIS instance of a layer
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 # Example 2: Instantiate Configurations and Context
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     # information like execution directory, ID of current job,
0280     # etc is available to the stack via the JobContext
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 = {},  # NOTE empty dict for testing
0288         artifacts = {},  # NOTE empty dict for testing
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 # Example 3: Generate Driver Script
0301 # =============================================================================
0302 
0303 def example_generate_driver(layers: List[EpicLayerConfig], configs: Tuple[ProblemConfiguration, JobContext]):
0304     """Generate driver script for configured layers"""
0305 
0306     # grab context and instantiate an ePIC stack
0307     context = configs[1]
0308     epic_stack = EpicStack()
0309 
0310     # add a dummy prepared geometry directory
0311     # to workflow_context for testing
0312     context.workflow_context.parameters["prepared_geometry_dir"] = CONST["enviro"]["epic_environment"]["epic_install"]
0313 
0314     # add dummy inputs/outputs to xcom
0315     # for testing
0316     context.xcom = {
0317 
0318         # simulation jobs
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         # merge layer
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         # reconstruction layer
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         # analysis layer
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     # make sure necessary environment variables are set
0350     context.problem_config.environment_config.activate()
0351 
0352     # copy layers for test and substitute relevant templates
0353     test_layers = copy.deepcopy(layers)
0354     resolved_layers = substitute_templates(test_layers, context)
0355     print(f"  -- Resolved layers:\n    {resolved_layers}")
0356 
0357     # prepare for generating script by modifying geometry
0358     prep = epic_stack.prepare_for_execution(context = context)
0359 
0360     # generate driver script
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 # Example 4: Run Script in a Workflow
0378 # =============================================================================
0379 
0380 def example_run_script(layers: List[EpicLayerConfig], configs: Tuple[ProblemConfiguration, JobContext]):
0381     """Run driver script in a workflow"""
0382 
0383     # update output/work location to execution dir
0384     problem = configs[0]
0385     problem.output_location = CONST['exec_dir']
0386     problem.work_location = CONST['exec_dir']
0387 
0388     # As an example, workflow will consist of 3 stages:
0389     #   1. run overlap check (geo)
0390     #   2. run sim A, B, C in parallel
0391     #   3. run ana A + rec + ana B in sequence
0392 
0393     # -------------------------------------------------------------------------
0394     # Stage 1
0395     # -------------------------------------------------------------------------
0396 
0397     job_1 = EpicJobDefinition(
0398         name = "geo_job",
0399         layers = [layers[0]],
0400         payload = {
0401             "evaluator_type": "stack",  # FIXME this should be set by default
0402             "stack_type": "epic",  # FIXME this should be set by default
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     # Stage 2
0415     # -------------------------------------------------------------------------
0416 
0417     job_2A = EpicJobDefinition(
0418         name = "sim_job_0",
0419         layers = [layers[1]],
0420         payload = {
0421             "evaluator_type": "stack", # FIXME this should be set by default
0422             "stack_type": "epic",  # FIXME this should be set by default
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", # FIXME this should be set by default
0432             "stack_type": "epic", # FIXME this should be set by default
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", # FIXME this should be set by default
0441             "stack_type": "epic", # FIXME this should be set by default
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     # Stage 3
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",  # FIXME this should be set by default
0461             "stack_type": "epic",  # FIXME this should be set by default
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     # Organize stages into a workflow
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     # Run workflow
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 # Main
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)