Pinch analysis and synthesis (hensmith.hxn_synthesis)#

hensmith.hxn_synthesis holds the machinery behind HeatExchangerNetwork: problem_table() builds the temperature-interval heat cascade of a set of process streams on their temperature-enthalpy curves and locates the pinch, 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, StreamLifeCycle records the exchangers each stream ends up passing through, and plot_pinch_diagram() draws the result. All four are usable on their own, without a HeatExchangerNetwork instance; Key Concepts explains the method.

hensmith.problem_table(streams_inlet, streams_quenched, is_hot, T_min_app, curves=None)[source]#

Energy-consistent problem table (temperature-interval heat cascade).

Parameters:
  • streams_inlet (list[Stream]) – Inlet stream of each utility heat exchanger.

  • streams_quenched (list[Stream]) – Corresponding outlet streams, re-flashed at their enthalpy.

  • is_hot (Sequence[bool]) – True where the stream is cooled.

  • T_min_app (float) – Minimum approach temperature [K].

  • curves (list, optional) – Prebuilt temperature-enthalpy curves of the same streams, in the same order (e.g. shared with the network synthesis); built here if not given.

Returns:

Grid temperatures Ts (shifted scale, descending), per-stream interval_H (N x n-1) and point_H (N x n) contributions (+ for hot, - for cold), the cascade residual (n) leaving each boundary (i.e. after its point loads), hot_util_load, cold_util_load and the shifted-scale pinch_T.

Return type:

ProblemTable

Notes

Each stream is described by a piecewise-linear temperature-enthalpy curve built once from a handful of flashes: breakpoints at its end temperatures and at every phase boundary inside its range (a pure component’s saturation temperature, a mixture’s bubble and dew points); a flat (isothermal) segment for a pure component’s latent heat, between its saturated-liquid and saturated-vapor enthalpies; samples of each mixture glide (binaries traced along their bubble-point curve); and interior breakpoints of each curved single-phase stretch (temperature-dependent Cp), dense enough that linear interpolation is within 0.002 K of the true curve. Single-phase stretches are evaluated with their phases fixed (no flash), so a grid point exactly at a saturation temperature is never ambiguous. Hot streams are shifted down by T_min_app; cold streams are not. The grid is the union of all shifted breakpoints, so every point at which any stream’s curve bends or jumps is a grid point, every stream is within 0.002 K of linear between grid points, and the grid minimum of the cascade is the true one to within 0.002 K * sum(CP).

At a grid temperature a stream contributes the jump of its curve there as a point load (point_H), and between two grid temperatures the heat of its curve in that open interval (interval_H). Inside its own temperature range a stream is taken at equilibrium with its enthalpy clipped to its real range, so a non-equilibrium end state (e.g. a superheated liquid from a non-rigorous HXutility) can never inflate the duty; what it departs from equilibrium at its own end temperature is a point load there. Every stream’s contributions therefore telescope exactly to sign * |H_out - H_in|. Streams whose outlet temperature does not move with their duty (isothermal, or a heated stream that exits colder than it entered, e.g. a reboiler outlet at VLE) are point loads at their outlet temperature.

The cascade starting from zero hot utility is residual[k] = sum(point_H[:, :k+1]) + sum(interval_H[:, :k]), the heat leaving boundary Ts[k]. Feasibility must also hold for the heat arriving at Ts[k] before its point loads are applied, arriving[k] = residual[k] - sum(point_H[:, k]), because a source at Ts[k] cannot serve a sink above Ts[k]. The minimum over both flows, min(residual, arriving), fixes the hot utility target, residual[-1] + hot_util_load the cold one, and its first location the pinch. With the per-stream identity above, hot_util_load - cold_util_load equals the net heating demand.

Examples

A threshold problem: 1000 kmol/hr of water cooled 400 -> 300 K supplies every interval of 900 kmol/hr of water heated 300 -> 390 K, so no hot utility is needed and the surplus leaves as cold utility. (The grid between the ends holds the breakpoints that follow the curvature of liquid water’s enthalpy.)

>>> import biosteam as bst
>>> from hensmith.hxn_synthesis import problem_table
>>> bst.settings.set_thermo(['Water'])
>>> hot_in = bst.Stream(Water=1000., T=400., P=5e5, phase='l', units='kmol/hr')
>>> hot_out = hot_in.copy(); hot_out.vle(T=300., P=5e5)
>>> 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.)
>>> table.Ts[[0, -1]]  # shifted grid ends (hot streams 5 K down)
array([395., 295.])
>>> round(table.hot_util_load, 3)
0.0
>>> round(table.cold_util_load, -1)
1445550.0
>>> table.pinch_T
395.0
class hensmith.ProblemTable(Ts, interval_H, point_H, residual, hot_util_load, cold_util_load, pinch_T)#

Bases: tuple

Result of problem_table: the temperature-interval heat cascade of a set of process streams on the shifted temperature scale (hot streams shifted down by the minimum approach temperature, cold streams unshifted).

Ts#

Shifted grid temperatures [K], descending: every shifted breakpoint of every stream’s temperature-enthalpy curve, i.e. its end temperatures, the phase boundaries inside its range (a pure component’s saturation temperature, a mixture’s bubble and dew points), the samples of its two-phase glides and of its curved single-phase stretches (temperature-dependent heat capacity), and the outlet temperature of every point-load stream (see point_H). Temperatures closer than 1e-9 K are one grid point.

Type:

numpy.ndarray

interval_H#

(N streams x n-1 intervals) heat contributed by each stream to each open interval (Ts[k], Ts[k+1]) [kJ/hr]: positive for hot streams (heat released), negative for cold streams (heat required); zero outside the stream’s own temperature range.

Type:

numpy.ndarray

point_H#

(N x n) heat contributed at each grid temperature [kJ/hr], with the same sign convention: the jump of the stream’s curve there, i.e. a pure component’s latent heat at its (shifted) saturation temperature, the enthalpy by which a non-equilibrium end state departs from equilibrium at its own end temperature, and the whole duty of a point-load stream at its shifted outlet temperature (isothermal streams and streams whose outlet temperature moves against their duty).

Type:

numpy.ndarray

residual#

(n,) heat cascaded leaving each grid temperature, after its point loads, when no hot utility is supplied [kJ/hr]; negative where that cascade is infeasible.

Type:

numpy.ndarray

hot_util_load#

Minimum hot utility target [kJ/hr] (zero for a threshold problem).

Type:

float

cold_util_load#

Minimum cold utility target [kJ/hr].

Type:

float

pinch_T#

Shifted grid temperature of the pinch [K] (Ts[0] for a threshold problem); the hot-stream pinch temperature is pinch_T + T_min_app, the cold-stream one is pinch_T.

Type:

float

See also

problem_table

builds the table and documents the cascade.

hensmith.synthesize_network(hus, T_min_app=5.0, Qmin=0.001, force_ideal_thermo=False, avoid_recycle=False, sort_hus_by_T=False, info=None)[source]#

Synthesize a heat exchanger network without stream splits for the process streams behind a set of utility heat exchangers: pinch analysis (problem_table), then a pinch-outward plan that reaches the minimum energy requirement (MER) targets whenever the search finds an unsplit network that does, realized with one HXprocess per match and one rigorous HXutility per stream.

Parameters:
  • hus (list[HeatUtility]) – One heat utility per process stream; hu.unit is the original heat exchanger (its ins[0]/outs[0] are the stream’s end states) and the sign of hu.duty marks the stream: positive = heated (cold stream), negative = cooled (hot stream); zero-duty utilities are dropped. Heating utilities are placed before cooling utilities; within each group the given order is kept unless sort_hus_by_T. All returned per-stream arrays and lists are indexed in that rearranged order (the stream index), which also breaks ties in the planner’s search.

  • T_min_app (float, optional) – Minimum approach temperature [K]: kept on the exact stream states at both ends of and everywhere inside every process exchanger, and used to shift hot streams in the problem table. Defaults to 5.

  • Qmin (float, optional) – Planned exchangers with a duty below this [kJ/hr] are dropped and their duty left to the utilities (a large value can cost MER). Defaults to 1e-3.

  • force_ideal_thermo (bool, optional) – Analyze copies of the streams with ideal thermodynamics (thermo.ideal()); the synthesized exchangers inherit that thermo. Defaults to False.

  • avoid_recycle (bool, optional) – Never match the same (hot, cold) pair twice anywhere, so no two exchangers connect the same pair of streams (a second exchanger between them can form a recycle loop in the network). This forbids the repeated matches that some unsplit MER networks need. Defaults to False.

  • sort_hus_by_T (bool, optional) – Sort the heating utilities by inlet temperature, descending, and the cooling utilities ascending, before analysis. Defaults to False.

  • info (dict, optional) – If given, filled with the synthesis report: ‘status’ (‘mer’ if the realized network’s utilities equal the targets, else ‘best_effort’), ‘Q_hot_target’ and ‘Q_cold_target’ (the problem table’s targets), ‘Q_hot_plan’ and ‘Q_cold_plan’ (the planned utilities), ‘Q_hot’ and ‘Q_cold’ (the utilities of the realized network, from the simulated exchanger duties), ‘penalty’ (Q_hot_plan - Q_hot_target), ‘sides’ (per side of the pinch: status, method, work, proof of a needed split, gaps, units), ‘plan_targets’ (the planner’s own cascade in the first round: Q_hot, Q_cold, pinch_T, cut), ‘refine_rounds’, ‘min_approach’ (the smallest approach inside any process exchanger [K]; exact on the stream states wherever it is within the curves’ linearization tolerance of T_min_app, else from the knots), ‘deviations’ (exchangers whose simulated duty differs from the plan), ‘qmin_dropped’ (matches dropped by Qmin), ‘repaired’ (matches shrunk to keep T_min_app on the exact states, see Notes), ‘dropped’ (matches that could not be simulated; normally empty) and ‘point_loads’ (the indices of the streams whose outlet temperature does not move with their duty, e.g. an isothermal condenser or a reboiler fed as a liquid above its boiling point, so that their whole duty is a point load at the outlet temperature; each enters its first process exchanger at equilibrium at its inlet enthalpy, see Notes).

Returns:

  • 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; ins/outs [0] is the cold stream and [1] the hot stream.

  • HXs_cold_side (list[HXprocess]) – Process exchangers of the cold-side (below-pinch) design, in plan order, IDs HX_<hot>_<cold>_cs; ins/outs [0] is the hot stream and [1] the cold stream. The n-th exchanger (n >= 2) of the same pair on the same side, counted in the order the hot stream meets them, gets the suffix _<n> (e.g. HX_3_2_cs_2).

  • new_HX_utils (list[HXutility]) – One rigorous utility exchanger per stream (possibly of zero duty) bringing it from its last process exchanger to its outlet enthalpy, IDs Util_<index>_cs (hot streams) / Util_<index>_hs (cold streams); listed hot streams first.

  • hxs (list[Unit]) – The original heat exchangers, in stream order.

  • T_in_arr, T_out_arr (numpy.ndarray) – Inlet and (quenched) outlet temperatures of each stream [K].

  • pinch_T_arr (numpy.ndarray) – Per-stream pinch temperature [K] (informational): the process pinch on the stream’s own scale when the stream crosses it (ProblemTable.pinch_T for a cold stream, that plus T_min_app for a hot one); the inlet temperature of a stream whose inlet already lies past the pinch in its direction of flow, or that is isothermal or non-monotone; the outlet temperature of a stream that ends before reaching the pinch.

  • C_flow_vector (numpy.ndarray) – Heat capacity flow rate of each process stream, |H_out - H_in| / |T_in - T_out| [kJ/hr/K] from its inlet and quenched outlet (the temperature difference is replaced by 1e-12 for an isothermal stream, which therefore ranks as a very large flow rate).

  • hx_utils_rearranged (list[HeatUtility]) – The heat utilities of hus in stream order.

  • streams_inlet (list[Stream]) – One copy of each stream’s inlet, in stream order, as prepared for the analysis (ideal-thermo copies if force_ideal_thermo). The network works on further copies, so these keep their inlet state.

  • stream_HXs_dict (dict[int, list[Unit]]) – For each stream index, its process exchangers in flow order, then its utility exchanger.

  • hot_indices, cold_indices (list[int]) – Stream indices of the hot and cold streams.

Notes

Curves and targets. Every stream’s outlet is quenched to equilibrium at its own enthalpy and described by a piecewise-linear temperature- enthalpy curve (see problem_table), built once. The problem table [Kemp07] on the union of all breakpoints gives the targets, the pinch and the side of the pinch that point loads at the pinch temperature belong to. The planner models each stream by its knots on that grid, which reproduces the table’s cascade exactly.

Planner. Each stream is cut at the pinch; above it the hot streams, below it the cold streams must be served completely by process matches (“musts”), while the partners (“flexes”) leave any remainder to a utility at their far end. A depth-first search builds each side from the pinch outward, one match at a time. Every match keeps T_min_app at every knot (so internal pinches, e.g. a condensing vapor against a boiling mixture, are respected), and every step keeps the problem table of the remaining problem feasible (remaining problem analysis [Smith05], as a closed-form bound on the duty). At every level where that table is tight, the pinch design rules [LH83] (see also [Seider17], Chapter 9; number and heat-capacity-flow rules, generalized to isothermal segments, which can serve several partners in series) must hold; at the pinch itself a violation proves that MER needs stream splitting. Candidate duties are the largest feasible one and a finite set of events (a stream ticked off, a partner saved for another stream, a switch of partner, a return). The same pair may be matched repeatedly, which emulates a split by series alternation. Budgets are counted in deterministic work units, so results do not depend on machine speed. A branch and bound then reduces the number of exchangers. A side that is proven to need splits, or whose search runs out of budget, gets a best-effort plan: heat a must cannot place is moved to its pinch end, where it crosses the pinch at the cost of an equal amount of extra hot and cold utility (the penalty), minimized by greedy dives and a bisection of these gaps. See hensmith._planner for the details.

Realization. Each stream is walked in flow order from its inlet: a hot stream through its hot-side matches from its inlet end, then its cold-side matches, then its cooler; a cold stream through its cold-side matches, then its hot-side matches, then its heater. Each match becomes a plain HXprocess whose two enthalpy limits (H_lim0, H_lim1) are the planned outlet enthalpies, so that its duty reproduces the plan; its dT is T_min_app - 1e-6 K, only a guard against rounding (the approach is enforced by the plan and the check below). A planned outlet whose equilibrium state is not past the stream’s state at the exchanger inlet cannot be a limit (HXprocess rejects it): one strictly inside a non-equilibrium end jump (see StreamCurve.jumps), or on a point-load stream colder (hotter) than its inlet state when heated (cooled). That stream’s limit is left out and the other stream’s sets the duty; a match in which neither stream can take a limit runs to its dT guard (a deviation if its duty differs). A stream’s first exchanger gets its real inlet, except a point-load stream (whose outlet temperature does not move with its duty, so the plan places its whole duty there, a temperature its real inlet lies beyond): it enters at equilibrium at its inlet enthalpy, which lies on the plan’s side of its outlet temperature (a reboiler fed as a liquid above its boiling point enters as the mixture it flashes to; a vapor fed below its dew point, e.g. under force_ideal_thermo, as the mixture it partially condenses to), because HXprocess compares the inlet temperatures with dT and would refuse or cut short a match planned there. Later exchangers get the stream’s exact state at the planned enthalpy. Every exchanger is simulated once; one whose duty differs from the plan is reported in info. Each stream ends in one rigorous HXutility to its outlet enthalpy; an AssertionError is raised if it does not reproduce the quenched outlet within tolerance.

Exactness. The knots are exact at grid points but chords in between (at most 0.002 K off inside glides and curved single-phase stretches). Every planned exchanger is therefore checked on the exact stream states wherever its planned approach is within that margin of T_min_app. Where a MER plan falls short by more than 1e-6 K, the exact states are inserted as knots and the network is planned again (at most three rounds). A best-effort plan (or a MER plan still short after the last round) instead has each violating match shrunk to the largest duty that keeps the approach, the rest going to the utilities: with its inlets fixed a smaller duty can only raise a match’s approach, and the later stages of both streams move toward their inlets, which never reduces another match’s approach. Constant heat capacity streams never need either step.

Guarantees and limits. The utilities are never below the targets. Every process exchanger keeps T_min_app - 1e-6 K on the exact states at its ends and at every checked position inside it, and the heat balance closes on every stream. ‘mer’ is reported only if the realized network reaches the targets. The result is deterministic. Completeness is empirical, not proven: every pruning test is a necessary condition, so a missed MER network can only come from the finite set of candidate duties, the caps on repeated pairs or the work budgets; the planner reached MER on every unsplit-feasible problem of a certified benchmark of about 1,700 problems with 2-40 streams. Networks whose match order is cyclic cannot be represented. An unsplit MER network can need many exchangers (series alternation approaches a split only in the limit); MER always takes precedence over the number of units. Problems that need stream splits get a best-effort network whose penalty is small but not minimal in general. A side that needs splits without a pinch-rule proof spends its whole MER budget before the best-effort step. Thermosteam’s TP flashes fail silently inside the glides of some mixtures (e.g. water-ethanol with 20-50 % ethanol); an exchanger simulated there can deviate from its plan (reported in info[‘deviations’]).

HeatExchangerNetwork calls this function, rewires each stream’s stages in series, converges the network as a System and costs it.

Examples

Problem r002: one hot stream against three cold ones (heat capacity flow rates in kW/K, temperatures 300 K above those of the classic problem, T_min_app = 10 K). Its only unsplit MER network matches the hot stream twice with the same cold stream. A constant heat capacity pseudo-component makes 1000 kmol/hr of fluid per kW/K:

>>> import biosteam as bst, thermosteam as tmo
>>> from hensmith.hxn_synthesis import synthesize_network
>>> Fluid = tmo.Chemical('Fluid', search_db=False, phase='l', MW=1.,
...                      Cn=3.6, default=True)
>>> bst.settings.set_thermo([Fluid], cache=True)
>>> def process_stream(ID, T_in, T_out, CP):
...     inlet = bst.Stream(ID + '_in', Fluid=1000. * CP, T=T_in,
...                        units='kmol/hr')
...     hx = bst.HXutility(ID, ins=inlet, T=T_out, rigorous=False)
...     hx.simulate()
...     return hx
>>> units = [process_stream('C1', 440., 470., 1.),
...          process_stream('C2', 400., 420., 1.),
...          process_stream('C3', 420., 550., 2.),
...          process_stream('H1', 520., 350., 4.)]
>>> hus = [hx.heat_utilities[0] for hx in units]
>>> info = {}
>>> result = synthesize_network(hus, T_min_app=10., info=info)
>>> HXs_hot_side, HXs_cold_side, new_HX_utils = result[:3]
>>> for hx in HXs_hot_side + HXs_cold_side:
...     print(hx.ID, round(hx.Q / 3600., 6), 'kW')
HX_3_2_cs 160.0 kW
HX_3_0_cs 30.0 kW
HX_3_2_cs_2 20.0 kW
HX_3_1_cs 20.0 kW
>>> info['status']
'mer'

The utilities equal the MER targets, 80 kW of heating and 450 kW of cooling:

>>> duties = [(hx.outs[0].H - hx.ins[0].H) / 3600. for hx in new_HX_utils]
>>> round(sum(Q for Q in duties if Q > 0), 6), round(-sum(Q for Q in duties if Q < 0), 6)
(80.0, 450.0)
>>> round(info['Q_hot_target'] / 3600., 6), round(info['Q_cold_target'] / 3600., 6)
(80.0, 450.0)

References

[LH83]

Linnhoff, B., & Hindmarsh, E. (1983). The pinch design method for heat exchanger networks. Chemical Engineering Science, 38(5), 745-763.

[Kemp07]

Kemp, I. C. (2007). Pinch Analysis and Process Integration (2nd ed.). Butterworth-Heinemann.

[Smith05]

Smith, R. (2005). Chemical Process Design and Integration. Wiley.

[Seider17]

Seider, W. D., Lewin, D. R., Seader, J. D., Widagdo, S., Gani, R., & Ng, M. K. (2017). Product and Process Design Principles. Wiley. Heat Exchanger Networks (Chapter 9).

class hensmith.StreamLifeCycle(index, cold)[source]#

Bases: object

The ordered sequence of heat exchangers one process stream passes through in a synthesized heat exchanger network, from its inlet state to its final utility exchanger.

Streams are numbered by their position in the rearranged utility list of synthesize_network (cold streams first, then hot streams); the network’s stream copies and exchanger IDs embed that index (s_<index>__<exchanger ID> for the inlet streams of the exchangers, HX_<hot>_<cold>_cs / HX_<cold>_<hot>_hs for process exchangers, with a suffix _<n> for the n-th exchanger of a repeated pair, Util_<index>_cs / Util_<index>_hs for utility exchangers), which is how the life cycle is recovered from the exchangers: the IDs are parsed (the first number is the stream at port 0, the second the stream at port 1), so the stream indices are matched exactly and never as substrings of other indices or of rewired stream IDs.

Parameters:
  • index (int) – Stream index in the synthesized network.

  • cold (bool) – True for a heated (cold) stream, False for a cooled (hot) stream.

index#

Stream index in the synthesized network.

Type:

int

cold#

Whether the stream is a heated (cold) stream.

Type:

bool

name#

's_<index>', the prefix of the stream’s copies in the network.

Type:

str

life_cycle#

Stages in flow order, set by get_life_cycle; None until then.

Type:

list[LifeStage] or None

Notes

HeatExchangerNetwork builds one life cycle per stream after synthesis and stores them in HeatExchangerNetwork.stream_life_cycles, aligned with HeatExchangerNetwork.original_heat_exchangers; plot_pinch_diagram draws them.

get_relevant_units(index, new_HXs, new_HX_utils)[source]#

Return the process and utility exchangers (two lists) that carry stream index: those whose parsed ID (HX_<a>_<b>_<hs|cs>[_<n>] or Util_<a>_<hs|cs>) names it; for any other ID, those whose ID contains _<index>_.

get_life_cycle(new_HXs, new_HX_utils)[source]#

Build and return the list of LifeStage objects for this stream.

Parameters:
  • new_HXs (list[HXprocess]) – Process exchangers of the synthesized network.

  • new_HX_utils (list[HXutility]) – Utility exchangers of the synthesized network.

Returns:

One stage per port that carries this stream: for an exchanger ID of the synthesizer (see the class notes) the port its ID assigns to index (0 or 1 for a process exchanger, 0 for a utility exchanger); for any other ID, every port among 0 and 1 (0 for a utility) whose inlet ID contains 's_<index>_'. Sorted in flow direction: by inlet enthalpy, ascending for a cold stream and descending for a hot one; ties (zero-duty stages only) put the stream’s first side of the pinch first (cold-side stages for a cold stream, hot-side stages for a hot one) and the utility last. Also stored as life_cycle.

Return type:

list[LifeStage]

show()[source]#

Print the life cycle, one stage per line.

class hensmith.hxn_synthesis.LifeStage(unit, index)[source]#

Bases: object

One stage of a stream’s passage through the synthesized network: the heat exchanger it passes through and which of that exchanger’s inlet/outlet pairs carries the stream.

Parameters:
  • unit (HXprocess or HXutility) – Heat exchanger of the synthesized network.

  • index (int) – Position of the stream in unit.ins / unit.outs (0 or 1 for an HXprocess; always 0 for an HXutility).

s_in#

unit.ins[index], the stream entering this stage.

Type:

Stream

s_out#

unit.outs[index], the stream leaving this stage.

Type:

Stream

H_in#

Enthalpy of s_in [kJ/hr], read from the stream when accessed.

Type:

float

H_out#

Enthalpy of s_out [kJ/hr], read from the stream when accessed.

Type:

float

hensmith.plot_pinch_diagram(stream_life_cycles, inlet_Ts, outlet_Ts, hot_side_HXs, cold_side_HXs, Qmin=0.001, original_hxs=None, show_units=True, show_auxiliary_units=True, show_stream_IDs=True, show_legend=True, ax=None, file=None, dpi=300)[source]#

Draw a pinch diagram of a synthesized heat exchanger network: cold streams (blue, flowing left to right) above hot streams (red, flowing right to left), one vertical connector per process heat exchanger with its duty, a dashed pinch line separating the cold-side from the hot-side exchangers, and circles marking the utility exchangers that bring each stream to its outlet temperature.

Parameters:
  • stream_life_cycles (list[StreamLifeCycle]) – One per stream, as built by HeatExchangerNetwork.

  • inlet_Ts (array-like) – Stream inlet and outlet temperatures [K], indexed like the life cycles.

  • outlet_Ts (array-like) – Stream inlet and outlet temperatures [K], indexed like the life cycles.

  • hot_side_HXs (list[HXprocess]) – Process exchangers above and below the pinch.

  • cold_side_HXs (list[HXprocess]) – Process exchangers above and below the pinch.

  • Qmin (float, optional) – Utility exchangers with a duty at or below this [kJ/hr] are not marked.

  • original_hxs (list[Unit], optional) – The original heat exchanger of each stream (indexed like the life cycles). Required for the stream labels below.

  • show_units (bool, optional) – Label each stream with the unit operation that owns its original heat exchanger (the main unit for auxiliary exchangers).

  • show_auxiliary_units (bool, optional) – Label each stream with the name of its original heat exchanger within the main unit (e.g. ‘condenser’), if it is an auxiliary unit.

  • show_stream_IDs (bool, optional) – Label each stream with the ID of the original heat exchanger’s inlet.

  • show_legend (bool, optional) – Add a legend of the symbols below the diagram.

  • ax (matplotlib.axes.Axes, optional) – Axes to draw on; a new figure is created if not given.

  • file (str, optional) – If given, the figure is saved to this path.

  • dpi (int, optional) – Resolution used when saving.

Returns:

  • fig (matplotlib.figure.Figure)

  • ax (matplotlib.axes.Axes)

Notes

Temperatures are shown in degC and heat flows in kJ/hr at the inlet and outlet of each stream. Exchanger columns on each side of the pinch are ordered so that each stream meets them in flow direction whenever the network allows it. Stream labels read ‘<unit> - <auxiliary> (<stream>)’ next to the stream index at the inlet.

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()
>>> fig, ax = HXN.plot_pinch_diagram()
>>> connectors = [i for i in ax.findobj() if (i.get_gid() or '').startswith('HX:')]
>>> len(connectors) == len(HXN.new_HXs)
True
>>> import matplotlib.pyplot as plt
>>> plt.close(fig)

Note

Internals. hensmith.hxn_synthesis.temperature_interval_pinch_analysis (the first step of 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.