processforge 0.3.16__tar.gz → 0.3.17__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.
- {processforge-0.3.16/src/processforge.egg-info → processforge-0.3.17}/PKG-INFO +1 -1
- {processforge-0.3.16 → processforge-0.3.17}/flowsheets/openmc/msre_eigenvalue.json +1 -1
- {processforge-0.3.16 → processforge-0.3.17}/pyproject.toml +1 -1
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/cli/run.py +33 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/providers/container_client.py +1 -0
- processforge-0.3.17/src/processforge/providers/errors.py +275 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/providers/festim_provider.py +3 -9
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/providers/openmc_provider.py +3 -9
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/types.py +15 -0
- {processforge-0.3.16 → processforge-0.3.17/src/processforge.egg-info}/PKG-INFO +1 -1
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge.egg-info/SOURCES.txt +3 -0
- processforge-0.3.17/tests/test_container_client.py +33 -0
- {processforge-0.3.16 → processforge-0.3.17}/tests/test_festim_provider.py +7 -2
- {processforge-0.3.16 → processforge-0.3.17}/tests/test_openmc_provider.py +19 -0
- processforge-0.3.17/tests/test_provider_errors.py +104 -0
- {processforge-0.3.16 → processforge-0.3.17}/LICENSE +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/MANIFEST.in +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/README.md +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/flowsheets/closed-loop-chain.json +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/flowsheets/coupled/msre_coupled.json +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/flowsheets/custom/crimson/MUWS_HB.json +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/flowsheets/custom/simple_hyrdaulic_scheme.json +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/flowsheets/festim/hydrogen_transport_1d.json +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/flowsheets/festim/tds_tungsten.json +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/flowsheets/hydraulic-chain.json +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/setup.cfg +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/__init__.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/__main__.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/_schema.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/analysis/__init__.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/analysis/dof.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/api/__init__.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/api/serve.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/cli/__init__.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/cli/apply.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/cli/common.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/cli/diagram.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/cli/display.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/cli/export_fmu.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/cli/export_modelica.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/cli/init.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/cli/plan.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/cli/validate.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/compose.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/coupling.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/__init__.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/backends/__init__.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/backends/base.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/backends/casadi_backend.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/backends/pyomo_backend.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/backends/scipy_backend.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/flowsheet.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/jacobian.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/mixin.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/solver.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/stream_var.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/units/__init__.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/units/flash_eo.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/units/heater_eo.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/units/pipes_eo.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/units/pump_eo.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/units/strainer_eo.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/eo/units/valve_eo.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/flowsheet.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/fmu/__init__.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/fmu/_fmi_vars.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/fmu/builder.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/fmu/slave_template.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/lock.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/modelica/__init__.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/modelica/mo_writer.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/modelica/omc_runner.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/modelica/transpiler.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/modelica/unit_equations.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/output_collector.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/pcl/__init__.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/pcl/compiler.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/pcl/namespace.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/persistence/__init__.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/persistence/archive.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/persistence/artifact_store.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/provenance.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/providers/__init__.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/providers/base.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/providers/base_jacobian_mixin.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/providers/cantera_jacobian.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/providers/cantera_provider.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/providers/coolprop_provider.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/providers/jacobian_contributor.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/providers/manager.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/providers/modelica_jacobian.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/providers/modelica_provider.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/providers/reference_state_registry.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/providers/registry.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/quantity.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/result.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/result_schema.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/schemas/__init__.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/schemas/festim/festim_model.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/schemas/flowsheet_schema.json +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/schemas/openmc/openmc_model.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/simulate.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/solver.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/state.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/thermo.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/units/__init__.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/units/base.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/units/cstr.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/units/flash.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/units/heater.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/units/pfr.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/units/pipes.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/units/pump.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/units/registry.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/units/solver_unit.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/units/strainer.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/units/tank.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/units/valve.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/utils/__init__.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/utils/flowsheet_diagram.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/utils/mermaid_diagram.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/utils/s3_upload.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/utils/topology.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/utils/unit_consistency.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/utils/validate_flowsheet.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge/utils/validation.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge.egg-info/dependency_links.txt +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge.egg-info/entry_points.txt +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge.egg-info/requires.txt +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/src/processforge.egg-info/top_level.txt +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/tests/test_cli.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/tests/test_coupling.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/tests/test_festim_schema.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/tests/test_init.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/tests/test_providers.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/tests/test_s3_upload.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/tests/test_state.py +0 -0
- {processforge-0.3.16 → processforge-0.3.17}/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.17"
|
|
8
8
|
description = "A Python-based process simulation framework for chemical engineering applications."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "BSD-3-Clause"
|
|
@@ -87,6 +87,8 @@ def run(
|
|
|
87
87
|
logger.info("=== Dynamic Results ===")
|
|
88
88
|
results = fs.run()
|
|
89
89
|
|
|
90
|
+
_check_for_failed_units(fs)
|
|
91
|
+
|
|
90
92
|
if hasattr(fs, "converged"):
|
|
91
93
|
if fs.converged:
|
|
92
94
|
logger.info("Dynamic simulation converged.")
|
|
@@ -106,6 +108,8 @@ def run(
|
|
|
106
108
|
logger.info("=== Steady-State EO Results ===")
|
|
107
109
|
results = fs.run()
|
|
108
110
|
|
|
111
|
+
_check_for_failed_units(fs)
|
|
112
|
+
|
|
109
113
|
if hasattr(fs, "converged"):
|
|
110
114
|
if fs.converged:
|
|
111
115
|
logger.info("Steady-state simulation converged.")
|
|
@@ -153,3 +157,32 @@ def run(
|
|
|
153
157
|
logger.info(f"Plots saved: {base_name}_results.png, {base_name}_timeseries.png")
|
|
154
158
|
except Exception as e:
|
|
155
159
|
logger.warning(f"Failed to generate plots: {type(e).__name__}: {e}")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _check_for_failed_units(fs):
|
|
163
|
+
"""Fail loudly if any SolverUnit run returned ``status="failed"``.
|
|
164
|
+
|
|
165
|
+
Containerized providers return HTTP 200 with a structured ``EngineOutput``
|
|
166
|
+
(status="failed") rather than raising, so the failure would otherwise be
|
|
167
|
+
swallowed and recorded as a successful run. Surface it clearly and exit
|
|
168
|
+
non-zero.
|
|
169
|
+
"""
|
|
170
|
+
from ..types import EngineOutput
|
|
171
|
+
|
|
172
|
+
failed = {
|
|
173
|
+
name: out
|
|
174
|
+
for name, out in getattr(fs, "engine_outputs", {}).items()
|
|
175
|
+
if isinstance(out, EngineOutput) and out.status == "failed"
|
|
176
|
+
}
|
|
177
|
+
if not failed:
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
for name, out in failed.items():
|
|
181
|
+
err = out.error
|
|
182
|
+
category = getattr(err, "category", "unknown") if err else "unknown"
|
|
183
|
+
message = getattr(err, "message", "") if err else ""
|
|
184
|
+
logger.error(f"Unit '{name}' simulation FAILED [{category}]: {message}")
|
|
185
|
+
hint = getattr(err, "hint", "") if err else ""
|
|
186
|
+
if hint:
|
|
187
|
+
logger.error(f" hint: {hint}")
|
|
188
|
+
raise SystemExit(1)
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""Provider-side run-error classification.
|
|
2
|
+
|
|
3
|
+
When a simulation is executed *inside* a provider/engine (e.g. OpenMC or FESTIM
|
|
4
|
+
running in a Docker container) the run can fail for reasons that live entirely
|
|
5
|
+
in the engine — missing nuclear-data temperatures, an MPI process abort, a
|
|
6
|
+
geometry that excludes a source point, a tally that references an unknown
|
|
7
|
+
filter, … . These are distinct from *flowsheet/setup* errors (bad JSON, unknown
|
|
8
|
+
material, schema validation), which are caught earlier during initialization or
|
|
9
|
+
flowsheet validation.
|
|
10
|
+
|
|
11
|
+
This module gives every provider a single, structured way to capture such a
|
|
12
|
+
run-time failure:
|
|
13
|
+
|
|
14
|
+
* :class:`ProviderRunError` — a typed, engine-agnostic record of what failed,
|
|
15
|
+
attributed to ``source="provider"`` so Processforge (and the user) can tell a
|
|
16
|
+
runtime engine error apart from a flowsheet configuration error.
|
|
17
|
+
* :func:`classify_run_error` — turn an exception (plus any captured
|
|
18
|
+
stdout/stderr from the engine) into a :class:`ProviderRunError`, picking a
|
|
19
|
+
category and a
|
|
20
|
+
concrete remediation hint where one is known.
|
|
21
|
+
* :func:`make_failed_output` — build the ``EngineOutput(status="failed")`` that
|
|
22
|
+
providers return, populated with the classification.
|
|
23
|
+
|
|
24
|
+
Categories are intentionally broad so a new engine signature can be added here
|
|
25
|
+
without touching the providers.
|
|
26
|
+
"""
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import re
|
|
30
|
+
from typing import Optional
|
|
31
|
+
|
|
32
|
+
from pydantic import BaseModel
|
|
33
|
+
|
|
34
|
+
# Error categories. Strings (not an Enum) so they serialize cleanly in JSON
|
|
35
|
+
# through the provider HTTP API and are easy to match on in tests/docs.
|
|
36
|
+
NUCLEAR_DATA = "nuclear_data"
|
|
37
|
+
CROSS_SECTIONS = "cross_sections"
|
|
38
|
+
MPI_ABORT = "mpi_abort"
|
|
39
|
+
GEOMETRY = "geometry"
|
|
40
|
+
TALLY = "tally"
|
|
41
|
+
CONVERGENCE = "convergence"
|
|
42
|
+
INPUT_VALIDATION = "input_validation"
|
|
43
|
+
ENVIRONMENT = "environment"
|
|
44
|
+
UNKNOWN = "unknown"
|
|
45
|
+
|
|
46
|
+
# (category, compiled-regex, remediation hint). First match wins.
|
|
47
|
+
# Order matters: more specific (and more actionable) patterns come first.
|
|
48
|
+
_ERROR_SIGNATURES: list[tuple[str, re.Pattern[str], str]] = [
|
|
49
|
+
(
|
|
50
|
+
NUCLEAR_DATA,
|
|
51
|
+
re.compile(
|
|
52
|
+
r"nuclear data library does not contain cross sections",
|
|
53
|
+
re.IGNORECASE,
|
|
54
|
+
),
|
|
55
|
+
(
|
|
56
|
+
"The cross-section library lacks data at the requested "
|
|
57
|
+
"temperature. "
|
|
58
|
+
"Either set the material temperature to a value present in the library "
|
|
59
|
+
"(e.g. 300 K), or enable openmc.Settings.temperature handling "
|
|
60
|
+
"(temperature_method / multipole interpolation) so intermediate "
|
|
61
|
+
"temperatures are treated."
|
|
62
|
+
),
|
|
63
|
+
),
|
|
64
|
+
(
|
|
65
|
+
CROSS_SECTIONS,
|
|
66
|
+
re.compile(
|
|
67
|
+
r"cross section[s]? (file|library|data|xml)|"
|
|
68
|
+
r"could not (find|read|open).*cross_section|"
|
|
69
|
+
r"no cross sections (available|found)|"
|
|
70
|
+
r"cross_sections\.xml",
|
|
71
|
+
re.IGNORECASE,
|
|
72
|
+
),
|
|
73
|
+
(
|
|
74
|
+
"Cross-section data could not be located or parsed. Verify the "
|
|
75
|
+
"provider 'cross_sections' path points at a valid cross_sections.xml "
|
|
76
|
+
"and that the data directory is mounted into the container."
|
|
77
|
+
),
|
|
78
|
+
),
|
|
79
|
+
(
|
|
80
|
+
MPI_ABORT,
|
|
81
|
+
re.compile(r"MPI_ABORT|mpi abort|orted: \w+ rank", re.IGNORECASE),
|
|
82
|
+
(
|
|
83
|
+
"The solver process crashed (MPI_ABORT). The root cause is usually the "
|
|
84
|
+
"preceding engine error (e.g. nuclear-data or geometry failure); fix "
|
|
85
|
+
"that first. If it occurs with no preceding error, the container may "
|
|
86
|
+
"be out of memory or MPI may be misconfigured."
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
(
|
|
90
|
+
GEOMETRY,
|
|
91
|
+
re.compile(
|
|
92
|
+
r"geometry ?error|cannot find cell|no cell found|"
|
|
93
|
+
r"particle (got )?lost|surface .*? not (found|defined)|"
|
|
94
|
+
r"universe .*? not (found|defined)|geometry does not contain",
|
|
95
|
+
re.IGNORECASE,
|
|
96
|
+
),
|
|
97
|
+
(
|
|
98
|
+
"The problem geometry is invalid or the source/particles fall outside "
|
|
99
|
+
"it. Check geometry_config dimensions and the source_point location, "
|
|
100
|
+
"and ensure every referenced material region is filled."
|
|
101
|
+
),
|
|
102
|
+
),
|
|
103
|
+
(
|
|
104
|
+
TALLY,
|
|
105
|
+
re.compile(r"tally ?error|tally .*? (not|does not) (exist|have)|invalid filter", re.IGNORECASE),
|
|
106
|
+
(
|
|
107
|
+
"A tally or its filter is invalid. Check mesh_tallies / tally scores, "
|
|
108
|
+
"filter IDs, and that referenced cells/meshes exist in the geometry."
|
|
109
|
+
),
|
|
110
|
+
),
|
|
111
|
+
(
|
|
112
|
+
CONVERGENCE,
|
|
113
|
+
re.compile(
|
|
114
|
+
r"did not converge|maximum number of (iterations|resampling)|"
|
|
115
|
+
r"failed to converge|stagnat",
|
|
116
|
+
re.IGNORECASE,
|
|
117
|
+
),
|
|
118
|
+
(
|
|
119
|
+
"The solve did not converge. Increase batches/iterations or relax "
|
|
120
|
+
"tolerances in solver_config; verify the model is well-posed."
|
|
121
|
+
),
|
|
122
|
+
),
|
|
123
|
+
(
|
|
124
|
+
INPUT_VALIDATION,
|
|
125
|
+
re.compile(
|
|
126
|
+
r"valueerror|keyerror|typeerror|validation ?error|"
|
|
127
|
+
r"expected .*? (got|found)|missing .*? argument",
|
|
128
|
+
re.IGNORECASE,
|
|
129
|
+
),
|
|
130
|
+
(
|
|
131
|
+
"The engine rejected the resolved configuration. This usually means a "
|
|
132
|
+
"value in solver_config/geometry_config is out of range or mistyped."
|
|
133
|
+
),
|
|
134
|
+
),
|
|
135
|
+
(
|
|
136
|
+
ENVIRONMENT,
|
|
137
|
+
re.compile(
|
|
138
|
+
r"permission denied|no such file or directory|"
|
|
139
|
+
r"cannot (write|create) .*? directory|"
|
|
140
|
+
r"shared library|lib[a-z0-9]+\.so",
|
|
141
|
+
re.IGNORECASE,
|
|
142
|
+
),
|
|
143
|
+
(
|
|
144
|
+
"An environment/container issue (file permissions, missing library, "
|
|
145
|
+
"or unwritable output dir). Check the container mounts and that the "
|
|
146
|
+
"output directory is writable."
|
|
147
|
+
),
|
|
148
|
+
),
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class ProviderRunError(BaseModel):
|
|
153
|
+
"""Structured record of a run-time failure that originated in a provider/engine.
|
|
154
|
+
|
|
155
|
+
Attributes:
|
|
156
|
+
category: One of the ``*`` constants above (``nuclear_data``, ``mpi_abort``,
|
|
157
|
+
…). ``unknown`` when no signature matched.
|
|
158
|
+
source: Always ``"provider"`` — distinguishes engine run-time failures
|
|
159
|
+
from flowsheet/setup validation errors.
|
|
160
|
+
message: A concise, human-readable one-line summary of the failure.
|
|
161
|
+
type: The Python exception class name (e.g. ``RuntimeError``).
|
|
162
|
+
detail: The full captured error text (engine stderr/stdout + traceback
|
|
163
|
+
tail). May be long; this is what gets logged.
|
|
164
|
+
hint: A concrete remediation suggestion where a category is recognised,
|
|
165
|
+
else a generic pointer to check the engine log.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
category: str = UNKNOWN
|
|
169
|
+
source: str = "provider"
|
|
170
|
+
message: str = ""
|
|
171
|
+
type: str = "Exception"
|
|
172
|
+
detail: str = ""
|
|
173
|
+
hint: str = (
|
|
174
|
+
"Inspect the engine log / run directory for the root cause; this is a "
|
|
175
|
+
"run-time error raised by the provider, not a flowsheet configuration error."
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
@classmethod
|
|
179
|
+
def from_exception(
|
|
180
|
+
cls,
|
|
181
|
+
exc: BaseException,
|
|
182
|
+
captured: str = "",
|
|
183
|
+
category: Optional[str] = None,
|
|
184
|
+
) -> "ProviderRunError":
|
|
185
|
+
"""Build a :class:`ProviderRunError` from an exception (category overridable)."""
|
|
186
|
+
text = str(exc)
|
|
187
|
+
if captured:
|
|
188
|
+
text = f"{text}\n\n{captured}".strip()
|
|
189
|
+
# Prefer an explicitly supplied category (e.g. a provider already knows).
|
|
190
|
+
if category is None:
|
|
191
|
+
category = _classify_text(text)
|
|
192
|
+
return cls(
|
|
193
|
+
category=category,
|
|
194
|
+
message=_summarize(text) or f"{type(exc).__name__}: {text}",
|
|
195
|
+
type=type(exc).__name__,
|
|
196
|
+
detail=text,
|
|
197
|
+
hint=_hint_for(category),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def classify_run_error(
|
|
202
|
+
engine: str,
|
|
203
|
+
exc: BaseException,
|
|
204
|
+
captured: str = "",
|
|
205
|
+
) -> ProviderRunError:
|
|
206
|
+
"""Classify a provider run-time exception into a :class:`ProviderRunError`.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
engine: Engine name (``"openmc"``, ``"festim"``, …) — recorded for context
|
|
210
|
+
only (it does not change classification today).
|
|
211
|
+
exc: The exception raised by the engine run.
|
|
212
|
+
captured: Optional captured engine stdout/stderr, used to improve the
|
|
213
|
+
classification when the raised message is terse.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
A populated :class:`ProviderRunError` (``source="provider"``).
|
|
217
|
+
"""
|
|
218
|
+
err = ProviderRunError.from_exception(exc, captured=captured)
|
|
219
|
+
err.message = f"[{engine}] {err.message}"
|
|
220
|
+
return err
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def make_failed_output(
|
|
224
|
+
engine: str,
|
|
225
|
+
sim_type: str,
|
|
226
|
+
run_dir,
|
|
227
|
+
err: ProviderRunError,
|
|
228
|
+
unit: str = "",
|
|
229
|
+
) -> "EngineOutput": # type: ignore[name-defined] # imported lazily to avoid cycle
|
|
230
|
+
"""Build the ``EngineOutput(status="failed")`` providers return on a run error."""
|
|
231
|
+
from processforge.types import EngineOutput
|
|
232
|
+
|
|
233
|
+
return EngineOutput(
|
|
234
|
+
status="failed",
|
|
235
|
+
engine=engine,
|
|
236
|
+
sim_type=sim_type,
|
|
237
|
+
unit=unit,
|
|
238
|
+
error=err,
|
|
239
|
+
diagnostics={
|
|
240
|
+
"run_dir": str(getattr(run_dir, "resolve", lambda: run_dir)()),
|
|
241
|
+
"error": err.detail,
|
|
242
|
+
"error_category": err.category,
|
|
243
|
+
"error_source": err.source,
|
|
244
|
+
},
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _classify_text(text: str) -> str:
|
|
249
|
+
for category, pattern, _hint in _ERROR_SIGNATURES:
|
|
250
|
+
if pattern.search(text):
|
|
251
|
+
return category
|
|
252
|
+
return UNKNOWN
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _hint_for(category: str) -> str:
|
|
256
|
+
for cat, _pattern, hint in _ERROR_SIGNATURES:
|
|
257
|
+
if cat == category:
|
|
258
|
+
return hint
|
|
259
|
+
return ProviderRunError().hint
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _summarize(text: str, limit: int = 280) -> str:
|
|
263
|
+
"""Return the most diagnostic single line of ``text`` (engine ERROR lines win)."""
|
|
264
|
+
lines = [ln.strip() for ln in text.splitlines() if ln.strip()]
|
|
265
|
+
if not lines:
|
|
266
|
+
return ""
|
|
267
|
+
for ln in lines:
|
|
268
|
+
if ln.upper().startswith("ERROR") or "ERROR:" in ln.upper():
|
|
269
|
+
return ln[:limit]
|
|
270
|
+
# Fall back to the first line that looks like a message (not a banner/traceback).
|
|
271
|
+
for ln in lines:
|
|
272
|
+
if ln.startswith(("Traceback", "File ", "raise ", "Proc:", "NOTE:")):
|
|
273
|
+
continue
|
|
274
|
+
return ln[:limit]
|
|
275
|
+
return lines[0][:limit]
|
|
@@ -67,6 +67,7 @@ from processforge.types import (
|
|
|
67
67
|
OutputField,
|
|
68
68
|
OutputProvenance,
|
|
69
69
|
)
|
|
70
|
+
from processforge.providers.errors import classify_run_error, make_failed_output
|
|
70
71
|
from processforge.units import Quantity
|
|
71
72
|
|
|
72
73
|
if TYPE_CHECKING:
|
|
@@ -771,15 +772,8 @@ class FestimProvider(AbstractProvider):
|
|
|
771
772
|
logger.exception(
|
|
772
773
|
f"FestimProvider: '{sim_type}' failed in '{run_dir}': {exc}"
|
|
773
774
|
)
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
engine="festim",
|
|
777
|
-
sim_type=sim_type,
|
|
778
|
-
diagnostics={
|
|
779
|
-
"run_dir": str(run_dir.resolve()),
|
|
780
|
-
"error": str(exc),
|
|
781
|
-
},
|
|
782
|
-
)
|
|
775
|
+
err = classify_run_error("festim", exc)
|
|
776
|
+
return make_failed_output("festim", sim_type, run_dir, err)
|
|
783
777
|
finally:
|
|
784
778
|
os.chdir(prev_cwd)
|
|
785
779
|
|
|
@@ -60,6 +60,7 @@ from processforge.types import (
|
|
|
60
60
|
OutputField,
|
|
61
61
|
OutputProvenance,
|
|
62
62
|
)
|
|
63
|
+
from processforge.providers.errors import classify_run_error, make_failed_output
|
|
63
64
|
from processforge.units import Quantity
|
|
64
65
|
|
|
65
66
|
|
|
@@ -806,15 +807,8 @@ class OpenMCProvider(AbstractProvider):
|
|
|
806
807
|
logger.exception(
|
|
807
808
|
f"OpenMCProvider: '{sim_type}' failed in '{run_dir}': {exc}"
|
|
808
809
|
)
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
engine="openmc",
|
|
812
|
-
sim_type=sim_type,
|
|
813
|
-
diagnostics={
|
|
814
|
-
"run_dir": str(run_dir.resolve()),
|
|
815
|
-
"error": str(exc),
|
|
816
|
-
},
|
|
817
|
-
)
|
|
810
|
+
err = classify_run_error("openmc", exc)
|
|
811
|
+
return make_failed_output("openmc", sim_type, run_dir, err)
|
|
818
812
|
finally:
|
|
819
813
|
if xs_path:
|
|
820
814
|
if original_xs is None:
|
|
@@ -473,6 +473,10 @@ class EngineOutput(BaseModel):
|
|
|
473
473
|
artifacts: list[OutputArtifact] = Field(default_factory=list)
|
|
474
474
|
diagnostics: dict = Field(default_factory=dict)
|
|
475
475
|
provenance: OutputProvenance = Field(default_factory=OutputProvenance)
|
|
476
|
+
# Structured classification of a run-time failure (populated only when
|
|
477
|
+
# status == "failed"). source is always "provider" so a runtime engine error
|
|
478
|
+
# is distinguishable from a flowsheet/setup validation error.
|
|
479
|
+
error: Optional["ProviderRunError"] = None
|
|
476
480
|
|
|
477
481
|
# -- convenience accessors -------------------------------------------
|
|
478
482
|
def get_field(self, name: str) -> Optional[OutputField]:
|
|
@@ -623,3 +627,14 @@ class MergedInletTimeseries(BaseModel):
|
|
|
623
627
|
|
|
624
628
|
def __iter__(self):
|
|
625
629
|
return iter(MergedInletTimeseries.model_fields)
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
# Imported at the end (not at module top) to avoid an import cycle: the
|
|
633
|
+
# providers package __init__ pulls in provider subclasses that themselves
|
|
634
|
+
# import this module. ``ProviderRunError`` defines no dependency on types.py,
|
|
635
|
+
# so importing it here — after every model above is defined — is safe.
|
|
636
|
+
from processforge.providers.errors import ProviderRunError # noqa: E402
|
|
637
|
+
|
|
638
|
+
# Resolve the forward reference used in ``EngineOutput.error`` now that
|
|
639
|
+
# ``ProviderRunError`` is available in this module's namespace.
|
|
640
|
+
EngineOutput.model_rebuild()
|
|
@@ -88,6 +88,7 @@ src/processforge/providers/cantera_jacobian.py
|
|
|
88
88
|
src/processforge/providers/cantera_provider.py
|
|
89
89
|
src/processforge/providers/container_client.py
|
|
90
90
|
src/processforge/providers/coolprop_provider.py
|
|
91
|
+
src/processforge/providers/errors.py
|
|
91
92
|
src/processforge/providers/festim_provider.py
|
|
92
93
|
src/processforge/providers/jacobian_contributor.py
|
|
93
94
|
src/processforge/providers/manager.py
|
|
@@ -122,11 +123,13 @@ src/processforge/utils/unit_consistency.py
|
|
|
122
123
|
src/processforge/utils/validate_flowsheet.py
|
|
123
124
|
src/processforge/utils/validation.py
|
|
124
125
|
tests/test_cli.py
|
|
126
|
+
tests/test_container_client.py
|
|
125
127
|
tests/test_coupling.py
|
|
126
128
|
tests/test_festim_provider.py
|
|
127
129
|
tests/test_festim_schema.py
|
|
128
130
|
tests/test_init.py
|
|
129
131
|
tests/test_openmc_provider.py
|
|
132
|
+
tests/test_provider_errors.py
|
|
130
133
|
tests/test_providers.py
|
|
131
134
|
tests/test_s3_upload.py
|
|
132
135
|
tests/test_state.py
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Tests for the CLI-side container provider HTTP client serialization."""
|
|
2
|
+
|
|
3
|
+
from processforge.providers.container_client import ContainerProviderClient
|
|
4
|
+
from processforge.types import UnitConfig
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_serialize_unit_config_includes_geometry_config():
|
|
8
|
+
"""geometry_config must survive the CLI -> container body serialization.
|
|
9
|
+
|
|
10
|
+
Regression test for the bug where geometry_config (a known UnitConfig
|
|
11
|
+
field) was dropped by the hardcoded serialization list, so the container
|
|
12
|
+
received an empty geometry_config and failed validation.
|
|
13
|
+
"""
|
|
14
|
+
geometry_config = {
|
|
15
|
+
"type": "reactor_core",
|
|
16
|
+
"core_radius": 72.5,
|
|
17
|
+
"core_height": 160.0,
|
|
18
|
+
"core_material": "salt",
|
|
19
|
+
}
|
|
20
|
+
uc = UnitConfig(
|
|
21
|
+
type="SolverUnit",
|
|
22
|
+
provider="openmc",
|
|
23
|
+
material=3,
|
|
24
|
+
sim_type="eigenvalue_reactor",
|
|
25
|
+
solver_config={"batches": 20},
|
|
26
|
+
geometry_config=geometry_config,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
body = ContainerProviderClient._serialize_unit_config(uc)
|
|
30
|
+
|
|
31
|
+
assert body["geometry_config"] == geometry_config
|
|
32
|
+
assert body["solver_config"] == {"batches": 20}
|
|
33
|
+
assert body["sim_type"] == "eigenvalue_reactor"
|
|
@@ -837,8 +837,13 @@ class TestFestimRunSimulation:
|
|
|
837
837
|
|
|
838
838
|
result = provider.run_simulation(unit_cfg, {})
|
|
839
839
|
assert result.status == "failed"
|
|
840
|
-
assert "SNES diverged" in result.
|
|
841
|
-
assert result.
|
|
840
|
+
assert "SNES diverged" in result.diagnostics["error"]
|
|
841
|
+
assert result.diagnostics["run_dir"] == str((tmp_path / "festim_run").resolve())
|
|
842
|
+
# Failure is classified and attributed to the provider, not the flowsheet.
|
|
843
|
+
assert result.error is not None
|
|
844
|
+
assert result.error.source == "provider"
|
|
845
|
+
assert result.diagnostics["error_source"] == "provider"
|
|
846
|
+
assert result.error.detail
|
|
842
847
|
|
|
843
848
|
def test_run_before_init_raises(self, fake_festim):
|
|
844
849
|
from processforge.providers.festim_provider import FestimProvider
|
|
@@ -435,6 +435,25 @@ def test_run_exception_returns_failed_result(tmp_path, fake_openmc):
|
|
|
435
435
|
assert os.environ.get("OPENMC_CROSS_SECTIONS") == prior
|
|
436
436
|
|
|
437
437
|
|
|
438
|
+
def test_run_exception_is_classified(tmp_path, fake_openmc):
|
|
439
|
+
"""A nuclear-data run failure must be classified with source='provider'."""
|
|
440
|
+
from processforge.providers.errors import NUCLEAR_DATA
|
|
441
|
+
|
|
442
|
+
provider = _init_provider(tmp_path, _default_materials())
|
|
443
|
+
_CTRL.raise_msg = (
|
|
444
|
+
"Nuclear data library does not contain cross sections for Ni58 at or near "
|
|
445
|
+
"400.000000 K. Available temperatures are 300 K."
|
|
446
|
+
)
|
|
447
|
+
result = provider.run_simulation(_unit_config(), {})
|
|
448
|
+
|
|
449
|
+
assert result.status == "failed"
|
|
450
|
+
assert result.error is not None
|
|
451
|
+
assert result.error.category == NUCLEAR_DATA
|
|
452
|
+
assert result.error.source == "provider"
|
|
453
|
+
assert result.diagnostics["error_category"] == NUCLEAR_DATA
|
|
454
|
+
assert "temperature" in result.error.hint.lower()
|
|
455
|
+
|
|
456
|
+
|
|
438
457
|
def test_run_without_statepoint_returns_failed(tmp_path, fake_openmc):
|
|
439
458
|
provider = _init_provider(tmp_path, _default_materials())
|
|
440
459
|
result = provider.run_simulation(_unit_config(), {})
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Tests for the provider run-error classification utility."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import pathlib
|
|
5
|
+
|
|
6
|
+
from processforge.providers.errors import (
|
|
7
|
+
CROSS_SECTIONS,
|
|
8
|
+
GEOMETRY,
|
|
9
|
+
MPI_ABORT,
|
|
10
|
+
NUCLEAR_DATA,
|
|
11
|
+
ProviderRunError,
|
|
12
|
+
UNKNOWN,
|
|
13
|
+
classify_run_error,
|
|
14
|
+
make_failed_output,
|
|
15
|
+
)
|
|
16
|
+
from processforge.types import EngineOutput
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _classify(msg: str, engine: str = "openmc", captured: str = "") -> ProviderRunError:
|
|
20
|
+
return classify_run_error(engine, RuntimeError(msg), captured=captured)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_nuclear_data_category_and_hint():
|
|
24
|
+
err = _classify(
|
|
25
|
+
"Nuclear data library does not contain cross sections for Ni58 at or near "
|
|
26
|
+
"400.000000 K. Available temperatures are 300 K."
|
|
27
|
+
)
|
|
28
|
+
assert err.category == NUCLEAR_DATA
|
|
29
|
+
assert err.source == "provider"
|
|
30
|
+
assert "temperature" in err.hint.lower()
|
|
31
|
+
assert err.type == "RuntimeError"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_mpi_abort_category():
|
|
35
|
+
err = _classify(
|
|
36
|
+
"RuntimeError: something bad\n"
|
|
37
|
+
"MPI_ABORT was invoked on rank 0 in communicator MPI_COMM_WORLD"
|
|
38
|
+
)
|
|
39
|
+
assert err.category == MPI_ABORT
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_cross_sections_category():
|
|
43
|
+
err = _classify("Could not find cross_sections.xml at the configured path")
|
|
44
|
+
assert err.category == CROSS_SECTIONS
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_geometry_category():
|
|
48
|
+
err = _classify("GeometryError: Could not find cell containing particle")
|
|
49
|
+
assert err.category == GEOMETRY
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_unknown_fallback():
|
|
53
|
+
err = _classify("some totally opaque engine complaint with no known signature")
|
|
54
|
+
assert err.category == UNKNOWN
|
|
55
|
+
assert err.source == "provider"
|
|
56
|
+
assert "run-time" in err.hint
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_message_prefixed_with_engine():
|
|
60
|
+
err = _classify("Nuclear data library does not contain cross sections for Ni58")
|
|
61
|
+
assert err.message.startswith("[openmc]")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_captured_text_improves_classification():
|
|
65
|
+
# Raised message is terse; the real signal is in the captured engine stderr.
|
|
66
|
+
err = _classify(
|
|
67
|
+
"RuntimeError",
|
|
68
|
+
captured="ERROR: Nuclear data library does not contain cross sections for U235",
|
|
69
|
+
)
|
|
70
|
+
assert err.category == NUCLEAR_DATA
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_from_exception_sets_fields():
|
|
74
|
+
err = ProviderRunError.from_exception(ValueError("bad value"))
|
|
75
|
+
assert err.type == "ValueError"
|
|
76
|
+
assert err.category == "input_validation" or err.category # input_validation or unknown
|
|
77
|
+
assert err.detail
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_make_failed_output_shape(tmp_path):
|
|
81
|
+
run_dir = tmp_path / "run"
|
|
82
|
+
run_dir.mkdir()
|
|
83
|
+
err = _classify("Nuclear data library does not contain cross sections for Ni58")
|
|
84
|
+
out = make_failed_output("openmc", "eigenvalue_reactor", run_dir, err, unit="msre")
|
|
85
|
+
assert isinstance(out, EngineOutput)
|
|
86
|
+
assert out.status == "failed"
|
|
87
|
+
assert out.engine == "openmc"
|
|
88
|
+
assert out.sim_type == "eigenvalue_reactor"
|
|
89
|
+
assert out.unit == "msre"
|
|
90
|
+
assert out.error is err
|
|
91
|
+
assert out.diagnostics["error_category"] == NUCLEAR_DATA
|
|
92
|
+
assert out.diagnostics["error_source"] == "provider"
|
|
93
|
+
assert out.diagnostics["run_dir"] == str(run_dir.resolve())
|
|
94
|
+
assert out.diagnostics["error"]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_engine_output_roundtrip_serialization(tmp_path):
|
|
98
|
+
err = _classify("Nuclear data library does not contain cross sections for Ni58")
|
|
99
|
+
out = make_failed_output("openmc", "eigenvalue_reactor", tmp_path, err)
|
|
100
|
+
dumped = out.model_dump()
|
|
101
|
+
restored = EngineOutput.model_validate(dumped)
|
|
102
|
+
assert restored.status == "failed"
|
|
103
|
+
assert restored.error is not None
|
|
104
|
+
assert restored.error.category == NUCLEAR_DATA
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{processforge-0.3.16 → processforge-0.3.17}/src/processforge/providers/base_jacobian_mixin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{processforge-0.3.16 → processforge-0.3.17}/src/processforge/providers/jacobian_contributor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{processforge-0.3.16 → processforge-0.3.17}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|