Targeting and supertargeting#

The chapters before this one judged a network by one pair of numbers, the minimum energy requirement (MER) targets of its problem table. Pinch analysis can say much more before a single exchanger exists: how the heating and cooling targets split between several utilities and what they cost, how much exchanger area and how many units a network of these streams needs, what that area costs, and therefore which T_min_app minimizes the total annualized cost. hensmith.targeting computes all of these (PinchTargets), sweeps T_min_app for the least total annualized cost (supertarget()), draws the composite curves, the grand composite curve and the sweep, and compares a synthesized network with its targets (network_report()).

Every number and figure below is output of the code shown on this page. The quickstart system built here is chapter 1’s build repeated verbatim, so this page runs on its own.

Imports and the system#

import numpy as np
import matplotlib.pyplot as plt
from hensmith import (supertarget, network_report, plot_composite_curves,
                      plot_grand_composite_curve, plot_supertargets)
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)
HXN = HeatExchangerNetwork('HXN', T_min_app=5.)
sys = bst.System.from_units('sys', units=[D1, D1_H1, D1_H2, F1, HXN])
sys.simulate()

The facility’s targets#

HXN.targets() returns the targets of the streams the facility synthesized from, in its stream order and at its T_min_app (or at another one, HXN.targets(10.), without synthesizing anything).

targets = HXN.targets()        # the facility's streams at its T_min_app
print(targets)
print('streams:', targets.labels)
print(f'shifted pinch: {targets.pinch_T:.2f} K')
info = HXN.synthesis_info
print('equal to the synthesis targets:',
      targets.Q_hot == info['Q_hot_target'], targets.Q_cold == info['Q_cold_target'])
print(f'heating utility of the network: {HXN.actual_heat_util_load:.4g} kJ/hr')
<PinchTargets: 5 streams, T_min_app = 5 K, Q_hot = 2.828e+08 kJ/hr, Q_cold = 1.936e+06 kJ/hr>
streams: ['D1 - reboiler', 'F1 - heat_exchanger', 'D1_H1', 'D1 - condenser', 'D1_H2']
shifted pinch: 298.15 K
equal to the synthesis targets: True True
heating utility of the network: 2.977e+08 kJ/hr

The streams are labeled by their units, an auxiliary exchanger by its owner and its own ID. The targets are the synthesis’s own, bit for bit: the facility prepares the streams for targeting exactly as the synthesis prepares them. They are process side: 2.828e+08 kJ/hr is the heat the cold streams need from a hot utility. The network’s heating load, 2.977e+08 kJ/hr here and in Configuring the network and a larger system, is utility side: biosteam divides each duty by its agent’s heat transfer efficiency, 0.95 for steam, and 2.828e+08 / 0.95 is 2.977e+08.

Nothing is stored on the facility, and nothing is re-synthesized: the targets are those of the units’ current states, which, with cache_network=True, can differ from the cached network’s. Each stream runs from its unit’s inlet now to its outlet as the unit was last simulated, so after a feed changes, simulate the system (or the units) before targeting: a stream whose inlet has changed in flow since warns that its targets mix the new inlet with the former outlet, a state of no process. The streams themselves are those of the facility’s last simulation, one per heat utility its units listed then: a unit in parallel simulated alone that changes its number of units (a fermentor’s parallel['self'], set by its volume) warns, as biosteam lists its heat utilities once per unit, until the system is simulated. The same object can be built without a facility, from the heat utilities of heat exchangers with PinchTargets.from_heat_utilities (as the trade-off example below does) or from streams as problem_table() takes them. The facility’s selection, bst.process_tools.heat_exchanger_utilities_from_units(sys.units), is the one to give it: a heat utility that a unit adds for itself (a reactor’s or a heated mixer’s, hu.hxn_ok False) raises a ValueError, as its unit’s first inlet and outlet need not be the stream it heats or cools. A selection is valid until its units are simulated again: biosteam then gives each unit new heat utilities, emptying the former ones, so select them again after any simulation (a former one, no longer among its unit’s heat_utilities, raises a ValueError rather than drop out as a heat utility without duty). A heat utility counts as a stream each time it is given, as in the synthesis: biosteam lists the heat utility of a unit in parallel once per unit (a fermentor’s recirculation cooler, for one, once per reactor of its parallel['self']), and so does the selection. One given more often than its unit and owners list it, as selections from overlapping unit lists (a unit and an auxiliary unit of it) give it, raises a ValueError: its stream would count once more per extra one. Given key_units=sys.units too, the keys of approach_contributions and of h may name any unit, as the facility’s may: 'K1' then covers the intercoolers of a biosteam.MultistageCompressor K1, which builds them without an owner. After a simulation with replace_unit_heat_utilities=True, though, the units’ heat utilities hold the network’s utilities, not the process duties, and those of the streams that the process exchangers serve completely are empty: the selection then gives the wrong streams (with a RuntimeWarning), and HXN.targets() is the one to take.

Several utilities on the grand composite curve#

The MER targets say how much heating and cooling the streams need, not which utilities supply them. utility_targets() places biosteam’s heating and cooling agents on the grand composite curve, following biosteam’s own rules for which agent may serve which temperature, and prices each with biosteam.

utilities = targets.utility_targets()     # biosteam's default agents
print('agent               duty        utility duty  level   process T [K]     cost')
print('                    [kJ/hr]     [kJ/hr]       [K]     in -> out         [USD/hr]')
for u in utilities:
    print(f'{u.ID:<18}  {u.duty:<10.4g}  {u.utility_duty:<12.4g}  {u.level:<6.2f}  '
          f'{u.T_in:.2f} -> {u.T_out:.2f}  {u.cost:.2f}')
print(f'utility cost target: {sum(u.cost for u in utilities):.2f} USD/hr')
for u in utilities:
    print(f'{u.ID}: {u.flow:.4g} kmol/hr, from {u.T_utility_in:.2f} to {u.T_utility_out:.2f} K')
agent               duty        utility duty  level   process T [K]     cost
                    [kJ/hr]     [kJ/hr]       [K]     in -> out         [USD/hr]
low_pressure_steam  2.828e+08   2.977e+08     407.19  298.15 -> 372.60  1829.93
chilled_water       1.936e+06   1.936e+06     282.37  303.15 -> 300.00  9.68
utility cost target: 1839.61 USD/hr
low_pressure_steam: 7695 kmol/hr, from 412.19 to 412.19 K
chilled_water: 1443 kmol/hr, from 280.37 to 298.15 K

Each agent has a level on the shifted scale: the warmest temperature it can serve, or the coldest, for a coolant. A heating agent’s level is its temperature less biosteam’s approach, the agent’s own dT plus HeatUtility.dT: 412.19 - 0 - 5 = 407.19 K for low pressure steam. A coolant’s level is its temperature plus that approach, moved onto the shifted scale by subtracting T_min_app, the hot streams’ shift: 280.37 + 2 + 5 - 5 = 282.37 K for chilled water. Low pressure steam, first in biosteam’s list of heating agents, is above the top of the cold streams, so it takes the whole hot utility target; a hotter steam would only be used for what it cannot serve. Every cold stream above the pinch is heated by it, from 298.15 K (the pinch) to 372.60 K, and biosteam prices that duty at 1829.93 USD/hr, with the same division by the heat transfer efficiency as in the network: the utility duty is 2.977e+08 kJ/hr. On the cold side, cooling water, the first coolant in the list, gets nothing: all the cold utility is needed below 303.15 K (30 °C, the pinch on the hot streams’ scale), colder than cooling water can serve. Chilled water serves it. A sensible agent, it enters at 280.37 K and leaves at 298.15 K, 5 K below the warmest process temperature it serves, 303.15 K: biosteam limits a sensible agent’s outlet at that end, and hensmith also keeps its line 5 K from the process across the whole stretch, which here allows the same outlet (a condenser at the cold end of the stretch would hold it lower). The utility cost target, 1839.61 USD/hr, is the cost of the MER utilities with biosteam’s agents placed this way.

The grand composite curve shows the allocation: each agent is a horizontal segment at its level, as wide as its duty, the heating ones from the lowest level up, each starting where the one below ends, and the cooling ones mirrored below the pinch.

fig, ax = plot_grand_composite_curve(targets, utilities=utilities)
Grand composite curve of the quickstart system: a black curve of shifted temperature in degrees Celsius against the heat cascaded in kJ/hr, starting near zero at the pinch, marked by an open circle at 25 degrees Celsius, and rising to the hot utility target at about 99 degrees Celsius, with a red horizontal segment labelled low_pressure_steam at about 134 degrees Celsius spanning the whole hot utility target, and a very short blue segment labelled chilled_water near 9 degrees Celsius.

The grand composite curve of the quickstart system at T_min_app = 5 K (the same curve as chapter 2’s), with the utility targets as a staircase: low pressure steam at its level, 407.19 K (134 °C on the shifted scale), serves all 2.828e+08 kJ/hr of the hot utility target, above every cold stream; chilled water at 282.37 K serves the 1.936e+06 kJ/hr of cold utility below the pinch. The open circle is the pinch, 298.15 K shifted.#

Composite curves, plain and balanced#

Chapter 2 drew the composite curves from the fields of a ProblemTable in its own script; plot_composite_curves() draws them from the targets, and composite_curves() returns their points. With kind='balanced' the utilities join the curves: the heating utilities the hot composite and the cooling ones the cold composite, so that both curves end at the same heat. These balanced composite curves are what the area target below is computed on.

fig, ax = plot_composite_curves(targets)
fig_balanced, ax_balanced = plot_composite_curves(targets, kind='balanced',
                                                  utilities=utilities)
Composite curves of the quickstart system drawn by hensmith: a red hot composite curve above a blue cold composite curve on axes of temperature in degrees Celsius against enthalpy in kJ/hr, a shaded band marking the heat recovered, a cold utility label of 1.94E6 kJ/hr at the cold end and a hot utility label of 2.83E8 kJ/hr at the warm end.

The composite curves of chapter 2, drawn by the library: the shaded band is the heat recovered, the cold composite starts at the cold utility target, 1.936e+06 kJ/hr, and ends the hot utility target, 2.828e+08 kJ/hr, beyond the end of the hot one. The script checks that their knots are chapter 2’s.#

Balanced composite curves of the quickstart system: the red hot balanced composite follows the hot composite and then rises vertically to a long flat at about 139 degrees Celsius labelled low_pressure_steam 2.83E8 kJ/hr, which runs to the end of the blue cold balanced composite; the blue curve starts with a short steep line from about 7 to 25 degrees Celsius labelled chilled_water 1.94E6 kJ/hr and then follows the cold composite.

The balanced composite curves. The steam condenses at 412.19 K, so it is a flat at that temperature (139 °C), 2.828e+08 kJ/hr long, at the warm end of the hot curve; chilled water warms from 280.37 to 298.15 K, a line at the cold end of the cold curve, 1.936e+06 kJ/hr long. Both curves now span the same heat; the area target assumes that every bit of it is transferred vertically, from the hot curve to the cold curve below it.#

Area, units, capital and total cost#

On the balanced composite curves, summary() targets the rest of the cost of a network. The methods that take utilities need those of the same targets: a list from targets at another T_min_app (targets.at(20.), say) or a part of one would not balance the curves, so its duties are checked against Q_hot and Q_cold, and the shifted temperatures its levels are on (each utility’s shift) against the targets’ shift; a mismatch raises a ValueError.

print('units:', targets.units_target(utilities))
capital = targets.capital_cost_target(utilities)
print(f"{capital['N_area_shells']} of {capital['N_shells']} shells share the area: "
      f"{capital['shell_area']:.4g} m2 each, {capital['type']}")
summary = targets.summary()     # 10 % over 10 years; 8000 h/yr (no system hours)
for key, value in summary.items():  # numbers, and the shell type
    print(f'{key:<24} {value}' if isinstance(value, str) else f'{key:<24} {value:.6g}')
units: {'above': 5, 'below': 1, 'total': 6}
6 of 6 shells share the area: 991 m2 each, Floating head
T_min_app                5
Q_hot                    2.82781e+08
Q_cold                   1.93588e+06
utility_cost             1839.61
area                     5946.13
N_units                  6
N_shells                 6
N_area_shells            6
shell_area               991.022
shell_type               Floating head
purchase_cost            710660
installed_cost           2.28172e+06
annualized_capital_cost  371339
operating_cost           1.47169e+07
TAC                      1.50882e+07
  • Area, 5946.13 m²: Bath’s formula, the area of an ideal network that transfers heat vertically between the balanced composite curves, each stretch between their breakpoints at its log-mean temperature difference and each stream’s share of it over its own film coefficient. By default every stream and utility has a film coefficient of 1 kW/m²/K, so any pair has biosteam’s heuristic overall coefficient of 0.5 kW/m²/K; h= and h_utilities= give streams and agents their own.

  • Units, 6 (5 above the pinch and 1 below): Euler’s rule, the number of streams and utilities less one, in each region between the zeros of the balanced grand composite curve. It is an estimate, not a bound: a subset of streams that balances on its own needs fewer units.

  • Capital: the area divided into as many shells of equal area as there are units, here 6 floating head shells of 991 m², each costed with biosteam’s own correlations: 2.28172e+06 USD installed. When the equal shells would fall below 150 ft² (14 m²), where biosteam’s double pipe costs a fraction of a shell and tube, the target keeps the shell and tubes the area fills if they cost more (see Key Concepts); 'N_area_shells', 'shell_area' and 'shell_type' say which shells carry the area.

  • Total annualized cost (TAC), 1.50882e+07 USD/yr: the installed cost times the capital recovery factor (371339 USD/yr: 10 % interest over 10 years by default) plus the utility cost times the operating hours (1.47169e+07 USD/yr: 8000 h/yr by default). It counts the bare module cost of the exchangers only, no Lang factor and no working capital. interest=, years= and operating_hours= change the economics, or tea= takes them from a biosteam TEA. The facility’s targets know their system: if it has operating hours (a TEA sets them), the summary and the sweep below use them instead of 8000 h/yr. The quickstart system has none.

Supertargeting: the least total annualized cost#

Chapter 4 swept T_min_app by synthesizing a network at each value. supertarget() sweeps the targets instead: it reuses the streams’ curves, so no stream is flashed again, and evaluates the summary at each T_min_app, then refines the grid around the least TAC. Each point still builds its own problem table, which reads every stream’s enthalpy on the grid; on a system of many streams with rigorous thermodynamics, a sweep of the default 40 points and three refinements can take longer than a synthesis, and a coarser T_min_apps or a smaller refine keeps it short.

sweep = supertarget(targets, np.arange(1., 31.))
print(sweep)
print('T_min_app  Q_hot      Q_cold     area    units  installed  capital    operating  TAC')
print('[K]        [kJ/hr]    [kJ/hr]    [m2]           [USD]      [USD/yr]   [USD/yr]   [USD/yr]')
optimum = sweep.optimum
after = sweep.records[sweep.records.index(optimum) + 1]     # the next point evaluated
for r in sweep.records:
    if r['T_min_app'] in (1., 2., 5., 10., 15., 20., 25., 30.) or r is optimum or r is after:
        print(f"{r['T_min_app']:<9.4g}  {r['Q_hot']:<9.4g}  {r['Q_cold']:<9.4g}  "
              f"{r['area']:<6.0f}  {r['N_units']:<5d}  {r['installed_cost']:<9.4g}  "
              f"{r['annualized_capital_cost']:<9.4g}  {r['operating_cost']:<9.4g}  {r['TAC']:.0f}")
print(f'optimum {sweep.T_min_app:.4g} K, at the bound of the sweep: {sweep.at_bound}')
fig, ax = plot_supertargets(sweep)
<Supertargets: 54 points, T_min_app 1-30 K, optimum 1.849 K (TAC 1.494e+07 USD/yr)>
T_min_app  Q_hot      Q_cold     area    units  installed  capital    operating  TAC
[K]        [kJ/hr]    [kJ/hr]    [m2]           [USD]      [USD/yr]   [USD/yr]   [USD/yr]
1          2.808e+08  0          6836    5      2.482e+06  4.04e+05   1.454e+07  14943217
1.849      2.808e+08  0          6836    5      2.482e+06  4.04e+05   1.454e+07  14943211
1.853      2.808e+08  2049       6835    6      2.555e+06  4.158e+05  1.454e+07  14955188
2          2.809e+08  9.219e+04  6759    6      2.532e+06  4.12e+05   1.455e+07  14959665
5          2.828e+08  1.936e+06  5946    6      2.282e+06  3.713e+05  1.472e+07  15088200
10         2.859e+08  5.009e+06  5440    6      2.127e+06  3.461e+05  1.5e+07    15344973
15         2.9e+08    9.138e+06  5158    9      2.269e+06  3.693e+05  1.533e+07  15694680
20         2.975e+08  1.662e+07  5073    9      2.243e+06  3.651e+05  1.573e+07  16093036
25         3.049e+08  2.41e+07   5042    9      2.234e+06  3.635e+05  1.613e+07  16498447
30         3.124e+08  3.157e+07  5042    9      2.234e+06  3.635e+05  1.654e+07  16904956
optimum 1.849 K, at the bound of the sweep: False

The quickstart system is dominated by steam: its operating cost, 1.454e+07 to 1.654e+07 USD/yr over the sweep, is about forty times its annualized capital cost, so the TAC follows the utility cost and falls as T_min_app does. Below about 1.85 K the system is a threshold problem: it needs no cold utility at all, so the hot utility, its steam and the balanced composite curves no longer change with T_min_app, and neither does any target. The TACs at 1 and at 1.849 K differ by less than 7 USD/yr, 4e-7 of the TAC, which is no physical effect: the problem table’s grid moves with T_min_app over the curved (vapor-liquid) profiles of the streams, and the area follows it, too little to show in the table (6836 m² at both). Every T_min_app of that flat stretch is an optimum but for these few USD/yr. The area, and with it the TAC, falls steadily across the stretch as the grid moves, so the least TAC is always the warmest point evaluated below the threshold, here 1.849 K (inside the sweep, not at its bound), 0.004 K short of the step. That step comes at the next point, 1.853 K: the cold utility appears and needs a unit of its own (6 units), which adds about 12000 USD/yr to the TAC. Between 10 and 15 K, where chapter 4’s sweep saw the pinch move to the column condenser’s vapor inlet, the units target jumps to 9.

Total annualized cost against T_min_app from 1 to 30 K for the quickstart system: the black total annualized cost and the red operating cost both rise almost linearly from about 1.5e7 USD per year to about 1.7e7 USD per year, the blue annualized capital cost stays near 4e5 USD per year along the bottom, and an open circle with a dashed vertical line marks the optimum at 1.85 K.

The sweep of the quickstart system. The total annualized cost (black) is the operating cost (red) plus the annualized capital cost (blue); the circle and the dashed line mark the least TAC, 1.494e+07 USD/yr at 1.849 K. Capital is a small fraction of the TAC, so the optimum has the least utility: it is on the flat stretch below 1.85 K, where no cold utility (and no unit for it) is needed, and any point of that stretch would do.#

A trade-off between capital and energy needs a system whose capital matters. The two streams below, a hot and a cold one of 100 kW/K each, are served by steam at 450 K and by water evaporating at 280 K, two latent agents made for the example with prices of 4e-7 and 2e-7 USD/kJ. Their targets come straight from their heat utilities with PinchTargets.from_heat_utilities, and heating_agents= and cooling_agents= are passed on to every summary of the sweep.

import thermosteam as tmo
from hensmith import PinchTargets
fluid = tmo.Chemical('Fluid', search_db=False, phase='l', MW=1.,
                     Cn=3.6, default=True)     # 1000 kmol/hr: 1 kW/K
bst.settings.set_thermo([fluid])
bst.main_flowsheet.set_flowsheet('trade_off')
H1 = bst.HXutility('H1', ins=bst.Stream('H1_in', Fluid=1e5, T=400.),
                   T=320., rigorous=False)       # 100 kW/K, 400 -> 320 K
C1 = bst.HXutility('C1', ins=bst.Stream('C1_in', Fluid=1e5, T=310.),
                   T=390., rigorous=False)       # 100 kW/K, 310 -> 390 K
H1.simulate(); C1.simulate()
pair = PinchTargets.from_heat_utilities([H1.heat_utilities[0], C1.heat_utilities[0]])
water = bst.HeatUtility.get_heating_agent('low_pressure_steam').thermo
steam = bst.UtilityAgent('steam_450K', Water=1, T=450., P=1e6, phase='g',
                         heat_transfer_price=4e-7, thermo=water)
refrigerant = bst.UtilityAgent('water_280K', Water=1, T=280., P=1e3, phase='l',
                               heat_transfer_price=2e-7, thermo=water)
pair_sweep = supertarget(pair, np.arange(11., 61.), heating_agents=[steam],
                         cooling_agents=[refrigerant])
print(pair_sweep)
for key in ('Q_hot', 'area', 'N_units', 'installed_cost', 'annualized_capital_cost',
            'operating_cost', 'TAC'):
    print(f'{key:<24} {pair_sweep.optimum[key]:.6g}')
fig, ax = plot_supertargets(pair_sweep)
<Supertargets: 74 points, T_min_app 11-60 K, optimum 21.59 K (TAC 9.164e+04 USD/yr)>
Q_hot                    4.17312e+06
area                     719.867
N_units                  3
installed_cost           439992
annualized_capital_cost  71606.7
operating_cost           20031
TAC                      91637.6

Here the annualized capital cost (71606.7 USD/yr at the optimum) falls as T_min_app grows and the operating cost (20031 USD/yr) rises with it, and the least TAC, 91637.6 USD/yr, is at 21.59 K, well inside the sweep: the classic supertargeting curve.

Total annualized cost against T_min_app from 11 to 60 K for a two-stream problem: the blue annualized capital cost falls steeply from about 110000 USD per year and flattens near 53000, the red operating cost rises linearly from near zero to about 87000 USD per year, and the black total annualized cost has a U shape with its minimum, marked by an open circle and a dashed vertical line, at 21.6 K.

The sweep of the two-stream problem: the capital cost (blue) falls and the operating cost (red) rises with T_min_app, and their sum (black) has its minimum, 9.164e+04 USD/yr, at 21.59 K.#

The shells of this pair are large, so the capital cost falls smoothly. With small streams it falls in steps: about one shell and tube each time the shrinking area fills one fewer of 150 ft², or when the last one goes and double pipes take the area. The least TAC then often lies just past such a step, at the first T_min_app evaluated beyond it, and says more about biosteam’s 150 ft² than about a trade-off of energy and capital; the sweep’s 'N_area_shells' and 'shell_type' (sweep.to_frame()) show where the steps are. A network has such steps too, but where its own exchangers cross 150 ft².

The network against its targets#

network_report() compares the network the facility synthesized with the targets of its streams, on the same options.

report = network_report(HXN)
target = report['targets']      # targets.summary() with the same options
print('                        network     target')
print(f"utility cost [USD/hr]   {report['utility_cost']:<10.2f}  {target['utility_cost']:.2f}")
print(f"units                   {report['N_units']:<10d}  {target['N_units']}")
print(f"area [m2]               {report['area']:<10.0f}  {target['area']:.0f}")
print(f"area, biosteam [m2]     {report['area_biosteam']:.0f}")
print(f"installed cost [USD]    {report['installed_cost']:<10.4g}  {target['installed_cost']:.4g}")
print(f"TAC [USD/yr]            {report['TAC']:<10.0f}  {target['TAC']:.0f}")
print('exchanger    area   biosteam [m2]')
for ID, (area, area_biosteam) in report['exchanger_areas'].items():
    print(f'{ID:<11}  {area:<5.0f}  {area_biosteam:.0f}')
                        network     target
utility cost [USD/hr]   1839.61     1839.61
units                   7           6
area [m2]               7462        5946
area, biosteam [m2]     5536
installed cost [USD]    2.184e+06   2.282e+06
TAC [USD/yr]            15072295    15088200
exchanger    area   biosteam [m2]
HX_1_2_hs    466    495
HX_0_2_hs    3097   3101
HX_1_4_hs    1      1
HX_1_3_hs    460    459
Util_2_cs    101    103
Util_0_hs    356    177
Util_1_hs    2983   1200

The network reaches the utility cost target, 1839.61 USD/hr: it is at MER, with the steam and chilled water that the targets allocate. That is not a rule: the utility cost target is the cost of biosteam’s agents placed on the grand composite curve, not a minimum, and a network at MER, which serves each stream with one agent at its outlet, can cost more (a costlier agent over a whole stream) or less (natural gas, which with biosteam’s default prices costs less per kJ than steam, heating a whole stream that ends above the hottest steam) than the target. The comparison of everything else needs care too, because a target is an estimate of an ideal network:

  • Area. The report’s 'area', 7462 m², re-sizes every exchanger of the network like the target sizes it: the same film coefficients, a correction factor of 1 and the integrated temperature difference along the streams’ profiles as the targets’ curves model them. On that basis the network needs more area than the 5946 m² target, as it should with equal film coefficients and the same utilities. biosteam’s own sizing, 5536 m² ('area_biosteam'), is lower still, mostly because it takes an overall coefficient of 1.0 kW/m²/K for condensing steam against a boiling stream (the steam heaters Util_0_hs and Util_1_hs: 177 and 1200 m² instead of 356 and 2983 m²), and because its terminal log-mean temperature difference overstates the mean driving force of a glide; it also applies a correction factor for its shells and sizes utility exchangers on the utility side duty. Because biosteam sizes on the terminal temperature differences only, an exchanger whose profiles cross inside it keeps a finite 'area_biosteam', while its 'area', and so the network’s, is infinite, with a RuntimeWarning naming it. This happens when biosteam heats cooling water to its limit, 324.82 K, against a stream that condenses a little below that, as in a vacuum condenser: the utility’s line crosses the condensing stretch (see the Notes of network_report()).

  • Units. The network has 7 exchangers against an estimate of 6 because it has a loop. Above the pinch, the bottoms cooler D1_H1 heats both the reboiler (HX_0_2_hs) and the flash’s feed heater (HX_1_2_hs), and each of these also has its own steam heater (Util_0_hs and Util_1_hs), so the four exchangers close a loop: reboiler, D1_H1, feed heater, steam and back to the reboiler. Euler’s S - 1 counts the steam as one stream however many streams it heats, and assumes the matches of a region form no loop. The estimate can be wrong both ways: a balanced subset of streams needs fewer units, and each loop needs one more, whether a repeated match between two streams (two branches of a split stream meeting the same stream make one too) or, as here, two streams linked both through process matches and through a shared utility.

  • Cost. The installed cost and the TAC of the network (2.184e+06 USD and 15072295 USD/yr) are biosteam’s, on biosteam’s areas, and here they are below the targets’ (2.282e+06 USD and 15088200 USD/yr), which divide a larger area into equal shells. Neither is a bound on the other.

The report’s 'targets' is summary() with the same options, and the network and its targets are priced over the same operating hours (the system’s, if it has them, else 8000 h/yr); exchanger_areas gives both areas of each exchanger, which is where a gap between the network and its target can be traced. The synthesis itself does not optimize area, capital or total cost: it reaches MER first and then keeps the number of exchangers low. Supertargeting is how to choose the T_min_app it synthesizes at.

Where to next#