Back to home page

EIC code displayed by LXR

 
 

    


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

0001 """Workflow configuration models for multi-stage, multi-objective orchestration.
0002 
0003 Defines DAG-based workflows with branches, stages, jobs, and objective computation specs.
0004 Reuses ObjectiveDefinition from objectives.py for unified objective specification.
0005 
0006 Key concepts:
0007     Workflow: An end-to-end evaluation unit (one design point evaluation).
0008     Branch: Optional subgraph inside a workflow (useful for multiple independent pipelines).
0009     Stage/Layer: Logical step group where multiple jobs run in parallel (fan-out).
0010     Job/Task: Smallest schedulable unit (one simulation, one training run, etc).
0011     Scheduler: Runtime executor for jobs (submit, monitor, collect status/artifacts).
0012 
0013 Project: AID2E v0.0.0 - AI assisted Detector Design for EIC
0014 Homepage: https://aid2e.github.io/aid2e-framework
0015 Repository: https://github.com/aid2e/AID2E-framework.git
0016 """
0017 
0018 from typing import List, Optional, Dict, Any, Union
0019 from pydantic import BaseModel, Field, field_validator
0020 from aid2e.utilities.configurations.objectives import ObjectiveDefinition, ObjectivePlanSpec, ObjectiveDirection
0021 from aid2e.utilities.configurations.scheduler_config import SchedulerConfiguration
0022 
0023 
0024 class CombinedObjectiveMetric(BaseModel):
0025     """Metric emitted by a combined objective plan.
0026 
0027     Attributes:
0028         name: Objective name (e.g., "f1").
0029         direction: Optimization direction for this metric.
0030         metric_key: Key in the plan output to extract this metric.
0031     """
0032 
0033     name: str = Field(..., description="Objective metric name")
0034     direction: ObjectiveDirection = Field(..., description="Direction for this metric")
0035     metric_key: str = Field(..., description="Key in plan output for this metric")
0036 
0037 
0038 class CombinedObjectivePlan(BaseModel):
0039     """Combined objective execution producing multiple metrics in one plan.
0040 
0041     Attributes:
0042         name: Identifier for the combined objective bundle.
0043         objective_plan: Plan executed once to emit multiple metrics.
0044         metrics: Metrics extracted from the plan output with their directions.
0045         scheduler: Optional scheduler default for this combined plan.
0046     """
0047 
0048     name: str = Field(..., description="Combined objective bundle name")
0049     objective_plan: ObjectivePlanSpec = Field(..., description="Plan producing multiple metrics")
0050     metrics: list[CombinedObjectiveMetric] = Field(
0051         ..., min_items=1, description="Metrics emitted by this plan"
0052     )
0053     scheduler: Optional[SchedulerConfiguration] = Field(
0054         default=None,
0055         description="Scheduler default for this combined plan",
0056     )
0057 
0058 
0059 class ParallelismPolicy(BaseModel):
0060     """Policy for parallel job execution within a stage.
0061     
0062     Attributes:
0063         max_concurrent: Maximum jobs to run concurrently in a stage.
0064         retry_max: Maximum retries on job failure.
0065         timeout_sec: Timeout per job in seconds.
0066         
0067     Example:
0068         >>> policy = ParallelismPolicy(max_concurrent=4, retry_max=2, timeout_sec=300)
0069     """
0070     max_concurrent: int = Field(default=4, ge=1, description="Max concurrent jobs in stage")
0071     retry_max: int = Field(default=2, ge=0, description="Max retries per failed job")
0072     timeout_sec: int = Field(default=300, ge=1, description="Timeout per job (seconds)")
0073 
0074 
0075 class ArtifactSpec(BaseModel):
0076     """Output artifact specification for a stage.
0077     
0078     Defines expected output files that stages/jobs produce.
0079     
0080     Attributes:
0081         path: File path pattern (e.g., "objectives_*.json").
0082         format: File format ("json", "yaml", "csv", or "root").
0083         
0084     Example:
0085         >>> artifact = ArtifactSpec(path="objectives_*.json", format="json")
0086     """
0087     path: str = Field(..., description="File path pattern (e.g., 'output_*.json')")
0088     format: str = Field(default="json", pattern="^(json|yaml|csv|root)$", description="File format")
0089 
0090 
0091 class JobDefinition(BaseModel):
0092     """Single job/task definition within a stage.
0093     
0094     A job is the smallest schedulable unit (e.g., one simulation, training run, etc).
0095     Jobs can be expanded from a template via job_factory.
0096     
0097     Attributes:
0098         name: Job name (e.g., "simulate").
0099         command: Executable command (e.g., "python scripts/dtlz2_problem.py").
0100         payload: Command arguments/payload (free-form dict, supports template substitution).
0101         rule: Optional template for constructing final command from payload.
0102               Uses format: "{command} {payload[key1]} {payload[key2]}" etc.
0103               If not specified, defaults to "{command}" (just execute command).
0104         resources: Resource requirements (free-form dict, e.g., {"memory": "4GB"}).
0105         outputs: Output artifacts this job produces.
0106         
0107     Example:
0108         >>> job = JobDefinition(
0109         ...     name="dtlz2_evaluate",
0110         ...     command="python scripts/dtlz2_problem.py",
0111         ...     rule="{{command}} {{payload[design_params_file]}} {{payload[output_dir]}} {payload[job_id]}",
0112         ...     payload={
0113         ...         "design_params_file": "{{input_design_params}}",
0114         ...         "output_dir": "{{output_dir}}",
0115         ...         "job_id": "{{job_id}}"
0116         ...     },
0117         ...     outputs=[ArtifactSpec(path="objectives_*.json", format="json")]
0118         ... )
0119         
0120     Notes:
0121         - Payload supports template substitution: {{job_id}}, {{output_dir}}, {{stage_outputs[stage_name]}}
0122         - Rule template follows experimental_stack.py StackLayer pattern
0123         - Resources dict is executor-dependent (e.g., JobLibRunner ignores, SlurmRunner uses)
0124     """
0125     name: str = Field(..., description="Job name")
0126     command: str = Field(..., description="Executable command")
0127     payload: Dict[str, Any] = Field(default_factory=dict, description="Command arguments and metadata")
0128     rule: Optional[str] = Field(
0129         default=None,
0130         description="Template rule for command construction (e.g., '{{command}} {{payload[input]}} {{payload[output]}}')"
0131     )
0132     resources: Dict[str, Any] = Field(default_factory=dict, description="Resource requirements")
0133     outputs: List[ArtifactSpec] = Field(default_factory=list, description="Output artifacts")
0134 
0135 
0136 class JobFactory(BaseModel):
0137     """Factory for generating multiple jobs from a template.
0138     
0139     Enables fan-out: creating N parallel jobs from one job definition.
0140     Useful for evaluating multiple design points in parallel.
0141     
0142     Attributes:
0143         type: Factory type ("range", "enumerate", "Cartesian", etc).
0144         params: Factory-specific parameters (e.g., {"n": 4} for range).
0145         
0146     Example:
0147         >>> # Create 4 parallel design point evaluations
0148         >>> factory = JobFactory(type="range", params={"n": 4})
0149         
0150     Notes:
0151         - "range" type: creates N copies with job_id = 0..N-1
0152         - "enumerate" type: creates one job per item in a list
0153         - "Cartesian" type: creates N_A * N_B jobs from two parameter sets
0154     """
0155     type: str = Field(default="range", description="Factory type (range, enumerate, Cartesian, etc)")
0156     params: Dict[str, Any] = Field(default_factory=dict, description="Factory parameters")
0157 
0158 
0159 class StageDefinition(BaseModel):
0160     """Stage/layer definition with jobs and scheduler.
0161     
0162     A stage is a logical step group where multiple jobs run in parallel (fan-out),
0163     then their outputs feed into downstream stages (fan-in).
0164     
0165     Attributes:
0166         name: Stage name (e.g., "evaluate", "aggregate").
0167         jobs: Job definitions to execute (usually one template, expanded via job_factory).
0168         job_factory: Optional factory for expanding jobs (e.g., N parallel evals).
0169         scheduler: Stage-level scheduler (optional, inherits global if not set).
0170         parallelism: Parallelism policy for this stage.
0171         outputs: Output artifact specs produced by this stage.
0172         
0173     Example:
0174         >>> stage = StageDefinition(
0175         ...     name="evaluate",
0176         ...     jobs=[
0177         ...         JobDefinition(
0178         ...             name="dtlz2_evaluate",
0179         ...             command="python scripts/dtlz2_problem.py",
0180         ...             payload={...},
0181         ...             outputs=[ArtifactSpec(path="objectives_*.json", format="json")]
0182         ...         )
0183         ...     ],
0184         ...     job_factory=JobFactory(type="range", params={"n": 4}),
0185         ...     parallelism=ParallelismPolicy(max_concurrent=4, retry_max=2),
0186         ...     outputs=[ArtifactSpec(path="objectives_*.json", format="json")]
0187         ... )
0188         
0189     Notes:
0190         - job_factory expands the first job in jobs list to N parallel jobs
0191         - scheduler overrides global scheduler (from WorkflowsConfiguration)
0192         - outputs are collected after all jobs complete
0193     """
0194     name: str = Field(..., description="Stage name")
0195     jobs: List[JobDefinition] = Field(default_factory=list, description="Job definitions")
0196     job_factory: Optional[JobFactory] = Field(default=None, description="Job expansion factory")
0197     scheduler: Optional[SchedulerConfiguration] = Field(default=None, description="Stage-level scheduler override")
0198     parallelism: ParallelismPolicy = Field(default_factory=ParallelismPolicy, description="Parallelism policy")
0199     outputs: List[ArtifactSpec] = Field(default_factory=list, description="Output artifacts")
0200 
0201 
0202 class BranchDefinition(BaseModel):
0203     """Branch definition (optional, for organizing stages in a DAG).
0204     
0205     A branch is an optional subgraph inside a workflow, useful when you want
0206     multiple independent pipelines under one workflow (e.g., "physics sim" branch
0207     + "surrogate" branch). Stages within a branch are executed in topological order.
0208     
0209     Attributes:
0210         name: Branch name (e.g., "main", "physics_sim", "surrogate").
0211         stages: List of stages in DAG order (assumes simple sequential order; extend with explicit DAG if needed).
0212         scheduler: Optional branch-level scheduler default (used if stage not set).
0213         
0214     Example:
0215         >>> branch = BranchDefinition(
0216         ...     name="main",
0217         ...     stages=[
0218         ...         StageDefinition(name="evaluate", ...),
0219         ...         StageDefinition(name="aggregate", ...)
0220         ...     ]
0221         ... )
0222         
0223     Notes:
0224         - Multiple branches in one workflow execute independently
0225         - For complex DAGs, extend this model with explicit edge definitions
0226     """
0227     name: str = Field(..., description="Branch name")
0228     stages: List[StageDefinition] = Field(default_factory=list, description="Stages in execution order")
0229     scheduler: Optional[SchedulerConfiguration] = Field(
0230         default=None,
0231         description="Branch-level scheduler default (overrides workflow, used if stage unset)",
0232     )
0233 
0234 
0235 class WorkflowDefinition(BaseModel):
0236     """Workflow definition with branches, objectives, and scheduler defaults.
0237     
0238     A workflow is an end-to-end evaluation unit (e.g., one design point evaluation).
0239     It consists of optional branches, each with multiple stages, and defines the
0240     objectives to compute from the outputs.
0241     
0242     Attributes:
0243         name: Workflow name (e.g., "dtlz2_eval").
0244         description: Optional description.
0245         branches: Workflow branches (optional, defaults to single implicit branch if missing).
0246         objectives: Objectives to compute (reuses ObjectiveDefinition).
0247         combined_objectives: Optional combined plans emitting multiple metrics in one run.
0248         scheduler: Workflow-level scheduler default (used if branch/stage unset).
0249         
0250     Notes:
0251         - If branches is empty, executor creates single implicit branch
0252         - Objectives are unified model (ObjectiveDefinition) for consistency
0253     """
0254     name: str = Field(..., description="Workflow name")
0255     description: Optional[str] = Field(default=None, description="Workflow description")
0256     stack_type: Optional[str] = Field(default=None,description="Experimental stack type for workflow-level geometry prep")
0257     branches: List[BranchDefinition] = Field(default_factory=list, description="Workflow branches (optional)")
0258     objectives: List[ObjectiveDefinition] = Field(
0259         default_factory=list,
0260         description="Objectives to compute (reuses ObjectiveDefinition)"
0261     )
0262     combined_objectives: List[CombinedObjectivePlan] = Field(
0263         default_factory=list,
0264         description="Combined objective plans emitting multiple metrics in one run",
0265     )
0266     scheduler: Optional[SchedulerConfiguration] = Field(
0267         default=None,
0268         description="Workflow-level scheduler default (overrides global, used if branch/stage unset)",
0269     )
0270     stack_type: Optional[str] = Field(default=None,description="Experimental stack type for workflow-level geometry prep")
0271 
0272     def get_implicit_branch(self) -> BranchDefinition:
0273         """Get or create single implicit branch if branches list is empty.
0274         
0275         Returns:
0276             Single implicit branch if branches is empty, else raises error.
0277             
0278         Raises:
0279             ValueError: If branches list is not empty.
0280         """
0281         if self.branches:
0282             raise ValueError("Branches already defined; cannot use implicit branch")
0283         return BranchDefinition(name="implicit")
0284 
0285 
0286 class WorkflowsConfiguration(BaseModel):
0287     """Top-level workflows configuration.
0288     
0289     Container for multiple independent workflows (e.g., one per objective in a
0290     holistic optimization). Each workflow can have its own stages, scheduler, and
0291     objective specs.
0292     
0293     Attributes:
0294         workflows: List of independent workflows.
0295         global_scheduler: Default scheduler for all stages (can be overridden per-stage).
0296         
0297     Example:
0298         >>> config = WorkflowsConfiguration(
0299         ...     workflows=[
0300         ...         WorkflowDefinition(name="dtlz2_eval", ...),
0301         ...         WorkflowDefinition(name="physics_sim", ...)
0302         ...     ],
0303         ...     global_scheduler=SchedulerConfiguration(
0304         ...         runner_type="JobLibRunner",
0305         ...         joblib=JobLibRunnerConfig(n_jobs=-1)
0306         ...     )
0307         ... )
0308         
0309     Notes:
0310         - global_scheduler is inherited by all stages unless overridden
0311         - workflows list must be non-empty
0312         - Useful for Option B: multiple independent workflows per objective
0313     """
0314     workflows: List[WorkflowDefinition] = Field(..., min_items=1, description="List of workflows")
0315     global_scheduler: Optional[SchedulerConfiguration] = Field(
0316         default=None,
0317         description="Default scheduler for all stages (can be overridden per-stage)"
0318     )
0319     
0320     @field_validator('workflows')
0321     @classmethod
0322     def validate_unique_workflow_names(cls, workflows: List[WorkflowDefinition]) -> List[WorkflowDefinition]:
0323         """Ensure all workflow names are unique.
0324         
0325         Args:
0326             workflows: List of workflow definitions.
0327             
0328         Returns:
0329             Same list if valid.
0330             
0331         Raises:
0332             ValueError: If duplicate workflow names found.
0333         """
0334         names = [w.name for w in workflows]
0335         if len(set(names)) != len(names):
0336             raise ValueError("Workflow names must be unique")
0337         return workflows