Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-12 08:24:56

0001 """Experimental software stack configuration models
0002 
0003 Defines models necessary for wiring experimental software stacks into workflows.
0004 Allows users to specify layers of a pre-defined stack as part of a workflow.
0005 
0006 Key Classes:
0007     - StackLayerConfig, configures a stack layer
0008 
0009 Project: AID2E v0.0.0 - AI assisted Detector Design for EIC
0010 Homepage: https://aid2e.github.io/aid2e-framework
0011 Repository: https://github.com/aid2e/AID2E-framework.git
0012 """
0013 
0014 from pydantic import AliasChoices, BaseModel, Field, model_validator
0015 from typing import List, Optional
0016 
0017 from aid2e.utilities.configurations.workflow_config import (
0018     BranchDefinition,
0019     JobDefinition,
0020     StageDefinition,
0021     WorkflowDefinition,
0022     WorkflowsConfiguration,
0023 )
0024 
0025 
0026 class StackLayerConfig(BaseModel):
0027     """Configures layer of an experimental stack
0028 
0029     A job may consist of 1 or many layers from an experimental
0030     software stack. Sanitizes provided data to make sure
0031     singular vs. plural inputs/outputs are handled consistently.
0032 
0033     Attributes:
0034         name: Unique name for this layer instance
0035         layer: Layer key (e.g. "sim", "rec", "ana")
0036         inputs: List of inputs to layer
0037         outputs: List of outputs from layer
0038         arguments: Optional list of any additional arguments to apply
0039         command: Optional command to be run. Can be used to
0040                  override default of layer.
0041         rule: Optional recipe for combining inputs, outputs, arguments,
0042               and command. Can be used to override default of layer.
0043 
0044     Notes:
0045         - rule supports template substitutions for {inputs}, {outputs},
0046           {arguments}, and {command}. See StackLayer for more details.
0047     """
0048     name: Optional[str] = Field(default=None, description="Unique name of instance")
0049     layer: str = Field(..., description="Layer key")
0050     inputs: List[str] = Field(..., description="List of inputs", validation_alias=AliasChoices('inputs', 'input'))
0051     outputs: List[str] = Field(..., description="List of outputs", validation_alias=AliasChoices('outputs', 'output'))
0052     arguments: Optional[List[str]] = Field(default=None, description="List of arguments")
0053     command: Optional[str] = Field(default=None, description="Executable command")
0054     rule: Optional[str] = Field(default=None, description="Recipe for combining arguments")
0055 
0056     @classmethod
0057     def pluralize_strings(cls, data, singular, plural):
0058         """Pluralize strings in data
0059 
0060          Sanitize input by data by ensuring that
0061          'singular' keys are always 'plural' and
0062          that their values are wrapped in lists.
0063 
0064          Args:
0065              data: the to be sanitized
0066              singular: the singular case of the key
0067                        (e.g. 'input')
0068              plural: the plural case of the key
0069                      (e.g. 'inputs')
0070 
0071          Returns:
0072              Sanitized data
0073          """
0074         if singular in data and plural not in data:
0075             data[singular] = data[plural]
0076         if isinstance(data.get(plural), str):
0077             data[plural] = [data[plural]]
0078         return data
0079 
0080     @model_validator(mode='before')
0081     @classmethod
0082     def handle_input_variants(cls, data):
0083         """
0084         Handles cases where (1) 'input' vs. 'inputs' was used in key,
0085         and (2) only 1 string was provided.
0086         """
0087         return cls.pluralize_strings(data, "input", "inputs")
0088 
0089     @model_validator(mode='before')
0090     @classmethod
0091     def handle_output_variants(cls, data):
0092         """
0093         Handles cases where (1) 'output' vs. 'outputs' was used in key,
0094         and (2) only 1 string was provided.
0095         """
0096         return cls.pluralize_strings(data, "output", "outputs")
0097 
0098 
0099 class StackJobDefinition(JobDefinition):
0100     """
0101     Extends the base JobDefinition with a list of stack
0102     layers to utilize built-in commands of an experimental
0103     stack.
0104 
0105     Extensions:
0106         script: Name of driver script to generate
0107         layers: Layer configurations to run in this job
0108     """
0109     command: Optional[str] = Field(default="./{script}", description="Executable command")
0110     script: Optional[str] = Field(default="do_job_{{context.job_id}}.sh", description="Driver script name")
0111     layers: List[StackLayerConfig] = Field(default_factory=list, description="Software stack layer configurations")
0112 
0113 
0114 class StackStageDefinition(StageDefinition):
0115 
0116     """
0117     Definition of a workflow stage narrowed to jobs from
0118     an experimental software stack.
0119     """
0120     jobs: List[StackJobDefinition] = Field(default_factory=list, description="Software stack job definitions")
0121 
0122 
0123 class StackBranchDefinition(BranchDefinition):
0124     """
0125     Definition of a workflow branch narrowed to stages
0126     from an experimental software stack.
0127     """
0128     stages: List[StackStageDefinition] = Field(default_factory=list, description="Software stack stage definitions")
0129 
0130 
0131 class StackWorkflowDefinition(WorkflowDefinition):
0132     """
0133     Definition of an experimental software stack
0134     workflow.
0135     """
0136     branches: List[StackBranchDefinition] = Field(default_factory=list, description="Software stack workflow branches (optional)")
0137 
0138     # FIXME there should be a way to automate this (incl. grabbing the
0139     # appropriate branch type) in the superclass
0140     def get_implicit_branch(self) -> StackBranchDefinition:
0141         """
0142         Get or create single implicit branch if branches list is empty.
0143         Overrides WorkflowDefinition.get_implicit_branch to return
0144         StackBranchDefinition.
0145         """
0146         if self.branches:
0147             raise ValueError("Branches already defined; cannot use implicit branch")
0148         return StackBranchDefinition(name="implicit")
0149 
0150 
0151 class StackWorkflowsConfiguration(WorkflowsConfiguration):
0152     """
0153     Container for software stack workflows.
0154     """
0155     # FIXME ideally we should allow for workflows from separate
0156     # stacks to be run concurrently
0157     workflows: List[StackWorkflowDefinition] = Field(..., min_items=1, description="List of workflows")