HeatExchangerNetwork#
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 – 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
Tutorial for a worked example and Key Concepts for the
method.
- class hensmith.HeatExchangerNetwork(ID='', T_min_app=5.0, units=None, ignored=None, Qmin=0.001, force_ideal_thermo=False, cache_network=False, avoid_recycle=False, acceptable_energy_balance_error=None, replace_unit_heat_utilities=False, sort_hus_by_T=False)[source]#
Bases:
FacilityCreate a HeatExchangerNetwork object that will perform a pinch analysis on the entire system’s heating and cooling utility objects. The heat exchanger network reduces the heating and cooling utility requirements of the system and may add additional capital cost.
- Parameters:
Notes
The network is synthesized without stream splits by
synthesize_network(): a problem table on the streams’ temperature-enthalpy curves gives the minimum energy requirement (MER) targets, and a planner builds each side of the pinch from the pinch outward [1] [2], keeping T_min_app everywhere inside every exchanger on the exact stream states. It reaches the targets whenever its search finds an unsplit network that does; the same pair of streams may then be matched more than once (IDs with a suffix_<n>, e.g.HX_3_2_cs_2), since series alternation can replace a split. Where the pinch design rules prove that MER needs stream splitting, the network is a best-effort one close to the targets. The outcome is recorded in synthesis_info (a dict; see the info keyword of synthesize_network): ‘status’ is ‘mer’ when the network’s utilities equal the targets and ‘best_effort’ otherwise, with the targets, the planned utilities and, per side of the pinch, any proof that a split is needed.Original system stream and heat exchanger objects are preserved. All stream copies and new HX objects can be found in a newly created flowsheet ‘<sys>_HXN’ where <sys> is the name of the system associated to the HeatExchangerNetwork object. Each stream passes its exchangers in series; the network is simulated as a System (HXN_sys) whose path follows the streams, with the loops that repeated matches can form torn and converged to a tight tolerance (every exchanger starts at its planned state, so the loops are at their fixed point after one pass).
With cache_network, a network is reused while the set of heat exchangers is the same: each process exchanger keeps, as its enthalpy limit, the share of the stream’s duty it had at synthesis (on the stream that the plan serves completely on that side of the pinch; its partner transfers that share, but never past its own outlet, and takes the rest to its utility), and the utility exchangers bring every stream to its new outlet. If the cached network does not reproduce the outlets, or its energy balance is off, the network is synthesized again.
Every utility exchanger is designed and costed by biosteam as usual. A stream that its process exchangers bring to its outlet (within 1e-9 of its duty: the residual of the enthalpy flashes) leaves its utility exchanger in the state it enters it, so that exchanger has exactly no duty and no cost instead of a spurious duty from re-flashing the stream.
The facility’s heat utilities are the new utilities less the original ones, summed by agent (a negative utility cost is a saving). With replace_unit_heat_utilities, each original heat utility takes the heat utility of its own stream’s utility exchanger instead, the utility costs of its unit and of that unit’s owner are reloaded, and the facility carries no heat utilities. The original data are given back before the network is costed again, so that the network is synthesized from the units’ own utilities whether or not the units were simulated again.
References
Examples
>>> import biosteam as bst >>> bst.settings.set_thermo(['Water', 'Methanol', 'Glycerol']) >>> feed1 = bst.Stream('feed1', flow=(8000, 100, 25)) >>> feed2 = bst.Stream('feed2', flow=(10000, 1000, 10)) >>> D1 = bst.ShortcutColumn('D1', ins=feed1, ... outs=('distillate', 'bottoms_product'), ... LHK=('Methanol', 'Water'), ... y_top=0.99, x_bot=0.01, k=2, ... is_divided=True) >>> D1_H1 = bst.HXutility('D1_H1', ins = D1.outs[1], T = 300) >>> D1_H2 = bst.HXutility('D1_H2', ins = D1.outs[0], T = 300) >>> F1 = bst.Flash('F1', ins=feed2, ... outs=('vapor', 'liquid'), V = 0.9, P = 101325) >>> HXN = bst.HeatExchangerNetwork('HXN', T_min_app = 5.) >>> sys = bst.System.from_units('sys', units=[D1, D1_H1, D1_H2, F1, HXN]) >>> sys.simulate() >>> # See all results >>> round(HXN.actual_heat_util_load/HXN.original_heat_util_load, 2) 0.82 >>> abs(HXN.energy_balance_percent_error) < 0.01 True >>> HXN.synthesis_info['status'] # the utilities equal the MER targets 'mer' >>> HXN.stream_life_cycles [<StreamLifeCycle: Stream_0, cold life_cycle = [ <LifeStage: <HXprocess: HX_0_2_hs>, H_in = 5.38e+06 kJ/hr, H_out = 4.24e+07 kJ/hr> <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_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.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> <LifeStage: <HXutility: Util_3_cs>, H_in = 2.47e+06 kJ/hr, H_out = 2.47e+06 kJ/hr> ]>, <StreamLifeCycle: Stream_4, hot life_cycle = [ <LifeStage: <HXprocess: HX_1_4_hs>, H_in = 7.51e+05 kJ/hr, H_out = 7.18e+05 kJ/hr> <LifeStage: <HXutility: Util_4_cs>, H_in = 7.18e+05 kJ/hr, H_out = 7.18e+05 kJ/hr> ]>]
- HeatExchangerNetwork.plot_pinch_diagram(file=None, **kwargs)[source]#
Draw the pinch diagram of the synthesized network; see
plot_pinch_diagram()for the keyword arguments. Returns the matplotlib figure and axes.
Constructor options#
In addition to ID, T_min_app and units documented above, the
constructor accepts the following options; Configuring the network and a larger system
shows what each of them changes.
Option |
Type |
Meaning |
|---|---|---|
|
Iterable[Unit] or callable |
Units whose heat utilities are excluded from the analysis; a callable is evaluated at simulation time. Defaults to None. |
|
float, kJ/hr |
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. |
|
bool |
Run the analysis on stream copies with ideal thermodynamics; the synthesized exchangers inherit that thermo. Defaults to False. |
|
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: 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. |
|
bool |
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. |
|
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. |
|
bool |
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. |
|
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 stream indices, which break ties in the planner’s search. Defaults to False. |
Class attributes#
Defaults shared by every instance; assigning to an instance overrides the value for that instance only.
Attribute |
Type |
Meaning |
|---|---|---|
|
str |
Default ID of an unnamed instance ( |
|
float (fraction) |
Fraction (0.02 = 2 %) absolute energy balance error above which the simulation warns (or raises); a cached network exceeding it is discarded and resynthesized. |
|
bool |
Raise a |
|
int |
Facility ordering key; facilities are simulated in ascending order of this value, so the network runs before the utility facilities. |
Attributes set by simulation#
The following attributes are set at the end of every simulation. They carry
no docstrings of their own, so autodoc cannot list them; each per-stream list
or array is indexed by the stream index of the rearranged utility list (see
synthesize_network()). With cache_network=True the attributes that
describe the synthesized topology are kept from the synthesis that produced
the cached network.
Attribute |
Type |
Meaning |
|---|---|---|
|
float, kJ/hr |
Total duty of the original heating utilities, before integration. |
|
float, kJ/hr |
Total heating duty of the synthesized utility exchangers, after integration. |
|
float, kJ/hr |
Total magnitude of the duty of the original cooling utilities, before integration. |
|
float, kJ/hr |
Total magnitude of the cooling duty of the synthesized utility exchangers, after integration. |
|
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 |
|
dict |
The synthesis report (see the |
|
list[StreamLifeCycle] |
Ordered sequence of exchangers each stream passes through, aligned with |
|
list[HXprocess] |
All synthesized process exchangers, the hot-side ones followed by the cold-side ones. |
|
list[HXprocess] |
Process exchangers of the hot-side (above-pinch) design, in plan order (from the pinch outward), IDs |
|
list[HXprocess] |
Process exchangers of the cold-side (below-pinch) design, in plan order, IDs |
|
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. |
|
list[Unit] |
The original heat exchangers behind the analyzed heat utilities, in stream order. |
|
list[HeatUtility] |
The original heat utilities rearranged into stream order, so that they align with |
|
System |
The system built from the synthesized exchangers, named |
|
Flowsheet |
The flowsheet |
|
ndarray, K |
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). |
|
ndarray, K |
Inlet temperature of each stream. |
|
ndarray, K |
Outlet temperature of each stream, after quenching the outlet to equilibrium at its own enthalpy. |
|
list[Stream] |
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. |
|
dict[int, list[Unit]] |
Exchangers that each stream index passes through: its process exchangers in flow order, then its utility exchanger. |
|
list[int] |
Stream indices of the heated (cold) streams. |
|
list[float], USD |
Purchase cost of each original heat exchanger. |
|
list[float], USD |
Purchase cost of each synthesized process exchanger, indexed like |
|
list[float], USD |
Purchase cost of each synthesized utility exchanger, indexed like |
|
list[HeatUtility] |
The original heat utilities summed by agent, with their duties reversed in sign so that they net against the new ones. |
|
list[HeatUtility] |
The heat utilities of the synthesized utility exchangers, summed by agent. |