OpenPinch 0.8.0__tar.gz → 0.8.2__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 (489) hide show
  1. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/execution/README.md +2 -1
  2. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/execution/executor.py +22 -2
  3. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/README.md +1 -0
  4. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_base/README.md +1 -0
  5. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_base/execution.py +10 -0
  6. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_stagewise/README.md +2 -2
  7. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_stagewise/evolution.py +39 -8
  8. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/problem.py +19 -1
  9. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/results/README.md +2 -2
  10. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/results/assembly.py +14 -1
  11. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/solver/README.md +4 -1
  12. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/solver/arrays.py +1 -0
  13. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/solver/backend.py +72 -3
  14. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/solver/pinch_design_decomposition.py +6 -4
  15. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/common/README.md +1 -1
  16. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/common/_shared/README.md +1 -1
  17. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/common/_shared/air.py +5 -3
  18. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/common/preprocessing.py +1 -0
  19. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/common/shared.py +1 -0
  20. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/targeting/README.md +1 -1
  21. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/targeting/_carnot_common.py +5 -1
  22. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/accessors/design.py +6 -0
  23. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/hpr.py +1 -0
  24. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/configuration.py +2 -0
  25. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/configuration_fields.py +9 -0
  26. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/optimisation/README.md +1 -1
  27. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/optimisation/execution.py +7 -0
  28. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/configuration_text.py +17 -0
  29. {openpinch-0.8.0 → openpinch-0.8.2}/PKG-INFO +1 -1
  30. {openpinch-0.8.0 → openpinch-0.8.2}/pyproject.toml +1 -1
  31. {openpinch-0.8.0 → openpinch-0.8.2}/.gitignore +0 -0
  32. {openpinch-0.8.0 → openpinch-0.8.2}/LICENSE +0 -0
  33. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/README.md +0 -0
  34. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/__init__.py +0 -0
  35. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/__main__.py +0 -0
  36. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/adapters/README.md +0 -0
  37. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/adapters/__init__.py +0 -0
  38. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/adapters/io/README.md +0 -0
  39. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/adapters/io/__init__.py +0 -0
  40. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/adapters/io/csv.py +0 -0
  41. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/adapters/io/file_exports.py +0 -0
  42. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/adapters/io/json.py +0 -0
  43. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/adapters/io/problem_sources.py +0 -0
  44. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/adapters/io/records.py +0 -0
  45. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/adapters/io/tabular.py +0 -0
  46. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/adapters/io/workbook.py +0 -0
  47. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/adapters/io/workspace_bundles.py +0 -0
  48. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/adapters/optional_dependencies.py +0 -0
  49. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/README.md +0 -0
  50. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/__init__.py +0 -0
  51. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/economics.py +0 -0
  52. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/energy_transfer/README.md +0 -0
  53. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/energy_transfer/__init__.py +0 -0
  54. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/energy_transfer/cascade.py +0 -0
  55. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/energy_transfer/diagram.py +0 -0
  56. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/energy_transfer/selection.py +0 -0
  57. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/energy_transfer/serialization.py +0 -0
  58. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/energy_transfer/service.py +0 -0
  59. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/exergy/README.md +0 -0
  60. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/exergy/__init__.py +0 -0
  61. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/exergy/service.py +0 -0
  62. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/graphs/README.md +0 -0
  63. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/graphs/__init__.py +0 -0
  64. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/graphs/composite.py +0 -0
  65. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/graphs/grand_composite.py +0 -0
  66. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/graphs/metadata.py +0 -0
  67. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/graphs/primitives.py +0 -0
  68. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/graphs/service.py +0 -0
  69. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/graphs/specifications.py +0 -0
  70. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/README.md +0 -0
  71. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/__init__.py +0 -0
  72. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/context.py +0 -0
  73. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/controllability.py +0 -0
  74. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/duty_optimisation.py +0 -0
  75. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/errors.py +0 -0
  76. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/execution/__init__.py +0 -0
  77. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/execution/fake_executor.py +0 -0
  78. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/execution/fallbacks.py +0 -0
  79. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/execution/pathways.py +0 -0
  80. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/execution/settings.py +0 -0
  81. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/execution/task_builders.py +0 -0
  82. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/extraction/README.md +0 -0
  83. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/extraction/__init__.py +0 -0
  84. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/extraction/metadata.py +0 -0
  85. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/extraction/period_state.py +0 -0
  86. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/extraction/recovery.py +0 -0
  87. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/extraction/segment_area.py +0 -0
  88. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/extraction/service.py +0 -0
  89. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/extraction/utility.py +0 -0
  90. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/indexing.py +0 -0
  91. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/__init__.py +0 -0
  92. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_base/__init__.py +0 -0
  93. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_base/alpha.py +0 -0
  94. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_base/approach.py +0 -0
  95. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_base/area.py +0 -0
  96. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_base/costing.py +0 -0
  97. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_base/parameters.py +0 -0
  98. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_base/piecewise.py +0 -0
  99. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_base/superstructure.py +0 -0
  100. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_pinch_design/README.md +0 -0
  101. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_pinch_design/__init__.py +0 -0
  102. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_pinch_design/amalgamation.py +0 -0
  103. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_pinch_design/equations.py +0 -0
  104. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_pinch_design/postprocess.py +0 -0
  105. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_pinch_design/preprocessing.py +0 -0
  106. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_stagewise/__init__.py +0 -0
  107. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_stagewise/equations.py +0 -0
  108. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_stagewise/objectives.py +0 -0
  109. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_stagewise/postprocess.py +0 -0
  110. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_stagewise/setup.py +0 -0
  111. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_stagewise/verification.py +0 -0
  112. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/_stagewise/warm_start.py +0 -0
  113. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/base.py +0 -0
  114. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/fixed_structure.py +0 -0
  115. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/packed_pinch_design.py +0 -0
  116. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/packed_stagewise.py +0 -0
  117. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/pinch_decomposition.py +0 -0
  118. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/stage_packing.py +0 -0
  119. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/stagewise.py +0 -0
  120. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/models/thermal_profiles.py +0 -0
  121. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/prepared_problem.py +0 -0
  122. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/reporting/README.md +0 -0
  123. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/reporting/__init__.py +0 -0
  124. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/reporting/exports.py +0 -0
  125. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/reporting/ranking.py +0 -0
  126. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/reporting/verification.py +0 -0
  127. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/results/__init__.py +0 -0
  128. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/results/seeds.py +0 -0
  129. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/results/selection.py +0 -0
  130. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/service.py +0 -0
  131. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/solver/__init__.py +0 -0
  132. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/solver/dependencies.py +0 -0
  133. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/solver/piecewise.py +0 -0
  134. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/targeting/README.md +0 -0
  135. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/targeting/__init__.py +0 -0
  136. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/targeting/_stage.py +0 -0
  137. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/targeting/network_evolution_method.py +0 -0
  138. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/targeting/open_hens_method.py +0 -0
  139. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/targeting/pinch_design_method.py +0 -0
  140. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/targeting/thermal_derivative_method.py +0 -0
  141. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_exchanger_networks/targeting/topology.py +0 -0
  142. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/README.md +0 -0
  143. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/__init__.py +0 -0
  144. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/_multiperiod/README.md +0 -0
  145. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/_multiperiod/__init__.py +0 -0
  146. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/_multiperiod/aggregation.py +0 -0
  147. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/_multiperiod/execution.py +0 -0
  148. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/_multiperiod/preparation.py +0 -0
  149. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/_multiperiod/setup.py +0 -0
  150. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/_multiperiod/state.py +0 -0
  151. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/_process_mvr/README.md +0 -0
  152. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/_process_mvr/__init__.py +0 -0
  153. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/_process_mvr/membership.py +0 -0
  154. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/_process_mvr/replacement_streams.py +0 -0
  155. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/_process_mvr/selection.py +0 -0
  156. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/_process_mvr/state.py +0 -0
  157. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/_process_mvr/values.py +0 -0
  158. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/_process_mvr/work.py +0 -0
  159. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/common/__init__.py +0 -0
  160. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/common/_shared/__init__.py +0 -0
  161. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/common/_shared/plotting.py +0 -0
  162. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/common/_shared/streams.py +0 -0
  163. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/common/encoding.py +0 -0
  164. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/common/layout.py +0 -0
  165. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/common/load_selection.py +0 -0
  166. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/common/multi_vc_objective.py +0 -0
  167. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/common/postprocessing.py +0 -0
  168. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/common/units.py +0 -0
  169. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/components.py +0 -0
  170. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/cycles/README.md +0 -0
  171. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/cycles/__init__.py +0 -0
  172. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/cycles/_multi_cycle_base.py +0 -0
  173. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/cycles/brayton_heat_pump.py +0 -0
  174. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/cycles/carnot_cycles.py +0 -0
  175. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/cycles/cascade_vapour_compression_cycle.py +0 -0
  176. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/cycles/mechanical_vapour_recompression_cycle.py +0 -0
  177. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/cycles/parallel_vapour_compression_cycles.py +0 -0
  178. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/cycles/vapour_compression_cycle.py +0 -0
  179. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/cycles/vapour_compression_mvr_cascade.py +0 -0
  180. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/direct_mvr/README.md +0 -0
  181. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/direct_mvr/__init__.py +0 -0
  182. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/direct_mvr/execution.py +0 -0
  183. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/direct_mvr/models.py +0 -0
  184. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/direct_mvr/thermodynamics.py +0 -0
  185. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/direct_mvr/units.py +0 -0
  186. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/optimisation_adapter.py +0 -0
  187. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/README.md +0 -0
  188. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/__init__.py +0 -0
  189. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/adapters/README.md +0 -0
  190. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/adapters/__init__.py +0 -0
  191. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/adapters/coolprop.py +0 -0
  192. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/adapters/tespy.py +0 -0
  193. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/characteristics/README.md +0 -0
  194. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/characteristics/__init__.py +0 -0
  195. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/characteristics/openpinch-single-stage-compressor-v1.json +0 -0
  196. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/context.py +0 -0
  197. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/coolprop_preflight.py +0 -0
  198. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/errors.py +0 -0
  199. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/factory.py +0 -0
  200. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/fluids.py +0 -0
  201. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/generation.py +0 -0
  202. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/models.py +0 -0
  203. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/points.py +0 -0
  204. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/protocols.py +0 -0
  205. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/provenance.py +0 -0
  206. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/resources.py +0 -0
  207. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/settings.py +0 -0
  208. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/target_basis.py +0 -0
  209. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/target_records.py +0 -0
  210. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/targeting.py +0 -0
  211. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/performance_maps/targeting_models.py +0 -0
  212. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/process_mvr.py +0 -0
  213. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/service.py +0 -0
  214. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/targeting/__init__.py +0 -0
  215. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/targeting/brayton.py +0 -0
  216. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/targeting/cascade_carnot.py +0 -0
  217. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/targeting/cascade_vapour_compression.py +0 -0
  218. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/targeting/parallel_carnot.py +0 -0
  219. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/targeting/parallel_vapour_compression.py +0 -0
  220. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_pumps/targeting/vapour_compression_mvr.py +0 -0
  221. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/heat_transfer.py +0 -0
  222. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/numerics.py +0 -0
  223. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/orc/README.md +0 -0
  224. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/orc/__init__.py +0 -0
  225. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/orc/carnot.py +0 -0
  226. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/orc/costing.py +0 -0
  227. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/orc/cycle.py +0 -0
  228. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/orc/inputs.py +0 -0
  229. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/orc/profile.py +0 -0
  230. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/orc/search.py +0 -0
  231. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/orc/service.py +0 -0
  232. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/orc/simulated.py +0 -0
  233. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/parallelism.py +0 -0
  234. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/power/README.md +0 -0
  235. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/power/__init__.py +0 -0
  236. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/power/service.py +0 -0
  237. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/power/steam_turbine.py +0 -0
  238. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/targeting/README.md +0 -0
  239. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/targeting/__init__.py +0 -0
  240. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/targeting/area_cost.py +0 -0
  241. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/targeting/cascade.py +0 -0
  242. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/targeting/context.py +0 -0
  243. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/targeting/direct.py +0 -0
  244. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/targeting/grand_composite.py +0 -0
  245. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/targeting/heat_recovery_dt_min.py +0 -0
  246. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/targeting/indirect.py +0 -0
  247. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/targeting/residual.py +0 -0
  248. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/targeting/temperature_driving_force.py +0 -0
  249. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/targeting/utilities.py +0 -0
  250. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/thermodynamics/README.md +0 -0
  251. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/thermodynamics/__init__.py +0 -0
  252. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/thermodynamics/water.py +0 -0
  253. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/utility_placement/README.md +0 -0
  254. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/utility_placement/__init__.py +0 -0
  255. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/utility_placement/allocation.py +0 -0
  256. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/utility_placement/bounds.py +0 -0
  257. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/utility_placement/codec.py +0 -0
  258. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/utility_placement/context.py +0 -0
  259. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/utility_placement/errors.py +0 -0
  260. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/utility_placement/evaluation.py +0 -0
  261. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/utility_placement/normalization.py +0 -0
  262. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/utility_placement/optimisation.py +0 -0
  263. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/utility_placement/penalties.py +0 -0
  264. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/utility_placement/profiles.py +0 -0
  265. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/utility_placement/replay.py +0 -0
  266. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/utility_placement/service.py +0 -0
  267. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/utility_placement/start_points.py +0 -0
  268. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/utility_placement/thermodynamics.py +0 -0
  269. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/analysis/utility_placement/units.py +0 -0
  270. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/README.md +0 -0
  271. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/__init__.py +0 -0
  272. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/README.md +0 -0
  273. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/__init__.py +0 -0
  274. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/accessors/README.md +0 -0
  275. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/accessors/__init__.py +0 -0
  276. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/accessors/component.py +0 -0
  277. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/accessors/plot.py +0 -0
  278. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/accessors/target.py +0 -0
  279. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/arguments.py +0 -0
  280. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/input/README.md +0 -0
  281. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/input/__init__.py +0 -0
  282. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/input/canonicalization.py +0 -0
  283. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/input/construction.py +0 -0
  284. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/input/loading.py +0 -0
  285. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/input/segments.py +0 -0
  286. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/input/semantics.py +0 -0
  287. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/input/utilities.py +0 -0
  288. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/input/validation.py +0 -0
  289. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/output/README.md +0 -0
  290. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/output/__init__.py +0 -0
  291. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/output/reporting.py +0 -0
  292. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/output/result_extraction.py +0 -0
  293. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/periods/README.md +0 -0
  294. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/periods/__init__.py +0 -0
  295. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/periods/aggregation.py +0 -0
  296. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/targeting/README.md +0 -0
  297. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/targeting/__init__.py +0 -0
  298. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/targeting/catalog.py +0 -0
  299. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/targeting/dispatch.py +0 -0
  300. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/targeting/execution.py +0 -0
  301. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/targeting/provenance.py +0 -0
  302. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_problem/targeting/state.py +0 -0
  303. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_workspace/README.md +0 -0
  304. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_workspace/__init__.py +0 -0
  305. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_workspace/case_inputs.py +0 -0
  306. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/_workspace/state.py +0 -0
  307. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/case_derivation.py +0 -0
  308. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/commands.py +0 -0
  309. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/heat_recovery_dt_min.py +0 -0
  310. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/hpr_selection.py +0 -0
  311. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/problem.py +0 -0
  312. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/residual_utility.py +0 -0
  313. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/targeting.py +0 -0
  314. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/utility_placement.py +0 -0
  315. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/application/workspace.py +0 -0
  316. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/README.md +0 -0
  317. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/__init__.py +0 -0
  318. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/common.py +0 -0
  319. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/controllability.py +0 -0
  320. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/graphs.py +0 -0
  321. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/heat_recovery_dt_min.py +0 -0
  322. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/hpr_performance_map.py +0 -0
  323. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/input.py +0 -0
  324. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/output.py +0 -0
  325. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/report_metrics.py +0 -0
  326. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/report_units.py +0 -0
  327. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/reporting.py +0 -0
  328. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/resources/README.md +0 -0
  329. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/resources/__init__.py +0 -0
  330. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/resources/hpr_performance_map/README.md +0 -0
  331. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/resources/hpr_performance_map/__init__.py +0 -0
  332. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/resources/hpr_performance_map/heat-pump-1.0.json +0 -0
  333. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/resources/hpr_performance_map/refrigeration-1.0.json +0 -0
  334. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/resources/hpr_performance_map/schema-1.0.json +0 -0
  335. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/schema.py +0 -0
  336. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/synthesis/README.md +0 -0
  337. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/synthesis/__init__.py +0 -0
  338. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/synthesis/common.py +0 -0
  339. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/synthesis/method.py +0 -0
  340. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/synthesis/result.py +0 -0
  341. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/synthesis/task.py +0 -0
  342. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/synthesis/topology.py +0 -0
  343. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/synthesis/view.py +0 -0
  344. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/turbine.py +0 -0
  345. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/units.py +0 -0
  346. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/utility_placement.py +0 -0
  347. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/contracts/workspace.py +0 -0
  348. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/README.md +0 -0
  349. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/__init__.py +0 -0
  350. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_heat_exchanger/README.md +0 -0
  351. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_heat_exchanger/__init__.py +0 -0
  352. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_heat_exchanger/area.py +0 -0
  353. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_heat_exchanger/period_state.py +0 -0
  354. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_heat_exchanger/rules.py +0 -0
  355. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_heat_exchanger_network/README.md +0 -0
  356. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_heat_exchanger_network/__init__.py +0 -0
  357. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_heat_exchanger_network/types.py +0 -0
  358. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_problem_table/README.md +0 -0
  359. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_problem_table/__init__.py +0 -0
  360. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_problem_table/constants.py +0 -0
  361. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_problem_table/equality.py +0 -0
  362. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_problem_table/intervals.py +0 -0
  363. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_problem_table/types.py +0 -0
  364. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_stream/README.md +0 -0
  365. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_stream/__init__.py +0 -0
  366. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_stream/linearisation.py +0 -0
  367. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_stream/profile.py +0 -0
  368. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_stream/segment.py +0 -0
  369. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_stream/segments.py +0 -0
  370. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_stream/thermodynamics.py +0 -0
  371. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_stream/value_state.py +0 -0
  372. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_stream_collection/README.md +0 -0
  373. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_stream_collection/__init__.py +0 -0
  374. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_stream_collection/filters.py +0 -0
  375. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_stream_collection/numeric_view.py +0 -0
  376. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_stream_collection/serialization.py +0 -0
  377. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_stream_collection/sorting.py +0 -0
  378. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_validation.py +0 -0
  379. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_value/README.md +0 -0
  380. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_value/__init__.py +0 -0
  381. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_value/coercion.py +0 -0
  382. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_value/resolution.py +0 -0
  383. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/_value/units.py +0 -0
  384. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/analysis.py +0 -0
  385. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/enums.py +0 -0
  386. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/fluids.py +0 -0
  387. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/heat_exchanger.py +0 -0
  388. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/heat_exchanger_network.py +0 -0
  389. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/hpr.py +0 -0
  390. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/problem_table.py +0 -0
  391. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/progress.py +0 -0
  392. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/solver_options.py +0 -0
  393. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/stream.py +0 -0
  394. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/stream_collection.py +0 -0
  395. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/targets.py +0 -0
  396. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/value.py +0 -0
  397. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/domain/zone.py +0 -0
  398. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/optimisation/__init__.py +0 -0
  399. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/optimisation/backends/README.md +0 -0
  400. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/optimisation/backends/__init__.py +0 -0
  401. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/optimisation/backends/_multistart.py +0 -0
  402. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/optimisation/backends/_surrogate.py +0 -0
  403. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/optimisation/backends/bayesian.py +0 -0
  404. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/optimisation/backends/cma_es.py +0 -0
  405. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/optimisation/backends/dual_annealing.py +0 -0
  406. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/optimisation/backends/protocol.py +0 -0
  407. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/optimisation/backends/rbf.py +0 -0
  408. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/optimisation/candidates.py +0 -0
  409. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/optimisation/errors.py +0 -0
  410. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/optimisation/feasibility.py +0 -0
  411. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/optimisation/models.py +0 -0
  412. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/optimisation/service.py +0 -0
  413. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/README.md +0 -0
  414. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/__init__.py +0 -0
  415. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/configuration.py +0 -0
  416. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/dashboard/README.md +0 -0
  417. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/dashboard/__init__.py +0 -0
  418. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/dashboard/dashboard.css +0 -0
  419. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/dashboard/dependencies.py +0 -0
  420. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/dashboard/exports.py +0 -0
  421. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/dashboard/rendering.py +0 -0
  422. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/dashboard/state.py +0 -0
  423. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/graphs/README.md +0 -0
  424. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/graphs/__init__.py +0 -0
  425. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/graphs/plotly.py +0 -0
  426. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/graphs/problem.py +0 -0
  427. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/graphs/simple.py +0 -0
  428. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/network_grid/README.md +0 -0
  429. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/network_grid/__init__.py +0 -0
  430. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/network_grid/_constants.py +0 -0
  431. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/network_grid/dependencies.py +0 -0
  432. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/network_grid/geometry.py +0 -0
  433. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/network_grid/labels.py +0 -0
  434. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/network_grid/layout.py +0 -0
  435. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/network_grid/plotly.py +0 -0
  436. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/network_grid/rendering.py +0 -0
  437. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/network_grid/results.py +0 -0
  438. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/network_grid/service.py +0 -0
  439. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/network_grid/state.py +0 -0
  440. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/network_grid/temperatures.py +0 -0
  441. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/network_grid/view.py +0 -0
  442. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/reporting/README.md +0 -0
  443. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/reporting/__init__.py +0 -0
  444. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/reporting/adapters.py +0 -0
  445. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/reporting/excel_text.py +0 -0
  446. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/reporting/hpr.py +0 -0
  447. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/reporting/problem_table.py +0 -0
  448. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/reporting/results.py +0 -0
  449. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/reporting/specialist.py +0 -0
  450. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/reporting/stream_collection.py +0 -0
  451. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/reporting/thermal.py +0 -0
  452. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/presentation/reporting/workbook.py +0 -0
  453. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/resources.py +0 -0
  454. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/README.md +0 -0
  455. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/__init__.py +0 -0
  456. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/01_first_solve_and_core_curves.ipynb +0 -0
  457. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/02_focused_direct_and_total_site.ipynb +0 -0
  458. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/03_multisegment_streams.ipynb +0 -0
  459. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/04_workspace_cases_and_scenarios.ipynb +0 -0
  460. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/05_workspace_persistence.ipynb +0 -0
  461. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/06_multiperiod_heat_integration.ipynb +0 -0
  462. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/07_area_cost_and_exergy.ipynb +0 -0
  463. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/08_carnot_heat_pump_and_refrigeration.ipynb +0 -0
  464. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/09_vapour_compression.ipynb +0 -0
  465. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/10_multiperiod_heat_pumps.ipynb +0 -0
  466. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/11_process_mvr_and_cascade.ipynb +0 -0
  467. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/12_cogeneration.ipynb +0 -0
  468. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/13_multiperiod_cogeneration.ipynb +0 -0
  469. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/14_energy_transfer.ipynb +0 -0
  470. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/15_hen_synthesis_and_selection.ipynb +0 -0
  471. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/16_advanced_hen_methods.ipynb +0 -0
  472. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/17_multiperiod_hen_synthesis.ipynb +0 -0
  473. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/18_results_plots_reports_exports.ipynb +0 -0
  474. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/19_utility_placement_optimisation.ipynb +0 -0
  475. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/README.md +0 -0
  476. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/notebooks/__init__.py +0 -0
  477. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/sample_cases/Four-stream-Yee-and-Grossmann-1990-1.json +0 -0
  478. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/sample_cases/README.md +0 -0
  479. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/sample_cases/__init__.py +0 -0
  480. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/sample_cases/basic_pinch.json +0 -0
  481. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/sample_cases/chocolate_factory.json +0 -0
  482. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/sample_cases/crude_preheat_train.json +0 -0
  483. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/sample_cases/crude_preheat_train_multiperiod.json +0 -0
  484. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/sample_cases/dairy_chilling_multiperiod.json +0 -0
  485. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/sample_cases/process_mvr.json +0 -0
  486. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/sample_cases/pulp_mill.json +0 -0
  487. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/sample_cases/zonal_site.json +0 -0
  488. {openpinch-0.8.0 → openpinch-0.8.2}/OpenPinch/tutorials/sample_cases/zonal_site_multiperiod.json +0 -0
  489. {openpinch-0.8.0 → openpinch-0.8.2}/README.md +0 -0
@@ -18,8 +18,9 @@ Resolves synthesis settings from a prepared problem, plans the quality-tier path
18
18
  ## Invariants
19
19
  - A downstream task needs a successful parent outcome and the parent's in-process solver problem; `_parent_problem` raises `WorkflowContractError`, but `LocalSynthesisExecutor.execute` catches it and returns a failed task outcome carrying the message, while `FakeSynthesisExecutor` lets it propagate [executor.py:LocalSynthesisExecutor._parent_problem] [executor.py:LocalSynthesisExecutor.execute] [fake_executor.py:FakeSynthesisExecutor.execute].
20
20
  - Tasks are built inside pool workers only when no parent outcomes exist, no task has a parent and more than one worker is in use [executor.py:_can_build_tasks_in_workers].
21
- - TDM and ESM models use a fixed approach of 0.1 K; PDM uses the task approach temperature [executor.py:_task_model_approach_temperature].
21
+ - TDM and ESM models use an approach of `max(0.1 K, HENS_MIN_EXCHANGER_APPROACH_SHARE x task approach)` (0.1 K at the default share of 0); PDM uses the task approach temperature [executor.py:_task_model_approach_temperature, LocalSynthesisExecutor._build_problem].
22
22
  - A solve counts as successful only when `mSuccess == 1` and `verify()` (if present) passes; failures carry a non-empty error string [executor.py:_solve_built_task, _failed_task_outcome].
23
+ - A successful solve whose solver stopped before converging records `diagnostics["unconverged_termination"]`; it stays a success so downstream tasks can start from it [executor.py:_solve_built_task].
23
24
  - Stage-packing scope `"auto"` resolves to `"none"` at execution time [executor.py:_stage_packing_scope]. The scope only decides packing for non-compact PDM tasks and for TDM tasks; PDM tasks with `pdm_mode == "compact"` (tier 0 and the compact routes of tiers 2 to 5) always use the stage-packed model [executor.py:LocalSynthesisExecutor._model_factories_for_task].
24
25
  - Outcomes are returned in the order of the input tasks [executor.py:LocalSynthesisExecutor._collect_outcomes].
25
26
  - Settings are a frozen dataclass; only `evm_solver_options` is copied on construction, while `pdm_solver_options` and `tdm_solver_options` are shared references [settings.py:SynthesisWorkflowSettings.__post_init__].
@@ -246,7 +246,10 @@ class LocalSynthesisExecutor:
246
246
  parent_outcomes: dict[str, HeatExchangerNetworkSynthesisTaskOutcome],
247
247
  ) -> InternalHeatExchangerNetworkProblem:
248
248
  parent_problem = self._parent_problem(task, parent_outcomes)
249
- dTmin = _task_model_approach_temperature(task)
249
+ dTmin = _task_model_approach_temperature(
250
+ task,
251
+ min_approach_share=problem.master_zone.config.hens.min_exchanger_approach_share,
252
+ )
250
253
  arrays = problem_to_solver_arrays(problem, dTmin)
251
254
  stage_selection = _pdm_stage_selection(problem, task)
252
255
  decompositions = None
@@ -407,6 +410,14 @@ def _solve_built_task(
407
410
  return task, _failed_task_outcome(task, reason), internal_problem
408
411
 
409
412
  network = internal_problem.extract_network(run_id=task.run_id)
413
+ # An unconverged solve stays a success so downstream tasks can start
414
+ # from it; demote_infeasible_outcomes keeps it out of the ranking.
415
+ solver_run = getattr(solved, "solver_run", None)
416
+ diagnostics = (
417
+ {}
418
+ if getattr(solver_run, "converged", True)
419
+ else {"unconverged_termination": str(solver_run.termination_condition)}
420
+ )
410
421
  return (
411
422
  task,
412
423
  HeatExchangerNetworkSynthesisTaskOutcome(
@@ -415,6 +426,7 @@ def _solve_built_task(
415
426
  network=network,
416
427
  objective_value=network.total_annual_cost,
417
428
  solver_status=_solver_status(internal_problem),
429
+ diagnostics=diagnostics,
418
430
  ),
419
431
  internal_problem,
420
432
  )
@@ -507,12 +519,20 @@ def _model_objective(method: HeatExchangerNetworkDesignMethod) -> str:
507
519
 
508
520
  def _task_model_approach_temperature(
509
521
  task: HeatExchangerNetworkSynthesisTask,
522
+ *,
523
+ min_approach_share: float = 0.0,
510
524
  ) -> float:
525
+ """Return the model's minimum approach temperature.
526
+
527
+ The pinch design step uses the task's approach. The thermal derivative and
528
+ evolution steps minimise cost down to 0.1 K, or to ``min_approach_share``
529
+ (``HENS_MIN_EXCHANGER_APPROACH_SHARE``) times the task's approach if larger.
530
+ """
511
531
  if task.method in {
512
532
  HeatExchangerNetworkDesignMethod.ThermalDerivative,
513
533
  HeatExchangerNetworkDesignMethod.NetworkEvolution,
514
534
  }:
515
- return 0.1
535
+ return max(0.1, min_approach_share * float(task.approach_temperature))
516
536
  return float(task.approach_temperature)
517
537
 
518
538
 
@@ -25,6 +25,7 @@ Concrete GEKKO-based equation models for heat exchanger network synthesis, plus
25
25
  - `ThermalProfile` is frozen; its arrays must share one non-zero length and `side` must be "hot" or "cold". Smoothing applies only to symbolic arguments; plain numbers are exact. [thermal_profiles.py:ThermalProfile.__post_init__] [thermal_profiles.py:smooth_max0]
26
26
  - Stage packing applies only when `integers` is true, `S > 1` and some stage has positive capacity. [stage_packing.py:add_recovery_stage_packing_constraints]
27
27
  - PDM construction needs "above" and "below" decompositions; stage-wise construction needs an explicit stage count or a parent solution. [problem.py:InternalHeatExchangerNetworkProblem._build_pdm] [problem.py:InternalHeatExchangerNetworkProblem._build_stage_wise]
28
+ - The amalgamated PDM case is built, not solved, so it takes the termination condition of the first required side whose solve did not converge; otherwise it keeps its own solver run. [problem.py:InternalHeatExchangerNetworkProblem._carry_unconverged_side_solve]
28
29
  - `get_solution` converts `ValueError` into `None` with `solution_failure_reason` set. [problem.py:InternalHeatExchangerNetworkProblem.get_solution]
29
30
  - Stage reduction keeps stages whose share of peak recovery meets a threshold (0.1, 5, 8, 10 percent for at most 3, 5, 8, 10 stages and 0 percent for more than 10; PDM and TDM solves only) and returns the original case unless the reduced model solves and verifies. [problem.py:InternalHeatExchangerNetworkProblem._utilisation_threshold] [problem.py:InternalHeatExchangerNetworkProblem.remove_unused_stages]
30
31
 
@@ -31,6 +31,7 @@ The parent imports `alpha`, `approach`, `execution`, `parameters` and `piecewise
31
31
  ## Side Effects
32
32
  - Creates a local (non-remote) GEKKO model and configures its solver. [execution.py:setup_model]
33
33
  - `optimise` always removes the GEKKO run directory afterwards; `cleanup_model` is safe to repeat. [execution.py:optimise] [execution.py:cleanup_model]
34
+ - A solve that stopped before converging (`solver_run.converged` false) keeps `mSuccess` and its values, but is logged as a warning (`[Unconverged]`) rather than `[Success]`. [execution.py:_solve_and_post_process]
34
35
  - `get_alpha_values` builds and solves a second GEKKO model and cleans it up. [alpha.py:get_alpha_values]
35
36
  - Writes solver results, logs and many attributes onto the model passed in (`mSuccess`, `solver_run`, `area_*`, `LMTD_*`, `z_allowed`). [execution.py:_solve_and_post_process] [area.py:set_utility_lmtd_and_areas]
36
37
  - Lazy imports of `OpenPinch.analysis.heat_exchanger_networks.solver.piecewise` inside segmented-stream branches. [area.py:_apply_segment_recovery_areas] [piecewise.py:_utility_cost_expression]
@@ -96,6 +96,16 @@ def _solve_and_post_process(model, print_output: bool) -> None:
96
96
  model.name,
97
97
  model.m._path,
98
98
  )
99
+ elif not model.solver_run.converged:
100
+ # The values are kept (evolution can start from them), but the
101
+ # objective is not a converged optimum to compare against.
102
+ model.mSuccess = model.m.options.SOLVESTATUS
103
+ logger.warning(
104
+ "[Unconverged] [model: %s] [path: %s] [termination: %s]",
105
+ model.name,
106
+ model.m._path,
107
+ model.solver_run.termination_condition,
108
+ )
99
109
  else:
100
110
  model.mSuccess = model.m.options.SOLVESTATUS
101
111
  logger.info(
@@ -19,7 +19,7 @@ Private helper modules for `StageWiseModel` (`../stagewise.py`), which delegates
19
19
  - Supported goals: "hot utility", "cold utility", "total utility", "utility costs", "heat recovery", "dQ/dA obj", "total cost", "variable total cost"; others raise `ValueError`. [objectives.py:set_obj]
20
20
  - Solver area terms use Chen's LMTD approximation; multiperiod total cost uses one shared area per unit bounded below by each period's requirement. [objectives.py:_chen_mean] [objectives.py:_set_multiperiod_total_cost_objective]
21
21
  - `verify` checks cost and area consistency only for the two total-cost goals; once those pass it also runs the reporting-layer network feasibility check. [verification.py:verify]
22
- - Evolution candidates are usable only if solved and `verify()` passes; the best is adopted only if its TAC is below the original and it verifies. [evolution.py:_is_usable_evolution_candidate] [evolution.py:_update_with_best_model]
22
+ - Evolution candidates are usable only if solved, converged (`solver_run.converged`) and `verify()` passes; the best is adopted only if it verifies and, when the starting solve converged, its TAC is below the original. When the starting solve did not converge its TAC is not a benchmark: the best usable candidate is adopted, and the workflow's final ranking compares it with the seed network on verified costs. [evolution.py:_is_usable_evolution_candidate] [evolution.py:_benchmark_model] [evolution.py:_update_with_best_model]
23
23
  - With more than one add or remove branch, or a patience limit, evolution keeps at most `beam_width` branches ranked by TAC then topology signature, solves each topology once, and orders parallel results by (branch, kind, rank) so serial and parallel runs agree. [evolution.py:get_net_benefit_evolution] [evolution.py:_solve_evolution_candidates]
24
24
  - With one add branch, one remove branch and no patience, the original single-path search runs. [evolution.py:get_net_benefit_evolution]
25
25
  - Evolution children are relaxed (`integers=False`, `tol=1e-3`) and use the root model's solver. [evolution.py:_build_and_solve_evolution]
@@ -28,7 +28,7 @@ Private helper modules for `StageWiseModel` (`../stagewise.py`), which delegates
28
28
 
29
29
  ## Side Effects
30
30
  - Evolution builds and solves child `StageWiseModel` instances (GEKKO models), in a `ThreadPoolExecutor` when `max_parallel` exceeds one and `supported_workers` allows it. [evolution.py:_solve_evolution_candidates]
31
- - `get_net_benefit_evolution` ends by calling `owner.cleanup()` on both search paths (only the early return when `owner.mSuccess != 1` skips it), and `_update_with_best_model` overwrites the owner's variables, `z_allowed`, alpha and cost attributes, then re-runs post-processing. [evolution.py:get_net_benefit_evolution] [evolution.py:_update_with_best_model]
31
+ - `get_net_benefit_evolution` ends by calling `owner.cleanup()` on both search paths (only the early return when `owner.mSuccess != 1` skips it), and `_update_with_best_model` overwrites the owner's variables, `z_allowed`, alpha, cost attributes and `solver_run`, then re-runs post-processing. [evolution.py:get_net_benefit_evolution] [evolution.py:_update_with_best_model]
32
32
  - `verify` lazily imports and runs `extract_heat_exchanger_network` and `verify_network_feasibility`. [verification.py:_check_network_feasibility]
33
33
  - Post-processing runs the post-optimisation alpha solve via `get_alpha_values`. [postprocess.py:_get_multiperiod_post_process]
34
34
  - Evolution and verification log mostly at debug level; exceptions in a candidate solve are caught and logged. [evolution.py:_solve_evolution_candidate]
@@ -82,8 +82,13 @@ def get_net_benefit_evolution(
82
82
  initial_z = getattr(owner, "z_allowed", None)
83
83
  if initial_z is not None:
84
84
  seen_signatures.add(_topology_signature_from_z(owner, initial_z))
85
- frontier = [_EvolutionBranchState(model=owner, best_tac=float(owner.TAC))]
86
- best_model = owner
85
+ best_model = _benchmark_model(owner)
86
+ frontier = [
87
+ _EvolutionBranchState(
88
+ model=owner,
89
+ best_tac=float(owner.TAC) if best_model is owner else float("inf"),
90
+ )
91
+ ]
87
92
 
88
93
  for unit in range(max_depth):
89
94
  logger.debug(
@@ -142,7 +147,7 @@ def get_net_benefit_evolution(
142
147
  )
143
148
  continue
144
149
  next_frontier.append(branch_period)
145
- if candidate.TAC < best_model.TAC:
150
+ if best_model is None or candidate.TAC < best_model.TAC:
146
151
  best_model = candidate
147
152
  # Keep the best branches by TAC, ties broken on topology signature, so
148
153
  # the frontier never grows past the beam width.
@@ -154,14 +159,14 @@ def get_net_benefit_evolution(
154
159
  logger.debug("No viable evolution model found.")
155
160
  break
156
161
 
157
- if best_model is not owner:
162
+ if best_model is not None and best_model is not owner:
158
163
  logger.debug(
159
164
  "New best evolution model %s with TAC %.6f",
160
165
  best_model.name,
161
166
  best_model.TAC,
162
167
  )
163
168
 
164
- if best_model.mSuccess and best_model.TAC < owner.TAC:
169
+ if best_model is not None and best_model is not owner:
165
170
  _update_with_best_model(owner, best_model)
166
171
  else:
167
172
  logger.debug("No evolution improvement found over original model.")
@@ -178,7 +183,7 @@ def _get_source_net_benefit_evolution(
178
183
  """Run the original OpenHENS single-path add/remove evolution search."""
179
184
 
180
185
  model = owner
181
- best_model = owner
186
+ best_model = _benchmark_model(owner)
182
187
  for unit in range(max_depth):
183
188
  logger.debug(
184
189
  "Evolution step %s/%s - Current TAC: %s",
@@ -205,7 +210,7 @@ def _get_source_net_benefit_evolution(
205
210
  if model is None:
206
211
  logger.debug("No viable model found; ending evolution.")
207
212
  break
208
- if model.TAC < best_model.TAC:
213
+ if best_model is None or model.TAC < best_model.TAC:
209
214
  best_model = model
210
215
  logger.debug(
211
216
  "New best model: %s found with TAC: %.6f",
@@ -213,7 +218,7 @@ def _get_source_net_benefit_evolution(
213
218
  best_model.TAC,
214
219
  )
215
220
 
216
- if best_model.mSuccess and best_model.TAC < owner.TAC:
221
+ if best_model is not None and best_model is not owner:
217
222
  _update_with_best_model(owner, best_model)
218
223
  else:
219
224
  logger.debug("No improvement found over original model.")
@@ -221,6 +226,27 @@ def _get_source_net_benefit_evolution(
221
226
  return owner
222
227
 
223
228
 
229
+ def _benchmark_model(owner):
230
+ """Return the starting model when its TAC is a fair benchmark, else None.
231
+
232
+ A solve that stopped before converging (for example at IPOPT's iteration
233
+ limit) is still reported as successful, but its objective can be far below
234
+ the cost of any real network. Evolution then keeps its best verified
235
+ candidate, and the workflow's final ranking compares it with the seed
236
+ network on verified costs.
237
+ """
238
+
239
+ solver_run = getattr(owner, "solver_run", None)
240
+ if getattr(solver_run, "converged", True):
241
+ return owner
242
+ logger.warning(
243
+ "Starting model %s did not converge; evolution candidates are not "
244
+ "compared with its TAC.",
245
+ getattr(owner, "name", None),
246
+ )
247
+ return None
248
+
249
+
224
250
  def _select_source_best_candidate(
225
251
  owner,
226
252
  current_model,
@@ -507,6 +533,8 @@ def _update_with_best_model(owner, best_model) -> None:
507
533
  owner.area_cu_shared = copy.deepcopy(best_model.area_cu_shared)
508
534
  owner.hu_area_cost_total = copy.deepcopy(best_model.hu_area_cost_total)
509
535
  owner.cu_area_cost_total = copy.deepcopy(best_model.cu_area_cost_total)
536
+ if hasattr(best_model, "solver_run"):
537
+ owner.solver_run = best_model.solver_run
510
538
  owner.get_post_process()
511
539
 
512
540
 
@@ -688,6 +716,9 @@ def _is_usable_evolution_candidate(candidate) -> bool:
688
716
  return False
689
717
  if not candidate.mSuccess:
690
718
  return False
719
+ # A solve stopped at a limit keeps mSuccess, but its TAC is not reliable.
720
+ if not getattr(getattr(candidate, "solver_run", None), "converged", True):
721
+ return False
691
722
  verify = getattr(candidate, "verify", None)
692
723
  if not callable(verify):
693
724
  return True
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from dataclasses import dataclass
5
+ from dataclasses import dataclass, replace
6
6
  from typing import Any, Literal, Mapping, Sequence
7
7
 
8
8
  from ....contracts.synthesis.result import HeatExchangerNetworkSynthesisResult
@@ -248,6 +248,7 @@ class InternalHeatExchangerNetworkProblem:
248
248
  below_case=self.below,
249
249
  above_case=self.above,
250
250
  )
251
+ self._carry_unconverged_side_solve()
251
252
  self.case.get_post_process()
252
253
  if print_output:
253
254
  self.case.output_to_cmd_line()
@@ -259,6 +260,23 @@ class InternalHeatExchangerNetworkProblem:
259
260
  }
260
261
  )
261
262
 
263
+ def _carry_unconverged_side_solve(self) -> None:
264
+ # The amalgamated model is built, not solved, so its own solver run
265
+ # says nothing about the sides' convergence.
266
+ for side in (self.above, self.below):
267
+ side_run = getattr(side, "solver_run", None)
268
+ if side.side_required and not getattr(side_run, "converged", True):
269
+ case_run = getattr(self.case, "solver_run", None)
270
+ self.case.solver_run = (
271
+ side_run
272
+ if case_run is None
273
+ else replace(
274
+ case_run,
275
+ termination_condition=side_run.termination_condition,
276
+ )
277
+ )
278
+ return
279
+
262
280
  def remove_unused_stages(self, case) -> StageWiseModel:
263
281
  """Apply the source stage-utilisation reduction after PDM/TDM solves."""
264
282
 
@@ -6,7 +6,7 @@ Turns executed task outcomes into the canonical synthesis result, resolves seed
6
6
  ## Interface
7
7
  - `SynthesisWorkflowResult` (`assembly.py`): frozen dataclass of tasks, outcomes, accepted result and run time.
8
8
  - `build_synthesis_result` (`assembly.py`): demotes infeasible outcomes, accepts the best one and builds the result with its manifest.
9
- - `demote_infeasible_outcomes` (`assembly.py`): marks solver successes that fail `verify_network_feasibility` as failed.
9
+ - `demote_infeasible_outcomes` (`assembly.py`): marks solver successes that fail `verify_network_feasibility`, or that carry `diagnostics["unconverged_termination"]`, as failed.
10
10
  - `resolve_seed_networks` (`seeds.py`): returns explicit seed networks or the cached upstream design network.
11
11
  - `ranked_networks` (`selection.py`): unique ranked outcomes of a result.
12
12
  - `network_for_rank` (`selection.py`): network at a one-based rank, without mutation.
@@ -14,7 +14,7 @@ Turns executed task outcomes into the canonical synthesis result, resolves seed
14
14
 
15
15
  ## Invariants
16
16
  - The accepted outcome is the successful outcome with the lowest `outcome_ranking_key`, whichever method produced it; ties are broken by task id [assembly.py:_accepted_outcome].
17
- - Infeasible outcomes are demoted before acceptance, keeping their network and a reason; `WorkflowContractError` is raised only when no success remains [assembly.py:demote_infeasible_outcomes, _accepted_outcome].
17
+ - Infeasible and unconverged outcomes are demoted before acceptance, keeping their network and a reason; `WorkflowContractError` is raised only when no success remains [assembly.py:demote_infeasible_outcomes, _accepted_outcome].
18
18
  - The accepted network must be present, and its run id, task id, period id, method are overwritten from settings and task, and the stage count is kept from the network when it has one [assembly.py:build_synthesis_result].
19
19
  - Manifest approach temperatures are those the tasks actually ran, falling back to the settings values [assembly.py:build_synthesis_result].
20
20
  - Manifest `method_sequence` lists each method once, in outcome order (PDM, TDM, EVM, though tiers above 1 also run some EVM tasks before TDM), without outcomes that failed because Couenne is missing; `settings.method_sequence` does not affect it [assembly.py:_executed_method_sequence].
@@ -179,10 +179,23 @@ def demote_infeasible_outcomes(
179
179
 
180
180
  One infeasible outcome no longer aborts the synthesis: it keeps its network
181
181
  and reasons for diagnostics, and the result is selected from the feasible
182
- outcomes. Selection raises only when none is left.
182
+ outcomes. Selection raises only when none is left. A solve that stopped
183
+ before converging is demoted too: it stayed a success only so downstream
184
+ tasks could start from it, and its objective is not a converged optimum.
183
185
  """
184
186
  demoted = []
185
187
  for outcome in outcomes:
188
+ termination = outcome.diagnostics.get("unconverged_termination")
189
+ if outcome.status == "success" and termination is not None:
190
+ demoted.append(
191
+ outcome.model_copy(
192
+ update={
193
+ "status": "failed",
194
+ "error": f"solver stopped before converging ({termination})",
195
+ }
196
+ )
197
+ )
198
+ continue
186
199
  failures = (
187
200
  verify_network_feasibility(outcome.network)
188
201
  if outcome.status == "success"
@@ -7,7 +7,7 @@ Everything between a prepared problem and the equation models: solver arrays bui
7
7
  - `problem_to_solver_arrays`, `PreparedSolverArrays`, `SEGMENT_PROFILE_VERSION`, `annual_cost_scales` (`arrays.py`): prepared-problem to padded, period-indexed arrays, axis maps, identities and unit conventions.
8
8
  - `PiecewiseThermalProfile`, `profile_from_solver_arrays`, `utility_thermal_profile`, `duty_aligned_area_contributions`, `add_piecewise_temperature_mapping`, `add_piecewise_cost_mapping`, `add_piecewise_temperature_contribution_mapping` (`piecewise.py`): segment profiles, duty-aligned area slices, and model constraints mapping duty to temperature, cost or contribution.
9
9
  - `build_pinch_design_decomposition`, `PinchDesignDecomposition`, `PinchDesignTarget`, `PinchLocation`, `StageSelection` (`pinch_design_decomposition.py`): per-period pinch targets and clipped active-stream fields for the PDM.
10
- - `SolverRun`, `create_gekko_model`, `cleanup_gekko_model`, `require_solver_backend`, `configure_gekko_solver`, `solve_gekko_model`, `suppress_gekko_numpy_array_copy_deprecation`, `PYOMO_SOLVERS`, `GEKKO_SOLVERS`, `DEFAULT_IPOPT_GEKKO_OPTIONS` (`backend.py`): solver selection, option files and normalised solves.
10
+ - `SolverRun`, `create_gekko_model`, `cleanup_gekko_model`, `require_solver_backend`, `configure_gekko_solver`, `solve_gekko_model`, `suppress_gekko_numpy_array_copy_deprecation`, `PYOMO_SOLVERS`, `GEKKO_SOLVERS`, `CONVERGED_TERMINATION_CONDITIONS`, `DEFAULT_IPOPT_GEKKO_OPTIONS` (`backend.py`): solver selection, option files and normalised solves.
11
11
  - `require_synthesis_dependency`, `require_declared_synthesis_dependencies`, `require_solver_binary`, `SYNTHESIS_DEPENDENCIES`, `SynthesisDependency`, `SYNTHESIS_EXTRA`, `MissingSynthesisDependencyError`, `MissingSynthesisSolverError` (`dependencies.py`): optional package and executable guards.
12
12
 
13
13
  ## Invariants
@@ -20,8 +20,11 @@ Everything between a prepared problem and the equation models: solver arrays bui
20
20
  - Exchanger duty beyond a profile raises, except for solver noise within a relative allowance that is dropped [piecewise.py:duty_aligned_area_contributions].
21
21
  - The PDM decomposition works on a deep copy of the master zone, and its stage boundary count satisfies `K = S + 1` [pinch_design_decomposition.py:_zone_with_hen_dt_contribution, PinchDesignDecomposition].
22
22
  - Shifted pinch temperature is the selected pinch (cold pinch when the hot utility target is zero, otherwise hot pinch) in K [pinch_design_decomposition.py:_shifted_pinch_temperature].
23
+ - A stream is active on a side of the pinch only if it reaches more than `tol` past its pinch temperature, so a stream that starts or ends at the pinch is not kept on the far side as a sliver [pinch_design_decomposition.py:_clip_stream_temperatures].
23
24
  - Solver names must be in `PYOMO_SOLVERS | GEKKO_SOLVERS`; solver options are validated [backend.py:require_solver_backend, _normalise_solver_options].
24
25
  - Pyomo solves that need an option file are serialised by a process-wide lock [backend.py:_solver_working_directory].
26
+ - `create_gekko_model` returns a GEKKO subclass that records each Pyomo solve's termination condition on `SolverRun.termination_condition`. GEKKO reports a solve that stopped at an iteration or time limit as successful; `SolverRun.converged` is false for any condition outside `CONVERGED_TERMINATION_CONDITIONS`, and true when none was recorded (GEKKO-native solvers) [backend.py:create_gekko_model, SolverRun.converged].
27
+ - That subclass and its Pyomo converter are built on first use (importing `backend` does not import gekko) and published once as module attributes of `backend`, so models from `create_gekko_model` pickle for process pool workers [backend.py:_termination_recording_class, __getattr__].
25
28
 
26
29
  ## Side Effects
27
30
  - Pyomo solves with an option file (solver options supplied) change the process working directory under a per-process thread lock, restoring it afterwards [backend.py:_solver_working_directory].
@@ -178,6 +178,7 @@ def problem_to_solver_arrays(
178
178
  "dt_cont_multipliers",
179
179
  None,
180
180
  ),
181
+ "HENS_MIN_EXCHANGER_APPROACH_SHARE": hens.min_exchanger_approach_share,
181
182
  "HENS_SYNTHESIS_QUALITY_TIER": hens.synthesis_quality_tier,
182
183
  "HENS_PDM_STAGE_PAIR_LIMIT": hens.pdm_stage_pair_limit,
183
184
  "HENS_TDM_PARENT_LIMIT": hens.tdm_parent_limit,
@@ -38,6 +38,12 @@ DEFAULT_IPOPT_GEKKO_OPTIONS: dict[str, Any] = {
38
38
  "max_iter": 1000,
39
39
  "print_level": 5,
40
40
  }
41
+ # Pyomo termination conditions of a converged solve. GEKKO's Pyomo extension
42
+ # only warns on the others (an iteration or time limit, for example) and still
43
+ # reports the solve as successful.
44
+ CONVERGED_TERMINATION_CONDITIONS = frozenset(
45
+ {"optimal", "locallyOptimal", "globallyOptimal", "feasible"}
46
+ )
41
47
  GEKKO_NUMPY_ARRAY_COPY_DEPRECATION = (
42
48
  r"__array__ implementation doesn't accept a copy keyword.*"
43
49
  )
@@ -55,17 +61,78 @@ class SolverRun:
55
61
  failure_reason: str | None = None
56
62
  solver_options: dict[str, Any] | None = None
57
63
  option_file: str | None = None
64
+ termination_condition: str | None = None
65
+
66
+ @property
67
+ def converged(self) -> bool:
68
+ """False when the solver stopped before converging, for example at its
69
+ iteration limit. Solves without a recorded condition count as converged."""
70
+
71
+ return (
72
+ self.termination_condition is None
73
+ or self.termination_condition in CONVERGED_TERMINATION_CONDITIONS
74
+ )
58
75
 
59
76
 
60
77
  def create_gekko_model(*, remote: bool = False) -> Any:
61
- """Create a GEKKO model after the optional dependency has been requested."""
78
+ """Create a GEKKO model after the optional dependency has been requested.
79
+
80
+ The model records the Pyomo termination condition of each solve, which
81
+ GEKKO itself only prints.
82
+ """
62
83
 
63
- gekko = require_synthesis_dependency(
84
+ require_synthesis_dependency(
64
85
  "gekko",
65
86
  package="gekko",
66
87
  purpose="GEKKO heat exchanger network equation model construction",
67
88
  )
68
- return gekko.GEKKO(remote=remote)
89
+ return _termination_recording_class("_TerminationRecordingGEKKO")(remote=remote)
90
+
91
+
92
+ def __getattr__(name: str) -> type:
93
+ # Pickle looks classes up by module and name, so unpickling a model in a
94
+ # process pool worker resolves the lazily built classes here.
95
+ if name in _TERMINATION_RECORDING_CLASS_NAMES:
96
+ return _termination_recording_class(name)
97
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
98
+
99
+
100
+ _TERMINATION_RECORDING_CLASS_NAMES = frozenset(
101
+ {"_TerminationRecordingGEKKO", "_TerminationRecordingPyomoConverter"}
102
+ )
103
+ _TERMINATION_RECORDING_CLASS_LOCK = threading.Lock()
104
+
105
+
106
+ def _termination_recording_class(name: str) -> type:
107
+ """Return a GEKKO subclass published as a module attribute of this module.
108
+
109
+ The classes are built on first use, so importing this module does not
110
+ import gekko, and published once, so models pickle by reference to them.
111
+ """
112
+
113
+ with _TERMINATION_RECORDING_CLASS_LOCK:
114
+ if name not in globals():
115
+ _define_termination_recording_classes()
116
+ return globals()[name]
117
+
118
+
119
+ def _define_termination_recording_classes() -> None:
120
+ from gekko import GEKKO
121
+ from gekko.gk_solver_extension_pyomo import PyomoConverter
122
+
123
+ class _TerminationRecordingPyomoConverter(PyomoConverter):
124
+ def handle_status(self, status_dict, status, status_string) -> None:
125
+ if isinstance(status, self.TerminationCondition):
126
+ self._gekko_model._openpinch_termination_condition = str(status)
127
+ super().handle_status(status_dict, status, status_string)
128
+
129
+ class _TerminationRecordingGEKKO(GEKKO):
130
+ def solver_extension_pyomo(self, disp=True):
131
+ self.solve_with_converter(_TerminationRecordingPyomoConverter, disp)
132
+
133
+ for cls in (_TerminationRecordingPyomoConverter, _TerminationRecordingGEKKO):
134
+ cls.__qualname__ = cls.__name__
135
+ globals()[cls.__name__] = cls
69
136
 
70
137
 
71
138
  def cleanup_gekko_model(model: Any) -> None:
@@ -208,6 +275,7 @@ def solve_gekko_model(
208
275
 
209
276
  extension = getattr(model.options, "SOLVER_EXTENSION", None)
210
277
  _restore_run_directory(model)
278
+ model._openpinch_termination_condition = None
211
279
  start = time.time()
212
280
  failure_reason = None
213
281
  try:
@@ -232,6 +300,7 @@ def solve_gekko_model(
232
300
  failure_reason=failure_reason,
233
301
  solver_options=getattr(model, "_openpinch_solver_options", None),
234
302
  option_file=getattr(model, "_openpinch_solver_option_file", None),
303
+ termination_condition=model._openpinch_termination_condition,
235
304
  )
236
305
 
237
306
 
@@ -474,13 +474,15 @@ def _clip_stream_temperatures(
474
474
  ) -> tuple[tuple[int, ...], tuple[int, ...]]:
475
475
  hot_thresholds = shifted_pinch_temperature + np.asarray(hot_contributions)
476
476
  cold_thresholds = shifted_pinch_temperature - np.asarray(cold_contributions)
477
+ # The problem table rounds the pinch to tol, so a stream that starts or ends
478
+ # at the pinch can look a sliver past it. It is not active on that side.
477
479
  if pinch_location == "above":
478
480
  z_i_active = tuple(
479
- 1 if value > threshold else 0
481
+ 1 if value > threshold + tol else 0
480
482
  for value, threshold in zip(T_h_in, hot_thresholds)
481
483
  )
482
484
  z_j_active = tuple(
483
- 1 if value > threshold else 0
485
+ 1 if value > threshold + tol else 0
484
486
  for value, threshold in zip(T_c_out, cold_thresholds)
485
487
  )
486
488
  for index, is_active in enumerate(z_i_active):
@@ -497,11 +499,11 @@ def _clip_stream_temperatures(
497
499
  T_c_out[index] = 0.0
498
500
  else:
499
501
  z_i_active = tuple(
500
- 1 if value < threshold else 0
502
+ 1 if value < threshold - tol else 0
501
503
  for value, threshold in zip(T_h_out, hot_thresholds)
502
504
  )
503
505
  z_j_active = tuple(
504
- 1 if value < threshold else 0
506
+ 1 if value < threshold - tol else 0
505
507
  for value, threshold in zip(T_c_in, cold_thresholds)
506
508
  )
507
509
  for index, is_active in enumerate(z_i_active):
@@ -33,7 +33,7 @@ multi-stage vapour-compression objective, and residual utility accounting.
33
33
  - A cascade stage whose work is at most `NEGLIGIBLE_STAGE_WORK_FRACTION` of the machine's work is not charged stage capital. [shared.py:_single_cost_unit]
34
34
  - The feasibility penalty is `rho * sum(r + r^2)` with violations relative to `Q_hpr_target`. Vapour results price it at the no-heat-pump annual cost, at least 1 $/y; Carnot results scale it by `max(Q_hpr_target, 1) * max(price ratio, 1)` in power-equivalent units, with violations measured against `max(Q_hpr_target, 1)`, with no annual-cost scale or floor. [shared.py:_cycle_penalty] [shared.py:hpr_penalty_cost_scale] [optimisation_adapter.py:build_hpr_accounting]
35
35
  - Errors raised after a cycle has solved are re-raised, not ranked as infeasible candidates. [multi_vc_objective.py:_evaluate_multi_vc_objective] [shared.py:post_solve_error]
36
- - Ambient air and cooling water are applied through `cascade_with_air` on the condenser and evaporator cascades; cooling water applies on the evaporator side only. [shared.py:evaluate_vapour_hpr_result]
36
+ - Ambient air and cooling water are applied through `cascade_with_air` on the condenser and evaporator cascades; cooling water applies on the evaporator side only. Air is left out when `args.ambient_air_enabled` is false (`HPR_AMBIENT_AIR_ENABLED`). [shared.py:_cascade_air_duties]
37
37
  - `calc_carnot_heat_pump_cop` and `calc_carnot_heat_engine_eta` take absolute temperatures (K); `satisfies_second_law` takes degC and adds 273.15 internally, and `compute_entropic_mean_temperature` takes degC by default and returns K. [shared.py:calc_carnot_heat_pump_cop] [shared.py:satisfies_second_law]
38
38
  - Residual loads below `max(tol, 1e-7 * largest)` are treated as rounding noise and set to zero; duplicate residual temperatures with conflicting values raise `ValueError`. [postprocessing.py:_residual_noise_floor] [postprocessing.py:_deduplicate_residual_profile_rows]
39
39
  - `common` imports the parent's `optimisation_adapter` and `performance_maps.targeting` for accounting and backend names. [shared.py] [preprocessing.py]
@@ -21,7 +21,7 @@ What the parent and siblings import from this package:
21
21
  ## Invariants
22
22
  - Air and cooling water never create a utility demand: the duties they take or give are limited by the smallest cascade flow on the affected side, and cannot be negative. [air.py:cascade_with_air]
23
23
  - The cascade is supplied with `T` descending and `H_net` clipped to non-negative before use. [air.py:cascade_with_air]
24
- - Air supplies heat before it takes heat, and cooling water acts only on what air leaves. [air.py:cascade_with_air]
24
+ - Air supplies heat before it takes heat, and cooling water acts only on what air leaves. With `air=False` no air is placed and cooling water acts on the whole cascade. [air.py:cascade_with_air]
25
25
  - A sink at or above the top of the cascade takes no heat. [air.py:_sink_duty]
26
26
  - Ambient and cooling-water levels are shifted by `dt_env_cont`, `dt_cooling_water` and `dtcont_hp`, with `dtcont_hp` applied in the opposite sense on the source and sink sides. [air.py:ambient_source_temperature]
27
27
  - Carnot condenser and evaporator levels closer than `dt_phase_change` are merged and their duties summed before streams are built. [streams.py:_get_carnot_hpr_cycle_cascade_profile]
@@ -99,6 +99,7 @@ def cascade_with_air(
99
99
  T_air_sink: float,
100
100
  T_cooling_water: float | None = None,
101
101
  utility_bands: Sequence[tuple[float, float]] = (),
102
+ air: bool = True,
102
103
  ) -> CascadeWithAir:
103
104
  """Place free ambient air in a feasible cascade and return external duties.
104
105
 
@@ -113,7 +114,8 @@ def cascade_with_air(
113
114
  saves the dearer hot utility, then takes heat from what flows on.
114
115
  Cooling water at ``T_cooling_water`` then takes heat the same way from
115
116
  what is left. A level inside one of ``utility_bands``, each ``(upper, lower)``,
116
- moves to the end of that band, so the band counts as at the level.
117
+ moves to the end of that band, so the band counts as at the level. With
118
+ ``air`` false no air is placed and only cooling water acts.
117
119
  """
118
120
  T = np.asarray(T, dtype=float)
119
121
  H_net = np.maximum(np.asarray(H_net, dtype=float), 0.0)
@@ -132,7 +134,7 @@ def cascade_with_air(
132
134
  # Air heat enters at the source level and can only meet deficits below it;
133
135
  # it also lowers every flow above that level, which must stay non-negative.
134
136
  Q_air_source = 0.0
135
- if T_air_source > T_grid[-1]:
137
+ if air and T_air_source > T_grid[-1]:
136
138
  above = T_grid >= T_air_source
137
139
  at = float(np.interp(T_air_source, T_grid[::-1], H_grid[::-1]))
138
140
  absorbed_below = at - float(H_grid[T_grid <= T_air_source].min())
@@ -140,7 +142,7 @@ def cascade_with_air(
140
142
  Q_air_source = max(min(flow_limit, absorbed_below), 0.0)
141
143
 
142
144
  remaining = H_grid - np.where(_at_or_above(T_grid, T_air_source), Q_air_source, 0.0)
143
- Q_air_sink = _sink_duty(T_grid, remaining, T_air_sink)
145
+ Q_air_sink = _sink_duty(T_grid, remaining, T_air_sink) if air else 0.0
144
146
  remaining = remaining - np.where(_at_or_below(T_grid, T_air_sink), Q_air_sink, 0.0)
145
147
  Q_cooling_water = (
146
148
  0.0
@@ -118,6 +118,7 @@ def construct_HPRTargetInputs(
118
118
  max_pressure_ratio=hpr.max_pressure_ratio,
119
119
  T_env=environment.temperature,
120
120
  dt_env_cont=hpr.dt_env_cont,
121
+ ambient_air_enabled=hpr.ambient_air_enabled,
121
122
  dt_phase_change=thermal.dt_phase_change,
122
123
  eta_penalty=hpr.eta_penalty,
123
124
  rho_penalty=hpr.rho_penalty,
@@ -428,6 +428,7 @@ def _cascade_air_duties(
428
428
  and getattr(args, "background_is_utility", False)
429
429
  else ()
430
430
  ),
431
+ air=getattr(args, "ambient_air_enabled", True),
431
432
  )
432
433
 
433
434
 
@@ -18,7 +18,7 @@ One module per heat pump and refrigeration (HPR) cycle topology. Each defines ho
18
18
  - Carnot objective vectors hold condenser fractions, evaporator fractions and heat-split fractions, all bounded to [0, 1]; the initial point starts every heat split at 1 [_carnot_common.py:carnot_opt_setup].
19
19
  - Parallel Carnot and parallel vapour compression equalise `n_cond` and `n_evap` to the larger value on `args`, and callers rely on that after the solve [_carnot_common.py:normalise_carnot_stage_counts, parallel_vapour_compression.py:optimise_parallel_heat_pump_placement].
20
20
  - Condenser temperatures are capped and evaporator temperatures floored through `common.shared` helpers before use [cascade_vapour_compression.py:_parse_cascade_hp_state_variables, parallel_vapour_compression.py:_parse_parallel_hp_state_temperatures].
21
- - Air is a partner for refrigeration heat sink and heat pump heat source only [_carnot_common.py:parse_carnot_state_variables].
21
+ - Air is a partner for refrigeration heat sink and heat pump heat source only, and adds no availability when `ambient_air_enabled` is false [_carnot_common.py:parse_carnot_state_variables].
22
22
  - The Carnot warm start is clipped into the refrigerant's subcritical condensing range and seed duties are encoded against availability at the seed temperatures [cascade_vapour_compression.py:_get_cascade_hp_opt_setup, parallel_vapour_compression.py:_get_parallel_hp_opt_setup, vapour_compression_mvr.py:_get_vc_mvr_opt_setup].
23
23
  - Cascade process-control counts depend on mode: a heat pump has `n_cond` heat controls and `n_evap - 1` cool controls, a refrigerator the reverse, with its heat controls on `T_cond[1:]` because the top stage takes the remaining heat [cascade_vapour_compression.py:_cascade_process_control_counts].
24
24
  - VC+MVR state arrays are ordered [MVR stages, VC stages]; the MVR lift is at most `MAX_MVR_STAGE_LIFT` (20 K) [vapour_compression_mvr.py:_unpack_vc_mvr_state, vapour_compression_mvr.py:_parse_vc_mvr_state_variables].
@@ -101,7 +101,11 @@ def parse_carnot_state_variables(
101
101
  # Air can be a refrigerator's heat sink and a heat pump's heat source, but
102
102
  # never a heat pump's load or a refrigerator's duty. Where it is a partner
103
103
  # it adds generous availability; the cascade decides how much air is used.
104
- air_allowance = float(args.Q_heat_max + args.Q_cool_max)
104
+ air_allowance = (
105
+ float(args.Q_heat_max + args.Q_cool_max)
106
+ if getattr(args, "ambient_air_enabled", True)
107
+ else 0.0
108
+ )
105
109
  heat_air = 0.0 if args.is_heat_pumping else air_allowance
106
110
  cool_air = air_allowance if args.is_heat_pumping else 0.0
107
111
  H_cold_available = args.H_cold + args.z_amb_cold * heat_air
@@ -340,6 +340,12 @@ class _DesignAccessor:
340
340
  """Run the default HEN synthesis workflow.
341
341
 
342
342
  It runs quality tier 0 unless ``HENS_SYNTHESIS_QUALITY_TIER`` is set.
343
+
344
+ ``approach_temperatures`` sets the pinch design step's approach. The
345
+ later thermal derivative and evolution steps choose each exchanger's
346
+ approach by total annual cost, down to 0.1 K, or down to
347
+ ``HENS_MIN_EXCHANGER_APPROACH_SHARE`` (in ``options``) times the
348
+ requested approach if that is larger.
343
349
  """
344
350
  from ....analysis.heat_exchanger_networks.service import (
345
351
  _heat_exchanger_network_enhanced_synthesis_method_service,
@@ -293,6 +293,7 @@ class HeatPumpTargetInputs(BaseModel):
293
293
  max_multi_start: int
294
294
  T_env: float
295
295
  dt_env_cont: float
296
+ ambient_air_enabled: bool = True
296
297
  eta_ii_hpr_carnot: float
297
298
  eta_ii_he_carnot: float
298
299
  refrigerant_ls: List[str]