Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
facility previously distributed as `biosteam.facilities.hxn`, including
`HeatExchangerNetwork`, pinch/problem-table analysis, and pinch diagram
plotting for [BioSTEAM](https://github.com/BioSTEAMDevelopmentGroup/biosteam)
systems.
systems. Targets come from a problem table on each stream's temperature-enthalpy
curve (phase changes included), and a pinch-outward planner synthesizes a
network without stream splits that reaches those minimum energy requirement
(MER) targets whenever it finds one, keeping the minimum approach temperature
everywhere inside every exchanger; where MER provably needs a split, the
network is a best-effort one close to the targets.

```python
import biosteam as bst # hensmith units plug into BioSTEAM systems
Expand Down
5 changes: 3 additions & 2 deletions docs/_demo_src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ python docs\_demo_src\build_all.py
first failure. That sequencing is not incidental: importing biosteam writes
numba's shared on-disk cache, and two Python processes writing it at once
corrupt it — so never run two of these scripts (or the test suite alongside
one) concurrently by hand either. A full run takes a minute or two; the hero
GIFs dominate it.
one) concurrently by hand either. A full run takes about three minutes; the
hero GIFs (about 70 s) and chapter 04's six-point ``T_min_app`` sweep and
ten-stream synthesis (about 50 s) dominate it.

Graphviz's `dot` must be on `PATH` — the flowsheet figures in chapters 01 and
03 are rendered by biosteam's `system.diagram()`.
Expand Down
2 changes: 2 additions & 0 deletions docs/_demo_src/examples/ch01_quickstart.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def main():
print(f'energy balance error: {HXN.energy_balance_percent_error:.2g} %')
print(f'added installed cost: {HXN.installed_costs["Heat exchangers"]:.3g} USD')
print(f'process exchangers: {[hx.ID for hx in HXN.new_HXs]}')
print(f"synthesis status: {HXN.synthesis_info['status']}")
# [end:loads]
with capturing('ch01_life_cycles'):
# [start:life_cycles]
Expand Down Expand Up @@ -89,6 +90,7 @@ def main():
'energy_balance_percent_error_abs': f'{abs(HXN.energy_balance_percent_error):.0e}',
})
assert len(HXN.new_HXs) == 4 and len(HXN.stream_life_cycles) == 5, 'quickstart network changed'
assert HXN.synthesis_info['status'] == 'mer', 'quickstart network no longer reaches MER'
assert abs(HXN.energy_balance_percent_error) < 1e-6


Expand Down
6 changes: 4 additions & 2 deletions docs/_demo_src/examples/ch02_pinch_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def main():
cold_in = bst.Stream(Water=900., T=300., P=5e5, phase='l', units='kmol/hr')
cold_out = cold_in.copy(); cold_out.vle(T=390., P=5e5)
table = problem_table([hot_in, cold_in], [hot_out, cold_out], [True, False], 5.)
print('shifted grid Ts [K]:', table.Ts)
print('shifted grid Ts [K]:', table.Ts.size, 'points,', table.Ts[0], 'down to', table.Ts[-1])
print('hot utility target [kJ/hr]: ', round(table.hot_util_load, 3))
print('cold utility target [kJ/hr]:', round(table.cold_util_load, -1))
print('pinch (shifted) [K]:', table.pinch_T)
Expand Down Expand Up @@ -117,7 +117,8 @@ def main():
for s in streams_quenched: s.vle(H=s.H, P=s.P)
is_hot = [hu.duty < 0 for hu in hus]
table = problem_table(streams_inlet, streams_quenched, is_hot, T_min_app=5.)
print('shifted grid Ts [K]:', table.Ts.round(2))
print(f'shifted grid Ts: {table.Ts.size} points, {table.Ts[0]:.2f} down to {table.Ts[-1]:.2f} K')
print(f'point loads: {np.count_nonzero(table.point_H)}')
print(f'hot utility target: {table.hot_util_load:.4g} kJ/hr')
print(f'cold utility target: {table.cold_util_load:.4g} kJ/hr')
print(f'pinch (shifted): {table.pinch_T:.2f} K')
Expand Down Expand Up @@ -169,6 +170,7 @@ def main():
cool = -sum(hu.unit_duty for hu in new_hus if hu.unit_duty < 0)
print(f'hot utility, process side: target {table.hot_util_load:.4g}, network {heat:.4g} kJ/hr')
print(f'cold utility, process side: target {table.cold_util_load:.4g}, network {cool:.4g} kJ/hr')
print(f"synthesis status: {HXN.synthesis_info['status']}")
# [end:compare]
# plumbing checks: the drawn curves are consistent with the table
gap = min_vertical_gap(hot_T, hot_H, cold_T, cold_H)
Expand Down
18 changes: 13 additions & 5 deletions docs/_demo_src/examples/ch04_configuring.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,16 @@ def main():
HXN.T_min_app = T_min_app
sys.simulate()
rows.append((T_min_app, HXN.actual_heat_util_load, HXN.actual_cool_util_load,
HXN.installed_costs['Heat exchangers']))
print('T_min_app [K] heating [kJ/hr] cooling [kJ/hr] added installed cost [USD]')
for T, heat, cool, cost in rows:
print(f'{T:13.0f} {heat:15.4g} {cool:15.4g} {cost:26.4g}')
HXN.installed_costs['Heat exchangers'], len(HXN.new_HXs),
dict(HXN.synthesis_info)))
print('T_min_app heating cooling added installed pinch process status')
print('[K] [kJ/hr] [kJ/hr] cost [USD] [K] exchangers')
for T, heat, cool, cost, n, info in rows:
pinch_T = info['plan_targets']['pinch_T'] # shifted scale
status = info['status']
if status != 'mer': # process-side hot utility above the MER target
status += f" (+{info['Q_hot'] - info['Q_hot_target']:.3g} kJ/hr)"
print(f'{T:<9.0f} {heat:<9.4g} {cool:<9.4g} {cost:<15.4g} {pinch_T:<8.2f} {n:<11d} {status}')
# [end:sweep]
# [start:sweep_plot]
T = [r[0] for r in rows]
Expand Down Expand Up @@ -137,11 +143,13 @@ def main():
print(f'hot utility, process side: target {table.hot_util_load:.4g}, network {heat:.4g} kJ/hr')
print(f'cold utility, process side: target {table.cold_util_load:.4g}, network {cool:.4g} kJ/hr')
print(f'energy balance error: {HXN10.energy_balance_percent_error:.2g} %')
print(f"synthesis status: {HXN10.synthesis_info['status']}")
fig, ax = HXN10.plot_pinch_diagram()
# [end:ten_streams]
save(fig, 'tutorial_04_ten_streams_pinch_diagram.png')
plt.close(fig)
assert len(HXN10.new_HXs) == 15, len(HXN10.new_HXs)
assert len(HXN10.new_HXs) == 10, len(HXN10.new_HXs)
assert HXN10.synthesis_info['status'] == 'mer'
assert HXN10.actual_heat_util_load >= table.hot_util_load * (1 - 1e-3)


Expand Down
31 changes: 18 additions & 13 deletions docs/source/API/heat_exchanger_network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ HeatExchangerNetwork
:class:`HeatExchangerNetwork` is a BioSTEAM facility that runs a pinch
analysis over the heating and cooling utilities of a whole system,
synthesizes a network of process heat exchangers that meets part of those
duties by stream-to-stream exchange, and reports the utility loads and
capital cost that result. The original units, streams and heat exchangers
are left untouched: the stream copies and synthesized exchangers live in a
separate flowsheet named ``<sys>_HXN``. See :doc:`../tutorial/index` for a
worked example.
duties by stream-to-stream exchange -- at the minimum energy requirement
(MER) targets whenever it finds such a network without stream splits -- and
reports the utility loads and capital cost that result. The original units,
streams and heat exchangers are left untouched: the stream copies and
synthesized exchangers live in a separate flowsheet named ``<sys>_HXN``. See
:doc:`../tutorial/index` for a worked example and :doc:`../concepts` for the
method.

.. autoclass:: HeatExchangerNetwork
:no-members:
Expand All @@ -37,16 +39,16 @@ shows what each of them changes.
- Units whose heat utilities are excluded from the analysis; a callable is evaluated at simulation time. Defaults to None.
* - ``Qmin``
- float, kJ/hr
- Candidate exchangers with a duty below this are discarded during synthesis, and utility exchangers at or below it are not marked on the pinch diagram. Defaults to 1e-3.
- Planned exchangers with a duty below this are dropped and their duty left to the utilities (a large value can cost MER), and utility exchangers at or below it are not marked on the pinch diagram. Defaults to 1e-3.
* - ``force_ideal_thermo``
- bool
- Run the analysis on stream copies with ideal thermodynamics; the synthesized exchangers inherit that thermo. Defaults to False.
* - ``cache_network``
- bool
- Reuse the network configuration of the previous simulation when the set of units contributing heat utilities is unchanged, updating only stream states and exchanger specifications. Defaults to False.
- Reuse the network configuration of the previous simulation when the set of units contributing heat utilities is unchanged, updating only stream states and exchanger specifications: each process exchanger keeps the fraction of its stream's duty at which its enthalpy limit sat at synthesis, and the utility exchangers bring every stream to its new outlet. The reused network is not planned again, so it need not be at MER for the new duties. Defaults to False.
* - ``avoid_recycle``
- bool
- Never match the same hot/cold stream pair twice, so that no two exchangers connect the same pair and form a recycle loop. Defaults to False.
- Never match the same hot/cold stream pair twice anywhere (on one side of the pinch or across the two), so that no two exchangers connect the same pair and form a recycle loop; this forbids the repeated matches some unsplit MER networks need. Defaults to False.
* - ``acceptable_energy_balance_error``
- float
- When given, sets an instance attribute that overrides the class default of 0.02 (see below). Defaults to None, i.e. the class value is used.
Expand All @@ -55,7 +57,7 @@ shows what each of them changes.
- Copy each synthesized utility exchanger's heat utility onto the corresponding original heat utility and reload that unit's utility cost, instead of reporting the net utilities on the facility itself. Applies only when at least one process exchanger was synthesized. Defaults to False.
* - ``sort_hus_by_T``
- bool
- Sort the heating utilities by inlet temperature descending and the cooling utilities ascending before the analysis, so that inlet temperature rather than signed duty (the default: smallest heating duty first, largest cooling duty first) sets the matching priority. Defaults to False.
- Sort the heating utilities by inlet temperature descending and the cooling utilities ascending before the analysis, so that inlet temperature rather than signed duty (the default: smallest heating duty first, largest cooling duty first) sets the stream indices, which break ties in the planner's search. Defaults to False.

Class attributes
----------------
Expand Down Expand Up @@ -115,6 +117,9 @@ the cached network.
* - ``energy_balance_percent_error``
- float, %
- Percent deviation from one of the ratio (twice the duty of each process exchanger, plus the new utility duties weighted by their agents' heat-transfer efficiency) / (the original utility duties weighted the same way), as computed in ``_cost``.
* - ``synthesis_info``
- dict
- The synthesis report (see the ``info`` keyword of :func:`synthesize_network`): ``'status'`` is ``'mer'`` when the network's utilities equal the MER targets and ``'best_effort'`` otherwise; next to it the targets, the planned and realized utilities, the penalty, per side of the pinch any proof that a split is needed, and the smallest approach inside any process exchanger. Kept from the synthesis that produced a cached network.
* - ``stream_life_cycles``
- list[StreamLifeCycle]
- Ordered sequence of exchangers each stream passes through, aligned with ``original_heat_exchangers``.
Expand All @@ -123,10 +128,10 @@ the cached network.
- All synthesized process exchangers, the hot-side ones followed by the cold-side ones.
* - ``new_HXs_hot_side``
- list[HXprocess]
- Process exchangers of the hot-side (above-pinch) design.
- Process exchangers of the hot-side (above-pinch) design, in plan order (from the pinch outward), IDs ``HX_<cold>_<hot>_hs``.
* - ``new_HXs_cold_side``
- list[HXprocess]
- Process exchangers of the cold-side (below-pinch) design.
- Process exchangers of the cold-side (below-pinch) design, in plan order, IDs ``HX_<hot>_<cold>_cs``; on either side the *n*-th exchanger of a repeated pair gets the suffix ``_<n>``.
* - ``new_HX_utils``
- list[HXutility]
- One rigorous utility exchanger per stream, bringing it from its last process exchanger (or its inlet, if it was not matched) to its outlet enthalpy.
Expand All @@ -144,7 +149,7 @@ the cached network.
- The flowsheet ``<sys>_HXN`` holding the network's stream copies and exchangers.
* - ``pinch_Ts``
- ndarray, K
- Per-stream pinch temperature at which the stream's duty is split between the hot-side and cold-side designs.
- Per-stream pinch temperature (informational): the process pinch on the stream's own scale when the stream crosses it, else its inlet temperature (inlet already past the pinch, or an isothermal or non-monotone stream) or its outlet temperature (stream ending before the pinch).
* - ``inlet_Ts``
- ndarray, K
- Inlet temperature of each stream.
Expand All @@ -156,7 +161,7 @@ the cached network.
- One copy of each stream's inlet, in stream order, as prepared for the analysis; the synthesis works on further copies, so these keep their inlet state.
* - ``stream_HXs_dict``
- dict[int, list[Unit]]
- Exchangers, the process ones then the utility one, that each stream index passes through, in synthesis order rather than flow order.
- Exchangers that each stream index passes through: its process exchangers in flow order, then its utility exchanger.
* - ``cold_indices``
- list[int]
- Stream indices of the heated (cold) streams.
Expand Down
31 changes: 19 additions & 12 deletions docs/source/API/hxn_synthesis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ Pinch analysis and synthesis (hensmith.hxn_synthesis)

``hensmith.hxn_synthesis`` holds the machinery behind
:class:`HeatExchangerNetwork`: :func:`problem_table` builds the
temperature-interval heat cascade of a set of process streams and locates
the pinch, :func:`synthesize_network` adds the sequential, heuristic
matching of hot and cold streams on each side of that pinch,
:class:`StreamLifeCycle` records the exchangers each stream ends up passing
through, and :func:`plot_pinch_diagram` draws the result. All four are
usable on their own, without a :class:`HeatExchangerNetwork` instance.
temperature-interval heat cascade of a set of process streams on their
temperature-enthalpy curves and locates the pinch, :func:`synthesize_network`
plans an unsplit network from the pinch outward on the same curves -- one that
reaches the minimum energy requirement (MER) targets whenever its search finds
one -- and realizes it as BioSTEAM exchangers, :class:`StreamLifeCycle`
records the exchangers each stream ends up passing through, and
:func:`plot_pinch_diagram` draws the result. All four are usable on their own,
without a :class:`HeatExchangerNetwork` instance; :doc:`../concepts` explains
the method.

.. autofunction:: problem_table

Expand All @@ -29,9 +32,13 @@ usable on their own, without a :class:`HeatExchangerNetwork` instance.

.. note::

**Internals.** ``hensmith.hxn_synthesis.temperature_interval_pinch_analysis``,
``hensmith.hxn_synthesis.pinch_state`` and
``hensmith.hxn_synthesis.load_duties`` are public in name only: they are
steps of :func:`synthesize_network`, are not exported by ``hensmith``, and
are not part of the supported API. Their signatures and behavior may change
without notice.
**Internals.** ``hensmith.hxn_synthesis.temperature_interval_pinch_analysis``
(the first step of :func:`synthesize_network`: preparing the process streams
and running the problem table on them), ``hensmith.hxn_synthesis.pinch_state``
and ``hensmith.hxn_synthesis.load_duties`` (standalone helpers that split a
stream at a pinch temperature; the synthesis itself plans on the stream
curves and does not use them) are public in name only: they are not exported
by ``hensmith``, and are not part of the supported API. Neither are the
private modules ``hensmith._curves`` (the stream temperature-enthalpy curves)
and ``hensmith._planner`` (the MER planner). Their signatures and behavior
may change without notice.
10 changes: 5 additions & 5 deletions docs/source/_generated/ch01_life_cycles.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
<LifeStage: <HXutility: Util_0_hs>, H_in = 4.24e+07 kJ/hr, H_out = 6.92e+07 kJ/hr>
]>, <StreamLifeCycle: Stream_1, cold
life_cycle = [
<LifeStage: <HXprocess: HX_1_4_hs>, H_in = 0 kJ/hr, H_out = 3.34e+04 kJ/hr>
<LifeStage: <HXprocess: HX_1_2_hs>, H_in = 3.34e+04 kJ/hr, H_out = 5.06e+06 kJ/hr>
<LifeStage: <HXprocess: HX_1_3_hs>, H_in = 5.06e+06 kJ/hr, H_out = 2.3e+07 kJ/hr>
<LifeStage: <HXprocess: HX_1_2_hs>, H_in = 0 kJ/hr, H_out = 5.05e+06 kJ/hr>
<LifeStage: <HXprocess: HX_1_4_hs>, H_in = 5.05e+06 kJ/hr, H_out = 5.08e+06 kJ/hr>
<LifeStage: <HXprocess: HX_1_3_hs>, H_in = 5.08e+06 kJ/hr, H_out = 2.3e+07 kJ/hr>
<LifeStage: <HXutility: Util_1_hs>, H_in = 2.3e+07 kJ/hr, H_out = 2.79e+08 kJ/hr>
]>, <StreamLifeCycle: Stream_2, hot
life_cycle = [
<LifeStage: <HXprocess: HX_0_2_hs>, H_in = 4.52e+07 kJ/hr, H_out = 8.12e+06 kJ/hr>
<LifeStage: <HXprocess: HX_1_2_hs>, H_in = 8.12e+06 kJ/hr, H_out = 3.1e+06 kJ/hr>
<LifeStage: <HXutility: Util_2_cs>, H_in = 3.1e+06 kJ/hr, H_out = 1.14e+06 kJ/hr>
<LifeStage: <HXprocess: HX_1_2_hs>, H_in = 8.12e+06 kJ/hr, H_out = 3.07e+06 kJ/hr>
<LifeStage: <HXutility: Util_2_cs>, H_in = 3.07e+06 kJ/hr, H_out = 1.14e+06 kJ/hr>
]>, <StreamLifeCycle: Stream_3, hot
life_cycle = [
<LifeStage: <HXprocess: HX_1_3_hs>, H_in = 2.04e+07 kJ/hr, H_out = 2.47e+06 kJ/hr>
Expand Down
5 changes: 3 additions & 2 deletions docs/source/_generated/ch01_loads.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
heating utility: 3.609e+08 -> 2.977e+08 kJ/hr
cooling utility: 6.201e+07 -> 1.96e+06 kJ/hr
cooling utility: 6.201e+07 -> 1.936e+06 kJ/hr
energy balance error: -1.8e-11 %
added installed cost: 1.11e+06 USD
process exchangers: ['HX_0_2_hs', 'HX_1_4_hs', 'HX_1_2_hs', 'HX_1_3_hs']
process exchangers: ['HX_1_2_hs', 'HX_0_2_hs', 'HX_1_4_hs', 'HX_1_3_hs']
synthesis status: mer
4 changes: 2 additions & 2 deletions docs/source/_generated/ch01_summary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ heating_before = 3.609e+08
heating_after = 2.977e+08
heating_reduction_percent = 17.5
cooling_before = 6.201e+07
cooling_after = 1.96e+06
cooling_reduction_percent = 96.8
cooling_after = 1.936e+06
cooling_reduction_percent = 96.9
heat_ratio = 0.82
utility_cost_usd_per_hr = -605
installed_cost_usd = 1.11e+06
Expand Down
5 changes: 3 additions & 2 deletions docs/source/_generated/ch02_compare.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
hot utility: target 2.828e+08, network 2.977e+08 kJ/hr
cold utility: target 1.936e+06, network 1.96e+06 kJ/hr
cold utility: target 1.936e+06, network 1.936e+06 kJ/hr
hot utility, process side: target 2.828e+08, network 2.828e+08 kJ/hr
cold utility, process side: target 1.936e+06, network 1.96e+06 kJ/hr
cold utility, process side: target 1.936e+06, network 1.936e+06 kJ/hr
synthesis status: mer
3 changes: 2 additions & 1 deletion docs/source/_generated/ch02_table.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
shifted grid Ts [K]: [372.6 369.07 366.38 333.53 333. 333. 332.98 306.37 298.15 295. ]
shifted grid Ts: 175 points, 372.60 down to 295.00 K
point loads: 0
hot utility target: 2.828e+08 kJ/hr
cold utility target: 1.936e+06 kJ/hr
pinch (shifted): 298.15 K
2 changes: 1 addition & 1 deletion docs/source/_generated/ch02_threshold.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
shifted grid Ts [K]: [395. 390. 300. 295.]
shifted grid Ts [K]: 25 points, 395.0 down to 295.0
hot utility target [kJ/hr]: 0.0
cold utility target [kJ/hr]: 1445550.0
pinch (shifted) [K]: 395.0
12 changes: 6 additions & 6 deletions docs/source/_generated/ch03_accounting.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
energy balance error: -1.8e-11 % (warns above 2 %)
original exchangers, purchase cost: 3.365e+05 USD
new process exchangers, purchase: 4.73e+05 USD
new utility exchangers, purchase: 2.096e+05 USD
facility purchase cost (added): 3.461e+05 USD
facility installed cost (added): 1.114e+06 USD
new process exchangers, purchase: 4.734e+05 USD
new utility exchangers, purchase: 2.095e+05 USD
facility purchase cost (added): 3.464e+05 USD
facility installed cost (added): 1.112e+06 USD
facility heat utilities (new minus original):
low_pressure_steam duty -6.321e+07 kJ/hr cost -388.6 USD/hr
chilled_water duty 4.212e+07 kJ/hr cost -210.6 USD/hr
low_pressure_steam duty -6.324e+07 kJ/hr cost -388.8 USD/hr
chilled_water duty 4.214e+07 kJ/hr cost -210.7 USD/hr
cooling_water duty 1.794e+07 kJ/hr cost -5.976 USD/hr
Loading
Loading