Source code for OpenPinch.contracts.synthesis.method

"""Method-level HEN synthesis schemas."""

from __future__ import annotations

import hashlib
import json
from typing import Any, Self

from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator

from ...domain.enums import HeatExchangerNetworkDesignMethod
from ...domain.heat_exchanger_network import HeatExchangerNetwork
from .common import (
    HeatExchangerNetworkSynthesisManifest,
    SynthesisTaskStatus,
    _validate_non_negative_finite,
    _validate_optional_identity,
    _validate_positive_finite,
    _validate_run_id,
)
from .topology import HeatExchangerNetworkTopologyRestriction


[docs] class HeatExchangerNetworkSynthesisMethodInput(BaseModel): """Validated input for one PDM, TDM, or evolution method run.""" model_config = ConfigDict(extra="forbid", validate_assignment=True) task_id: str | None = None run_id: str method: HeatExchangerNetworkDesignMethod approach_temperature: float derivative_threshold: float | None = None stage_count: int | None = None problem_id: str | None = None workspace_variant: str | None = None period_id: str | None = None settings: dict[str, Any] = Field(default_factory=dict) seed_network: HeatExchangerNetwork | None = None seed_network_index: int | None = None parent_task_id: str | None = None metadata: dict[str, Any] = Field(default_factory=dict) topology_restrictions: tuple["HeatExchangerNetworkTopologyRestriction", ...] = ( Field(default_factory=tuple) ) @field_validator("run_id") @classmethod def _validate_run_id(cls, value: str) -> str: return _validate_run_id(value) @field_validator( "task_id", "problem_id", "workspace_variant", "period_id", "parent_task_id", ) @classmethod def _validate_optional_identity(cls, value: str | None) -> str | None: return _validate_optional_identity(value) @field_validator("approach_temperature", "derivative_threshold") @classmethod def _validate_positive_float(cls, value: float | None) -> float | None: if value is None: return value return _validate_positive_finite(value) @field_validator("stage_count") @classmethod def _validate_stage_count(cls, value: int | None) -> int | None: if value is not None and value <= 0: raise ValueError("stage_count must be a positive integer when supplied") return value @field_validator("seed_network_index") @classmethod def _validate_seed_network_index(cls, value: int | None) -> int | None: if value is not None and value < 0: raise ValueError("seed_network_index must be non-negative when supplied") return value @model_validator(mode="after") def _ensure_task_id(self) -> Self: if self.task_id is None: self.task_id = self.generate_task_id() return self
[docs] def generate_task_id(self) -> str: """Return the deterministic identifier for this task definition.""" task_key_data = { "approach_temperature": self.approach_temperature, "derivative_threshold": self.derivative_threshold, "method": self.method, "parent_task_id": self.parent_task_id, "problem_id": self.problem_id, "run_id": self.run_id, "stage_count": self.stage_count, "settings": self.settings, "period_id": self.period_id, "topology_restrictions": [ restriction.model_dump(mode="json") for restriction in self.topology_restrictions ], "workspace_variant": self.workspace_variant, } if self.seed_network_index is not None: task_key_data["seed_network_index"] = self.seed_network_index encoded = json.dumps( task_key_data, sort_keys=True, separators=(",", ":"), ).encode() digest = hashlib.sha256(encoded).hexdigest()[:16] return f"hens-task-{digest}"
[docs] class HeatExchangerNetworkSynthesisMethodOutput(BaseModel): """Validated output for one PDM, TDM, or evolution method run.""" model_config = ConfigDict(extra="forbid", validate_assignment=True) method: HeatExchangerNetworkDesignMethod | None = None task: HeatExchangerNetworkSynthesisMethodInput | None = None status: SynthesisTaskStatus network: HeatExchangerNetwork | None = None accepted_networks: tuple[HeatExchangerNetwork, ...] = Field(default_factory=tuple) ranked_networks: tuple[HeatExchangerNetwork, ...] = Field(default_factory=tuple) task_manifest: HeatExchangerNetworkSynthesisManifest | None = None objective_value: float | None = None solver_status: str | None = None error: str | None = None diagnostics: dict[str, Any] = Field(default_factory=dict) trace: dict[str, Any] = Field(default_factory=dict) diagnostic_references: tuple[str, ...] = Field(default_factory=tuple) @field_validator("objective_value") @classmethod def _validate_objective_value(cls, value: float | None) -> float | None: if value is None: return value return _validate_non_negative_finite(value) @field_validator("solver_status", "error") @classmethod def _validate_optional_text(cls, value: str | None) -> str | None: return _validate_optional_identity(value) @field_validator("diagnostic_references") @classmethod def _validate_diagnostic_references( cls, value: tuple[str, ...], ) -> tuple[str, ...]: return tuple(_validate_optional_identity(item) for item in value) @model_validator(mode="after") def _fill_method_from_task(self) -> Self: if self.method is None and self.task is not None: self.method = self.task.method return self
HeatExchangerNetworkSynthesisMethodInput.model_rebuild() HeatExchangerNetworkSynthesisMethodOutput.model_rebuild()