|
| 1 | +""" |
| 2 | +poltype.pipeline.context – mutable per-run pipeline state. |
| 3 | +
|
| 4 | +A :class:`PipelineContext` is created once at the beginning of a |
| 5 | +pipeline run and flows through every stage. It carries: |
| 6 | +
|
| 7 | +* the immutable :class:`PoltypeConfig`, |
| 8 | +* the current :class:`Molecule` (which may be replaced after geometry |
| 9 | + optimisation), |
| 10 | +* the selected :class:`QMBackend`, |
| 11 | +* an ``artifacts`` dict for inter-stage data exchange, and |
| 12 | +* a ``stage_results`` dict that records the outcome of each stage. |
| 13 | +
|
| 14 | +This module has **no dependency on PoltypeModules** and can be |
| 15 | +unit-tested in isolation. |
| 16 | +""" |
| 17 | + |
| 18 | +from __future__ import annotations |
| 19 | + |
| 20 | +from pathlib import Path |
| 21 | +from typing import Any, Dict, Optional |
| 22 | + |
| 23 | +from poltype.config.schema import PoltypeConfig |
| 24 | +from poltype.molecule.molecule import Molecule |
| 25 | +from poltype.pipeline.stage import StageResult |
| 26 | +from poltype.qm.backend import QMBackend |
| 27 | + |
| 28 | + |
| 29 | +class PipelineContext: |
| 30 | + """Mutable per-run state that flows through every pipeline stage. |
| 31 | +
|
| 32 | + Parameters |
| 33 | + ---------- |
| 34 | + config: |
| 35 | + Immutable run configuration. |
| 36 | + molecule: |
| 37 | + Initial molecule (may be ``None`` if loaded during the |
| 38 | + input-preparation stage). |
| 39 | + backend: |
| 40 | + QM backend to use for calculations (may be ``None`` if |
| 41 | + selected during the pipeline). |
| 42 | + work_dir: |
| 43 | + Working directory for file I/O. Defaults to the current |
| 44 | + working directory. |
| 45 | + """ |
| 46 | + |
| 47 | + def __init__( |
| 48 | + self, |
| 49 | + config: PoltypeConfig, |
| 50 | + molecule: Optional[Molecule] = None, |
| 51 | + backend: Optional[QMBackend] = None, |
| 52 | + work_dir: Optional[Path] = None, |
| 53 | + ) -> None: |
| 54 | + self._config = config |
| 55 | + self._molecule = molecule |
| 56 | + self._backend = backend |
| 57 | + self._work_dir = Path(work_dir).resolve() if work_dir else Path.cwd() |
| 58 | + self.artifacts: Dict[str, Any] = {} |
| 59 | + self.stage_results: Dict[str, StageResult] = {} |
| 60 | + |
| 61 | + # -- read-only property for config -- |
| 62 | + |
| 63 | + @property |
| 64 | + def config(self) -> PoltypeConfig: |
| 65 | + """Immutable run configuration.""" |
| 66 | + return self._config |
| 67 | + |
| 68 | + # -- molecule (reassignable) -- |
| 69 | + |
| 70 | + @property |
| 71 | + def molecule(self) -> Optional[Molecule]: |
| 72 | + """Current molecule (may be replaced after geometry optimisation).""" |
| 73 | + return self._molecule |
| 74 | + |
| 75 | + @molecule.setter |
| 76 | + def molecule(self, value: Optional[Molecule]) -> None: |
| 77 | + self._molecule = value |
| 78 | + |
| 79 | + # -- backend (reassignable) -- |
| 80 | + |
| 81 | + @property |
| 82 | + def backend(self) -> Optional[QMBackend]: |
| 83 | + """QM backend for the run.""" |
| 84 | + return self._backend |
| 85 | + |
| 86 | + @backend.setter |
| 87 | + def backend(self, value: Optional[QMBackend]) -> None: |
| 88 | + self._backend = value |
| 89 | + |
| 90 | + # -- work_dir -- |
| 91 | + |
| 92 | + @property |
| 93 | + def work_dir(self) -> Path: |
| 94 | + """Working directory for file I/O.""" |
| 95 | + return self._work_dir |
| 96 | + |
| 97 | + # -- artifact helpers -- |
| 98 | + |
| 99 | + def get_artifact(self, key: str, default: Any = None) -> Any: |
| 100 | + """Retrieve an artifact by key, returning *default* if absent.""" |
| 101 | + return self.artifacts.get(key, default) |
| 102 | + |
| 103 | + def set_artifact(self, key: str, value: Any) -> None: |
| 104 | + """Store an artifact for downstream stages.""" |
| 105 | + self.artifacts[key] = value |
| 106 | + |
| 107 | + # -- molecule replacement -- |
| 108 | + |
| 109 | + def update_molecule(self, new_mol: Molecule) -> None: |
| 110 | + """Replace the current molecule (e.g. after geometry optimisation). |
| 111 | +
|
| 112 | + Parameters |
| 113 | + ---------- |
| 114 | + new_mol: |
| 115 | + The new molecule to use for subsequent stages. |
| 116 | + """ |
| 117 | + self._molecule = new_mol |
| 118 | + |
| 119 | + def __repr__(self) -> str: |
| 120 | + mol_name = self._molecule.name if self._molecule else "None" |
| 121 | + return ( |
| 122 | + f"PipelineContext(config=PoltypeConfig, " |
| 123 | + f"molecule={mol_name!r}, " |
| 124 | + f"work_dir={str(self._work_dir)!r})" |
| 125 | + ) |
0 commit comments