heatpumplab 0.1.0__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.
- heatpumplab-0.1.0/.gitignore +27 -0
- heatpumplab-0.1.0/HeatPumpLab/__init__.py +17 -0
- heatpumplab-0.1.0/HeatPumpLab/components/__init__.py +29 -0
- heatpumplab-0.1.0/HeatPumpLab/components/common/__init__.py +5 -0
- heatpumplab-0.1.0/HeatPumpLab/components/common/base_heat_exchanger.py +496 -0
- heatpumplab-0.1.0/HeatPumpLab/components/compressor.py +166 -0
- heatpumplab-0.1.0/HeatPumpLab/components/custom_relationship.py +355 -0
- heatpumplab-0.1.0/HeatPumpLab/components/economiser.py +180 -0
- heatpumplab-0.1.0/HeatPumpLab/components/evaporator.py +30 -0
- heatpumplab-0.1.0/HeatPumpLab/components/expansion_valve.py +64 -0
- heatpumplab-0.1.0/HeatPumpLab/components/gas_cooler.py +31 -0
- heatpumplab-0.1.0/HeatPumpLab/components/heat_exchanger.py +33 -0
- heatpumplab-0.1.0/HeatPumpLab/components/mixer.py +73 -0
- heatpumplab-0.1.0/HeatPumpLab/components/plate_heat_exchanger.py +38 -0
- heatpumplab-0.1.0/HeatPumpLab/components/setRelationship.py +79 -0
- heatpumplab-0.1.0/HeatPumpLab/components/splitter.py +64 -0
- heatpumplab-0.1.0/HeatPumpLab/components/stream.py +229 -0
- heatpumplab-0.1.0/HeatPumpLab/components/turbine.py +44 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/BraytonHeatPump/__init__.py +12 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/BraytonHeatPump/brayton_hp.py +558 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/CascadeHeatPump/__init__.py +6 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/CascadeHeatPump/cascade_hp.py +502 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/ConventionalHeatPump/__init__.py +66 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/ConventionalHeatPump/compressor_curves.py +540 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/ConventionalHeatPump/compressor_data.py +117 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/ConventionalHeatPump/compressor_window.py +205 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/ConventionalHeatPump/conventional_hp.py +771 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/ConventionalHeatPump/density_basis.py +291 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/ConventionalHeatPump/water_steam.py +123 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/MVRHeatPump/__init__.py +13 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/MVRHeatPump/mvr_hp.py +540 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/ABOUT.md +24 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/BAYESIAN_OPTIMISATION.md +107 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/BOTORCH_COMPARISON.md +95 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/BOTORCH_FAILURE_INVESTIGATION.md +100 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/BOTORCH_PROTOCOL.md +100 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/NEAR_ZERO_INVESTIGATION.md +184 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/OPTIMISER_COMPARISON.md +168 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/OPTIMISER_RETEST_V13_400.md +32 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/ORIGIN_OPTIMISER_COMPARISON.md +112 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/PH_INTERPOLATION_INVESTIGATION.md +57 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/REMAINING_ISSUES_INVESTIGATION.md +91 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/ROBUSTNESS_INVESTIGATION.md +621 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/SMOOTHNESS_FIXES.md +98 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/TRUST_REGION_COMPARISON.md +121 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/TRUST_REGION_PROTOCOL.md +101 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/V14_SOLVER_FIXES.md +61 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/V15_SOLVER_FIXES.md +71 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/V17_INTERNAL_PH_FALLBACK.md +50 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/__init__.py +25 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/_refrigerant.py +141 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/optimisation/__init__.py +56 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/optimisation/_batch.py +248 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/optimisation/_bayesian.py +424 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/optimisation/_cases.py +262 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/optimisation/_constraint_model.py +102 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/optimisation/_incumbents.py +280 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/optimisation/_isolated_evaluation.py +207 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/optimisation/_objective.py +551 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/optimisation/_results_io.py +398 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/optimisation/_torch_env.py +25 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/optimisation/_types.py +327 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/optimisation/origin_search.py +115 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/optimisation/outer_design.py +119 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/optimisation/surrogate_search.py +173 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/rhp_base.py +259 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/rhp_helpers.py +781 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/solver/__init__.py +5 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/solver/_cycle_analysis.py +138 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/solver/_discharge_pressure.py +403 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/solver/_evaporator_alignment.py +100 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/solver/_fluid_policy.py +20 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/solver/_gas_cooler_pinch.py +180 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/solver/_metrics.py +91 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/solver/cycle_solver.py +181 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/utils/__init__.py +17 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/utils/analysis.py +516 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/utils/cases.py +243 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/utils/composition_pareto.py +170 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/utils/fluid_properties_table.py +73 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/utils/optimiser_benchmark_report.py +164 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/utils/origin_benchmark_report.py +260 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/utils/plot_comp2_power_frac_analysis.py +401 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/utils/plot_heatmap_metrics.py +495 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/utils/single_case_analysis.py +159 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/RecuperativeHeatPump/utils/summarize_best_results.py +313 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/SplitHeatPump/__init__.py +5 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/SplitHeatPump/split_hp.py +452 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/TTCascadeHeatPump/__init__.py +8 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/TTCascadeHeatPump/tt_cascade.py +498 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/__init__.py +1 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/common/__init__.py +27 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/common/dryer.py +611 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/common/heatPump.py +94 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/common/scalar_search.py +191 -0
- heatpumplab-0.1.0/HeatPumpLab/cycles/common/solver.py +120 -0
- heatpumplab-0.1.0/HeatPumpLab/utils/__init__.py +33 -0
- heatpumplab-0.1.0/HeatPumpLab/utils/analysis.py +219 -0
- heatpumplab-0.1.0/HeatPumpLab/utils/coolprop_properties.py +858 -0
- heatpumplab-0.1.0/HeatPumpLab/utils/decorator_functions.py +16 -0
- heatpumplab-0.1.0/HeatPumpLab/utils/graph.py +447 -0
- heatpumplab-0.1.0/HeatPumpLab/utils/parallel_solving.py +55 -0
- heatpumplab-0.1.0/HeatPumpLab/utils/properties_package.py +789 -0
- heatpumplab-0.1.0/HeatPumpLab/utils/property_cache.py +59 -0
- heatpumplab-0.1.0/HeatPumpLab/utils/readWriteOutput.py +126 -0
- heatpumplab-0.1.0/HeatPumpLab/utils/solve_profile.py +92 -0
- heatpumplab-0.1.0/LICENSE +177 -0
- heatpumplab-0.1.0/PKG-INFO +263 -0
- heatpumplab-0.1.0/README.md +234 -0
- heatpumplab-0.1.0/apps/dryer/README.md +38 -0
- heatpumplab-0.1.0/apps/steam/README.md +40 -0
- heatpumplab-0.1.0/pyproject.toml +72 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Python environments and generated caches
|
|
2
|
+
*.py[cod]
|
|
3
|
+
__pycache__/
|
|
4
|
+
/.venv/
|
|
5
|
+
.pytest_cache/
|
|
6
|
+
.ruff_cache/
|
|
7
|
+
.mypy_cache/
|
|
8
|
+
.hypothesis/
|
|
9
|
+
.ipynb_checkpoints/
|
|
10
|
+
|
|
11
|
+
# Python packaging and coverage reports
|
|
12
|
+
/build/
|
|
13
|
+
/dist/
|
|
14
|
+
*.egg-info/
|
|
15
|
+
htmlcov/
|
|
16
|
+
|
|
17
|
+
# macOS metadata
|
|
18
|
+
.DS_Store
|
|
19
|
+
|
|
20
|
+
# Local research outputs
|
|
21
|
+
/results/
|
|
22
|
+
/paper/
|
|
23
|
+
/experiments/
|
|
24
|
+
/outputs/
|
|
25
|
+
|
|
26
|
+
# Local runner scripts and their tests (not tracked)
|
|
27
|
+
/local_scripts/
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Recuperative Heat Pump public API helpers."""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
5
|
+
# linear_operator 0.6.1 (via GPyTorch/BoTorch) decorates two CG helpers with @torch.jit.script at
|
|
6
|
+
# import time, and recent torch releases warn that torch.jit.script is deprecated. Harmless; silence only that
|
|
7
|
+
# message, before anything imports BoTorch. Child processes inherit nothing, but they import this
|
|
8
|
+
# package first as well.
|
|
9
|
+
warnings.filterwarnings(
|
|
10
|
+
"ignore", message=r"`torch\.jit\.script` is deprecated", category=FutureWarning
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from .utils import get_parameter_sweep # noqa: E402
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"get_parameter_sweep",
|
|
17
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Convenience imports for component classes."""
|
|
2
|
+
|
|
3
|
+
from .stream import Stream
|
|
4
|
+
from .compressor import Compressor
|
|
5
|
+
from .evaporator import Evaporator
|
|
6
|
+
from .expansion_valve import ExpansionValve
|
|
7
|
+
from .gas_cooler import GasCooler
|
|
8
|
+
from .plate_heat_exchanger import PlateHeatExchanger
|
|
9
|
+
from .turbine import Turbine
|
|
10
|
+
from .custom_relationship import CustomRelationship
|
|
11
|
+
from .setRelationship import SetRelationship
|
|
12
|
+
from .economiser import Economiser
|
|
13
|
+
from .mixer import Mixer
|
|
14
|
+
from .splitter import Splitter
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"Stream",
|
|
18
|
+
"Compressor",
|
|
19
|
+
"Evaporator",
|
|
20
|
+
"ExpansionValve",
|
|
21
|
+
"GasCooler",
|
|
22
|
+
"PlateHeatExchanger",
|
|
23
|
+
"Turbine",
|
|
24
|
+
"CustomRelationship",
|
|
25
|
+
"SetRelationship",
|
|
26
|
+
"Economiser",
|
|
27
|
+
"Mixer",
|
|
28
|
+
"Splitter",
|
|
29
|
+
]
|
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
"""Abstract base support for heat exchanger component models."""
|
|
2
|
+
|
|
3
|
+
from math import log
|
|
4
|
+
from ..stream import Stream
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TemperatureCrossError(ValueError):
|
|
8
|
+
"""Report a non-positive heat-exchanger temperature approach."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, approach_violation: float) -> None:
|
|
11
|
+
self.approach_violation = max(0.0, float(approach_violation))
|
|
12
|
+
super().__init__("LMTD is undefined because a temperature cross occurs.")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseHeatExchanger:
|
|
16
|
+
"""Shared heat-exchanger behaviour extended by concrete component models."""
|
|
17
|
+
|
|
18
|
+
# Set the stream connection points
|
|
19
|
+
name: str = ""
|
|
20
|
+
model: str
|
|
21
|
+
source_fluid: str
|
|
22
|
+
source_in: Stream
|
|
23
|
+
source_pinch: Stream
|
|
24
|
+
source_out: Stream
|
|
25
|
+
source_out_max: Stream
|
|
26
|
+
sink_fluid: str
|
|
27
|
+
sink_in: Stream
|
|
28
|
+
sink_pinch: Stream
|
|
29
|
+
sink_out: Stream
|
|
30
|
+
sink_out_max: Stream
|
|
31
|
+
pinch_length = None
|
|
32
|
+
bypassed: bool = False
|
|
33
|
+
converged: bool = False
|
|
34
|
+
HX_Eff: float = 0.0
|
|
35
|
+
NTU: float = 0.0
|
|
36
|
+
c: float = 0.0
|
|
37
|
+
U: float = 0.0
|
|
38
|
+
Area: float = 0.0
|
|
39
|
+
duty: float = 0.0
|
|
40
|
+
delta_entropy_src: float = 0.0
|
|
41
|
+
delta_entropy_snk: float = 0.0
|
|
42
|
+
dP_h: float = 0.0
|
|
43
|
+
dP_c: float = 0.0
|
|
44
|
+
variable_dP_model: bool = False
|
|
45
|
+
tol: float = 1e-6
|
|
46
|
+
|
|
47
|
+
def __init__(self) -> None:
|
|
48
|
+
"""Initialise placeholder attributes for subclasses."""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
def set_port_mass_streams(self, source_fluid: str, sink_fluid: str) -> None:
|
|
52
|
+
"""Create inlet, pinch, and outlet streams for both sides."""
|
|
53
|
+
self.source_in = Stream(name="source in", fluid=source_fluid)
|
|
54
|
+
self.source_pinch = Stream(name="source pinch", fluid=source_fluid)
|
|
55
|
+
self.source_out = Stream(name="source out", fluid=source_fluid)
|
|
56
|
+
self.source_out_max = Stream(name="source out max", fluid=source_fluid)
|
|
57
|
+
|
|
58
|
+
self.sink_in = Stream(name="sink in", fluid=sink_fluid)
|
|
59
|
+
self.sink_pinch = Stream(name="sink pinch", fluid=sink_fluid)
|
|
60
|
+
self.sink_out = Stream(name="sink out", fluid=sink_fluid)
|
|
61
|
+
self.sink_out_max = Stream(name="sink out max", fluid=sink_fluid)
|
|
62
|
+
|
|
63
|
+
def solve(self):
|
|
64
|
+
"""Evaluate the exchanger according to the selected performance model."""
|
|
65
|
+
if self.bypassed:
|
|
66
|
+
self.source_out = self.source_in
|
|
67
|
+
self.sink_out = self.sink_in
|
|
68
|
+
self.converged = True
|
|
69
|
+
else:
|
|
70
|
+
if (
|
|
71
|
+
self.source_in.mass_flow <= self.tol
|
|
72
|
+
or self.sink_in.mass_flow <= self.tol
|
|
73
|
+
):
|
|
74
|
+
self.source_out.copy_fld_props(fld=self.source_in)
|
|
75
|
+
self.source_out.set_flows(self.source_in.mass_flow)
|
|
76
|
+
self.sink_out.copy_fld_props(fld=self.sink_in)
|
|
77
|
+
self.sink_out.set_flows(self.sink_in.mass_flow)
|
|
78
|
+
self.converged = True
|
|
79
|
+
|
|
80
|
+
else:
|
|
81
|
+
self.get_performance()
|
|
82
|
+
|
|
83
|
+
self.duty = self.sink_out.heat_flow - self.sink_in.heat_flow
|
|
84
|
+
if self._has_active_heat_transfer():
|
|
85
|
+
self.converged = self.converged and self._has_valid_heat_transfer_direction()
|
|
86
|
+
self.delta_entropy_src = (
|
|
87
|
+
abs(self.source_in.entropy - self.source_out.entropy)
|
|
88
|
+
* self.source_out.mass_flow
|
|
89
|
+
)
|
|
90
|
+
self.delta_entropy_snk = (
|
|
91
|
+
abs(self.sink_in.entropy - self.sink_out.entropy) * self.sink_out.mass_flow
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def _has_active_heat_transfer(self) -> bool:
|
|
95
|
+
"""Return whether the exchanger should enforce heat-flow direction."""
|
|
96
|
+
return (
|
|
97
|
+
not self.bypassed
|
|
98
|
+
and self.source_in.mass_flow > self.tol
|
|
99
|
+
and self.sink_in.mass_flow > self.tol
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def _has_valid_heat_transfer_direction(self) -> bool:
|
|
103
|
+
"""Return whether heat flows from the source side to the sink side."""
|
|
104
|
+
return (
|
|
105
|
+
self.duty >= -self.tol
|
|
106
|
+
and self.source_out.temperature <= self.source_in.temperature + self.tol
|
|
107
|
+
and self.sink_out.temperature >= self.sink_in.temperature - self.tol
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def get_performance(self):
|
|
111
|
+
"""Dispatch to the underlying heat exchanger formulation."""
|
|
112
|
+
if self.model == "dTmin_terminal":
|
|
113
|
+
self.get_dTmin_terminal_model()
|
|
114
|
+
|
|
115
|
+
elif self.model == "effectiveness":
|
|
116
|
+
self.get_effectiveness_model()
|
|
117
|
+
|
|
118
|
+
elif self.model == "fixed cold duty":
|
|
119
|
+
self.get_fixed_cold_duty_model()
|
|
120
|
+
|
|
121
|
+
elif self.model == "fixed hot duty":
|
|
122
|
+
self.get_fixed_hot_duty_model()
|
|
123
|
+
|
|
124
|
+
elif self.model == "fixed cold temperature outlet":
|
|
125
|
+
self.get_fixed_cold_T_outlet_model()
|
|
126
|
+
|
|
127
|
+
elif self.model == "fixed hot temperature outlet":
|
|
128
|
+
self.get_fixed_hot_T_outlet_model()
|
|
129
|
+
|
|
130
|
+
elif self.model == "fixed hot quality outlet":
|
|
131
|
+
self.get_fixed_hot_Q_outlet_model()
|
|
132
|
+
|
|
133
|
+
elif self.model == "fixed cold delta temperature":
|
|
134
|
+
self.get_fixed_cold_delta_T_model()
|
|
135
|
+
|
|
136
|
+
elif self.model == "fixed hot delta temperature":
|
|
137
|
+
self.get_fixed_hot_delta_T_model()
|
|
138
|
+
|
|
139
|
+
elif self.model == "saturated gas outlet evaporator":
|
|
140
|
+
self.get_saturated_gas_sink_out_model()
|
|
141
|
+
|
|
142
|
+
elif self.model == "specialised":
|
|
143
|
+
self.get_detailed_performance()
|
|
144
|
+
|
|
145
|
+
else:
|
|
146
|
+
raise ValueError("No heat exchanger model selected.")
|
|
147
|
+
|
|
148
|
+
def _preserve_negligible_duty_states(self):
|
|
149
|
+
"""Keep unchanged states when heat is below the absolute duty tolerance."""
|
|
150
|
+
if not (0 <= self.duty <= self.tol and self.dP_h == 0 and self.dP_c == 0):
|
|
151
|
+
return False
|
|
152
|
+
self.source_out.copy_fld_props(self.source_in)
|
|
153
|
+
self.source_out.set_flows(self.source_in.mass_flow)
|
|
154
|
+
self.sink_out.copy_fld_props(self.sink_in)
|
|
155
|
+
self.sink_out.set_flows(self.sink_in.mass_flow)
|
|
156
|
+
self.duty = 0.0
|
|
157
|
+
self.converged = self.source_in.temperature >= self.sink_in.temperature
|
|
158
|
+
return True
|
|
159
|
+
|
|
160
|
+
def get_dTmin_terminal_model(self):
|
|
161
|
+
"""Solve for the duty limited by the specified terminal delta T."""
|
|
162
|
+
try:
|
|
163
|
+
self.source_out.pressure = self.source_in.pressure - self.dP_h
|
|
164
|
+
self.source_out_max.props_in_ab(
|
|
165
|
+
"PT", self.source_out.pressure, self.sink_in.temperature + self.dTmin
|
|
166
|
+
)
|
|
167
|
+
self.dH_h_max = self.source_in.mass_flow * (
|
|
168
|
+
self.source_in.enthalpy - self.source_out_max.enthalpy
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
self.sink_out.pressure = self.sink_in.pressure - self.dP_c
|
|
172
|
+
self.sink_out_max.props_in_ab(
|
|
173
|
+
"PT", self.sink_out.pressure, self.source_in.temperature - self.dTmin
|
|
174
|
+
)
|
|
175
|
+
self.dH_c_max = self.sink_in.mass_flow * (
|
|
176
|
+
self.sink_out_max.enthalpy - self.sink_in.enthalpy
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
self.duty = min(self.dH_h_max, self.dH_c_max)
|
|
180
|
+
|
|
181
|
+
if self._preserve_negligible_duty_states():
|
|
182
|
+
# Below the component's absolute duty tolerance, preserve both
|
|
183
|
+
# inlet states. Reflashing can introduce a larger enthalpy error
|
|
184
|
+
# than the heat being transferred. Zero duty conserves energy
|
|
185
|
+
# exactly on both sides; never apply this to pressure drops or
|
|
186
|
+
# negative requested duty.
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
self.source_out.enthalpy = (
|
|
190
|
+
self.source_in.enthalpy - self.duty / self.source_in.mass_flow
|
|
191
|
+
)
|
|
192
|
+
self.source_out.props_in_mPH()
|
|
193
|
+
self.sink_out.enthalpy = (
|
|
194
|
+
self.sink_in.enthalpy + self.duty / self.sink_in.mass_flow
|
|
195
|
+
)
|
|
196
|
+
self.sink_out.props_in_mPH()
|
|
197
|
+
except Exception as exc:
|
|
198
|
+
raise ValueError(
|
|
199
|
+
"Warning: Phase change occuring in heat exchanger " + self.name
|
|
200
|
+
) from exc
|
|
201
|
+
|
|
202
|
+
self.converged = (
|
|
203
|
+
True
|
|
204
|
+
if self.source_out.temperature > self.sink_in.temperature
|
|
205
|
+
and self.source_in.temperature > self.sink_out.temperature
|
|
206
|
+
else False
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def get_effectiveness_model(self):
|
|
210
|
+
"""Apply an effectiveness definition to compute energy transfer."""
|
|
211
|
+
try:
|
|
212
|
+
self.dT_max = abs(self.source_in.temperature - self.sink_in.temperature)
|
|
213
|
+
|
|
214
|
+
self.source_out.pressure = self.source_in.pressure - self.dP_h
|
|
215
|
+
self.source_out_max.pressure = self.source_out.pressure
|
|
216
|
+
self.source_out_max.temperature = (
|
|
217
|
+
self.sink_in.temperature + self.dT_max * self.HX_Eff
|
|
218
|
+
)
|
|
219
|
+
self.source_out_max.props_in_mPT()
|
|
220
|
+
self.dH_h_max = self.source_in.mass_flow * (
|
|
221
|
+
self.source_in.enthalpy - self.source_out_max.enthalpy
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
self.sink_out.pressure = self.sink_in.pressure - self.dP_c
|
|
225
|
+
self.sink_out_max.pressure = self.sink_out.pressure
|
|
226
|
+
self.sink_out_max.temperature = (
|
|
227
|
+
self.source_in.temperature - self.dT_max * self.HX_Eff
|
|
228
|
+
)
|
|
229
|
+
self.sink_out_max.props_in_mPT()
|
|
230
|
+
self.dH_c_max = self.sink_in.mass_flow * (
|
|
231
|
+
self.sink_out_max.enthalpy - self.sink_in.enthalpy
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
self.duty = min(self.dH_h_max, self.dH_c_max)
|
|
235
|
+
|
|
236
|
+
self.source_out.enthalpy = (
|
|
237
|
+
self.source_in.enthalpy - self.duty / self.source_in.mass_flow
|
|
238
|
+
)
|
|
239
|
+
self.source_out.props_in_mPH()
|
|
240
|
+
self.sink_out.enthalpy = (
|
|
241
|
+
self.sink_in.enthalpy + self.duty / self.sink_in.mass_flow
|
|
242
|
+
)
|
|
243
|
+
self.sink_out.props_in_mPH()
|
|
244
|
+
|
|
245
|
+
except Exception as exc:
|
|
246
|
+
raise ValueError(
|
|
247
|
+
"Warning: Phase change occuring in heat exchanger " + self.name
|
|
248
|
+
) from exc
|
|
249
|
+
|
|
250
|
+
self.converged = (
|
|
251
|
+
True
|
|
252
|
+
if self.source_out.temperature > self.sink_in.temperature
|
|
253
|
+
and self.source_in.temperature > self.sink_out.temperature
|
|
254
|
+
else False
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
def get_fixed_cold_duty_model(self):
|
|
258
|
+
"""Resolve state changes when cold-side duty is fixed."""
|
|
259
|
+
self.duty = self.sink_out.heat_flow - self.sink_in.heat_flow
|
|
260
|
+
self.sink_out.pressure = self.sink_in.pressure - self.dP_c
|
|
261
|
+
self.sink_out.props_in_mPH()
|
|
262
|
+
|
|
263
|
+
self.source_out.pressure = self.source_in.pressure - self.dP_h
|
|
264
|
+
self.source_out.enthalpy = (
|
|
265
|
+
self.source_in.enthalpy - self.duty / self.source_out.mass_flow
|
|
266
|
+
)
|
|
267
|
+
self.source_out.props_in_mPH()
|
|
268
|
+
|
|
269
|
+
self.converged = (
|
|
270
|
+
True
|
|
271
|
+
if self.source_out.temperature > self.sink_in.temperature
|
|
272
|
+
and self.source_in.temperature > self.sink_out.temperature
|
|
273
|
+
else False
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
def get_fixed_hot_duty_model(self):
|
|
277
|
+
"""Resolve state changes when hot-side duty is fixed."""
|
|
278
|
+
self.duty = self.source_in.heat_flow - self.source_out.heat_flow
|
|
279
|
+
self.source_out.pressure = self.source_in.pressure - self.dP_h
|
|
280
|
+
self.source_out.props_in_mPH()
|
|
281
|
+
|
|
282
|
+
self.sink_out.pressure = self.sink_in.pressure - self.dP_c
|
|
283
|
+
self.sink_out.enthalpy = (
|
|
284
|
+
self.sink_in.enthalpy + self.duty / self.sink_in.mass_flow
|
|
285
|
+
)
|
|
286
|
+
self.sink_out.props_in_mPH()
|
|
287
|
+
|
|
288
|
+
self.converged = (
|
|
289
|
+
True
|
|
290
|
+
if self.source_out.temperature > self.sink_in.temperature
|
|
291
|
+
and self.source_in.temperature > self.sink_out.temperature
|
|
292
|
+
else False
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
def get_fixed_cold_T_outlet_model(self):
|
|
296
|
+
"""Solve with a fixed cold outlet temperature constraint."""
|
|
297
|
+
self.sink_out.pressure = self.sink_in.pressure - self.dP_c
|
|
298
|
+
self.sink_out.props_in_mPT()
|
|
299
|
+
self.duty = self.sink_out.heat_flow - self.sink_in.heat_flow
|
|
300
|
+
|
|
301
|
+
self.source_out.pressure = self.source_in.pressure - self.dP_h
|
|
302
|
+
self.source_out.enthalpy = (
|
|
303
|
+
self.source_in.enthalpy - self.duty / self.source_out.mass_flow
|
|
304
|
+
)
|
|
305
|
+
self.source_out.props_in_mPH()
|
|
306
|
+
|
|
307
|
+
self.converged = (
|
|
308
|
+
True
|
|
309
|
+
if self.source_out.temperature > self.sink_in.temperature
|
|
310
|
+
and self.source_in.temperature > self.sink_out.temperature
|
|
311
|
+
else False
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
def get_fixed_hot_T_outlet_model(self):
|
|
315
|
+
"""Solve with a fixed hot outlet temperature constraint."""
|
|
316
|
+
self.source_out.pressure = self.source_in.pressure - self.dP_h
|
|
317
|
+
self.source_out.props_in_mPT()
|
|
318
|
+
self.duty = self.source_in.heat_flow - self.source_out.heat_flow
|
|
319
|
+
|
|
320
|
+
self.sink_out.pressure = self.sink_in.pressure - self.dP_c
|
|
321
|
+
self.sink_out.enthalpy = (
|
|
322
|
+
self.sink_in.enthalpy + self.duty / self.sink_in.mass_flow
|
|
323
|
+
)
|
|
324
|
+
self.sink_out.props_in_mPH()
|
|
325
|
+
|
|
326
|
+
self.converged = (
|
|
327
|
+
True
|
|
328
|
+
if self.source_out.temperature > self.sink_in.temperature
|
|
329
|
+
and self.source_in.temperature > self.sink_out.temperature
|
|
330
|
+
else False
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
def get_fixed_hot_Q_outlet_model(self):
|
|
334
|
+
"""Solve with a fixed hot outlet vapour quality constraint."""
|
|
335
|
+
self.source_out.pressure = self.source_in.pressure - self.dP_h
|
|
336
|
+
self.source_out.props_in_mPQ()
|
|
337
|
+
self.duty = self.source_in.heat_flow - self.source_out.heat_flow
|
|
338
|
+
|
|
339
|
+
self.sink_out.pressure = self.sink_in.pressure - self.dP_c
|
|
340
|
+
self.sink_out.enthalpy = (
|
|
341
|
+
self.sink_in.enthalpy + self.duty / self.sink_in.mass_flow
|
|
342
|
+
)
|
|
343
|
+
self.sink_out.props_in_mPH()
|
|
344
|
+
|
|
345
|
+
self.converged = (
|
|
346
|
+
True
|
|
347
|
+
if self.source_out.temperature > self.sink_in.temperature
|
|
348
|
+
and self.source_in.temperature > self.sink_out.temperature
|
|
349
|
+
else False
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
def get_fixed_cold_delta_T_model(self):
|
|
353
|
+
"""Resolve states when enforcing a cold-side temperature rise."""
|
|
354
|
+
self.sink_out.pressure = self.sink_in.pressure - self.dP_c
|
|
355
|
+
self.sink_out.temperature = self.sink_in.temperature + self.dT_c
|
|
356
|
+
self.sink_out.props_in_mPT()
|
|
357
|
+
self.duty = self.sink_out.heat_flow - self.sink_in.heat_flow
|
|
358
|
+
|
|
359
|
+
if 0 <= self.dT_c <= self.tol and self._preserve_negligible_duty_states():
|
|
360
|
+
return
|
|
361
|
+
|
|
362
|
+
self.source_out.pressure = self.source_in.pressure - self.dP_h
|
|
363
|
+
self.source_out.enthalpy = (
|
|
364
|
+
self.source_in.enthalpy - self.duty / self.source_out.mass_flow
|
|
365
|
+
)
|
|
366
|
+
self.source_out.props_in_mPH()
|
|
367
|
+
|
|
368
|
+
self.converged = (
|
|
369
|
+
True
|
|
370
|
+
if self.source_out.temperature > self.sink_in.temperature
|
|
371
|
+
and self.source_in.temperature > self.sink_out.temperature
|
|
372
|
+
else False
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
def get_fixed_hot_delta_T_model(self):
|
|
376
|
+
"""Resolve states when enforcing a hot-side temperature drop."""
|
|
377
|
+
self.source_out.pressure = self.source_in.pressure - self.dP_h
|
|
378
|
+
self.source_out.temperature = self.source_in.temperature - self.dT_h
|
|
379
|
+
self.source_out.props_in_mPT()
|
|
380
|
+
self.duty = self.source_in.heat_flow - self.source_out.heat_flow
|
|
381
|
+
|
|
382
|
+
if 0 <= self.dT_h <= self.tol and self._preserve_negligible_duty_states():
|
|
383
|
+
return
|
|
384
|
+
|
|
385
|
+
self.sink_out.pressure = self.sink_in.pressure - self.dP_c
|
|
386
|
+
self.sink_out.enthalpy = (
|
|
387
|
+
self.sink_in.enthalpy + self.duty / self.sink_in.mass_flow
|
|
388
|
+
)
|
|
389
|
+
self.sink_out.props_in_mPH()
|
|
390
|
+
|
|
391
|
+
self.converged = (
|
|
392
|
+
True
|
|
393
|
+
if self.source_out.temperature > self.sink_in.temperature
|
|
394
|
+
and self.source_in.temperature > self.sink_out.temperature
|
|
395
|
+
else False
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
def get_saturated_gas_sink_out_model(self):
|
|
399
|
+
"""Evaluate an evaporator with saturated-vapour outlet constraint."""
|
|
400
|
+
self.sink_out.props_in_mTQ()
|
|
401
|
+
self.sink_in.pressure = self.sink_out.pressure + self.dP_c
|
|
402
|
+
self.sink_in.props_in_mPH()
|
|
403
|
+
self.duty = self.sink_out.heat_flow - self.sink_in.heat_flow
|
|
404
|
+
|
|
405
|
+
self.source_out.pressure = self.source_in.pressure - self.dP_h
|
|
406
|
+
self.source_out.enthalpy = (
|
|
407
|
+
self.source_in.enthalpy - self.duty / self.source_out.mass_flow
|
|
408
|
+
)
|
|
409
|
+
self.source_out.props_in_mPH()
|
|
410
|
+
|
|
411
|
+
# if self.source_out.temperature < self.sink_in.temperature or self.source_in.temperature < self.sink_out.temperature:
|
|
412
|
+
# print('temperature cross occuring in the heat exchanger: ' + self.name)
|
|
413
|
+
|
|
414
|
+
self.converged = (
|
|
415
|
+
True
|
|
416
|
+
if self.source_out.temperature > self.sink_in.temperature
|
|
417
|
+
and self.source_in.temperature > self.sink_out.temperature
|
|
418
|
+
else False
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
def get_graph_data(self, divisions=50):
|
|
422
|
+
"""Sample temperature profiles along the exchanger length."""
|
|
423
|
+
data = {"source": [], "sink": []}
|
|
424
|
+
|
|
425
|
+
def get_inter_data(s_in: Stream, s_out: Stream):
|
|
426
|
+
"""Interpolate intermediate stream states for plotting."""
|
|
427
|
+
ls = []
|
|
428
|
+
dP_tot = s_in.pressure - s_out.pressure
|
|
429
|
+
dh_tot = s_in.enthalpy - s_out.enthalpy
|
|
430
|
+
|
|
431
|
+
for i in range(divisions + 1):
|
|
432
|
+
if i == 0:
|
|
433
|
+
m_str = Stream(
|
|
434
|
+
name="source i",
|
|
435
|
+
fluid=s_in.fluid,
|
|
436
|
+
pp_instance=s_in.pp_instance,
|
|
437
|
+
mass_flow=s_in.mass_flow,
|
|
438
|
+
)
|
|
439
|
+
m_str.copy_fld_props(s_in)
|
|
440
|
+
m_str.set_flows(s_in.mass_flow)
|
|
441
|
+
ls.append(m_str)
|
|
442
|
+
continue
|
|
443
|
+
if i == divisions:
|
|
444
|
+
m_str = Stream(
|
|
445
|
+
name="source i",
|
|
446
|
+
fluid=s_out.fluid,
|
|
447
|
+
pp_instance=s_out.pp_instance,
|
|
448
|
+
mass_flow=s_out.mass_flow,
|
|
449
|
+
)
|
|
450
|
+
m_str.copy_fld_props(s_out)
|
|
451
|
+
m_str.set_flows(s_out.mass_flow)
|
|
452
|
+
ls.append(m_str)
|
|
453
|
+
continue
|
|
454
|
+
m_str = Stream(
|
|
455
|
+
name="source i",
|
|
456
|
+
fluid=s_in.fluid,
|
|
457
|
+
pp_instance=s_in.pp_instance,
|
|
458
|
+
pressure=s_in.pressure - dP_tot * i / divisions,
|
|
459
|
+
enthalpy=s_in.enthalpy - dh_tot * i / divisions,
|
|
460
|
+
mass_flow=s_in.mass_flow,
|
|
461
|
+
)
|
|
462
|
+
m_str.props_in_mPH()
|
|
463
|
+
ls.append(m_str)
|
|
464
|
+
return ls
|
|
465
|
+
|
|
466
|
+
data["sink"] = get_inter_data(self.sink_in, self.sink_out)
|
|
467
|
+
data["source"] = get_inter_data(self.source_in, self.source_out)
|
|
468
|
+
return data
|
|
469
|
+
|
|
470
|
+
def get_LMTD_ave(self):
|
|
471
|
+
"""Average the segment-wise log-mean temperature difference."""
|
|
472
|
+
data = self.get_graph_data()
|
|
473
|
+
div = len(data["source"]) - 1
|
|
474
|
+
LMTD_seg = []
|
|
475
|
+
for i in range(div):
|
|
476
|
+
LMTD_seg.append(
|
|
477
|
+
_get_LMTD(
|
|
478
|
+
# counterflow: sink segment i (sink[i] -> sink[i+1]) faces source[div-1-i] -> source[div-i]
|
|
479
|
+
Thin=data["source"][div - 1 - i].temperature,
|
|
480
|
+
Thout=data["source"][div - i].temperature,
|
|
481
|
+
Tcin=data["sink"][i].temperature,
|
|
482
|
+
Tcout=data["sink"][i + 1].temperature,
|
|
483
|
+
)
|
|
484
|
+
)
|
|
485
|
+
return sum(LMTD_seg) / len(LMTD_seg)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _get_LMTD(Thin, Thout, Tcin, Tcout):
|
|
489
|
+
"""Compute log mean temperature difference for a segment."""
|
|
490
|
+
dt_hot = Thin - Tcout
|
|
491
|
+
dt_cold = Thout - Tcin
|
|
492
|
+
if dt_hot <= 0.0 or dt_cold <= 0.0:
|
|
493
|
+
raise TemperatureCrossError(-min(dt_hot, dt_cold))
|
|
494
|
+
if dt_hot == dt_cold:
|
|
495
|
+
return dt_hot
|
|
496
|
+
return (dt_hot - dt_cold) / log(dt_hot / dt_cold)
|