processforge 0.3.3__tar.gz → 0.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of processforge might be problematic. Click here for more details.
- {processforge-0.3.3/src/processforge.egg-info → processforge-0.3.4}/PKG-INFO +1 -1
- {processforge-0.3.3 → processforge-0.3.4}/pyproject.toml +1 -1
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/cli/init.py +17 -7
- processforge-0.3.4/src/processforge/providers/container_client.py +218 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/providers/manager.py +15 -4
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/providers/openmc_provider.py +13 -1
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/providers/registry.py +9 -2
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/types.py +2 -0
- {processforge-0.3.3 → processforge-0.3.4/src/processforge.egg-info}/PKG-INFO +1 -1
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge.egg-info/SOURCES.txt +1 -0
- {processforge-0.3.3 → processforge-0.3.4}/LICENSE +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/MANIFEST.in +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/README.md +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/flowsheets/closed-loop-chain.json +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/flowsheets/custom/crimson/MUWS_HB.json +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/flowsheets/custom/simple_hyrdaulic_scheme.json +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/flowsheets/festim/hydrogen_transport_1d.json +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/flowsheets/hydraulic-chain.json +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/flowsheets/openmc/msre_eigenvalue.json +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/setup.cfg +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/__init__.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/__main__.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/_schema.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/analysis/__init__.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/analysis/dof.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/cli/__init__.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/cli/apply.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/cli/common.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/cli/diagram.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/cli/display.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/cli/export_fmu.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/cli/export_modelica.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/cli/plan.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/cli/run.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/cli/validate.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/compose.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/__init__.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/backends/__init__.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/backends/base.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/backends/casadi_backend.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/backends/pyomo_backend.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/backends/scipy_backend.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/flowsheet.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/jacobian.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/mixin.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/solver.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/stream_var.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/units/__init__.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/units/flash_eo.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/units/heater_eo.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/units/pipes_eo.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/units/pump_eo.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/units/strainer_eo.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/eo/units/valve_eo.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/flowsheet.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/fmu/__init__.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/fmu/_fmi_vars.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/fmu/builder.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/fmu/slave_template.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/lock.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/modelica/__init__.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/modelica/mo_writer.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/modelica/omc_runner.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/modelica/transpiler.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/modelica/unit_equations.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/pcl/__init__.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/pcl/compiler.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/pcl/namespace.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/provenance.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/providers/__init__.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/providers/base.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/providers/base_jacobian_mixin.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/providers/cantera_jacobian.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/providers/cantera_provider.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/providers/coolprop_provider.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/providers/festim_provider.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/providers/jacobian_contributor.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/providers/modelica_jacobian.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/providers/modelica_provider.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/providers/reference_state_registry.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/result.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/result_schema.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/schemas/__init__.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/schemas/flowsheet_schema.json +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/schemas/openmc/openmc_model.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/simulate.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/solver.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/state.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/thermo.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/units/__init__.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/units/base.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/units/cstr.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/units/flash.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/units/heater.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/units/pfr.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/units/pipes.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/units/pump.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/units/registry.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/units/solver_unit.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/units/strainer.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/units/tank.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/units/valve.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/utils/__init__.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/utils/flowsheet_diagram.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/utils/mermaid_diagram.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/utils/topology.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/utils/unit_consistency.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/utils/validate_flowsheet.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge/utils/validation.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge.egg-info/dependency_links.txt +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge.egg-info/entry_points.txt +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge.egg-info/requires.txt +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/src/processforge.egg-info/top_level.txt +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/tests/test_cli.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/tests/test_festim_provider.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/tests/test_init.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/tests/test_providers.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/tests/test_state.py +0 -0
- {processforge-0.3.3 → processforge-0.3.4}/tests/test_types.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "processforge"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.4"
|
|
8
8
|
description = "A Python-based process simulation framework for chemical engineering applications."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "BSD-3-Clause"
|
|
@@ -127,24 +127,34 @@ def init(
|
|
|
127
127
|
generate_compose(pf_dir, docker_providers, outputs_dir)
|
|
128
128
|
logger.info(f"Generated {compose_path}")
|
|
129
129
|
|
|
130
|
-
# Attempt docker compose pull
|
|
130
|
+
# Attempt docker compose pull — stream progress live
|
|
131
131
|
try:
|
|
132
|
-
|
|
132
|
+
logger.info("Pulling Docker images (this may take a while)...")
|
|
133
|
+
process = subprocess.Popen(
|
|
133
134
|
["docker", "compose", "-f", compose_path, "pull"],
|
|
134
|
-
|
|
135
|
+
stdout=subprocess.PIPE,
|
|
136
|
+
stderr=subprocess.STDOUT,
|
|
135
137
|
text=True,
|
|
136
|
-
|
|
138
|
+
bufsize=1,
|
|
137
139
|
)
|
|
138
|
-
|
|
140
|
+
assert process.stdout is not None
|
|
141
|
+
for line in process.stdout:
|
|
142
|
+
line = line.rstrip()
|
|
143
|
+
if line:
|
|
144
|
+
logger.info(line)
|
|
145
|
+
returncode = process.wait(timeout=600)
|
|
146
|
+
if returncode == 0:
|
|
139
147
|
logger.info("Pulled Docker images.")
|
|
140
148
|
else:
|
|
141
|
-
logger.warning(
|
|
149
|
+
logger.warning(
|
|
150
|
+
f"docker compose pull failed with exit code {returncode}."
|
|
151
|
+
)
|
|
142
152
|
except FileNotFoundError:
|
|
143
153
|
logger.warning(
|
|
144
154
|
"Docker not found. Install Docker to use containerized providers."
|
|
145
155
|
)
|
|
146
156
|
except subprocess.TimeoutExpired:
|
|
147
|
-
logger.warning("docker compose pull timed out after
|
|
157
|
+
logger.warning("docker compose pull timed out after 600s.")
|
|
148
158
|
else:
|
|
149
159
|
logger.info("No containerized providers — skipping Docker setup.")
|
|
150
160
|
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""CLI-side HTTP client for containerized providers (OpenMC, FESTIM, …).
|
|
2
|
+
|
|
3
|
+
A provider that runs in a Docker container does its real work inside the
|
|
4
|
+
container (in ``docker/provider_server.py``). The CLI side must NOT import the
|
|
5
|
+
backend library (OpenMC, FESTIM, …) locally — instead it speaks HTTP to the
|
|
6
|
+
container's ``provider_server.py`` instance. :class:`ContainerProviderClient`
|
|
7
|
+
implements the :class:`~processforge.providers.base.AbstractProvider` contract
|
|
8
|
+
as a thin HTTP wrapper around that server.
|
|
9
|
+
|
|
10
|
+
Request/response contract (matches ``docker/provider_server.py``)
|
|
11
|
+
----------------------------------------------------------------
|
|
12
|
+
* ``POST /run`` with a JSON body
|
|
13
|
+
``{"unit_config", "materials", "inlet", "output_dir", "provider_config"}``.
|
|
14
|
+
* JSON response ``{"status", "sim_type", <scalars…>, "metadata"}`` deserialised
|
|
15
|
+
into a :class:`~processforge.types.SimulationResult`.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import urllib.error
|
|
22
|
+
import urllib.request
|
|
23
|
+
from typing import TYPE_CHECKING, Optional
|
|
24
|
+
|
|
25
|
+
from loguru import logger
|
|
26
|
+
|
|
27
|
+
from .base import AbstractProvider
|
|
28
|
+
from .registry import get_provider_default_port
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from processforge.types import (
|
|
32
|
+
FlowsheetConfig,
|
|
33
|
+
MaterialDef,
|
|
34
|
+
ProviderConfig,
|
|
35
|
+
SimulationResult,
|
|
36
|
+
UnitConfig,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ContainerProviderClient(AbstractProvider):
|
|
41
|
+
"""CLI-side HTTP client for a provider running in a Docker container.
|
|
42
|
+
|
|
43
|
+
Responsibilities
|
|
44
|
+
----------------
|
|
45
|
+
* **Health check** — ``initialize()`` verifies the container is reachable
|
|
46
|
+
via ``GET /health``.
|
|
47
|
+
* **Simulation dispatch** — ``run_simulation()`` serialises the unit config
|
|
48
|
+
and material registry and ``POST``s them to the container's ``/run``
|
|
49
|
+
endpoint, then deserialises the JSON response into a ``SimulationResult``.
|
|
50
|
+
* **Material validation** — delegated to the provider class via
|
|
51
|
+
``validate_material()`` (it is a classmethod and needs no local backend).
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, provider_type: str):
|
|
55
|
+
self._ptype = provider_type
|
|
56
|
+
self._url: Optional[str] = None
|
|
57
|
+
self._provider_output_dir: str = "outputs"
|
|
58
|
+
self._provider_config: Optional["ProviderConfig"] = None
|
|
59
|
+
self._materials: dict = {}
|
|
60
|
+
self._initialized: bool = False
|
|
61
|
+
|
|
62
|
+
# ------------------------------------------------------------------
|
|
63
|
+
# Helpers
|
|
64
|
+
# ------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
def _resolve_url(self, provider_config: "ProviderConfig") -> str:
|
|
67
|
+
"""Return the service URL, deriving a default from the catalog if needed."""
|
|
68
|
+
url = getattr(provider_config, "url", None)
|
|
69
|
+
if not url:
|
|
70
|
+
port = get_provider_default_port(self._ptype) or 9000
|
|
71
|
+
url = f"http://localhost:{port}"
|
|
72
|
+
return url.rstrip("/")
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def _serialize_unit_config(unit_config: "UnitConfig") -> dict:
|
|
76
|
+
"""Serialise a ``UnitConfig`` into the ``unit_config`` block of the body."""
|
|
77
|
+
return {
|
|
78
|
+
"type": unit_config.type,
|
|
79
|
+
"in": unit_config.inputs,
|
|
80
|
+
"material": unit_config.material,
|
|
81
|
+
"provider": unit_config.provider,
|
|
82
|
+
"out": unit_config.out,
|
|
83
|
+
"sim_type": unit_config.sim_type,
|
|
84
|
+
"solver_config": unit_config.solver_config,
|
|
85
|
+
**unit_config.extra,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def _serialize_materials(materials: dict[str, "MaterialDef"]) -> dict:
|
|
90
|
+
"""Serialise the material registry into the ``materials`` block of the body."""
|
|
91
|
+
out: dict = {}
|
|
92
|
+
for name, mat in materials.items():
|
|
93
|
+
out[name] = {
|
|
94
|
+
"id": mat.id,
|
|
95
|
+
"density": mat.density,
|
|
96
|
+
"density_units": mat.density_units,
|
|
97
|
+
"temperature": mat.temperature,
|
|
98
|
+
"depletable": mat.depletable,
|
|
99
|
+
"nuclides": mat.nuclides,
|
|
100
|
+
**mat.extra,
|
|
101
|
+
}
|
|
102
|
+
return out
|
|
103
|
+
|
|
104
|
+
# ------------------------------------------------------------------
|
|
105
|
+
# Lifecycle
|
|
106
|
+
# ------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
def initialize(
|
|
109
|
+
self,
|
|
110
|
+
provider_config: "ProviderConfig",
|
|
111
|
+
flowsheet_config: "FlowsheetConfig",
|
|
112
|
+
) -> None:
|
|
113
|
+
"""Store config, resolve URL, verify the Docker service is reachable."""
|
|
114
|
+
self._url = self._resolve_url(provider_config)
|
|
115
|
+
|
|
116
|
+
out_dir = os.path.expandvars(
|
|
117
|
+
getattr(provider_config, "output_dir", "outputs") or "outputs"
|
|
118
|
+
)
|
|
119
|
+
if not os.path.isabs(out_dir):
|
|
120
|
+
root = os.environ.get("PROCESSFORGE_OUTPUT_DIR", "outputs")
|
|
121
|
+
out_dir = os.path.join(root, out_dir)
|
|
122
|
+
self._provider_output_dir = out_dir
|
|
123
|
+
|
|
124
|
+
# Material registry — serialised into /run and used by validate_material().
|
|
125
|
+
self._materials = dict(flowsheet_config.materials)
|
|
126
|
+
self._provider_config = provider_config
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
req = urllib.request.Request(f"{self._url}/health", method="GET")
|
|
130
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
131
|
+
health = json.loads(resp.read().decode())
|
|
132
|
+
logger.info(
|
|
133
|
+
f"ContainerProviderClient connected to {self._url} — "
|
|
134
|
+
f"status={health.get('status')}"
|
|
135
|
+
)
|
|
136
|
+
except (urllib.error.URLError, OSError, TimeoutError) as exc:
|
|
137
|
+
raise RuntimeError(
|
|
138
|
+
f"Provider '{self._ptype}' service unreachable at {self._url}. "
|
|
139
|
+
f"Ensure the container is running: pf init <flowsheet>"
|
|
140
|
+
) from exc
|
|
141
|
+
|
|
142
|
+
self._initialized = True
|
|
143
|
+
|
|
144
|
+
def get_thermo_properties(self, stream: dict) -> dict:
|
|
145
|
+
raise NotImplementedError(
|
|
146
|
+
"ContainerProviderClient does not support stream thermodynamics."
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def compute_unit(self, unit_type: str, config: dict, inlet: dict):
|
|
150
|
+
"""Return ``None`` — the container provider uses ``run_simulation``."""
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
def teardown(self) -> None:
|
|
154
|
+
"""Release provider state."""
|
|
155
|
+
self._initialized = False
|
|
156
|
+
|
|
157
|
+
def run_simulation(self, unit_config: "UnitConfig", inlet: dict) -> "SimulationResult":
|
|
158
|
+
"""Serialise the request, POST it to the container, return the result."""
|
|
159
|
+
from processforge.types import SimulationResult
|
|
160
|
+
|
|
161
|
+
if not self._url:
|
|
162
|
+
raise RuntimeError(
|
|
163
|
+
"ContainerProviderClient has not been initialized — call initialize() first."
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
provider_config_dump = (
|
|
167
|
+
self._provider_config.model_dump()
|
|
168
|
+
if self._provider_config is not None
|
|
169
|
+
else {"type": unit_config.provider or self._ptype, "url": self._url}
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
body = {
|
|
173
|
+
"unit_config": self._serialize_unit_config(unit_config),
|
|
174
|
+
"materials": self._serialize_materials(self._materials),
|
|
175
|
+
"inlet": inlet,
|
|
176
|
+
"output_dir": self._provider_output_dir,
|
|
177
|
+
"provider_config": provider_config_dump,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
logger.info(
|
|
181
|
+
f"ContainerProviderClient: POST {self._url}/run "
|
|
182
|
+
f"sim_type='{unit_config.sim_type}'"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
payload = json.dumps(body).encode()
|
|
186
|
+
req = urllib.request.Request(
|
|
187
|
+
f"{self._url}/run",
|
|
188
|
+
data=payload,
|
|
189
|
+
headers={"Content-Type": "application/json"},
|
|
190
|
+
method="POST",
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
with urllib.request.urlopen(req, timeout=900) as resp:
|
|
195
|
+
data = json.loads(resp.read().decode())
|
|
196
|
+
except urllib.error.HTTPError as exc:
|
|
197
|
+
detail = exc.read().decode() if exc.fp else str(exc)
|
|
198
|
+
raise RuntimeError(
|
|
199
|
+
f"Provider '{self._ptype}' service returned HTTP {exc.code}: {detail}"
|
|
200
|
+
) from exc
|
|
201
|
+
except (urllib.error.URLError, OSError, TimeoutError) as exc:
|
|
202
|
+
raise RuntimeError(
|
|
203
|
+
f"Provider '{self._ptype}' service unreachable at {self._url}: {exc}"
|
|
204
|
+
) from exc
|
|
205
|
+
|
|
206
|
+
scalars = {
|
|
207
|
+
k: v
|
|
208
|
+
for k, v in data.items()
|
|
209
|
+
if k not in ("status", "sim_type", "metadata")
|
|
210
|
+
}
|
|
211
|
+
return SimulationResult(
|
|
212
|
+
status=data.get("status", "completed"),
|
|
213
|
+
sim_type=data.get("sim_type", unit_config.sim_type or ""),
|
|
214
|
+
scalars=scalars,
|
|
215
|
+
metadata=data.get("metadata", {}),
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
@@ -19,7 +19,7 @@ from pydantic import BaseModel, ConfigDict
|
|
|
19
19
|
from processforge.types import CoolPropProviderConfig, FlowsheetConfig, ProviderConfig
|
|
20
20
|
|
|
21
21
|
from .coolprop_provider import CoolPropProvider
|
|
22
|
-
from .registry import get_provider_class
|
|
22
|
+
from .registry import get_provider_class, is_containerized
|
|
23
23
|
|
|
24
24
|
_BUILTIN_DEFAULT_KEY = "__coolprop__"
|
|
25
25
|
_DEFAULT_KEY = "__default__"
|
|
@@ -136,11 +136,22 @@ def build_provider_map(
|
|
|
136
136
|
# Step 2: instantiate every declared provider.
|
|
137
137
|
for name, cfg in providers_config.items():
|
|
138
138
|
ptype = cfg.type
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
if is_containerized(ptype):
|
|
140
|
+
# Containerized providers (OpenMC, FESTIM, …) run their backend
|
|
141
|
+
# inside a Docker container; the CLI only needs a thin HTTP client
|
|
142
|
+
# that talks to the container's provider_server.py.
|
|
143
|
+
from .container_client import ContainerProviderClient
|
|
144
|
+
|
|
145
|
+
instance = ContainerProviderClient(ptype)
|
|
146
|
+
logger.info(
|
|
147
|
+
f"Initialized provider '{name}' (type='{ptype}') via container client"
|
|
148
|
+
)
|
|
149
|
+
else:
|
|
150
|
+
cls = get_provider_class(ptype)
|
|
151
|
+
instance = cls()
|
|
152
|
+
logger.info(f"Initialized provider '{name}' (type='{ptype}')")
|
|
141
153
|
instance.initialize(cfg, flowsheet_config)
|
|
142
154
|
providers[name] = instance
|
|
143
|
-
logger.info(f"Initialized provider '{name}' (type='{ptype}')")
|
|
144
155
|
|
|
145
156
|
# Step 3: resolve the active default.
|
|
146
157
|
default = None
|
|
@@ -37,6 +37,7 @@ from __future__ import annotations
|
|
|
37
37
|
import math
|
|
38
38
|
import os
|
|
39
39
|
import pathlib
|
|
40
|
+
import tempfile
|
|
40
41
|
from abc import ABC, abstractmethod
|
|
41
42
|
from typing import TYPE_CHECKING, Optional
|
|
42
43
|
|
|
@@ -506,7 +507,18 @@ class OpenMCProvider(AbstractProvider):
|
|
|
506
507
|
|
|
507
508
|
# --- Working directory management ---
|
|
508
509
|
run_dir = pathlib.Path(self._provider_output_dir)
|
|
509
|
-
|
|
510
|
+
try:
|
|
511
|
+
run_dir.mkdir(parents=True, exist_ok=True)
|
|
512
|
+
except (PermissionError, OSError) as exc:
|
|
513
|
+
# The configured output dir (often the mounted /data volume) may not
|
|
514
|
+
# be writable by the container user. Fall back to a temp dir so the
|
|
515
|
+
# simulation can still run; only the persisted XML artifacts are lost.
|
|
516
|
+
run_dir = pathlib.Path(tempfile.mkdtemp(prefix="processforge_openmc_"))
|
|
517
|
+
logger.warning(
|
|
518
|
+
f"OpenMCProvider: cannot use output dir "
|
|
519
|
+
f"'{self._provider_output_dir}' ({exc}); "
|
|
520
|
+
f"falling back to '{run_dir}'."
|
|
521
|
+
)
|
|
510
522
|
original_cwd = pathlib.Path.cwd()
|
|
511
523
|
|
|
512
524
|
# --- Cross-section override ---
|
|
@@ -171,8 +171,15 @@ def get_provider_default_port(provider_type: str) -> int | None:
|
|
|
171
171
|
|
|
172
172
|
|
|
173
173
|
def is_containerized(provider_type: str) -> bool:
|
|
174
|
-
"""Return True if the provider runs in a Docker container.
|
|
175
|
-
|
|
174
|
+
"""Return True if the provider runs in a Docker container.
|
|
175
|
+
|
|
176
|
+
Returns False for unknown provider types rather than raising, so callers
|
|
177
|
+
can cleanly fall through to the local/import path.
|
|
178
|
+
"""
|
|
179
|
+
try:
|
|
180
|
+
return get_provider_docker_image(provider_type) is not None
|
|
181
|
+
except ValueError:
|
|
182
|
+
return False
|
|
176
183
|
|
|
177
184
|
|
|
178
185
|
# Seed the registry with the always-available CoolProp provider.
|
|
@@ -194,6 +194,7 @@ class OpenMCProviderConfig(BaseModel):
|
|
|
194
194
|
"""
|
|
195
195
|
|
|
196
196
|
type: str = "openmc"
|
|
197
|
+
url: Optional[str] = None
|
|
197
198
|
output_dir: str = "outputs/openmc"
|
|
198
199
|
cross_sections: Optional[str] = None
|
|
199
200
|
docker_image: Optional[str] = None
|
|
@@ -201,6 +202,7 @@ class OpenMCProviderConfig(BaseModel):
|
|
|
201
202
|
@classmethod
|
|
202
203
|
def from_dict(cls, d: dict) -> "OpenMCProviderConfig":
|
|
203
204
|
return cls(
|
|
205
|
+
url=d.get("url"),
|
|
204
206
|
output_dir=d.get("output_dir", "outputs/openmc"),
|
|
205
207
|
cross_sections=d.get("cross_sections"),
|
|
206
208
|
docker_image=d.get("docker_image"),
|
|
@@ -76,6 +76,7 @@ src/processforge/providers/base.py
|
|
|
76
76
|
src/processforge/providers/base_jacobian_mixin.py
|
|
77
77
|
src/processforge/providers/cantera_jacobian.py
|
|
78
78
|
src/processforge/providers/cantera_provider.py
|
|
79
|
+
src/processforge/providers/container_client.py
|
|
79
80
|
src/processforge/providers/coolprop_provider.py
|
|
80
81
|
src/processforge/providers/festim_provider.py
|
|
81
82
|
src/processforge/providers/jacobian_contributor.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{processforge-0.3.3 → processforge-0.3.4}/src/processforge/providers/jacobian_contributor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{processforge-0.3.3 → processforge-0.3.4}/src/processforge/providers/reference_state_registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|