processforge 0.3.14__tar.gz → 0.3.16__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.
Files changed (135) hide show
  1. {processforge-0.3.14/src/processforge.egg-info → processforge-0.3.16}/PKG-INFO +1 -1
  2. processforge-0.3.16/flowsheets/coupled/msre_coupled.json +172 -0
  3. {processforge-0.3.14 → processforge-0.3.16}/flowsheets/openmc/msre_eigenvalue.json +20 -28
  4. {processforge-0.3.14 → processforge-0.3.16}/pyproject.toml +1 -1
  5. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/__init__.py +1 -1
  6. processforge-0.3.16/src/processforge/coupling.py +183 -0
  7. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/flowsheet.py +139 -29
  8. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/providers/container_client.py +1 -1
  9. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/providers/festim_provider.py +1 -4
  10. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/providers/openmc_provider.py +310 -49
  11. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/schemas/festim/festim_model.py +0 -3
  12. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/schemas/openmc/openmc_model.py +116 -36
  13. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/types.py +6 -4
  14. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/units/base.py +11 -2
  15. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/units/solver_unit.py +32 -3
  16. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/utils/validate_flowsheet.py +116 -14
  17. {processforge-0.3.14 → processforge-0.3.16/src/processforge.egg-info}/PKG-INFO +1 -1
  18. {processforge-0.3.14 → processforge-0.3.16}/src/processforge.egg-info/SOURCES.txt +3 -0
  19. processforge-0.3.16/tests/test_coupling.py +251 -0
  20. {processforge-0.3.14 → processforge-0.3.16}/tests/test_festim_provider.py +0 -2
  21. {processforge-0.3.14 → processforge-0.3.16}/tests/test_openmc_provider.py +170 -35
  22. {processforge-0.3.14 → processforge-0.3.16}/tests/test_types.py +4 -4
  23. {processforge-0.3.14 → processforge-0.3.16}/LICENSE +0 -0
  24. {processforge-0.3.14 → processforge-0.3.16}/MANIFEST.in +0 -0
  25. {processforge-0.3.14 → processforge-0.3.16}/README.md +0 -0
  26. {processforge-0.3.14 → processforge-0.3.16}/flowsheets/closed-loop-chain.json +0 -0
  27. {processforge-0.3.14 → processforge-0.3.16}/flowsheets/custom/crimson/MUWS_HB.json +0 -0
  28. {processforge-0.3.14 → processforge-0.3.16}/flowsheets/custom/simple_hyrdaulic_scheme.json +0 -0
  29. {processforge-0.3.14 → processforge-0.3.16}/flowsheets/festim/hydrogen_transport_1d.json +0 -0
  30. {processforge-0.3.14 → processforge-0.3.16}/flowsheets/festim/tds_tungsten.json +0 -0
  31. {processforge-0.3.14 → processforge-0.3.16}/flowsheets/hydraulic-chain.json +0 -0
  32. {processforge-0.3.14 → processforge-0.3.16}/setup.cfg +0 -0
  33. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/__main__.py +0 -0
  34. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/_schema.py +0 -0
  35. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/analysis/__init__.py +0 -0
  36. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/analysis/dof.py +0 -0
  37. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/api/__init__.py +0 -0
  38. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/api/serve.py +0 -0
  39. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/cli/__init__.py +0 -0
  40. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/cli/apply.py +0 -0
  41. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/cli/common.py +0 -0
  42. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/cli/diagram.py +0 -0
  43. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/cli/display.py +0 -0
  44. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/cli/export_fmu.py +0 -0
  45. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/cli/export_modelica.py +0 -0
  46. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/cli/init.py +0 -0
  47. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/cli/plan.py +0 -0
  48. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/cli/run.py +0 -0
  49. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/cli/validate.py +0 -0
  50. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/compose.py +0 -0
  51. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/__init__.py +0 -0
  52. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/backends/__init__.py +0 -0
  53. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/backends/base.py +0 -0
  54. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/backends/casadi_backend.py +0 -0
  55. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/backends/pyomo_backend.py +0 -0
  56. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/backends/scipy_backend.py +0 -0
  57. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/flowsheet.py +0 -0
  58. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/jacobian.py +0 -0
  59. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/mixin.py +0 -0
  60. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/solver.py +0 -0
  61. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/stream_var.py +0 -0
  62. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/units/__init__.py +0 -0
  63. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/units/flash_eo.py +0 -0
  64. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/units/heater_eo.py +0 -0
  65. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/units/pipes_eo.py +0 -0
  66. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/units/pump_eo.py +0 -0
  67. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/units/strainer_eo.py +0 -0
  68. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/eo/units/valve_eo.py +0 -0
  69. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/fmu/__init__.py +0 -0
  70. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/fmu/_fmi_vars.py +0 -0
  71. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/fmu/builder.py +0 -0
  72. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/fmu/slave_template.py +0 -0
  73. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/lock.py +0 -0
  74. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/modelica/__init__.py +0 -0
  75. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/modelica/mo_writer.py +0 -0
  76. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/modelica/omc_runner.py +0 -0
  77. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/modelica/transpiler.py +0 -0
  78. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/modelica/unit_equations.py +0 -0
  79. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/output_collector.py +0 -0
  80. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/pcl/__init__.py +0 -0
  81. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/pcl/compiler.py +0 -0
  82. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/pcl/namespace.py +0 -0
  83. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/persistence/__init__.py +0 -0
  84. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/persistence/archive.py +0 -0
  85. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/persistence/artifact_store.py +0 -0
  86. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/provenance.py +0 -0
  87. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/providers/__init__.py +0 -0
  88. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/providers/base.py +0 -0
  89. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/providers/base_jacobian_mixin.py +0 -0
  90. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/providers/cantera_jacobian.py +0 -0
  91. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/providers/cantera_provider.py +0 -0
  92. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/providers/coolprop_provider.py +0 -0
  93. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/providers/jacobian_contributor.py +0 -0
  94. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/providers/manager.py +0 -0
  95. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/providers/modelica_jacobian.py +0 -0
  96. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/providers/modelica_provider.py +0 -0
  97. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/providers/reference_state_registry.py +0 -0
  98. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/providers/registry.py +0 -0
  99. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/quantity.py +0 -0
  100. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/result.py +0 -0
  101. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/result_schema.py +0 -0
  102. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/schemas/__init__.py +0 -0
  103. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/schemas/flowsheet_schema.json +0 -0
  104. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/simulate.py +0 -0
  105. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/solver.py +0 -0
  106. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/state.py +0 -0
  107. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/thermo.py +0 -0
  108. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/units/__init__.py +0 -0
  109. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/units/cstr.py +0 -0
  110. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/units/flash.py +0 -0
  111. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/units/heater.py +0 -0
  112. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/units/pfr.py +0 -0
  113. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/units/pipes.py +0 -0
  114. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/units/pump.py +0 -0
  115. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/units/registry.py +0 -0
  116. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/units/strainer.py +0 -0
  117. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/units/tank.py +0 -0
  118. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/units/valve.py +0 -0
  119. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/utils/__init__.py +0 -0
  120. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/utils/flowsheet_diagram.py +0 -0
  121. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/utils/mermaid_diagram.py +0 -0
  122. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/utils/s3_upload.py +0 -0
  123. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/utils/topology.py +0 -0
  124. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/utils/unit_consistency.py +0 -0
  125. {processforge-0.3.14 → processforge-0.3.16}/src/processforge/utils/validation.py +0 -0
  126. {processforge-0.3.14 → processforge-0.3.16}/src/processforge.egg-info/dependency_links.txt +0 -0
  127. {processforge-0.3.14 → processforge-0.3.16}/src/processforge.egg-info/entry_points.txt +0 -0
  128. {processforge-0.3.14 → processforge-0.3.16}/src/processforge.egg-info/requires.txt +0 -0
  129. {processforge-0.3.14 → processforge-0.3.16}/src/processforge.egg-info/top_level.txt +0 -0
  130. {processforge-0.3.14 → processforge-0.3.16}/tests/test_cli.py +0 -0
  131. {processforge-0.3.14 → processforge-0.3.16}/tests/test_festim_schema.py +0 -0
  132. {processforge-0.3.14 → processforge-0.3.16}/tests/test_init.py +0 -0
  133. {processforge-0.3.14 → processforge-0.3.16}/tests/test_providers.py +0 -0
  134. {processforge-0.3.14 → processforge-0.3.16}/tests/test_s3_upload.py +0 -0
  135. {processforge-0.3.14 → processforge-0.3.16}/tests/test_state.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: processforge
3
- Version: 0.3.14
3
+ Version: 0.3.16
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
@@ -0,0 +1,172 @@
1
+ {
2
+ "metadata": {
3
+ "name": "MSRE coupled neutronics + thermal (CoolProp thermo source)",
4
+ "description": "Demonstrates cross-engine parameter coupling using the per-unit 'inputs' block. A CoolProp-backed heater produces the 'coolant' stream; its temperature feeds both the OpenMC reactor material temperature and the FESTIM thermal model. The OpenMC reactor's fission power then drives a second OpenMC fixed-source unit, exercising the iterative coupling resolver (units are resolved in topological order and converged over iterations).",
5
+ "version": "1.0"
6
+ },
7
+ "providers": {
8
+ "openmc": {
9
+ "type": "openmc",
10
+ "url": "http://localhost:9001",
11
+ "output_dir": "openmc/msre_coupled_run",
12
+ "cross_sections": "${OPENMC_DATA_ROOT}/cross_sections/cross_sections.xml"
13
+ },
14
+ "festim": {
15
+ "type": "festim",
16
+ "url": "http://localhost:9002",
17
+ "output_dir": "festim/msre_coupled_run"
18
+ },
19
+ "coolprop": {
20
+ "type": "coolprop"
21
+ }
22
+ },
23
+ "materials": {
24
+ "graphite": {
25
+ "id": 1,
26
+ "density": 2.26,
27
+ "density_units": "g/cm3",
28
+ "temperature": 300.0,
29
+ "elements": [{ "element": "C", "percent": 1.0, "percent_type": "ao" }]
30
+ },
31
+ "inconel": {
32
+ "id": 4,
33
+ "density": 8.9,
34
+ "density_units": "g/cm3",
35
+ "temperature": 300.0,
36
+ "elements": [
37
+ { "element": "Ni", "percent": 0.72, "percent_type": "ao" },
38
+ { "element": "Cr", "percent": 0.20, "percent_type": "ao" },
39
+ { "element": "Fe", "percent": 0.08, "percent_type": "ao" }
40
+ ]
41
+ },
42
+ "salt": {
43
+ "id": 3,
44
+ "density": 2.2,
45
+ "density_units": "g/cm3",
46
+ "temperature": 300.0,
47
+ "depletable": true,
48
+ "nuclides": [
49
+ { "name": "Li7", "percent": 0.335000, "percent_type": "ao" },
50
+ { "name": "U235", "percent": 0.000527, "percent_type": "ao" },
51
+ { "name": "U238", "percent": 0.001053, "percent_type": "ao" }
52
+ ],
53
+ "elements": [
54
+ { "element": "F", "percent": 0.534653, "percent_type": "ao" },
55
+ { "element": "Be", "percent": 0.076667, "percent_type": "ao" },
56
+ { "element": "Zr", "percent": 0.010000, "percent_type": "ao" }
57
+ ]
58
+ },
59
+ "tungsten": {
60
+ "id": 6,
61
+ "extra": { "D_0": 1, "E_D": 0.0 }
62
+ }
63
+ },
64
+ "streams": {
65
+ "feed": {
66
+ "T": 900.0,
67
+ "P": 101325.0,
68
+ "flowrate": 1.0,
69
+ "z": { "salt": 1.0 }
70
+ }
71
+ },
72
+ "units": {
73
+ "coolant_heater": {
74
+ "type": "Heater",
75
+ "provider": "coolprop",
76
+ "material": 1,
77
+ "in": "feed",
78
+ "out": "coolant",
79
+ "solver_config": { "delta_T": 0.0 }
80
+ },
81
+ "openmc_reactor": {
82
+ "type": "SolverUnit",
83
+ "provider": "openmc",
84
+ "sim_type": "eigenvalue_reactor",
85
+ "material": 3,
86
+ "solver_config": {
87
+ "batches": 20,
88
+ "inactive": 5,
89
+ "particles": 20000
90
+ },
91
+ "geometry_config": {
92
+ "type": "reactor_core",
93
+ "core_radius": 72.5,
94
+ "core_height": 160.0,
95
+ "reflector_thickness": 50.0,
96
+ "core_material": "salt",
97
+ "reflector_material": "graphite",
98
+ "vessel_material": "inconel",
99
+ "source_point": { "xyz": [0.0, 0.0, 0.0] }
100
+ },
101
+ "inputs": {
102
+ "solver_config.temperature_default": { "ref": "coolant.T", "as_unit": "K" }
103
+ }
104
+ },
105
+ "thermal": {
106
+ "type": "SolverUnit",
107
+ "provider": "festim",
108
+ "sim_type": "hydrogen_transport_tds",
109
+ "material": 6,
110
+ "solver_config": {
111
+ "mesh": {
112
+ "vertices": [0.0, 0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007, 0.008, 0.009, 0.01]
113
+ },
114
+ "subdomains": {
115
+ "volume": [
116
+ { "id": 1, "borders": [0.0, 0.01], "material": "tungsten" }
117
+ ],
118
+ "surface": [
119
+ { "id": 2, "x": 0.0 },
120
+ { "id": 3, "x": 0.01 }
121
+ ]
122
+ },
123
+ "species": [
124
+ { "name": "H", "mobile": true }
125
+ ],
126
+ "boundary_conditions": [
127
+ { "type": "fixed_concentration", "subdomain": 2, "species": "H", "value": 1 },
128
+ { "type": "fixed_concentration", "subdomain": 3, "species": "H", "value": 0 }
129
+ ],
130
+ "exports": [
131
+ { "type": "profile_1d", "field": "H", "volume": 1, "times": [0.05, 0.1, 0.2, 0.5, 1] }
132
+ ],
133
+ "atol": 1e-10,
134
+ "rtol": 1e-10,
135
+ "final_time": 2,
136
+ "temperature": 300,
137
+ "stepsize": {
138
+ "initial_value": 0.05,
139
+ "growth_factor": 1.1,
140
+ "cutback_factor": 0.9,
141
+ "target_nb_iterations": 4,
142
+ "milestones": [0.05, 0.1, 0.2, 0.5, 1]
143
+ }
144
+ },
145
+ "inputs": {
146
+ "solver_config.temperature": { "ref": "coolant.T", "as_unit": "K" }
147
+ }
148
+ },
149
+ "openmc_source": {
150
+ "type": "SolverUnit",
151
+ "provider": "openmc",
152
+ "sim_type": "fixed_source_point",
153
+ "material": 3,
154
+ "solver_config": {
155
+ "batches": 20,
156
+ "inactive": 5,
157
+ "particles": 20000
158
+ },
159
+ "geometry_config": {
160
+ "source_point": { "xyz": [0.0, 0.0, 0.0] },
161
+ "sphere_radius": 200.0,
162
+ "sphere_material": "salt"
163
+ },
164
+ "inputs": {
165
+ "solver_config.source_strength": { "ref": "openmc_reactor.power", "as_unit": "W" }
166
+ }
167
+ }
168
+ },
169
+ "simulation": {
170
+ "mode": "steady"
171
+ }
172
+ }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "metadata": {
3
- "name": "MSRE Eigenvalue — CSG geometry",
4
- "description": "Molten Salt Reactor Experiment criticality calculation. Fission source at reactor centre inside a homogeneous salt sphere. 5 materials: graphite, inor, salt, inconel, helium.",
5
- "version": "1.3"
3
+ "name": "MSRE Eigenvalue — approximate reactor geometry",
4
+ "description": "Molten Salt Reactor Experiment criticality calculation using an approximate cylindrical reactor-core geometry (salt core, graphite reflector, inconel vessel, helium gap, inor structure). Uses all 5 declared materials. Fission source at reactor centre.",
5
+ "version": "1.4"
6
6
  },
7
7
  "providers": {
8
8
  "openmc": {
@@ -78,34 +78,26 @@
78
78
  "type": "SolverUnit",
79
79
  "provider": "openmc",
80
80
  "material": 3,
81
- "sim_type": "eigenvalue_csg",
81
+ "sim_type": "eigenvalue_reactor",
82
82
  "solver_config": {
83
83
  "batches": 20,
84
84
  "inactive": 5,
85
- "particles": 20000,
86
- "source_point": {
87
- "xyz": [0.0, 0.0, 0.0]
88
- },
89
- "point_source_sphere_radius": 200.0,
90
- "point_source_material": "salt",
91
- "mesh_tallies": [
92
- {
93
- "tally_id": 1,
94
- "name": "flux1",
95
- "lower_left": [-125, -125, 90],
96
- "upper_right": [ 125, 125, 110],
97
- "dimension": [400, 400, 1],
98
- "scores": ["flux", "fission"]
99
- },
100
- {
101
- "tally_id": 2,
102
- "name": "flux2",
103
- "lower_left": [-175, -10, -50],
104
- "upper_right": [ 175, 10, 400],
105
- "dimension": [400, 1, 400],
106
- "scores": ["flux", "fission"]
107
- }
108
- ]
85
+ "particles": 20000
86
+ },
87
+ "geometry_config": {
88
+ "type": "reactor_core",
89
+ "core_radius": 72.5,
90
+ "core_height": 160.0,
91
+ "reflector_thickness": 50.0,
92
+ "vessel_thickness": 5.0,
93
+ "gap_thickness": 3.0,
94
+ "structure_thickness": 10.0,
95
+ "core_material": "salt",
96
+ "reflector_material": "graphite",
97
+ "vessel_material": "inconel",
98
+ "gap_material": "helium",
99
+ "structure_material": "inor",
100
+ "source_point": {"xyz": [0.0, 0.0, 0.0]}
109
101
  }
110
102
  }
111
103
  },
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "processforge"
7
- version = "0.3.14"
7
+ version = "0.3.16"
8
8
  description = "A Python-based process simulation framework for chemical engineering applications."
9
9
  readme = "README.md"
10
10
  license = "BSD-3-Clause"
@@ -29,7 +29,7 @@ from .units.flash import Flash
29
29
  from .units.heater import Heater
30
30
  from .eo import EOFlowsheet, EOSolver
31
31
 
32
- __version__ = "0.3.14"
32
+ __version__ = "0.3.16"
33
33
 
34
34
  __all__ = [
35
35
  "Flowsheet",
@@ -0,0 +1,183 @@
1
+ """Multi-physics coupling resolver for Processforge flowsheets.
2
+
3
+ This module implements *parameter-reference coupling* between heterogeneous
4
+ solver units (OpenMC neutronics, FESTIM thermal, CoolProp thermo, …). Each
5
+ solver unit may declare an ``inputs`` block mapping a dotted configuration
6
+ path to a reference to another unit's (or stream's) output field::
7
+
8
+ "inputs": {
9
+ "solver_config.heat_source": {"ref": "openmc_solver.power", "as_unit": "W"},
10
+ "solver_config.temperature": {"ref": "thermal.temperature", "reduce": "mean", "as_unit": "K"}
11
+ }
12
+
13
+ A single shared :class:`ParameterStore` collects every unit/stream output as a
14
+ unit-bearing :class:`~processforge.quantity.Quantity` (the same ``Quantity``
15
+ type every engine already emits — see Phase 5A). The :class:`CouplingResolver`
16
+ then, for each consumer unit and each declared input:
17
+
18
+ 1. looks the referenced ``<unit>.<field>`` quantity up in the store,
19
+ 2. reduces array-valued fields to a scalar (``mean`` / ``sum`` / ``max`` /
20
+ ``min`` — the natural choice for uniform coupling),
21
+ 3. converts units through the process-wide ``pint`` registry, and
22
+ 4. injects the bare magnitude into the consumer's config at the dotted path.
23
+
24
+ Because solver units run in topological order, a single forward pass resolves
25
+ acyclic couplings; cyclic couplings (e.g. neutronics power ↔ thermal
26
+ temperature) converge via the iterative loop in :mod:`processforge.flowsheet`.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import re
32
+ from typing import Any, Optional
33
+
34
+ from loguru import logger
35
+
36
+ from .quantity import Quantity, ureg
37
+
38
+ __all__ = [
39
+ "CouplingError",
40
+ "ParameterStore",
41
+ "CouplingResolver",
42
+ "deep_merge",
43
+ ]
44
+
45
+
46
+ class CouplingError(ValueError):
47
+ """Raised when a coupling reference cannot be resolved."""
48
+
49
+
50
+ _REF_RE = re.compile(r"^(?P<src>[A-Za-z0-9_]+)\.(?P<field>[A-Za-z0-9_]+)$")
51
+
52
+
53
+ def _reduce_value(value: Any, mode: str) -> float:
54
+ """Collapse an (optionally array) value to a scalar for uniform coupling."""
55
+ if isinstance(value, (list, tuple)):
56
+ arr = [float(x) for x in value]
57
+ if not arr:
58
+ return 0.0
59
+ if mode == "sum":
60
+ return sum(arr)
61
+ if mode == "max":
62
+ return max(arr)
63
+ if mode == "min":
64
+ return min(arr)
65
+ return sum(arr) / len(arr)
66
+ if value is None:
67
+ return 0.0
68
+ return float(value)
69
+
70
+
71
+ def _convert(scalar: float, from_unit: str, to_unit: Optional[str]) -> float:
72
+ """Convert *scalar* from *from_unit* to *to_unit* via the shared registry."""
73
+ if not to_unit or not from_unit:
74
+ return float(scalar)
75
+ try:
76
+ return float(ureg.Quantity(float(scalar), from_unit).to(to_unit).magnitude)
77
+ except Exception as exc: # noqa: BLE001
78
+ logger.warning(
79
+ f"Coupling unit conversion {from_unit} -> {to_unit} failed ({exc}); "
80
+ f"using raw magnitude {scalar}."
81
+ )
82
+ return float(scalar)
83
+
84
+
85
+ def _deep_set(d: dict, dotted_path: str, value: Any) -> None:
86
+ """Set *value* at *dotted_path* inside nested dict *d*, creating maps."""
87
+ parts = dotted_path.split(".")
88
+ cur = d
89
+ for p in parts[:-1]:
90
+ nxt = cur.get(p)
91
+ if not isinstance(nxt, dict):
92
+ nxt = {}
93
+ cur[p] = nxt
94
+ cur = nxt
95
+ cur[parts[-1]] = value
96
+
97
+
98
+ def deep_merge(base: dict, overrides: dict) -> dict:
99
+ """Recursively merge *overrides* into a copy of *base*."""
100
+ out = dict(base)
101
+ for k, v in (overrides or {}).items():
102
+ if isinstance(v, dict) and isinstance(out.get(k), dict):
103
+ out[k] = deep_merge(out[k], v)
104
+ else:
105
+ out[k] = v
106
+ return out
107
+
108
+
109
+ class ParameterStore:
110
+ """Registry of unit/stream output quantities, keyed by ``<src>.<field>``."""
111
+
112
+ def __init__(self) -> None:
113
+ self._quantities: dict[str, Quantity] = {}
114
+
115
+ def register(self, src: str, field: str, quantity: Quantity) -> None:
116
+ self._quantities[f"{src}.{field}"] = quantity
117
+
118
+ def register_unit(self, unit_name: str, engine_output: Any) -> None:
119
+ """Register every field of an :class:`EngineOutput`."""
120
+ for field in getattr(engine_output, "fields", []) or []:
121
+ self.register(unit_name, field.name, field.quantity)
122
+
123
+ def register_stream(
124
+ self,
125
+ stream_name: str,
126
+ stream_dict: dict,
127
+ thermo_fields: Optional[dict] = None,
128
+ ) -> None:
129
+ """Register stream scalars (T in K, P in Pa) and any thermo fields."""
130
+ if "T" in stream_dict:
131
+ self.register(
132
+ stream_name, "T", Quantity(value=float(stream_dict["T"]), unit="K")
133
+ )
134
+ if "P" in stream_dict:
135
+ self.register(
136
+ stream_name, "P", Quantity(value=float(stream_dict["P"]), unit="Pa")
137
+ )
138
+ for fname, q in (thermo_fields or {}).items():
139
+ if isinstance(q, Quantity):
140
+ self.register(stream_name, fname, q)
141
+
142
+ def get(self, ref: str) -> Optional[Quantity]:
143
+ return self._quantities.get(ref)
144
+
145
+ def __contains__(self, ref: str) -> bool:
146
+ return ref in self._quantities
147
+
148
+
149
+ class CouplingResolver:
150
+ """Resolve a unit's ``inputs`` block against a :class:`ParameterStore`."""
151
+
152
+ def __init__(self, store: ParameterStore) -> None:
153
+ self.store = store
154
+
155
+ def resolve(self, spec: dict) -> float:
156
+ ref = spec.get("ref") if isinstance(spec, dict) else None
157
+ if not ref:
158
+ raise CouplingError("coupling spec missing 'ref'")
159
+ if not _REF_RE.match(ref):
160
+ raise CouplingError(
161
+ f"invalid coupling ref '{ref}' (expected '<unit>.<field>')"
162
+ )
163
+ quantity = self.store.get(ref)
164
+ if quantity is None:
165
+ raise CouplingError(f"coupling ref '{ref}' not found in store")
166
+ reduced = _reduce_value(quantity.value, spec.get("reduce", "mean"))
167
+ return _convert(reduced, quantity.unit, spec.get("as_unit"))
168
+
169
+ def build_overrides(self, inputs: dict):
170
+ """Return ``(nested_overrides, injected_scalars)`` for a unit.
171
+
172
+ *nested_overrides* mirrors the dotted input paths and is deep-merged
173
+ into the unit config before the provider runs. *injected_scalars*
174
+ maps each local path to the resolved float, used for convergence
175
+ checking by the flowsheet driver.
176
+ """
177
+ overrides: dict = {}
178
+ injected: dict = {}
179
+ for path, spec in (inputs or {}).items():
180
+ value = self.resolve(spec)
181
+ _deep_set(overrides, path, value)
182
+ injected[path] = value
183
+ return overrides, injected