Back to home page

EIC code displayed by LXR

 
 

    


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

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         resources: Resource requirements (free-form dict, e.g., {"memory": "4GB"}).
0102         outputs: Output artifacts this job produces.
0103         
0104     Example:
0105         >>> job = JobDefinition(
0106         ...     name="dtlz2_evaluate",
0107         ...     command="python scripts/dtlz2_problem.py",
0108         ...     payload={
0109         ...         "design_params_file": "{input_design_params}",
0110         ...         "output_file": "{output_dir}/objectives_{job_id}.json"
0111         ...     },
0112         ...     outputs=[ArtifactSpec(path="objectives_*.json", format="json")]
0113         ... )
0114         
0115     Notes:
0116         - Payload supports template substitution: {job_id}, {output_dir}, {stage_outputs[stage_name]}
0117         - Resources dict is executor-dependent (e.g., JobLibRunner ignores, SlurmRunner uses)
0118     """
0119     name: str = Field(..., description="Job name")
0120     command: str = Field(..., description="Executable command")
0121     payload: Dict[str, Any] = Field(default_factory=dict, description="Command arguments")
0122     resources: Dict[str, Any] = Field(default_factory=dict, description="Resource requirements")
0123     outputs: List[ArtifactSpec] = Field(default_factory=list, description="Output artifacts")
0124 
0125 
0126 class JobFactory(BaseModel):
0127     """Factory for generating multiple jobs from a template.
0128     
0129     Enables fan-out: creating N parallel jobs from one job definition.
0130     Useful for evaluating multiple design points in parallel.
0131     
0132     Attributes:
0133         type: Factory type ("range", "enumerate", "Cartesian", etc).
0134         params: Factory-specific parameters (e.g., {"n": 4} for range).
0135         
0136     Example:
0137         >>> # Create 4 parallel design point evaluations
0138         >>> factory = JobFactory(type="range", params={"n": 4})
0139         
0140     Notes:
0141         - "range" type: creates N copies with job_id = 0..N-1
0142         - "enumerate" type: creates one job per item in a list
0143         - "Cartesian" type: creates N_A * N_B jobs from two parameter sets
0144     """
0145     type: str = Field(default="range", description="Factory type (range, enumerate, Cartesian, etc)")
0146     params: Dict[str, Any] = Field(default_factory=dict, description="Factory parameters")
0147 
0148 
0149 class StageDefinition(BaseModel):
0150     """Stage/layer definition with jobs and scheduler.
0151     
0152     A stage is a logical step group where multiple jobs run in parallel (fan-out),
0153     then their outputs feed into downstream stages (fan-in).
0154     
0155     Attributes:
0156         name: Stage name (e.g., "evaluate", "aggregate").
0157         jobs: Job definitions to execute (usually one template, expanded via job_factory).
0158         job_factory: Optional factory for expanding jobs (e.g., N parallel evals).
0159         scheduler: Stage-level scheduler (optional, inherits global if not set).
0160         parallelism: Parallelism policy for this stage.
0161         outputs: Output artifact specs produced by this stage.
0162         
0163     Example:
0164         >>> stage = StageDefinition(
0165         ...     name="evaluate",
0166         ...     jobs=[
0167         ...         JobDefinition(
0168         ...             name="dtlz2_evaluate",
0169         ...             command="python scripts/dtlz2_problem.py",
0170         ...             payload={...},
0171         ...             outputs=[ArtifactSpec(path="objectives_*.json", format="json")]
0172         ...         )
0173         ...     ],
0174         ...     job_factory=JobFactory(type="range", params={"n": 4}),
0175         ...     parallelism=ParallelismPolicy(max_concurrent=4, retry_max=2),
0176         ...     outputs=[ArtifactSpec(path="objectives_*.json", format="json")]
0177         ... )
0178         
0179     Notes:
0180         - job_factory expands the first job in jobs list to N parallel jobs
0181         - scheduler overrides global scheduler (from WorkflowsConfiguration)
0182         - outputs are collected after all jobs complete
0183     """
0184     name: str = Field(..., description="Stage name")
0185     jobs: List[JobDefinition] = Field(default_factory=list, description="Job definitions")
0186     job_factory: Optional[JobFactory] = Field(default=None, description="Job expansion factory")
0187     scheduler: Optional[SchedulerConfiguration] = Field(default=None, description="Stage-level scheduler override")
0188     parallelism: ParallelismPolicy = Field(default_factory=ParallelismPolicy, description="Parallelism policy")
0189     outputs: List[ArtifactSpec] = Field(default_factory=list, description="Output artifacts")
0190 
0191 
0192 class BranchDefinition(BaseModel):
0193     """Branch definition (optional, for organizing stages in a DAG).
0194     
0195     A branch is an optional subgraph inside a workflow, useful when you want
0196     multiple independent pipelines under one workflow (e.g., "physics sim" branch
0197     + "surrogate" branch). Stages within a branch are executed in topological order.
0198     
0199     Attributes:
0200         name: Branch name (e.g., "main", "physics_sim", "surrogate").
0201         stages: List of stages in DAG order (assumes simple sequential order; extend with explicit DAG if needed).
0202         scheduler: Optional branch-level scheduler default (used if stage not set).
0203         
0204     Example:
0205         >>> branch = BranchDefinition(
0206         ...     name="main",
0207         ...     stages=[
0208         ...         StageDefinition(name="evaluate", ...),
0209         ...         StageDefinition(name="aggregate", ...)
0210         ...     ]
0211         ... )
0212         
0213     Notes:
0214         - Multiple branches in one workflow execute independently
0215         - For complex DAGs, extend this model with explicit edge definitions
0216     """
0217     name: str = Field(..., description="Branch name")
0218     stages: List[StageDefinition] = Field(default_factory=list, description="Stages in execution order")
0219     scheduler: Optional[SchedulerConfiguration] = Field(
0220         default=None,
0221         description="Branch-level scheduler default (overrides workflow, used if stage unset)",
0222     )
0223 
0224 
0225 class WorkflowDefinition(BaseModel):
0226     """Workflow definition with branches, objectives, and scheduler defaults.
0227     
0228     A workflow is an end-to-end evaluation unit (e.g., one design point evaluation).
0229     It consists of optional branches, each with multiple stages, and defines the
0230     objectives to compute from the outputs.
0231     
0232     Attributes:
0233         name: Workflow name (e.g., "dtlz2_eval").
0234         description: Optional description.
0235         branches: Workflow branches (optional, defaults to single implicit branch if missing).
0236         objectives: Objectives to compute (reuses ObjectiveDefinition).
0237         combined_objectives: Optional combined plans emitting multiple metrics in one run.
0238         scheduler: Workflow-level scheduler default (used if branch/stage unset).
0239         
0240     Notes:
0241         - If branches is empty, executor creates single implicit branch
0242         - Objectives are unified model (ObjectiveDefinition) for consistency
0243     """
0244     name: str = Field(..., description="Workflow name")
0245     description: Optional[str] = Field(default=None, description="Workflow description")
0246     branches: List[BranchDefinition] = Field(default_factory=list, description="Workflow branches (optional)")
0247     objectives: List[ObjectiveDefinition] = Field(
0248         default_factory=list,
0249         description="Objectives to compute (reuses ObjectiveDefinition)"
0250     )
0251     combined_objectives: List[CombinedObjectivePlan] = Field(
0252         default_factory=list,
0253         description="Combined objective plans emitting multiple metrics in one run",
0254     )
0255     scheduler: Optional[SchedulerConfiguration] = Field(
0256         default=None,
0257         description="Workflow-level scheduler default (overrides global, used if branch/stage unset)",
0258     )
0259     stack_type: Optional[str] = Field(default=None,description="Experimental stack type for workflow-level geometry prep")
0260 
0261     def get_implicit_branch(self) -> BranchDefinition:
0262         """Get or create single implicit branch if branches list is empty.
0263         
0264         Returns:
0265             Single implicit branch if branches is empty, else raises error.
0266             
0267         Raises:
0268             ValueError: If branches list is not empty.
0269         """
0270         if self.branches:
0271             raise ValueError("Branches already defined; cannot use implicit branch")
0272         return BranchDefinition(name="implicit")
0273 
0274 
0275 class WorkflowsConfiguration(BaseModel):
0276     """Top-level workflows configuration.
0277     
0278     Container for multiple independent workflows (e.g., one per objective in a
0279     holistic optimization). Each workflow can have its own stages, scheduler, and
0280     objective specs.
0281     
0282     Attributes:
0283         workflows: List of independent workflows.
0284         global_scheduler: Default scheduler for all stages (can be overridden per-stage).
0285         
0286     Example:
0287         >>> config = WorkflowsConfiguration(
0288         ...     workflows=[
0289         ...         WorkflowDefinition(name="dtlz2_eval", ...),
0290         ...         WorkflowDefinition(name="physics_sim", ...)
0291         ...     ],
0292         ...     global_scheduler=SchedulerConfiguration(
0293         ...         runner_type="JobLibRunner",
0294         ...         joblib=JobLibRunnerConfig(n_jobs=-1)
0295         ...     )
0296         ... )
0297         
0298     Notes:
0299         - global_scheduler is inherited by all stages unless overridden
0300         - workflows list must be non-empty
0301         - Useful for Option B: multiple independent workflows per objective
0302     """
0303     workflows: List[WorkflowDefinition] = Field(..., min_items=1, description="List of workflows")
0304     global_scheduler: Optional[SchedulerConfiguration] = Field(
0305         default=None,
0306         description="Default scheduler for all stages (can be overridden per-stage)"
0307     )
0308     
0309     @field_validator('workflows')
0310     @classmethod
0311     def validate_unique_workflow_names(cls, workflows: List[WorkflowDefinition]) -> List[WorkflowDefinition]:
0312         """Ensure all workflow names are unique.
0313         
0314         Args:
0315             workflows: List of workflow definitions.
0316             
0317         Returns:
0318             Same list if valid.
0319             
0320         Raises:
0321             ValueError: If duplicate workflow names found.
0322         """
0323         names = [w.name for w in workflows]
0324         if len(set(names)) != len(names):
0325             raise ValueError("Workflow names must be unique")
0326         return workflows