processforge 0.3.10__tar.gz → 0.3.11__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.10/src/processforge.egg-info → processforge-0.3.11}/PKG-INFO +1 -1
- {processforge-0.3.10 → processforge-0.3.11}/pyproject.toml +1 -1
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/providers/openmc_provider.py +291 -164
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/schemas/openmc/openmc_model.py +33 -2
- {processforge-0.3.10 → processforge-0.3.11/src/processforge.egg-info}/PKG-INFO +1 -1
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge.egg-info/SOURCES.txt +1 -2
- processforge-0.3.11/tests/test_openmc_provider.py +524 -0
- processforge-0.3.10/flowsheets/custom/crimson/MUWS_HB.json +0 -216
- processforge-0.3.10/flowsheets/custom/simple_hyrdaulic_scheme.json +0 -202
- {processforge-0.3.10 → processforge-0.3.11}/LICENSE +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/MANIFEST.in +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/README.md +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/flowsheets/closed-loop-chain.json +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/flowsheets/festim/hydrogen_transport_1d.json +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/flowsheets/hydraulic-chain.json +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/flowsheets/openmc/msre_eigenvalue.json +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/setup.cfg +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/__init__.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/__main__.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/_schema.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/analysis/__init__.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/analysis/dof.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/api/__init__.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/api/serve.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/cli/__init__.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/cli/apply.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/cli/common.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/cli/diagram.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/cli/display.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/cli/export_fmu.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/cli/export_modelica.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/cli/init.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/cli/plan.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/cli/run.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/cli/validate.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/compose.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/__init__.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/backends/__init__.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/backends/base.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/backends/casadi_backend.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/backends/pyomo_backend.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/backends/scipy_backend.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/flowsheet.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/jacobian.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/mixin.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/solver.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/stream_var.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/units/__init__.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/units/flash_eo.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/units/heater_eo.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/units/pipes_eo.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/units/pump_eo.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/units/strainer_eo.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/eo/units/valve_eo.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/flowsheet.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/fmu/__init__.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/fmu/_fmi_vars.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/fmu/builder.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/fmu/slave_template.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/lock.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/modelica/__init__.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/modelica/mo_writer.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/modelica/omc_runner.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/modelica/transpiler.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/modelica/unit_equations.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/pcl/__init__.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/pcl/compiler.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/pcl/namespace.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/provenance.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/providers/__init__.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/providers/base.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/providers/base_jacobian_mixin.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/providers/cantera_jacobian.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/providers/cantera_provider.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/providers/container_client.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/providers/coolprop_provider.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/providers/festim_provider.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/providers/jacobian_contributor.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/providers/manager.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/providers/modelica_jacobian.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/providers/modelica_provider.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/providers/reference_state_registry.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/providers/registry.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/result.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/result_schema.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/schemas/__init__.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/schemas/flowsheet_schema.json +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/simulate.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/solver.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/state.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/thermo.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/types.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/units/__init__.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/units/base.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/units/cstr.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/units/flash.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/units/heater.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/units/pfr.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/units/pipes.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/units/pump.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/units/registry.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/units/solver_unit.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/units/strainer.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/units/tank.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/units/valve.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/utils/__init__.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/utils/flowsheet_diagram.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/utils/mermaid_diagram.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/utils/s3_upload.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/utils/topology.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/utils/unit_consistency.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/utils/validate_flowsheet.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge/utils/validation.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge.egg-info/dependency_links.txt +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge.egg-info/entry_points.txt +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge.egg-info/requires.txt +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/src/processforge.egg-info/top_level.txt +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/tests/test_cli.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/tests/test_festim_provider.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/tests/test_init.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/tests/test_providers.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/tests/test_s3_upload.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/tests/test_state.py +0 -0
- {processforge-0.3.10 → processforge-0.3.11}/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.11"
|
|
8
8
|
description = "A Python-based process simulation framework for chemical engineering applications."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "BSD-3-Clause"
|
|
@@ -38,6 +38,7 @@ import math
|
|
|
38
38
|
import os
|
|
39
39
|
import pathlib
|
|
40
40
|
import tempfile
|
|
41
|
+
import threading
|
|
41
42
|
from abc import ABC, abstractmethod
|
|
42
43
|
from typing import TYPE_CHECKING, Optional
|
|
43
44
|
|
|
@@ -45,7 +46,12 @@ from loguru import logger
|
|
|
45
46
|
|
|
46
47
|
from .base import AbstractProvider
|
|
47
48
|
from .registry import register_provider
|
|
48
|
-
from processforge.schemas.openmc.openmc_model import
|
|
49
|
+
from processforge.schemas.openmc.openmc_model import (
|
|
50
|
+
MeshTallyConfig,
|
|
51
|
+
RunMode,
|
|
52
|
+
SolverConfig,
|
|
53
|
+
SourcePoint,
|
|
54
|
+
)
|
|
49
55
|
|
|
50
56
|
|
|
51
57
|
def _resolve_omc_path(path: Optional[str]) -> Optional[str]:
|
|
@@ -54,6 +60,7 @@ def _resolve_omc_path(path: Optional[str]) -> Optional[str]:
|
|
|
54
60
|
return None
|
|
55
61
|
return os.path.expandvars(path)
|
|
56
62
|
|
|
63
|
+
|
|
57
64
|
if TYPE_CHECKING:
|
|
58
65
|
from processforge.types import (
|
|
59
66
|
FlowsheetConfig,
|
|
@@ -68,10 +75,18 @@ if TYPE_CHECKING:
|
|
|
68
75
|
OpenMCSolverConfig = SolverConfig
|
|
69
76
|
|
|
70
77
|
|
|
78
|
+
# Serializes OpenMC runs. Every run mutates process-global state (the
|
|
79
|
+
# OPENMC_CROSS_SECTIONS env var read by the spawned `openmc` subprocess), so
|
|
80
|
+
# concurrent runs sharing a process — e.g. the provider HTTP server threadpool —
|
|
81
|
+
# must not interleave.
|
|
82
|
+
_OPENMC_RUN_LOCK = threading.Lock()
|
|
83
|
+
|
|
84
|
+
|
|
71
85
|
# ---------------------------------------------------------------------------
|
|
72
86
|
# Build helpers
|
|
73
87
|
# ---------------------------------------------------------------------------
|
|
74
88
|
|
|
89
|
+
|
|
75
90
|
class OpenMCBuildHelpers:
|
|
76
91
|
"""Shared OpenMC model-building utilities injected into sim strategies.
|
|
77
92
|
|
|
@@ -150,7 +165,13 @@ class OpenMCBuildHelpers:
|
|
|
150
165
|
settings.batches = solver_cfg.batches
|
|
151
166
|
settings.inactive = solver_cfg.inactive
|
|
152
167
|
settings.particles = solver_cfg.particles
|
|
153
|
-
|
|
168
|
+
# `run_mode` may be a RunMode enum (validated config) or a raw string
|
|
169
|
+
# (strategies apply it via `model_copy(update=...)`, which pydantic v2
|
|
170
|
+
# does not re-validate). Normalise to a plain string for OpenMC.
|
|
171
|
+
run_mode = solver_cfg.run_mode
|
|
172
|
+
settings.run_mode = (
|
|
173
|
+
run_mode.value if isinstance(run_mode, RunMode) else str(run_mode)
|
|
174
|
+
)
|
|
154
175
|
settings.source = [source]
|
|
155
176
|
if solver_cfg.temperature_default is not None:
|
|
156
177
|
settings.temperature = {"default": solver_cfg.temperature_default}
|
|
@@ -161,6 +182,7 @@ class OpenMCBuildHelpers:
|
|
|
161
182
|
# Simulation type strategy registry
|
|
162
183
|
# ---------------------------------------------------------------------------
|
|
163
184
|
|
|
185
|
+
|
|
164
186
|
class OpenMCSimStrategy(ABC):
|
|
165
187
|
"""Base class for an OpenMC simulation type.
|
|
166
188
|
|
|
@@ -227,14 +249,18 @@ def get_registered_sim_types() -> dict[str, type]:
|
|
|
227
249
|
# Built-in strategies
|
|
228
250
|
# ---------------------------------------------------------------------------
|
|
229
251
|
|
|
230
|
-
class _FixedSourcePointStrategy(OpenMCSimStrategy):
|
|
231
|
-
"""Fixed-source point-source approximation using a simple CSG sphere geometry.
|
|
232
252
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
253
|
+
class _PointSourceSphereStrategy(OpenMCSimStrategy):
|
|
254
|
+
"""Shared build logic for CSG-sphere point-source simulations.
|
|
255
|
+
|
|
256
|
+
Models the plant as a single point source inside a homogeneous sphere of a
|
|
257
|
+
given radius and material. Subclasses differ only in ``sim_type``
|
|
258
|
+
(error/source-seed strings) and ``run_mode``. No DAGMC file required.
|
|
236
259
|
"""
|
|
237
260
|
|
|
261
|
+
sim_type: str = "point_source_csg"
|
|
262
|
+
run_mode: str = "eigenvalue"
|
|
263
|
+
|
|
238
264
|
def build(
|
|
239
265
|
self,
|
|
240
266
|
openmc,
|
|
@@ -244,7 +270,7 @@ class _FixedSourcePointStrategy(OpenMCSimStrategy):
|
|
|
244
270
|
) -> tuple:
|
|
245
271
|
if solver_cfg.source_point is None:
|
|
246
272
|
raise ValueError(
|
|
247
|
-
"sim_type='
|
|
273
|
+
f"sim_type='{self.sim_type}' requires 'source_point' in solver_config"
|
|
248
274
|
)
|
|
249
275
|
|
|
250
276
|
fill_name = solver_cfg.point_source_material
|
|
@@ -256,24 +282,42 @@ class _FixedSourcePointStrategy(OpenMCSimStrategy):
|
|
|
256
282
|
|
|
257
283
|
omc_materials = openmc.Materials(list(materials_map.values()))
|
|
258
284
|
|
|
259
|
-
sphere = openmc.Sphere(
|
|
285
|
+
sphere = openmc.Sphere(
|
|
286
|
+
r=solver_cfg.point_source_sphere_radius, boundary_type="vacuum"
|
|
287
|
+
)
|
|
260
288
|
fill_mat = materials_map[fill_name] if fill_name else None
|
|
261
|
-
geometry = openmc.Geometry(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
289
|
+
geometry = openmc.Geometry(
|
|
290
|
+
[
|
|
291
|
+
openmc.Cell(fill=fill_mat, region=-sphere),
|
|
292
|
+
openmc.Cell(region=+sphere),
|
|
293
|
+
]
|
|
294
|
+
)
|
|
265
295
|
|
|
266
296
|
source = helpers.build_point_source(openmc, solver_cfg.source_point)
|
|
267
|
-
|
|
268
|
-
settings = helpers.build_settings(openmc,
|
|
297
|
+
run_cfg = solver_cfg.model_copy(update={"run_mode": self.run_mode})
|
|
298
|
+
settings = helpers.build_settings(openmc, run_cfg, source)
|
|
269
299
|
|
|
270
|
-
tally_objs = [
|
|
300
|
+
tally_objs = [
|
|
301
|
+
helpers.build_mesh_tally(openmc, t) for t in solver_cfg.mesh_tallies
|
|
302
|
+
]
|
|
271
303
|
tallies = openmc.Tallies(tally_objs) if tally_objs else openmc.Tallies()
|
|
272
304
|
|
|
273
305
|
return omc_materials, geometry, settings, tallies
|
|
274
306
|
|
|
275
307
|
|
|
276
|
-
class
|
|
308
|
+
class _FixedSourcePointStrategy(_PointSourceSphereStrategy):
|
|
309
|
+
"""Fixed-source point-source approximation using a simple CSG sphere geometry.
|
|
310
|
+
|
|
311
|
+
No DAGMC file required. Models the plant as a single point source inside
|
|
312
|
+
a homogeneous sphere of a given radius and material — useful for broad-scale
|
|
313
|
+
dose/flux estimates where full CAD geometry is not needed.
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
sim_type = "fixed_source_point"
|
|
317
|
+
run_mode = "fixed source"
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class _EigenvalueCSGStrategy(_PointSourceSphereStrategy):
|
|
277
321
|
"""Eigenvalue (criticality) simulation using a simple CSG sphere geometry.
|
|
278
322
|
|
|
279
323
|
Computes k_eff for a homogeneous sphere of fissile material. The initial
|
|
@@ -284,42 +328,8 @@ class _EigenvalueCSGStrategy(OpenMCSimStrategy):
|
|
|
284
328
|
salt or other homogeneous fissile compositions.
|
|
285
329
|
"""
|
|
286
330
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
openmc,
|
|
290
|
-
solver_cfg: SolverConfig,
|
|
291
|
-
materials_map: dict,
|
|
292
|
-
helpers: OpenMCBuildHelpers,
|
|
293
|
-
) -> tuple:
|
|
294
|
-
if solver_cfg.source_point is None:
|
|
295
|
-
raise ValueError(
|
|
296
|
-
"sim_type='eigenvalue_csg' requires 'source_point' in solver_config"
|
|
297
|
-
)
|
|
298
|
-
|
|
299
|
-
fill_name = solver_cfg.point_source_material
|
|
300
|
-
if fill_name is not None and fill_name not in materials_map:
|
|
301
|
-
raise ValueError(
|
|
302
|
-
f"point_source_material='{fill_name}' not found in materials map. "
|
|
303
|
-
f"Available: {sorted(materials_map)}"
|
|
304
|
-
)
|
|
305
|
-
|
|
306
|
-
omc_materials = openmc.Materials(list(materials_map.values()))
|
|
307
|
-
|
|
308
|
-
sphere = openmc.Sphere(r=solver_cfg.point_source_sphere_radius, boundary_type="vacuum")
|
|
309
|
-
fill_mat = materials_map[fill_name] if fill_name else None
|
|
310
|
-
geometry = openmc.Geometry([
|
|
311
|
-
openmc.Cell(fill=fill_mat, region=-sphere),
|
|
312
|
-
openmc.Cell(region=+sphere),
|
|
313
|
-
])
|
|
314
|
-
|
|
315
|
-
source = helpers.build_point_source(openmc, solver_cfg.source_point)
|
|
316
|
-
eigenvalue_cfg = solver_cfg.model_copy(update={"run_mode": "eigenvalue"})
|
|
317
|
-
settings = helpers.build_settings(openmc, eigenvalue_cfg, source)
|
|
318
|
-
|
|
319
|
-
tally_objs = [helpers.build_mesh_tally(openmc, t) for t in solver_cfg.mesh_tallies]
|
|
320
|
-
tallies = openmc.Tallies(tally_objs) if tally_objs else openmc.Tallies()
|
|
321
|
-
|
|
322
|
-
return omc_materials, geometry, settings, tallies
|
|
331
|
+
sim_type = "eigenvalue_csg"
|
|
332
|
+
run_mode = "eigenvalue"
|
|
323
333
|
|
|
324
334
|
|
|
325
335
|
# Register built-ins at module load
|
|
@@ -331,15 +341,16 @@ register_openmc_sim_type("eigenvalue_csg", _EigenvalueCSGStrategy)
|
|
|
331
341
|
# Material validation constants
|
|
332
342
|
# ---------------------------------------------------------------------------
|
|
333
343
|
|
|
334
|
-
_VALID_DENSITY_UNITS = frozenset(
|
|
335
|
-
"g/cm3", "g/cc", "kg/m3", "atom/b-cm", "atom/cm3", "sum", "macro"
|
|
336
|
-
|
|
344
|
+
_VALID_DENSITY_UNITS = frozenset(
|
|
345
|
+
{"g/cm3", "g/cc", "kg/m3", "atom/b-cm", "atom/cm3", "sum", "macro"}
|
|
346
|
+
)
|
|
337
347
|
|
|
338
348
|
|
|
339
349
|
# ---------------------------------------------------------------------------
|
|
340
350
|
# OpenMCProvider
|
|
341
351
|
# ---------------------------------------------------------------------------
|
|
342
352
|
|
|
353
|
+
|
|
343
354
|
class OpenMCProvider(AbstractProvider):
|
|
344
355
|
"""Monte Carlo neutronics provider backed by OpenMC.
|
|
345
356
|
|
|
@@ -347,18 +358,22 @@ class OpenMCProvider(AbstractProvider):
|
|
|
347
358
|
----------------
|
|
348
359
|
* **Material registry** — ``initialize()`` loads all material defs from the
|
|
349
360
|
flowsheet and indexes them by ``id`` and name.
|
|
350
|
-
* **Working-directory management** —
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
361
|
+
* **Working-directory management** — each run executes in
|
|
362
|
+
``<provider_output_dir>`` via ``openmc.model.Model.run(cwd=...)`` — no
|
|
363
|
+
process-wide ``chdir``. Falls back to a temp dir when the configured
|
|
364
|
+
output dir is not writable.
|
|
354
365
|
* **Cross-section override** — If ``provider_config.cross_sections`` or
|
|
355
366
|
``solver_cfg.cross_sections`` is set, ``OPENMC_CROSS_SECTIONS`` is
|
|
356
|
-
temporarily overridden.
|
|
367
|
+
temporarily overridden. Runs are serialized behind a module-wide lock so
|
|
368
|
+
the process-global env var cannot race across concurrent requests.
|
|
357
369
|
* **Simulation dispatch** — ``run_simulation()`` looks up the strategy from
|
|
358
|
-
``_SIM_TYPE_REGISTRY`` by ``sim_type``, calls ``strategy.build()``,
|
|
359
|
-
|
|
370
|
+
``_SIM_TYPE_REGISTRY`` by ``sim_type``, calls ``strategy.build()``, runs
|
|
371
|
+
via ``Model.run(cwd=...)``, then parses the returned statepoint.
|
|
372
|
+
Failures are returned as ``SimulationResult(status="failed")`` with
|
|
373
|
+
``run_dir`` and error metadata rather than silently bubbling up.
|
|
360
374
|
* **Result extraction** — extracts ``k_eff`` (eigenvalue mode) and per-tally
|
|
361
|
-
aggregate scalars (mean totals, representative std devs).
|
|
375
|
+
aggregate scalars (mean totals, representative std devs). Extraction
|
|
376
|
+
issues surface in ``metadata["tally_warnings"]``.
|
|
362
377
|
"""
|
|
363
378
|
|
|
364
379
|
def __init__(self):
|
|
@@ -392,10 +407,31 @@ class OpenMCProvider(AbstractProvider):
|
|
|
392
407
|
out_dir = os.path.join(root, out_dir)
|
|
393
408
|
self._provider_output_dir = out_dir
|
|
394
409
|
self._cross_sections = provider_config.cross_sections
|
|
395
|
-
self._materials = {}
|
|
396
410
|
|
|
411
|
+
# Material IDs must be unique — duplicate IDs were previously dropped
|
|
412
|
+
# silently, causing missing-material KeyErrors deep in the run.
|
|
413
|
+
id_to_name: dict = {}
|
|
414
|
+
duplicates = []
|
|
397
415
|
for mat_name, mat_def in flowsheet_config.materials.items():
|
|
398
|
-
|
|
416
|
+
mid = mat_def.id
|
|
417
|
+
if mid in id_to_name:
|
|
418
|
+
duplicates.append(
|
|
419
|
+
f"id={mid} shared by '{id_to_name[mid]}' and '{mat_name}'"
|
|
420
|
+
)
|
|
421
|
+
else:
|
|
422
|
+
id_to_name[mid] = mat_name
|
|
423
|
+
if duplicates:
|
|
424
|
+
raise ValueError(
|
|
425
|
+
"Flowsheet materials have duplicate OpenMC ids: "
|
|
426
|
+
+ "; ".join(duplicates)
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
# Fail fast on a bad cross-section path instead of deep inside openmc.
|
|
430
|
+
resolved_xs = _resolve_omc_path(self._cross_sections)
|
|
431
|
+
if resolved_xs:
|
|
432
|
+
self._check_cross_sections_path(resolved_xs)
|
|
433
|
+
|
|
434
|
+
self._materials = dict(flowsheet_config.materials.items())
|
|
399
435
|
|
|
400
436
|
self._initialized = True
|
|
401
437
|
n_mats = len({v.id for v in self._materials.values()})
|
|
@@ -405,7 +441,9 @@ class OpenMCProvider(AbstractProvider):
|
|
|
405
441
|
)
|
|
406
442
|
|
|
407
443
|
def get_thermo_properties(self, stream: dict) -> dict:
|
|
408
|
-
raise NotImplementedError(
|
|
444
|
+
raise NotImplementedError(
|
|
445
|
+
"OpenMCProvider does not support stream thermodynamics."
|
|
446
|
+
)
|
|
409
447
|
|
|
410
448
|
def compute_unit(self, unit_type: str, config: dict, inlet: dict):
|
|
411
449
|
"""Return ``None`` — OpenMC uses ``run_simulation`` via ``SolverUnit``."""
|
|
@@ -421,18 +459,22 @@ class OpenMCProvider(AbstractProvider):
|
|
|
421
459
|
|
|
422
460
|
Rules
|
|
423
461
|
-----
|
|
424
|
-
* ``density``
|
|
462
|
+
* ``density`` is required unless ``density_units`` is ``"sum"``.
|
|
425
463
|
* ``density_units`` must be one of the OpenMC-recognised strings.
|
|
426
464
|
* At least one of ``nuclides`` or ``elements`` must be non-empty.
|
|
465
|
+
* Each ``nuclides`` entry needs ``name`` + numeric ``percent``;
|
|
466
|
+
each ``elements`` entry needs ``element`` + numeric ``percent``.
|
|
467
|
+
* ``percent_type`` must be ``"ao"`` or ``"wo"``.
|
|
427
468
|
|
|
428
469
|
Returns:
|
|
429
470
|
List of error strings (empty = valid).
|
|
430
471
|
"""
|
|
431
472
|
errors = []
|
|
432
473
|
|
|
433
|
-
if mat_def.density is None:
|
|
474
|
+
if mat_def.density is None and mat_def.density_units != "sum":
|
|
434
475
|
errors.append(
|
|
435
|
-
f"Material '{mat_name}' is missing 'density' (required for OpenMC
|
|
476
|
+
f"Material '{mat_name}' is missing 'density' (required for OpenMC; "
|
|
477
|
+
"only 'sum' density_units may omit it)."
|
|
436
478
|
)
|
|
437
479
|
if mat_def.density_units is None:
|
|
438
480
|
errors.append(
|
|
@@ -452,22 +494,62 @@ class OpenMCProvider(AbstractProvider):
|
|
|
452
494
|
"Add at least one 'nuclides' or 'elements' entry."
|
|
453
495
|
)
|
|
454
496
|
|
|
497
|
+
errors.extend(
|
|
498
|
+
cls._validate_component_entries(mat_name, "nuclides", mat_def.nuclides)
|
|
499
|
+
)
|
|
500
|
+
errors.extend(
|
|
501
|
+
cls._validate_component_entries(
|
|
502
|
+
mat_name, "elements", mat_def.extra.get("elements", [])
|
|
503
|
+
)
|
|
504
|
+
)
|
|
505
|
+
|
|
455
506
|
return errors
|
|
456
507
|
|
|
457
|
-
|
|
508
|
+
@staticmethod
|
|
509
|
+
def _validate_component_entries(mat_name: str, kind: str, entries: list) -> list:
|
|
510
|
+
"""Validate each nuclide/element component entry in a material."""
|
|
511
|
+
label_key = "name" if kind == "nuclides" else "element"
|
|
512
|
+
errors = []
|
|
513
|
+
for i, entry in enumerate(entries or []):
|
|
514
|
+
if not isinstance(entry, dict):
|
|
515
|
+
errors.append(
|
|
516
|
+
f"Material '{mat_name}' {kind}[{i}] must be an object, "
|
|
517
|
+
f"got {type(entry).__name__}."
|
|
518
|
+
)
|
|
519
|
+
continue
|
|
520
|
+
if not entry.get(label_key):
|
|
521
|
+
errors.append(
|
|
522
|
+
f"Material '{mat_name}' {kind}[{i}] is missing '{label_key}'."
|
|
523
|
+
)
|
|
524
|
+
if not isinstance(entry.get("percent"), (int, float)):
|
|
525
|
+
errors.append(
|
|
526
|
+
f"Material '{mat_name}' {kind}[{i}] must have a numeric 'percent'."
|
|
527
|
+
)
|
|
528
|
+
ptype = entry.get("percent_type", "ao")
|
|
529
|
+
if ptype not in ("ao", "wo"):
|
|
530
|
+
errors.append(
|
|
531
|
+
f"Material '{mat_name}' {kind}[{i}] percent_type must be "
|
|
532
|
+
f"'ao' or 'wo', got '{ptype}'."
|
|
533
|
+
)
|
|
534
|
+
return errors
|
|
535
|
+
|
|
536
|
+
def run_simulation(
|
|
537
|
+
self, unit_config: "UnitConfig", inlet: dict
|
|
538
|
+
) -> "SimulationResult":
|
|
458
539
|
"""Build and run an OpenMC simulation from a typed ``UnitConfig``.
|
|
459
540
|
|
|
460
541
|
Execution flow
|
|
461
542
|
--------------
|
|
462
|
-
1. Parse ``solver_config`` → ``
|
|
543
|
+
1. Parse ``solver_config`` → ``SolverConfig``
|
|
463
544
|
2. Look up strategy from ``_SIM_TYPE_REGISTRY``
|
|
464
545
|
3. Build ``openmc.Material`` objects for all materials in the registry
|
|
465
546
|
4. Call ``strategy.build()`` → ``(materials, geometry, settings, tallies)``
|
|
466
|
-
5.
|
|
467
|
-
6. Apply cross-section
|
|
468
|
-
7.
|
|
469
|
-
8. Parse statepoint → extract k_eff and tally scalars
|
|
470
|
-
9.
|
|
547
|
+
5. Resolve the run directory (with temp-dir fallback)
|
|
548
|
+
6. Apply the cross-section override inside a module-wide lock
|
|
549
|
+
7. Run via ``openmc.model.Model.run(cwd=run_dir, ...)`` (no ``chdir``)
|
|
550
|
+
8. Parse the returned statepoint → extract k_eff and tally scalars
|
|
551
|
+
9. Return ``SimulationResult``; failures surface as ``status="failed"``
|
|
552
|
+
with ``run_dir`` and error diagnostics in ``metadata``
|
|
471
553
|
"""
|
|
472
554
|
import openmc
|
|
473
555
|
from processforge.types import SimulationResult
|
|
@@ -501,54 +583,67 @@ class OpenMCProvider(AbstractProvider):
|
|
|
501
583
|
f"{solver_cfg.batches} batches × {solver_cfg.particles} particles"
|
|
502
584
|
)
|
|
503
585
|
|
|
504
|
-
omc_materials, geometry, settings, tallies = (
|
|
505
|
-
|
|
586
|
+
omc_materials, geometry, settings, tallies = strategy_cls().build(
|
|
587
|
+
openmc, solver_cfg, materials_map, helpers
|
|
506
588
|
)
|
|
507
589
|
|
|
508
|
-
|
|
509
|
-
run_dir = pathlib.Path(self._provider_output_dir)
|
|
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
|
-
)
|
|
522
|
-
original_cwd = pathlib.Path.cwd()
|
|
590
|
+
run_dir = self._resolve_run_dir()
|
|
523
591
|
|
|
524
|
-
# --- Cross-section override ---
|
|
525
592
|
xs_path = _resolve_omc_path(solver_cfg.cross_sections or self._cross_sections)
|
|
526
|
-
original_xs = os.environ.get("OPENMC_CROSS_SECTIONS")
|
|
527
593
|
if xs_path:
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
try:
|
|
531
|
-
os.chdir(run_dir)
|
|
532
|
-
|
|
533
|
-
# Export model XML files
|
|
534
|
-
omc_materials.export_to_xml()
|
|
535
|
-
geometry.export_to_xml()
|
|
536
|
-
settings.export_to_xml()
|
|
537
|
-
if tallies:
|
|
538
|
-
tallies.export_to_xml()
|
|
539
|
-
|
|
540
|
-
openmc.run()
|
|
541
|
-
logger.info(f"OpenMCProvider: '{sim_type}' completed in '{run_dir}'")
|
|
542
|
-
|
|
543
|
-
scalars, metadata = self._extract_results(openmc, solver_cfg, run_dir)
|
|
594
|
+
self._check_cross_sections_path(xs_path)
|
|
544
595
|
|
|
545
|
-
|
|
546
|
-
|
|
596
|
+
# Runs spawn a fresh `openmc` subprocess that reads OPENMC_CROSS_SECTIONS
|
|
597
|
+
# from the environment. That env var is process-global, so the whole
|
|
598
|
+
# export+run window is serialized (see module-level comment).
|
|
599
|
+
with _OPENMC_RUN_LOCK:
|
|
600
|
+
original_xs = os.environ.get("OPENMC_CROSS_SECTIONS")
|
|
547
601
|
if xs_path:
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
602
|
+
os.environ["OPENMC_CROSS_SECTIONS"] = str(xs_path)
|
|
603
|
+
try:
|
|
604
|
+
model = openmc.model.Model(
|
|
605
|
+
geometry=geometry,
|
|
606
|
+
materials=omc_materials,
|
|
607
|
+
settings=settings,
|
|
608
|
+
tallies=tallies,
|
|
609
|
+
)
|
|
610
|
+
statepoint_path = model.run(cwd=str(run_dir), export_model_xml=False)
|
|
611
|
+
logger.info(f"OpenMCProvider: '{sim_type}' completed in '{run_dir}'")
|
|
612
|
+
except Exception as exc: # noqa: BLE001
|
|
613
|
+
logger.exception(
|
|
614
|
+
f"OpenMCProvider: '{sim_type}' failed in '{run_dir}': {exc}"
|
|
615
|
+
)
|
|
616
|
+
return SimulationResult(
|
|
617
|
+
status="failed",
|
|
618
|
+
sim_type=sim_type,
|
|
619
|
+
scalars={},
|
|
620
|
+
metadata={
|
|
621
|
+
"run_dir": str(run_dir.resolve()),
|
|
622
|
+
"error": str(exc),
|
|
623
|
+
},
|
|
624
|
+
)
|
|
625
|
+
finally:
|
|
626
|
+
if xs_path:
|
|
627
|
+
if original_xs is None:
|
|
628
|
+
os.environ.pop("OPENMC_CROSS_SECTIONS", None)
|
|
629
|
+
else:
|
|
630
|
+
os.environ["OPENMC_CROSS_SECTIONS"] = original_xs
|
|
631
|
+
|
|
632
|
+
scalars, metadata = self._extract_results(
|
|
633
|
+
openmc, solver_cfg, statepoint_path, run_dir
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
if statepoint_path is None:
|
|
637
|
+
logger.warning(
|
|
638
|
+
f"OpenMCProvider: '{sim_type}' produced no statepoint in '{run_dir}'."
|
|
639
|
+
)
|
|
640
|
+
metadata.setdefault("error", "run produced no statepoint file")
|
|
641
|
+
return SimulationResult(
|
|
642
|
+
status="failed",
|
|
643
|
+
sim_type=sim_type,
|
|
644
|
+
scalars=scalars,
|
|
645
|
+
metadata=metadata,
|
|
646
|
+
)
|
|
552
647
|
|
|
553
648
|
return SimulationResult(
|
|
554
649
|
status="completed",
|
|
@@ -557,10 +652,41 @@ class OpenMCProvider(AbstractProvider):
|
|
|
557
652
|
metadata=metadata,
|
|
558
653
|
)
|
|
559
654
|
|
|
655
|
+
def _resolve_run_dir(self) -> pathlib.Path:
|
|
656
|
+
"""Create the run output directory, falling back to a temp dir on failure.
|
|
657
|
+
|
|
658
|
+
The configured output dir (often the mounted /data volume) may not be
|
|
659
|
+
writable by the container user. Fall back to a temp dir so the
|
|
660
|
+
simulation can still run; only the persisted XML artifacts are lost.
|
|
661
|
+
"""
|
|
662
|
+
run_dir = pathlib.Path(self._provider_output_dir)
|
|
663
|
+
try:
|
|
664
|
+
run_dir.mkdir(parents=True, exist_ok=True)
|
|
665
|
+
except (PermissionError, OSError) as exc:
|
|
666
|
+
run_dir = pathlib.Path(tempfile.mkdtemp(prefix="processforge_openmc_"))
|
|
667
|
+
logger.warning(
|
|
668
|
+
f"OpenMCProvider: cannot use output dir "
|
|
669
|
+
f"'{self._provider_output_dir}' ({exc}); "
|
|
670
|
+
f"falling back to '{run_dir}'."
|
|
671
|
+
)
|
|
672
|
+
return run_dir
|
|
673
|
+
|
|
674
|
+
@staticmethod
|
|
675
|
+
def _check_cross_sections_path(xs_path: str) -> None:
|
|
676
|
+
"""Fail fast if a cross-section path does not exist."""
|
|
677
|
+
if pathlib.Path(xs_path).is_file():
|
|
678
|
+
return
|
|
679
|
+
raise RuntimeError(
|
|
680
|
+
f"OpenMC cross-sections file not found at '{xs_path}'. "
|
|
681
|
+
"Set a valid OPENMC_CROSS_SECTIONS / provider 'cross_sections' / "
|
|
682
|
+
"solver_config 'cross_sections' path."
|
|
683
|
+
)
|
|
684
|
+
|
|
560
685
|
def _extract_results(
|
|
561
686
|
self,
|
|
562
687
|
openmc,
|
|
563
688
|
solver_cfg: SolverConfig,
|
|
689
|
+
statepoint_path,
|
|
564
690
|
run_dir: pathlib.Path,
|
|
565
691
|
) -> tuple:
|
|
566
692
|
"""Parse the statepoint file and return ``(scalars_dict, metadata_dict)``.
|
|
@@ -572,65 +698,66 @@ class OpenMCProvider(AbstractProvider):
|
|
|
572
698
|
``tally_{id}_{score}_mean_total`` — sum of all bin means
|
|
573
699
|
``tally_{id}_{score}_std_dev`` — RMS of per-bin std devs
|
|
574
700
|
|
|
575
|
-
``metadata["
|
|
576
|
-
|
|
701
|
+
Extraction issues are collected in ``metadata["tally_warnings"]`` so
|
|
702
|
+
callers can observe degraded (but not failed) results. ``metadata``
|
|
703
|
+
always carries ``run_dir``; ``metadata["statepoint_path"]`` holds the
|
|
704
|
+
absolute HDF5 path for downstream post-processing.
|
|
577
705
|
"""
|
|
578
|
-
import glob as _glob
|
|
579
|
-
|
|
580
706
|
scalars: dict = {}
|
|
581
707
|
metadata: dict = {"run_dir": str(run_dir.resolve())}
|
|
708
|
+
warnings: list = []
|
|
582
709
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
logger.warning(
|
|
587
|
-
f"OpenMCProvider: statepoint file not found at '{sp_pattern}'. "
|
|
588
|
-
"Scalars will be empty."
|
|
589
|
-
)
|
|
710
|
+
if statepoint_path is None:
|
|
711
|
+
warnings.append("run produced no statepoint file")
|
|
712
|
+
metadata["tally_warnings"] = warnings
|
|
590
713
|
return scalars, metadata
|
|
591
714
|
|
|
592
|
-
|
|
593
|
-
metadata["statepoint_path"] = str(pathlib.Path(sp_path).resolve())
|
|
594
|
-
|
|
595
|
-
sp = openmc.StatePoint(sp_path)
|
|
715
|
+
metadata["statepoint_path"] = str(pathlib.Path(statepoint_path).resolve())
|
|
596
716
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
# Tally scalars
|
|
607
|
-
for tally_cfg in solver_cfg.mesh_tallies:
|
|
608
|
-
try:
|
|
609
|
-
tally = sp.get_tally(id=tally_cfg.tally_id)
|
|
610
|
-
except Exception: # noqa: BLE001
|
|
611
|
-
logger.warning(
|
|
612
|
-
f"OpenMCProvider: tally id={tally_cfg.tally_id} not found "
|
|
613
|
-
"in statepoint. Skipping."
|
|
717
|
+
sp = openmc.StatePoint(statepoint_path)
|
|
718
|
+
try:
|
|
719
|
+
# k_eff — only present in eigenvalue mode
|
|
720
|
+
if sp.keff is not None:
|
|
721
|
+
scalars["k_eff"] = float(sp.keff.n)
|
|
722
|
+
scalars["k_eff_std_dev"] = float(sp.keff.s)
|
|
723
|
+
logger.info(
|
|
724
|
+
f"OpenMCProvider: k_eff = {scalars['k_eff']:.6f} "
|
|
725
|
+
f"+/- {scalars['k_eff_std_dev']:.6f}"
|
|
614
726
|
)
|
|
615
|
-
|
|
727
|
+
elif solver_cfg.run_mode == RunMode.eigenvalue:
|
|
728
|
+
warnings.append("eigenvalue run produced no k_eff in statepoint")
|
|
616
729
|
|
|
617
|
-
for
|
|
730
|
+
for tally_cfg in solver_cfg.mesh_tallies:
|
|
618
731
|
try:
|
|
619
|
-
|
|
620
|
-
means = df["mean"].values
|
|
621
|
-
std_devs = df["std. dev."].values
|
|
622
|
-
key_prefix = f"tally_{tally_cfg.tally_id}_{score}"
|
|
623
|
-
scalars[f"{key_prefix}_mean_total"] = float(means.sum())
|
|
624
|
-
scalars[f"{key_prefix}_std_dev"] = (
|
|
625
|
-
float(math.sqrt((std_devs ** 2).mean())) if len(std_devs) > 0 else 0.0
|
|
626
|
-
)
|
|
732
|
+
tally = sp.get_tally(id=tally_cfg.tally_id)
|
|
627
733
|
except Exception as exc: # noqa: BLE001
|
|
628
|
-
|
|
629
|
-
f"
|
|
630
|
-
f"from tally {tally_cfg.tally_id}: {exc}"
|
|
734
|
+
warnings.append(
|
|
735
|
+
f"tally id={tally_cfg.tally_id} not found in statepoint: {exc}"
|
|
631
736
|
)
|
|
737
|
+
continue
|
|
738
|
+
|
|
739
|
+
for score in tally_cfg.scores:
|
|
740
|
+
try:
|
|
741
|
+
df = tally.get_pandas_dataframe(scores=[score])
|
|
742
|
+
means = df["mean"].values
|
|
743
|
+
std_devs = df["std. dev."].values
|
|
744
|
+
key_prefix = f"tally_{tally_cfg.tally_id}_{score}"
|
|
745
|
+
scalars[f"{key_prefix}_mean_total"] = float(means.sum())
|
|
746
|
+
scalars[f"{key_prefix}_std_dev"] = (
|
|
747
|
+
float(math.sqrt((std_devs**2).mean()))
|
|
748
|
+
if len(std_devs) > 0
|
|
749
|
+
else 0.0
|
|
750
|
+
)
|
|
751
|
+
except Exception as exc: # noqa: BLE001
|
|
752
|
+
warnings.append(
|
|
753
|
+
f"could not extract score '{score}' from tally "
|
|
754
|
+
f"{tally_cfg.tally_id}: {exc}"
|
|
755
|
+
)
|
|
756
|
+
finally:
|
|
757
|
+
del sp
|
|
632
758
|
|
|
633
|
-
|
|
759
|
+
if warnings:
|
|
760
|
+
metadata["tally_warnings"] = warnings
|
|
634
761
|
return scalars, metadata
|
|
635
762
|
|
|
636
763
|
|