OpenPinch 0.2.0__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/_problem/_target_accessor.py +6 -7
  2. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/pinch_problem.py +47 -0
  3. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/lib/__init__.py +2 -0
  4. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/lib/enums.py +19 -1
  5. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/power_cogeneration/power_cogeneration_analysis.py +14 -14
  6. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/services_entry.py +121 -43
  7. {openpinch-0.2.0 → openpinch-0.2.1}/PKG-INFO +1 -1
  8. {openpinch-0.2.0 → openpinch-0.2.1}/pyproject.toml +1 -1
  9. openpinch-0.2.0/OpenPinch/services/power_cogeneration_analysis/power_cogeneration_analysis.py +0 -139
  10. {openpinch-0.2.0 → openpinch-0.2.1}/.gitignore +0 -0
  11. {openpinch-0.2.0 → openpinch-0.2.1}/LICENSE +0 -0
  12. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/__init__.py +0 -0
  13. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/__main__.py +0 -0
  14. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/__init__.py +0 -0
  15. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/_problem/__init__.py +0 -0
  16. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/_problem/_loading.py +0 -0
  17. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/_problem/_output.py +0 -0
  18. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/_problem/_plot_accessor.py +0 -0
  19. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/_problem/_validation.py +0 -0
  20. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/_problem_table/__init__.py +0 -0
  21. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/_problem_table/_problem_table_constants.py +0 -0
  22. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/_stream_collection/__init__.py +0 -0
  23. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/_stream_collection/_helpers.py +0 -0
  24. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/_workspace/__init__.py +0 -0
  25. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/_workspace/execution.py +0 -0
  26. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/_workspace/payloads.py +0 -0
  27. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/_workspace/views.py +0 -0
  28. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/pinch_workspace.py +0 -0
  29. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/problem_table.py +0 -0
  30. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/stream.py +0 -0
  31. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/stream_collection.py +0 -0
  32. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/value.py +0 -0
  33. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/classes/zone.py +0 -0
  34. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/data/__init__.py +0 -0
  35. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/data/notebooks/01_basic_pinch_and_dtcont_sensitivity.ipynb +0 -0
  36. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/data/notebooks/02_total_site_targets_and_sugcc.ipynb +0 -0
  37. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/data/notebooks/03_carnot_hpr_comparison.ipynb +0 -0
  38. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/data/notebooks/__init__.py +0 -0
  39. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/data/sample_cases/__init__.py +0 -0
  40. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/data/sample_cases/basic_pinch.json +0 -0
  41. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/data/sample_cases/chocolate_factory.json +0 -0
  42. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/data/sample_cases/crude_preheat_train.json +0 -0
  43. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/data/sample_cases/heat_pump_targeting.json +0 -0
  44. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/data/sample_cases/pulp_mill.json +0 -0
  45. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/data/sample_cases/zonal_site.json +0 -0
  46. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/lib/config.py +0 -0
  47. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/lib/config_metadata.py +0 -0
  48. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/lib/problem_table_types.py +0 -0
  49. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/lib/schemas/__init__.py +0 -0
  50. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/lib/schemas/common.py +0 -0
  51. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/lib/schemas/graphs.py +0 -0
  52. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/lib/schemas/hpr.py +0 -0
  53. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/lib/schemas/io.py +0 -0
  54. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/lib/schemas/reporting.py +0 -0
  55. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/lib/schemas/targets.py +0 -0
  56. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/lib/schemas/turbine.py +0 -0
  57. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/lib/schemas/workspace.py +0 -0
  58. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/main.py +0 -0
  59. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/resources.py +0 -0
  60. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/__init__.py +0 -0
  61. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/common/__init__.py +0 -0
  62. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/common/capital_cost_and_area_targeting.py +0 -0
  63. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/common/gcc_manipulation.py +0 -0
  64. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/common/graph_data.py +0 -0
  65. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/common/graph_series_meta.py +0 -0
  66. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/common/problem_table_analysis.py +0 -0
  67. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/common/temperature_driving_force.py +0 -0
  68. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/common/utility_targeting.py +0 -0
  69. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/direct_heat_integration/__init__.py +0 -0
  70. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/direct_heat_integration/direct_integration_entry.py +0 -0
  71. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/exergy_analysis/__init__.py +0 -0
  72. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/exergy_analysis/exergy_targeting_entry.py +0 -0
  73. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/__init__.py +0 -0
  74. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/common/__init__.py +0 -0
  75. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/common/encoding.py +0 -0
  76. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/common/layout.py +0 -0
  77. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/common/postprocessing.py +0 -0
  78. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/common/preprocessing.py +0 -0
  79. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/common/shared.py +0 -0
  80. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/cycles/brayton.py +0 -0
  81. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/heat_pump_and_refrigeration_entry.py +0 -0
  82. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/targeting_services/__init__.py +0 -0
  83. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/targeting_services/brayton.py +0 -0
  84. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/targeting_services/cascade_vapour_compression.py +0 -0
  85. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/targeting_services/multi_simple_carnot.py +0 -0
  86. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/targeting_services/multi_simple_vapour_compression.py +0 -0
  87. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/targeting_services/multi_temperature_carnot.py +0 -0
  88. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/unit_models/__init__.py +0 -0
  89. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/unit_models/brayton_heat_pump.py +0 -0
  90. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/unit_models/cascade_vapour_compression_cycle.py +0 -0
  91. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/unit_models/parallel_vapour_compression_cycles.py +0 -0
  92. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/heat_pump_integration/unit_models/vapour_compression_cycle.py +0 -0
  93. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/indirect_heat_integration/__init__.py +0 -0
  94. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/indirect_heat_integration/indirect_integration_entry.py +0 -0
  95. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/input_data_processing/__init__.py +0 -0
  96. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/input_data_processing/_canonicalization.py +0 -0
  97. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/input_data_processing/_utility_preparation.py +0 -0
  98. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/input_data_processing/data_preparation.py +0 -0
  99. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/power_cogeneration/__init__.py +0 -0
  100. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/services/power_cogeneration/unit_models/multi_stage_steam_turbine.py +0 -0
  101. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/streamlit_webviewer/web_graphing.py +0 -0
  102. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/__init__.py +0 -0
  103. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/_tabular_input.py +0 -0
  104. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/bb_optimisers/__init__.py +0 -0
  105. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/bb_optimisers/bayesian_optimisation.py +0 -0
  106. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/bb_optimisers/cmaes.py +0 -0
  107. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/bb_optimisers/common.py +0 -0
  108. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/bb_optimisers/dual_annealing.py +0 -0
  109. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/bb_optimisers/rbf_surrogate.py +0 -0
  110. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/blackbox_minimisers.py +0 -0
  111. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/costing.py +0 -0
  112. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/csv_to_json.py +0 -0
  113. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/decorators.py +0 -0
  114. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/export.py +0 -0
  115. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/heat_exchanger.py +0 -0
  116. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/input_validation.py +0 -0
  117. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/miscellaneous.py +0 -0
  118. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/multiscale_targeting.py +0 -0
  119. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/plots.py +0 -0
  120. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/stream_linearisation.py +0 -0
  121. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/water_properties.py +0 -0
  122. {openpinch-0.2.0 → openpinch-0.2.1}/OpenPinch/utils/wkbook_to_json.py +0 -0
  123. {openpinch-0.2.0 → openpinch-0.2.1}/README.md +0 -0
@@ -164,19 +164,18 @@ class _TargetAccessor:
164
164
  include_subzones: bool = False,
165
165
  state_id: Optional[str] = None,
166
166
  ) -> BaseTargetModel:
167
- """Run turbine cogeneration post-processing on a compatible target."""
168
- target_id = TT.DI.value
167
+ """
168
+ Run cogeneration on `TS -> IHP -> IR -> DHP -> DR -> DI`
169
+ unless overridden.
170
+ """
169
171
  runtime_options = dict(options or {})
170
172
  if state_id is not None:
171
173
  runtime_options["state_id"] = state_id
172
- if runtime_options and "base_target_type" in runtime_options:
173
- target_id = str(runtime_options["base_target_type"])
174
- return self._problem._execute_targeting(
175
- target_id=target_id,
174
+ return self._problem._execute_cogeneration_targeting(
176
175
  application_zone=zone_name,
177
176
  options=runtime_options,
178
177
  include_subzones=include_subzones,
179
- direct_service_func=power_cogeneration_service,
178
+ service_func=power_cogeneration_service,
180
179
  )
181
180
 
182
181
  def area_cost(
@@ -179,6 +179,53 @@ class PinchProblem:
179
179
  f"for zone {zone.name!r}."
180
180
  ) from exc
181
181
 
182
+ def _execute_cogeneration_targeting(
183
+ self,
184
+ *,
185
+ application_zone: Optional[str | Zone],
186
+ options: Optional[dict[str, Any]],
187
+ include_subzones: bool,
188
+ service_func: Optional[ZoneService] = None,
189
+ sid: str = None,
190
+ ) -> BaseTargetModel:
191
+ """Run cogeneration targeting and return the family selected at runtime."""
192
+ execution_master_zone = self._build_execution_master_zone()
193
+ runtime_options, sid = self._resolve_runtime_state_options(
194
+ options,
195
+ zone=execution_master_zone,
196
+ )
197
+ zone = self._resolve_target_zone(
198
+ application_zone, master_zone=execution_master_zone
199
+ )
200
+ if include_subzones:
201
+ self._run_targeting_for_zone_and_subzones(
202
+ zone=zone,
203
+ direct_service_func=service_func,
204
+ options=runtime_options,
205
+ sid=sid,
206
+ )
207
+ else:
208
+ if service_func is not None:
209
+ service_func(zone, runtime_options)
210
+ self._results = TargetOutput.model_validate(
211
+ extract_results(execution_master_zone, state_id=sid)
212
+ )
213
+
214
+ selected_target_type = getattr(zone, "_selected_cogeneration_target_type", None)
215
+ if not isinstance(selected_target_type, str):
216
+ raise RuntimeError(
217
+ "Cogeneration did not select a compatible target "
218
+ f"for zone {zone.name!r}."
219
+ )
220
+ try:
221
+ return zone.targets[selected_target_type]
222
+ except KeyError as exc:
223
+ raise RuntimeError(
224
+ "Cogeneration selected target "
225
+ f"{selected_target_type!r} for zone {zone.name!r}, "
226
+ "but that target was not available on the zone."
227
+ ) from exc
228
+
182
229
  def _resolve_target_zone(
183
230
  self,
184
231
  application_zone: Optional[str] = None,
@@ -18,6 +18,7 @@ from .enums import (
18
18
  ZT,
19
19
  ArrowHead,
20
20
  BB_Minimiser,
21
+ CogenerationTarget,
21
22
  GraphType,
22
23
  HeatExchangerTypes,
23
24
  HeatFlowUnits,
@@ -113,6 +114,7 @@ __all__ = [
113
114
  "schemas",
114
115
  "ACTIVATE_TIMING",
115
116
  "C_to_K",
117
+ "CogenerationTarget",
116
118
  "Configuration",
117
119
  "LOG_TIMING",
118
120
  "T_CRIT",
@@ -1,10 +1,27 @@
1
- """Enumerations defining canonical labels across OpenPinch.
1
+ """Enumerations and lightweight typed contracts used across OpenPinch.
2
2
 
3
3
  These enums standardize zone types, stream classifications, Problem Table
4
4
  column names, graph labels, and options keys used by configuration and schemas.
5
5
  """
6
6
 
7
+ from __future__ import annotations
8
+
7
9
  from enum import Enum
10
+ from typing import TYPE_CHECKING, Protocol
11
+
12
+ if TYPE_CHECKING:
13
+ from ..classes.stream_collection import StreamCollection
14
+ from .config import Configuration
15
+
16
+
17
+ class CogenerationTarget(Protocol):
18
+ """Compatible target surface required by cogeneration analysis helpers."""
19
+
20
+ config: Configuration
21
+ hot_utilities: StreamCollection
22
+ work_target: float | None
23
+ turbine_efficiency_target: float | None
24
+ state_ids: dict[str, int] | None
8
25
 
9
26
 
10
27
  class ZoneType(Enum):
@@ -286,6 +303,7 @@ class BB_Minimiser(str, Enum):
286
303
  __all__ = [
287
304
  "ArrowHead",
288
305
  "BB_Minimiser",
306
+ "CogenerationTarget",
289
307
  "GraphType",
290
308
  "GT",
291
309
  "HeatExchangerTypes",
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING
7
7
  import numpy as np
8
8
 
9
9
  from ...lib.config import T_CRIT, Configuration, tol
10
+ from ...lib.enums import CogenerationTarget
10
11
  from ...utils.miscellaneous import get_state_index
11
12
  from ...utils.water_properties import psat_T
12
13
  from .unit_models.multi_stage_steam_turbine import MultiStageSteamTurbine
@@ -15,7 +16,6 @@ if TYPE_CHECKING:
15
16
  import numpy as np
16
17
 
17
18
  from ...classes.stream import Stream
18
- from ...classes.zone import Zone
19
19
 
20
20
  __all__ = [
21
21
  "get_power_cogeneration_above_pinch",
@@ -24,18 +24,18 @@ __all__ = [
24
24
 
25
25
 
26
26
  def get_power_cogeneration_above_pinch(
27
- zone: Zone,
27
+ target: CogenerationTarget,
28
28
  args: dict | None = None,
29
- ) -> Zone:
30
- """Calculate the power cogeneration potential above pinch for a given zone."""
31
- turbine_params = _prepare_turbine_parameters(zone.config)
29
+ ) -> CogenerationTarget:
30
+ """Calculate above-Pinch cogeneration for a compatible thermal target object."""
31
+ turbine_params = _prepare_turbine_parameters(target.config)
32
32
  idx, sid = get_state_index(
33
- state_ids=getattr(zone, "state_ids", None),
33
+ state_ids=getattr(target, "state_ids", None),
34
34
  args=args,
35
35
  )
36
- utility_data = _preprocess_utilities(zone, turbine_params, idx=idx)
36
+ utility_data = _preprocess_utilities(target, turbine_params, idx=idx)
37
37
  if utility_data is None:
38
- return zone
38
+ return target
39
39
 
40
40
  turbine = MultiStageSteamTurbine()
41
41
  total_work, details = turbine.solve(
@@ -51,9 +51,9 @@ def get_power_cogeneration_above_pinch(
51
51
  is_high_p_cond_flash=turbine_params["is_high_p_cond_flash"],
52
52
  )
53
53
 
54
- zone.work_target = total_work
55
- zone.turbine_efficiency_target = details["overall_efficiency"]
56
- return zone
54
+ target.work_target = total_work
55
+ target.turbine_efficiency_target = details["overall_efficiency"]
56
+ return target
57
57
 
58
58
 
59
59
  def get_power_cogeneration_below_pinch(
@@ -96,18 +96,18 @@ def _prepare_turbine_parameters(zone_config: Configuration) -> dict:
96
96
 
97
97
 
98
98
  def _preprocess_utilities(
99
- zone: Zone,
99
+ target: CogenerationTarget,
100
100
  turbine_params: dict,
101
101
  *,
102
102
  idx: int | None = None,
103
103
  ) -> dict | None:
104
- """Translate hot-utility demands into turbine stage temperatures and duties."""
104
+ """Translate target hot-utility demands into turbine stage temperatures."""
105
105
  stage_temperatures: list[float] = []
106
106
  stage_heat_flows: list[float] = []
107
107
  source_indices: list[int] = []
108
108
 
109
109
  u: Stream
110
- for i, u in enumerate(zone.hot_utilities):
110
+ for i, u in enumerate(target.hot_utilities):
111
111
  t_supply = float(u.t_supply[idx])
112
112
  t_target = float(u.t_target[idx])
113
113
  heat_flow = float(u.heat_flow[idx])
@@ -30,6 +30,15 @@ __all__ = [
30
30
  "area_cost_targeting_service",
31
31
  ]
32
32
 
33
+ _COGENERATION_TARGET_ORDER = (
34
+ TT.TS.value,
35
+ TT.IHP.value,
36
+ TT.IR.value,
37
+ TT.DHP.value,
38
+ TT.DR.value,
39
+ TT.DI.value,
40
+ )
41
+
33
42
 
34
43
  def _record_selected_state(zone: Zone, args: dict | None) -> tuple[int, str | None]:
35
44
  """Persist the selected state metadata on a prepared zone."""
@@ -78,6 +87,86 @@ def _apply_zone_config_overrides(zone: Zone, args: dict | None) -> None:
78
87
  setattr(zone.config, key, value)
79
88
 
80
89
 
90
+ def _normalize_cogeneration_base_target_type(
91
+ base_target_type: object | None,
92
+ ) -> str | None:
93
+ """Validate an explicit cogeneration base target override."""
94
+ if base_target_type is None:
95
+ return None
96
+
97
+ normalized = str(base_target_type)
98
+ if normalized not in _COGENERATION_TARGET_ORDER:
99
+ supported = ", ".join(_COGENERATION_TARGET_ORDER)
100
+ raise ValueError(
101
+ "Unsupported cogeneration base_target_type "
102
+ f"{normalized!r}. Supported types: {supported}."
103
+ )
104
+ return normalized
105
+
106
+
107
+ def _get_cogeneration_candidate_order(
108
+ base_target_type: str | None,
109
+ ) -> tuple[str, ...]:
110
+ """Return the exact cogeneration target search order for this call."""
111
+ if base_target_type is not None:
112
+ return (base_target_type,)
113
+ return _COGENERATION_TARGET_ORDER
114
+
115
+
116
+ def _get_cogeneration_refresh_services():
117
+ """Map compatible cogeneration target families to their prerequisite service."""
118
+ return {
119
+ TT.DI.value: direct_heat_integration_service,
120
+ TT.TS.value: indirect_heat_integration_service,
121
+ TT.DHP.value: direct_heat_pump_service,
122
+ TT.DR.value: direct_refrigeration_service,
123
+ TT.IHP.value: indirect_heat_pump_service,
124
+ TT.IR.value: indirect_refrigeration_service,
125
+ }
126
+
127
+
128
+ def _ensure_cogeneration_target(
129
+ zone: Zone,
130
+ *,
131
+ target_type: str,
132
+ refresh_args: dict | None,
133
+ compare_args: dict | None,
134
+ ):
135
+ """Ensure one compatible target family exists for the requested state."""
136
+ target = zone.targets.get(target_type)
137
+ if _target_matches_requested_state(
138
+ target,
139
+ args=compare_args,
140
+ state_ids=getattr(zone, "state_ids", None),
141
+ ):
142
+ return target
143
+
144
+ refresh_service = _get_cogeneration_refresh_services().get(target_type)
145
+ if refresh_service is None:
146
+ return None
147
+
148
+ refresh_service(zone, refresh_args)
149
+ refreshed_target = zone.targets.get(target_type)
150
+ if _target_matches_requested_state(
151
+ refreshed_target,
152
+ args=compare_args,
153
+ state_ids=getattr(zone, "state_ids", None),
154
+ ):
155
+ return refreshed_target
156
+ return None
157
+
158
+
159
+ def _format_cogeneration_state_suffix(args: dict | None) -> str:
160
+ """Render the selected state into cogeneration error messages."""
161
+ if not isinstance(args, dict):
162
+ return ""
163
+ if args.get("state_id") is not None:
164
+ return f" for state_id {str(args['state_id'])!r}"
165
+ if args.get("idx") is not None:
166
+ return f" for idx {int(args['idx'])}"
167
+ return ""
168
+
169
+
81
170
  def data_preprocessing_service(
82
171
  input_data: TargetInput,
83
172
  project_name: str = "Site",
@@ -203,56 +292,45 @@ def indirect_refrigeration_service(zone: Zone, args: dict | None = None) -> Zone
203
292
 
204
293
 
205
294
  def power_cogeneration_service(zone: Zone, args: dict | None = None) -> Zone:
206
- """Post-process an existing target to recover above Pinch cogeneration work."""
295
+ """Post-process one compatible target in
296
+ TS -> IHP -> IR -> DHP -> DR -> DI order."""
207
297
  _apply_zone_config_overrides(zone, args)
208
- target_type = [
209
- TT.IHP.value,
210
- TT.IR.value,
211
- TT.TS.value,
212
- TT.DHP.value,
213
- TT.DR.value,
214
- TT.DI.value,
215
- ]
216
298
  runtime_args = dict(args or {})
299
+ explicit_target_type = _normalize_cogeneration_base_target_type(
300
+ runtime_args.get("base_target_type")
301
+ )
217
302
  idx, sid = _record_selected_state(zone, runtime_args)
218
303
  runtime_args["idx"] = idx
219
304
  if sid is not None:
220
305
  runtime_args["state_id"] = sid
221
- refresh_args = runtime_args if isinstance(args, dict) else None
222
- compare_args = runtime_args if isinstance(args, dict) else None
223
- should_refresh_missing_target = False
224
- if "base_target_type" in runtime_args:
225
- target_type = [str(runtime_args["base_target_type"])]
226
- should_refresh_missing_target = True
227
- elif len(zone.targets) == 0:
228
- direct_heat_integration_service(zone, refresh_args)
229
- refresh_services = {
230
- TT.DI.value: direct_heat_integration_service,
231
- TT.TS.value: indirect_heat_integration_service,
232
- TT.DHP.value: direct_heat_pump_service,
233
- TT.DR.value: direct_refrigeration_service,
234
- TT.IHP.value: indirect_heat_pump_service,
235
- TT.IR.value: indirect_refrigeration_service,
236
- }
237
- for tt in target_type:
238
- existing_target = zone.targets.get(tt)
239
- if existing_target is None and not should_refresh_missing_target:
306
+ compare_args = dict(args or {}) if isinstance(args, dict) else {}
307
+ zone._selected_cogeneration_target_type = None
308
+
309
+ for target_type in _get_cogeneration_candidate_order(explicit_target_type):
310
+ target = _ensure_cogeneration_target(
311
+ zone,
312
+ target_type=target_type,
313
+ refresh_args=runtime_args,
314
+ compare_args=compare_args,
315
+ )
316
+ if target is None:
317
+ if explicit_target_type is not None:
318
+ raise RuntimeError(
319
+ "Cogeneration could not produce target "
320
+ f"{target_type!r} for zone {zone.name!r}"
321
+ f"{_format_cogeneration_state_suffix(runtime_args)}."
322
+ )
240
323
  continue
241
- if existing_target is None or not _target_matches_requested_state(
242
- existing_target,
243
- args=compare_args,
244
- state_ids=getattr(zone, "state_ids", None),
245
- ):
246
- refresh_service = refresh_services.get(tt)
247
- if refresh_service is not None:
248
- refresh_service(zone, refresh_args)
249
- if tt in zone.targets:
250
- if isinstance(args, dict):
251
- get_power_cogeneration_above_pinch(zone.targets[tt], args=runtime_args)
252
- else:
253
- get_power_cogeneration_above_pinch(zone.targets[tt])
254
- return zone
255
- raise ValueError("Load data before running pinch analysis services.")
324
+
325
+ get_power_cogeneration_above_pinch(target, args=runtime_args)
326
+ zone._selected_cogeneration_target_type = target_type
327
+ return zone
328
+
329
+ raise RuntimeError(
330
+ "Cogeneration could not find a compatible target for zone "
331
+ f"{zone.name!r}{_format_cogeneration_state_suffix(runtime_args)} "
332
+ f"using implicit order {' -> '.join(_COGENERATION_TARGET_ORDER)}."
333
+ )
256
334
 
257
335
 
258
336
  def area_cost_targeting_service(zone: Zone, args: dict | None = None) -> Zone:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OpenPinch
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: An advanced pinch analysis and total site integration toolkit
5
5
  Project-URL: Homepage, https://github.com/waikato-ahuora-smart-energy-systems/OpenPinch
6
6
  Project-URL: Issues, https://github.com/waikato-ahuora-smart-energy-systems/OpenPinch/issues
@@ -34,7 +34,7 @@ dependencies=[
34
34
  "pydantic",
35
35
  "scipy",
36
36
  ]
37
- version = "0.2.0"
37
+ version = "0.2.1"
38
38
  authors = [
39
39
  { name="Tim Walmsley", email="tim.walmsley@waikato.ac.nz" },
40
40
  ]
@@ -1,139 +0,0 @@
1
- """Utility routines for estimating turbine cogeneration targets."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import TYPE_CHECKING
6
-
7
- import numpy as np
8
-
9
- from ...lib.config import T_CRIT, Configuration, tol
10
- from ...utils.miscellaneous import get_state_index
11
- from ...utils.water_properties import psat_T
12
- from ..power_cogeneration.unit_models.multi_stage_steam_turbine import (
13
- MultiStageSteamTurbine,
14
- )
15
-
16
- if TYPE_CHECKING:
17
- import numpy as np
18
-
19
- from ...classes.stream import Stream
20
- from ...classes.zone import Zone
21
-
22
- __all__ = [
23
- "get_power_cogeneration_above_pinch",
24
- "get_power_cogeneration_below_pinch",
25
- ]
26
-
27
-
28
- def get_power_cogeneration_above_pinch(
29
- zone: Zone,
30
- args: dict | None = None,
31
- ) -> Zone:
32
- """Calculate the power cogeneration potential above pinch for a given zone."""
33
- turbine_params = _prepare_turbine_parameters(zone.config)
34
- idx, sid = get_state_index(
35
- state_ids=getattr(zone, "state_ids", None),
36
- args=args,
37
- )
38
- utility_data = _preprocess_utilities(zone, turbine_params, idx=idx)
39
- if utility_data is None:
40
- return zone
41
-
42
- turbine = MultiStageSteamTurbine()
43
- total_work, details = turbine.solve(
44
- utility_data["stage_temperatures"],
45
- utility_data["stage_heat_flows"],
46
- mode="above_pinch",
47
- T_in=turbine_params["T_in"],
48
- P_in=turbine_params["P_in"],
49
- model=turbine_params["model"],
50
- min_eff=turbine_params["min_eff"],
51
- load_frac=turbine_params["load_frac"],
52
- mech_eff=turbine_params["mech_eff"],
53
- is_high_p_cond_flash=turbine_params["is_high_p_cond_flash"],
54
- )
55
-
56
- zone.work_target = total_work
57
- zone.turbine_efficiency_target = details["overall_efficiency"]
58
- return zone
59
-
60
-
61
- def get_power_cogeneration_below_pinch(
62
- temperatures: np.ndarray,
63
- heat_flows: np.ndarray,
64
- *,
65
- zone_config: Configuration | None = None,
66
- T_sink: float | None = None,
67
- ) -> tuple[float, dict]:
68
- """Solve a below Pinch turbine target against an environmental sink."""
69
- zone_config = zone_config or Configuration()
70
- turbine_params = _prepare_turbine_parameters(zone_config)
71
- sink_temperature = zone_config.T_ENV if T_sink is None else float(T_sink)
72
-
73
- turbine = MultiStageSteamTurbine()
74
- return turbine.solve(
75
- temperatures,
76
- heat_flows,
77
- mode="below_pinch",
78
- T_sink=sink_temperature,
79
- model=turbine_params["model"],
80
- min_eff=turbine_params["min_eff"],
81
- load_frac=turbine_params["load_frac"],
82
- mech_eff=turbine_params["mech_eff"],
83
- is_high_p_cond_flash=turbine_params["is_high_p_cond_flash"],
84
- )
85
-
86
-
87
- def _prepare_turbine_parameters(zone_config: Configuration) -> dict:
88
- """Load and sanitize turbine parameters from ``zone_config``."""
89
- return {
90
- "P_in": float(zone_config.TURB_P_IN),
91
- "T_in": float(zone_config.TURB_T_IN),
92
- "min_eff": float(zone_config.MIN_EFF),
93
- "model": zone_config.TURB_MODEL,
94
- "load_frac": min(max(float(zone_config.LOAD_FRACTION), 0.0), 1.0),
95
- "mech_eff": min(max(float(zone_config.ETA_MECH), 0.0), 1.0),
96
- "is_high_p_cond_flash": bool(zone_config.IS_HIGH_P_COND_FLASH),
97
- }
98
-
99
-
100
- def _preprocess_utilities(
101
- zone: Zone,
102
- turbine_params: dict,
103
- *,
104
- idx: int | None = None,
105
- ) -> dict | None:
106
- """Translate hot-utility demands into turbine stage temperatures and duties."""
107
- stage_temperatures: list[float] = []
108
- stage_heat_flows: list[float] = []
109
- source_indices: list[int] = []
110
-
111
- u: Stream
112
- for i, u in enumerate(zone.hot_utilities):
113
- t_supply = float(u.t_supply[idx])
114
- t_target = float(u.t_target[idx])
115
- heat_flow = float(u.heat_flow[idx])
116
- dt_cont_act = float(u.dt_cont_act[idx])
117
- if t_supply >= T_CRIT or heat_flow <= tol:
118
- continue
119
-
120
- T_stage = (
121
- t_target
122
- if abs(t_supply - t_target) < 1.0 + tol
123
- else t_target + dt_cont_act * 2
124
- )
125
- if turbine_params["P_in"] + tol < psat_T(T_stage):
126
- continue
127
-
128
- stage_temperatures.append(float(T_stage))
129
- stage_heat_flows.append(float(heat_flow))
130
- source_indices.append(i)
131
-
132
- if not stage_temperatures:
133
- return None
134
-
135
- return {
136
- "stage_temperatures": np.asarray(stage_temperatures, dtype=float),
137
- "stage_heat_flows": np.asarray(stage_heat_flows, dtype=float),
138
- "source_indices": np.asarray(source_indices, dtype=int),
139
- }
File without changes
File without changes
File without changes
File without changes