Quickstart#
The canonical hensmith example is a small methanol/water system: a shortcut
distillation column (divided into a separately-costed rectifier and stripper)
whose condenser and reboiler are auxiliary exchangers, a cooler on each of the
column’s two products, and a flash whose feed is heated by its own auxiliary
exchanger. A
HeatExchangerNetwork facility added to that system
integrates every heating and cooling utility in it. Once biosteam is imported
the simulation takes about a second, and every number, table and figure below
is output of the code shown on this page.
Build the flowsheet#
import biosteam as bst
from hensmith import HeatExchangerNetwork
bst.settings.set_thermo(['Water', 'Methanol', 'Glycerol'], cache=True)
bst.main_flowsheet.set_flowsheet('quickstart')
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)
bst.settings.set_thermo fixes the chemicals and the property package used
for every stream that follows; cache=True reuses cached chemical objects
rather than recreating them. The two feeds are given as molar flow rates in
kmol/hr, in the order the chemicals were declared: feed1 is 8000 water,
100 methanol and 25 glycerol, and feed2 is 10000 water, 1000 methanol and
10 glycerol.
D1 separates methanol (light key) from water (heavy key) with a shortcut
method. Its condenser and reboiler are always auxiliary HXutility units of
the column, each with its own heat utility, and the network integrates each of
them like any other exchanger. is_divided=True costs the rectifier and
stripper as two separate vessels rather than one, and matches the
HeatExchangerNetwork docstring example. D1_H1 and
D1_H2 cool the bottoms product and the distillate to 300 K, and each is a
stand-alone exchanger in its own right. F1 flashes feed2 to 90 % vapor
at 101325 Pa; the heating its feed needs is carried by an auxiliary exchanger
of the flash, created when the flash is constructed and simulated as part of
the flash’s own run.
Add the network#
HXN = HeatExchangerNetwork('HXN', T_min_app=5.)
sys = bst.System.from_units('sys', units=[D1, D1_H1, D1_H2, F1, HXN])
T_min_app=5. is the minimum approach temperature in K: no synthesized
process exchanger is allowed to transfer heat across a temperature difference
smaller than this. It is the one thermodynamic knob that sets how much
integration is feasible, and it is revisited in Pinch analysis and targets.
bst.System.from_units builds the system from the units given, sorting the
process units into a simulation order and setting the facility aside; the
network is listed among the units but is not connected to any of them.
sys.diagram()
The quickstart system. HXN appears as a facility with no material
streams: it neither receives nor produces process material, and only reads
the heat utilities of the other units. Besides the two visible exchangers
D1_H1 and D1_H2, the column’s condenser and reboiler and the flash’s
feed exchanger are auxiliary exchangers inside their owner units, and the
network integrates those as well – 5 streams in total, of which 3 are
auxiliaries.#
The quickstart system. HXN appears as a facility with no material
streams: it neither receives nor produces process material, and only reads
the heat utilities of the other units. Besides the two visible exchangers
D1_H1 and D1_H2, the column’s condenser and reboiler and the flash’s
feed exchanger are auxiliary exchangers inside their owner units, and the
network integrates those as well – 5 streams in total, of which 3 are
auxiliaries.#
Because HeatExchangerNetwork is a BioSTEAM Facility, it
is simulated only after every process unit in the system has converged, so it
always works with final duties. Its network_priority = -2 is the lowest of
the standard facilities, which places it first among them: the chilled water
package, cooling tower and boiler that follow are then sized on the loads the
network has already reduced.
Simulate#
sys.simulate()
Converging the process units comes first; the network’s pinch analysis,
synthesis and costing all run afterwards, in its costing step. The original
streams and heat exchangers of the system are left untouched – the stream
copies and the new exchangers the network creates live in a separate flowsheet
named sys_HXN, after the ID of the system.
Read the savings#
print(HXN.results())
Heat exchanger network Units HXN
Low pressure steam Duty kJ/hr -6.32e+07
Flow kmol/hr -1.63e+03
Cost USD/hr -389
Chilled water Duty kJ/hr 4.21e+07
Flow kmol/hr -2.78e+04
Cost USD/hr -211
Cooling water Duty kJ/hr 1.79e+07
Flow kmol/hr -1.22e+04
Cost USD/hr -5.98
Purchase cost Heat exchangers USD 3.46e+05
Total purchase cost USD 3.46e+05
Installed equipment cost USD 1.11e+06
Utility cost USD/hr -605
Every utility row of this table is a difference, not a requirement: the
facility’s heat_utilities are the utilities of the new network summed with
the reversed original ones, that is new - original. A negative cost is
therefore a saving, and all three agents show one here: -389 USD/hr of low
pressure steam, -211 USD/hr of chilled water and -5.98 USD/hr of cooling water,
for a total utility cost of -605 USD/hr. The flow rows are negative for all
three agents as well, so less of each utility is consumed. The duty rows carry
the sign convention of the agent they belong to: the steam duty is
-6.32e+07 kJ/hr, exactly the reduction in heating load reported below, while
the chilled and cooling water duties are positive (4.21e+07 and
1.79e+07 kJ/hr) because cooling duties are negative to begin with, so a
positive difference again means less cooling.
The capital rows are differences too, but clipped at zero. Both the network’s
installed_costs['Heat exchangers'] and purchase_costs['Heat exchangers']
are max(0, new - original), the added exchanger cost – 1.11e+06 USD
here. Clipping at zero means a network
whose exchangers happen to be cheaper than the ones it replaces is reported as
adding nothing, rather than as a capital credit.
print(f'heating utility: {HXN.original_heat_util_load:.4g} -> {HXN.actual_heat_util_load:.4g} kJ/hr')
print(f'cooling utility: {HXN.original_cool_util_load:.4g} -> {HXN.actual_cool_util_load:.4g} kJ/hr')
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]}')
heating utility: 3.609e+08 -> 2.977e+08 kJ/hr
cooling utility: 6.201e+07 -> 1.96e+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']
The heating load falls from 3.609e+08 to 2.977e+08 kJ/hr, a reduction of
17.5 %; the ratio of the two, 0.82, is the value checked by the
HeatExchangerNetwork docstring example. The cooling load
falls from 6.201e+07 to 1.96e+06 kJ/hr, a reduction of 96.8 %: nearly all of
the cooling duty of this system can be recovered into a stream that needed
heating. Four process exchangers do that work, and they are the new_HXs of
the network. The energy balance error, -1.8e-11 %, checks that the synthesized
network moves exactly as much heat as the original one; it is computed on
every synthesis and compared against acceptable_energy_balance_error.
Draw the pinch diagram#
fig, ax = HXN.plot_pinch_diagram()
The synthesized network, read as a pinch diagram. The two cold streams
(blue, drawn left to right) are 0 D1 - reboiler and 1
F1 - heat_exchanger (feed2); the three hot streams (red, drawn right to
left) are 2 D1_H1 (bottoms_product), 3
D1 - condenser (vapor) and 4 D1_H2 (distillate). Each stream is
annotated with its inlet and outlet temperature and enthalpy flow. The four
vertical connectors are the process exchangers, each labelled with its duty
in kJ/hr: 3.34E4 between streams 1 and 4, 5.03E6 between 1 and 2, 3.71E7
between 0 and 2, and 1.79E7 between 1 and 3. Columns are ordered so that a
stream meets its exchangers in flow direction, which is why stream 1 reads
3.34E4, 5.03E6, 1.79E7 from left to right. The dashed line is the pinch,
separating the cold-side design on its left from the hot-side design on its
right; all four exchangers of this network lie on the hot side. The open
circles are the utility exchangers that finish each stream: a hot utility
(red) at the outlet of both cold streams, and a single cold utility (blue)
on stream 2. Streams 3 and 4 carry no circle because their remaining
cooling duty is zero – process heat exchange alone brings them to their
outlet temperature.#
Where to next#
Pinch analysis and targets – where the utility targets above come from, and what
T_min_appchanges.Anatomy of a synthesized network – the exchangers and stream life cycles behind the diagram, unit by unit.
Configuring the network and a larger system – the constructor options of the facility, and applying it to a larger system.
Key Concepts – the pinch concepts and terminology used throughout.
API Reference – the full API reference.