processforge 0.2.40__tar.gz → 0.3.1__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.2.40 → processforge-0.3.1}/PKG-INFO +8 -4
- {processforge-0.2.40 → processforge-0.3.1}/README.md +6 -3
- {processforge-0.2.40 → processforge-0.3.1}/pyproject.toml +2 -1
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/__init__.py +1 -1
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/result.py +163 -57
- processforge-0.3.1/src/processforge/result_schema.py +26 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/types.py +51 -115
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge.egg-info/PKG-INFO +8 -4
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge.egg-info/SOURCES.txt +3 -1
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge.egg-info/requires.txt +1 -0
- processforge-0.3.1/tests/test_types.py +440 -0
- {processforge-0.2.40 → processforge-0.3.1}/LICENSE +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/MANIFEST.in +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/flowsheets/closed-loop-chain.json +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/flowsheets/festim/hydrogen_transport_1d.json +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/flowsheets/hydraulic-chain.json +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/flowsheets/openmc/msre_eigenvalue.json +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/setup.cfg +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/__main__.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/_schema.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/analysis/__init__.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/analysis/dof.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/cli/__init__.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/cli/apply.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/cli/common.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/cli/diagram.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/cli/display.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/cli/export_fmu.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/cli/export_modelica.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/cli/init.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/cli/plan.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/cli/run.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/cli/validate.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/compose.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/__init__.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/backends/__init__.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/backends/base.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/backends/casadi_backend.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/backends/pyomo_backend.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/backends/scipy_backend.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/flowsheet.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/jacobian.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/mixin.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/solver.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/stream_var.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/units/__init__.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/units/flash_eo.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/units/heater_eo.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/units/pipes_eo.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/units/pump_eo.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/units/strainer_eo.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/eo/units/valve_eo.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/flowsheet.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/fmu/__init__.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/fmu/_fmi_vars.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/fmu/builder.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/fmu/slave_template.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/lock.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/modelica/__init__.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/modelica/mo_writer.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/modelica/omc_runner.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/modelica/transpiler.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/modelica/unit_equations.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/pcl/__init__.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/pcl/compiler.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/pcl/namespace.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/provenance.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/providers/__init__.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/providers/base.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/providers/base_jacobian_mixin.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/providers/cantera_jacobian.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/providers/cantera_provider.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/providers/coolprop_provider.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/providers/festim_provider.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/providers/jacobian_contributor.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/providers/manager.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/providers/modelica_jacobian.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/providers/modelica_provider.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/providers/openmc_provider.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/providers/reference_state_registry.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/providers/registry.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/schemas/__init__.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/schemas/flowsheet_schema.json +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/schemas/openmc/openmc_model.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/simulate.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/solver.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/state.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/thermo.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/units/__init__.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/units/cstr.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/units/flash.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/units/heater.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/units/pfr.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/units/pipes.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/units/provider_mixin.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/units/pump.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/units/registry.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/units/solver.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/units/solver_unit.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/units/strainer.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/units/tank.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/units/valve.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/utils/__init__.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/utils/flowsheet_diagram.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/utils/mermaid_diagram.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/utils/topology.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/utils/unit_consistency.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/utils/validate_flowsheet.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge/utils/validation.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge.egg-info/dependency_links.txt +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge.egg-info/entry_points.txt +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/src/processforge.egg-info/top_level.txt +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/tests/test_cli.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/tests/test_festim_provider.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/tests/test_init.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/tests/test_providers.py +0 -0
- {processforge-0.2.40 → processforge-0.3.1}/tests/test_state.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: processforge
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: A Python-based process simulation framework for chemical engineering applications.
|
|
5
5
|
Author-email: Processforge Team <team@processforge.dev>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -30,6 +30,7 @@ Requires-Dist: pandas>=2.2
|
|
|
30
30
|
Requires-Dist: zarr>=3.0
|
|
31
31
|
Requires-Dist: coolprop>=7.2.0
|
|
32
32
|
Requires-Dist: typer>=0.12
|
|
33
|
+
Requires-Dist: pip>=26.1.2
|
|
33
34
|
Provides-Extra: eo
|
|
34
35
|
Requires-Dist: pyomo>=6.7; extra == "eo"
|
|
35
36
|
Provides-Extra: eo-casadi
|
|
@@ -112,7 +113,8 @@ uv tool install "processforge[modelica]"
|
|
|
112
113
|
4. **Look at the output**
|
|
113
114
|
|
|
114
115
|
`pf apply` writes results under `outputs/`:
|
|
115
|
-
- `*_results.zarr` — simulation results store
|
|
116
|
+
- `*_results.zarr` — simulation results store (per-variable arrays, composition flattened)
|
|
117
|
+
- `*_results.zarr.schema.json` — schema file describing streams, variables, dtypes, units, shapes, and run provenance
|
|
116
118
|
- `*_validation.xlsx` — validation report
|
|
117
119
|
- `*.pfstate/` — versioned snapshot store with a `latest` pointer
|
|
118
120
|
|
|
@@ -127,8 +129,10 @@ fs = EOFlowsheet(config, backend="scipy")
|
|
|
127
129
|
results = fs.run()
|
|
128
130
|
```
|
|
129
131
|
|
|
130
|
-
## Usage
|
|
131
|
-
|
|
132
|
+
## Usage
|
|
133
|
+
See the [usage guide](docs/usage.md) for CLI commands and workflows.
|
|
134
|
+
|
|
135
|
+
Individual providers (e.g. FESTIM, OpenMC) can be run via Docker images using the provider image contract.
|
|
132
136
|
|
|
133
137
|
## Flowsheets
|
|
134
138
|
The core of Processforge is the flowsheet JSON format, which defines materials, units, and recycle rules. See below for more information on the formats and example flowsheets.
|
|
@@ -53,7 +53,8 @@ uv tool install "processforge[modelica]"
|
|
|
53
53
|
4. **Look at the output**
|
|
54
54
|
|
|
55
55
|
`pf apply` writes results under `outputs/`:
|
|
56
|
-
- `*_results.zarr` — simulation results store
|
|
56
|
+
- `*_results.zarr` — simulation results store (per-variable arrays, composition flattened)
|
|
57
|
+
- `*_results.zarr.schema.json` — schema file describing streams, variables, dtypes, units, shapes, and run provenance
|
|
57
58
|
- `*_validation.xlsx` — validation report
|
|
58
59
|
- `*.pfstate/` — versioned snapshot store with a `latest` pointer
|
|
59
60
|
|
|
@@ -68,8 +69,10 @@ fs = EOFlowsheet(config, backend="scipy")
|
|
|
68
69
|
results = fs.run()
|
|
69
70
|
```
|
|
70
71
|
|
|
71
|
-
## Usage
|
|
72
|
-
|
|
72
|
+
## Usage
|
|
73
|
+
See the [usage guide](docs/usage.md) for CLI commands and workflows.
|
|
74
|
+
|
|
75
|
+
Individual providers (e.g. FESTIM, OpenMC) can be run via Docker images using the provider image contract.
|
|
73
76
|
|
|
74
77
|
## Flowsheets
|
|
75
78
|
The core of Processforge is the flowsheet JSON format, which defines materials, units, and recycle rules. See below for more information on the formats and example flowsheets.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "processforge"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "A Python-based process simulation framework for chemical engineering applications."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "BSD-3-Clause"
|
|
@@ -36,6 +36,7 @@ dependencies = [
|
|
|
36
36
|
"zarr>=3.0",
|
|
37
37
|
"coolprop>=7.2.0",
|
|
38
38
|
"typer>=0.12",
|
|
39
|
+
"pip>=26.1.2",
|
|
39
40
|
]
|
|
40
41
|
|
|
41
42
|
[project.optional-dependencies]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import shutil
|
|
3
|
+
from datetime import datetime, timezone
|
|
3
4
|
|
|
4
5
|
import numpy as np
|
|
5
6
|
import pandas as pd
|
|
@@ -7,8 +8,11 @@ import matplotlib.pyplot as plt
|
|
|
7
8
|
import zarr
|
|
8
9
|
from loguru import logger
|
|
9
10
|
|
|
11
|
+
from .result_schema import ResultSchema, SolverUnitSchema, StreamSchema
|
|
10
12
|
from .types import RunInfo
|
|
11
13
|
|
|
14
|
+
_VARIABLE_UNITS = {"T": "K", "P": "Pa", "flowrate": "mol/s", "beta": ""}
|
|
15
|
+
|
|
12
16
|
|
|
13
17
|
def _ensure_parent_dir(path):
|
|
14
18
|
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
|
|
@@ -32,13 +36,19 @@ def _is_dynamic(results):
|
|
|
32
36
|
def _store_stream(stream_group, stream_data):
|
|
33
37
|
for key, value in stream_data.items():
|
|
34
38
|
if key == "z" and isinstance(value, dict):
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
for comp in sorted(value):
|
|
40
|
+
stream_group.create_array(comp, data=_ensure_array(value[comp]))
|
|
41
|
+
if value:
|
|
42
|
+
stream_group.attrs["composition"] = sorted(value)
|
|
39
43
|
continue
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
stream_group.create_array(key, data=_ensure_array(value))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _store_solver_unit(group, data):
|
|
48
|
+
for k, v in data.items():
|
|
49
|
+
if isinstance(v, (np.ndarray, np.generic)):
|
|
50
|
+
v = v.item()
|
|
51
|
+
group.attrs[k] = v
|
|
42
52
|
|
|
43
53
|
|
|
44
54
|
def _normalize_run_info(run_info: RunInfo | dict) -> dict:
|
|
@@ -93,6 +103,88 @@ def _store_run_info(root, run_info: RunInfo | dict) -> None:
|
|
|
93
103
|
ig_group.attrs["var_names"] = list(var_names)
|
|
94
104
|
|
|
95
105
|
|
|
106
|
+
def _friendly_dtype(dt: str) -> str:
|
|
107
|
+
dtype = str(dt)
|
|
108
|
+
if dtype.startswith(("<U", "|U")) or dtype.startswith(("<S", "|S")):
|
|
109
|
+
return "str"
|
|
110
|
+
return dtype
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _build_schema(root) -> ResultSchema:
|
|
114
|
+
from . import __version__ as _pf_version
|
|
115
|
+
|
|
116
|
+
mode = root.attrs.get("mode", "steady")
|
|
117
|
+
schema = ResultSchema(
|
|
118
|
+
created=datetime.now(timezone.utc).isoformat(),
|
|
119
|
+
mode=mode,
|
|
120
|
+
processforge_version=_pf_version,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
for name in sorted(root.group_keys()):
|
|
124
|
+
if name == "run_info":
|
|
125
|
+
continue
|
|
126
|
+
group = root[name]
|
|
127
|
+
arrays = sorted(group.array_keys())
|
|
128
|
+
if arrays:
|
|
129
|
+
dtypes = {k: _friendly_dtype(group[k].dtype) for k in arrays}
|
|
130
|
+
units = {k: _VARIABLE_UNITS.get(k, "") for k in arrays}
|
|
131
|
+
n_rows = max(group[k].shape[0] for k in arrays) if arrays else 0
|
|
132
|
+
schema.streams[name] = StreamSchema(
|
|
133
|
+
variables=arrays,
|
|
134
|
+
dtypes=dtypes,
|
|
135
|
+
units=units,
|
|
136
|
+
shape=[n_rows, len(arrays)],
|
|
137
|
+
has_time="time" in arrays,
|
|
138
|
+
has_phase="phase" in arrays,
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
attrs_keys = sorted(k for k in group.attrs if not k.startswith("_"))
|
|
142
|
+
if attrs_keys:
|
|
143
|
+
dtypes = {k: type(group.attrs[k]).__name__ for k in attrs_keys}
|
|
144
|
+
schema.solver_units[name] = SolverUnitSchema(
|
|
145
|
+
variables=attrs_keys, dtypes=dtypes
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if "run_info" in root:
|
|
149
|
+
ri = root["run_info"]
|
|
150
|
+
prov = {}
|
|
151
|
+
for key in ("git_hash", "timestamp", "mode", "backend",
|
|
152
|
+
"python_version", "platform", "processforge_version"):
|
|
153
|
+
if key in ri.attrs:
|
|
154
|
+
prov[key] = ri.attrs[key]
|
|
155
|
+
if key == "processforge_version":
|
|
156
|
+
schema.processforge_version = ri.attrs[key]
|
|
157
|
+
if "pkg_versions" in ri:
|
|
158
|
+
prov["pkg_versions"] = dict(ri["pkg_versions"].attrs)
|
|
159
|
+
if prov:
|
|
160
|
+
schema.provenance = prov
|
|
161
|
+
|
|
162
|
+
return schema
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _write_schema(store_path):
|
|
166
|
+
store = zarr.storage.LocalStore(store_path)
|
|
167
|
+
root = zarr.open_group(store=store, mode="r")
|
|
168
|
+
schema = _build_schema(root)
|
|
169
|
+
schema_path = store_path + ".schema.json"
|
|
170
|
+
with open(schema_path, "w", encoding="utf-8") as f:
|
|
171
|
+
f.write(schema.model_dump_json(indent=2))
|
|
172
|
+
logger.debug(f"Wrote schema to {schema_path}")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _write_schema_s3(s3_uri, storage_options):
|
|
176
|
+
import s3fs
|
|
177
|
+
|
|
178
|
+
store = zarr.storage.FsspecStore.from_url(s3_uri, storage_options=storage_options)
|
|
179
|
+
root = zarr.open_group(store=store, mode="r")
|
|
180
|
+
schema = _build_schema(root)
|
|
181
|
+
fs = s3fs.S3FileSystem(**storage_options)
|
|
182
|
+
schema_uri = s3_uri + ".schema.json"
|
|
183
|
+
with fs.open(schema_uri, "w") as f:
|
|
184
|
+
f.write(schema.model_dump_json(indent=2))
|
|
185
|
+
logger.debug(f"Wrote schema to {schema_uri}")
|
|
186
|
+
|
|
187
|
+
|
|
96
188
|
def save_results_zarr(results, fname="results.zarr", run_info: RunInfo | dict | None = None):
|
|
97
189
|
"""Persist simulation results in a Zarr directory.
|
|
98
190
|
|
|
@@ -117,11 +209,27 @@ def save_results_zarr(results, fname="results.zarr", run_info: RunInfo | dict |
|
|
|
117
209
|
root = zarr.group(store=store)
|
|
118
210
|
mode = "dynamic" if _is_dynamic(results) else "steady"
|
|
119
211
|
root.attrs["mode"] = mode
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
212
|
+
|
|
213
|
+
streams = {}
|
|
214
|
+
solver_units = {}
|
|
215
|
+
for name, data in results.items():
|
|
216
|
+
if isinstance(data, dict) and "status" in data:
|
|
217
|
+
solver_units[name] = data
|
|
218
|
+
else:
|
|
219
|
+
streams[name] = data
|
|
220
|
+
|
|
221
|
+
for name, data in streams.items():
|
|
222
|
+
group = root.create_group(name)
|
|
223
|
+
_store_stream(group, data)
|
|
224
|
+
|
|
225
|
+
for name, data in solver_units.items():
|
|
226
|
+
group = root.create_group(name)
|
|
227
|
+
_store_solver_unit(group, data)
|
|
228
|
+
|
|
123
229
|
if run_info is not None:
|
|
124
230
|
_store_run_info(root, run_info)
|
|
231
|
+
|
|
232
|
+
_write_schema(store_path)
|
|
125
233
|
logger.info(f"Saved Zarr results to {store_path}")
|
|
126
234
|
return store_path
|
|
127
235
|
|
|
@@ -140,7 +248,6 @@ def save_results_zarr_s3(results, s3_uri: str, run_info: RunInfo | dict | None =
|
|
|
140
248
|
run_info: Optional provenance metadata (same as :func:`save_results_zarr`).
|
|
141
249
|
"""
|
|
142
250
|
import s3fs # noqa: F401 – registers the s3:// protocol with fsspec
|
|
143
|
-
import os
|
|
144
251
|
|
|
145
252
|
storage_options = {
|
|
146
253
|
"key": os.environ.get("S3_ACCESS_KEY"),
|
|
@@ -160,11 +267,27 @@ def save_results_zarr_s3(results, s3_uri: str, run_info: RunInfo | dict | None =
|
|
|
160
267
|
root = zarr.group(store=store, overwrite=True)
|
|
161
268
|
mode = "dynamic" if _is_dynamic(results) else "steady"
|
|
162
269
|
root.attrs["mode"] = mode
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
270
|
+
|
|
271
|
+
streams = {}
|
|
272
|
+
solver_units = {}
|
|
273
|
+
for name, data in results.items():
|
|
274
|
+
if isinstance(data, dict) and "status" in data:
|
|
275
|
+
solver_units[name] = data
|
|
276
|
+
else:
|
|
277
|
+
streams[name] = data
|
|
278
|
+
|
|
279
|
+
for name, data in streams.items():
|
|
280
|
+
group = root.create_group(name)
|
|
281
|
+
_store_stream(group, data)
|
|
282
|
+
|
|
283
|
+
for name, data in solver_units.items():
|
|
284
|
+
group = root.create_group(name)
|
|
285
|
+
_store_solver_unit(group, data)
|
|
286
|
+
|
|
166
287
|
if run_info is not None:
|
|
167
288
|
_store_run_info(root, run_info)
|
|
289
|
+
|
|
290
|
+
_write_schema_s3(s3_uri, storage_options)
|
|
168
291
|
logger.info(f"Saved Zarr results to {s3_uri}")
|
|
169
292
|
|
|
170
293
|
|
|
@@ -270,53 +393,36 @@ def _scalar_from_sequence(value):
|
|
|
270
393
|
return _convert_value(candidate)
|
|
271
394
|
|
|
272
395
|
|
|
273
|
-
def _get_zarr_value(dataset, idx):
|
|
274
|
-
if dataset is None:
|
|
275
|
-
return ""
|
|
276
|
-
length = dataset.shape[0]
|
|
277
|
-
if length == 0:
|
|
278
|
-
return ""
|
|
279
|
-
position = min(idx, length - 1)
|
|
280
|
-
return _convert_value(dataset[position])
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
def _build_dataframe_row(group, stream_name, idx, comp_names, include_time):
|
|
284
|
-
row = {"stream": stream_name}
|
|
285
|
-
if include_time and "time" in group:
|
|
286
|
-
row["time"] = _get_zarr_value(group.get("time"), idx)
|
|
287
|
-
row["T [K]"] = _get_zarr_value(group.get("T"), idx)
|
|
288
|
-
row["P [Pa]"] = _get_zarr_value(group.get("P"), idx)
|
|
289
|
-
row["Phase"] = _get_zarr_value(group.get("phase"), idx)
|
|
290
|
-
row["VaporFrac"] = _get_zarr_value(group.get("beta"), idx)
|
|
291
|
-
row["flowrate"] = _get_zarr_value(group.get("flowrate"), idx)
|
|
292
|
-
comp_group = group.get("__composition__")
|
|
293
|
-
if comp_group is not None:
|
|
294
|
-
for comp in comp_names:
|
|
295
|
-
row[comp] = _get_zarr_value(comp_group.get(comp), idx)
|
|
296
|
-
return row
|
|
297
|
-
|
|
298
|
-
|
|
299
396
|
def _load_dataframe_from_zarr(store_path):
|
|
300
397
|
store = zarr.storage.LocalStore(store_path)
|
|
301
398
|
root = zarr.open(store=store, mode="r")
|
|
302
|
-
streams = sorted(k for k in root.group_keys() if k != "run_info")
|
|
303
399
|
rows = []
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
for stream in streams:
|
|
400
|
+
|
|
401
|
+
for stream in sorted(k for k in root.group_keys() if k != "run_info"):
|
|
307
402
|
group = root[stream]
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
403
|
+
arrays = list(group.array_keys())
|
|
404
|
+
if not arrays:
|
|
405
|
+
continue
|
|
406
|
+
|
|
407
|
+
has_time = "time" in group
|
|
408
|
+
time_arr = group["time"][:] if has_time else None
|
|
409
|
+
has_phase = "phase" in group
|
|
410
|
+
phase_arr = group["phase"][:] if has_phase else None
|
|
411
|
+
|
|
412
|
+
data_vars = [k for k in arrays if k not in ("time", "phase")]
|
|
413
|
+
if not data_vars:
|
|
414
|
+
continue
|
|
415
|
+
n = max(group[k].shape[0] for k in data_vars)
|
|
416
|
+
|
|
417
|
+
for idx in range(n):
|
|
418
|
+
row = {"stream": stream}
|
|
419
|
+
if has_time:
|
|
420
|
+
row["time"] = float(time_arr[idx])
|
|
421
|
+
for var in data_vars:
|
|
422
|
+
row[var] = _convert_value(group[var][idx])
|
|
423
|
+
if has_phase:
|
|
424
|
+
row["phase"] = str(phase_arr[idx])
|
|
425
|
+
rows.append(row)
|
|
426
|
+
|
|
427
|
+
return pd.DataFrame(rows)
|
|
322
428
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class StreamSchema(BaseModel):
|
|
5
|
+
variables: list[str]
|
|
6
|
+
dtypes: dict[str, str]
|
|
7
|
+
units: dict[str, str] = {}
|
|
8
|
+
shape: list[int]
|
|
9
|
+
has_time: bool = False
|
|
10
|
+
has_phase: bool = False
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SolverUnitSchema(BaseModel):
|
|
14
|
+
variables: list[str]
|
|
15
|
+
dtypes: dict[str, str]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ResultSchema(BaseModel):
|
|
19
|
+
version: int = 1
|
|
20
|
+
store_type: str = "simulation_results"
|
|
21
|
+
created: str
|
|
22
|
+
mode: str
|
|
23
|
+
processforge_version: str = ""
|
|
24
|
+
streams: dict[str, StreamSchema] = {}
|
|
25
|
+
solver_units: dict[str, SolverUnitSchema] = {}
|
|
26
|
+
provenance: dict = {}
|