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: Facility

Create 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:
  • ID (str) – Unique name for the facility.

  • T_min_app (float) – Minimum approach temperature observed during synthesis of heat exchanger network.

  • units (Iterable[Unit], optional) – All unit operations available to the heat exchanger network. Defaults to all unit operations in the system.

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

ignored

Iterable[Unit] or callable

Units whose heat utilities are excluded from the analysis; a callable is evaluated at simulation time. Defaults to None.

Qmin

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.

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: 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 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.

replace_unit_heat_utilities

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.

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 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

ticket_name = 'HXN'

str

Default ID of an unnamed instance ('HXN'; facilities are not auto-numbered).

acceptable_energy_balance_error = 0.02

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.

raise_energy_balance_error = False

bool

Raise a RuntimeError instead of warning when the energy balance error exceeds the tolerance above.

network_priority = -2

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

original_heat_util_load

float, kJ/hr

Total duty of the original heating utilities, before integration.

actual_heat_util_load

float, kJ/hr

Total heating duty of the synthesized utility exchangers, after integration.

original_cool_util_load

float, kJ/hr

Total magnitude of the duty of the original cooling utilities, before integration.

actual_cool_util_load

float, kJ/hr

Total magnitude of the cooling duty of the synthesized utility exchangers, after integration.

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 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.

new_HXs

list[HXprocess]

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, 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, 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.

original_heat_exchangers

list[Unit]

The original heat exchangers behind the analyzed heat utilities, in stream order.

original_heat_utils

list[HeatUtility]

The original heat utilities rearranged into stream order, so that they align with stream_life_cycles.

HXN_sys

System

The system built from the synthesized exchangers, named <sys>_HXN and registered in HXN_flowsheet; converged and summarized during costing.

HXN_flowsheet

Flowsheet

The flowsheet <sys>_HXN holding the network’s stream copies and exchangers.

pinch_Ts

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).

inlet_Ts

ndarray, K

Inlet temperature of each stream.

outlet_Ts

ndarray, K

Outlet temperature of each stream, after quenching the outlet to equilibrium at its own enthalpy.

streams_inlet

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.

stream_HXs_dict

dict[int, list[Unit]]

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.

original_purchase_costs

list[float], USD

Purchase cost of each original heat exchanger.

new_purchase_costs_HXp

list[float], USD

Purchase cost of each synthesized process exchanger, indexed like new_HXs.

new_purchase_costs_HXu

list[float], USD

Purchase cost of each synthesized utility exchanger, indexed like new_HX_utils.

original_utility_costs

list[HeatUtility]

The original heat utilities summed by agent, with their duties reversed in sign so that they net against the new ones.

new_utility_costs

list[HeatUtility]

The heat utilities of the synthesized utility exchangers, summed by agent.