From 5f6a082eb0381a592235a0558547ea50db97b031 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Fri, 16 Oct 2026 21:03:37 +0000 Subject: [PATCH 01/52] add per-stream approach temperature contributions to the numerics Each process stream can carry its own approach temperature contribution c_j [K]; a match between hot stream h and cold stream c must then keep c_h + c_c instead of T_min_app (Kemp 2007). A stream without a contribution contributes T_min_app / 2. Stream j is shifted down by s_j = T_min_app/2 + c_j if hot and T_min_app/2 - c_j if cold, so T*_h >= T*_c iff T_h - T_c >= c_h + c_c. The problem table, the planner and the splitting work unchanged on that one shifted scale, so the targets, the pinch rules and Theorem M carry over. Every real-temperature step takes its own pair's requirement s_h - s_c: the exact check, the repair, the HXprocess dT guard and the reported margin. Each stream's pinch temperature is pinch_T + s_j. Utilities are unaffected. Values are None or nan (unspecified), or finite and > 0, which keeps every pair requirement and the dT guard positive. This is the numerics work package (WP1 of the F1 spec). - _planner: _contribution_value, _approach_contributions, _approach_shifts (the default is still the literal np.where(is_hot, T_min_app, 0.)). _stream_curves takes a shift. plan_network(approach_contributions=...) adds plan.info 'shift' and 'min_approach_margin' only when contributions are given, and reports min_approach per pair. - hxn_synthesis: problem_table / _problem_table(approach_contributions=...) with grid['contributions']. - hxn_synthesis: _pinch_analysis and temperature_interval_pinch_analysis resolve contributions before any copy or flash, and give per-stream pinch temperatures. - hxn_synthesis: _exact_approach, _repair and _realize take `required` (a scalar, or the shift vector, length-checked); helpers _required_approach and _approach_margin. - hxn_synthesis: synthesize_network(approach_contributions=...) takes a sequence aligned with hus for now (mapping and callable keys follow in WP2). It always passes grid['shift'] to the exact steps. It adds info['min_approach_margin'] and info['approach_contributions'] (stream order). - Docstrings updated wherever they said every match keeps T_min_app. problem_table has a new doctest. Default-off results are bit for bit unchanged. With no contributions the shifts are the old expression, and every pair's requirement float(s_h - s_c) is T_min_app as the same double. Evidence: - the out-of-tree oracle (29 facility syntheses, every number as float.hex) gives sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf before and after (byte-identical JSON); - the four golden plan digests ffff603a8687fd88, 9a2b7cbfa36b0791, 07eeacf5af328127 and 57b9addff180e07e are unchanged; - the new tests compare None, [None]*N, [nan]*N and [T/2]*N with the default in-process with float.hex and tobytes. New tests/test_hxn_contributions.py (23 tests, 18 s serial): - default bit-for-bit identity of plans and problem tables; - uniform contributions against a doubled T_min_app (planner) and against uniform real-thermo runs, plus monotone bounds; - targets and pinch against an independent closed-form cascade with classic symmetric shifts (40 random problems, KEMP, a corpus case); - the 24 constant-CP NO_SPLIT cases (walked balances and per-pair end approaches); - five split cases at MER with splits; - the per-pair exact check, repair and realization; - value and sequence validation. Full suite: 981 passed (958 existing + 23 new) in 202 s with -n 4. The module doctests pass. ast.parse(feature_version=(3, 12)) is OK on both modules and the test file. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- hensmith/_planner.py | 132 +++++++-- hensmith/hxn_synthesis.py | 397 +++++++++++++++++++------- tests/test_hxn_contributions.py | 482 ++++++++++++++++++++++++++++++++ 3 files changed, 895 insertions(+), 116 deletions(-) create mode 100644 tests/test_hxn_contributions.py diff --git a/hensmith/_planner.py b/hensmith/_planner.py index a4374bb..8fd3382 100644 --- a/hensmith/_planner.py +++ b/hensmith/_planner.py @@ -34,7 +34,10 @@ the highest for a cold one. Interior knots within ``_KNOT_TOL`` K of the chord through their neighbours are dropped, so a constant-CP stream becomes a single segment and the fast paths apply. On the *shifted* scale ``T*``, -hot streams are moved down by ``T_min_app`` and cold streams are not moved. +stream j is moved down by its shift ``s_j``: ``T_min_app`` for hot and 0 +for cold streams, or ``T_min_app/2 + c_j`` (hot) and ``T_min_app/2 - c_j`` +(cold) with approach contributions ``c_j``, so a pair feasible on the +shifted scale keeps ``c_h + c_c``. Shifted temperatures closer than ``_LEVEL_EQ`` K are merged, exactly as the problem table merges its grid. @@ -54,10 +57,10 @@ ====== ====== ===== ======================= side stream role level ``y`` ====== ====== ===== ======================= -above hot must ``T - T_min_app`` -above cold flex ``T`` -below cold must ``-T`` -below hot flex ``-(T - T_min_app)`` +above hot must ``T - s_j`` +above cold flex ``T - s_j`` +below cold must ``-(T - s_j)`` +below hot flex ``-(T - s_j)`` ====== ====== ===== ======================= A *must* stream has to be served completely by process matches on its side: @@ -176,7 +179,8 @@ Guarantees and limits --------------------- - The utilities are never below the targets. Every planned exchanger keeps - ``T_min_app`` on the knot curves at its ends and at every knot inside it. + its pair's required approach (``T_min_app``, or ``c_h + c_c``) on the + knot curves at its ends and at every knot inside it. The heat balance closes on every stream, and ``'mer'`` is never claimed falsely. Results are deterministic. - Every pruning test is a necessary condition. A missed MER network can @@ -229,6 +233,7 @@ import math from bisect import bisect_left, bisect_right from collections import defaultdict +from numbers import Real import numpy as np @@ -1862,11 +1867,66 @@ def _simplify(T, H, hot): return np.array(Tk), np.array(Hk) -def _stream_curves(knots, is_hot, T_min_app): - """Validate, shift, snap and simplify every stream's knots; None for a - stream without duty.""" +def _contribution_value(x, label): + """ + A stream's approach temperature contribution [K]: None for None or nan + (unspecified), ``float(x)`` for a finite real number > 0 (not a bool); + a ValueError naming `label` otherwise. + """ + if x is None: return None + if isinstance(x, Real) and not isinstance(x, (bool, np.bool_)): + value = float(x) + if math.isnan(value): return None + if math.isfinite(value) and value > 0.: return value + raise ValueError(f'the approach contribution of {label} must be a ' + f'finite number > 0 K, or None, not {x!r}') + + +def _approach_contributions(N, T_min_app, contributions=None): + """ + The (N,) approach temperature contributions [K] of `N` streams: + `contributions` (one entry per stream, see `_contribution_value`) with + every unspecified one, or all of them if `contributions` is None, at + ``T_min_app / 2``. + """ + half = T_min_app / 2. + if contributions is None: return np.full(N, half) + if not (math.isfinite(T_min_app) and T_min_app > 0.): + raise ValueError('T_min_app must be finite and > 0 with approach ' + f'contributions, not {T_min_app!r}') + contributions = list(contributions) + if len(contributions) != N: + raise ValueError(f'{len(contributions)} approach contributions given ' + f'for {N} streams') + values = [_contribution_value(x, f'stream {j}') + for j, x in enumerate(contributions)] + return np.array([half if x is None else x for x in values], dtype=float) + + +def _approach_shifts(is_hot, T_min_app, contributions=None): + """ + Each stream's shift onto the shifted scale, ``T* = T - shift``: + ``T_min_app`` for hot and 0 for cold streams without `contributions`, + else ``T_min_app/2 + c`` for hot and ``T_min_app/2 - c`` for cold + streams (`_approach_contributions`), so that a hot and a cold stream + that touch on the shifted scale are ``c_h + c_c`` apart. Unspecified + contributions give the default shifts bit for bit (``x/2 + x/2 == x``, + ``x/2 - x/2 == 0.``). + """ + is_hot = np.asarray(is_hot, dtype=bool) + if contributions is None: + return np.where(is_hot, T_min_app, 0.) + c = _approach_contributions(is_hot.size, T_min_app, contributions) + half = T_min_app / 2. + return np.where(is_hot, half + c, half - c) + + +def _stream_curves(knots, is_hot, T_min_app, shift=None): + """Validate, shift (by `shift`, default `_approach_shifts`), snap and + simplify every stream's knots; None for a stream without duty.""" + if shift is None: shift = _approach_shifts(is_hot, T_min_app) raw = [] - for kn, hot in zip(knots, is_hot): + for j, (kn, hot) in enumerate(zip(knots, is_hot)): T, H = kn T = np.asarray(T, float).ravel() H = np.asarray(H, float).ravel() @@ -1885,7 +1945,7 @@ def _stream_curves(knots, is_hot, T_min_app): 'non-decreasing') T = np.maximum.accumulate(T) H = np.maximum.accumulate(H) - raw.append(_simplify(T - (T_min_app if hot else 0.), H, hot)) + raw.append(_simplify(T - shift[j], H, hot)) if not raw: return [] u = np.unique(np.concatenate([r[0] for r in raw])) @@ -2042,7 +2102,8 @@ class Plan: Targets of the planner's cascade. pinch_T : float Pinch on the shifted scale (hot streams shifted down by - ``T_min_app``). + ``T_min_app`` by default): stream j's real pinch is at ``pinch_T + + shift_j`` (``plan.info['shift']`` with approach contributions). cut : {'above', 'below'} Side of the pinch the point loads at `pinch_T` belong to. exchangers : list[Exchanger] @@ -2077,8 +2138,12 @@ class Plan: ``candidate`` and ``signature`` None), ``qmin_dropped`` (list of (side, hot, cold, Q)), ``dropped`` (matches removed by the safety net; always empty unless there is a bug), - ``min_approach`` (on the knot curves), ``work``, ``scale``, - ``tolQ``, ``cascade`` (the planner's problem table). + ``min_approach`` (the smallest real approach on the knot curves), + ``work``, ``scale``, ``tolQ``, ``cascade`` (the planner's problem + table); with `approach_contributions` also ``shift`` (each stream's + shift, ``T* = T - shift``) and ``min_approach_margin`` (the smallest + approach less its pair's required approach, on the shifted scale; + None without exchangers). """ __slots__ = ('status', 'Q_hot_target', 'Q_cold_target', 'pinch_T', 'cut', 'exchangers', 'stages', 'utility', 'Q_hot', 'Q_cold', @@ -2140,7 +2205,8 @@ def _walk(curves, recs, N): def plan_network(knots, is_hot, T_min_app, *, avoid_recycle=False, Qmin=0., - work_scale=1., stream_splitting=False, _split_exclude=None, + work_scale=1., stream_splitting=False, + approach_contributions=None, _split_exclude=None, _split_prefer=None): """ Plan a heat exchanger network at minimum energy requirement, unsplit @@ -2158,7 +2224,8 @@ def plan_network(knots, is_hot, T_min_app, *, avoid_recycle=False, Qmin=0., is_hot : sequence[bool] True for streams that are cooled. T_min_app : float - Minimum approach temperature. + Minimum approach temperature: the required approach of every pair + unless `approach_contributions` are given. avoid_recycle : bool, optional Never match the same (hot, cold) pair twice anywhere. Qmin : float, optional @@ -2183,6 +2250,14 @@ def plan_network(knots, is_hot, T_min_app, *, avoid_recycle=False, Qmin=0., each side's chosen candidate; `synthesize_network` realizes them. With `avoid_recycle`, a split that would repeat a stream pair is not used, and MER is then not guaranteed. Defaults to False. + approach_contributions : sequence[float or None], optional + Approach temperature contribution of each stream [K], in the order + of `knots`: None or nan (unspecified: ``T_min_app / 2``), or a + finite number > 0 (then `T_min_app` must be finite and > 0). A + match between hot stream h and cold stream c keeps ``c_h + c_c``: + stream j is planned on the shifted scale ``T - s_j``, with ``s_j = + T_min_app/2 + c_j`` for a hot and ``T_min_app/2 - c_j`` for a cold + stream. Defaults to None (every pair keeps `T_min_app`). _split_exclude : dict[str, set[tuple]], optional Private (the refine loop): per side, network signatures a split candidate may not have unless it is the side's last candidate. @@ -2262,7 +2337,8 @@ def plan_network(knots, is_hot, T_min_app, *, avoid_recycle=False, Qmin=0., dT = float(T_min_app) if not math.isfinite(dT): raise ValueError('T_min_app must be finite') - curves = _stream_curves(knots, is_hot, dT) + shift = _approach_shifts(is_hot, dT, approach_contributions) + curves = _stream_curves(knots, is_hot, dT, shift) act = [c for c in curves if c is not None] scale = float(sum(c.duty for c in act)) tolQ = _REL_Q * max(scale, 1.) @@ -2365,11 +2441,26 @@ def plan_network(knots, is_hot, T_min_app, *, avoid_recycle=False, Qmin=0., if g > 0.}) if stream_splitting: side_info[name]['split'] = p.split + if approach_contributions is None: + min_approach = (dT + min_dT) if recs else None + else: + # each exchanger's real approach: its shifted one plus its pair's + # required approach (the difference of the two streams' shifts) + if split_mode: + from ._splitting import _approach_violation_split as violation + else: + violation = _approach_violation + min_approach = min( + (violation(curves[e.hot], curves[e.cold], e) + + float(shift[e.hot] - shift[e.cold]) for e in recs), + default=None) plan.info = dict(sides=side_info, qmin_dropped=qmin_dropped, - dropped=dropped, - min_approach=(dT + min_dT) if recs else None, + dropped=dropped, min_approach=min_approach, work=sum(p.work for p in plans.values()), scale=scale, tolQ=tolQ, cascade=table) + if approach_contributions is not None: + plan.info.update(shift=shift, + min_approach_margin=min_dT if recs else None) return plan @@ -2380,7 +2471,8 @@ def _plan_numeric(streams, dTmin, knots=1, **kwargs): Plan a constant-CP problem given as dicts ``{name, kind ('hot' | 'cold'), T_in, T_out, CP}``; `knots` > 1 splits every stream into that many collinear pieces (discretisation tests). Keywords go to - :func:`plan_network`. + :func:`plan_network`; ``approach_contributions`` is in the order of + `streams`. Returns a dict in the certificate format of the scratch oracle: ``matches`` (side, hot, cold, Q, T_hot_in, T_hot_out, T_cold_in, diff --git a/hensmith/hxn_synthesis.py b/hensmith/hxn_synthesis.py index fb3907e..a8adebd 100644 --- a/hensmith/hxn_synthesis.py +++ b/hensmith/hxn_synthesis.py @@ -15,6 +15,7 @@ cycles (`StreamLifeCycle`) and pinch diagrams (`plot_pinch_diagram`). """ from collections import namedtuple +from collections.abc import Sequence import heapq import math import re @@ -23,7 +24,8 @@ from warnings import warn from ._curves import (StreamCurve, stream_curves, _end_state, _T_EQ, _T_SIDE, _copy, _point_load_inlet) -from ._planner import plan_network +from ._planner import (plan_network, _approach_shifts, + _approach_contributions, _contribution_value) from ._splitting import Split, _ISO_TOL, _same_network __all__ = ('StreamLifeCycle', 'ProblemTable', 'problem_table', @@ -472,7 +474,8 @@ def show(self): ProblemTable.__doc__ = """ 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). +by the minimum approach temperature, cold streams unshifted; with approach +contributions every stream by its own shift, see `problem_table`). Attributes ---------- @@ -508,8 +511,10 @@ def show(self): Minimum cold utility target [kJ/hr]. pinch_T : float 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`. + problem). Stream j's pinch temperature is ``pinch_T + s_j``, its shift: + ``pinch_T + T_min_app`` (hot) and ``pinch_T`` (cold) by default, + ``pinch_T + T_min_app/2 + c_j`` (hot) and ``pinch_T + T_min_app/2 - + c_j`` (cold) with approach contributions. See Also -------- @@ -518,7 +523,7 @@ def show(self): """ def problem_table(streams_inlet, streams_quenched, is_hot, T_min_app, - curves=None): + curves=None, approach_contributions=None): """ Energy-consistent problem table (temperature-interval heat cascade). @@ -531,11 +536,19 @@ def problem_table(streams_inlet, streams_quenched, is_hot, T_min_app, is_hot : Sequence[bool] True where the stream is cooled. T_min_app : float - Minimum approach temperature [K]. + Minimum approach temperature [K]: the required approach of every + pair of streams unless `approach_contributions` are given. 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. + approach_contributions : Sequence[float or None], optional + Approach temperature contribution of each stream [K], in the order + of `streams_inlet`: None or nan (unspecified: ``T_min_app / 2``), or + a finite number > 0 (`T_min_app` must then be finite and > 0 too). + A hot and a cold stream then need the sum of their contributions + between them (see Notes). Defaults to None (every pair needs + `T_min_app`). Returns ------- @@ -560,7 +573,8 @@ def problem_table(streams_inlet, streams_quenched, is_hot, T_min_app, 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 + by `T_min_app`; cold streams are not (see below for approach + contributions). 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 @@ -589,6 +603,14 @@ def problem_table(streams_inlet, streams_quenched, is_hot, T_min_app, first location the pinch. With the per-stream identity above, hot_util_load - cold_util_load equals the net heating demand. + With `approach_contributions` ``c_j`` (Kemp 2007), hot streams are + shifted down by ``T_min_app/2 + c_j`` and cold streams by + ``T_min_app/2 - c_j``, so that a hot and a cold stream at the same + shifted temperature are ``c_h + c_c`` apart: every match must keep the + sum of its two streams' contributions. An unspecified contribution is + ``T_min_app/2``; with none specified, the table is the default one, bit + for bit. + Examples -------- A threshold problem: 1000 kmol/hr of water cooled 400 -> 300 K supplies @@ -614,12 +636,27 @@ def problem_table(streams_inlet, streams_quenched, is_hot, T_min_app, 1445550.0 >>> table.pinch_T 395.0 + + If the cold stream contributes 10 K and the hot one the default 2.5 K, + the cold stream needs 12.5 K against the hot one, which a 400 K inlet + cannot give against a 390 K outlet, so hot utility appears. The hot + stream's pinch temperature is ``395 + 5 = 400`` K and the cold one's + ``395 - 7.5 = 387.5`` K: + + >>> table = problem_table([hot_in, cold_in], [hot_out, cold_out], + ... [True, False], 5., approach_contributions=[None, 10.]) + >>> table.Ts[[0, -1]] + array([397.5, 295. ]) + >>> round(table.hot_util_load, 3) + 171724.348 + >>> table.pinch_T + 395.0 """ return _problem_table(streams_inlet, streams_quenched, is_hot, T_min_app, - curves)[0] + curves, approach_contributions)[0] def _problem_table(streams_inlet, streams_quenched, is_hot, T_min_app, - curves=None): + curves=None, approach_contributions=None): """ Return the `ProblemTable` of `problem_table` together with the stream curves it was built from and its grid enthalpies, as ``(table, curves, @@ -629,8 +666,11 @@ def _problem_table(streams_inlet, streams_quenched, is_hot, T_min_app, * 'Ts': the table's shifted grid temperatures (descending, n); * 'shift': each stream's shift (`T_min_app` for hot streams, 0 for - cold ones; N), so stream j's real temperature at grid index k is - ``Ts[k] + shift[j]``; + cold ones by default; ``T_min_app/2 + c_j`` and ``T_min_app/2 - c_j`` + with `approach_contributions`; N), so stream j's real temperature at + grid index k is ``Ts[k] + shift[j]``; + * 'contributions': each stream's approach temperature contribution + [K], unspecified ones (all by default) at ``T_min_app / 2`` (N); * 'Hl', 'Hr': (N x n) each stream's enthalpy at every grid temperature, left (low-enthalpy) and right (high-enthalpy) limit; they differ only where the stream's curve has a flat (point load) at that grid @@ -647,7 +687,7 @@ def _problem_table(streams_inlet, streams_quenched, is_hot, T_min_app, N = len(streams_inlet) is_hot = np.asarray(is_hot, dtype=bool) sign = np.where(is_hot, 1., -1.) - shift = np.where(is_hot, T_min_app, 0.) + shift = _approach_shifts(is_hot, T_min_app, approach_contributions) if curves is None: curves = stream_curves(streams_inlet, streams_quenched, is_hot) elif len(curves) != N: @@ -702,7 +742,9 @@ def _problem_table(streams_inlet, streams_quenched, is_hot, T_min_app, cold_util_load = 0. table = ProblemTable(Ts, interval_H, point_H, residual, hot_util_load, cold_util_load, Ts[k_pinch]) - grid = dict(Ts=Ts, shift=shift, Hl=Hl, Hr=Hr, k_hi=k_hi, k_lo=k_lo) + grid = dict(Ts=Ts, shift=shift, Hl=Hl, Hr=Hr, k_hi=k_hi, k_lo=k_lo, + contributions=_approach_contributions( + N, T_min_app, approach_contributions)) return table, curves, grid def _pinch_cut(table): @@ -726,16 +768,54 @@ def _pinch_cut(table): arriving = table.residual - point_total return 'below' if arriving[k] <= table.residual[k] else 'above' +def _stream_contributions(hus, approach_contributions, units=None): + """ + Resolve `approach_contributions` to one approach temperature + contribution per heat utility of `hus`, in the given order (zero-duty + ones included): a list of float, or None where unspecified (nan becomes + None; not filled with ``T_min_app / 2``). Accepts None (all + unspecified) or a sequence aligned with `hus` (not a str or bytes); + `units` is reserved for keys that name units. + """ + N = len(hus) + x = approach_contributions + if x is None: return [None] * N + if not isinstance(x, (Sequence, np.ndarray)) or isinstance(x, (str, bytes)): + raise TypeError('approach_contributions must be a sequence with one ' + 'value per heat utility, not ' + f'{type(x).__name__!r}') + values = list(x) + if len(values) != N: + raise ValueError(f'{len(values)} approach contributions given for ' + f'{N} heat utilities') + return [_contribution_value(v, f'heat utility {k}') + for k, v in enumerate(values)] + +def _explicit_contributions(hus, approach_contributions, T_min_app, + units=None): + """ + `_stream_contributions` of `hus`, or None if nothing is specified + (`approach_contributions` None, or every value None or nan: the default + path). With a value, `T_min_app` must be finite and > 0. + """ + if approach_contributions is None: return None + values = _stream_contributions(hus, approach_contributions, units) + if all(v is None for v in values): return None + _approach_contributions(len(values), T_min_app, values) # checks T_min_app + return values + def _pinch_analysis(hus, T_min_app=10, force_ideal_thermo=False, - sort_hus_by_T=False): + sort_hus_by_T=False, approach_contributions=None): """ The first step of `synthesize_network`: prepare the process streams behind `hus` and run the problem table on them. Returns the 12 values of `temperature_interval_pinch_analysis` (which wraps this function) and then the table's ``table, curves, grid`` (see `_problem_table`), on which the network is planned, so that the curves are built only - once. + once. `approach_contributions` (see `_stream_contributions`) is resolved + first, before any stream is copied or flashed. """ + values = _explicit_contributions(hus, approach_contributions, T_min_app) hx_utils = hus hus_heating = [hu for hu in hx_utils if hu.duty > 0] hus_cooling = [hu for hu in hx_utils if hu.duty < 0] @@ -765,38 +845,47 @@ def _pinch_analysis(hus, T_min_app=10, force_ideal_thermo=False, T_out_arr = np.array([i.T for i in streams_quenched]) is_hot = np.zeros(len(hxs), dtype=bool) is_hot[hot_indices] = True + if values is None: + contributions = None + else: # in stream order + value_of = dict(zip(map(id, hus), values)) + contributions = [value_of[id(hu)] for hu in hx_utils_rearranged] table, curves, grid = _problem_table(streams_inlet, streams_quenched, - is_hot, T_min_app) + is_hot, T_min_app, None, + contributions) hot_util_load = table.hot_util_load cold_util_load = table.cold_util_load - pinch_cold_stream_T = table.pinch_T - pinch_hot_stream_T = pinch_cold_stream_T + T_min_app + pinch_T = table.pinch_T # shifted scale + shift = grid['shift'] # Per-stream pinch temperature, for information only (returned as # `HeatExchangerNetwork.pinch_Ts`; `load_duties` splits a stream there): # the network is planned on the curves (see `synthesize_network`), not - # on these temperatures. A stream already entirely on one side of the - # process pinch (T_in past pinch_cold_stream_T for a cold stream, or - # past pinch_hot_stream_T for a hot stream) is not split; its pinch_T is - # its own T_in. So is a non-monotone stream (T_out on the wrong side of - # T_in for its duty, e.g. a cold stream whose VLE outlet ends up cooler - # than it entered: a point load at T_out), whose whole duty - # `load_duties` then puts on a single side (hot side for a cold stream, - # cold side for a hot one). + # on these temperatures. Stream i's pinch is the shifted pinch plus its + # shift, T_p (pinch_T for a cold and pinch_T + T_min_app for a hot + # stream by default). A stream already entirely on one side of the + # process pinch (T_in past T_p) is not split; its pinch_T is its own + # T_in. So is a non-monotone stream (T_out on the wrong side of T_in for + # its duty, e.g. a cold stream whose VLE outlet ends up cooler than it + # entered: a point load at T_out), whose whole duty `load_duties` then + # puts on a single side (hot side for a cold stream, cold side for a hot + # one). pinch_T_arr = [] for i in cold_indices: - if T_in_arr[i] > pinch_cold_stream_T or T_in_arr[i] > T_out_arr[i]: + T_p = pinch_T + shift[i] + if T_in_arr[i] > T_p or T_in_arr[i] > T_out_arr[i]: pinch_T_arr.append(T_in_arr[i]) - elif T_out_arr[i] < pinch_cold_stream_T: + elif T_out_arr[i] < T_p: pinch_T_arr.append(T_out_arr[i]) else: - pinch_T_arr.append(pinch_cold_stream_T) + pinch_T_arr.append(T_p) for i in hot_indices: - if T_in_arr[i] < pinch_hot_stream_T or T_in_arr[i] < T_out_arr[i]: + T_p = pinch_T + shift[i] + if T_in_arr[i] < T_p or T_in_arr[i] < T_out_arr[i]: pinch_T_arr.append(T_in_arr[i]) - elif T_out_arr[i] > pinch_hot_stream_T: + elif T_out_arr[i] > T_p: pinch_T_arr.append(T_out_arr[i]) else: - pinch_T_arr.append(pinch_hot_stream_T) + pinch_T_arr.append(T_p) pinch_T_arr = np.array(pinch_T_arr) return pinch_T_arr, hot_util_load, cold_util_load, T_in_arr, T_out_arr,\ hxs, hot_indices, cold_indices, indices, streams_inlet, hx_utils_rearranged, \ @@ -805,7 +894,8 @@ def _pinch_analysis(hus, T_min_app=10, force_ideal_thermo=False, def temperature_interval_pinch_analysis(hus, T_min_app=10, force_ideal_thermo=False, - sort_hus_by_T=False): + sort_hus_by_T=False, + approach_contributions=None): """ Prepare the process streams behind `hus` and run the problem table on them, as `synthesize_network` does first; a standalone pinch analysis @@ -815,7 +905,8 @@ def temperature_interval_pinch_analysis(hus, Heating utilities (``hu.duty > 0``, cold streams) come first, then cooling utilities; zero-duty utilities are dropped. Each stream is a copy of its exchanger's inlet (renamed ``s___Util_``) and - of its outlet re-flashed at its own enthalpy. + of its outlet re-flashed at its own enthalpy. `approach_contributions` + takes the forms of `synthesize_network`'s parameter. Returns ------- @@ -836,7 +927,7 @@ def temperature_interval_pinch_analysis(hus, """ return _pinch_analysis(hus, T_min_app, force_ideal_thermo, - sort_hus_by_T)[:12] + sort_hus_by_T, approach_contributions)[:12] def pinch_state(stream_in, stream_out, T_pinch, side=None, curve=None): """ @@ -935,8 +1026,9 @@ def load_duties(streams, streams_quenched, pinch_T_arr, T_out_arr, indices, # %% Network synthesis #: Exact-state approach acceptance [K]. Synthesized process exchangers get -#: ``HXprocess(dT=T_min_app - _APPROACH_TOL)`` as a guard only: the planner -#: and the exactness check enforce `T_min_app` on the exact states. +#: ``HXprocess(dT=required - _APPROACH_TOL)``, with `required` their pair's +#: required approach, as a guard only: the planner and the exactness check +#: enforce the required approach on the exact states. _APPROACH_TOL = 1e-6 #: A simulated process-exchanger duty that differs from the planned one by #: more than this times the two streams' total duties is a deviation. @@ -1042,7 +1134,8 @@ def _exchanger_approach(curves, knots, h, c, H_hot_in, H_cold_in, Q, """ Exact-state check of one counter-current exchanger of duty `Q` in which stream `h` enters hot at `H_hot_in` and stream `c` enters cold at - `H_cold_in` (enthalpies relative to each stream's H_lo). + `H_cold_in` (enthalpies relative to each stream's H_lo); `T_min_app` is + the pair's required approach. With stream splitting, `fh` and `fc` are the flow fractions of the hot and the cold branch in the exchanger: a branch at fraction f has its @@ -1067,9 +1160,10 @@ def _exchanger_approach(curves, knots, h, c, H_hot_in, H_cold_in, Q, Returns the smallest approach found [K] (exact where evaluated) and the exact states ``(T_hot, H_hot, T_cold, H_cold)`` wherever the exact - approach is below ``T_min_app - _APPROACH_TOL``. With ``T_min_app = - inf`` every interval is checked, so the approach returned is the exact - minimum over the exchanger and the states are all those evaluated. + approach is below the pair's required approach less the tolerance, + ``T_min_app - _APPROACH_TOL``. With ``T_min_app = inf`` every interval + is checked, so the approach returned is the exact minimum over the + exchanger and the states are all those evaluated. This is the only exact internal-approach check of the synthesis (the exchangers themselves, `HXprocess`, check their two terminals only, which misses an internal pinch at a phase change). @@ -1111,14 +1205,32 @@ def exact(q): worst = min(worst, _interval_min(exact, a, fa, b, fb)) return worst, points -def _exact_approach(plan, duties, ends, curves, knots, T_min_app): +def _required_approach(required, h, c): + """The required approach [K] of hot stream `h` against cold stream `c`: + `required` itself if it is a scalar, else ``required[h] - required[c]`` + (`required` the streams' shifts, ``grid['shift']``).""" + if np.ndim(required) == 0: return required + return float(required[h] - required[c]) + +def _check_required(required, knots): + """Raise a ValueError unless `required` is a scalar or has one shift per + stream of `knots`.""" + if np.ndim(required) and len(required) != len(knots): + raise ValueError(f'{len(required)} shifts given for {len(knots)} ' + 'streams') + +def _exact_approach(plan, duties, ends, curves, knots, required): """ `_exchanger_approach` of every exchanger in `duties` (duty by index into ``plan.exchangers``) at the enthalpies of the walk `ends` (see `_walk`), - with the flow fractions of its branches (1 on a trunk). + with the flow fractions of its branches (1 on a trunk), against its + pair's required approach: `required` is the scalar pair requirement, or + the per-stream shift vector ``grid['shift']`` (never + ``grid['contributions']``; see `_required_approach`). Returns the smallest approach [K], the violating exact states by stream, ``{stream: [(T_exact, H - H_lo), ...]}``, and the violating exchangers. """ + _check_required(required, knots) worst = np.inf violations = {} bad = [] @@ -1126,8 +1238,8 @@ def _exact_approach(plan, duties, ends, curves, knots, T_min_app): e = plan.exchangers[n] h, c = e.hot, e.cold approach, points = _exchanger_approach( - curves, knots, h, c, ends[n, h][0], ends[n, c][0], Q, T_min_app, - e.hot_frac, e.cold_frac + curves, knots, h, c, ends[n, h][0], ends[n, c][0], Q, + _required_approach(required, h, c), e.hot_frac, e.cold_frac ) worst = min(worst, approach) if points: bad.append(n) @@ -1136,12 +1248,31 @@ def _exact_approach(plan, duties, ends, curves, knots, T_min_app): violations.setdefault(c, []).append((T_cold, H_cold)) return worst, violations, bad +def _approach_margin(plan, duties, ends, curves, knots, required): + """ + The smallest approach less its pair's required approach [K] over the + exchangers in `duties`, as `_exact_approach` evaluates it (`required` + as there). + """ + margin = np.inf + for n, Q in duties.items(): + e = plan.exchangers[n] + h, c = e.hot, e.cold + req = _required_approach(required, h, c) + approach = _exchanger_approach( + curves, knots, h, c, ends[n, h][0], ends[n, c][0], Q, req, + e.hot_frac, e.cold_frac + )[0] + margin = min(margin, approach - req) + return margin + def _shrink(curves, knots, h, c, H_hot_in, H_cold_in, Q, T_min_app, fh=1., fc=1.): """ Largest duty ``Q' <= Q`` (to 1e-9 of Q) at which the exchanger of `_exchanger_approach` (branches at flow fractions `fh` and `fc`) keeps - ``T_min_app - _APPROACH_TOL`` on the exact states. With both inlets + the pair's required approach ``T_min_app`` less `_APPROACH_TOL` on the + exact states. With both inlets fixed, a smaller duty lowers the cold stream's enthalpy (so its temperature) at every position and shortens the exchanger, so the approach can only grow: the feasible duties form an interval [0, Q'] @@ -1172,10 +1303,11 @@ def ok(x): else: hi = mid return lo -def _repair(plan, duties, knots, is_hot, curves, T_min_app): +def _repair(plan, duties, knots, is_hot, curves, required): """ - Shrink every exchanger that falls short of ``T_min_app - _APPROACH_TOL`` - on the exact states to its largest feasible duty (`_shrink`); the rest of + Shrink every exchanger that falls short of its pair's required approach + less `_APPROACH_TOL` on the exact states (`required` as in + `_exact_approach`) to its largest feasible duty (`_shrink`); the rest of its duty goes to the utilities. Shrinking a match moves the later stages of both its streams toward their inlets, which never reduces another exchanger's approach (the curves are monotone), so one pass suffices; @@ -1189,13 +1321,14 @@ def _repair(plan, duties, knots, is_hot, curves, T_min_app): changes = [] for _ in range(len(duties) + 1): ends = _walk(plan, duties, knots, is_hot)[0] - bad = _exact_approach(plan, duties, ends, curves, knots, T_min_app)[2] + bad = _exact_approach(plan, duties, ends, curves, knots, required)[2] if not bad: break for n in bad: e = plan.exchangers[n] ends = _walk(plan, duties, knots, is_hot)[0] Q = _shrink(curves, knots, e.hot, e.cold, ends[n, e.hot][0], - ends[n, e.cold][0], duties[n], T_min_app, + ends[n, e.cold][0], duties[n], + _required_approach(required, e.hot, e.cold), e.hot_frac, e.cold_frac) changes.append((n, duties[n], Q)) duties[n] = Q @@ -1272,7 +1405,7 @@ def _first_inlet(stream, point_load, T_point, hot): and the partner's outlet is capped at the inlet temperature -/+ `dT`), so a point-load stream's real inlet, on the wrong side of `T_point` by definition, would make it refuse or cut short a match that the plan - keeps `T_min_app` for at `T_point`. E.g. a reboiler fed as a liquid + keeps the required approach for at `T_point`. E.g. a reboiler fed as a liquid above its boiling point enters as the vapor-liquid mixture it flashes to, and a vapor fed below its dew point (e.g. the ideal-thermo copy of a saturated vapor whose ideal dew point is higher) as the mixture it @@ -1500,10 +1633,12 @@ def _discard(units): for s in (*unit.ins, *unit.outs): bst.main_flowsheet.stream.discard(s) bst.main_flowsheet.unit.discard(unit) -def _realize(plan, duties, curves, knots, streams_inlet, is_hot, T_min_app): +def _realize(plan, duties, curves, knots, streams_inlet, is_hot, required): """ Build and run one plain `HXprocess` per exchanger in `duties` (duty by - index into ``plan.exchangers``), in plan order. Raise + index into ``plan.exchangers``), in plan order, each with ``dT`` its + pair's required approach less `_APPROACH_TOL` (`required` as in + `_exact_approach`). Raise `_RealizationError` (after discarding the units built so far) if one of them cannot be simulated. Returns ``(units, first, last)``: the units by exchanger index, each stream's first exchanger (None if it has none) @@ -1517,15 +1652,16 @@ def _realize(plan, duties, curves, knots, streams_inlet, is_hot, T_min_app): first exchanger of every branch of a split at a stream's inlet takes the real inlet (`_split_nodes`). """ + _check_required(required, knots) ends, last, pair_index = _walk(plan, duties, knots, is_hot) first = [next((n for n in plan.stages[j] if n in duties), None) for j in range(len(is_hot))] first_nodes = _split_nodes(plan, duties, ends)[0] if plan.splits else {} units = {} - dT = T_min_app - _APPROACH_TOL for n in sorted(duties): e = plan.exchangers[n] h, c = e.hot, e.cold + dT = _required_approach(required, h, c) - _APPROACH_TOL suffix = '' if pair_index[n] == 1 else f'_{pair_index[n]}' if e.side == 'above': ID = f'HX_{c}_{h}_hs{suffix}' @@ -1689,7 +1825,7 @@ def run(unit, k, live): def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, avoid_recycle=False, sort_hus_by_T=False, info=None, - stream_splitting=False): + stream_splitting=False, approach_contributions=None): """ Synthesize a heat exchanger network for the process streams behind a set of utility heat exchangers: pinch analysis (`problem_table`), then a @@ -1715,7 +1851,9 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, 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. + used to shift hot streams in the problem table. With + `approach_contributions`, the default approach: each stream without + a contribution contributes ``T_min_app / 2``. 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). @@ -1743,14 +1881,22 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, (``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; + Q_hot, Q_cold, pinch_T on the shifted scale, 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 its pair's required + approach, else from the knots; None without process exchangers), + 'min_approach_margin' (the smallest approach less its pair's + required approach over the process exchangers [K], as evaluated by + the exact check: at least -1e-6 up to round-off; None when + 'min_approach' is None), 'approach_contributions' (each stream's + approach temperature contribution as used [K], unspecified ones at + ``T_min_app / 2``, in stream order: that of the returned arrays, + not of `hus`), 'deviations' (exchangers whose simulated duty + differs from the plan), 'qmin_dropped' (matches dropped by + `Qmin`), 'repaired' (matches shrunk to keep their pair's required + approach 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 @@ -1782,6 +1928,18 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, and ``synthesis_info['splits']`` (`HeatExchangerNetwork`). With `avoid_recycle`, a split that would repeat a stream pair is not used, and MER is then not guaranteed. Defaults to False. + approach_contributions : Sequence[float or None], optional + Approach temperature contribution of each process stream [K]: a + match between hot stream h and cold stream c must keep ``c_h + + c_c`` instead of `T_min_app` (see Notes, "Approach + contributions"). One value per heat utility, aligned with `hus` as + given (before the heating utilities are placed first; zero-duty + utilities included): None or nan (unspecified, ``T_min_app / 2``), + or a finite number > 0 (`T_min_app` must then be finite and > 0 + too). Checked before any stream is copied or flashed; a wrong + length raises a ValueError. If every value is unspecified, the + result is the default one. Defaults to None (every stream + contributes ``T_min_app / 2``). Returns ------- @@ -1807,8 +1965,10 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, 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 + (`ProblemTable.pinch_T` plus the stream's shift: `T_min_app` for a + hot stream, 0 for a cold one by default, ``T_min_app/2 + c_j`` and + ``T_min_app/2 - c_j`` with approach contributions); 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. @@ -1845,10 +2005,11 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, 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 + the pinch outward, one match at a time. Every match keeps its pair's + required approach (`T_min_app` by default) 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, @@ -1874,10 +2035,11 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, 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` + duty reproduces the plan; its `dT` is its pair's required approach + (`T_min_app` by default) less 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 @@ -1902,16 +2064,16 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, *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. + wherever its planned approach is within that margin of its pair's + required approach. 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. *Stream splitting.* With `stream_splitting`, a side that no unsplit network serves at MER is planned with splits (see @@ -1937,8 +2099,9 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, dropped, as in ``info['dropped']``). *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 + Every process exchanger keeps its pair's required approach (`T_min_app` + by default) less 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 @@ -1962,9 +2125,9 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, about 1e-9 of the total duty). The remaining gap is the realization. With constant heat capacities the knots are exact, and the realized network reaches the targets to round-off. With real thermodynamics - the knots are chords, so a split plan can fall short of `T_min_app` on - the exact states by up to the chords' tolerance; the refine rounds - close that as they do for unsplit plans, with up to two more rounds + the knots are chords, so a split plan can fall short of the required + approach on the exact states by up to the chords' tolerance; the refine + rounds close that as they do for unsplit plans, with up to two more rounds that exclude the violating network (see "Stream splitting" above), and only an exchanger still short after them is shrunk (reported in 'repaired', with status 'best_effort'). A rigorous mixer's outlet @@ -1976,6 +2139,24 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, minimized only among the candidate split plans a side generates, and splitters and mixers are not costed. + *Approach contributions.* With `approach_contributions`, each stream j + contributes ``c_j`` (``T_min_app / 2`` unless given), and a match + between hot stream h and cold stream c must keep ``c_h + c_c`` instead + of `T_min_app` [Kemp07]_: e.g. a small contribution for a condensing or + boiling stream (high film coefficients) and a large one for a gas. + Stream j is shifted down by ``s_j = T_min_app/2 + c_j`` if hot and + ``T_min_app/2 - c_j`` if cold, so that ``T*_h >= T*_c`` if and only if + ``T_h - T_c >= c_h + c_c``: the problem table, the targets and the + planner work on one shared shifted scale, and every guarantee above + carries over. A pair's required approach, ``s_h - s_c``, is enforced on + the exact states by the exactness check and the repair, is each + exchanger's `dT` guard (less 1e-6 K), and ``info['min_approach_margin']`` + reports the smallest margin over it. Stream j's pinch temperature is + the shifted pinch plus ``s_j``. Only the process matches follow the + contributions: each stream's `HXutility` gets its agent from BioSTEAM's + own rule. With every contribution unspecified, the shifts are the + default ones bit for bit (``x/2 + x/2 == x``), and so is the result. + 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 @@ -2050,10 +2231,18 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, raise ValueError('stream_splitting=True needs an info dict: the ' 'splitters and mixers are returned in ' 'info["splits"]') + # resolved before any stream is copied or flashed (None: the default) + values = _explicit_contributions(hus, approach_contributions, T_min_app) pinch_T_arr, hot_util_load, cold_util_load, T_in_arr, T_out_arr, \ hxs, hot_indices, cold_indices, indices, streams_inlet, \ hx_utils_rearranged, streams_quenched, table, curves, grid = \ - _pinch_analysis(hus, T_min_app, force_ideal_thermo, sort_hus_by_T) + _pinch_analysis(hus, T_min_app, force_ideal_thermo, sort_hus_by_T, + values) + # every pair's required approach is shift[h] - shift[c] (T_min_app by + # default, to the bit); the planner gets the contributions only if any + # is given, so the default call is unchanged + shift = grid['shift'] + contributions = None if values is None else grid['contributions'] N = len(hxs) is_hot = [False] * N for i in hot_indices: is_hot[i] = True @@ -2083,12 +2272,14 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, **(dict(stream_splitting=True, _split_exclude=exclude, _split_prefer=prefer) - if stream_splitting else {})) + if stream_splitting else {}), + **({} if contributions is None else + dict(approach_contributions=contributions))) if not refine_round: plan_targets = plan.info['cascade'] duties = {n: e.Q for n, e in enumerate(plan.exchangers)} ends = _walk(plan, duties, knots, is_hot)[0] min_approach, violations, bad = _exact_approach( - plan, duties, ends, curves, knots, T_min_app + plan, duties, ends, curves, knots, shift ) if stream_splitting and plan.status == 'mer' and ( best is None or len(bad) < best[0]): @@ -2125,7 +2316,7 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, repaired = [] if bad: duties, changes = _repair(plan, duties, knots, is_hot, curves, - T_min_app) + shift) for n, Q_before, Q_after in changes: e = plan.exchangers[n] repaired.append(dict(side=e.side, hot=e.hot, cold=e.cold, @@ -2133,7 +2324,19 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, if Q_after < Qmin: del duties[n] ends = _walk(plan, duties, knots, is_hot)[0] min_approach = _exact_approach(plan, duties, ends, curves, knots, - T_min_app)[0] + shift)[0] + # the smallest approach less its pair's required approach: by default + # every pair's is T_min_app (rounding is monotone, so this is the + # smallest per-exchanger margin); else measured per pair, on fresh walk + # ends (`ends` is stale after a best plan was restored) + if not duties: + min_approach_margin = None + elif contributions is None: + min_approach_margin = min_approach - T_min_app + else: + min_approach_margin = _approach_margin( + plan, duties, _walk(plan, duties, knots, is_hot)[0], curves, + knots, shift) # Realize; a match that cannot be simulated is dropped (its duty goes to # the utilities: removing a match never reduces another's approach), and # a split whose splitters or mixer cannot be simulated becomes a trunk @@ -2143,7 +2346,7 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, while True: try: units, first, last = _realize(plan, duties, curves, knots, - streams_inlet, is_hot, T_min_app) + streams_inlet, is_hot, shift) splits, split_deviations = ( _realize_splits(plan, duties, units, curves, knots, streams_inlet, is_hot) @@ -2229,6 +2432,8 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, cut=plan_targets['cut']), refine_rounds=refine_round, min_approach=min_approach if duties else None, + min_approach_margin=min_approach_margin if duties else None, + approach_contributions=grid['contributions'].copy(), deviations=deviations, qmin_dropped=plan.info['qmin_dropped'], dropped=dropped, repaired=repaired, diff --git a/tests/test_hxn_contributions.py b/tests/test_hxn_contributions.py new file mode 100644 index 0000000..28d74a3 --- /dev/null +++ b/tests/test_hxn_contributions.py @@ -0,0 +1,482 @@ +# -*- coding: utf-8 -*- +# hensmith: Heat Exchanger Network Synthesis, Modeling, Integration, +# Thermodynamics, and Heuristics +# Copyright (C) 2026-, Sarang Bhagwat +# +# This module is under the UIUC open-source license. See +# github.com/BioSTEAMDevelopmentGroup/hensmith/blob/master/LICENSE.txt +# for license details. +""" +Tests of per-stream approach temperature contributions +(``approach_contributions``): a match between hot stream h and cold stream c +must keep ``c_h + c_c`` instead of ``T_min_app``. + +A. Default unchanged: unspecified contributions (None, nan, or every stream + at ``T_min_app / 2``) give the default plans and problem tables bit for + bit, compared in-process with ``float.hex`` and ``tobytes``. +B. Uniform contributions: every stream at ``x`` plans the network of + ``T_min_app = 2x`` on a shifted scale offset by a constant, and on + real-thermo cases all hot streams at ``a`` and all cold ones at ``b`` + give the default targets at ``T_min_app = a + b`` (references that do + not use the contribution code); mixed contributions give targets between + those of the smallest and of the largest pair requirement. +C. Non-uniform contributions against INDEPENDENT references: a closed-form + constant-CP cascade with the classic symmetric shifts (hot streams down + by ``c_j``, cold streams up by ``c_j``; hensmith's shifted scale is that + scale less ``T_min_app / 2``), and a walk of every planned match on + recomputed constant-CP temperatures that checks each pair's requirement + at both ends (the minimum for linear profiles). The exactness check, the + repair and the realization enforce each pair's own requirement. +D. Validation of the value domain (None or nan, else a finite number > 0) + and of the sequence form. +""" +import math +import random +import numpy as np +import pytest +import biosteam as bst +from numpy.testing import assert_allclose +from hensmith import hxn_synthesis +from hensmith import _planner as P +from hensmith.hxn_synthesis import problem_table, synthesize_network +from test_hxn import cp_units, KEMP, R002 as R002_UNITS, planned_split +from test_hxn_regression import internal_approach, CASES +from test_hxn_mer import _build, _case, _kelvin, APPROACH_TOL +from test_hxn_planner import (streams_from, check_network, duty_scale, + random_problem, R002, LINNHOFF4, HARD, + REPEATED_PAIR, SPLIT as PLANNER_SPLIT) +from hxn_mer_cases import NO_SPLIT, Q_UNITS, T_UNITS + +NAN = float('nan') + +# --------------------------------------------------------------------------- +# Independent helpers (no hensmith code) +# --------------------------------------------------------------------------- + +#: contribution factors of the corpus streams, times dTmin / 2 +FACTORS = (0.4, 1.0, 1.7) + +def corpus_contributions(case): + """Contributions of a corpus case's streams [source degrees].""" + return [FACTORS[k % 3] * case['dTmin'] / 2 + for k in range(len(case['streams']))] + +def corpus_streams(case): + """A constant-CP corpus case as `_plan_numeric` streams (source units).""" + return [dict(name=str(n), kind=k, T_in=float(a), T_out=float(b), + CP=float(cp)) for n, k, a, b, cp in case['streams']] + +def classic_cascade(streams, c): + """ + Closed-form constant-CP problem table with the classic symmetric shifts + (hot streams at ``T - c_j``, cold streams at ``T + c_j``; `c` aligned + with `streams`). Returns ``(Qh, Qc, zeros)``: the targets and the + interval boundaries where the cascaded heat flow (``Qh`` at the top) is + at most 1e-9 of the total duty (the top when ``Qh = 0``, the bottom + when ``Qc = 0``). hensmith's shifted pinch is ``z - T_min_app / 2`` for + some ``z`` in `zeros`. + """ + spans = [(s['T_out'] - cj, s['T_in'] - cj, s['CP']) if s['kind'] == 'hot' + else (s['T_in'] + cj, s['T_out'] + cj, -s['CP']) + for s, cj in zip(streams, c)] + Ts = sorted({T for lo, hi, _ in spans for T in (lo, hi)}, reverse=True) + heat = [0.] + for upper, lower in zip(Ts, Ts[1:]): + heat.append(heat[-1] + sum(CP * (upper - lower) for lo, hi, CP in spans + if lo <= lower and hi >= upper)) + Qh = max(0., -min(heat)) + tol = 1e-9 * duty_scale(streams) + zeros = [T for T, h in zip(Ts, heat) if h + Qh <= tol] + return Qh, heat[-1] + Qh, zeros + +def pair_ends_ok(streams, cmap, net): + """Every match keeps ``c_h + c_c`` at both ends (constant CP: the ends + are the minimum); `cmap` maps stream names to contributions.""" + for m in net['matches']: + approach = min(m['T_hot_in'] - m['T_cold_out'], + m['T_hot_out'] - m['T_cold_in']) + assert approach >= cmap[m['hot']] + cmap[m['cold']] - 1e-6, m + return True + +def assert_targets(net, Qh, Qc, streams): + tol = 1e-9 * duty_scale(streams) + assert abs(net['targets']['Qh'] - Qh) <= tol + assert abs(net['targets']['Qc'] - Qc) <= tol + +def plan_record(plan): + """Every number of a plan that the default path must keep, as hex.""" + hx = lambda x: float(x).hex() + record = [(e.side, e.hot, e.cold, hx(e.Q), hx(e.H_hot_in), + hx(e.H_cold_in), hx(e.hot_frac), hx(e.cold_frac)) + for e in plan.exchangers] + record.append((hx(plan.pinch_T), hx(plan.Q_hot), hx(plan.Q_cold), + [hx(u) for u in plan.utility], + hx(plan.info['min_approach']))) + return record + +def table_record(table): + """Every field of a `ProblemTable` as bytes or hex.""" + return [x.tobytes() + str(x.shape).encode() if isinstance(x, np.ndarray) + else float(x).hex() for x in table] + +def water_example(): + """The two-stream water example of `problem_table`'s docstring.""" + bst.settings.set_thermo(['Water'], cache=True) + bst.main_flowsheet.set_flowsheet('contributions_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) + return [hot_in, cold_in], [hot_out, cold_out], [True, False], 5. + +def unit_streams(units): + """Inlets, re-flashed outlets and is_hot of the units' process streams + (in the given order), as the facility prepares them.""" + hus = [hx.heat_utilities[0] for hx in units] + inlets = [hu.unit.ins[0].copy() for hu in hus] + outlets = [hu.unit.outs[0].copy() for hu in hus] + for s in outlets: s.vle(H=s.H, P=s.P) + return inlets, outlets, [hu.duty < 0 for hu in hus] + +def by_duty(units): + return sorted((hx.heat_utilities[0] for hx in units), + key=lambda hu: hu.duty) + +# --------------------------------------------------------------------------- +# A. Default unchanged +# --------------------------------------------------------------------------- + +PLANNER_DEFAULT = { + 'R002': (10., R002, {}), + 'LINNHOFF4': (10., LINNHOFF4, {}), + 's13_111_3x9': (*HARD['s13_111_3x9'], {}), + 'smith2005_exr18_4': (*PLANNER_SPLIT['smith2005_exr18_4'], + dict(stream_splitting=True)), +} + +@pytest.mark.parametrize('name', list(PLANNER_DEFAULT)) +def test_default_plans_bit_for_bit(name): + # unspecified contributions plan the default network to the bit + dT, rows, kw = PLANNER_DEFAULT[name] + streams = streams_from(rows) + N = len(streams) + default = P._plan_numeric(streams, dT, **kw)['plan'] + reference = plan_record(default) + assert 'shift' not in default.info + assert 'min_approach_margin' not in default.info + margins = set() + for contributions in ([None] * N, [NAN] * N, [dT / 2] * N): + plan = P._plan_numeric(streams, dT, approach_contributions=contributions, + **kw)['plan'] + assert plan_record(plan) == reference + assert plan.status == default.status + assert plan.info['shift'].tobytes() == np.where( + [s['kind'] == 'hot' for s in streams], dT, 0.).tobytes() + margins.add(float(plan.info['min_approach_margin']).hex()) + assert abs(plan.info['min_approach_margin'] + - (default.info['min_approach'] - dT)) <= 1e-12 + assert len(margins) == 1 + +@pytest.mark.parametrize('name', ['water', 'case_05_boiling_cold']) +def test_default_problem_table_bit_for_bit(name): + if name == 'water': + inlets, outlets, is_hot, T = water_example() + else: + units, T = CASES[name][0]() + inlets, outlets, is_hot = unit_streams(units) + N = len(inlets) + table, curves, grid = hxn_synthesis._problem_table(inlets, outlets, is_hot, + T) + reference = table_record(table) + assert grid['shift'].tobytes() == np.where(is_hot, T, 0.).tobytes() + assert (grid['contributions'] == T / 2).all() + assert table_record(problem_table(inlets, outlets, is_hot, T)) == reference + for contributions in ([None] * N, [NAN] * N, [T / 2] * N): + other, _, other_grid = hxn_synthesis._problem_table( + inlets, outlets, is_hot, T, curves, contributions) + assert table_record(other) == reference + for key in ('Ts', 'shift', 'Hl', 'Hr', 'k_hi', 'k_lo', + 'contributions'): + assert other_grid[key].tobytes() == grid[key].tobytes(), key + assert table_record(problem_table( + inlets, outlets, is_hot, T, curves, + approach_contributions=contributions)) == reference + +# --------------------------------------------------------------------------- +# B. Uniform contributions +# --------------------------------------------------------------------------- + +UNIFORM = { + 'LINNHOFF4': (10., LINNHOFF4, {}), + 'R002': (10., R002, {}), + '4sp2_dt20F': (*REPEATED_PAIR['4sp2_dt20F'], {}), + 'smith2005_exr18_4': (*PLANNER_SPLIT['smith2005_exr18_4'], + dict(stream_splitting=True)), +} + +@pytest.mark.parametrize('name', list(UNIFORM)) +def test_uniform_contributions_equal_doubled_T_min_app(name): + # every stream at x (T_min_app = 4): the network of T_min_app = 2x, on a + # shifted scale x - 2 K above it + dT, rows, kw = UNIFORM[name] + streams = streams_from(rows) + N = len(streams) + x = dT / 2. + scale = duty_scale(streams) + ref = P._plan_numeric(streams, 2. * x, **kw) + net = P._plan_numeric(streams, 4., approach_contributions=[x] * N, **kw) + assert net['status'] == ref['status'] + for key in ('Qh', 'Qc'): + assert abs(net['targets'][key] - ref['targets'][key]) <= 1e-9 * scale + plan, plan_ref = net['plan'], ref['plan'] + assert ([(e.side, e.hot, e.cold) for e in plan.exchangers] + == [(e.side, e.hot, e.cold) for e in plan_ref.exchangers]) + assert_allclose([e.Q for e in plan.exchangers], + [e.Q for e in plan_ref.exchangers], rtol=0, + atol=1e-9 * scale) + assert abs(plan.pinch_T - (plan_ref.pinch_T + x - 2.)) <= 1e-9 + assert abs(plan.info['min_approach'] + - plan_ref.info['min_approach']) <= 1e-9 + assert abs(plan.info['min_approach_margin'] + - (plan_ref.info['min_approach'] - 2. * x)) <= 1e-9 + +@pytest.mark.parametrize('name', ['case_05_boiling_cold', + 'case_08_two_condensers']) +def test_real_thermo_targets_against_uniform_runs(name): + units, T = CASES[name][0]() + hus = by_duty(units) + hot = [hu.duty < 0 for hu in hus] + total = sum(abs(hu.duty) for hu in hus) + + def table(T_min_app, contributions=None): + r = hxn_synthesis._pinch_analysis(hus, T_min_app, False, False, + contributions) + return r[12], r[14], r[10] # table, grid, heat utilities by stream + + # all hot streams at a, all cold ones at b: the default at a + b, on a + # grid offset by b - T/2 + for a, b in ((1., 4.), (3., 0.5)): + t, grid, streams = table(T, [a if h else b for h in hot]) + ref = table(a + b)[0] + assert_allclose([t.hot_util_load, t.cold_util_load], + [ref.hot_util_load, ref.cold_util_load], + rtol=1e-12, atol=1e-12 * total) + assert t.Ts.shape == ref.Ts.shape + assert_allclose(t.Ts, ref.Ts + (b - T / 2), rtol=0, atol=1e-9) + assert grid['contributions'].tolist() == [ + a if hu.duty < 0 else b for hu in streams] # stream order + # mixed contributions: between the smallest and the largest requirement + C = [(1., 4., 2., 6., 3., 0.5)[k % 6] for k in range(len(hus))] + t = table(T, C)[0] + c_hot = [c for c, h in zip(C, hot) if h] + c_cold = [c for c, h in zip(C, hot) if not h] + lo = table(min(c_hot) + min(c_cold))[0] + hi = table(max(c_hot) + max(c_cold))[0] + tol = 1e-9 * total + for load in ('hot_util_load', 'cold_util_load'): + assert (getattr(lo, load) - tol <= getattr(t, load) + <= getattr(hi, load) + tol), load + +# --------------------------------------------------------------------------- +# C. Non-uniform contributions, independent references +# --------------------------------------------------------------------------- + +def test_planner_targets_match_classic_cascade(): + rng = random.Random(21) + for _ in range(40): + rows, dT = random_problem(rng, 2, 10) + streams = streams_from(rows) + c = [rng.choice((0.5, 1., 2.5, 4., 7.5)) for _ in streams] + net = P._plan_numeric(streams, dT, approach_contributions=c, + work_scale=0.05) + Qh, Qc, zeros = classic_cascade(streams, c) + assert_targets(net, Qh, Qc, streams) + assert min(abs(net['plan'].pinch_T - (z - dT / 2)) for z in zeros) <= 1e-9 + +@pytest.mark.parametrize('name', ['kemp', 'smith2005_exr17_2_dT10']) +def test_problem_table_matches_classic_cascade(name): + if name == 'kemp': + T = 10. + units = cp_units('contributions_kemp_table', KEMP, 300.) + streams = [dict(name=ID, kind='hot' if T_in > T_out else 'cold', + T_in=T_in + 300., T_out=T_out + 300., CP=CP) + for ID, T_in, T_out, CP in KEMP] + given = [2., 6., 1., None] + else: + case = _case(name) + units, T = _build(case) + scale = T_UNITS[case['T_unit']][0] + kW = Q_UNITS[case['Q_unit']] + streams = [dict(name=n, kind=k, T_in=_kelvin(case, a), + T_out=_kelvin(case, b), CP=cp * kW / scale) + for n, k, a, b, cp in case['streams']] + given = [x * scale for x in corpus_contributions(case)] + c = [T / 2 if x is None else x for x in given] + inlets, outlets, is_hot = unit_streams(units) + assert is_hot == [s['kind'] == 'hot' for s in streams] + table, curves, grid = hxn_synthesis._problem_table( + inlets, outlets, is_hot, T, None, given) + Qh, Qc, zeros = classic_cascade(streams, c) + if name == 'kemp': # closed form: 7 kW hot, 47 kW cold, pinch 81 degC + assert_allclose((Qh, Qc), (7., 47.), rtol=0, atol=1e-9) + assert 381. in zeros + total = 3600. * sum(abs(s['CP'] * (s['T_out'] - s['T_in'])) for s in streams) + assert abs(table.hot_util_load - 3600. * Qh) <= 1e-9 * total + assert abs(table.cold_util_load - 3600. * Qc) <= 1e-9 * total + assert min(abs(table.pinch_T - (z - T / 2)) for z in zeros) <= 1e-9 + assert grid['shift'].tobytes() == np.where( + is_hot, T / 2 + np.array(c), T / 2 - np.array(c)).tobytes() + assert grid['contributions'].tolist() == c + for (Tk, _), s in zip(hxn_synthesis._grid_knots(curves, grid), streams): + assert_allclose([Tk[0], Tk[-1]], sorted((s['T_in'], s['T_out'])), + rtol=0, atol=1e-9) + +NO_SPLIT_CP = [case for case in NO_SPLIT if case['kind'] == 'constant_cp'] + +def test_no_split_corpus_with_contributions(): + # no status assertion: an unsplit MER network is known for the default + # requirement, not for these contributions (all 24 reached 'mer' when + # this test was written) + assert len(NO_SPLIT_CP) == 24 + for case in NO_SPLIT_CP: + streams = corpus_streams(case) + c = corpus_contributions(case) + net = P._plan_numeric(streams, case['dTmin'], approach_contributions=c) + Qh, Qc, _ = classic_cascade(streams, c) + assert_targets(net, Qh, Qc, streams) + hot, cold = check_network(streams, -math.inf, net) + pair_ends_ok(streams, {s['name']: cj for s, cj in zip(streams, c)}, + net) + info = net['plan'].info + assert net['matches'], case['name'] + assert info['min_approach_margin'] >= -1e-9, case['name'] + walked = min(min(m['T_hot_in'] - m['T_cold_out'], + m['T_hot_out'] - m['T_cold_in']) + for m in net['matches']) + assert abs(info['min_approach'] - walked) <= 1e-9, case['name'] + if net['status'] == 'mer': + tol = 1e-6 * duty_scale(streams) + assert abs(hot - Qh) <= tol and abs(cold - Qc) <= tol + +SPLIT_NAMES = ['smith2005_ex18_2_split', 'smith2005_exr18_4', + '7sp4_dolan1990_dt10K', 'fs_15sp_tkm', + 'cornell_processdesign_four_stream_split'] + +@pytest.mark.parametrize('name', SPLIT_NAMES) +def test_split_cases_with_contributions_reach_mer(name): + # Theorem M holds on the shared shifted scale: MER with splits + case = _case(name) + streams = corpus_streams(case) + c = corpus_contributions(case) + net = P._plan_numeric(streams, case['dTmin'], approach_contributions=c, + stream_splitting=True) + assert net['status'] == 'mer' + Qh, Qc, _ = classic_cascade(streams, c) + assert_targets(net, Qh, Qc, streams) + # branch temperatures: the parent's at the parent-equivalent enthalpies + pair_ends_ok(streams, {s['name']: cj for s, cj in zip(streams, c)}, net) + assert net['plan'].splits + assert net['plan'].info['min_approach_margin'] >= -1e-9 + +def test_exact_check_and_repair_per_pair(): + # hot stream 2 (on branches above the pinch) needs 5 K more against + # every partner: only its exchangers are flagged and shrunk, and only + # their exchangers get the larger dT guard + plan, curves, knots, is_hot, streams_inlet, dT = planned_split( + 'smith2005_ex18_2_split') + j = 2 + assert is_hot[j] + shift = np.where(is_hot, dT, 0.) + shift[j] += 5. + duties = {n: e.Q for n, e in enumerate(plan.exchangers)} + ends = hxn_synthesis._walk(plan, duties, knots, is_hot)[0] + bad = hxn_synthesis._exact_approach(plan, duties, ends, curves, knots, + shift)[2] + assert bad and all(plan.exchangers[n].hot == j for n in bad) + assert any(plan.exchangers[n].hot_frac != 1. for n in bad) + others = [n for n in duties if n not in bad] + assert others + new, changes = hxn_synthesis._repair(plan, duties, knots, is_hot, curves, + shift) + assert {n for n, _, _ in changes} == set(bad) + assert all(0. <= Q_after < Q_before for _, Q_before, Q_after in changes) + assert all(new[n] == duties[n] for n in others) + ends = hxn_synthesis._walk(plan, new, knots, is_hot)[0] + assert not hxn_synthesis._exact_approach(plan, new, ends, curves, knots, + shift)[2] + bst.main_flowsheet.set_flowsheet('contributions_repair') + units = hxn_synthesis._realize(plan, new, curves, knots, streams_inlet, + is_hot, shift)[0] + try: + for n, hx in units.items(): + required = dT + 5. if n in bad else dT + assert hx.dT == required - 1e-6, hx.ID + assert internal_approach(hx) >= required - APPROACH_TOL, hx.ID + finally: + hxn_synthesis._discard(units.values()) + # one shift per stream, or a scalar + with pytest.raises(ValueError, match='3 shifts given for 4 streams'): + hxn_synthesis._exact_approach(plan, duties, ends, curves, knots, + shift[:3]) + with pytest.raises(ValueError, match='3 shifts given for 4 streams'): + hxn_synthesis._realize(plan, new, curves, knots, streams_inlet, + is_hot, shift[:3]) + +# --------------------------------------------------------------------------- +# D. Validation +# --------------------------------------------------------------------------- + +def test_invalid_contributions(monkeypatch): + streams = streams_from(LINNHOFF4) + N = len(streams) + inlets, outlets, is_hot, T = water_example() + for n in (N - 1, N + 1): + with pytest.raises(ValueError, match=f'{n} approach contributions ' + f'given for {N} streams'): + P._plan_numeric(streams, 10., approach_contributions=[1.] * n) + with pytest.raises(ValueError, match='3 approach contributions given ' + 'for 2 streams'): + problem_table(inlets, outlets, is_hot, T, + approach_contributions=[1., 1., 1.]) + for value in (0., -1., math.inf, -math.inf, True, np.bool_(True), '3'): + with pytest.raises(ValueError, match='approach contribution of ' + 'stream 1 must be'): + P._plan_numeric(streams, 10., + approach_contributions=[None, value, None, None]) + with pytest.raises(ValueError, match='approach contribution of ' + 'stream 1 must be'): + problem_table(inlets, outlets, is_hot, T, + approach_contributions=[None, value]) + # a contribution needs a positive T_min_app (it sets the default T/2) + with pytest.raises(ValueError, match='T_min_app must be finite and > 0'): + P._plan_numeric(streams, 0., approach_contributions=[1.] * N) + with pytest.raises(ValueError, match='T_min_app must be finite and > 0'): + problem_table(inlets, outlets, is_hot, 0., + approach_contributions=[1., 1.]) + # nan is unspecified; numpy numbers are accepted + default = plan_record(P._plan_numeric(streams, 10.)['plan']) + for contributions in ([NAN] * N, np.full(N, NAN), np.full(N, 5.), + [np.float64(5.), 5, None, NAN]): + plan = P._plan_numeric(streams, 10., + approach_contributions=contributions)['plan'] + assert plan_record(plan) == default + # synthesize_network: one value per heat utility, checked before any + # stream is copied or flashed + units = cp_units('contributions_invalid', R002_UNITS, 300.) + hus = [hx.heat_utilities[0] for hx in units] + def no_copy(*args, **kwargs): + raise AssertionError('copied') + monkeypatch.setattr(hxn_synthesis, '_copy', no_copy) + with pytest.raises(ValueError, match='3 approach contributions given ' + 'for 4 heat utilities'): + synthesize_network(hus, 10., approach_contributions=[1.] * 3) + for x in ({1., 2., 3., 4.}, '1234', b'1234', 3.): + with pytest.raises(TypeError, match='approach_contributions must be'): + synthesize_network(hus, 10., approach_contributions=x) + with pytest.raises(ValueError, match='approach contribution of heat ' + 'utility 1 must be'): + synthesize_network(hus, 10., approach_contributions=[1., 0., 1., 1.]) + with pytest.raises(ValueError, match='T_min_app must be finite and > 0'): + synthesize_network(hus, 0., approach_contributions=[1., 1., 1., 1.]) + with pytest.raises(AssertionError, match='copied'): # valid: flashes next + synthesize_network(hus, 10., approach_contributions=[1., 2., None, 4.]) From ad1bcefe5d2614ce1d081d5b1da5af4a450a744d Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Fri, 16 Oct 2026 21:22:38 +0000 Subject: [PATCH 02/52] add unit keys and facility support for approach contributions Approach temperature contributions can now be given by unit, which is how they are meant to be set on a flowsheet, and through the HeatExchangerNetwork facility. This is WP2 of the F1 spec; WP1 took only a sequence aligned with hus. - hxn_synthesis: synthesize_network(approach_contributions=...) accepts a mapping, a sequence or a callable, checked in that order; anything else raises a TypeError. - Keys name units in three ways: the unit itself (or one with the same (owner.ID, ID), so a stale auxiliary that its owner rebuilt still matches), the tuple (owner.ID, unit.ID), or the ID of a unit without an owner. - A heat utility takes the key nearest to its unit along the _owner chain: 'D1' covers a column's condenser and reboiler, and ('D1', 'condenser') overrides it for the condenser. None and nan are an explicit default at their level. - Rejected keys and values: - heat utility keys raise a TypeError with a hint, because biosteam replaces heat utilities at every simulation; - other key types raise a TypeError; - keys that name no unit raise a ValueError ("name no unit"); - two keys at the same level with different values raise a ValueError; - bad values raise a ValueError naming the key. All of this runs before any stream is copied or flashed. A callable is called once per heat utility, in order. - New private helpers: _names_unit, _contribution_key_level, _check_contribution_key, _owner_chains, _mapping_contributions and _unit_name. _stream_contributions dispatches to them. - HeatExchangerNetwork(approach_contributions=None) takes a mapping or a callable; a sequence raises a TypeError because the facility's stream order is internal. The attribute can be reassigned between simulations, and a class attribute covers older instances. - The facility checks keys against every candidate unit (the units and their nested auxiliary units, new _get_units and _candidate_units) before it filters the heat utilities. A bad key, value or conflict therefore raises even while its unit is idle. A valid key for an idle or ignored unit has no effect. - _cost resolves the contributions once per simulation and passes them to synthesize_network. A cached network is reused only while the contributions resolve to the same values per stream (_resolved_contributions, None by default). Without explicit contributions every reuse decision is unchanged, and T_min_app is still not part of the cache key. - Docstrings: the synthesize_network parameter (all forms) and the r002 mapping doctest ('mer', 74/444 kW, every dT 6.999999). In HeatExchangerNetwork: the T_min_app line, the new parameter, the Notes wording, a Notes paragraph "Approach temperature contributions", the cache paragraph, and a doctest with per-pair dT guards (7 and 4 K). The doctest outputs are pasted from real runs. Default-off results are bit for bit unchanged: - the out-of-tree oracle (29 facility syntheses, every number as float.hex) gives sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf, unchanged; - the golden plan digests ffff603a8687fd88, 9a2b7cbfa36b0791, 07eeacf5af328127 and 57b9addff180e07e are unchanged; - new in-process tests compare the facility with None, {}, {u: nan}, lambda hu: None and {u: T/2} against the default with float.hex and tobytes. tests/test_hxn_contributions.py gains 15 tests (38 in all, about 25 s serial): - default networks bit for bit (r002, case_05, split smith2005_ex18_2); - uniform contributions in the facility against a doubled T_min_app; - a contribution that reproduces the T=7 shifts gives that network bit for bit; - facility split and unsplit cases against the contribution-aware problem table, with per-pair exact approaches and dT guards; - per-stream pinch temperatures (with and without sort_hus_by_T); - key resolution on auxiliary units, stale HXutilities keys and the quickstart facility simulated twice; - validation, before any flash and for idle units; - the cache rule. Full suite: 996 passed in 261 s with -n 4 (981 before, plus 15). Sphinx -W html build in a scratch venv: exit 0. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- hensmith/_heat_exchanger_network.py | 154 +++++++++- hensmith/hxn_synthesis.py | 186 ++++++++++-- tests/test_hxn_contributions.py | 440 +++++++++++++++++++++++++++- 3 files changed, 730 insertions(+), 50 deletions(-) diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index 6a257c0..f4440c4 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -17,10 +17,12 @@ @author: sarangbhagwat and yoelcp """ import heapq +from collections.abc import Mapping import biosteam as bst import numpy as np from .hxn_synthesis import ( synthesize_network, StreamLifeCycle, plot_pinch_diagram, _first_inlet, + _explicit_contributions, ) from warnings import warn @@ -201,7 +203,9 @@ class HeatExchangerNetwork(bst.Facility): ID : str Unique name for the facility. T_min_app : float - Minimum approach temperature observed during synthesis of heat exchanger network. + Minimum approach temperature observed during synthesis of heat + exchanger network: the default approach temperature, required of + every pair of streams unless `approach_contributions` are given. units : Iterable[Unit], optional All unit operations available to the heat exchanger network. Defaults to all unit operations in the system. @@ -220,6 +224,19 @@ class HeatExchangerNetwork(bst.Facility): and ``synthesis_info['splits']`` (`HeatExchangerNetwork`). With `avoid_recycle`, a split that would repeat a stream pair is not used, and MER is then not guaranteed. Defaults to False. + approach_contributions : Mapping or callable, optional + Approach temperature contribution of each process stream [K]: a + match between hot stream h and cold stream c must keep ``c_h + + c_c`` instead of `T_min_app`, and a stream without one contributes + ``T_min_app / 2`` (see Notes, "Approach temperature + contributions"). A mapping ``{key: value}`` whose keys name units + (a unit, its ID if it has no owner, or ``(owner.ID, unit.ID)``; + the nearest key along a unit's chain of owners applies), or a + callable ``f(hu)`` returning the value of a heat utility. A value + is None or nan (unspecified) or a finite number > 0. A sequence + raises a TypeError: the order of the facility's streams is + internal. May be changed between simulations. Defaults to None + (every stream contributes ``T_min_app / 2``). Notes ----- @@ -227,12 +244,12 @@ class HeatExchangerNetwork(bst.Facility): splits by :func:`~hensmith.hxn_synthesis.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 - ``_``, e.g. ``HX_3_2_cs_2``), since series alternation can replace a - split. Where the pinch design rules prove that MER needs stream + pinch from the pinch outward [1]_ [2]_, keeping each pair's required + approach (`T_min_app` by default) 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 ``_``, 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, or, with `stream_splitting`, one that splits streams to reach them. The outcome is recorded in `synthesis_info` (a dict; see the `info` keyword @@ -257,6 +274,39 @@ class HeatExchangerNetwork(bst.Facility): the exchangers of every branch), and a split stream's life cycle lists its branch stages with their branch and fraction. + *Approach temperature contributions.* With `approach_contributions`, + each process stream contributes its own approach temperature ``c_j`` + (``T_min_app / 2`` unless given), and every process exchanger keeps + the sum of its two streams' contributions instead of `T_min_app` (see + `synthesize_network`, Notes); each stream's utility exchanger gets its + agent as usual. A key names a unit: the unit itself, the ID of a unit + without an owner (e.g. ``'D1'``), or ``(owner.ID, unit.ID)`` for an + auxiliary unit (e.g. ``('D1', 'condenser')``; a bare auxiliary ID names + nothing). A heat utility takes the value of the key nearest to its unit + along the unit's chain of owners, so ``'D1'`` covers the condenser and + the reboiler of column D1 and ``('D1', 'condenser')`` overrides it for + the condenser; a None or nan value is an explicit default that wins at + its level like any other, and two keys of the same level for a unit + must agree. ID keys are the robust form: a unit that rebuilds its + auxiliary units at every simulation (e.g. `biosteam.HXutilities`) + leaves a key holding an old auxiliary to match by its owner's ID and + its own (heat utility keys are rejected, since BioSTEAM replaces them at + every simulation). The keys and values are checked against every unit + available to the network and its nested auxiliary units before the + heat utilities are taken: a key that names none of them, a bad value + or two conflicting keys raise a ValueError, even while their unit is + idle, but a valid key whose unit holds no heat utility in a simulation + (no duty, `ignored`, or not available to a heat exchanger network) has + no effect, so that, e.g., Monte Carlo samples do not fail. A callable is + called once per heat utility at every simulation (also to check a + cached network, and again if a failed cache synthesizes anew), in the + order of the facility's heat utilities: it should be pure. + ``synthesis_info`` reports the contributions as used by stream + ('approach_contributions', in the order of `original_heat_exchangers`) + and the smallest margin over each exchanger's required approach + ('min_approach_margin'), and `pinch_Ts` holds each stream's own pinch + temperature. + Original system stream and heat exchanger objects are preserved. All stream copies and new HX objects can be found in a newly created flowsheet '_HXN' where is the name of the system associated @@ -269,7 +319,8 @@ class HeatExchangerNetwork(bst.Facility): pass). With `cache_network`, a network is reused while the set of heat - exchangers and `stream_splitting` are the same: each process exchanger + exchangers, `stream_splitting` and the explicit approach contributions + (resolved per stream) are 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 @@ -386,13 +437,28 @@ class HeatExchangerNetwork(bst.Facility): >>> HXN.synthesis_info['splits'] [] + With approach temperature contributions of 2 K for C1 and H3 (the other + streams keep ``T_min_app / 2 = 5`` K), C1 needs 7 K against H1 and H2 + but only 4 K against H3, and each exchanger's `dT` guard is its pair's + requirement less 1e-6 K: + + >>> HXN.stream_splitting = False + >>> HXN.approach_contributions = {'C1': 2., 'H3': 2.} + >>> sys.simulate() + >>> HXN.synthesis_info['status'] + 'best_effort' + >>> [round(hx.dT, 6) for hx in HXN.new_HXs] + [6.999999, 6.999999, 6.999999, 6.999999, 6.999999, 3.999999] + """ ticket_name = 'HXN' acceptable_energy_balance_error = 0.02 raise_energy_balance_error = False - # Default of an instance without it (e.g. one from before stream - # splitting); `__init__` sets it on every new facility. + # Defaults of an instance without them (e.g. one from before stream + # splitting or approach contributions); `__init__` sets them on every + # new facility. stream_splitting = False + approach_contributions = None network_priority = -2 _N_ins = 0 _N_outs = 0 @@ -402,7 +468,8 @@ class HeatExchangerNetwork(bst.Facility): def __init__(self, ID='', T_min_app=5., units=None, ignored=None, Qmin=1e-3, 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, stream_splitting=False): + sort_hus_by_T=False, stream_splitting=False, + approach_contributions=None): bst.Facility.__init__(self, ID, None, None) self.T_min_app = T_min_app self.units = units @@ -414,16 +481,53 @@ def __init__(self, ID='', T_min_app=5., units=None, ignored=None, Qmin=1e-3, self.replace_unit_heat_utilities = replace_unit_heat_utilities self.sort_hus_by_T = sort_hus_by_T self.stream_splitting = stream_splitting + self.approach_contributions = approach_contributions if acceptable_energy_balance_error is not None: self.acceptable_energy_balance_error = acceptable_energy_balance_error - def _get_original_heat_utilties(self): + def _get_units(self): + """The units whose heat utilities the network may take: the `units` + option (called if callable), or else the system's units.""" sys = self.system if self.units: units = self.units if callable(units): units = units() else: units = sys.units + return units + + def _candidate_units(self): + """The units that approach contribution keys may name: each unit of + `_get_units` followed by its nested auxiliary units, whether or not + it holds a heat utility the network takes.""" + candidates = [] + for unit in self._get_units(): + candidates.append(unit) + candidates.extend([i for i in unit.nested_auxiliary_units + if isinstance(i, bst.Unit)]) + return candidates + + def _network_contributions(self, hx_utils): + """ + The explicit approach contributions of `hx_utils` (aligned with + it; None where unspecified), or None if none is given (see + `hensmith.hxn_synthesis._explicit_contributions`). The keys are + checked against every candidate unit (`_candidate_units`), so a bad + key or value raises even while its unit holds no heat utility. + """ + x = self.approach_contributions + if x is None: return None + if not (isinstance(x, Mapping) or callable(x)): + raise TypeError('HeatExchangerNetwork.approach_contributions must ' + 'be a mapping or a callable (the order of the ' + "facility's streams is internal, so a sequence " + f'has nothing to align with), not ' + f'{type(x).__name__!r}') + return _explicit_contributions(hx_utils, x, self.T_min_app, + units=self._candidate_units()) + + def _get_original_heat_utilties(self): + units = self._get_units() ignored = self.ignored if ignored: if callable(ignored): ignored = ignored() @@ -498,6 +602,11 @@ def _cost(self): with flowsheet.temporary(): self._restore_unit_heat_utilities() hx_utils = self._get_original_heat_utilties() + # the explicit approach contributions by heat utility (resolved + # once per simulation; None without any) + values = self._network_contributions(hx_utils) + value_of = (None if values is None + else {id(hu): v for hu, v in zip(hx_utils, values)}) use_cached_network = False # A network synthesized with other options (stream splitting on or # off), or by a facility from before stream splitting (no options @@ -506,10 +615,16 @@ def _cost(self): and hasattr(self, '_stage_fractions') and getattr(self, '_synthesis_options', None) == (self.stream_splitting,)): - # Units are a stable key to compare whether system has changed configuration. + # Units are a stable key to compare whether system has changed + # configuration; the explicit contributions, resolved per + # stream, must be those the network was synthesized with. hu_by_unit = {hu.unit: hu for hu in hx_utils} use_cached_network = ( hu_by_unit.keys() == set(self.original_heat_exchangers) + and getattr(self, '_resolved_contributions', None) == ( + None if value_of is None else tuple( + value_of[id(hu_by_unit[hx])] + for hx in self.original_heat_exchangers)) ) with flowsheet.temporary(), bst.IgnoreDockingWarnings(): if use_cached_network: @@ -582,7 +697,10 @@ def _cost(self): synthesize_network(hx_utils, self.T_min_app, self.Qmin, self.force_ideal_thermo, self.avoid_recycle, self.sort_hus_by_T, info=synthesis_info, - stream_splitting=self.stream_splitting) + stream_splitting=self.stream_splitting, + approach_contributions=( + None if value_of is None else + [value_of[id(hu)] for hu in hx_utils])) new_HXs = HXs_hot_side + HXs_cold_side # the realized splits (none without stream splitting) splits = synthesis_info.get('splits', ()) @@ -626,10 +744,16 @@ def _cost(self): # of the flow) has f times the whole stream's limit: its # share is that of the whole stream's limit (on the parent # basis), and f is kept in `_stage_scales`. The options the - # network was synthesized with are the cache's key. + # network was synthesized with, and its explicit approach + # contributions (unfilled, in the order of + # `original_heat_exchangers`; None without any), are the + # cache's key. self._stage_fractions = stage_fractions = {} self._stage_scales = stage_scales = {} self._synthesis_options = (self.stream_splitting,) + self._resolved_contributions = ( + None if value_of is None else tuple( + value_of[id(hu)] for hu in hx_heat_utils_rearranged)) for i, life_cycle in enumerate(stream_life_cycles): hx = hx_heat_utils_rearranged[i].unit H_in = hx.ins[0].H diff --git a/hensmith/hxn_synthesis.py b/hensmith/hxn_synthesis.py index a8adebd..83146ae 100644 --- a/hensmith/hxn_synthesis.py +++ b/hensmith/hxn_synthesis.py @@ -15,7 +15,7 @@ cycles (`StreamLifeCycle`) and pinch diagrams (`plot_pinch_diagram`). """ from collections import namedtuple -from collections.abc import Sequence +from collections.abc import Mapping, Sequence import heapq import math import re @@ -768,28 +768,128 @@ def _pinch_cut(table): arriving = table.residual - point_total return 'below' if arriving[k] <= table.residual[k] else 'above' +def _unit_name(unit): + """``(owner.ID, unit.ID)``: how messages name a unit (``(ID, ID)`` for + a unit without an owner).""" + return unit.owner.ID, unit.ID + +def _names_unit(key, unit): + """Whether the approach contribution key `key` (of a valid type, see + `_check_contribution_key`) names `unit`: the unit itself, or one with + its owner's ID and its ID (a stale auxiliary that its owner rebuilt); + ``(owner.ID, unit.ID)``; or the ID of a unit without an owner.""" + if isinstance(key, bst.Unit): + return key is unit or _unit_name(key) == _unit_name(unit) + if isinstance(key, tuple): return key == _unit_name(unit) + return key == unit.ID and unit.owner is unit + +def _contribution_key_level(key, unit): + """The number of steps along the owner chain of `unit` (`unit`, + ``unit._owner``, ...) to the first unit that `key` names, or None.""" + level = 0 + while unit is not None: + if _names_unit(key, unit): return level + unit = getattr(unit, '_owner', None) + level += 1 + return None + +def _check_contribution_key(key): + """Raise a TypeError unless `key` is a unit, a str or a pair of str.""" + if isinstance(key, bst.HeatUtility): + raise TypeError('a heat utility cannot be an approach_contributions ' + 'key (biosteam replaces the heat utilities of a unit ' + 'at every simulation): use its unit (hu.unit), the ' + "unit's ID or (owner.ID, unit.ID), a sequence, or a " + 'callable') + if isinstance(key, (bst.Unit, str)): return + if (isinstance(key, tuple) and len(key) == 2 + and all(isinstance(i, str) for i in key)): return + raise TypeError('approach_contributions keys must be units, unit IDs ' + '(str) or (owner.ID, unit.ID) tuples, not ' + f'{type(key).__name__!r} ({key!r})') + +def _owner_chains(units): + """Every unit on the owner chains of `units`, once each.""" + found = {} + for unit in units: + while unit is not None and id(unit) not in found: + found[id(unit)] = unit + unit = getattr(unit, '_owner', None) + return list(found.values()) + +def _mapping_contributions(hus, mapping, units): + """ + `_stream_contributions` of a mapping from keys naming units to + contributions: checks the types of keys and values, then that every key + names a unit of the universe (the owner chains of the units of `hus` + and of `units`), then that no two keys of the same level for a unit of + the universe give it different values (None and nan are equal); then + each heat utility takes the value of the key of the lowest level on its + unit's owner chain (None without one). + """ + items = [] + for key, value in mapping.items(): + _check_contribution_key(key) + items.append((key, _contribution_value(value, f'key {key!r}'))) + universe = _owner_chains([hu.unit for hu in hus] + list(units or ())) + unknown = [key for key, _ in items + if not any(_names_unit(key, u) for u in universe)] + if unknown: + raise ValueError('approach_contributions keys that name no unit: ' + f"{', '.join(map(repr, unknown))} (a key is a unit, " + 'the ID of a unit without an owner, or (owner.ID, ' + 'unit.ID))') + for unit in universe: + first = {} # level -> (key, value) + for key, value in items: + level = _contribution_key_level(key, unit) + if level is None: continue + other, other_value = first.setdefault(level, (key, value)) + if other_value != value: + raise ValueError( + f'approach_contributions keys {other!r} and {key!r} ' + f'give unit {_unit_name(unit)!r} different values at the ' + f'same level: {other_value!r} and {value!r}') + values = [] + for hu in hus: + best = None # (level, value) of the nearest key + for key, value in items: + level = _contribution_key_level(key, hu.unit) + if level is not None and (best is None or level < best[0]): + best = (level, value) + values.append(None if best is None else best[1]) + return values + def _stream_contributions(hus, approach_contributions, units=None): """ Resolve `approach_contributions` to one approach temperature contribution per heat utility of `hus`, in the given order (zero-duty ones included): a list of float, or None where unspecified (nan becomes - None; not filled with ``T_min_app / 2``). Accepts None (all - unspecified) or a sequence aligned with `hus` (not a str or bytes); - `units` is reserved for keys that name units. + None; not filled with ``T_min_app / 2``). Accepts the forms of + `synthesize_network`'s parameter: None (all unspecified), a mapping + whose keys name units (see `_mapping_contributions`), a sequence + aligned with `hus` (not a str or bytes), or a callable of a heat + utility (called once per heat utility, in order). `units` adds units + (with their owner chains) to those that keys may name and that are + checked for conflicting keys, whether or not they hold a heat utility + of `hus`. """ N = len(hus) x = approach_contributions if x is None: return [None] * N - if not isinstance(x, (Sequence, np.ndarray)) or isinstance(x, (str, bytes)): - raise TypeError('approach_contributions must be a sequence with one ' - 'value per heat utility, not ' - f'{type(x).__name__!r}') - values = list(x) - if len(values) != N: - raise ValueError(f'{len(values)} approach contributions given for ' - f'{N} heat utilities') - return [_contribution_value(v, f'heat utility {k}') - for k, v in enumerate(values)] + if isinstance(x, Mapping): return _mapping_contributions(hus, x, units) + if isinstance(x, (Sequence, np.ndarray)) and not isinstance(x, (str, bytes)): + values = list(x) + if len(values) != N: + raise ValueError(f'{len(values)} approach contributions given for ' + f'{N} heat utilities') + return [_contribution_value(v, f'heat utility {k}') + for k, v in enumerate(values)] + if callable(x): + return [_contribution_value(x(hu), f'unit {_unit_name(hu.unit)!r}') + for hu in hus] + raise TypeError('approach_contributions must be a mapping, a sequence or ' + f'a callable, not {type(x).__name__!r}') def _explicit_contributions(hus, approach_contributions, T_min_app, units=None): @@ -1928,18 +2028,42 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, and ``synthesis_info['splits']`` (`HeatExchangerNetwork`). With `avoid_recycle`, a split that would repeat a stream pair is not used, and MER is then not guaranteed. Defaults to False. - approach_contributions : Sequence[float or None], optional + approach_contributions : Mapping, Sequence or callable, optional Approach temperature contribution of each process stream [K]: a match between hot stream h and cold stream c must keep ``c_h + c_c`` instead of `T_min_app` (see Notes, "Approach - contributions"). One value per heat utility, aligned with `hus` as - given (before the heating utilities are placed first; zero-duty - utilities included): None or nan (unspecified, ``T_min_app / 2``), - or a finite number > 0 (`T_min_app` must then be finite and > 0 - too). Checked before any stream is copied or flashed; a wrong - length raises a ValueError. If every value is unspecified, the - result is the default one. Defaults to None (every stream - contributes ``T_min_app / 2``). + contributions"). A value is None or nan (unspecified: ``T_min_app + / 2``) or a finite number > 0 (`T_min_app` must then be finite and + > 0 too). Given as one of: + + - a mapping ``{key: value}`` whose keys name units: a unit (the + same object, or one with the same ID and owner's ID: a stale + auxiliary that its owner rebuilt), ``(owner.ID, unit.ID)`` (the + unit's own ID twice for a unit without an owner) or the ID of a + unit without an owner (a bare auxiliary ID such as + ``'condenser'`` names nothing). A heat utility takes the value of + the key nearest to its unit along the unit's chain of owners: a + unit's own key beats its owner's, which beats the owner's owner's + (e.g. ``'D1'`` covers the condenser and the reboiler of column + D1, and ``('D1', 'condenser')`` overrides it for the condenser). + A None or nan value wins at its level like any other. Heat + utility keys are rejected (BioSTEAM replaces them at every + simulation). A key that names neither the unit of a heat utility + of `hus` nor one of its owners, or two keys of the same level for + a unit with different values, raise a ValueError; + - a sequence (or `numpy.ndarray`; not a str or bytes) with one + value per heat utility, aligned with `hus` as given (before the + heating utilities are placed first; zero-duty utilities + included); a wrong length raises a ValueError; + - a callable ``f(hu)`` returning the value of a heat utility, + called once per heat utility in the order of `hus`. + + Anything else raises a TypeError. Checked and resolved before any + stream is copied or flashed. If every value is unspecified, the + result is the default one. The contributions as used are reported + in ``info['approach_contributions']`` in stream order, not in the + order of `hus`. Defaults to None (every stream contributes + ``T_min_app / 2``). Returns ------- @@ -2213,6 +2337,22 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, >>> round(info['Q_hot_target'] / 3600., 6), round(info['Q_cold_target'] / 3600., 6) (80.0, 450.0) + With an approach temperature contribution of 2 K for the hot stream + (keyed by its unit) and the default ``T_min_app / 2 = 5`` K for the + cold streams, every match needs 7 K instead of 10 K, so the targets + fall to those of ``T_min_app = 7`` K, and every exchanger's `dT` + guard is 7 K less 1e-6 K: + + >>> info = {} + >>> result = synthesize_network(hus, T_min_app=10., info=info, + ... approach_contributions={units[3]: 2.}) + >>> info['status'], round(info['Q_hot_target'] / 3600., 6), round(info['Q_cold_target'] / 3600., 6) + ('mer', 74.0, 444.0) + >>> info['approach_contributions'] + array([5., 5., 5., 2.]) + >>> sorted({round(hx.dT, 6) for hx in result[0] + result[1]}) + [6.999999] + References ---------- .. [LH83] Linnhoff, B., & Hindmarsh, E. (1983). The pinch design method diff --git a/tests/test_hxn_contributions.py b/tests/test_hxn_contributions.py index 28d74a3..f5ba516 100644 --- a/tests/test_hxn_contributions.py +++ b/tests/test_hxn_contributions.py @@ -11,24 +11,39 @@ (``approach_contributions``): a match between hot stream h and cold stream c must keep ``c_h + c_c`` instead of ``T_min_app``. -A. Default unchanged: unspecified contributions (None, nan, or every stream - at ``T_min_app / 2``) give the default plans and problem tables bit for - bit, compared in-process with ``float.hex`` and ``tobytes``. +A. Default unchanged: unspecified contributions (None, nan, an empty + mapping, a callable returning None, or every stream at + ``T_min_app / 2``) give the default plans, problem tables and facility + networks bit for bit, compared in-process with ``float.hex`` and + ``tobytes``. B. Uniform contributions: every stream at ``x`` plans the network of ``T_min_app = 2x`` on a shifted scale offset by a constant, and on real-thermo cases all hot streams at ``a`` and all cold ones at ``b`` give the default targets at ``T_min_app = a + b`` (references that do not use the contribution code); mixed contributions give targets between - those of the smallest and of the largest pair requirement. + those of the smallest and of the largest pair requirement. A hot + stream's contribution that reproduces the shifts of a smaller + ``T_min_app`` gives that default network bit for bit. C. Non-uniform contributions against INDEPENDENT references: a closed-form constant-CP cascade with the classic symmetric shifts (hot streams down by ``c_j``, cold streams up by ``c_j``; hensmith's shifted scale is that scale less ``T_min_app / 2``), and a walk of every planned match on recomputed constant-CP temperatures that checks each pair's requirement at both ends (the minimum for linear profiles). The exactness check, the - repair and the realization enforce each pair's own requirement. -D. Validation of the value domain (None or nan, else a finite number > 0) - and of the sequence form. + repair and the realization enforce each pair's own requirement. The + facility (split and unsplit, constant CP and real thermodynamics) + reaches the contribution-aware problem table's targets, keeps every + pair's requirement on the exact states of its streams (independent + recomputation) and gives each exchanger its pair's ``dT`` guard; each + stream's pinch temperature is the shifted pinch plus its own shift. +D. Keys and validation: the value domain (None or nan, else a finite + number > 0); the mapping, sequence and callable forms; keys naming + units, auxiliary units through their owners (the nearest key wins) and + stale auxiliaries; unknown keys, conflicting keys and bad values, + checked before any stream is flashed and, in the facility, even for + idle units. +E. The ``cache_network`` rule: a cached network is reused while the + explicit contributions resolve to the same values per stream. """ import math import random @@ -36,11 +51,13 @@ import pytest import biosteam as bst from numpy.testing import assert_allclose -from hensmith import hxn_synthesis +from hensmith import hxn_synthesis, HeatExchangerNetwork from hensmith import _planner as P -from hensmith.hxn_synthesis import problem_table, synthesize_network -from test_hxn import cp_units, KEMP, R002 as R002_UNITS, planned_split -from test_hxn_regression import internal_approach, CASES +from hensmith.hxn_synthesis import problem_table, synthesize_network, _pinch_cut +from test_hxn import (cp_units, KEMP, R002 as R002_UNITS, planned_split, + simulate_HXN, simulate_strictly) +from test_hxn_regression import (internal_approach, actual_loads, CASES, + STATUS_RTOL, CASCADE_RTOL) from test_hxn_mer import _build, _case, _kelvin, APPROACH_TOL from test_hxn_planner import (streams_from, check_network, duty_scale, random_problem, R002, LINNHOFF4, HARD, @@ -142,6 +159,66 @@ def by_duty(units): return sorted((hx.heat_utilities[0] for hx in units), key=lambda hu: hu.duty) +def required(HXN, hx, of_unit): + """The required approach of a synthesized process exchanger: the sum of + its two streams' contributions, `of_unit` of their original units.""" + return sum(of_unit(HXN.original_heat_exchangers[i]) + for i in hxn_synthesis._stream_ports(hx)) + +def sampled_approach(hx, n=21): + """The smallest hot-minus-cold temperature difference of a process + exchanger at `n` uniform duty points, from PH flashes of copies of its + streams (any mixture; at least the exact minimum).""" + def T(stream, H): + s = stream.copy() + s.vle(H=H, P=stream.P) + return s.T + dH = [s_in.H - s_out.H for s_in, s_out in zip(hx.ins, hx.outs)] + h = 0 if dH[0] >= dH[1] else 1 + hot_in, hot_out = hx.ins[h], hx.outs[h] + cold_in, cold_out = hx.ins[1 - h], hx.outs[1 - h] + return min(T(hot_in, hot_in.H - q) - T(cold_in, max(cold_out.H - q, + cold_in.H)) + for q in np.linspace(0., hot_in.H - hot_out.H, n)) + +def network_record(HXN): + """Every number of a facility's network that the default path must + keep, as hex or bytes.""" + hx = lambda x: float(x).hex() + info = HXN.synthesis_info + return ([(u.ID, hx(u.Q), hx(u.dT)) for u in HXN.new_HXs], + [hx(u.Q) for u in HXN.new_HX_utils], # an int 0 if served + HXN.pinch_Ts.tobytes(), hx(info['min_approach']), info['status'], + [u.ID for u in HXN.new_splitters]) + +def contribution_table(units, T, of_unit): + """The problem table of the units' process streams with their + contributions (`of_unit` of each unit), prepared as the facility + prepares them, and the total duty.""" + hus = by_duty(units) + inlets, outlets, is_hot = unit_streams([hu.unit for hu in hus]) + table = problem_table(inlets, outlets, is_hot, T, approach_contributions=[ + of_unit(hu.unit) for hu in hus]) + return table, sum(abs(hu.duty) for hu in hus) + +def quickstart(name): + """The system of `HeatExchangerNetwork`'s first example: column D1 + (condenser and reboiler), its product coolers D1_H1 and D1_H2, and + flash F1 (its heater).""" + bst.main_flowsheet.set_flowsheet(name) + bst.settings.set_thermo(['Water', 'Methanol', 'Glycerol'], cache=True) + 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) + return D1, D1_H1, D1_H2, F1 + # --------------------------------------------------------------------------- # A. Default unchanged # --------------------------------------------------------------------------- @@ -202,6 +279,35 @@ def test_default_problem_table_bit_for_bit(name): inlets, outlets, is_hot, T, curves, approach_contributions=contributions)) == reference +def default_network_units(name): + """(units, T_min_app, stream_splitting) of a facility test case.""" + if name == 'r002': + return cp_units('contributions_default_r002', R002_UNITS, 300.), 10., False + if name == 'case_05_boiling_cold': + return (*CASES[name][0](), False) + return (*_build(_case(name)), True) + +@pytest.mark.parametrize('name', ['r002', 'case_05_boiling_cold', + 'smith2005_ex18_2_split']) +def test_default_networks_bit_for_bit(name): + # the facility with unspecified contributions in every form, or every + # stream at T_min_app / 2, synthesizes the default network to the bit + units, T, splitting = default_network_units(name) + sys, HXN = simulate_HXN(units, T, f'contributions_default_{name}', + stream_splitting=splitting) + reference = network_record(HXN) + assert HXN._resolved_contributions is None + for contributions in ({}, {u: NAN for u in units}, lambda hu: None, + {u: T / 2 for u in units}, None): + HXN.approach_contributions = contributions + simulate_strictly(sys) + assert network_record(HXN) == reference + info = HXN.synthesis_info + assert (info['approach_contributions'] == T / 2).all() + assert (float(info['min_approach_margin']).hex() + == float(info['min_approach'] - T).hex()) + if splitting: assert HXN.new_splitters + # --------------------------------------------------------------------------- # B. Uniform contributions # --------------------------------------------------------------------------- @@ -240,6 +346,42 @@ def test_uniform_contributions_equal_doubled_T_min_app(name): assert abs(plan.info['min_approach_margin'] - (plan_ref.info['min_approach'] - 2. * x)) <= 1e-9 +def test_uniform_contributions_in_the_facility(): + # KEMP: every unit at 5 K with T_min_app = 4 synthesizes the network of + # T_min_app = 10 + units = cp_units('contributions_uniform', KEMP, 300.) + sys, HXN = simulate_HXN(units, 10., 'contributions_uniform') + ref = [(hx.ID, hx.Q) for hx in HXN.new_HXs] + HXN.T_min_app = 4. + HXN.approach_contributions = {u: 5. for u in units} + simulate_strictly(sys) + assert [hx.ID for hx in HXN.new_HXs] == [ID for ID, _ in ref] + assert_allclose([hx.Q for hx in HXN.new_HXs], [Q for _, Q in ref], + rtol=1e-9) + assert all(hx.dT == 10. - 1e-6 for hx in HXN.new_HXs) + assert (HXN.synthesis_info['approach_contributions'] == 5.).all() + +def test_contributions_equal_to_a_shift_are_exact(): + # r002 at T_min_app = 10 with H1 at 2 K has the shifts of the default + # run at 7 K (hot 5 + 2, cold 5 - 5), so its network is that one to the + # bit + units = cp_units('contributions_shift_7', R002_UNITS, 300.) + sys, HXN = simulate_HXN(units, 7., 'contributions_shift_7') + reference = network_record(HXN) + min_approach = HXN.synthesis_info['min_approach'] + units = cp_units('contributions_shift_10', R002_UNITS, 300.) + sys, HXN = simulate_HXN(units, 10., 'contributions_shift_10', + approach_contributions={units[3]: 2.}) + info = HXN.synthesis_info + assert network_record(HXN) == reference + assert info['status'] == 'mer' + assert (float(info['min_approach_margin']).hex() + == float(min_approach - 7.).hex()) + assert info['approach_contributions'].tolist() == [5., 5., 5., 2.] + assert all(hx.dT == 7. - 1e-6 for hx in HXN.new_HXs) + assert round(info['Q_hot_target'] / 3600., 6) == 74. + assert round(info['Q_cold_target'] / 3600., 6) == 444. + @pytest.mark.parametrize('name', ['case_05_boiling_cold', 'case_08_two_condensers']) def test_real_thermo_targets_against_uniform_runs(name): @@ -422,10 +564,185 @@ def test_exact_check_and_repair_per_pair(): hxn_synthesis._realize(plan, new, curves, knots, streams_inlet, is_hot, shift[:3]) +def facility_case(name): + """(units, T_min_app, stream_splitting, contributions by unit) of a + facility case with contributions: the corpus factors on constant-CP + split cases, and hand values in the case's own unit order on + real-thermo ones.""" + if name in CASES: + units, T = CASES[name][0]() + values, splitting = { + 'case_04_report_case': ([1., 4., 2., 6.], True), + 'case_08_two_condensers': ([1., 4., 2., 6., 3., 0.5], False), + }[name] + return units, T, splitting, dict(zip(units, values)) + case = _case(name) + units, T = _build(case) + scale = T_UNITS[case['T_unit']][0] + return units, T, True, dict(zip(units, [ + scale * c for c in corpus_contributions(case)])) + +@pytest.mark.parametrize('name', ['smith2005_ex18_2_split', + '7sp4_dolan1990_dt10K', + 'case_04_report_case', + 'case_08_two_condensers']) +def test_facility_contributions_split_and_unsplit(name): + # the facility reaches the contribution-aware problem table's targets + # and keeps every pair's own requirement on the exact stream states + units, T, splitting, C = facility_case(name) + sys, HXN = simulate_HXN(units, T, f'contributions_{name}', + stream_splitting=splitting, + approach_contributions=C) + info = HXN.synthesis_info + # Theorem M with splitting; observed for case_08 (unsplit) + assert info['status'] == 'mer' + table, total = contribution_table(units, T, C.get) + heat, cool = actual_loads(HXN) + assert abs(heat - table.hot_util_load) <= STATUS_RTOL * total + assert abs(cool - table.cold_util_load) <= STATUS_RTOL * total + assert abs(HXN.energy_balance_percent_error) < 1e-6 + for hx in HXN.new_HXs: + r = required(HXN, hx, C.get) + assert internal_approach(hx) >= r - APPROACH_TOL, hx.ID + assert abs(hx.dT - (r - 1e-6)) <= 1e-12, hx.ID + assert info['min_approach_margin'] >= -APPROACH_TOL + assert info['approach_contributions'].tolist() == [ + C[hx] for hx in HXN.original_heat_exchangers] + planned = info['plan_targets'] + assert abs(planned['Q_hot'] - table.hot_util_load) <= CASCADE_RTOL * total + assert abs(planned['Q_cold'] - table.cold_util_load) <= CASCADE_RTOL * total + assert abs(planned['pinch_T'] - table.pinch_T) <= 1e-9 + assert planned['cut'] == _pinch_cut(table) + if splitting: assert HXN.new_splitters and info['splits'] + +@pytest.mark.parametrize('sort_hus_by_T', [False, True]) +def test_pinch_Ts_per_stream(sort_hus_by_T): + # KEMP (300 K above the classic temperatures) at 10 K with C1 at 2, H2 + # at 6 and C3 at 1 K (H4 at the default 5 K): 7 kW of heating (closed + # form, see test_problem_table_matches_classic_cascade); every stream's + # pinch temperature is the shifted pinch plus its own shift + units = cp_units('contributions_pinch_Ts', KEMP, 300.) + C1, H2, C3, H4 = units + hand = {C1: 2., H2: 6., C3: 1., H4: 5.} + sys, HXN = simulate_HXN(units, 10., 'contributions_pinch_Ts', + sort_hus_by_T=sort_hus_by_T, + approach_contributions={C1: 2., H2: 6., 'C3': 1.}) + info = HXN.synthesis_info + assert info['status'] == 'mer' # observed + assert round(info['Q_hot_target'] / 3600., 6) == 7. + hxs = HXN.original_heat_exchangers + assert info['approach_contributions'].tolist() == [hand[hx] for hx in hxs] + pinch_T = info['plan_targets']['pinch_T'] + expected = {'C1': 379., 'C3': 380., 'H2': 387., 'H4': 386.} + for i, hx in enumerate(hxs): + hot = i not in HXN.cold_indices + T_p = pinch_T + 5. + (hand[hx] if hot else -hand[hx]) + T_in, T_out = HXN.inlet_Ts[i], HXN.outlet_Ts[i] + # every stream reaches the pinch (C3 starts there) + assert min(T_in, T_out) <= T_p <= max(T_in, T_out), hx.ID + assert abs(HXN.pinch_Ts[i] - T_p) <= 1e-9, hx.ID + assert abs(T_p - expected[hx.ID]) <= 1e-9, hx.ID + # --------------------------------------------------------------------------- -# D. Validation +# D. Keys and validation # --------------------------------------------------------------------------- +def test_contribution_keys_on_auxiliary_units(): + # column D1 holds its condenser and reboiler as auxiliary units, flash F1 + # its heater; D1_H1 and D1_H2 have no owner + D1, D1_H1, D1_H2, F1 = quickstart('contributions_keys') + units = [D1, D1_H1, D1_H2, F1] + bst.System.from_units('contributions_keys', units=units).simulate() + hus = bst.process_tools.heat_exchanger_utilities_from_units(units) + IDs = [hu.unit.ID for hu in hus] + assert sorted(IDs) == ['D1_H1', 'D1_H2', 'condenser', 'heat_exchanger', + 'reboiler'] + none = dict.fromkeys(IDs) + + def resolve(x): + return dict(zip(IDs, hxn_synthesis._stream_contributions(hus, x))) + + # an owner's key covers its auxiliary units; the nearest key wins + assert resolve({'D1': 3.}) == none | {'condenser': 3., 'reboiler': 3.} + assert resolve({'D1': 3., ('D1', 'condenser'): 1.}) == none | { + 'condenser': 1., 'reboiler': 3.} + assert resolve({D1: 3., D1.condenser: 1.}) == none | { + 'condenser': 1., 'reboiler': 3.} + assert resolve({F1: 2., D1_H1: 4., 'D1_H2': 1., + ('D1_H1', 'D1_H1'): 4.}) == none | { + 'heat_exchanger': 2., 'D1_H1': 4., 'D1_H2': 1.} + # None and nan are an explicit default at their level + for unspecified in (None, NAN): + assert resolve({'D1': 3., ('D1', 'condenser'): unspecified}) == ( + none | {'reboiler': 3.}) + assert resolve({D1: None, 'D1': NAN}) == none + # same-level keys must agree; a bare auxiliary ID names nothing + assert resolve({D1: 3., 'D1': 3., ('D1', 'D1'): 3.})['reboiler'] == 3. + for x in ({D1_H1: 4., 'D1_H1': 3.}, {D1: 3., 'D1': 2.}, + {D1: None, 'D1': 3.}, {'D1': 3., D1.reboiler: 1., + ('D1', 'reboiler'): 2.}): + with pytest.raises(ValueError, match='different values at the same ' + 'level'): + resolve(x) + with pytest.raises(ValueError, match="name no unit: 'condenser', " + r"\('D1', 'nope'\)"): + resolve({'condenser': 1., ('D1', 'nope'): 1., 'D1': 2.}) + # sequence (aligned with hus) and callable (once per heat utility, in + # order) forms; heat utility and other keys are rejected + assert hxn_synthesis._stream_contributions( + hus, [1., None, NAN, 2., 3.]) == [1., None, None, 2., 3.] + calls = [] + def f(hu): + calls.append(hu) + return 2. if hu.unit.owner is D1 else None + assert resolve(f) == none | {'condenser': 2., 'reboiler': 2.} + assert [id(hu) for hu in calls] == [id(hu) for hu in hus] + with pytest.raises(TypeError, match='a heat utility cannot be an ' + r'approach_contributions key.*hu\.unit'): + resolve({hus[0]: 1.}) + for key in (1, ('D1',), ('D1', 'condenser', 'x'), ('D1', 1), D1.ins[0]): + with pytest.raises(TypeError, match='keys must be units'): + resolve({key: 1.}) + # stale auxiliary units: biosteam.HXutilities rebuilds its exchangers at + # every simulation, and a key holding an old one names the new one + bst.main_flowsheet.set_flowsheet('contributions_stale') + bst.settings.set_thermo(['Water'], cache=True) + water = bst.Stream('water', Water=100., T=300., P=5e6, units='kmol/hr') + U = bst.HXutilities('U', ins=water, T=500., rigorous=False) + U.simulate() + old = U.heat_exchangers[0] + U.simulate() + stale_hus = bst.process_tools.heat_exchanger_utilities_from_units([U]) + assert len(stale_hus) > 1 + assert all(hu.unit is not old for hu in stale_hus) + assert hxn_synthesis._stream_contributions(stale_hus, {old: 2., 'U': 3.}) \ + == [2.] + [3.] * (len(stale_hus) - 1) + +def test_contribution_keys_in_the_facility(): + # the facility, simulated twice: every key resolves to the hand values + D1, D1_H1, D1_H2, F1 = quickstart('contributions_keys_facility') + HXN = HeatExchangerNetwork('HXN', T_min_app=5., approach_contributions={ + 'D1': 1., ('D1', 'condenser'): 1.5, F1: 1., 'D1_H1': 4., + D1.reboiler: 2.}) + sys = bst.System.from_units('contributions_keys_facility', + units=[D1, D1_H1, D1_H2, F1, HXN]) + hand = {'condenser': 1.5, 'reboiler': 2., 'heat_exchanger': 1., + 'D1_H1': 4., 'D1_H2': 2.5} + of_unit = lambda unit: hand[unit.ID] + for _ in range(2): + simulate_strictly(sys) + info = HXN.synthesis_info + assert info['status'] == 'mer' # observed + assert info['approach_contributions'].tolist() == [ + hand[hx.ID] for hx in HXN.original_heat_exchangers] + assert info['min_approach_margin'] >= -APPROACH_TOL + assert len({round(hx.dT, 6) for hx in HXN.new_HXs}) > 1 + for hx in HXN.new_HXs: + r = required(HXN, hx, of_unit) + assert abs(hx.dT - (r - 1e-6)) <= 1e-12, hx.ID + # ternary streams (internal_approach takes binaries at most) + assert sampled_approach(hx) >= r - APPROACH_TOL, hx.ID + def test_invalid_contributions(monkeypatch): streams = streams_from(LINNHOFF4) N = len(streams) @@ -480,3 +797,102 @@ def no_copy(*args, **kwargs): synthesize_network(hus, 0., approach_contributions=[1., 1., 1., 1.]) with pytest.raises(AssertionError, match='copied'): # valid: flashes next synthesize_network(hus, 10., approach_contributions=[1., 2., None, 4.]) + # mappings and callables: keys and values are checked before any flash + for x, error, match in [ + ({'nope': 1., 'C1': 2.}, ValueError, "name no unit: 'nope'"), + ({hus[0]: 1.}, TypeError, 'a heat utility cannot be'), + ({'C1': 2., ('H1', 'H1'): -1.}, ValueError, + r"approach contribution of key \('H1', 'H1'\) must be"), + (lambda hu: 0. if hu.unit.ID == 'C2' else None, ValueError, + r"approach contribution of unit \('C2', 'C2'\) must be"), + ({'C1': 2., units[0]: 3.}, ValueError, 'different values')]: + with pytest.raises(error, match=match): + synthesize_network(hus, 10., approach_contributions=x) + with pytest.raises(ValueError, match='T_min_app must be finite and > 0'): + synthesize_network(hus, 0., approach_contributions={'C1': 2.}) + # a zero-duty heat utility of hus may be keyed (it is then dropped) + idle_in = bst.Stream('idle_in', Fluid=1000., T=400., units='kmol/hr') + idle = bst.HXutility('idle', ins=idle_in, T=400., rigorous=False) + idle.simulate() + assert idle.heat_utilities[0].duty == 0. + for x in ({idle: 3., 'C1': 2.}, lambda hu: 3.): + with pytest.raises(AssertionError, match='copied'): + synthesize_network(hus + idle.heat_utilities, 10., + approach_contributions=x) + +def test_invalid_contributions_in_the_facility(): + # bad keys and values raise at simulate, even when their unit is idle; + # valid keys of idle or ignored units have no effect + units = cp_units('contributions_invalid_facility', R002_UNITS, 300.) + idle_in = bst.Stream('idle_in', Fluid=1000., T=400., units='kmol/hr') + idle = bst.HXutility('idle', ins=idle_in, T=400., rigorous=False) + HXN = HeatExchangerNetwork('HXN', T_min_app=10., ignored=[units[0]]) + sys = bst.System.from_units('contributions_invalid_facility', + units=[*units, idle, HXN]) + simulate_strictly(sys) + reference = network_record(HXN) + for x, error, match in [ + ({'nope': 1.}, ValueError, 'name no unit'), + ([1.] * 3, TypeError, 'must be a mapping or a callable'), + (np.ones(3), TypeError, 'must be a mapping or a callable'), + ({units[1].heat_utilities[0]: 1.}, TypeError, + 'a heat utility cannot be'), + ({idle: -1.}, ValueError, 'approach contribution of key'), + ({idle: 2., 'idle': 3.}, ValueError, 'different values'), + ({units[0]: 2., ('C1', 'C1'): None}, ValueError, + 'different values')]: + HXN.approach_contributions = x + with pytest.raises(error, match=match): + sys.simulate() + for x in ({units[0]: 3., idle: 2.}, {idle: 2., 'idle': 2., 'C2': NAN}): + HXN.approach_contributions = x + simulate_strictly(sys) + assert network_record(HXN) == reference + assert HXN._resolved_contributions is None + +# --------------------------------------------------------------------------- +# E. Cache +# --------------------------------------------------------------------------- + +def test_cache_reuses_unless_contributions_change(): + # a cached network is reused while the explicit contributions resolve + # to the same values per stream; T_min_app is not part of the key + units = cp_units('contributions_cache', KEMP, 300.) + C = {units[0]: 2., 'H2': 6.} + sys, HXN = simulate_HXN(units, 10., 'contributions_cache', + cache_network=True, approach_contributions=C) + by_ID = lambda: dict(zip([hx.ID for hx in HXN.original_heat_exchangers], + HXN._resolved_contributions)) + assert by_ID() == {'C1': 2., 'H2': 6., 'C3': None, 'H4': None} + first = HXN.HXN_sys + + def simulate(reused): + network = HXN.HXN_sys + simulate_strictly(sys) + assert (HXN.HXN_sys is network) == reused + assert HXN._synthesis_options == (False,) + return HXN.HXN_sys + + simulate(True) + C['H2'] = 3. + second = simulate(False) + assert second is not first + assert by_ID() == {'C1': 2., 'H2': 3., 'C3': None, 'H4': None} + hand = {'C1': 2., 'H2': 3., 'C3': 5., 'H4': 5.} + for hx in HXN.new_HXs: + r = required(HXN, hx, lambda unit: hand[unit.ID]) + assert abs(hx.dT - (r - 1e-6)) <= 1e-12, hx.ID + # observed: C1-H2 5 K, C1-H4 7 K, C3-H2 8 K + assert {round(hx.dT, 6) for hx in HXN.new_HXs} == {4.999999, 6.999999, + 7.999999} + HXN.approach_contributions = lambda hu: {'C1': 2., 'H2': 3.}.get(hu.unit.ID) + simulate(True) + HXN.T_min_app = 8. + simulate(True) + HXN.approach_contributions = None + simulate(False) + assert HXN._resolved_contributions is None + HXN.approach_contributions = {} + simulate(True) + HXN.T_min_app = 9. + simulate(True) From a3351f53c85905858e70e215c8f4271238e824c9 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Fri, 16 Oct 2026 21:43:16 +0000 Subject: [PATCH 03/52] document per-stream approach temperature contributions Document the approach_contributions option of the two previous commits. - README: one sentence after the stream-splitting sentence. - concepts: a section "Approach temperature contributions" between the problem table and the targets. It covers the convention and the shifts, the value domain and why it is > 0, and the bit-for-bit default. It says why the planner and splitting guarantees carry over (one shifted scale; a branch keeps its parent's shift), and what is per pair: the exact check, the repair, HXprocess dT and min_approach_margin. It also covers the per-stream pinch temperatures, utilities following BioSTEAM's rule, the facility keys (levels, robust ID keys, no heat-utility keys, the skip rule after validation, a pure callable), the cache rule, and additive requirements only. - concepts, elsewhere: the wording that every match keeps T_min_app now says the pair's required approach. The cache paragraph, min_approach_margin, a validation paragraph on tests/test_hxn_contributions.py, and the limitation now titled "Additive approach temperatures only". - API page: an approach_contributions row; T_min_app as the default pair requirement; the cache_network, synthesis_info and pinch_Ts rows. - Tutorial chapter 4: a section on the quickstart system, with 'D1' and F1 at 1 K and the coolers at the default 2.5 K. Its numbers come from the new capture ch04_contributions.txt and are compared with the 5 K and 2 K networks of the sweep. Also the intro, a forward reference, "Other options" and the cache paragraph. - Contributing: an entry for the new test module. The _demo_src outputs table lists the new capture. The demo block follows the spec, plus one line that prints the network's heating and cooling loads, which the comparison with the sweep needs. Validation: - Only ch04_configuring.py was run. ch04_sweep.txt, ch04_ignored.txt, ch04_splitting.txt and ch04_ten_streams.txt are byte-identical, and ch04_contributions.txt is the only new asset. The two re-rendered chapter 4 PNGs differed by byte noise only (same data, different renderer) and were restored. - SPHINX_OFFLINE=1 sphinx-build -W -b html docs/source /html (sphinx 9.1.0 from docs/requirements.txt, --system-site-packages venv): build succeeded, 0 warnings. - Full suite: 996 passed in 242 s (-n 4), as on the parent. Documentation only; no library or test code changed. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- README.md | 6 +- docs/_demo_src/README.md | 2 +- docs/_demo_src/examples/ch04_configuring.py | 30 ++- docs/source/API/heat_exchanger_network.rst | 17 +- docs/source/_generated/ch04_contributions.txt | 7 + docs/source/concepts.rst | 188 ++++++++++++++++-- docs/source/contributing/contributing.rst | 7 + docs/source/tutorial/04_configuring.rst | 73 ++++++- 8 files changed, 298 insertions(+), 32 deletions(-) create mode 100644 docs/source/_generated/ch04_contributions.txt diff --git a/README.md b/README.md index fcff57f..10d8f23 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,11 @@ split, the network is a best-effort one close to the targets. With `HeatExchangerNetwork(..., stream_splitting=True)`, streams are instead split into parallel branches there (BioSTEAM splitters and rigorous mixers), and the network reaches the MER targets (not guaranteed with `avoid_recycle`; -see the documentation for the limits with real thermodynamics). +see the documentation for the limits with real thermodynamics). Each stream +can also carry its own approach temperature contribution +(`HeatExchangerNetwork(..., approach_contributions={unit: dT})`): a match +must then keep the sum of its two streams' contributions instead of +`T_min_app`. ```python import biosteam as bst # hensmith units plug into BioSTEAM systems diff --git a/docs/_demo_src/README.md b/docs/_demo_src/README.md index 0674c02..3afd13c 100644 --- a/docs/_demo_src/README.md +++ b/docs/_demo_src/README.md @@ -25,7 +25,7 @@ directories themselves. | `examples/ch01_quickstart.py` | `_static/images/examples/tutorial_01_quickstart_flowsheet_light.png`, `…_flowsheet_dark.png`, `_static/images/examples/tutorial_01_quickstart_pinch_diagram.png`; `_generated/ch01_results.txt`, `ch01_loads.txt`, `ch01_life_cycles.txt`, `ch01_summary.txt` | | `examples/ch02_pinch_analysis.py` | `_static/images/examples/tutorial_02_composite_curves.png`, `tutorial_02_grand_composite.png`; `_generated/ch02_threshold.txt`, `ch02_table.txt`, `ch02_compare.txt` | | `examples/ch03_network_anatomy.py` | `_static/images/examples/tutorial_03_hxn_flowsheet_light.png`, `…_hxn_flowsheet_dark.png`, `tutorial_03_pinch_diagram_minimal.png`; `_generated/ch03_flowsheet.txt`, `ch03_life_cycles.txt`, `ch03_stage.txt`, `ch03_pinch_Ts.txt`, `ch03_accounting.txt` | -| `examples/ch04_configuring.py` | `_static/images/examples/tutorial_04_T_min_app_sweep.png`, `tutorial_04_ten_streams_pinch_diagram.png`; `_generated/ch04_sweep.txt`, `ch04_splitting.txt`, `ch04_ignored.txt`, `ch04_ten_streams.txt` | +| `examples/ch04_configuring.py` | `_static/images/examples/tutorial_04_T_min_app_sweep.png`, `tutorial_04_ten_streams_pinch_diagram.png`; `_generated/ch04_sweep.txt`, `ch04_splitting.txt`, `ch04_contributions.txt`, `ch04_ignored.txt`, `ch04_ten_streams.txt` | | `make_hero_gif.py` | `_static/images/demo/hero_light.gif`, `hero_dark.gif` (8 s loop, 20 fps, 2000 × 720), `hero_light_still.png`, `hero_dark_still.png` (the frame-0 stills served under `prefers-reduced-motion`) | | `build_demo.py` | `_static/quickstart_demo.html` — the interactive quickstart demo, filled in from `quickstart_demo_template.html` | | `make_poster.py` | `_static/images/examples/quickstart_demo_poster.png` — the README poster that links to the demo (2400 × 1260) | diff --git a/docs/_demo_src/examples/ch04_configuring.py b/docs/_demo_src/examples/ch04_configuring.py index dd2a18b..fea4643 100644 --- a/docs/_demo_src/examples/ch04_configuring.py +++ b/docs/_demo_src/examples/ch04_configuring.py @@ -9,7 +9,9 @@ """Tutorial chapter 04 (docs/source/tutorial/04_configuring.rst): configuring a HeatExchangerNetwork -- sweeping the minimum approach temperature to expose the utility/capital trade-off, splitting streams where the targets need it -(``stream_splitting=True``), scoping the analysis with ``ignored=``, and +(``stream_splitting=True``), giving streams their own approach temperature +contributions (``approach_contributions=``), scoping the analysis with +``ignored=``, and synthesizing a larger ten-stream system (the ten-stream case of the regression suite, inlined here rather than imported from ``tests``). Regions between ``# [start:x]`` / ``# [end:x]`` are literalinclude'd by the page; everything @@ -128,6 +130,32 @@ def main(): HXN.stream_splitting = False HXN.T_min_app = 5. # [end:splitting] + with capturing('ch04_contributions'): + # [start:contributions] + HXN.approach_contributions = { + 'D1': 1., # the column's condenser and reboiler: phase change + F1: 1., # the flash's heater boils its feed + } # the two product coolers keep T_min_app / 2 = 2.5 K + sys.simulate() + info = HXN.synthesis_info + print(f"status: {info['status']}, {len(HXN.new_HXs)} process exchangers, " + f"{HXN.installed_costs['Heat exchangers']:.4g} USD added installed cost") + print(f'heating {HXN.actual_heat_util_load:.4g} kJ/hr, ' + f'cooling {HXN.actual_cool_util_load:.4g} kJ/hr') + contributions = info['approach_contributions'] # by stream, as used + print('contributions by stream [K]:', contributions) + required = {} # each match: its two streams' sum + for c, life_cycle in zip(contributions, HXN.stream_life_cycles): + for stage in life_cycle.life_cycle: + if stage.unit in HXN.new_HXs: + required[stage.unit] = required.get(stage.unit, 0.) + c + for hx in HXN.new_HXs: + print(f'{hx.ID:<10} requires {required[hx]:.1f} K, Q = {hx.Q:.4g} kJ/hr') + print(f"smallest approach {info['min_approach']:.3f} K, " + f"margin over the requirement {info['min_approach_margin']:.2g} K") + HXN.approach_contributions = None + # [end:contributions] + assert info['min_approach_margin'] >= -1e-6 - 1e-9 with capturing('ch04_ten_streams'): # [start:ten_streams] bst.settings.set_thermo(['Water', 'Ethanol'], cache=True) diff --git a/docs/source/API/heat_exchanger_network.rst b/docs/source/API/heat_exchanger_network.rst index 8804657..02367bc 100644 --- a/docs/source/API/heat_exchanger_network.rst +++ b/docs/source/API/heat_exchanger_network.rst @@ -24,9 +24,11 @@ method. Constructor options ------------------- -In addition to ``ID``, ``T_min_app`` and ``units`` documented above, the -constructor accepts the following options; :doc:`../tutorial/04_configuring` -shows what each of them changes. +In addition to ``ID``, ``T_min_app`` (the default pair requirement: each +stream contributes ``T_min_app / 2`` unless given its own contribution with +``approach_contributions``) and ``units`` documented above, the constructor +accepts the following options; :doc:`../tutorial/04_configuring` shows what +each of them changes. .. list-table:: :header-rows: 1 @@ -46,7 +48,7 @@ shows what each of them changes. - 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 and ``stream_splitting`` are 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 (the splitters keep their branch fractions), 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. + - Reuse the network configuration of the previous simulation when the set of units contributing heat utilities, ``stream_splitting`` and the explicit approach contributions (resolved per stream) are 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 (the splitters keep their branch fractions), 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. @@ -62,6 +64,9 @@ shows what each of them changes. * - ``stream_splitting`` - bool - Allow a process stream to be split into parallel branches that re-join. A side of the pinch that no unsplit network serves at MER is planned with splits and reaches the targets exactly on the planner's knots; sides that an unsplit network serves are never split, so a problem that needs no split gets the same network as with the default. Each split is a chain of ``Splitter`` units and a rigorous ``Mixer`` (adiabatic, no cost), listed in ``new_splitters`` and ``new_mixers``. With ``avoid_recycle``, a split that would repeat a stream pair is not used, and MER is then not guaranteed. Stored as an attribute of the same name, and part of the ``cache_network`` key. Defaults to False. + * - ``approach_contributions`` + - Mapping or callable + - Approach temperature contribution of each process stream, in K: a match between hot stream *h* and cold stream *c* must keep :math:`c_h + c_c` instead of ``T_min_app`` everywhere inside it, and a stream without a contribution contributes ``T_min_app / 2``; the utility exchangers keep BioSTEAM's own approach rule. A mapping's keys name units -- a unit, the ID of a unit without an owner (``'D1'``), or ``(owner.ID, unit.ID)`` for an auxiliary unit (``('D1', 'condenser')``) -- and a heat utility takes the value of the key nearest to its unit along the unit's chain of owners (two keys of the same level must agree); ID keys stay valid when a unit rebuilds its auxiliaries, and heat utility keys are rejected (``TypeError``). A callable ``f(hu)`` is called once per heat utility at every simulation and should be pure; a sequence raises a ``TypeError``. A value is None or nan (unspecified, an explicit default at its level) or a finite number > 0. Keys and values are checked against every available unit and its auxiliaries before any filtering, so an unknown key, a bad value or a conflict raises a ``ValueError`` even while its unit is idle, but a valid key whose unit holds no heat utility in a simulation has no effect. The resolved values are part of the ``cache_network`` key. May be changed between simulations. Defaults to None (every stream contributes ``T_min_app / 2``, so every pair requires ``T_min_app``). Class attributes ---------------- @@ -123,7 +128,7 @@ the cached network. - 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 :func:`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. With ``stream_splitting``, also ``'stream_splitting'``, ``'splits'`` (the realized splits, :class:`~hensmith.hxn_synthesis.StreamSplit`), ``'split_deviations'`` and, per side, the split candidate chosen (``'split'``). Kept from the synthesis that produced a cached network. + - The synthesis report (see the ``info`` keyword of :func:`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, the smallest approach inside any process exchanger (``'min_approach'``) and the smallest margin of an exchanger's approach over its pair's required approach (``'min_approach_margin'``, no less than -1e-6 K, the exact check's rounding guard), and each stream's approach contribution as used (``'approach_contributions'``, ``T_min_app / 2`` where none was given, in stream order). With ``stream_splitting``, also ``'stream_splitting'``, ``'splits'`` (the realized splits, :class:`~hensmith.hxn_synthesis.StreamSplit`), ``'split_deviations'`` and, per side, the split candidate chosen (``'split'``). 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``; a split stream's life cycle also lists its splits and marks each branch stage with its branch and flow fraction. @@ -159,7 +164,7 @@ the cached network. - The flowsheet ``_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). + - Per-stream pinch temperature (informational): the shifted pinch plus the stream's shift (``T_min_app`` for a hot stream and 0 for a cold one by default; ``T_min_app / 2 + c_j`` for a hot and ``T_min_app / 2 - c_j`` for a cold stream with approach contributions) 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. diff --git a/docs/source/_generated/ch04_contributions.txt b/docs/source/_generated/ch04_contributions.txt new file mode 100644 index 0000000..b5eb048 --- /dev/null +++ b/docs/source/_generated/ch04_contributions.txt @@ -0,0 +1,7 @@ +status: mer, 3 process exchangers, 1.449e+06 USD added installed cost +heating 2.967e+08 kJ/hr, cooling 1.014e+06 kJ/hr +contributions by stream [K]: [1. 1. 2.5 1. 2.5] +HX_1_2_hs requires 3.5 K, Q = 4.303e+07 kJ/hr +HX_0_4_hs requires 3.5 K, Q = 3.338e+04 kJ/hr +HX_0_3_hs requires 2.0 K, Q = 1.794e+07 kJ/hr +smallest approach 3.500 K, margin over the requirement -1.3e-12 K diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 0d24b7a..85368c4 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -33,7 +33,8 @@ used to shift hot streams in the problem table and required between the streams everywhere inside every synthesized exchanger: - in :func:`~hensmith.problem_table`, where every hot stream's temperature is - shifted *down* by ``T_min_app`` before the streams are compared, so that two + shifted *down* by ``T_min_app`` (with approach contributions, every stream + by its own shift; see below) before the streams are compared, so that two streams which meet on the shifted scale are really ``T_min_app`` apart; - in the planner of :func:`~hensmith.synthesize_network`, which admits a match only if the two streams stay at least ``T_min_app`` apart at every @@ -41,8 +42,8 @@ streams everywhere inside every synthesized exchanger: not only at its two ends; and - on every synthesized process exchanger, whose approach is verified on the exact states of its two streams, again along its whole length. The - exchanger itself is a ``biosteam.HXprocess`` constructed with - ``dT=T_min_app - 1e-6``, a guard against rounding only. + exchanger itself is a ``biosteam.HXprocess`` constructed with ``dT`` equal + to its pair's required approach minus 1e-6, a guard against rounding only. Lowering ``T_min_app`` lowers the utility targets and raises exchanger area; raising it does the reverse. It is the parameter in hensmith that sets the @@ -76,7 +77,8 @@ outlet, for instance); what a non-equilibrium end state departs from equilibrium at its own end temperature becomes a flat there. **The shifted grid.** Hot streams are shifted down by ``T_min_app``; cold -streams are not. The grid ``Ts`` is the sorted (descending) union of the +streams are not (with approach contributions, every stream by its own shift; +see below). The grid ``Ts`` is the sorted (descending) union of the shifted breakpoints of all curves, temperatures closer than 1e-9 K being one grid point. Every temperature at which any stream's curve bends or jumps is therefore a grid point, and between two consecutive grid points every stream @@ -174,6 +176,128 @@ demand of the whole set of streams. That identity is the table's own consistency check -- whatever the algorithm does with the cascade, it can neither invent nor destroy energy. +Approach temperature contributions +---------------------------------- + +A single ``T_min_app`` holds every match to the same driving-force floor, +whether its streams condense and boil with high film coefficients or are +gases with low ones. Each stream :math:`j` may therefore carry its own +*approach temperature contribution* :math:`c_j`, in K, as described by Kemp +(2007): a process match between hot stream :math:`h` and cold stream :math:`c` +must keep :math:`c_h + c_c` everywhere inside it, on the exact states, and a +stream without a contribution contributes ``T_min_app / 2``, so that without +contributions every pair requires ``T_min_app`` exactly as before. A small +contribution suits a condensing or boiling stream, a large one a gas. +:func:`~hensmith.problem_table` takes the contributions as a sequence aligned +with its streams, :func:`~hensmith.synthesize_network` as a sequence aligned +with its heat utilities, a mapping or a callable, and +:class:`~hensmith.HeatExchangerNetwork` as a mapping or a callable (see +below). + +**The shift.** Writing :math:`\Delta T_{min}` for ``T_min_app``, every stream +is shifted by its own amount, :math:`T^* = T - \mathrm{shift}_j`, with + +.. math:: + + \mathrm{shift}_h = \tfrac{1}{2} \Delta T_{min} + c_h \quad \text{(hot)}, + \qquad + \mathrm{shift}_c = \tfrac{1}{2} \Delta T_{min} - c_c \quad \text{(cold)}, + +so that + +.. math:: + + T^*_h \ge T^*_c \iff T_h - T_c \ge \mathrm{shift}_h - \mathrm{shift}_c + = c_h + c_c. + +Two streams that meet on the shifted scale are therefore exactly their pair's +required approach apart, and the problem table above is built on that scale +with no other change. A contribution is None or nan (unspecified) or a finite +number greater than zero. Zero is excluded so that every pair's requirement +stays positive: a match allowed to touch would have no driving force at its +pinch and need an infinite area, and its ``HXprocess`` guard, the +requirement less 1e-6 K, would turn negative. Whenever a contribution is +given, ``T_min_app`` must be finite and positive as well, since it sets the +default ``T_min_app / 2``; without contributions ``T_min_app = 0`` is +accepted as before. + +**The default is unchanged, bit for bit.** An unspecified stream gets +exactly the default shifts, ``T_min_app`` if hot and 0 if cold +(:math:`x/2 + x/2 = x` and :math:`x/2 - x/2 = 0` hold exactly in floating +point), and with nothing specified the code takes the old expression +literally. A run without contributions, with every contribution unspecified +or with every one equal to ``T_min_app / 2`` gives the same targets and the +same network, number for number. + +**One shifted scale for the planner.** The planner's search sees one curve +per stream on the shifted scale, not the shifts themselves, and on that scale +a match is feasible exactly when its real streams keep their pair's +requirement. The MER targets, the pinch design rules, the remaining problem +analysis and the guarantee of `Stream splitting`_ below therefore carry over +unchanged; a branch is its parent's curve with every heat scaled by its +fraction, so it keeps its parent's shift. + +**Per pair on the real scale.** What works on real temperatures uses the +pair's own value. A pair's required approach is +:math:`\mathrm{shift}_h - \mathrm{shift}_c` -- equal to :math:`c_h + c_c` up +to round-off, and to ``T_min_app`` exactly without contributions. The exact +approach verification and the repair described below enforce it, each +``HXprocess`` gets it less 1e-6 K as its ``dT``, and +``synthesis_info['min_approach_margin']`` reports the smallest margin of any +exchanger's exact approach over its own pair's requirement (no less than +-1e-6 K, the check's rounding guard), next to ``'min_approach'``, the +smallest approach itself. Wherever this page says that a match keeps +``T_min_app``, read its pair's required approach. + +**Per-stream pinch temperatures.** The pinch of the problem table, and the +``'pinch_T'`` of ``synthesis_info['plan_targets']``, stay on the shifted +scale. Stream :math:`j`'s own pinch temperature is that pinch plus +:math:`\mathrm{shift}_j` -- ``T_min_app`` above it for a hot stream and at +it for a cold one by default -- and that is what the facility's ``pinch_Ts`` +reports. The real composite curves no longer keep one fixed distance: they +can come as close as the smallest hot plus the smallest cold contribution, +and only the shifted curves carry the pinch. + +**Utilities are not affected.** The contributions apply to the process +matches only: each stream's utility exchanger gets its agent from BioSTEAM's +own rule (``HeatUtility.dT`` plus the agent's ``dT``), whatever the stream's +contribution. + +**Keys in the facility.** :class:`~hensmith.HeatExchangerNetwork` takes a +mapping or a callable, since the order of its streams is internal. A mapping's +keys name units: a unit object (matched by identity, or by its owner's ID +and its own), the ID of a unit without an owner (``'D1'``), or +``(owner.ID, unit.ID)`` for an auxiliary unit (``('D1', 'condenser')``; a +bare auxiliary ID such as ``'condenser'`` names nothing). A heat utility +takes the value of +the key nearest to its unit along the unit's chain of owners, so ``'D1'`` +covers a column's condenser and reboiler and ``('D1', 'condenser')`` +overrides it for the condenser. A None or nan value is an explicit default +that wins at its level like any value, and two keys of the same level for a +unit must agree. ID keys are the robust form: a unit that rebuilds its +auxiliary units at every simulation (``biosteam.HXutilities``, for one) +leaves a key holding an old auxiliary object to be matched by its owner's ID +and its own. Heat utilities cannot be keys, because BioSTEAM replaces them +at every simulation; a value per heat utility is given by a callable +``f(hu)`` instead, which is called once per heat utility at every simulation +and should therefore be pure. The keys and values are checked against every +unit available to the network and its auxiliary units before any heat +utility is filtered out: a key that names none of them, a bad value or two +conflicting keys raise a ``ValueError`` even while their unit is idle, but a +valid key whose unit holds no heat utility in a simulation (no duty, +``ignored``, ``hxn_ok=False``) has no effect, so that a Monte Carlo sample in +which a unit falls idle does not fail. + +**The cache.** With ``cache_network=True``, a cached network is reused only +while the explicit contributions resolve to the same value for every stream +(see *Reusing a network* below). Without explicit contributions the reuse +test is the one described there, so changing ``T_min_app`` alone still keeps +a cached network. + +**Additive requirements only.** A requirement that belongs to a pair but is +not the sum of two stream terms cannot be written as one shift per stream, +so it has no problem table and is not supported. + Targets: MER, pinch, composite and grand composite curves --------------------------------------------------------- @@ -190,7 +314,9 @@ overlap horizontally, heat can pass from hot to cold: that overlap is the heat recovered by process exchange. The overhang of the cold curve at its warm end is the hot utility, and the overhang of the hot curve at its cold end is the cold utility. The place where the curves come closest vertically is the pinch, -and by construction they approach no closer than ``T_min_app``. +and by construction they approach no closer than ``T_min_app`` (with approach +contributions, no closer than the smallest hot plus the smallest cold +contribution; the shifted curves carry the pinch). .. figure:: /_static/images/examples/tutorial_02_composite_curves.png :class: white-bg @@ -537,7 +663,8 @@ own utilities. If no process match was made at all, the facility reports zero capital and no utilities. **Reusing a network.** With ``cache_network=True`` a later simulation checks -whether the set of units behind the heat utilities is unchanged; if it is, the +whether the set of units behind the heat utilities, and the explicit approach +contributions they resolve to, are unchanged; if they are, the same network configuration is reused instead of being synthesized again. Each life cycle's first inlet is copied from the current stream, and each process exchanger keeps, as the enthalpy limit of its *must* stream (the hot stream @@ -588,8 +715,10 @@ checked the same way, against the quenched outlet enthalpy and temperature. **The approach inside every exchanger.** The synthesis verifies every process exchanger on the exact states of its streams (see *Exact approach verification* above) and records the smallest approach it found in -``synthesis_info['min_approach']``; the tests re-check it with temperatures -computed independently of the code under test. +``synthesis_info['min_approach']``, and the smallest margin of an +exchanger's approach over its pair's required approach in +``synthesis_info['min_approach_margin']``; the tests re-check both with +temperatures computed independently of the code under test. **MER identification and achievement.** ``tests/test_hxn_mer.py`` synthesizes a corpus of 78 problems through the public facility. For 40 of them -- 24 @@ -619,8 +748,8 @@ systems of increasing complexity -- all with phase-changing streams from the third on -- and for each one requires that the synthesized network (i) closes its energy balance without raising ``RuntimeWarning``, (ii) never uses less hot or cold utility than the MER targets computed on the same streams, and -reports ``'mer'`` exactly when it reaches them, (iii) keeps ``T_min_app`` -inside every process exchanger on exact states, (iv) is planned on the +reports ``'mer'`` exactly when it reaches them, (iii) keeps each pair's +required approach inside every process exchanger on exact states, (iv) is planned on the problem table's own cascade, and (v) recovers at least as much heat as a load recorded in the test file. A network that improves leaves slack in (v); those recorded numbers are lowered deliberately by a maintainer, never raised to @@ -628,6 +757,27 @@ make a failing test pass. Every case is synthesized again with ``stream_splitting=True`` and held to the same checks, and the three cases that need stream splits for MER must then reach their targets. +**Approach contributions.** ``tests/test_hxn_contributions.py`` checks the +contributions against references that do not use them. Without +contributions -- and with every contribution unspecified or equal to +``T_min_app / 2`` -- plans, problem tables and facility networks must equal +the default ones bit for bit, compared in-process. Every stream at +:math:`x` must plan the network of ``T_min_app`` :math:`= 2x`, and on +real-thermodynamics cases all hot streams at :math:`a` and all cold ones at +:math:`b` must give the default targets at ``T_min_app`` :math:`= a + b`. +With mixed contributions, the targets of 40 random problems, of the 24 +constant heat capacity no-split problems of the MER corpus and of five split +ones (which must reach MER with ``stream_splitting=True``) must equal a +closed-form constant heat capacity cascade with the classic symmetric shifts, +and every planned match must keep its pair's requirement on recomputed +temperatures. The facility, with and without splits, on constant heat +capacity and real-thermodynamics cases, must reach the contribution-aware +targets, keep each pair's requirement on exact states recomputed +independently and give each exchanger its pair's ``dT``; the exact check and +the repair must act on each pair's own requirement. The key rules, the +validation of keys and values (for idle units too, and before any stream is +flashed) and the ``cache_network`` rule are tested as well. + **Doctests.** The examples in the docstrings are executed as part of the test suite, so the numbers printed in the API reference are numbers the code currently produces. @@ -640,9 +790,10 @@ What the synthesized network is guaranteed to be: - **Never better than MER.** Its utilities are never below the targets of the problem table, and ``'mer'`` is reported only if the realized network reaches them. -- **Feasible everywhere inside.** Every process exchanger keeps - ``T_min_app - 1e-6`` K on the exact stream states at its ends and at every - checked position inside it, and the heat balance closes on every stream. +- **Feasible everywhere inside.** Every process exchanger keeps its pair's + required approach (``T_min_app``, or ``c_h + c_c``) minus 1e-6 K on the + exact stream states at its ends and at every checked position inside it, + and the heat balance closes on every stream. - **Deterministic.** The search is budgeted in work units, not seconds, so the same streams give the same network however fast the machine is. @@ -685,11 +836,12 @@ What it is not: utilities flagged as not usable for HXN integration (``hxn_ok=False``) or with non-positive flow, and streams excluded with ``ignored``, are removed from the analysis entirely. -- **One approach temperature for everything.** A single ``T_min_app`` shifts - the problem table and constrains every synthesized exchanger. There is no - per-stream or per-match approach temperature, so a match whose exchange is - cheap and one whose exchange is expensive are held to the same driving-force - floor. +- **Additive approach temperatures only.** Per-stream approach temperature + contributions are supported, so a match's requirement is ``T_min_app`` or + the sum of its two streams' contributions; a requirement that belongs to a + pair and is not such a sum is not (see + `Approach temperature contributions`_). The utility exchangers use + BioSTEAM's approach rule whatever a stream's contribution. References ---------- diff --git a/docs/source/contributing/contributing.rst b/docs/source/contributing/contributing.rst index 7fb1201..14bac38 100644 --- a/docs/source/contributing/contributing.rst +++ b/docs/source/contributing/contributing.rst @@ -82,6 +82,13 @@ Tests live in ``tests/``: system is synthesized again with ``stream_splitting=True``, and the three that need stream splits must then reach their targets. +``tests/test_hxn_contributions.py`` + Approach temperature contributions: default bit-for-bit identity + in-process, uniform contributions against a doubled ``T_min_app`` and + against uniform real-thermodynamics runs, closed-form targets and + per-pair exact approaches on corpus and real-thermodynamics cases, key + resolution and validation, and the ``cache_network`` rule. + Development environment ----------------------- diff --git a/docs/source/tutorial/04_configuring.rst b/docs/source/tutorial/04_configuring.rst index caa26d8..22c3249 100644 --- a/docs/source/tutorial/04_configuring.rst +++ b/docs/source/tutorial/04_configuring.rst @@ -6,7 +6,9 @@ temperature of 5 K, every unit of the system in scope, and one small five-stream flowsheet. This chapter varies all three. It sweeps ``T_min_app`` over the quickstart system to expose the trade-off between recovered heat and added area, lets hensmith split streams where the targets need it -(``stream_splitting=True``), narrows the analysis with ``ignored=``, goes +(``stream_splitting=True``), gives some streams their own approach +temperature contributions (``approach_contributions=``), narrows the +analysis with ``ignored=``, goes through the remaining constructor options of :class:`~hensmith.HeatExchangerNetwork` one by one, and finishes by synthesizing a ten-stream network with ten process exchangers. @@ -80,7 +82,9 @@ every breakpoint of both streams' temperature-enthalpy curves, and verified on their exact states. A smaller approach therefore admits more matches and lets each one transfer more heat, but the exchangers that do so work across a smaller temperature difference and need more area for the same duty -- which -is what the right-hand panel below prices. +is what the right-hand panel below prices. ``T_min_app`` holds every match to +the same floor; `Approach temperature contributions`_ below relaxes it for +the streams that need less. The cost does not fall monotonically, though: it rises from 6.004e+05 USD at 10 K to 6.684e+05 USD at 15 K, and the last three columns say why. Up to 10 K @@ -167,6 +171,61 @@ splits serves is planned exactly as without the option, so the MER networks of the sweep at 2, 5 and 10 K would come out the same. :doc:`../concepts` describes the method and its guarantee. +Approach temperature contributions +---------------------------------- + +``T_min_app`` is one number for every match, but the film coefficients behind +it are not: a condensing or boiling stream transfers heat far more readily +than a liquid that is only cooled, and can work across a smaller temperature +difference for the same area. ``approach_contributions`` gives streams their +own approach temperature contribution, in K. A match between a hot and a cold +stream must then keep the sum of their two contributions instead of +``T_min_app``, everywhere inside it, and a stream without a contribution +contributes ``T_min_app / 2``, so that two such streams still need +``T_min_app``. Like ``stream_splitting`` it is a plain attribute of the +facility. Its keys name units: ``'D1'`` covers both auxiliary exchangers of +the column, its condenser and its reboiler, and ``F1``, the flash itself, +covers the auxiliary heater of its feed. + +.. literalinclude:: /../_demo_src/examples/ch04_configuring.py + :language: python + :start-after: # [start:contributions] + :end-before: # [end:contributions] + :dedent: + +.. literalinclude:: /_generated/ch04_contributions.txt + :language: text + +``synthesis_info['approach_contributions']`` lists the contributions as used, +in the stream order of ``original_heat_exchangers``: the reboiler (stream 0), +the flash's heater (1) and the condenser (3) contribute 1 K each, and the two +product coolers (2 and 4) the default 2.5 K. Each match requires the sum over +its two streams, read off the stream life cycles: 3.5 K for ``HX_1_2_hs`` and +``HX_0_4_hs``, which each pair a cooler with a phase-changing stream, and +2.0 K for ``HX_0_3_hs``, which pairs the condenser with the reboiler. Each of +these exchangers is a ``HXprocess`` whose ``dT`` is its own requirement less +1e-6 K. + +The network reaches its targets -- status ``mer`` -- with three process +exchangers. Compared with the 5 K network of the sweep (four exchangers, +2.977e+08 kJ/hr of heating, 1.936e+06 kJ/hr of cooling and 1.112e+06 USD of +added installed cost), it uses 2.967e+08 kJ/hr of heating and 1.014e+06 kJ/hr +of cooling, for 1.449e+06 USD. All three numbers lie between those of the +5 K network and of the 2 K one (2.957e+08 and 9.219e+04 kJ/hr, 2.02e+06 USD): +more heat is recovered than at 5 K, at the price of more area, but every +match with a cooler still needs 3.5 K rather than the 2 K that the sweep's +first point allows every match. The smallest approach inside any of the +three exchangers is 3.500 K, so the 2.0 K match stays well clear of its +requirement; the margin over the requirement, -1.3e-12 K, says that a 3.5 K +match is held at exactly its requirement, to round-off (the exact check +allows 1e-6 K below it). Setting ``approach_contributions`` back to ``None`` +restores the default on the next simulation. :doc:`../concepts` gives the +shifted scale that makes the targets exact for any contributions, and the +full key rules: +``(owner.ID, unit.ID)`` keys for a single auxiliary unit, the nearest key +along a unit's owners winning, and keys that are checked even while their +unit is idle. + Scoping the analysis -------------------- @@ -218,10 +277,12 @@ Other options The remaining keyword arguments of :class:`~hensmith.HeatExchangerNetwork` are listed in the :doc:`../API/api` reference; what each of them does to the synthesis is described below (``stream_splitting`` in `Splitting streams`_ +and ``approach_contributions`` in `Approach temperature contributions`_ above). Five of them -- ``Qmin``, ``force_ideal_thermo``, ``avoid_recycle``, ``sort_hus_by_T`` and ``stream_splitting`` -- are passed straight through to :func:`~hensmith.synthesize_network`, which can also be called directly on a -list of heat utilities. +list of heat utilities; ``approach_contributions`` is first resolved by the +facility to one value per heat utility (keys naming idle units ignored). ``Qmin`` is a duty floor, in kJ/hr, defaulting to 1e-3. A planned exchanger whose duty is below it (``Q < Qmin``) is dropped from the plan and its duty @@ -238,8 +299,10 @@ stream that finishes on process heat alone carries no utility symbol. simulations, which is worth doing when the same system is simulated many times with slightly different inputs, as in a Monte Carlo or a sensitivity analysis. When it is on and a network has already been synthesized, the units owning the -current heat utilities are compared with the exchangers behind the cached one; -if the sets are identical the cached network is kept and only the stream states +current heat utilities are compared with the exchangers behind the cached one, +and the explicit approach contributions, resolved to one value per stream, +with those the cached network was synthesized with; if both match, the cached +network is kept and only the stream states and the exchanger specifications are updated. Each life cycle's first inlet is copied from the current stream. Each process exchanger keeps, as the enthalpy limit of the stream its side of the pinch must serve completely (the hot From 2659e8c36a162dbe079f9fb158431d4afd204f2e Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Fri, 16 Oct 2026 21:46:39 +0000 Subject: [PATCH 04/52] add the targeting module: composite curves and utility targets hensmith.targeting tells what any network of a set of process streams can achieve before (or instead of) synthesizing one. This is commit A (F2 core) of the targeting spec; area, units, capital and the supertargeting sweep follow in B, the facility method and the network report in C, the docs in D. - PinchTargets holds the problem table of the streams. from_heat_utilities prepares them exactly as synthesize_network does, so Q_hot and Q_cold equal the synthesis's targets bit for bit; it takes approach contributions in every form synthesize_network takes, resolved with F1's helpers on the heat utilities as given. The constructor takes streams as problem_table does (contributions as a per-stream sequence). at() re-targets at another T_min_app on the same curves (no flash); unspecified contributions follow T_min_app/2, specified ones stay or, with scale_contributions, scale with T_min_app. - composite_curves (real or shifted) are built on the table's own grid knots by a vectorized composite builder, so they reproduce the table to rounding; grand_composite_curve gives the arriving and leaving heat at every grid temperature. - utility_targets places biosteam's agents on the grand composite curve: one level per agent from biosteam's own limit (agent.T -+ agent.dT -+ HeatUtility.dT, the exact rule without contributions, conservative with them), greedy allocation in list order on the constraint positions of the curve, the remainder to the first fuel agent or unserved with a RuntimeWarning. Each agent is priced as one biosteam heat utility over the stretch it serves (its level down to the next zero of the balanced curve), with fresh utility streams in a temporary flowsheet: costs are deterministic and the registry and the agents' stream dumps are untouched. - balanced_composite_curves and balanced_grand_composite_curve add the utilities (latent, sensible, fuel as a furnace at infinite T). - plot_composite_curves (real, shifted, balanced) and plot_grand_composite_curve (with the utility staircase) follow plot_pinch_diagram, with stable gids. - hxn_synthesis: _arrange_heat_utilities and _process_streams are factored out of _pinch_analysis and shared with targeting, so the two preparations cannot drift apart. No behavior change. - hensmith exports PinchTargets, plot_composite_curves and plot_grand_composite_curve (9 names). Validation: tests/test_targeting.py (15 tests, about 8 s serial): KEMP composites in closed form; consistency with the table on four real-thermo cases; contributions against the independent classic cascade, levels recomputed from the agents' attributes; reference A's allocation (17.189/32.811/27.628/12.372 kW), served stretches and balanced knots; list order, biosteam's dT, unserved remainders; KEMP's single LP against the facility's MP/chilled-water network; the furnace case (natural gas 16.009 kW, flue 508.991 K) and pricing isolation; gids of both plots. Three new doctests; hensmith.targeting joins the docstring citation test. Bit-identity oracle dcd15864... and the four golden digests are unchanged. Full suite: 1014 passed (996 + 18) in 245 s with -n 4. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- hensmith/__init__.py | 3 + hensmith/hxn_synthesis.py | 52 +- hensmith/targeting.py | 1186 +++++++++++++++++++++++++++++++++++++ tests/test_hxn.py | 4 +- tests/test_targeting.py | 499 ++++++++++++++++ 5 files changed, 1725 insertions(+), 19 deletions(-) create mode 100644 hensmith/targeting.py create mode 100644 tests/test_targeting.py diff --git a/hensmith/__init__.py b/hensmith/__init__.py index b8729c2..af417e3 100644 --- a/hensmith/__init__.py +++ b/hensmith/__init__.py @@ -23,13 +23,16 @@ from ._heat_exchanger_network import * from .hxn_synthesis import * +from .targeting import * from . import _heat_exchanger_network from . import hxn_synthesis +from . import targeting __all__ = ( *_heat_exchanger_network.__all__, *hxn_synthesis.__all__, + *targeting.__all__, ) # %% Register with biosteam diff --git a/hensmith/hxn_synthesis.py b/hensmith/hxn_synthesis.py index 83146ae..7e6266a 100644 --- a/hensmith/hxn_synthesis.py +++ b/hensmith/hxn_synthesis.py @@ -904,6 +904,37 @@ def _explicit_contributions(hus, approach_contributions, T_min_app, _approach_contributions(len(values), T_min_app, values) # checks T_min_app return values +def _arrange_heat_utilities(hus, sort_hus_by_T): + """ + The heat utilities of `hus` in stream order, ``(hus_rearranged, + N_heating)``: the heating ones (``hu.duty > 0``, cold streams), then the + cooling ones; zero-duty ones are dropped. With `sort_hus_by_T`, heating + utilities are sorted by descending and cooling ones by ascending inlet + temperature of their unit. + """ + hus_heating = [hu for hu in hus if hu.duty > 0] + hus_cooling = [hu for hu in hus if hu.duty < 0] + if sort_hus_by_T: + hus_heating.sort(key=lambda i: i.unit.ins[0].T, reverse=True) + hus_cooling.sort(key=lambda i: i.unit.ins[0].T) + return hus_heating + hus_cooling, len(hus_heating) + +def _process_streams(hxs, force_ideal_thermo): + """ + The process streams of the exchangers `hxs`, ``(streams_inlet, + streams_quenched)``: unregistered copies (see `hensmith._curves._copy`) + of each inlet and of each outlet re-flashed at its own enthalpy and + pressure, with ideal thermodynamics if `force_ideal_thermo`. + """ + if force_ideal_thermo: + streams_inlet = [_copy(hx.ins[0], hx.ins[0].thermo.ideal()) for hx in hxs] + streams_quenched = [_copy(hx.outs[0], hx.outs[0].thermo.ideal()) for hx in hxs] + else: + streams_inlet = [_copy(hx.ins[0]) for hx in hxs] + streams_quenched = [_copy(hx.outs[0]) for hx in hxs] + for i in streams_quenched: i.vle(H=i.H, P=i.P) + return streams_inlet, streams_quenched + def _pinch_analysis(hus, T_min_app=10, force_ideal_thermo=False, sort_hus_by_T=False, approach_contributions=None): """ @@ -916,28 +947,15 @@ def _pinch_analysis(hus, T_min_app=10, force_ideal_thermo=False, first, before any stream is copied or flashed. """ values = _explicit_contributions(hus, approach_contributions, T_min_app) - hx_utils = hus - hus_heating = [hu for hu in hx_utils if hu.duty > 0] - hus_cooling = [hu for hu in hx_utils if hu.duty < 0] - if sort_hus_by_T: - hus_heating.sort(key=lambda i: i.unit.ins[0].T, reverse=True) - hus_cooling.sort(key=lambda i: i.unit.ins[0].T) - hx_utils_rearranged = hus_heating + hus_cooling + hx_utils_rearranged, N_heating = _arrange_heat_utilities(hus, sort_hus_by_T) hxs = [hu.unit for hu in hx_utils_rearranged] - # unregistered copies (see `hensmith._curves._copy`); the inlets are - # registered under their own IDs below - if force_ideal_thermo: - streams_inlet = [_copy(hx.ins[0], hx.ins[0].thermo.ideal()) for hx in hxs] - streams_quenched = [_copy(hx.outs[0], hx.outs[0].thermo.ideal()) for hx in hxs] - else: - streams_inlet = [_copy(hx.ins[0]) for hx in hxs] - streams_quenched = [_copy(hx.outs[0]) for hx in hxs] - for i in streams_quenched: i.vle(H=i.H, P=i.P) + # unregistered copies; the inlets are registered under their own IDs + # below + streams_inlet, streams_quenched = _process_streams(hxs, force_ideal_thermo) for i in range(len(streams_inlet)): stream = streams_inlet[i] ID = 'Util_%s'%i stream.ID = 's_%s__%s'%(i,ID) - N_heating = len(hus_heating) cold_indices = list(range(N_heating)) hot_indices = list(range(N_heating, len(hxs))) indices = cold_indices + hot_indices diff --git a/hensmith/targeting.py b/hensmith/targeting.py new file mode 100644 index 0000000..50631c2 --- /dev/null +++ b/hensmith/targeting.py @@ -0,0 +1,1186 @@ +# -*- coding: utf-8 -*- +# hensmith: Heat Exchanger Network Synthesis, Modeling, Integration, +# Thermodynamics, and Heuristics +# Copyright (C) 2020-, Sarang Bhagwat +# +# This module is under the UIUC open-source license. See +# github.com/BioSTEAMDevelopmentGroup/hensmith/blob/master/LICENSE.txt +# for license details. +""" +Targeting: what any heat exchanger network of a set of process streams can +achieve, before (or instead of) synthesizing one. `PinchTargets` holds the +problem table of the streams and derives from it the composite and grand +composite curves, the multiple-utility targets priced by biosteam +(`PinchTargets.utility_targets`) and the balanced composite curves; +`plot_composite_curves` and `plot_grand_composite_curve` draw them. +""" +import math +from collections import namedtuple +from collections.abc import Sequence +from contextlib import contextmanager +from warnings import warn +import numpy as np +import biosteam as bst +from ._curves import _T_EQ +from ._planner import _approach_contributions, _contribution_value +from .hxn_synthesis import (_problem_table, _pinch_cut, _format_H, + _arrange_heat_utilities, _process_streams, _explicit_contributions) + +__all__ = ('PinchTargets', 'plot_composite_curves', + 'plot_grand_composite_curve') + +#: Kinds of the constraint positions of the grand composite curve (see +#: `_positions`): the heat arriving at a grid temperature (before its point +#: loads), the heat leaving it (after them), and the heat just above and +#: just below a utility level strictly between two grid temperatures. +_ARRIVING, _LEAVING, _ABOVE, _BELOW = 0, 1, 2, 3 + +UtilityTarget = namedtuple('UtilityTarget', ( + 'ID', 'agent', 'heating', 'level', 'duty', 'utility_duty', 'flow', 'cost', + 'T_in', 'T_out', 'T_utility_in', 'T_utility_out')) + +UtilityTarget.__doc__ = """ +One utility of `PinchTargets.utility_targets`: an agent placed on the grand +composite curve and priced by biosteam as a single heat utility over the +temperatures it serves. + +Unlike biosteam's signed `HeatUtility.duty`, `duty` is a magnitude: whether +the utility heats or cools the process is the `heating` flag. + +Attributes +---------- +ID : str or None + ID of the agent; None for a remainder that no agent can serve. +agent : UtilityAgent or None + The agent; None for an unserved remainder. +heating : bool + True for a heating utility (heat given to cold streams). +level : float + Shifted temperature level of the utility on the grand composite curve + [K]; +inf for fuel and for unserved heating, -inf for unserved cooling. +duty : float + Process-side heat [kJ/hr] (a magnitude). +utility_duty : float + Utility-side heat, ``duty`` divided by the agent's heat transfer + efficiency [kJ/hr], from biosteam; nan when unserved. +flow : float + Flow rate of the agent [kmol/hr], from biosteam; nan when unserved. +cost : float + Cost of the utility [USD/hr], from biosteam; nan when unserved. +T_in, T_out : float + Process temperatures at which the utility was priced [K]: the inlet and + outlet of the stretch of the process it serves. +T_utility_in, T_utility_out : float + Inlet and outlet temperature of the agent [K], from the priced + `HeatUtility`; nan when unserved. + +""" + +def _stream_knots(grid, j, shifted): + """ + The knots of stream j on the table's grid, ascending in temperature, as + ``(T, Hl, Hr)``: real temperatures (shifted ones if `shifted`), and the + left (low-enthalpy) and right (high-enthalpy) enthalpy at each, measured + from the stream's cold end. + """ + k_hi, k_lo = grid['k_hi'][j], grid['k_lo'][j] + sl = slice(k_lo, k_hi - 1 if k_hi > 0 else None, -1) + T = grid['Ts'][sl] if shifted else grid['Ts'][sl] + grid['shift'][j] + H0 = grid['Hl'][j, k_lo] + return T, grid['Hl'][j][sl] - H0, grid['Hr'][j][sl] - H0 + +def _member_values(member, R): + """ + Left and right enthalpy of a composite member ``(T, Hl, Hr)`` at each + temperature of `R`: its knot values at a knot (within `_T_EQ`), else its + chord from ``Hr[i]`` at ``T[i]`` to ``Hl[i+1]`` at ``T[i+1]``; 0 below its + range and its total above it. + """ + T, Hl, Hr = member + n = T.size + il = np.searchsorted(T, R - _T_EQ, 'left') + ir = np.searchsorted(T, R + _T_EQ, 'right') + if n > 1: + i1 = np.clip(il, 1, n - 1) + i0 = i1 - 1 + x0 = T[i0] + y0 = Hr[i0] + chord = y0 + (Hl[i1] - y0) * (R - x0) / (T[i1] - x0) + outside = np.where(il == 0, Hl[0], np.where(il >= n, Hr[-1], chord)) + else: + outside = np.where(il == 0, Hl[0], Hr[-1]) + match = ir > il + hl = np.where(match, Hl[np.minimum(il, n - 1)], outside) + hr = np.where(match, Hr[np.maximum(ir - 1, 0)], outside) + return hl, hr + +def _composite(members, inv_h=None): + """ + Composite of `members` (each ``(T, Hl, Hr)``, ascending T, enthalpies + from 0, see `_stream_knots`), as segments ``(Ha, Hb, Ta, Tb, w)`` in + ascending temperature: slopes from ``(Ha, Ta)`` to ``(Hb, Tb)`` and flats + (``Ta == Tb``) of positive heat. Each member is linear in H within a + segment (on a flat in proportion to its own flat), so a stretch [x, y] of + a segment carries ``sum(q_m * inv_h[m]) = (y - x) * w`` (`inv_h`: one + weight per member, 1 by default). + """ + if not members: + empty = np.empty(0) + return empty, empty, empty, empty, empty + inv_h = np.ones(len(members)) if inv_h is None else np.asarray(inv_h, float) + values = np.unique(np.concatenate([m[0] for m in members])) + # temperatures closer than _T_EQ are one knot, represented by the + # highest member of the cluster (as in `_problem_table`) + cluster = np.concatenate([[0], np.cumsum(np.diff(values) > _T_EQ)]) + R = values[np.flatnonzero(np.append(np.diff(cluster) > 0, True))] + n = R.size + hl = np.empty((len(members), n)) + hr = np.empty((len(members), n)) + for m, member in enumerate(members): + hl[m], hr[m] = _member_values(member, R) + Cl = hl.sum(axis=0) + Cr = hr.sum(axis=0) + # interleaved: the flat at R[0], the slope to R[1], the flat at R[1], ... + Ha = np.empty(2 * n - 1) + Hb = np.empty(2 * n - 1) + Ta = np.empty(2 * n - 1) + Tb = np.empty(2 * n - 1) + d = np.empty((len(members), 2 * n - 1)) + Ha[0::2], Hb[0::2], Ta[0::2], Tb[0::2] = Cl, Cr, R, R + d[:, 0::2] = hr - hl + Ha[1::2], Hb[1::2], Ta[1::2], Tb[1::2] = Cr[:-1], Cl[1:], R[:-1], R[1:] + d[:, 1::2] = hl[:, 1:] - hr[:, :-1] + keep = Hb > Ha + Ha, Hb, Ta, Tb = Ha[keep], Hb[keep], Ta[keep], Tb[keep] + w = (inv_h @ d[:, keep]) / (Hb - Ha) + return Ha, Hb, Ta, Tb, w + +def _curve(segments): + """The ``(T, H)`` polyline through `segments` (see `_composite`).""" + Ha, Hb, Ta, Tb, _ = segments + if not Ha.size: return np.empty(0), np.empty(0) + start = np.ones(Ha.size, dtype=bool) + start[1:] = (Ha[1:] != Hb[:-1]) | (Ta[1:] != Tb[:-1]) + keep = np.column_stack([start, np.ones(Ha.size, dtype=bool)]).ravel() + T = np.column_stack([Ta, Tb]).ravel()[keep] + H = np.column_stack([Ha, Hb]).ravel()[keep] + return T, H + +def _snap(Ts, level): + """`level`, or the first grid temperature within `_T_EQ` of it.""" + k = np.flatnonzero(np.abs(Ts - level) <= _T_EQ) + return float(Ts[k[0]]) if k.size else level + +def _positions(Ts, arr, lea, levels): + """ + The constraint positions of the grand composite curve (`arr`, `lea`: + the heat arriving at and leaving each grid temperature `Ts`) for the + utility `levels`, top to bottom, as arrays ``(T, G, kind, k)``: per grid + index k the arriving and then the leaving heat, and per level l strictly + between ``Ts[k]`` and ``Ts[k+1]`` (in descending l, after the leaving + heat at ``Ts[k]``) the heat just above and just below l, both the linear + value of the curve at l (`_ABOVE`, `_BELOW`). + """ + n = Ts.size + T = np.repeat(Ts, 2) + G = np.column_stack([arr, lea]).ravel() + kind = np.tile([_ARRIVING, _LEAVING], n) + k = np.repeat(np.arange(n), 2) + inner = sorted({l for l in levels if Ts[-1] < l < Ts[0] + and not np.any(Ts == l)}, reverse=True) + if inner: + l = np.array(inner) + ki = np.searchsorted(-Ts, -l, 'right') - 1 # Ts[ki] > l > Ts[ki + 1] + Gl = lea[ki] + (arr[ki + 1] - lea[ki]) * (Ts[ki] - l) / (Ts[ki] - Ts[ki + 1]) + index = np.repeat(2 * (ki + 1), 2) + T = np.insert(T, index, np.repeat(l, 2)) + G = np.insert(G, index, np.repeat(Gl, 2)) + kind = np.insert(kind, index, np.tile([_ABOVE, _BELOW], l.size)) + k = np.insert(k, index, np.repeat(ki, 2)) + return T, G, kind, k + +def _gap(positions, level): + """ + Index of the first position below the gap in which a utility at the + (snapped) `level` acts: 0 above the grid, the number of positions below + it, the leaving heat at a grid temperature, else the position just below + the level. + """ + T, _, kind, _ = positions + if level > T[0]: return 0 + if level < T[-1]: return T.size + i = np.flatnonzero((T == level) & ((kind == _LEAVING) | (kind == _BELOW))) + return int(i[0]) + +def _balance(positions, heating, cooling): + """ + The balanced heat flow at every position: the curve minus the heating + utilities ``(level, q)`` acting below each position and the cooling ones + acting above it. + """ + B = positions[1].copy() + for level, q in heating: B[:_gap(positions, level)] -= q + for level, q in cooling: B[_gap(positions, level):] -= q + return B + +@contextmanager +def _isolated_pricing(agents): + """ + Price heat utilities of `agents` without side effects: each agent's + `utility_stream_dump` is replaced by a new empty list (so every pricing + gets fresh utility streams, and the costs do not depend on what was + priced before) and new streams are registered in a temporary flowsheet; + the original dumps are restored on exit. + """ + dumps = {id(a): (a, a.utility_stream_dump) for a in agents} + try: + for a, _ in dumps.values(): a.utility_stream_dump = [] + with bst.Flowsheet('_hensmith_targets'): + yield + finally: + for a, dump in dumps.values(): a.utility_stream_dump = dump + +def _unit_label(unit): + """``unit.ID``, or ``' - '`` for an auxiliary unit.""" + return unit.ID if unit.owner is unit else f'{unit.owner.ID} - {unit.ID}' + +def _utility_member(u): + """The composite member ``(T, Hl, Hr)`` of a non-fuel `UtilityTarget`: + isothermal at the agent's temperature for a latent agent, else linear + between the agent's inlet and outlet temperatures.""" + T_a, T_b = sorted((u.T_utility_in, u.T_utility_out)) + if u.agent.T_limit is None or T_b - T_a <= _T_EQ: + T = u.agent.T if u.agent.T_limit is None else T_b + return np.array([T]), np.array([0.]), np.array([u.duty]) + return (np.array([T_a, T_b]), np.array([0., u.duty]), + np.array([0., u.duty])) + + +class PinchTargets: + """ + Pinch analysis targets of a set of process streams: the problem table + (`hensmith.hxn_synthesis.problem_table`) and what follows from it, the + composite and grand composite curves, the targets of several utilities + (`utility_targets`) priced by biosteam, and the balanced composite + curves. Build it from heat utilities with `from_heat_utilities` (as + `hensmith.hxn_synthesis.synthesize_network` prepares them) or from + streams as `problem_table` takes them. + + Parameters + ---------- + streams_inlet : list[Stream] + Inlet of each process stream. + streams_quenched : list[Stream] + Corresponding outlets, re-flashed at their enthalpy. + is_hot : Sequence[bool] + True where the stream is cooled. + T_min_app : float, optional + Minimum approach temperature [K]. Defaults to 5. + approach_contributions : Sequence[float or None], optional + Approach temperature contribution of each stream [K], in stream + order, as `problem_table` takes them: None or nan (unspecified: + ``T_min_app / 2``), or a finite number > 0 (`T_min_app` must then be + finite and > 0 too). All unspecified is the same as None. + labels : list[str], optional + Display name of each stream. Defaults to the IDs of `streams_inlet`. + curves : list[StreamCurve], optional + Prebuilt temperature-enthalpy curves of the streams (built here if + not given). + + Attributes + ---------- + T_min_app : float + Minimum approach temperature [K], as given. + approach_contributions : tuple[float or None] or None + Approach temperature contribution of each stream [K] in stream order + (None: ``T_min_app / 2``), or None if no stream has one. + is_hot : numpy.ndarray + (N,) bool, True for hot (cooled) streams. + labels : list[str] + Display name of each stream. + streams_inlet, streams_quenched : list[Stream] + The process streams. + curves : list[StreamCurve] + Temperature-enthalpy curve of each stream (independent of + `T_min_app`). + table : ProblemTable + The problem table. + grid : dict + The table's grid (see `hensmith.hxn_synthesis._problem_table`). + shift : numpy.ndarray + (N,) shift of each stream [K]: its real temperature at shifted + temperature T* is ``T* + shift[j]``. + Q_hot, Q_cold : float + Minimum hot and cold utility [kJ/hr], process side (the table's + `hot_util_load` and `cold_util_load`). + pinch_T : float + Shifted pinch temperature [K]. + cut : str + 'below' or 'above': the side of the pinch to which the point loads + at the pinch temperature belong (see + `hensmith.hxn_synthesis._pinch_cut`). + heat_utilities : list[HeatUtility] or None + The heat utilities the streams were taken from, in stream order; + None when built from streams. + units : list[Unit] or None + The unit of each stream (``hu.unit``), in stream order; None when + built from streams. + force_ideal_thermo : bool + Whether the stream copies use ideal thermodynamics (False when built + from streams). + + Notes + ----- + *Composite curves.* `composite_curves` adds up the streams on the + table's own grid, whose knots hold every breakpoint of every stream, so + the composites reproduce the table to rounding: the cold composite + starts at the cold utility target and ends the hot utility target to + the right of the hot one. On the shifted scale the composites touch at + the pinch (if there is hot utility). On the real scale they are at + least `T_min_app` apart, or, with approach contributions, at least the + smallest hot and the smallest cold contribution apart (unspecified ones + ``T_min_app / 2``); the shifted composites then carry the pinch. + + *Grand composite curve.* `grand_composite_curve` is the heat cascaded + through each shifted grid temperature with the minimum hot utility, both + as it arrives (before the point loads at that temperature) and as it + leaves (after them). It is non-negative and zero at the pinch. + + *Multiple utilities.* `utility_targets` places biosteam's agents on the + grand composite curve, heating and cooling separately. biosteam lets a + heating agent serve real temperatures below ``R = agent.T - agent.dT - + dT`` and a cooling agent real temperatures above ``R = agent.T + + agent.dT + dT`` (``dT = bst.HeatUtility.dT``, read at call time). On the + shifted scale a stream with shift s may take an agent's heat only up to + ``R - s``; the agent's level is the highest single level that serves no + stream above its own limit (lowest for cooling): ``R`` for heating and + ``R - T_min_app`` for cooling without approach contributions, which is + biosteam's rule; with contributions it can be conservative for streams + with slack. Non-fuel agents are placed in list order (biosteam's first + feasible agent: for the default lists the lowest steam and the warmest + coolant first), each as much as the curve above its level (below it, for + cooling) allows given the agents already placed; what remains goes to + the first fuel agent, or is left unserved with a RuntimeWarning (nan + cost). A level equal to a grid temperature serves that temperature's + point loads (biosteam's own rule is strict; they differ only at exact + equality). + + Each agent is priced as one biosteam heat utility over the stretch it + serves, from its level to the next zero of the balanced curve (the + pinch, or a utility pinch), at real temperatures: shifted ones plus the + largest shift of the cold streams there (heating) or the smallest of the + hot streams (cooling), exactly the streams' own temperatures without + approach contributions and an approximation with them. So a sensible + agent's outlet follows the temperatures it actually serves, and biosteam + divides the duty by the agent's heat transfer efficiency. Pricing uses + fresh utility streams in a temporary flowsheet: costs are + deterministic, and the flowsheet's registry and the agents' stream dumps + are untouched. + + *Balanced composite curves.* `balanced_composite_curves` adds the + utilities to the real composites (heating ones to the hot composite, + cooling ones to the cold one), so both end at the same heat: a latent + agent as a flat at its temperature, a sensible one as a line between its + inlet and outlet temperatures, and fuel as a furnace at infinite + temperature. `balanced_grand_composite_curve` is the cascade net of the + utilities, zero at the process pinch and at every utility pinch. + + Examples + -------- + One hot stream cooled 400 -> 300 K and one cold stream heated 330 -> + 440 K, both with a heat capacity flow rate of 1 kW/K (1000 kmol/hr of a + pseudo-component with a molar heat capacity of 3.6 J/mol/K): + + >>> import biosteam as bst, thermosteam as tmo + >>> from hensmith import PinchTargets + >>> fluid = tmo.Chemical('Fluid', search_db=False, phase='l', MW=1., + ... Cn=3.6, default=True) + >>> bst.settings.set_thermo([fluid]) + >>> bst.main_flowsheet.set_flowsheet('targeting_example') + >>> H1 = bst.HXutility('H1', ins=bst.Stream('H1_in', Fluid=1000., T=400.), + ... T=300., rigorous=False) + >>> C1 = bst.HXutility('C1', ins=bst.Stream('C1_in', Fluid=1000., T=330.), + ... T=440., rigorous=False) + >>> H1.simulate(); C1.simulate() + >>> hus = [H1.heat_utilities[0], C1.heat_utilities[0]] + >>> targets = PinchTargets.from_heat_utilities(hus, T_min_app=10.) + >>> targets + + >>> targets.labels, targets.pinch_T + (['C1', 'H1'], 390.0) + + The composite curves [K, kW], on the knots of the table's grid (340 K + is the cold inlet and 390 K the hot inlet, each shifted to the other + side): the cold one starts at the cold utility target, 40 kW, and ends + 50 kW (the hot utility target) to the right of the hot one. + + >>> hot_T, hot_H, cold_T, cold_H = targets.composite_curves() + >>> hot_T, hot_H / 3600 + (array([300., 340., 400.]), array([ 0., 40., 100.])) + >>> cold_T, cold_H / 3600 + (array([330., 390., 440.]), array([ 40., 100., 150.])) + + The utility targets with biosteam's default agents [kW]: low pressure + steam serves the cold stream up to its level, 407.189 K, and medium + pressure steam the rest; cooling water serves the hot stream down to its + level (302.372 K shifted) and chilled water the rest. + + >>> utilities = targets.utility_targets() + >>> [(u.ID, round(u.duty / 3600, 3)) for u in utilities] + [('low_pressure_steam', 17.189), ('medium_pressure_steam', 32.811), ('cooling_water', 27.628), ('chilled_water', 12.372)] + >>> [(round(u.T_in, 3), round(u.T_out, 3)) for u in utilities] + [(390.0, 407.189), (407.189, 440.0), (340.0, 312.372), (312.372, 300.0)] + >>> round(sum(u.cost for u in utilities), 4) # USD/hr + 1.656 + + The balanced composite curves end at the same heat: + + >>> hot_T, hot_H, cold_T, cold_H = targets.balanced_composite_curves(utilities) + >>> round(hot_H[-1] / 3600, 6), round(cold_H[-1] / 3600, 6) + (150.0, 150.0) + + The same streams at another minimum approach temperature (the curves + are reused): + + >>> targets.at(20.) + + + """ + def __init__(self, streams_inlet, streams_quenched, is_hot, T_min_app=5., + approach_contributions=None, labels=None, curves=None): + N = len(streams_inlet) + if not N: + raise ValueError('PinchTargets needs at least one process stream') + if len(streams_quenched) != N: + raise ValueError(f'{len(streams_quenched)} outlets given for ' + f'{N} inlets') + is_hot = np.asarray(is_hot, dtype=bool) + if is_hot.shape != (N,): + raise ValueError(f'{is_hot.size} is_hot values given for {N} ' + 'streams') + x = approach_contributions + if x is not None: + if (not isinstance(x, (Sequence, np.ndarray)) + or isinstance(x, (str, bytes))): + raise TypeError('approach_contributions must be a sequence ' + 'with one value per stream, not ' + f'{type(x).__name__!r}') + x = [_contribution_value(v, f'stream {j}') for j, v in enumerate(x)] + if all(v is None for v in x): + x = None + else: # checks T_min_app, then the length + _approach_contributions(N, T_min_app, x) + x = tuple(x) + if labels is None: + labels = [s.ID for s in streams_inlet] + elif len(labels) != N: + raise ValueError(f'{len(labels)} labels given for {N} streams') + if x is None: + table, curves, grid = _problem_table( + streams_inlet, streams_quenched, is_hot, T_min_app, curves) + else: + table, curves, grid = _problem_table( + streams_inlet, streams_quenched, is_hot, T_min_app, curves, + approach_contributions=list(x)) + self.T_min_app = T_min_app + self.approach_contributions = x + self.is_hot = is_hot + self.labels = list(labels) + self.streams_inlet = streams_inlet + self.streams_quenched = streams_quenched + self.curves = curves + self.table = table + self.grid = grid + self.shift = grid['shift'] + self.Q_hot = table.hot_util_load + self.Q_cold = table.cold_util_load + self.pinch_T = table.pinch_T + self.cut = _pinch_cut(table) + self.heat_utilities = None + self.units = None + self.force_ideal_thermo = False + #: Grid index of the pinch. + self._k_pinch = int(np.flatnonzero(table.Ts == table.pinch_T)[0]) + #: Threshold of every heat comparison [kJ/hr], the table's own. + self._tol = 1e-9 * float(np.abs( + np.array([c.H_out - c.H_in for c in curves])).sum()) + + @classmethod + def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, + force_ideal_thermo=False, sort_hus_by_T=False): + """ + Targets of the process streams behind heat utilities, prepared + exactly as `hensmith.hxn_synthesis.synthesize_network` prepares them + (so the targets equal the synthesis's, bit for bit). + + Parameters + ---------- + hus : Iterable[HeatUtility] + Heat utilities of heat exchangers (``hu.unit``). Zero-duty ones + are dropped. + T_min_app : float, optional + Minimum approach temperature [K]. Defaults to 5. + approach_contributions : Mapping, Sequence or callable, optional + Approach temperature contributions [K] in any form + `synthesize_network` takes (a sequence is aligned with `hus` as + given, zero-duty heat utilities included). Defaults to None. + force_ideal_thermo : bool, optional + Copy the streams with ideal thermodynamics. Defaults to False. + sort_hus_by_T : bool, optional + Order the streams by inlet temperature, as the synthesis's + option. Defaults to False. + + Returns + ------- + PinchTargets + Streams in stream order (heating utilities, i.e. cold streams, + first), labeled by unit (``' - '`` for an + auxiliary unit). + + """ + hus = list(hus) + values = _explicit_contributions(hus, approach_contributions, T_min_app) + arranged, N_heating = _arrange_heat_utilities(hus, sort_hus_by_T) + if values is None: + contributions = None + else: # in stream order + value_of = dict(zip(map(id, hus), values)) + contributions = [value_of[id(hu)] for hu in arranged] + is_hot = [False] * N_heating + [True] * (len(arranged) - N_heating) + return cls._from_exchangers([hu.unit for hu in arranged], is_hot, + T_min_app, contributions, + force_ideal_thermo, None, arranged) + + @classmethod + def _from_exchangers(cls, hxs, is_hot, T_min_app, approach_contributions, + force_ideal_thermo, labels=None, heat_utilities=None): + """Targets of the process streams of the exchangers `hxs` (stream + order, contributions resolved in stream order), labeled by unit by + default.""" + streams_inlet, streams_quenched = _process_streams(hxs, force_ideal_thermo) + if labels is None: labels = [_unit_label(hx) for hx in hxs] + self = cls(streams_inlet, streams_quenched, is_hot, T_min_app, + approach_contributions, labels) + self.units = list(hxs) + self.heat_utilities = None if heat_utilities is None else list(heat_utilities) + self.force_ideal_thermo = bool(force_ideal_thermo) + return self + + def at(self, T_min_app, scale_contributions=False): + """ + The targets of the same streams at another minimum approach + temperature, reusing their curves (no flash). + + Parameters + ---------- + T_min_app : float + Minimum approach temperature [K]. + scale_contributions : bool, optional + Scale the specified approach contributions by ``T_min_app / + self.T_min_app`` (the contribution scaling of contribution-based + supertargeting, Ahmad, Linnhoff & Smith 1990); by default they + stay as they are. Unspecified ones are ``T_min_app / 2`` either + way. Defaults to False. + + Returns + ------- + PinchTargets + With the same streams, curves, labels, units and heat utilities. + + """ + c = self.approach_contributions + if c is not None and scale_contributions: + _approach_contributions(len(c), T_min_app, c) # checks T_min_app + c = [None if x is None else x * T_min_app / self.T_min_app + for x in c] + new = type(self)(self.streams_inlet, self.streams_quenched, + self.is_hot, T_min_app, c, self.labels, self.curves) + new.heat_utilities = self.heat_utilities + new.units = self.units + new.force_ideal_thermo = self.force_ideal_thermo + return new + + def __repr__(self): + N = self.is_hot.size + return (f'<{type(self).__name__}: {N} stream{"s" if N != 1 else ""}, ' + f'T_min_app = {self.T_min_app:g} K, Q_hot = {self.Q_hot:.3e} ' + f'kJ/hr, Q_cold = {self.Q_cold:.3e} kJ/hr>') + + # %% Curves + + def _members(self, hot, shifted=False): + """The composite members of the hot (or cold) streams.""" + return [_stream_knots(self.grid, j, shifted) + for j in np.flatnonzero(self.is_hot == hot)] + + def composite_curves(self, shifted=False): + """ + The hot and cold composite curves. + + Parameters + ---------- + shifted : bool, optional + Return shifted temperatures (each stream's real temperature + minus its shift; the scale of the problem table) instead of real + ones. Defaults to False. + + Returns + ------- + hot_T, hot_H, cold_T, cold_H : numpy.ndarray + Temperatures [K] and cumulative heat [kJ/hr] of each composite, + ascending; the hot one starts at 0 and the cold one at the cold + utility target. Empty if there is no stream of that kind. + + """ + hot_T, hot_H = _curve(_composite(self._members(True, shifted))) + cold_T, cold_H = _curve(_composite(self._members(False, shifted))) + return hot_T, hot_H, cold_T, cold_H + self.Q_cold + + def _gcc(self): + """The grid temperatures and the heat cascaded with the hot utility + target arriving at and leaving each, ``(Ts, arr, lea)``.""" + table = self.table + leaving = table.residual + table.hot_util_load + arriving = leaving - table.point_H.sum(axis=0) + return table.Ts, arriving, leaving + + def grand_composite_curve(self): + """ + The grand composite curve. + + Returns + ------- + T : numpy.ndarray + Shifted grid temperatures [K], descending, each twice. + H : numpy.ndarray + Heat cascaded with the hot utility target [kJ/hr]: at each grid + temperature the heat arriving (before its point loads) and then + the heat leaving it (after them). It starts at the hot utility + target and ends at the cold one, to rounding, and is zero at the + pinch. + + """ + Ts, arr, lea = self._gcc() + return np.repeat(Ts, 2), np.column_stack([arr, lea]).ravel() + + # %% Utilities + + def _side_heat(self): + """Each stream's heat above and below the pinch [kJ/hr] (all below + for a threshold problem).""" + table = self.table + k = self._k_pinch + interval_H = np.abs(table.interval_H) + point_H = np.abs(table.point_H) + above = interval_H[:, :k].sum(axis=1) + point_H[:, :k].sum(axis=1) + below = interval_H[:, k:].sum(axis=1) + point_H[:, k+1:].sum(axis=1) + if self.cut == 'above': above = above + point_H[:, k] + else: below = below + point_H[:, k] + if self.Q_hot == 0.: above, below = 0. * above, below + above + return above, below + + def _levels(self, agents, heating): + """ + ``[(agent, level)]``: each agent's shifted level, the highest + (lowest for cooling) that serves no stream beyond its own real + limit (see Notes of the class). + """ + dT = bst.HeatUtility.dT + grid = self.grid + Ts, s = grid['Ts'], self.shift + above, below = self._side_heat() + if heating: + streams = [j for j in np.flatnonzero(~self.is_hot) if above[j] > self._tol] + ends = Ts[grid['k_hi']] + else: + streams = [j for j in np.flatnonzero(self.is_hot) if below[j] > self._tol] + ends = Ts[grid['k_lo']] + levels = [] + for agent in agents: + if heating: + R = agent.T - agent.dT - dT + reached = [R - s[j] for j in streams if ends[j] > R - s[j]] + if reached: L = min(reached) + elif streams: L = R - min(s[j] for j in streams) + else: L = R + else: + R = agent.T + agent.dT + dT + reached = [R - s[j] for j in streams if ends[j] < R - s[j]] + if reached: L = max(reached) + elif streams: L = R - max(s[j] for j in streams) + else: L = R - self.T_min_app + levels.append((agent, float(L))) + return levels + + def _allocate(self, levels, Q, heating): + """ + Greedy list-order allocation of `Q` to the agents of `levels` on the + grand composite curve: ``([(level, q, agent)], remaining)``. + """ + Ts, arr, lea = self._gcc() + tol = self._tol + allocated = [] + remaining = Q + for agent, level in levels: + if remaining <= tol: break + level = _snap(Ts, level) + positions = _positions(Ts, arr, lea, + [level] + [l for l, _, _ in allocated]) + done = [(l, q) for l, q, _ in allocated] + if heating: + B = _balance(positions, done, ()) + bound = B[:_gap(positions, level)] + else: + B = _balance(positions, (), done) + bound = B[_gap(positions, level):] + q = min(remaining, bound.min()) if bound.size else remaining + if q > tol: + allocated.append((level, q, agent)) + remaining -= q + return allocated, remaining + + def _offset(self, lo, hi, heating): + """The pricing offset of a utility serving shifted [lo, hi]: the + largest shift of the cold streams overlapping it (heating; 0 if + none) or the smallest of the hot ones (cooling; `T_min_app`).""" + grid = self.grid + Ts = grid['Ts'] + overlap = (Ts[grid['k_hi']] > lo) & (Ts[grid['k_lo']] < hi) + overlap &= ~self.is_hot if heating else self.is_hot + if not overlap.any(): return 0. if heating else self.T_min_app + s = self.shift[overlap] + return float(s.max() if heating else s.min()) + + def utility_targets(self, heating_agents=None, cooling_agents=None): + """ + Multiple-utility targets: the agents placed on the grand composite + curve and priced by biosteam (see Notes of the class). + + Parameters + ---------- + heating_agents : Sequence[UtilityAgent], optional + Heating agents in order of preference; fuel agents (``isfuel``) + take the remainder, the first one only. Defaults to + ``bst.HeatUtility.heating_agents`` (read at call time). + cooling_agents : Sequence[UtilityAgent], optional + Cooling agents in order of preference. Defaults to + ``bst.HeatUtility.cooling_agents``. + + Returns + ------- + list[UtilityTarget] + The heating utilities in allocation order (fuel, or an unserved + remainder, last), then the cooling ones. Agents with no duty are + left out. + + Warns + ----- + RuntimeWarning + If no agent can serve part of the hot or cold utility target. + + """ + if heating_agents is None: heating_agents = bst.HeatUtility.heating_agents + if cooling_agents is None: cooling_agents = bst.HeatUtility.cooling_agents + heating_agents = list(heating_agents) + cooling_agents = list(cooling_agents) + tol = self._tol + heat, remaining = self._allocate( + self._levels([a for a in heating_agents if not a.isfuel], True), + self.Q_hot, True) + if remaining > tol: + fuels = [a for a in heating_agents if a.isfuel] + heat.append((math.inf, remaining, fuels[0] if fuels else None)) + if not fuels: + warn(f'no heating agent can serve {remaining:.6g} kJ/hr of the ' + 'hot utility target; it is left unserved', RuntimeWarning) + cool, remaining = self._allocate( + self._levels(cooling_agents, False), self.Q_cold, False) + if remaining > tol: + cool.append((-math.inf, remaining, None)) + warn(f'no cooling agent can serve {remaining:.6g} kJ/hr of the ' + 'cold utility target; it is left unserved', RuntimeWarning) + # the stretch each agent serves: from its level to the next zero of + # the balanced curve, the pinch or a utility pinch + Ts, arr, lea = self._gcc() + positions = _positions(Ts, arr, lea, + [l for l, _, _ in heat + cool if math.isfinite(l)]) + zero = _balance(positions, [(l, q) for l, q, _ in heat], + [(l, q) for l, q, _ in cool]) <= tol + T = positions[0] + utilities = [] + with _isolated_pricing([a for *_, a in heat + cool if a is not None]): + for level, q, agent in heat: + g = _gap(positions, level) + z = np.flatnonzero(zero[g:]) + lo = float(T[g + z[0]]) if z.size else self.pinch_T + hi = min(level, float(Ts[0])) + o = self._offset(lo, hi, True) + utilities.append(self._utility(agent, True, level, q, + lo + o, hi + o)) + for level, q, agent in cool: + g = _gap(positions, level) + z = np.flatnonzero(zero[:g]) + hi = float(T[z[-1]]) if z.size else self.pinch_T + lo = max(level, float(Ts[-1])) + o = self._offset(lo, hi, False) + utilities.append(self._utility(agent, False, level, q, + hi + o, lo + o)) + return utilities + + @staticmethod + def _utility(agent, heating, level, q, T_in, T_out): + """The `UtilityTarget` of `agent` serving `q` from `T_in` to `T_out` + (process temperatures), priced as one biosteam heat utility (inside + `_isolated_pricing`).""" + if agent is None: + nan = math.nan + return UtilityTarget(None, None, heating, level, q, nan, nan, nan, + T_in, T_out, nan, nan) + hu = bst.HeatUtility() + hu(q if heating else -q, T_in, T_out, agent=agent) + return UtilityTarget(agent.ID, agent, heating, level, q, abs(hu.duty), + abs(hu.flow), hu.cost, T_in, T_out, + hu.inlet_utility_stream.T, + hu.outlet_utility_stream.T) + + # %% Balanced curves + + def _balanced_segments(self, utilities, inv_h=None, inv_h_utilities=None): + """ + The hot and cold segments (`_composite`) of the balanced composite + curves on real temperatures: the streams plus the served utilities, + fuel as a last hot segment at infinite temperature (a weight of 0, + no area). `inv_h` weighs the streams (N) and `inv_h_utilities` the + `utilities` (1 by default). + """ + if inv_h is None: inv_h = np.ones(self.is_hot.size) + if inv_h_utilities is None: inv_h_utilities = [1.] * len(utilities) + sides = [] + for hot in (True, False): + members = self._members(hot) + weights = list(inv_h[self.is_hot == hot]) + fuel = 0. + for u, weight in zip(utilities, inv_h_utilities): + if u.heating != hot or u.agent is None: continue + if u.agent.isfuel: + fuel += u.duty + continue + members.append(_utility_member(u)) + weights.append(weight) + segments = _composite(members, weights) + if fuel: + end = segments[1][-1] if segments[1].size else 0. + segments = tuple(np.append(x, v) for x, v in zip( + segments, (end, end + fuel, math.inf, math.inf, 0.))) + sides.append(segments) + return sides + + def balanced_composite_curves(self, utilities=None): + """ + The balanced composite curves (see Notes of the class). + + Parameters + ---------- + utilities : list[UtilityTarget], optional + Defaults to `utility_targets` with the default agents. + + Returns + ------- + hot_T, hot_H, cold_T, cold_H : numpy.ndarray + Real temperatures [K] and cumulative heat [kJ/hr] of each + composite, ascending from 0; fuel is a last hot stretch at + ``T = inf``. Both end at the same heat unless part of a target is + unserved. + + """ + if utilities is None: utilities = self.utility_targets() + hot, cold = self._balanced_segments(utilities) + return (*_curve(hot), *_curve(cold)) + + def _balanced_positions(self, utilities): + """The constraint positions with the levels of every allocated + finite level of `utilities`, and the balanced heat flow at each.""" + Ts, arr, lea = self._gcc() + positions = _positions(Ts, arr, lea, [u.level for u in utilities + if math.isfinite(u.level)]) + B = _balance(positions, + [(u.level, u.duty) for u in utilities if u.heating], + [(u.level, u.duty) for u in utilities if not u.heating]) + return positions, B + + def balanced_grand_composite_curve(self, utilities=None): + """ + The balanced grand composite curve: the grand composite curve net of + the utilities. + + Parameters + ---------- + utilities : list[UtilityTarget], optional + Defaults to `utility_targets` with the default agents. + + Returns + ------- + T : numpy.ndarray + Shifted temperatures [K], descending: the grid temperatures + (twice each, arriving and leaving heat) and the utility levels + between them (twice each, just above and just below). + B : numpy.ndarray + Heat cascaded net of the utilities [kJ/hr], non-negative: zero at + the pinch and at every utility pinch, at the top the duty of the + fuel and of levels above the grid, at the bottom that of cooling + levels below it. + + """ + if utilities is None: utilities = self.utility_targets() + positions, B = self._balanced_positions(utilities) + return positions[0].copy(), B + + +def plot_composite_curves(targets, kind='real', utilities=None, ax=None, + file=None, dpi=300): + """ + Draw the composite curves of `targets`: real, shifted, or balanced with + the utilities. + + Parameters + ---------- + targets : PinchTargets + kind : str, optional + 'real' (the default) or 'shifted' for `PinchTargets.composite_curves` + with the heat recovered shaded and the utility targets labeled + (shifted ones with the pinch marked), or 'balanced' for + `PinchTargets.balanced_composite_curves` with each utility labeled. + utilities : list[UtilityTarget], optional + Utilities of the balanced curves. Defaults to + `PinchTargets.utility_targets` with the default agents. + 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. Artists carry stable gids: 'Hot + composite' and 'Cold composite' (or 'Hot balanced composite' and 'Cold + balanced composite'), 'Heat recovered', 'Hot utility', 'Cold utility', + 'Pinch', 'Fuel' and 'Utility:'. Fuel, at infinite temperature + on the balanced curves, is labeled rather than drawn. + + Examples + -------- + The streams of `PinchTargets`: + + >>> import biosteam as bst, thermosteam as tmo + >>> from hensmith import PinchTargets, plot_composite_curves + >>> fluid = tmo.Chemical('Fluid', search_db=False, phase='l', MW=1., + ... Cn=3.6, default=True) + >>> bst.settings.set_thermo([fluid]) + >>> bst.main_flowsheet.set_flowsheet('targeting_example') + >>> H1 = bst.HXutility('H1', ins=bst.Stream('H1_in', Fluid=1000., T=400.), + ... T=300., rigorous=False) + >>> C1 = bst.HXutility('C1', ins=bst.Stream('C1_in', Fluid=1000., T=330.), + ... T=440., rigorous=False) + >>> H1.simulate(); C1.simulate() + >>> targets = PinchTargets.from_heat_utilities( + ... [H1.heat_utilities[0], C1.heat_utilities[0]], T_min_app=10.) + >>> fig, ax = plot_composite_curves(targets, kind='shifted') + >>> [line.get_gid() for line in ax.get_lines()] + ['Hot composite', 'Cold composite', 'Pinch'] + >>> import matplotlib.pyplot as plt + >>> plt.close(fig) + + """ + import matplotlib.pyplot as plt + if kind not in ('real', 'shifted', 'balanced'): + raise ValueError("kind must be 'real', 'shifted' or 'balanced', not " + f'{kind!r}') + if ax is None: + fig, ax = plt.subplots(figsize=(7.5, 4.2)) + else: + fig = ax.figure + hot_color, cold_color = '#d62728', '#2e6db4' + text_kwargs = dict(fontsize=8) + if kind == 'balanced': + if utilities is None: utilities = targets.utility_targets() + hot_T, hot_H, cold_T, cold_H = targets.balanced_composite_curves(utilities) + finite = np.isfinite(hot_T) + hot_T, hot_H = hot_T[finite], hot_H[finite] + ax.plot(hot_H, hot_T - 273.15, color=hot_color, lw=2, + label='hot balanced composite', gid='Hot balanced composite') + ax.plot(cold_H, cold_T - 273.15, color=cold_color, lw=2, + label='cold balanced composite', gid='Cold balanced composite') + for u in utilities: + if u.agent is None: continue + label = f'{u.ID} {_format_H(u.duty)} kJ/hr' + if u.agent.isfuel: # at the end of the finite hot curve + x = hot_H[-1] if hot_H.size else 0. + y = (hot_T[-1] if hot_T.size else targets.grid['Ts'][0]) - 273.15 + ax.annotate(f'{u.ID} (furnace) {_format_H(u.duty)} kJ/hr', + xy=(x, y), xytext=(0, 4), textcoords='offset points', + ha='right', va='bottom', color=hot_color, gid='Fuel', + **text_kwargs) + continue + T, H = (hot_T, hot_H) if u.heating else (cold_T, cold_H) + if u.agent.T_limit is None: # beside the agent's flat + T_u = u.agent.T + if u.heating: i = np.searchsorted(T, T_u - 1e-6) + else: i = np.searchsorted(T, T_u + 1e-6) - 1 + x = H[min(max(i, 0), H.size - 1)] + else: + T_u = 0.5 * (u.T_utility_in + u.T_utility_out) + x = np.interp(T_u, T, H) + ax.annotate(label, xy=(x, T_u - 273.15), + xytext=(-6 if u.heating else 6, 0), + textcoords='offset points', + ha='right' if u.heating else 'left', va='center', + color=hot_color if u.heating else cold_color, + gid='Utility:' + u.ID, **text_kwargs) + ax.set_ylabel('T [°C]') + else: + shifted = kind == 'shifted' + hot_T, hot_H, cold_T, cold_H = targets.composite_curves(shifted) + if cold_H.size and hot_H.size and cold_H[0] < hot_H[-1]: + ax.axvspan(cold_H[0], hot_H[-1], color='0.92', lw=0, zorder=0, + label='heat recovered', gid='Heat recovered') + ax.plot(hot_H, hot_T - 273.15, color=hot_color, lw=2, + label='hot composite', gid='Hot composite') + ax.plot(cold_H, cold_T - 273.15, color=cold_color, lw=2, + label='cold composite', gid='Cold composite') + T_all = np.concatenate([hot_T, cold_T]) - 273.15 + H_hot_end = hot_H[-1] if hot_H.size else 0. + H_cold_end = cold_H[-1] if cold_H.size else targets.Q_cold + ax.margins(y=0.12) # headroom for the utility labels + ax.text(0.5 * (H_hot_end + H_cold_end), T_all.max() + 1., + f'hot utility {_format_H(targets.Q_hot)} kJ/hr', ha='center', + va='bottom', gid='Hot utility', **text_kwargs) + ax.text(0.5 * targets.Q_cold, T_all.min() - 1., + f'cold utility {_format_H(targets.Q_cold)} kJ/hr', ha='left', + va='top', gid='Cold utility', **text_kwargs) + if shifted: + # where the composites touch: the cold utility plus the cold + # streams' heat below the pinch + below = targets._side_heat()[1] + x = targets.Q_cold + below[~targets.is_hot].sum() + ax.plot([x], [targets.pinch_T - 273.15], 'o', mfc='w', mec='k', + ms=7, zorder=4, label='pinch', gid='Pinch') + ax.set_ylabel('shifted T [°C]' if shifted else 'T [°C]') + ax.set_xlabel('H [kJ·h$^{-1}$]') + ax.legend(loc='lower right', fontsize=8) + ax.grid(alpha=0.3) + if file: fig.savefig(file, dpi=dpi, bbox_inches='tight') + return fig, ax + +def plot_grand_composite_curve(targets, utilities=None, ax=None, file=None, + dpi=300): + """ + Draw the grand composite curve of `targets` with the utility targets as + a staircase of levels. + + Parameters + ---------- + targets : PinchTargets + utilities : list[UtilityTarget] or False, optional + Utilities to draw. Defaults to `PinchTargets.utility_targets` with + the default agents; False draws the curve only. + 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 + ----- + Shifted temperatures are shown in degC. Heating utilities are drawn by + ascending level from the temperature axis, each a horizontal segment at + its level as long as its duty, joined by dotted verticals; cooling + utilities likewise by descending level. Fuel is drawn dashed at the top + of the grid and an unserved remainder in gray. Artists carry stable + gids: 'GCC', 'Pinch', 'Utility:' (the segments, + 'Utility:unserved' for a remainder) and 'Label:'. + + Examples + -------- + The streams of `PinchTargets`: + + >>> import biosteam as bst, thermosteam as tmo + >>> from hensmith import PinchTargets, plot_grand_composite_curve + >>> fluid = tmo.Chemical('Fluid', search_db=False, phase='l', MW=1., + ... Cn=3.6, default=True) + >>> bst.settings.set_thermo([fluid]) + >>> bst.main_flowsheet.set_flowsheet('targeting_example') + >>> H1 = bst.HXutility('H1', ins=bst.Stream('H1_in', Fluid=1000., T=400.), + ... T=300., rigorous=False) + >>> C1 = bst.HXutility('C1', ins=bst.Stream('C1_in', Fluid=1000., T=330.), + ... T=440., rigorous=False) + >>> H1.simulate(); C1.simulate() + >>> targets = PinchTargets.from_heat_utilities( + ... [H1.heat_utilities[0], C1.heat_utilities[0]], T_min_app=10.) + >>> fig, ax = plot_grand_composite_curve(targets) + >>> sorted(line.get_gid() for line in ax.get_lines() + ... if (line.get_gid() or '').startswith('Utility:')) + ['Utility:chilled_water', 'Utility:cooling_water', 'Utility:low_pressure_steam', 'Utility:medium_pressure_steam'] + >>> import matplotlib.pyplot as plt + >>> plt.close(fig) + + """ + import matplotlib.pyplot as plt + if ax is None: + fig, ax = plt.subplots(figsize=(5, 4.2)) + else: + fig = ax.figure + hot_color, cold_color = '#d62728', '#2e6db4' + T, H = targets.grand_composite_curve() + ax.plot(H, T - 273.15, color='k', lw=2, label='grand composite curve', + gid='GCC') + ax.axvline(0, color='k', ls='--', lw=0.8) + if targets.Q_hot > 0.: + x, y = 0., targets.pinch_T + else: # threshold problem + i = int(np.argmin(H)) + x, y = H[i], T[i] + ax.plot([x], [y - 273.15], 'o', mfc='w', mec='k', ms=7, zorder=4, + gid='Pinch') + if utilities is None: utilities = targets.utility_targets() + if utilities is not False: + Ts = targets.grid['Ts'] + heating = sorted((u for u in utilities if u.heating), + key=lambda u: u.level) + cooling = sorted((u for u in utilities if not u.heating), + key=lambda u: -u.level) + for steps, color, end in ((heating, hot_color, Ts[0]), + (cooling, cold_color, Ts[-1])): + x = 0. + previous = None + for u in steps: + y = (u.level if math.isfinite(u.level) else end) - 273.15 + ID = 'unserved' if u.ID is None else u.ID + if u.agent is None: style = dict(color='0.5', ls='-') + elif u.agent.isfuel: style = dict(color=color, ls='--') + else: style = dict(color=color, ls='-') + if previous is not None: + ax.plot([x, x], [previous, y], color=style['color'], + ls=':', lw=0.8) + ax.plot([x, x + u.duty], [y, y], lw=1.5, gid='Utility:' + ID, + **style) + ax.text(x + u.duty, y, ' ' + ID, ha='left', va='center', + fontsize=7, color=style['color'], gid='Label:' + ID) + x += u.duty + previous = y + ax.set_xlabel('heat cascaded [kJ·h$^{-1}$]') + ax.set_ylabel('shifted T [°C]') + ax.grid(alpha=0.3) + if file: fig.savefig(file, dpi=dpi, bbox_inches='tight') + return fig, ax diff --git a/tests/test_hxn.py b/tests/test_hxn.py index 4344a7f..3eeef03 100644 --- a/tests/test_hxn.py +++ b/tests/test_hxn.py @@ -1270,9 +1270,9 @@ def test_docstring_references_are_cited(): # the documentation build fails on a reference ('.. [label]') that its # docstring never cites ('[label]_') import inspect, re - import hensmith._curves, hensmith._planner + import hensmith._curves, hensmith._planner, hensmith.targeting modules = (hensmith._heat_exchanger_network, hensmith.hxn_synthesis, - hensmith._planner, hensmith._curves) + hensmith._planner, hensmith._curves, hensmith.targeting) def docstrings(obj, seen): if id(obj) in seen: return seen.add(id(obj)) diff --git a/tests/test_targeting.py b/tests/test_targeting.py new file mode 100644 index 0000000..479c75a --- /dev/null +++ b/tests/test_targeting.py @@ -0,0 +1,499 @@ +# -*- coding: utf-8 -*- +# hensmith: Heat Exchanger Network Synthesis, Modeling, Integration, +# Thermodynamics, and Heuristics +# Copyright (C) 2026-, Sarang Bhagwat +# +# This module is under the UIUC open-source license. See +# github.com/BioSTEAMDevelopmentGroup/hensmith/blob/master/LICENSE.txt +# for license details. +""" +Tests of `hensmith.targeting`. + +A. Composite and grand composite curves: closed-form knots of Linnhoff & + Hindmarsh's problem, consistency with the problem table on real-thermo + cases, and approach contributions against an independent cascade. +B. Utility targets: the multiple-utility allocation on the grand composite + curve with biosteam's agents (levels recomputed from the agents' + attributes, list order, biosteam's dT, unserved remainders, served + stretches), priced against an independent isolated pricing, and the + balanced curves. +C. Plots: stable gids whose data equal the curves. + +Reference A: H1 400 -> 300 K and C1 330 -> 440 K, both 1 kW/K, at +T_min_app = 10 K: 50 kW hot and 40 kW cold utility, shifted pinch 390 K. +""" +import inspect +import math +import warnings +import numpy as np +import pytest +import biosteam as bst +from numpy.testing import assert_allclose +from hensmith import PinchTargets, plot_composite_curves, plot_grand_composite_curve +from hensmith.hxn_synthesis import _problem_table, temperature_interval_pinch_analysis +from test_hxn import cp_units, KEMP, simulate_HXN, _gid_artists +from test_hxn_contributions import classic_cascade +import test_hxn_targets + +K = 273.15 +kW = 3600. # kJ/hr +#: Reference A (temperatures in K, CP in kW/K). +REF_A = [('H1', 400., 300., 1.), ('C1', 330., 440., 1.)] +#: Contributions of the KEMP reference with approach contributions [K]. +KEMP_C = {'C1': 1., 'C3': 2., 'H2': 6., 'H4': 8.} + +def targets_of(units, T_min_app, **kwargs): + """`PinchTargets` of the units' heat utilities, sorted by duty as the + facility sorts them.""" + hus = sorted([u.heat_utilities[0] for u in units], key=lambda hu: hu.duty) + return PinchTargets.from_heat_utilities(hus, T_min_app, **kwargs) + +def targets(name, rows, T_min_app, **kwargs): + """`targets_of` constant-CP streams given in K.""" + return targets_of(cp_units(name, rows), T_min_app, **kwargs) + +def agent(ID): + return bst.HeatUtility.get_agent(ID) + +def fresh_price(q, T_in, T_out, agent): + """``(cost, utility duty, utility outlet T)`` of a heat utility priced + with its own empty stream dump in its own temporary flowsheet (`q` > 0 + heats the process): an independent copy of the isolated pricing.""" + dump = agent.utility_stream_dump + agent.utility_stream_dump = [] + try: + with bst.Flowsheet('test_targeting_price'): + hu = bst.HeatUtility() + hu(q, T_in, T_out, agent=agent) + return hu.cost, abs(hu.duty), hu.outlet_utility_stream.T + finally: + agent.utility_stream_dump = dump + +def min_vertical_gap(hot_T, hot_H, cold_T, cold_H): + """Smallest T_hot(H) - T_cold(H) over the H range both composite curves + span (from docs/_demo_src/_common.py). Curves are piecewise linear in H, + so the minimum is at a vertex; at a vertical (equal-H) run the hot curve + takes its lower T and the cold curve its higher T (conservative).""" + hot_H, hot_T = np.asarray(hot_H), np.asarray(hot_T) + cold_H, cold_T = np.asarray(cold_H), np.asarray(cold_T) + _, i = np.unique(hot_H, return_index=True) # first = lower T + hH, hT = hot_H[i], hot_T[i] + _, j = np.unique(cold_H[::-1], return_index=True) # last = higher T + j = cold_H.size - 1 - j + cH, cT = cold_H[j], cold_T[j] + lo, hi = max(hH[0], cH[0]), min(hH[-1], cH[-1]) + Hs = np.concatenate([hH, cH]) + Hs = Hs[(Hs >= lo) & (Hs <= hi)] + return float(np.min(np.interp(Hs, hH, hT) - np.interp(Hs, cH, cT))) + +def min_driving_force(hot_T, hot_H, cold_T, cold_H): + """Smallest T_hot - T_cold over every stretch of positive heat between + the breakpoints of two piecewise-linear curves (a vertical run carries + no heat: each stretch is evaluated inside the segments it lies in).""" + end = min(hot_H[-1], cold_H[-1]) + X = np.unique(np.concatenate([hot_H, cold_H])) + X = X[(X >= 0) & (X <= end)] + def T_at(T, H, i, x): + return T[i] + (T[i + 1] - T[i]) * (x - H[i]) / (H[i + 1] - H[i]) + gaps = [] + for x, y in zip(X[:-1], X[1:]): + m = 0.5 * (x + y) + i = np.searchsorted(hot_H, m, 'right') - 1 + j = np.searchsorted(cold_H, m, 'right') - 1 + for z in (x, y): + gaps.append(T_at(hot_T, hot_H, i, z) - T_at(cold_T, cold_H, j, z)) + return min(gaps) + +def points(T, H): + """The polyline's points with repeated ones dropped.""" + keep = np.ones(T.size, dtype=bool) + keep[1:] = (np.diff(T) != 0) | (np.diff(H) != 0) + return np.column_stack([T[keep], H[keep]]) + +def by_ID(utilities): + return {u.ID: u for u in utilities} + +@pytest.fixture(scope='module') +def kemp_hxn(): + """KEMP synthesized by the facility at 10 K.""" + units = cp_units('test_targeting_kemp_hxn', KEMP, K) + sys, HXN = simulate_HXN(units, 10.) + return units, HXN + +# %% Composites and grand composite curve + +def test_composites_closed_form_kemp(): + t = targets_of(cp_units('test_targeting_kemp', KEMP, K), 10.) + hot_T, hot_H, cold_T, cold_H = t.composite_curves() + hot = [(30, 0), (60, 45), (90, 180), (145, 427.5), (150, 450), (170, 510)] + cold = [(20, 60), (50, 120), (80, 180), (135, 510), (140, 530)] + to_K = lambda knots: np.array([(T + K, H * kW) for T, H in knots]) + assert_allclose(points(hot_T, hot_H), to_K(hot), rtol=1e-12) + assert_allclose(points(cold_T, cold_H), to_K(cold), rtol=1e-12) + shifted = t.composite_curves(shifted=True) + assert_allclose(points(*shifted[:2]), to_K(hot) - [10., 0.], rtol=1e-12) + assert_allclose(points(*shifted[2:]), to_K(cold), rtol=1e-12) + T, H = t.grand_composite_curve() + assert T[0] == t.table.Ts[0] and H[0] == pytest.approx(20 * kW, rel=1e-12) + assert H.min() == pytest.approx(0., abs=1e-9 * kW) + assert T[np.argmin(H)] == t.pinch_T == 80. + K + +@pytest.mark.parametrize('name', ['linnhoff', 'two_stream', 'condenser_at_pinch', + 'binary_glide']) +def test_composites_consistent_with_table(name): + units, T_min_app = test_hxn_targets.CASES[name]() + t = targets_of(units, T_min_app) + hus = sorted([u.heat_utilities[0] for u in units], key=lambda hu: hu.duty) + # bit for bit the synthesis's own pinch analysis + analysis = temperature_interval_pinch_analysis(hus, T_min_app) + assert t.Q_hot.hex() == analysis[1].hex() + assert t.Q_cold.hex() == analysis[2].hex() + # and the problem table of the same streams given directly + direct = PinchTargets(t.streams_inlet, t.streams_quenched, t.is_hot, T_min_app) + assert direct.table.residual.tobytes() == t.table.residual.tobytes() + assert direct.labels == [s.ID for s in t.streams_inlet] + assert direct.units is direct.heat_utilities is None + assert t.units == [hu.unit for hu in hus if hu.duty > 0] + [ + hu.unit for hu in hus if hu.duty < 0] + hot_T, hot_H, cold_T, cold_H = t.composite_curves() + assert_allclose(cold_H[-1] - hot_H[-1], t.Q_hot, rtol=1e-9) + assert_allclose(cold_H[0], t.Q_cold, rtol=1e-9) + assert min_vertical_gap(hot_T, hot_H, cold_T, cold_H) >= T_min_app - 1e-6 + if t.Q_hot > 0: + shifted_gap = min_vertical_gap(*t.composite_curves(shifted=True)) + assert abs(shifted_gap) <= 1e-6 + T, H = t.grand_composite_curve() + assert np.all(np.diff(T) <= 0) + assert abs(H.min()) <= t._tol + assert_allclose([H[0], H[-1]], [t.Q_hot, t.Q_cold], rtol=1e-12, atol=t._tol) + +def test_contributions(): + assert 'approach_contributions' in inspect.signature(_problem_table).parameters + units = cp_units('test_targeting_contributions', KEMP, K) + t = targets_of(units, 10.) + N = len(units) + # all unspecified: the default, bit for bit + none = PinchTargets(t.streams_inlet, t.streams_quenched, t.is_hot, 10., + [None] * N, curves=t.curves) + assert none.approach_contributions is None + assert none.table.residual.tobytes() == t.table.residual.tobytes() + assert none.Q_hot.hex() == t.Q_hot.hex() + assert targets_of(units, 10., approach_contributions={}).approach_contributions is None + # KEMP with contributions against the classic cascade (hot T - c, cold T + c) + tc = targets_of(units, 10., approach_contributions=KEMP_C) + labels = tc.labels + assert labels == ['C1', 'C3', 'H2', 'H4'] + rows = {ID: (T_in + K, T_out + K, CP) for ID, T_in, T_out, CP in KEMP} + streams = [dict(T_in=rows[ID][0], T_out=rows[ID][1], CP=rows[ID][2], + kind='hot' if rows[ID][0] > rows[ID][1] else 'cold') + for ID in labels] + c = [KEMP_C[ID] for ID in labels] + Qh, Qc, zeros = classic_cascade(streams, c) + assert_allclose([tc.Q_hot / kW, tc.Q_cold / kW], [Qh, Qc], rtol=1e-9) + assert_allclose([Qh, Qc], [12., 52.], rtol=1e-12) + assert tc.approach_contributions == tuple(c) + assert abs(min_vertical_gap(*tc.composite_curves(shifted=True))) <= 1e-6 + real_gap = min_vertical_gap(*tc.composite_curves()) + assert real_gap >= min(c[2:]) + min(c[:2]) - 1e-9 + assert real_gap == pytest.approx(8.222222, abs=1e-6) + # the levels of section 3.4, recomputed from shifts computed here + hot = np.array([s['kind'] == 'hot' for s in streams]) + shift = np.where(hot, 5. + np.array(c), 5. - np.array(c)) + assert_allclose(tc.shift, shift, rtol=0, atol=0) + pinch = zeros[0] - 5. + assert tc.pinch_T == pytest.approx(pinch, abs=1e-9) + top = np.array([max(s['T_in'], s['T_out']) for s in streams]) - shift + bottom = np.array([min(s['T_in'], s['T_out']) for s in streams]) - shift + dT = bst.HeatUtility.dT + LP, CW = agent('low_pressure_steam'), agent('cooling_water') + R = LP.T - LP.dT - dT + above = [j for j in range(N) if not hot[j] and top[j] > pinch] + reached = [R - shift[j] for j in above if top[j] > R - shift[j]] + L_LP = min(reached) if reached else R - min(shift[j] for j in above) + R = CW.T + CW.dT + dT + below = [j for j in range(N) if hot[j] and bottom[j] < pinch] + reached = [R - shift[j] for j in below if bottom[j] < R - shift[j]] + L_CW = max(reached) if reached else R - max(shift[j] for j in below) + utilities = by_ID(tc.utility_targets()) + assert set(utilities) == {'low_pressure_steam', 'cooling_water'} + assert utilities['low_pressure_steam'].level == L_LP + assert utilities['cooling_water'].level == L_CW + assert_allclose([L_LP, L_CW], [403.189, 299.372], rtol=1e-12) + # every form of synthesize_network: a mapping keyed by unit ID, a + # sequence aligned with the heat utilities as given (a zero-duty one + # included and dropped) and a callable; the per-stream sequence of + # __init__ (stream order) + zero = cp_units('test_targeting_contributions_zero', [('Z', 350., 350., 1.)]) + assert zero[0].heat_utilities[0].duty == 0. + given = sorted(units, key=lambda u: u.heat_utilities[0].duty) + given.insert(2, zero[0]) + hus = [u.heat_utilities[0] for u in given] + forms = [KEMP_C, [KEMP_C.get(u.ID, 5.) for u in given], + lambda hu: KEMP_C.get(hu.unit.ID)] + for form in forms: + other = PinchTargets.from_heat_utilities(hus, 10., form) + assert other.labels == labels + assert other.approach_contributions == tc.approach_contributions + assert other.Q_hot.hex() == tc.Q_hot.hex() + direct = PinchTargets(tc.streams_inlet, tc.streams_quenched, tc.is_hot, 10., + c, curves=tc.curves) + assert direct.Q_hot.hex() == tc.Q_hot.hex() + with pytest.raises(ValueError, match='5 approach contributions given for 4'): + PinchTargets.from_heat_utilities(hus[:4], 10., forms[1]) + with pytest.raises(TypeError, match='a heat utility cannot be'): + PinchTargets.from_heat_utilities(hus, 10., {hus[0]: 1.}) + with pytest.raises(TypeError, match='approach_contributions must be a sequence'): + PinchTargets(tc.streams_inlet, tc.streams_quenched, tc.is_hot, 10., + KEMP_C, curves=tc.curves) + with pytest.raises(ValueError, match='approach contribution of stream 1'): + PinchTargets(tc.streams_inlet, tc.streams_quenched, tc.is_hot, 10., + [1., 0., None, None], curves=tc.curves) + # at: unspecified streams follow T_min_app, specified ones stay or scale + with pytest.raises(ValueError, match='T_min_app must be finite and > 0'): + tc.at(0.) + with pytest.raises(ValueError, match='T_min_app must be finite and > 0'): + tc.at(0., scale_contributions=True) + partial = targets_of(units, 10., approach_contributions={'C1': 2.}) + at20 = partial.at(20.) + assert at20.approach_contributions == (2., None, None, None) + assert_allclose(at20.shift, [8., 0., 20., 20.], rtol=0, atol=0) + assert partial.at(20., scale_contributions=True).approach_contributions == ( + 4., None, None, None) + assert at20.curves is partial.curves and at20.units is partial.units + assert at20.labels == partial.labels + assert t.at(0.).Q_hot < t.Q_hot + assert t.at(10.).Q_hot.hex() == t.Q_hot.hex() + +# %% Utilities + +def test_two_steam_two_coolant_allocation(): + t = targets('test_targeting_ref_A', REF_A, 10.) + assert (t.Q_hot / kW, t.Q_cold / kW, t.pinch_T, t.cut) == ( + pytest.approx(50.), pytest.approx(40.), 390., 'below') + dT = bst.HeatUtility.dT + LP, MP = agent('low_pressure_steam'), agent('medium_pressure_steam') + CW, ChW = agent('cooling_water'), agent('chilled_water') + q_LP = LP.T - LP.dT - dT - 390. + q_CW = 330. - (CW.T + CW.dT + dT - 10.) + utilities = t.utility_targets() + assert [u.ID for u in utilities] == [ + 'low_pressure_steam', 'medium_pressure_steam', 'cooling_water', 'chilled_water'] + assert [u.heating for u in utilities] == [True, True, False, False] + assert_allclose([u.duty / kW for u in utilities], + [q_LP, 50. - q_LP, q_CW, 40. - q_CW], rtol=1e-9) + served = [(390., 407.189), (407.189, 440.), (340., 312.372), (312.372, 300.)] + assert_allclose([(u.T_in, u.T_out) for u in utilities], served, rtol=1e-12) + for u in utilities: + cost, utility_duty, T_out = fresh_price( + u.duty if u.heating else -u.duty, u.T_in, u.T_out, u.agent) + assert u.cost == pytest.approx(cost, rel=1e-12) + assert u.utility_duty == pytest.approx(utility_duty, rel=1e-12) + assert u.utility_duty == pytest.approx( + u.duty / u.agent.heat_transfer_efficiency, rel=1e-12) + assert u.T_utility_out == T_out + assert u.flow > 0. + assert by_ID(utilities)['cooling_water'].T_utility_out == CW.T_limit + assert by_ID(utilities)['medium_pressure_steam'].level > t.table.Ts[0] + +def test_list_order_rule(): + t = targets('test_targeting_list_order', REF_A, 10.) + LP, MP = agent('low_pressure_steam'), agent('medium_pressure_steam') + CW, ChW = agent('cooling_water'), agent('chilled_water') + utilities = t.utility_targets([MP, LP], [ChW, CW]) + assert [(u.ID, round(u.duty / kW, 9)) for u in utilities] == [ + ('medium_pressure_steam', 50.), ('chilled_water', 40.)] + assert [(u.T_in, u.T_out) for u in utilities] == [(390., 440.), (340., 300.)] + +def test_dT_follows_biosteam(monkeypatch): + t = targets('test_targeting_dT', REF_A, 10.) + before = by_ID(t.utility_targets()) + monkeypatch.setattr(bst.HeatUtility, 'dT', 10.) + after = by_ID(t.utility_targets()) + LP, CW = 'low_pressure_steam', 'cooling_water' + assert after[LP].duty == pytest.approx(before[LP].duty - 5 * kW, rel=1e-9) + assert after[CW].duty == pytest.approx(before[CW].duty - 5 * kW, rel=1e-9) + assert after[CW].T_out == pytest.approx(before[CW].T_out + 5., rel=1e-12) + for u in after.values(): + cost, _, T_out = fresh_price(u.duty if u.heating else -u.duty, + u.T_in, u.T_out, u.agent) + assert u.cost == pytest.approx(cost, rel=1e-12) + assert u.T_utility_out == T_out + +def test_unserved_warns(): + t = targets('test_targeting_unserved', REF_A, 10.) + LP, CW = agent('low_pressure_steam'), agent('cooling_water') + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter('always') + utilities = t.utility_targets([LP], [CW]) + messages = [str(w.message) for w in caught if w.category is RuntimeWarning] + assert len(messages) == 2 + assert 'heating agent' in messages[0] and 'kJ/hr' in messages[0] + assert 'cooling agent' in messages[1] + unserved = [u for u in utilities if u.ID is None] + assert [(u.heating, u.level) for u in unserved] == [(True, math.inf), + (False, -math.inf)] + assert_allclose([u.duty / kW for u in unserved], [32.811, 12.372], rtol=1e-9) + for u in unserved: + assert u.agent is None + assert all(math.isnan(x) for x in (u.cost, u.flow, u.utility_duty, + u.T_utility_in, u.T_utility_out)) + # the unserved heat is missing from the balanced composites + hot_T, hot_H, cold_T, cold_H = t.balanced_composite_curves(utilities) + assert_allclose(cold_H[-1] - hot_H[-1], (32.811 - 12.372) * kW, rtol=1e-9) + +def test_kemp_single_LP(kemp_hxn): + units, HXN = kemp_hxn + t = targets_of(units, 10.) + utilities = t.utility_targets() + assert [(u.ID, round(u.duty / kW, 9)) for u in utilities] == [ + ('low_pressure_steam', 20.), ('cooling_water', 60.)] + # the network serves C1 with medium pressure steam (its outlet, 135 degC, + # is above low pressure steam's limit) and H4 with chilled water + agents = {} + for hx in HXN.new_HX_utils: + for hu in hx.heat_utilities: + if hu.agent: agents[hu.unit.ID] = hu.agent.ID + index = {hx.ID: i for i, hx in enumerate(HXN.original_heat_exchangers)} + assert agents == {f'Util_{index["C1"]}_hs': 'medium_pressure_steam', + f'Util_{index["H4"]}_cs': 'chilled_water'} + network_cost = sum(hu.cost for hx in HXN.new_HX_utils for hu in hx.heat_utilities) + assert network_cost > sum(u.cost for u in utilities) + +def test_served_interval_and_balanced(): + t = targets('test_targeting_balanced', REF_A, 10.) + utilities = t.utility_targets() + hot_T, hot_H, cold_T, cold_H = t.balanced_composite_curves(utilities) + hot = [(300, 0), (340, 40), (400, 100), (412.189, 100), (412.189, 117.189), + (454.77, 117.189), (454.77, 150)] + cold = [(280.372, 0), (300.372, 12.372), (305.372, 12.372), (324.817, 40), + (330, 40), (390, 100), (440, 150)] + assert_allclose(points(hot_T, hot_H / kW), hot, rtol=1e-9) + assert_allclose(points(cold_T, cold_H / kW), cold, rtol=1e-9) + assert_allclose([hot_H[-1], cold_H[-1]], 150 * kW, rtol=1e-9) + T, B = t.balanced_grand_composite_curve(utilities) + assert np.all(np.diff(T) <= 0) and B.min() >= -1e-9 + for level in (390., 407.189): + assert B[T == level].min() == pytest.approx(0., abs=1e-9) + assert B[0] == pytest.approx(by_ID(utilities)['medium_pressure_steam'].duty, rel=1e-12) + assert B[-1] == pytest.approx(by_ID(utilities)['chilled_water'].duty, rel=1e-12) + # cooling water serves the stretch it reaches, priced as biosteam prices + # that stream (outlet 5 K below the process inlet), and the balanced + # composites keep a positive driving force + rows = [('H2', 400., 350., 1.), ('C2', 340., 390., 1.), ('H1', 322., 315., 1.)] + t = targets('test_targeting_served', rows, 10.) + CW = by_ID(t.utility_targets())['cooling_water'] + assert (CW.T_in, CW.T_out) == (322., 315.) + assert CW.T_utility_out == pytest.approx(317., abs=1e-9) + for T_min_app in (1., 5., 10.): + tt = t.at(T_min_app) + curves = tt.balanced_composite_curves() + assert_allclose(curves[1][-1], curves[3][-1], rtol=1e-9) + assert min_driving_force(*curves) > 0. + +def test_pricing_is_isolated(): + # a fuel case: C1 330 -> 520 K needs natural gas above high pressure steam + t = targets('test_targeting_isolated', [('C1', 330., 520., 1.), + ('H1', 400., 300., 1.)], 10.) + agents = bst.HeatUtility.heating_agents + bst.HeatUtility.cooling_agents + dumps = [(a.utility_stream_dump, list(a.utility_stream_dump)) for a in agents] + registered = lambda: {ID: id(s) for ID, s in bst.main_flowsheet.stream.data.items()} + registry = registered() + gas = agent('natural_gas') + before = fresh_price(10 * kW, 450., 500., gas)[0] + first = t.utility_targets() + second = t.utility_targets() + assert [u.cost for u in first] == [u.cost for u in second] + assert [u.ID for u in first] == ['low_pressure_steam', 'medium_pressure_steam', + 'high_pressure_steam', 'natural_gas', + 'cooling_water', 'chilled_water'] + assert_allclose([u.duty / kW for u in first[:4]], [17.189, 42.581, 54.221, 16.009], + atol=1e-9) + fuel = first[3] + assert fuel.T_utility_out == pytest.approx(508.991, abs=1e-9) + assert fuel.cost == pytest.approx(fresh_price(fuel.duty, fuel.T_in, fuel.T_out, gas)[0], + rel=1e-12) + assert registered() == registry + for a, (dump, items) in zip(agents, dumps): + assert a.utility_stream_dump is dump and a.utility_stream_dump == items + assert fresh_price(10 * kW, 450., 500., gas)[0] == before + +# %% Plots + +def test_plot_composite_curves_gids(tmp_path): + import matplotlib + matplotlib.use('Agg') + import matplotlib.pyplot as plt + t = targets('test_targeting_plots', REF_A, 10.) + for kind in ('real', 'shifted', 'balanced'): + fig, ax = plot_composite_curves(t, kind=kind) + try: + lines = {a.get_gid(): a for a in ax.get_lines()} + if kind == 'balanced': + hot_T, hot_H, cold_T, cold_H = t.balanced_composite_curves() + hot_line = lines['Hot balanced composite'] + cold_line = lines['Cold balanced composite'] + labels = _gid_artists(ax, 'Utility:') + assert len(labels) == 4 + assert not _gid_artists(ax, 'Fuel') + else: + hot_T, hot_H, cold_T, cold_H = t.composite_curves(kind == 'shifted') + hot_line = lines['Hot composite'] + cold_line = lines['Cold composite'] + assert len(_gid_artists(ax, 'Heat recovered')) == 1 + assert len(_gid_artists(ax, 'Hot utility')) == 1 + assert len(_gid_artists(ax, 'Cold utility')) == 1 + assert_allclose(hot_line.get_xdata(), hot_H, rtol=0, atol=0) + assert_allclose(hot_line.get_ydata(), hot_T - 273.15, rtol=0, atol=0) + assert_allclose(cold_line.get_xdata(), cold_H, rtol=0, atol=0) + assert_allclose(cold_line.get_ydata(), cold_T - 273.15, rtol=0, atol=0) + pinch = _gid_artists(ax, 'Pinch') + assert len(pinch) == (kind == 'shifted') + if pinch: + assert pinch[0].get_xdata()[0] == pytest.approx(100 * kW) + assert pinch[0].get_ydata()[0] == pytest.approx(390. - 273.15) + finally: + plt.close(fig) + with pytest.raises(ValueError, match="kind must be"): + plot_composite_curves(t, kind='hot') + # fuel is labeled, not drawn + furnace = targets('test_targeting_plots_fuel', [('C1', 330., 520., 1.), + ('H1', 400., 300., 1.)], 10.) + file = tmp_path / 'balanced.png' + fig, ax = plot_composite_curves(furnace, kind='balanced', file=str(file)) + try: + assert file.read_bytes()[:8] == b'\x89PNG\r\n\x1a\n' + fuel = _gid_artists(ax, 'Fuel') + assert len(fuel) == 1 and 'natural_gas (furnace)' in fuel[0].get_text() + hot = [a for a in ax.get_lines() if a.get_gid() == 'Hot balanced composite'][0] + assert np.all(np.isfinite(hot.get_ydata())) + finally: + plt.close(fig) + +def test_plot_gcc_gids(): + import matplotlib + matplotlib.use('Agg') + import matplotlib.pyplot as plt + t = targets('test_targeting_plot_gcc', REF_A, 10.) + utilities = t.utility_targets() + fig, ax = plot_grand_composite_curve(t, utilities) + try: + T, H = t.grand_composite_curve() + gcc, = _gid_artists(ax, 'GCC') + assert_allclose(gcc.get_xdata(), H, rtol=0, atol=0) + assert_allclose(gcc.get_ydata(), T - 273.15, rtol=0, atol=0) + pinch, = _gid_artists(ax, 'Pinch') + assert (pinch.get_xdata()[0], pinch.get_ydata()[0]) == (0., 390. - 273.15) + segments = {a.get_gid(): a for a in _gid_artists(ax, 'Utility:')} + assert len(segments) == 4 + for u in utilities: + x, y = segments['Utility:' + u.ID].get_data() + assert_allclose(y, u.level - 273.15, rtol=1e-12) + assert x[1] - x[0] == pytest.approx(u.duty, rel=1e-12) + assert len(_gid_artists(ax, 'Label:')) == 4 + finally: + plt.close(fig) + fig, ax = plot_grand_composite_curve(t, utilities=False) + try: + assert not _gid_artists(ax, 'Utility:') and not _gid_artists(ax, 'Label:') + finally: + plt.close(fig) From 38d609a0c422192f00f66dd3769a35ccb06eaf09 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Fri, 16 Oct 2026 22:12:49 +0000 Subject: [PATCH 05/52] add area, units, capital and total cost targets and the supertargeting sweep Commit B (F3 numerics) of the targeting spec: PinchTargets now targets what the energy targets cost, and supertarget finds the T_min_app of least targeted total annualized cost. - area_target: Bath's formula on the balanced composite curves, exact on the stretches between breakpoints of both curves (vectorized), real temperatures (approach contributions included), film coefficients per stream (number, stream-order sequence, or a mapping from stream indices or units resolved with F1's key levels; heat utility keys rejected like F1) and per agent. Fuel takes no area; a zero driving force gives inf with a RuntimeWarning, an unserved utility nan. - units_target: Euler's S - 1 per region between zeros of the balanced grand composite curve. Where the curve stays zero, the pieces on one side of the pinch count S - 1 per group linked by common streams (otherwise a zero-duty stream's grid knot alone added a unit, against the spec's "counts nowhere"); every spec reference is unchanged. - capital_cost_target: N shells of equal area with biosteam's own correlations (_exchanger_costs replicates HX._cost and _load_costs; inf and overflow give inf), one furnace on the fuel's utility duty. - summary prices once and adds the TAC (CRF from keywords, a tea, or 10 %/10 yr/8000 h); options are validated before any pricing. - supertarget sweeps at() on the reused curves (no flash) with grid refinement around the least TAC (spacing x 2/9 per round); Supertargets holds the records (to_frame, sweep[key], at_bound); plot_supertargets draws TAC, capital and operating cost with stable gids. - hensmith exports 12 names. Validation: tests/test_targeting.py 24 tests (about 12 s serial), 5 doctests in targeting.py. Bath 20.0/25.0 m2 on a balanced pair (indices, unit IDs, tuples, units, sequences), reference A 24.14438 m2, KEMP with film coefficients 56.139 and with contributions 64.2819 m2, all against an independent midpoint integration (agreement 2e-12, 5e-8, 1e-12); quickstart 5946.1 m2 and (5, 1) units; the eight units references plus zero-stretch cases; exchanger costs equal biosteam's on every KEMP exchanger (double pipe and floating head); furnace capital, TAC inf at 0 K, nan when unserved; CRF equal to solve_payment, tea precedence; the closed-form optimum 21.595 K found within 0.02 K; a 64-point scaled sweep with contributions; plots. Only targeting.py and its tests change, so the synthesis path is untouched. Full suite: 1025 passed (1014 + 9 tests + 2 doctests) in 197 s with -n 4. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- hensmith/targeting.py | 933 +++++++++++++++++++++++++++++++++++++++- tests/test_targeting.py | 493 ++++++++++++++++++++- 2 files changed, 1415 insertions(+), 11 deletions(-) diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 50631c2..d347ead 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -11,23 +11,29 @@ achieve, before (or instead of) synthesizing one. `PinchTargets` holds the problem table of the streams and derives from it the composite and grand composite curves, the multiple-utility targets priced by biosteam -(`PinchTargets.utility_targets`) and the balanced composite curves; -`plot_composite_curves` and `plot_grand_composite_curve` draw them. +(`PinchTargets.utility_targets`), the balanced composite curves, and the +area, units, capital cost and total annualized cost targets +(`PinchTargets.summary`); `supertarget` sweeps the minimum approach +temperature for the least total annualized cost. `plot_composite_curves`, +`plot_grand_composite_curve` and `plot_supertargets` draw them. """ import math from collections import namedtuple -from collections.abc import Sequence +from collections.abc import Mapping, Sequence from contextlib import contextmanager +from numbers import Integral, Real from warnings import warn import numpy as np import biosteam as bst from ._curves import _T_EQ from ._planner import _approach_contributions, _contribution_value from .hxn_synthesis import (_problem_table, _pinch_cut, _format_H, - _arrange_heat_utilities, _process_streams, _explicit_contributions) + _arrange_heat_utilities, _process_streams, _explicit_contributions, + _contribution_key_level, _names_unit, _owner_chains, _unit_name) -__all__ = ('PinchTargets', 'plot_composite_curves', - 'plot_grand_composite_curve') +__all__ = ('PinchTargets', 'supertarget', 'Supertargets', + 'plot_composite_curves', 'plot_grand_composite_curve', + 'plot_supertargets') #: Kinds of the constraint positions of the grand composite curve (see #: `_positions`): the heat arriving at a grid temperature (before its point @@ -35,6 +41,33 @@ #: just below a utility level strictly between two grid temperatures. _ARRIVING, _LEAVING, _ABOVE, _BELOW = 0, 1, 2, 3 +#: Square feet per square meter, biosteam's own factor in `HX._design`. +_FT2_PER_M2 = 10.763 + +#: Default film coefficient [kW/m²/K]. A pair gives U = 0.5, biosteam's +#: heuristic overall heat transfer coefficient (except condensing against +#: boiling). +_H_DEFAULT = 1.0 + +#: Btu per kJ, biosteam's factor in `HX._cost` (furnace). +_BTU_PER_KJ = 0.947817 + +#: Default economics: interest rate, years of annualization and operating +#: hours per year (a capital recovery factor of 0.16275). +_INTEREST, _YEARS, _OPERATING_HOURS = 0.10, 10, 8000. + +#: Default minimum approach temperatures of `supertarget` [K]. +_T_MIN_APPS = np.arange(1., 41.) + +#: A vertical temperature difference at or below this is a zero driving +#: force [K]. +_DT_ZERO = 1e-9 + +#: The options of `PinchTargets.summary` (and `supertarget`). +_SUMMARY_OPTIONS = ('heating_agents', 'cooling_agents', 'h', 'h_utilities', + 'P', 'heat_exchanger_type', 'material', 'interest', + 'years', 'operating_hours', 'tea') + UtilityTarget = namedtuple('UtilityTarget', ( 'ID', 'agent', 'heating', 'level', 'duty', 'utility_duty', 'flow', 'cost', 'T_in', 'T_out', 'T_utility_in', 'T_utility_out')) @@ -255,14 +288,216 @@ def _utility_member(u): return (np.array([T_a, T_b]), np.array([0., u.duty]), np.array([0., u.duty])) +def _euler(regions): + """Euler's S - 1 per connected component of the members of `regions` + (sets; members of one region are connected).""" + components = [] + for members in regions: + if not members: continue + component = set(members) + for other in [c for c in components if c & component]: + component |= other + components.remove(other) + components.append(component) + return sum(len(c) - 1 for c in components) + +def _film_value(x, label): + """A film coefficient [kW/m²/K]: ``float(x)`` for a finite real number + > 0 (not a bool); a ValueError naming `label` otherwise.""" + if isinstance(x, Real) and not isinstance(x, (bool, np.bool_)): + value = float(x) + if math.isfinite(value) and value > 0.: return value + raise ValueError(f'{label} must be a finite number > 0 kW/m2/K, not {x!r}') + +def _utility_films(utilities, h_utilities, agents=()): + """ + The film coefficient [kW/m²/K] of each of `utilities` from + `h_utilities`: None (`_H_DEFAULT`), a number, or a mapping from agent + IDs or agents to numbers (`_H_DEFAULT` for an agent it leaves out). A + key must name an agent of `utilities`, of `agents` or of biosteam's + default lists. + """ + if h_utilities is None: return [_H_DEFAULT] * len(utilities) + if not isinstance(h_utilities, Mapping): + return [_film_value(h_utilities, 'h_utilities')] * len(utilities) + known = {a.ID for a in (*agents, *bst.HeatUtility.heating_agents, + *bst.HeatUtility.cooling_agents)} + known.update(u.ID for u in utilities if u.agent is not None) + values = {} # agent ID -> (key, value) + unknown = [] + for key, value in h_utilities.items(): + if isinstance(key, bst.UtilityAgent): ID = key.ID + elif isinstance(key, str): ID = key + else: + raise TypeError('h_utilities keys must be agent IDs (str) or ' + f'UtilityAgent objects, not {type(key).__name__!r} ' + f'({key!r})') + value = _film_value(value, f'h_utilities of key {key!r}') + if ID not in known and not isinstance(key, bst.UtilityAgent): + unknown.append(key) + other, other_value = values.setdefault(ID, (key, value)) + if other_value != value: + raise ValueError(f'h_utilities keys {other!r} and {key!r} give agent ' + f'{ID!r} different values: {other_value!r} and ' + f'{value!r}') + if unknown: + raise ValueError('h_utilities keys that name no agent: ' + f"{', '.join(map(repr, unknown))}") + return [values[u.ID][1] if u.ID in values else _H_DEFAULT + for u in utilities] + +def _T_at(segments, i, H): + """Temperature of the segments `i` (see `_composite`) at heat `H`.""" + Ha, Hb, Ta, Tb = (x[i] for x in segments[:4]) + with np.errstate(invalid='ignore'): # inf - inf on a furnace's flat + T = Ta + (Tb - Ta) * (H - Ha) / (Hb - Ha) + return np.where(Ta == Tb, Ta, T) + +def _bath(hot, cold): + """ + Area [m²] of vertical heat transfer between the `hot` and `cold` + segments (`_composite`, weighted by 1/h [m²·K/kW]): Bath's formula, + exact on the stretches between the breakpoints of both curves, where + both and every member are linear. A hot stretch at infinite temperature + (fuel) takes no area. inf with a RuntimeWarning at a driving force of + `_DT_ZERO` or less. + """ + if not (hot[0].size and cold[0].size): return 0. + end = min(hot[1][-1], cold[1][-1]) + X = np.unique(np.concatenate([hot[0], hot[1], cold[0], cold[1]])) + X = X[(X >= 0.) & (X <= end)] + x, y = X[:-1], X[1:] + keep = y - x > 1e-12 * end + x, y = x[keep], y[keep] + m = 0.5 * (x + y) + i = np.searchsorted(hot[0], m, 'right') - 1 + j = np.searchsorted(cold[0], m, 'right') - 1 + Th_x, Th_y = _T_at(hot, i, x), _T_at(hot, i, y) + finite = np.isfinite(Th_x) # not fuel + x, y, i, j = x[finite], y[finite], i[finite], j[finite] + d1 = Th_x[finite] - _T_at(cold, j, x) + d2 = Th_y[finite] - _T_at(cold, j, y) + zero = np.flatnonzero(np.minimum(d1, d2) <= _DT_ZERO) + if zero.size: + k = zero[0] + warn('vertical heat transfer at zero driving force between H = ' + f'{x[k]:.6g} and {y[k]:.6g} kJ/hr', RuntimeWarning) + return math.inf + with np.errstate(divide='ignore', invalid='ignore'): + LMTD = np.where(np.abs(d1 - d2) <= 1e-12 * np.maximum(d1, d2), d1, + (d1 - d2) / np.log(d1 / d2)) + return float(np.sum((y - x) * (hot[4][i] + cold[4][j]) / 3600. / LMTD)) + +def _check_exchanger(heat_exchanger_type, material): + """Raise a ValueError unless biosteam knows the shell and tube + `heat_exchanger_type` and `material`.""" + from biosteam.units.heat_exchange import Cb_dict + from biosteam.units.design_tools.specification_factors import ( + shell_and_tube_material_factor_coefficients as materials) + types = [i for i in Cb_dict if i != 'Furnace'] + if heat_exchanger_type not in types: + raise ValueError(f'heat_exchanger_type must be one of {types}, not ' + f'{heat_exchanger_type!r}') + if material not in materials: + raise ValueError(f'material must be one of {list(materials)}, not ' + f'{material!r}') + +def _exchanger_costs(A, P=101325., heat_exchanger_type='Floating head', + material='Carbon steel/carbon steel'): + """ + ``(purchase, installed, type)`` [USD] of one heat exchanger of area `A` + [ft²] at pressure `P` [Pa], as biosteam's `HX._cost` and + `Unit._load_costs` cost it: a double pipe below 150 ft², else a shell + and tube of `heat_exchanger_type` and `material` with 20 ft tubes. + ``(inf, inf, None)`` for an infinite area (or one beyond the + correlations), ``(nan, nan, None)`` for nan. + """ + from biosteam.units.heat_exchange import ( + Cb_dict, p2, compute_double_pipe_purchase_cost) + from biosteam.units.design_tools.specification_factors import ( + compute_shell_and_tube_material_factor, + shell_and_tube_material_factor_coefficients) + _check_exchanger(heat_exchanger_type, material) + if math.isnan(A): return math.nan, math.nan, None + if math.isinf(A): return math.inf, math.inf, None + P_psi = P * 14.7 / 101325 + L = 20 # ft, as biosteam assumes + try: + if A < 150: # double pipe + p = P_psi / 600 + F_p = 0.8510 + 0.1292 * p + 0.0198 * p**2 + F_m = 2 + if A < 2.1718: + F_l = A / 2.1718 + A = 2.1718 + else: + F_l = 1 + kind = 'Double pipe' + C_b = compute_double_pipe_purchase_cost(A, bst.CE) + else: # shell and tube + F_m = compute_shell_and_tube_material_factor( + A, *shell_and_tube_material_factor_coefficients[material]) + F_l = np.polyval(p2, L) # L == 20 is not > 20 in biosteam + p = P_psi / 100 + F_p = 0.9803 + 0.018 * p + 0.0017 * p**2 + kind = heat_exchanger_type + C_b = Cb_dict[kind](A, bst.CE) + except OverflowError: + return math.inf, math.inf, None + F = F_l * F_p * F_m + return C_b * F, C_b * (bst.HX._F_BM_default[kind] + F - 1.), kind + +def _furnace_costs(Q): + """``(purchase, installed)`` [USD] of a furnace of utility duty `Q` + [kJ/hr], as biosteam's `HX._cost` costs it (500 psi).""" + from biosteam.units.heat_exchange import compute_furnace_purchase_cost + C_b = compute_furnace_purchase_cost(Q * _BTU_PER_KJ, bst.CE) + S = 500 / 500 + F_P = 0.986 - 0.0035 * S + 0.0175 * S * S + return C_b * F_P, C_b * (bst.HX._F_BM_default['Furnace'] + F_P - 1.) + +def _real(x, name): + """``float(x)`` of a finite real number (not a bool); a ValueError + naming `name` otherwise.""" + if isinstance(x, Real) and not isinstance(x, (bool, np.bool_)): + value = float(x) + if math.isfinite(value): return value + raise ValueError(f'{name} must be a finite number, not {x!r}') + +def _economics(interest=None, years=None, operating_hours=None, tea=None): + """ + ``(crf, hours)``: the capital recovery factor [1/yr] and the operating + hours per year. Each keyword takes precedence over `tea` (its `IRR`, + ``duration[1] - duration[0]`` and `operating_hours`, unless None), which + takes precedence over the defaults. + """ + if tea is not None: + if interest is None: interest = tea.IRR + if years is None: years = tea.duration[1] - tea.duration[0] + if operating_hours is None: operating_hours = tea.operating_hours + interest = _real(_INTEREST if interest is None else interest, 'interest') + years = _real(_YEARS if years is None else years, 'years') + hours = _real(_OPERATING_HOURS if operating_hours is None + else operating_hours, 'operating_hours') + if interest <= -1.: + raise ValueError(f'interest must be > -1, not {interest!r}') + if years <= 0.: + raise ValueError(f'years must be > 0, not {years!r}') + if hours < 0.: + raise ValueError(f'operating_hours must be >= 0, not {hours!r}') + if interest == 0.: return 1. / years, hours + fn = (1. + interest) ** years + return interest * fn / (fn - 1.), hours + class PinchTargets: """ Pinch analysis targets of a set of process streams: the problem table (`hensmith.hxn_synthesis.problem_table`) and what follows from it, the composite and grand composite curves, the targets of several utilities - (`utility_targets`) priced by biosteam, and the balanced composite - curves. Build it from heat utilities with `from_heat_utilities` (as + (`utility_targets`) priced by biosteam, the balanced composite curves, + and the targets of area, units, capital cost and total annualized cost + (`summary`). Build it from heat utilities with `from_heat_utilities` (as `hensmith.hxn_synthesis.synthesize_network` prepares them) or from streams as `problem_table` takes them. @@ -385,6 +620,42 @@ class PinchTargets: temperature. `balanced_grand_composite_curve` is the cascade net of the utilities, zero at the process pinch and at every utility pinch. + *Area target.* `area_target` is Bath's formula [Townsend84]_ on the + balanced composite curves: vertical heat transfer, counter-current (a + correction factor of 1), each stretch between breakpoints of either + curve at its log-mean temperature difference, ``A = sum_i (1 / + LMTD_i) sum_j q_ij / h_j``, over the real temperatures (with approach + contributions too) and with a film coefficient h per stream and per + utility (1 kW/m²/K by default, so a pair has biosteam's heuristic + overall coefficient of 0.5 kW/m²/K; condensing against boiling, where + biosteam takes 1, is not reproduced). The sum is exact on the + piecewise-linear curves, a stream's share of a stretch being linear in + the heat. Fuel takes no area, and the area is inf (with a + RuntimeWarning) at a zero driving force, as at ``T_min_app = 0``. + + *Units target.* `units_target` applies Euler's N = S - 1 to each region + of the balanced grand composite curve between its zeros (the process + pinch and the utility pinches), S counting the streams with heat in the + region and the utilities acting in it. Where the curve stays zero, each + piece between grid temperatures is balanced on its own: on each side of + the pinch such pieces count S - 1 per group of them linked by common + streams, so a stream across several counts once. It is an estimate, not + a bound: it assumes one component per region, and a balanced subset of + streams needs fewer units. + + *Capital and total annualized cost.* `capital_cost_target` divides the + area target into as many shells of equal area as the units target (less + one furnace for all fuel) [Linnhoff90]_ and costs each with biosteam's + correlations at one pressure (biosteam takes each exchanger's highest + inlet pressure: its pressure factor is up to about 12 % higher for + high pressure steam heaters): a double pipe below 150 ft² (whose cost + jumps from 3,208 to 20,516 USD at 150 ft²; the floating head + correlation, not monotone below about 126 ft², is not used there), a + shell and tube of any area above. The furnace is costed on the fuel's + utility duty. `summary` adds the total annualized cost, the installed + (bare module) cost times the capital recovery factor plus the utility + cost times the operating hours: no Lang factor and no working capital. + Examples -------- One hot stream cooled 400 -> 300 K and one cold stream heated 330 -> @@ -439,12 +710,46 @@ class PinchTargets: >>> round(hot_H[-1] / 3600, 6), round(cold_H[-1] / 3600, 6) (150.0, 150.0) + The area target [m²] with film coefficients of 1 kW/m²/K, the units + target (the balanced grand composite curve is zero at the utility + pinches of low pressure steam and cooling water and from the pinch down + to 330 K, which leaves five regions of two streams or utilities each), + and the capital cost target, five double pipes of equal area [USD]: + + >>> round(targets.area_target(utilities), 2) + 24.14 + >>> targets.units_target(utilities) + {'above': 2, 'below': 3, 'total': 5} + >>> capital = targets.capital_cost_target(utilities) + >>> capital['N_shells'], capital['type'], round(capital['installed_cost']) + (5, 'Double pipe', 33991) + + All targets, with the total annualized cost [USD/yr] at 10 % interest + over 10 years and 8000 operating hours a year: + + >>> {key: round(value, 2) for key, value in targets.summary().items()} + {'T_min_app': 10.0, 'Q_hot': 180000.0, 'Q_cold': 144000.0, + 'utility_cost': 1.66, 'area': 24.14, 'N_units': 5, 'N_shells': 5, + 'purchase_cost': 23150.38, 'installed_cost': 33991.4, + 'annualized_capital_cost': 5531.94, 'operating_cost': 13247.77, + 'TAC': 18779.71} + The same streams at another minimum approach temperature (the curves are reused): >>> targets.at(20.) + References + ---------- + .. [Townsend84] Townsend, D. W., & Linnhoff, B. (1984). Surface area + targets for heat exchanger networks. IChemE 11th Annual Research + Meeting, Bath, UK. + .. [Linnhoff90] Linnhoff, B., & Ahmad, S. (1990). Cost optimum heat + exchanger networks - 1. Minimum energy and capital using simple + models for capital cost. Computers & Chemical Engineering, 14(7), + 729-750. + """ def __init__(self, streams_inlet, streams_quenched, is_hot, T_min_app=5., approach_contributions=None, labels=None, curves=None): @@ -935,6 +1240,541 @@ def balanced_grand_composite_curve(self, utilities=None): positions, B = self._balanced_positions(utilities) return positions[0].copy(), B + # %% Supertargets + + def _film_coefficients(self, h): + """The film coefficient of each stream [kW/m²/K] from `h` (see + `area_target`), an (N,) array.""" + N = self.is_hot.size + if h is None: return np.full(N, _H_DEFAULT) + if isinstance(h, Mapping): return self._film_mapping(h) + if isinstance(h, (Sequence, np.ndarray)) and not isinstance(h, (str, bytes)): + values = list(h) + if len(values) != N: + raise ValueError(f'{len(values)} film coefficients h given for ' + f'{N} streams') + return np.array([_film_value(v, f'h[{j}]') + for j, v in enumerate(values)]) + return np.full(N, _film_value(h, 'h')) + + def _film_mapping(self, h): + """ + `_film_coefficients` of a mapping whose keys are stream indices or + name units as approach contribution keys do (see + `hensmith.hxn_synthesis._mapping_contributions`): the key of the + lowest level on the owner chain of a stream's unit wins, and a stream + index is below its unit. Keys of the same level for a unit must give + it the same value. + """ + N = self.is_hot.size + units = self.units or () + universe = _owner_chains(units) + items = [] + unknown = [] + for key, value in h.items(): + if isinstance(key, bst.HeatUtility): + raise TypeError('a heat utility cannot be a key of h (biosteam ' + 'replaces the heat utilities of a unit at every ' + "simulation): use a stream index, its unit " + "(hu.unit), the unit's ID or (owner.ID, " + 'unit.ID)') + if isinstance(key, Integral) and not isinstance(key, (bool, np.bool_)): + if not 0 <= key < N: unknown.append(key) + elif not (isinstance(key, (bst.Unit, str)) + or isinstance(key, tuple) and len(key) == 2 + and all(isinstance(i, str) for i in key)): + raise TypeError('h keys must be stream indices (int), units, ' + 'unit IDs (str) or (owner.ID, unit.ID) tuples, ' + f'not {type(key).__name__!r} ({key!r})') + elif not any(_names_unit(key, u) for u in universe): + unknown.append(key) + items.append((key, _film_value(value, f'h of key {key!r}'))) + if unknown: + raise ValueError('h keys that name no stream: ' + f"{', '.join(map(repr, unknown))} (a key is a " + 'stream index or, for targets of heat utilities, ' + 'a unit, the ID of a unit without an owner, or ' + '(owner.ID, unit.ID))') + unit_items = [(k, v) for k, v in items if not isinstance(k, Integral)] + for unit in universe: + first = {} # level -> (key, value) + for key, value in unit_items: + level = _contribution_key_level(key, unit) + if level is None: continue + other, other_value = first.setdefault(level, (key, value)) + if other_value != value: + raise ValueError( + f'h keys {other!r} and {key!r} give unit ' + f'{_unit_name(unit)!r} different values at the same ' + f'level: {other_value!r} and {value!r}') + values = np.full(N, _H_DEFAULT) + for j in range(N): + best = None # (level, value) of the nearest key + for key, value in items: + if isinstance(key, Integral): + level = -1 if key == j else None + else: + level = _contribution_key_level(key, units[j]) + if level is not None and (best is None or level < best[0]): + best = (level, value) + if best is not None: values[j] = best[1] + return values + + def _area(self, utilities, h, h_utilities): + """`area_target` with resolved film coefficients (`h`, an (N,) + array; `h_utilities`, one per utility).""" + if any(u.agent is None for u in utilities): return math.nan + hot, cold = self._balanced_segments( + utilities, 1. / h, [1. / x for x in h_utilities]) + return _bath(hot, cold) + + def area_target(self, utilities=None, h=None, h_utilities=None): + """ + The area target [m²] of vertical heat transfer between the balanced + composite curves: Bath's formula (see Notes of the class). + + Parameters + ---------- + utilities : list[UtilityTarget], optional + Defaults to `utility_targets` with the default agents. + h : float, Sequence[float] or Mapping, optional + Film coefficient of the streams [kW/m²/K]: one for all, one per + stream in stream order, or a mapping from stream indices (int) + or, for targets of heat utilities, keys that name units as + approach contribution keys do (a unit, the ID of a unit without + an owner, or ``(owner.ID, unit.ID)``; an auxiliary unit takes the + value of its owner's key unless a key names it). The nearest key + wins, a stream index first; streams left out take 1. Labels are + not keys. Defaults to 1 for every stream. + h_utilities : float or Mapping, optional + Film coefficient of the utilities [kW/m²/K]: one for all, or a + mapping from agent IDs or agents (the others take 1). Defaults + to 1. + + Returns + ------- + float + The area [m²]: inf (with a RuntimeWarning) at a zero driving + force, nan if part of a utility target is unserved. + + """ + if utilities is None: utilities = self.utility_targets() + return self._area(utilities, self._film_coefficients(h), + _utility_films(utilities, h_utilities)) + + def units_target(self, utilities=None): + """ + An estimate of the number of units: Euler's S - 1 per region of the + balanced grand composite curve between its zeros (see Notes of the + class). + + Parameters + ---------- + utilities : list[UtilityTarget], optional + Defaults to `utility_targets` with the default agents. + + Returns + ------- + dict + ``{'above': int, 'below': int, 'total': int}``: the units of the + regions above the process pinch, below it, and both. + + """ + if utilities is None: utilities = self.utility_targets() + positions, B = self._balanced_positions(utilities) + T, _, kind, k = positions + P = T.size + tol = self._tol + Ts, Hl, Hr = self.grid['Ts'], self.grid['Hl'], self.grid['Hr'] + # each stream's cumulative enthalpy at every position, between the + # virtual top and bottom ones + H = np.where(kind == _ARRIVING, Hr[:, k], Hl[:, k]) + inner = np.flatnonzero((kind == _ABOVE) | (kind == _BELOW)) + if inner.size: + ki = k[inner] + f = (T[inner] - Ts[ki + 1]) / (Ts[ki] - Ts[ki + 1]) + H[:, inner] = Hr[:, ki + 1] + (Hl[:, ki] - Hr[:, ki + 1]) * f + H = np.column_stack([Hr[:, 0], H, Hl[:, -1]]) + cuts = [-1, *np.flatnonzero(B <= tol).tolist(), P] + gaps = [_gap(positions, u.level) for u in utilities if u.duty > tol] + pinch = int(np.flatnonzero( + (T == Ts[self._k_pinch]) + & (kind == (_ARRIVING if self.cut == 'below' else _LEAVING)))[0]) + # the members of each region (streams j, utilities -1 - i), grouped: + # consecutive regions of one gap between two zeros (where the curve + # stays zero, each piece balanced on its own) on one side of the + # pinch form one group + groups = [] # [above, [members of each region], zero] + for c0, c1 in zip(cuts[:-1], cuts[1:]): + heat = np.abs(H[:, c1 + 1] - H[:, c0 + 1]) + members = {*np.flatnonzero(heat > tol).tolist(), + *(-1 - i for i, g in enumerate(gaps) if c0 < g <= c1)} + side = c1 <= pinch + zero = 0 <= c0 and c1 == c0 + 1 < P + if zero and groups and groups[-1][2] and groups[-1][0] == side: + groups[-1][1].append(members) + else: + groups.append([side, [members], zero]) + above = below = 0 + for side, regions, _ in groups: + N = _euler(regions) + if side: above += N + else: below += N + return {'above': above, 'below': below, 'total': above + below} + + def _capital(self, utilities, h, h_utilities, P, heat_exchanger_type, + material): + """`capital_cost_target` with resolved film coefficients (see + `_area`), and the units target.""" + _check_exchanger(heat_exchanger_type, material) + A = self._area(utilities, h, h_utilities) + N = self.units_target(utilities)['total'] + fuel = [u for u in utilities if u.agent is not None and u.agent.isfuel] + N_shells = N - (1 if fuel else 0) # one furnace for all fuel + if A > 0. and N_shells < 1: N_shells = 1 + elif A == 0.: N_shells = 0 + if N_shells: + shell_area = A / N_shells + purchase, installed, kind = _exchanger_costs( + shell_area * _FT2_PER_M2, P, heat_exchanger_type, material) + purchase *= N_shells + installed *= N_shells + else: # no area, or nan + shell_area = purchase = installed = A + kind = None + furnace_duty = sum((u.utility_duty for u in fuel), 0.) + if fuel: + furnace_purchase, furnace_installed = _furnace_costs(furnace_duty) + purchase += furnace_purchase + installed += furnace_installed + return {'area': A, 'N_shells': N_shells, 'shell_area': shell_area, + 'type': kind, 'furnace_duty': furnace_duty, + 'purchase_cost': purchase, 'installed_cost': installed}, N + + def capital_cost_target(self, utilities=None, h=None, h_utilities=None, + P=101325., heat_exchanger_type='Floating head', + material='Carbon steel/carbon steel'): + """ + The capital cost target: the area target in as many shells of equal + area as the units target (less a furnace), costed with biosteam's + correlations, plus a furnace for the fuel (see Notes of the class). + + Parameters + ---------- + utilities : list[UtilityTarget], optional + Defaults to `utility_targets` with the default agents. + h, h_utilities : optional + Film coefficients, as `area_target` takes them. + P : float, optional + Pressure of every shell [Pa]. Defaults to 101325. + heat_exchanger_type : str, optional + biosteam's type of the shell and tube exchangers ('Floating + head', 'Fixed head', 'U tube', 'Kettle vaporizer' or 'Double + pipe'). Defaults to 'Floating head'. + material : str, optional + biosteam's shell and tube material. Defaults to 'Carbon + steel/carbon steel'. + + Returns + ------- + dict + 'area' [m²]; 'N_shells', the number of shells; 'shell_area', the + area of each [m²]; 'type', biosteam's type of each ('Double pipe' + below 150 ft², None without shells or with an infinite area); + 'furnace_duty', the utility duty of the fuel [kJ/hr]; + 'purchase_cost' and 'installed_cost' (bare module) [USD]. inf + with an infinite area, nan with an unserved utility. + + """ + if utilities is None: utilities = self.utility_targets() + return self._capital(utilities, self._film_coefficients(h), + _utility_films(utilities, h_utilities), P, + heat_exchanger_type, material)[0] + + def _check_options(self, options): + """The options of `summary` (agents as lists), after raising for + any that `summary` would reject, before pricing anything.""" + unknown = [i for i in options if i not in _SUMMARY_OPTIONS] + if unknown: + raise TypeError(f"unexpected options: {', '.join(map(repr, unknown))} " + f'(the options are {", ".join(_SUMMARY_OPTIONS)})') + options = dict(options) + for key in ('heating_agents', 'cooling_agents'): + if options.get(key) is not None: options[key] = list(options[key]) + get = options.get + _economics(get('interest'), get('years'), get('operating_hours'), + get('tea')) + _check_exchanger(get('heat_exchanger_type', 'Floating head'), + get('material', 'Carbon steel/carbon steel')) + self._film_coefficients(get('h')) + _utility_films((), get('h_utilities'), + [*(get('heating_agents') or ()), + *(get('cooling_agents') or ())]) + return options + + def summary(self, **options): + """ + The targets of energy, area, units, capital and total annualized + cost (TAC), pricing the utilities once. + + Parameters + ---------- + **options + `heating_agents` and `cooling_agents` (as `utility_targets` + takes them); `h` and `h_utilities` (as `area_target` takes + them); `P`, `heat_exchanger_type` and `material` (as + `capital_cost_target` takes them); `interest` (default 0.10), + `years` (default 10) and `operating_hours` (per year, default + 8000), each taken from `tea` when not given and `tea` is (its + `IRR`, ``duration[1] - duration[0]`` and `operating_hours` + unless None). + + Returns + ------- + dict + 'T_min_app' [K]; 'Q_hot' and 'Q_cold' [kJ/hr], process side; + 'utility_cost' [USD/hr]; 'area' [m²]; 'N_units', the units + target; 'N_shells'; 'purchase_cost' and 'installed_cost' [USD]; + 'annualized_capital_cost', the installed cost times the capital + recovery factor, 'operating_cost', the utility cost times the + operating hours, and their sum 'TAC' [USD/yr]. + + """ + options = self._check_options(options) + get = options.get + heating_agents = get('heating_agents') + cooling_agents = get('cooling_agents') + crf, hours = _economics(get('interest'), get('years'), + get('operating_hours'), get('tea')) + utilities = self.utility_targets(heating_agents, cooling_agents) + h_utilities = _utility_films( + utilities, get('h_utilities'), + [*(heating_agents or ()), *(cooling_agents or ())]) + capital, N = self._capital( + utilities, self._film_coefficients(get('h')), h_utilities, + get('P', 101325.), get('heat_exchanger_type', 'Floating head'), + get('material', 'Carbon steel/carbon steel')) + utility_cost = sum((u.cost for u in utilities), 0.) + annualized = crf * capital['installed_cost'] + operating = hours * utility_cost + return {'T_min_app': self.T_min_app, 'Q_hot': self.Q_hot, + 'Q_cold': self.Q_cold, 'utility_cost': utility_cost, + 'area': capital['area'], 'N_units': N, + 'N_shells': capital['N_shells'], + 'purchase_cost': capital['purchase_cost'], + 'installed_cost': capital['installed_cost'], + 'annualized_capital_cost': annualized, + 'operating_cost': operating, 'TAC': annualized + operating} + + +def _least_TAC(records): + """Index of the record of least TAC (nan counts as inf).""" + TAC = np.array([r['TAC'] for r in records], dtype=float) + return int(np.argmin(np.where(np.isnan(TAC), math.inf, TAC))) + +def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, + scale_contributions=False, **options): + """ + Sweep the minimum approach temperature for the least total annualized + cost (TAC) target: supertargeting [Ahmad90]_. + + Parameters + ---------- + targets : PinchTargets + The streams (their curves are reused: no flash). + T_min_apps : Sequence[float], optional + Minimum approach temperatures to evaluate [K], finite and >= 0 (> 0 + with approach contributions); sorted and made unique. Defaults to 1, + 2, ..., 40 K. + refine : int, optional + Rounds of refinement around the least TAC. Defaults to 3. + n_refine : int, optional + Evaluations per round of refinement. Defaults to 8. + scale_contributions : bool, optional + Scale the specified approach contributions of `targets` with the + minimum approach temperature (see `PinchTargets.at`). Defaults to + False. + **options + Options of `PinchTargets.summary`. + + Returns + ------- + Supertargets + + Notes + ----- + Each minimum approach temperature is evaluated with + ``targets.at(T_min_app, scale_contributions).summary(**options)``. Only + the streams without an approach contribution follow the minimum approach + temperature (``T_min_app / 2`` each), unless `scale_contributions` + scales the specified ones by ``T_min_app / targets.T_min_app`` too, the + contribution scaling of [Ahmad90]_; with every stream specified, the + sweep needs it. + + The TAC is not smooth (the units target jumps, so do biosteam's + correlations at 150 ft², and the pinch can move), so the sweep is a + grid search: each round of refinement evaluates `n_refine` evenly spaced + temperatures between the neighbors of the least TAC so far, so after r + rounds the spacing is the grid's times (2/9)**r for the defaults. A + least TAC at the smallest or largest temperature evaluated is flagged + (`Supertargets.at_bound`). + + Examples + -------- + One hot stream cooled 400 -> 300 K and one cold stream heated 290 -> + 390 K, both 1 kW/K. Up to 10 K they balance with no utility in a single + exchanger of 20 m² (215 ft², a shell and tube); above it the utilities + add units, and three double pipes of less than 150 ft² each cost less, + so on a coarse grid the least total annualized cost (TAC) is at 15 K: + + >>> import biosteam as bst, thermosteam as tmo + >>> from hensmith import PinchTargets, supertarget + >>> fluid = tmo.Chemical('Fluid', search_db=False, phase='l', MW=1., + ... Cn=3.6, default=True) + >>> bst.settings.set_thermo([fluid]) + >>> bst.main_flowsheet.set_flowsheet('supertargeting_example') + >>> H1 = bst.HXutility('H1', ins=bst.Stream('H1_in', Fluid=1000., T=400.), + ... T=300., rigorous=False) + >>> C1 = bst.HXutility('C1', ins=bst.Stream('C1_in', Fluid=1000., T=290.), + ... T=390., rigorous=False) + >>> H1.simulate(); C1.simulate() + >>> targets = PinchTargets.from_heat_utilities( + ... [H1.heat_utilities[0], C1.heat_utilities[0]], T_min_app=10.) + >>> grid = [5., 10., 15., 20., 25., 30.] + >>> sweep = supertarget(targets, grid, refine=0) + >>> sweep + + >>> sweep['N_units'], sweep['area'].round(2) + (array([1, 1, 3, 3, 4, 4]), array([20. , 20. , 13.88, 11.07, 9.73, 9.17])) + + Refining around it finds the least TAC just above 10 K, with almost no + utility and the area already in three double pipes: + + >>> sweep = supertarget(targets, grid) + >>> sweep + + >>> sweep.optimum['N_shells'], round(sweep.optimum['area'], 2) + (3, 19.89) + + References + ---------- + .. [Ahmad90] Ahmad, S., Linnhoff, B., & Smith, R. (1990). Cost optimum + heat exchanger networks - 2. Targets and design for detailed capital + cost models. Computers & Chemical Engineering, 14(7), 751-767. + + """ + if T_min_apps is None: T_min_apps = _T_MIN_APPS + T = np.asarray(T_min_apps, dtype=float) + if T.ndim != 1 or not T.size: + raise ValueError('T_min_apps must be a non-empty sequence of ' + 'temperatures [K]') + if not np.all(np.isfinite(T)): + raise ValueError(f'T_min_apps must be finite, not {T_min_apps!r}') + contributions = targets.approach_contributions + if contributions is None: + if np.any(T < 0.): + raise ValueError(f'T_min_apps must be >= 0 K, not {T_min_apps!r}') + elif np.any(T <= 0.): + raise ValueError('T_min_apps must be > 0 K with approach ' + f'contributions, not {T_min_apps!r}') + for value, name, low in ((refine, 'refine', 0), (n_refine, 'n_refine', 1)): + if (not isinstance(value, Integral) or isinstance(value, (bool, np.bool_)) + or value < low): + raise ValueError(f'{name} must be an int >= {low}, not {value!r}') + if (contributions is not None and not scale_contributions + and all(c is not None for c in contributions)): + raise ValueError('every stream has an approach contribution, so ' + 'T_min_app changes nothing: pass ' + 'scale_contributions=True to scale them with it') + options = targets._check_options(options) + def evaluate(T_min_app): + return targets.at(T_min_app, scale_contributions).summary(**options) + records = [evaluate(float(x)) for x in np.unique(T)] + for _ in range(refine): + records.sort(key=lambda r: r['T_min_app']) + i = _least_TAC(records) + lo = records[max(i - 1, 0)]['T_min_app'] + hi = records[min(i + 1, len(records) - 1)]['T_min_app'] + if hi - lo <= 1e-6: break + evaluated = np.array([r['T_min_app'] for r in records]) + for x in np.linspace(lo, hi, n_refine + 2)[1:-1]: + if np.abs(evaluated - x).min() > 1e-12: + records.append(evaluate(float(x))) + return Supertargets(targets, records, options, scale_contributions) + + +class Supertargets: + """ + The targets of a sweep of the minimum approach temperature, as + `supertarget` returns them. + + Parameters + ---------- + targets : PinchTargets + The streams swept. + records : list[dict] + `PinchTargets.summary` at each minimum approach temperature. + options : dict, optional + Options of `PinchTargets.summary` of the records. + scale_contributions : bool, optional + Whether the approach contributions were scaled (see + `PinchTargets.at`). Defaults to False. + + Attributes + ---------- + targets : PinchTargets + The streams swept. + options : dict + Options of `PinchTargets.summary` of the records. + scale_contributions : bool + Whether the approach contributions were scaled. + records : list[dict] + `PinchTargets.summary` at each minimum approach temperature, + ascending. + optimum : dict + The record of least TAC (the first of equal ones; nan counts as + inf). + T_min_app : float + Minimum approach temperature of `optimum` [K]. + at_bound : bool + Whether `optimum` is at the smallest or largest temperature + evaluated: the least TAC may lie beyond the sweep. + + Notes + ----- + ``sweep[key]`` is the array of a key of the records over the sweep (for + instance ``sweep['TAC']``), and `to_frame` a pandas DataFrame of them + indexed by 'T_min_app'. + + """ + def __init__(self, targets, records, options=None, + scale_contributions=False): + if not records: raise ValueError('Supertargets needs at least one record') + self.targets = targets + self.options = dict(options or {}) + self.scale_contributions = scale_contributions + self.records = sorted(records, key=lambda r: r['T_min_app']) + i = _least_TAC(self.records) + self.optimum = self.records[i] + self.T_min_app = self.optimum['T_min_app'] + self.at_bound = i in (0, len(self.records) - 1) + + def __getitem__(self, key): + return np.array([r[key] for r in self.records]) + + def to_frame(self): + """The records as a pandas DataFrame indexed by 'T_min_app'.""" + import pandas as pd + return pd.DataFrame(self.records).set_index('T_min_app') + + def __repr__(self): + n = len(self.records) + lo, hi = self.records[0]['T_min_app'], self.records[-1]['T_min_app'] + bound = ', at the bound' if self.at_bound else '' + return (f'<{type(self).__name__}: {n} point{"s" if n != 1 else ""}, ' + f'T_min_app {lo:g}-{hi:g} K, optimum {self.T_min_app:.4g} K ' + f'(TAC {self.optimum["TAC"]:.3e} USD/yr){bound}>') + def plot_composite_curves(targets, kind='real', utilities=None, ax=None, file=None, dpi=300): @@ -1184,3 +2024,80 @@ def plot_grand_composite_curve(targets, utilities=None, ax=None, file=None, ax.grid(alpha=0.3) if file: fig.savefig(file, dpi=dpi, bbox_inches='tight') return fig, ax + +def plot_supertargets(sweep, ax=None, file=None, dpi=300): + """ + Draw the total annualized cost (TAC) of a sweep of the minimum approach + temperature, its annualized capital and operating parts, and the least + TAC. + + Parameters + ---------- + sweep : Supertargets + 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 + ----- + Artists carry stable gids: 'TAC', 'Capital' (the annualized capital + cost), 'Operating' and 'Optimum'. + + Examples + -------- + The streams of `PinchTargets`: + + >>> import biosteam as bst, thermosteam as tmo + >>> from hensmith import PinchTargets, supertarget, plot_supertargets + >>> fluid = tmo.Chemical('Fluid', search_db=False, phase='l', MW=1., + ... Cn=3.6, default=True) + >>> bst.settings.set_thermo([fluid]) + >>> bst.main_flowsheet.set_flowsheet('targeting_example') + >>> H1 = bst.HXutility('H1', ins=bst.Stream('H1_in', Fluid=1000., T=400.), + ... T=300., rigorous=False) + >>> C1 = bst.HXutility('C1', ins=bst.Stream('C1_in', Fluid=1000., T=330.), + ... T=440., rigorous=False) + >>> H1.simulate(); C1.simulate() + >>> targets = PinchTargets.from_heat_utilities( + ... [H1.heat_utilities[0], C1.heat_utilities[0]], T_min_app=10.) + >>> sweep = supertarget(targets) + >>> sweep + + >>> fig, ax = plot_supertargets(sweep) + >>> [line.get_gid() for line in ax.get_lines() if line.get_gid()] + ['TAC', 'Capital', 'Operating', 'Optimum'] + >>> import matplotlib.pyplot as plt + >>> plt.close(fig) + + """ + import matplotlib.pyplot as plt + if ax is None: + fig, ax = plt.subplots(figsize=(6, 4)) + else: + fig = ax.figure + T = sweep['T_min_app'] + ax.plot(T, sweep['TAC'], color='k', lw=2, label='total annualized cost', + gid='TAC') + ax.plot(T, sweep['annualized_capital_cost'], color='#2e6db4', lw=1.5, + label='annualized capital cost', gid='Capital') + ax.plot(T, sweep['operating_cost'], color='#d62728', lw=1.5, + label='operating cost', gid='Operating') + optimum = sweep.optimum + ax.axvline(optimum['T_min_app'], color='k', ls='--', lw=0.8) + ax.plot([optimum['T_min_app']], [optimum['TAC']], 'o', mfc='w', mec='k', + ms=7, zorder=4, label=f"optimum {optimum['T_min_app']:.3g} K", + gid='Optimum') + ax.set_xlabel('$T_{min,app}$ [K]') + ax.set_ylabel('cost [USD·yr$^{-1}$]') + ax.legend(loc='best', fontsize=8) + ax.grid(alpha=0.3) + if file: fig.savefig(file, dpi=dpi, bbox_inches='tight') + return fig, ax diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 479c75a..4078b59 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -17,21 +17,29 @@ attributes, list order, biosteam's dT, unserved remainders, served stretches), priced against an independent isolated pricing, and the balanced curves. -C. Plots: stable gids whose data equal the curves. +C. Supertargets: the area target against closed forms and an independent + integration, the units target on references with process and utility + pinches, the capital costs against biosteam's own costing, the + economics, and the sweep against a closed-form optimum. +D. Plots: stable gids whose data equal the curves. Reference A: H1 400 -> 300 K and C1 330 -> 440 K, both 1 kW/K, at T_min_app = 10 K: 50 kW hot and 40 kW cold utility, shifted pinch 390 K. """ import inspect import math +import time +import types import warnings import numpy as np import pytest import biosteam as bst from numpy.testing import assert_allclose -from hensmith import PinchTargets, plot_composite_curves, plot_grand_composite_curve +from hensmith import (PinchTargets, Supertargets, supertarget, plot_composite_curves, + plot_grand_composite_curve, plot_supertargets) from hensmith.hxn_synthesis import _problem_table, temperature_interval_pinch_analysis -from test_hxn import cp_units, KEMP, simulate_HXN, _gid_artists +from hensmith.targeting import _exchanger_costs, _economics +from test_hxn import build_system, cp_units, KEMP, simulate_HXN, _gid_artists from test_hxn_contributions import classic_cascade import test_hxn_targets @@ -39,8 +47,14 @@ kW = 3600. # kJ/hr #: Reference A (temperatures in K, CP in kW/K). REF_A = [('H1', 400., 300., 1.), ('C1', 330., 440., 1.)] +#: A balanced pair: no utility up to T_min_app = 10 K, 10 K apart. +PAIR = [('H1', 400., 300., 1.), ('C1', 290., 390., 1.)] +#: Reference A with C1 heated to 520 K: natural gas above high pressure steam. +FURNACE = [('C1', 330., 520., 1.), ('H1', 400., 300., 1.)] #: Contributions of the KEMP reference with approach contributions [K]. KEMP_C = {'C1': 1., 'C3': 2., 'H2': 6., 'H4': 8.} +#: Film coefficients of the KEMP reference of the area target [kW/m2/K]. +KEMP_H = {'C1': 1., 'H2': 2., 'C3': .5, 'H4': 4.} def targets_of(units, T_min_app, **kwargs): """`PinchTargets` of the units' heat utilities, sorted by duty as the @@ -113,6 +127,81 @@ def points(T, H): def by_ID(utilities): return {u.ID: u for u in utilities} +def member(T_a, T_b, Q, h=1.): + """A member of `integrated_area`.""" + return (min(T_a, T_b), max(T_a, T_b), Q, h) + +def utility_members(utilities, h=None): + """The `integrated_area` members of the served utilities ``(hot, + cold)``: latent at the agent's temperature (no `T_limit`), else linear + between the utility's inlet and outlet temperatures.""" + h = h or {} + hot, cold = [], [] + for u in utilities: + if u.agent.T_limit is None: T_a = T_b = u.agent.T + else: T_a, T_b = u.T_utility_in, u.T_utility_out + (hot if u.heating else cold).append(member(T_a, T_b, u.duty, h.get(u.ID, 1.))) + return hot, cold + +def integrated_area(hot, cold, n=1_500_000): + """ + Area [m2] by midpoint integration of ``sum_j (1/h_j) (dq_j/dH) / dT`` + over the heat of two composite curves, each built here from members + ``(T_a, T_b, Q [kJ/hr], h)``: linear from T_a to T_b, or latent at T_a + == T_b. At each H the curve's temperature is found by inverting the + cumulative heat, and the heat is shared by the members present there in + proportion to their heat capacity flow rates (on a latent flat, by the + latent members at that temperature). + """ + def side(members): + knots = np.unique([T for m in members for T in m[:2]]) + def H_at(T, right): + total = np.zeros(T.size) + for T_a, T_b, Q, _ in members: + if T_a == T_b: total += Q * ((T > T_a) | (right & (T == T_a))) + else: total += Q * np.clip((T - T_a) / (T_b - T_a), 0., 1.) + return total + T = np.repeat(knots, 2) + H = np.column_stack([H_at(knots, False), H_at(knots, True)]).ravel() + return T, H + hot_T, hot_H = side(hot) + cold_T, cold_H = side(cold) + end = min(hot_H[-1], cold_H[-1]) + dH = end / n + H = (np.arange(n) + 0.5) * dH + def weights(members, T): + latent = [m for m in members if m[0] == m[1]] + flat = np.zeros(T.size, dtype=bool) + for T_a, _, _, _ in latent: flat |= T == T_a + CP = np.zeros(T.size) + w = np.zeros(T.size) + Q_flat = np.zeros(T.size) + w_flat = np.zeros(T.size) + for T_a, T_b, Q, h in members: + if T_a == T_b: + at = T == T_a + Q_flat += Q * at + w_flat += Q * at / h + else: + present = (T > T_a) & (T < T_b) + CP += Q / (T_b - T_a) * present + w += Q / (T_b - T_a) * present / h + with np.errstate(invalid='ignore', divide='ignore'): + return np.where(flat, w_flat / Q_flat, w / CP) + Th = np.interp(H, hot_H, hot_T) + Tc = np.interp(H, cold_H, cold_T) + return float(np.sum((weights(hot, Th) + weights(cold, Tc)) / (Th - Tc)) * dH / kW) + +def rows_members(rows, offset=0., h=None): + """``(hot, cold)`` `integrated_area` members of constant-CP rows.""" + h = h or {} + hot, cold = [], [] + for ID, T_in, T_out, CP in rows: + m = member(T_in + offset, T_out + offset, CP * abs(T_in - T_out) * kW, + h.get(ID, 1.)) + (hot if T_in > T_out else cold).append(m) + return hot, cold + @pytest.fixture(scope='module') def kemp_hxn(): """KEMP synthesized by the facility at 10 K.""" @@ -120,6 +209,15 @@ def kemp_hxn(): sys, HXN = simulate_HXN(units, 10.) return units, HXN +@pytest.fixture(scope='module') +def quickstart(): + """The targets of the documentation's quickstart system at 5 K (two + auxiliary exchangers of the column D0).""" + sys, HXN, _ = build_system() + sys.simulate() + hus = sorted(HXN.original_heat_utils, key=lambda hu: hu.duty) + return PinchTargets.from_heat_utilities(hus, 5.) + # %% Composites and grand composite curve def test_composites_closed_form_kemp(): @@ -340,6 +438,15 @@ def test_unserved_warns(): # the unserved heat is missing from the balanced composites hot_T, hot_H, cold_T, cold_H = t.balanced_composite_curves(utilities) assert_allclose(cold_H[-1] - hot_H[-1], (32.811 - 12.372) * kW, rtol=1e-9) + # so the area, the capital cost and the total annualized cost are nan + assert math.isnan(t.area_target(utilities)) + assert math.isnan(t.capital_cost_target(utilities)['installed_cost']) + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + summary = t.summary(heating_agents=[LP], cooling_agents=[CW]) + for key in ('utility_cost', 'area', 'installed_cost', 'TAC'): + assert math.isnan(summary[key]), key + assert summary['N_units'] == t.units_target(utilities)['total'] def test_kemp_single_LP(kemp_hxn): units, HXN = kemp_hxn @@ -389,6 +496,7 @@ def test_served_interval_and_balanced(): curves = tt.balanced_composite_curves() assert_allclose(curves[1][-1], curves[3][-1], rtol=1e-9) assert min_driving_force(*curves) > 0. + assert 0. < tt.area_target() < math.inf def test_pricing_is_isolated(): # a fuel case: C1 330 -> 520 K needs natural gas above high pressure steam @@ -417,6 +525,362 @@ def test_pricing_is_isolated(): assert a.utility_stream_dump is dump and a.utility_stream_dump == items assert fresh_price(10 * kW, 450., 500., gas)[0] == before +# %% Area, units, capital and total annualized cost + +def test_bath_balanced_pair(quickstart): + t = targets('test_targeting_pair', PAIR, 10.) + utilities = t.utility_targets() + assert (t.Q_hot, t.Q_cold, utilities) == (0., 0., []) + assert t.labels == ['C1', 'H1'] + # 100 kW, 10 K apart: U = 0.5 kW/m2/K by default, 1 / (1/2 + 1/0.5) else + assert t.area_target(utilities) == pytest.approx(20., rel=1e-12) + forms = [{0: .5, 1: 2.}, {'H1': 2., 'C1': .5}, {('H1', 'H1'): 2., ('C1', 'C1'): .5}, + {t.units[1]: 2., t.units[0]: .5}, [.5, 2.], np.array([.5, 2.]), + {'H1': 1., 1: 2., 'C1': .5}] # the stream index is nearer + for h in forms: + assert t.area_target(utilities, h=h) == pytest.approx(25., rel=1e-12), h + assert t.area_target(h=2.) == pytest.approx(10., rel=1e-12) + assert t.area_target(h={'H1': 2.}) == pytest.approx(15., rel=1e-12) + for h in ({'H9': 1.}, {2: 1.}, {-1: 1.}, {('D0', 'H1'): 1.}): + with pytest.raises(ValueError, match='h keys that name no stream'): + t.area_target(h=h) + for h, message in (({0: True}, 'h of key 0 must be'), (0., 'h must be'), + (math.inf, 'h must be'), ([1., -1.], r'h\[1\] must be'), + ({'C1': math.nan}, "h of key 'C1' must be"), + ([1.], '1 film coefficients h given for 2')): + with pytest.raises(ValueError, match=message): + t.area_target(h=h) + with pytest.raises(ValueError, match='different values at the same level'): + t.area_target(h={'H1': 2., ('H1', 'H1'): 3.}) + with pytest.raises(TypeError, match='a heat utility cannot be a key of h'): + t.area_target(h={t.heat_utilities[0]: 2.}) + for key in (True, 1.5, ('H1',)): + with pytest.raises(TypeError, match='h keys must be'): + t.area_target(h={key: 2.}) + # labels are not keys of targets built from streams + direct = PinchTargets(t.streams_inlet, t.streams_quenched, t.is_hot, 10., + labels=['C1', 'H1']) + assert direct.area_target(h={0: .5, 1: 2.}) == pytest.approx(25., rel=1e-12) + with pytest.raises(ValueError, match='name no stream'): + direct.area_target(h={'H1': 2.}) + # utilities: reference A, keyed by agent ID or agent + a = targets('test_targeting_pair_utilities', REF_A, 10.) + utilities = a.utility_targets() + LP = agent('low_pressure_steam') + by_ID_ = a.area_target(utilities, h_utilities={'low_pressure_steam': 2.}) + assert by_ID_ < a.area_target(utilities) + assert a.area_target(utilities, h_utilities={LP: 2.}) == by_ID_ + assert a.area_target(utilities, h_utilities=2.) < by_ID_ + with pytest.raises(ValueError, match='h_utilities keys that name no agent'): + a.area_target(utilities, h_utilities={'steam': 2.}) + with pytest.raises(ValueError, match='different values'): + a.area_target(utilities, h_utilities={LP: 2., 'low_pressure_steam': 3.}) + with pytest.raises(TypeError, match='h_utilities keys must be'): + a.area_target(utilities, h_utilities={1: 2.}) + with pytest.raises(ValueError, match='h_utilities must be'): + a.area_target(utilities, h_utilities=-1.) + # auxiliary units: the owner's key reaches both, a key naming the unit + # (owner.ID, unit.ID) is nearer + labels = quickstart.labels + reboiler, condenser = labels.index('D0 - reboiler'), labels.index('D0 - condenser') + utilities = quickstart.utility_targets() + both = quickstart.area_target(utilities, h={'D0': 2.}) + assert both == quickstart.area_target(utilities, h={reboiler: 2., condenser: 2.}) + assert both != quickstart.area_target(utilities) + assert quickstart.area_target(utilities, h={'D0': 2., ('D0', 'reboiler'): 1.}) == ( + quickstart.area_target(utilities, h={condenser: 2.})) + with pytest.raises(ValueError, match='name no stream'): + quickstart.area_target(utilities, h={'reboiler': 2.}) + +def test_bath_independent_integration(quickstart): + # reference A: the streams plus the four utilities + t = targets('test_targeting_integration_A', REF_A, 10.) + utilities = t.utility_targets() + hot, cold = rows_members(REF_A) + u_hot, u_cold = utility_members(utilities) + expected = integrated_area(hot + u_hot, cold + u_cold) + A = t.area_target(utilities) + assert A == pytest.approx(expected, rel=1e-6) + assert A == pytest.approx(24.144381021937, rel=1e-9) + # KEMP with film coefficients per stream + units = cp_units('test_targeting_integration_kemp', KEMP, K) + t = targets_of(units, 10.) + utilities = t.utility_targets() + hot, cold = rows_members(KEMP, K, KEMP_H) + u_hot, u_cold = utility_members(utilities) + expected = integrated_area(hot + u_hot, cold + u_cold) + A = t.area_target(utilities, h=KEMP_H) + assert A == pytest.approx(expected, rel=1e-5) + assert A == pytest.approx(56.139, rel=1e-5) + assert t.area_target(utilities) == pytest.approx(59.6813, rel=1e-6) + # KEMP with approach contributions: Bath on real temperatures with the + # utilities allocated on the shifted ones + t = targets_of(units, 10., approach_contributions=KEMP_C) + utilities = t.utility_targets() + hot, cold = rows_members(KEMP, K) + u_hot, u_cold = utility_members(utilities) + expected = integrated_area(hot + u_hot, cold + u_cold) + A = t.area_target(utilities) + assert A == pytest.approx(expected, rel=1e-6) + assert A == pytest.approx(64.2819, rel=1e-6) + # the quickstart at 5 K + assert quickstart.area_target() == pytest.approx(5946.1, rel=1e-5) + +def test_units_targets(quickstart): + cases = [ + ('kemp', [(ID, T_in + K, T_out + K, CP) for ID, T_in, T_out, CP in KEMP], (4, 3)), + ('ref_A', REF_A, (2, 3)), + ('pair', PAIR, (0, 1)), + ('threshold', [('H1', 400., 320., 1.), ('C1', 310., 350., 1.)], (0, 2)), + ('cold_only', [('C1', 330., 440., 1.), ('C2', 300., 350., 2.)], (3, 0)), + # low pressure steam is pinched at 407.189 K and touches both cold + # streams on either side + ('utility_pinch', [('H1', 400., 300., 2.), ('C1', 330., 440., 1.), + ('C2', 330., 440., 1.)], (4, 4)), + # H2 and C2 balance: the network needs fewer units than the estimate + ('balanced_subset', [('H2', 400., 350., 1.), ('C2', 340., 390., 1.), + ('H1', 325., 315., 1.)], (0, 2)), + ] + for name, rows, (above, below) in cases: + t = targets('test_targeting_units_' + name, rows, 10.) + assert t.units_target() == {'above': above, 'below': below, + 'total': above + below}, name + t = targets('test_targeting_units_pinched', cases[5][1], 10.) + LP = by_ID(t.utility_targets())['low_pressure_steam'] + assert (LP.duty / kW, LP.level) == (pytest.approx(34.378, abs=1e-9), 407.189) + assert quickstart.units_target() == {'above': 5, 'below': 1, 'total': 6} + # where the balanced curve stays zero, a stream across several pieces + # counts once: a zero-duty stream given directly (a grid temperature in + # the zero stretch) counts nowhere + zero = cp_units('test_targeting_units_zero', [('Z', 350., 350., 1.)])[0] + for name, rows in (('ref_A', REF_A), ('pair', PAIR)): + t = targets('test_targeting_units_zero_' + name, rows, 10.) + direct = PinchTargets(t.streams_inlet + zero.ins, t.streams_quenched + zero.outs, + [*t.is_hot, True], 10.) + assert direct.table.Ts.size == t.table.Ts.size + 1 + assert direct.units_target() == t.units_target(), name + # and two balanced pairs in one zero stretch are two units + rows = [('H1', 400., 350., 1.), ('C1', 340., 390., 1.), + ('H2', 340., 300., 1.), ('C2', 290., 330., 1.)] + t = targets('test_targeting_units_two_pairs', rows, 10.) + assert (t.Q_hot, t.Q_cold) == (0., 0.) + assert t.units_target() == {'above': 0, 'below': 2, 'total': 2} + +def test_exchanger_costs_match_biosteam(kemp_hxn): + units, HXN = kemp_hxn + seen = set() + for hx in HXN.new_HXs + HXN.new_HX_utils: + design = hx.design_results + if 'Area' not in design: continue + purchase, installed, kind = _exchanger_costs( + design['Area'], design['Operating pressure'] * 101325 / 14.7) + assert list(hx.purchase_costs) == [kind] + assert purchase == pytest.approx(hx.purchase_cost, rel=1e-12) + assert installed == pytest.approx(hx.installed_cost, rel=1e-12) + seen.add(kind) + assert seen == {'Double pipe', 'Floating head'} + assert _exchanger_costs(math.inf)[:2] == (math.inf, math.inf) + assert _exchanger_costs(1e300) == (math.inf, math.inf, None) # overflow + assert all(math.isnan(x) for x in _exchanger_costs(math.nan)[:2]) + # a type and a material of biosteam's lists + p, i, kind = _exchanger_costs(500., heat_exchanger_type='U tube', + material='Stainless steel/stainless steel') + assert kind == 'U tube' and p < _exchanger_costs(500., material='Stainless steel/stainless steel')[0] + with pytest.raises(ValueError, match='heat_exchanger_type must be one of'): + _exchanger_costs(500., heat_exchanger_type='Furnace') + with pytest.raises(ValueError, match='material must be one of'): + _exchanger_costs(500., material='steel') + +def test_capital_target_formula(): + from biosteam.units.heat_exchange import compute_furnace_purchase_cost + t = targets('test_targeting_capital', REF_A, 10.) + utilities = t.utility_targets() + capital = t.capital_cost_target(utilities) + N = t.units_target(utilities)['total'] + A = t.area_target(utilities) + assert capital['area'] == A and capital['N_shells'] == N == 5 + assert capital['shell_area'] == A / N + p, i, kind = _exchanger_costs(A * 10.763 / N) + assert (capital['purchase_cost'], capital['installed_cost'], capital['type']) == ( + N * p, N * i, kind) + assert capital['furnace_duty'] == 0. + # a higher pressure costs more + assert t.capital_cost_target(utilities, P=20e5)['installed_cost'] > capital['installed_cost'] + # the furnace: one for all fuel, costed on the utility duty + t = targets('test_targeting_capital_furnace', FURNACE, 10.) + utilities = t.utility_targets() + assert_allclose([u.duty / kW for u in utilities[:4]], [17.189, 42.581, 54.221, 16.009], + atol=1e-9) + fuel = utilities[3] + assert fuel.ID == 'natural_gas' and fuel.T_utility_out == pytest.approx(508.991, abs=1e-9) + assert fuel.cost == pytest.approx(fresh_price(fuel.duty, fuel.T_in, fuel.T_out, fuel.agent)[0], + rel=1e-12) + capital = t.capital_cost_target(utilities) + N = t.units_target(utilities)['total'] + assert capital['furnace_duty'] == fuel.utility_duty + assert capital['N_shells'] == N - 1 + A = t.area_target(utilities) + p, i, _ = _exchanger_costs(A * 10.763 / (N - 1)) + C_b = compute_furnace_purchase_cost(fuel.utility_duty * 0.947817, bst.CE) + assert capital['installed_cost'] == pytest.approx((N - 1) * i + C_b * 2.19, rel=1e-12) + assert capital['purchase_cost'] == pytest.approx((N - 1) * p + C_b, rel=1e-12) + summary = t.summary() + assert summary['utility_cost'] == pytest.approx(sum(u.cost for u in utilities), rel=1e-12) + assert summary['installed_cost'] == capital['installed_cost'] + # a zero minimum approach temperature: no driving force at the pinch + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter('always') + zero = t.at(0.).summary() + assert any('zero driving force' in str(w.message) for w in caught) + assert zero['area'] == zero['installed_cost'] == zero['TAC'] == math.inf + assert zero['Q_hot'] < t.Q_hot + with pytest.raises(ValueError, match='heat_exchanger_type must be one of'): + t.capital_cost_target(utilities, heat_exchanger_type='shell') + +def test_crf_and_tea(): + from biosteam._tea import solve_payment + assert _economics(.1, 10)[0] == solve_payment(1., .1, 10) + assert _economics() == (pytest.approx(0.16275, abs=1e-5), 8000.) + assert _economics(0., 10) == (0.1, 8000.) + tea = types.SimpleNamespace(IRR=.08, duration=(2020, 2040), operating_hours=7920) + assert _economics(tea=tea) == (solve_payment(1., .08, 20), 7920.) + assert _economics(.05, 5, 100., tea) == (solve_payment(1., .05, 5), 100.) + tea.operating_hours = None + assert _economics(tea=tea)[1] == 8000. + for kwargs, message in ((dict(years=0), 'years must be > 0'), + (dict(operating_hours=-1.), 'operating_hours must be >= 0'), + (dict(interest=-1.), 'interest must be > -1'), + (dict(interest=math.nan), 'interest must be a finite number'), + (dict(years=True), 'years must be a finite number')): + with pytest.raises(ValueError, match=message): + _economics(**kwargs) + # the total annualized cost of the summary + t = targets('test_targeting_tea', REF_A, 10.) + tea.operating_hours = 7920 + summary = t.summary(tea=tea, interest=.12) + crf = solve_payment(1., .12, 20) + assert summary['annualized_capital_cost'] == crf * summary['installed_cost'] + assert summary['operating_cost'] == 7920 * summary['utility_cost'] + assert summary['TAC'] == summary['annualized_capital_cost'] + summary['operating_cost'] + with pytest.raises(TypeError, match="unexpected options: 'dT'"): + t.summary(dT=5.) + with pytest.raises(ValueError, match='years must be > 0'): + t.summary(years=-1) + +def test_optimum_T_min_app_closed_form(): + from biosteam.units.heat_exchange import (compute_double_pipe_purchase_cost, + compute_floating_head_purchase_cost, p2) + from biosteam.units.design_tools.specification_factors import ( + compute_shell_and_tube_material_factor, shell_and_tube_material_factor_coefficients) + CP = 100. + rows = [('H1', 400., 320., CP), ('C1', 310., 390., CP)] + t = targets('test_targeting_closed_form', rows, 20.) + thermo = agent('low_pressure_steam').thermo + with bst.Flowsheet('test_targeting_closed_form_agents'): + steam = bst.UtilityAgent('test_steam', Water=1, T=450, P=1e6, phase='g', + heat_transfer_price=4e-7, thermo=thermo) + refrigerant = bst.UtilityAgent('test_refrigerant', Water=1, T=280, P=1e3, + phase='l', heat_transfer_price=2e-7, thermo=thermo) + options = dict(heating_agents=[steam], cooling_agents=[refrigerant]) + def LMTD(a, b): return a if a == b else (a - b) / math.log(a / b) + def installed(A): # [ft2], biosteam's correlations at 1 atm + if A < 150: + F_l = min(A / 2.1718, 1.) + C_b = compute_double_pipe_purchase_cost(max(A, 2.1718), bst.CE) + p = 14.7 / 600 + return C_b * (1.8 + F_l * (0.8510 + 0.1292 * p + 0.0198 * p**2) * 2 - 1.) + p = 14.7 / 100 + F_m = compute_shell_and_tube_material_factor( + A, *shell_and_tube_material_factor_coefficients['Carbon steel/carbon steel']) + F = np.polyval(p2, 20) * (0.9803 + 0.018 * p + 0.0017 * p**2) * F_m + return compute_floating_head_purchase_cost(A, bst.CE) * (3.17 + F - 1.) + def TAC(T): + Q = CP * (T - 10.) # each utility [kW]: C1 is above the shifted top of H1 + A = 2 * (Q / LMTD(40., 40. + Q / CP) + (80 * CP - Q) / T + + Q / LMTD(60. + Q / CP, 60.)) + crf = 0.1 * 1.1**10 / (1.1**10 - 1.) + return crf * 3 * installed(A * 10.763 / 3) + 8000 * 3600 * (4e-7 * Q + 2e-7 * Q) + for T in (12., 20., 35.): + summary = t.at(T).summary(**options) + assert summary['N_units'] == 3 + assert summary['TAC'] == pytest.approx(TAC(T), rel=1e-9) + fine = np.arange(11., 60., 0.001) + values = np.array([TAC(T) for T in fine]) + T_opt = fine[np.argmin(values)] + assert T_opt == pytest.approx(21.595, abs=1e-6) + assert np.all(np.diff(values[fine < T_opt]) < 0) and np.all(np.diff(values[fine > T_opt]) > 0) + sweep = supertarget(t, np.arange(11., 61.), **options) + assert abs(sweep.T_min_app - T_opt) <= 0.02 + assert sweep.optimum['TAC'] == pytest.approx(TAC(sweep.T_min_app), rel=1e-9) + assert not sweep.at_bound + assert sweep.options == options + assert repr(sweep) == ('') + +def test_sweep(monkeypatch): + import hensmith.hxn_synthesis + t = targets('test_targeting_sweep', REF_A, 10.) + def flash(*args, **kwargs): raise AssertionError('the curves are rebuilt') + monkeypatch.setattr(hensmith.hxn_synthesis, 'stream_curves', flash) + sweep = supertarget(t, [20., 5., 10., 5.], refine=1, n_refine=3) + assert isinstance(sweep, Supertargets) and sweep.targets is t + T = sweep['T_min_app'] + assert np.all(np.diff(T) > 0) and T.size == 3 + 3 + assert_allclose(sweep['TAC'], [r['TAC'] for r in sweep.records], rtol=0, atol=0) + frame = sweep.to_frame() + assert frame.index.name == 'T_min_app' and list(frame.index) == list(T) + assert list(frame.columns) == ['Q_hot', 'Q_cold', 'utility_cost', 'area', 'N_units', + 'N_shells', 'purchase_cost', 'installed_cost', + 'annualized_capital_cost', 'operating_cost', 'TAC'] + assert sweep.optimum['TAC'] == frame['TAC'].min() + assert sweep.T_min_app == sweep.optimum['T_min_app'] + assert sweep.at_bound == (sweep.T_min_app in (T[0], T[-1])) + single = supertarget(t, [10.], refine=2) + assert len(single.records) == 1 and single.at_bound and 'at the bound' in repr(single) + assert single.optimum == t.summary() + # the options reach every evaluation + hours = supertarget(t, [5., 10.], refine=0, operating_hours=0.) + assert np.all(hours['operating_cost'] == 0.) + LP, MP = agent('low_pressure_steam'), agent('medium_pressure_steam') + listed = supertarget(t, [5., 10.], refine=0, heating_agents=[MP, LP]) + assert listed.records == supertarget(t, [5., 10.], refine=0, + heating_agents=iter([MP, LP])).records + assert listed.records != supertarget(t, [5., 10.], refine=0).records + # validation, before sweeping + for kwargs, error, message in ( + (dict(T_min_apps=[]), ValueError, 'non-empty'), + (dict(T_min_apps=[[5.]]), ValueError, 'non-empty'), + (dict(T_min_apps=[5., math.nan]), ValueError, 'finite'), + (dict(T_min_apps=[-1., 5.]), ValueError, '>= 0 K'), + (dict(refine=-1), ValueError, 'refine must be an int >= 0'), + (dict(refine=1.), ValueError, 'refine must be an int >= 0'), + (dict(n_refine=0), ValueError, 'n_refine must be an int >= 1'), + (dict(n_refine=True), ValueError, 'n_refine must be an int >= 1'), + (dict(dT=5.), TypeError, 'unexpected options'), + (dict(h={'H9': 1.}), ValueError, 'name no stream'), + (dict(h_utilities={'steam': 1.}), ValueError, 'name no agent'), + (dict(material='steel'), ValueError, 'material must be')): + with pytest.raises(error, match=message): + supertarget(t, **kwargs) + # every stream with an approach contribution: T_min_app changes nothing + # unless the contributions scale with it + monkeypatch.undo() + units = cp_units('test_targeting_sweep_kemp', KEMP, K) + tc = targets_of(units, 10., approach_contributions=KEMP_C) + with pytest.raises(ValueError, match='scale_contributions=True'): + supertarget(tc) + start = time.perf_counter() + sweep = supertarget(tc, scale_contributions=True) + assert time.perf_counter() - start < 20. + assert len(sweep.records) > 60 and sweep.scale_contributions + assert np.ptp(sweep['TAC']) > 0. and np.ptp(sweep['Q_hot']) > 0. + record = sweep.records[0] + assert record == tc.at(record['T_min_app'], scale_contributions=True).summary() + partial = targets_of(units, 10., approach_contributions={'C1': 1.}) + with pytest.raises(ValueError, match='> 0 K with approach contributions'): + supertarget(partial, [0., 5.]) + assert np.ptp(supertarget(partial, [5., 20.], refine=0)['Q_hot']) > 0. + # %% Plots def test_plot_composite_curves_gids(tmp_path): @@ -497,3 +961,26 @@ def test_plot_gcc_gids(): assert not _gid_artists(ax, 'Utility:') and not _gid_artists(ax, 'Label:') finally: plt.close(fig) + +def test_plot_supertargets_gids(tmp_path): + import matplotlib + matplotlib.use('Agg') + import matplotlib.pyplot as plt + t = targets('test_targeting_plot_sweep', REF_A, 10.) + sweep = supertarget(t, [0., 5., 10., 20.], refine=1, n_refine=2) + assert sweep['TAC'][0] == math.inf + file = tmp_path / 'sweep.png' + fig, ax = plot_supertargets(sweep, file=str(file)) + try: + assert file.read_bytes()[:8] == b'\x89PNG\r\n\x1a\n' + T = sweep['T_min_app'] + for gid, key in (('TAC', 'TAC'), ('Capital', 'annualized_capital_cost'), + ('Operating', 'operating_cost')): + line, = _gid_artists(ax, gid) + assert_allclose(line.get_xdata(), T, rtol=0, atol=0) + assert_allclose(line.get_ydata(), sweep[key], rtol=0, atol=0) + optimum, = _gid_artists(ax, 'Optimum') + assert (optimum.get_xdata()[0], optimum.get_ydata()[0]) == ( + sweep.T_min_app, sweep.optimum['TAC']) + finally: + plt.close(fig) From 4965f9a3f06a95c5058005d0c407aa8ffd5a789b Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Fri, 16 Oct 2026 22:33:01 +0000 Subject: [PATCH 06/52] add the facility's targets method and the network report Commit C (facility and report) of the targeting spec: a simulated HeatExchangerNetwork can be targeted directly, and its synthesized network compared with those targets like for like. - HeatExchangerNetwork.targets(T_min_app=None): after a simulation, the PinchTargets of original_heat_exchangers in the network's stream order, with the approach contributions resolved against original_heat_utils (so replaced unit heat utilities do not matter); before one, those of the heat utilities the next simulation would take, sorted and prepared as the synthesis does. Bit for bit equal to synthesis_info's Q_hot_target/Q_cold_target; nothing is stored, the synthesis path, the cache key and _synthesis_options are unchanged. PinchTargets is imported lazily. - network_report(HXN, targets=None, **options): energy (process side), utilities by agent, unit counts, the network's area integrated along the exact stream profiles with the targets' film model (counter-current, process duty; a furnace takes none) beside biosteam's own sizing, the costs of the whole new network, TAC with summary's economics (operating hours default to the system's), and the targets' summary. Given targets must be of the same units in the same order, thermodynamic basis and current states. The utilities are grouped by hand rather than with HeatUtility.sum_by_agent, which loads agents (registry and stream dump side effects). hensmith exports 13 names. Validation: tests/test_targeting.py 27 tests (+3: facility targets bitwise before/after simulation, with contributions and with replaced heat utilities; the report on KEMP, 54.06 m2 equal to terminal-LMTD areas with the process duty of the medium pressure steam heater, 6 units against the estimate of 7, a no-match network, stale or mismatched targets; no side effects on the registry, the agents' dumps, a later furnace cost or a later synthesis's float(hx.Q).hex()), about 15 s with the doctests; 2 new doctests. Quickstart at 5 K: area 7462 m2 against a 5946 m2 target and biosteam's 5536 m2, utility cost 1839.61 USD/hr for both. Oracle digest dcd15864... and the four golden digests unchanged. Full suite: 1030 passed (1025 + 3 tests + 2 doctests) in 241 s with -n 4. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- hensmith/_heat_exchanger_network.py | 98 +++++++++ hensmith/targeting.py | 320 +++++++++++++++++++++++++++- tests/test_targeting.py | 220 ++++++++++++++++++- 3 files changed, 624 insertions(+), 14 deletions(-) diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index f4440c4..84ef075 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -954,6 +954,104 @@ def plot_pinch_diagram(self, file=None, **kwargs): file=file, **kwargs, ) + def targets(self, T_min_app=None): + """ + The pinch analysis targets of the facility's process streams (see + `hensmith.targeting.PinchTargets`): energy, utilities, area, units, + capital and total annualized cost. + + Parameters + ---------- + T_min_app : float, optional + Minimum approach temperature [K]. Defaults to the facility's + `T_min_app`. + + Returns + ------- + PinchTargets + The targets, with the facility's `approach_contributions` and + `force_ideal_thermo`. + + Raises + ------ + RuntimeError + If the facility has neither `units` nor a system (it learns its + system when the system is first simulated). + + Notes + ----- + After a simulation, the streams are those of + `original_heat_exchangers`, in the network's stream order, at the + units' current states: right after a synthesis, the energy targets + at the facility's `T_min_app` equal ``synthesis_info['Q_hot_target']`` + and ``synthesis_info['Q_cold_target']`` bit for bit. The approach + contributions are resolved against `original_heat_utils` (the + original heat utilities, kept even where + `replace_unit_heat_utilities` overwrote them). Before any + simulation, the streams are those of the heat utilities the next + simulation would take, prepared as the synthesis prepares them. + Nothing is stored: with `cache_network`, the targets follow the + units' current states, not those of the network's synthesis. + + Examples + -------- + The liquids of the second example of the class at 10 K (whose + unsplit network falls short of the energy targets): + + >>> import biosteam as bst + >>> bst.main_flowsheet.set_flowsheet('HXN_targets_example') + >>> bst.settings.set_thermo(['Water', 'Ethanol'], cache=True) + >>> def process_stream(ID, T_in, T_out, P, **kmol_hr): + ... inlet = bst.Stream(ID + '_in', T=T_in, P=P, phase='l', + ... units='kmol/hr', **kmol_hr) + ... return bst.HXutility(ID, ins=inlet, T=T_out, rigorous=False) + >>> units = [process_stream('H1', 400., 320., 5e5, Water=60.), + ... process_stream('H2', 390., 330., 5e5, Ethanol=30.), + ... process_stream('C1', 310., 410., 5e5, Water=150.), + ... process_stream('H3', 360., 300., 101325., Water=200.)] + >>> HXN = bst.HeatExchangerNetwork('HXN', T_min_app=10.) + >>> sys = bst.System.from_units('sys', units=[*units, HXN]) + >>> sys.simulate() + >>> HXN.synthesis_info['status'] + 'best_effort' + + The targets [kW] are the synthesis's own, bit for bit: + + >>> targets = HXN.targets() + >>> targets.labels + ['C1', 'H3', 'H1', 'H2'] + >>> round(targets.Q_hot / 3600, 3), round(targets.Q_cold / 3600, 3) + (102.821, 209.035) + >>> info = HXN.synthesis_info + >>> targets.Q_hot == info['Q_hot_target'], targets.Q_cold == info['Q_cold_target'] + (True, True) + + At another minimum approach temperature: + + >>> HXN.targets(20.) + + + """ + from .targeting import PinchTargets + if T_min_app is None: T_min_app = self.T_min_app + if (hasattr(self, 'original_heat_exchangers') + and hasattr(self, 'original_heat_utils')): + hxs = self.original_heat_exchangers + hus = self.original_heat_utils # in stream order + cold = set(self.cold_indices) + return PinchTargets._from_exchangers( + hxs, [i not in cold for i in range(len(hxs))], T_min_app, + self._network_contributions(hus), self.force_ideal_thermo, + None, hus) + if self.system is None and not self.units: + raise RuntimeError('simulate the system (or give the facility its ' + 'units) before targeting') + hx_utils = self._get_original_heat_utilties() + hx_utils.sort(key=lambda x: x.duty) # as `_cost` sorts them + return PinchTargets.from_heat_utilities( + hx_utils, T_min_app, self._network_contributions(hx_utils), + self.force_ideal_thermo, self.sort_hus_by_T) + def get_original_hxs_associated_with_streams(self): # pragma: no cover original_units = self.system.units original_heat_utils = self.original_heat_utils diff --git a/hensmith/targeting.py b/hensmith/targeting.py index d347ead..f438167 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -14,7 +14,8 @@ (`PinchTargets.utility_targets`), the balanced composite curves, and the area, units, capital cost and total annualized cost targets (`PinchTargets.summary`); `supertarget` sweeps the minimum approach -temperature for the least total annualized cost. `plot_composite_curves`, +temperature for the least total annualized cost, and `network_report` +compares a synthesized network with its targets. `plot_composite_curves`, `plot_grand_composite_curve` and `plot_supertargets` draw them. """ import math @@ -31,7 +32,7 @@ _arrange_heat_utilities, _process_streams, _explicit_contributions, _contribution_key_level, _names_unit, _owner_chains, _unit_name) -__all__ = ('PinchTargets', 'supertarget', 'Supertargets', +__all__ = ('PinchTargets', 'supertarget', 'Supertargets', 'network_report', 'plot_composite_curves', 'plot_grand_composite_curve', 'plot_supertargets') @@ -317,12 +318,16 @@ def _utility_films(utilities, h_utilities, agents=()): key must name an agent of `utilities`, of `agents` or of biosteam's default lists. """ - if h_utilities is None: return [_H_DEFAULT] * len(utilities) + return _agent_films([u.agent for u in utilities], h_utilities, agents) + +def _agent_films(of, h_utilities, agents=()): + """`_utility_films` of the agents `of` (None for an unserved utility).""" + if h_utilities is None: return [_H_DEFAULT] * len(of) if not isinstance(h_utilities, Mapping): - return [_film_value(h_utilities, 'h_utilities')] * len(utilities) + return [_film_value(h_utilities, 'h_utilities')] * len(of) known = {a.ID for a in (*agents, *bst.HeatUtility.heating_agents, *bst.HeatUtility.cooling_agents)} - known.update(u.ID for u in utilities if u.agent is not None) + known.update(a.ID for a in of if a is not None) values = {} # agent ID -> (key, value) unknown = [] for key, value in h_utilities.items(): @@ -343,8 +348,8 @@ def _utility_films(utilities, h_utilities, agents=()): if unknown: raise ValueError('h_utilities keys that name no agent: ' f"{', '.join(map(repr, unknown))}") - return [values[u.ID][1] if u.ID in values else _H_DEFAULT - for u in utilities] + return [values[a.ID][1] if a is not None and a.ID in values + else _H_DEFAULT for a in of] def _T_at(segments, i, H): """Temperature of the segments `i` (see `_composite`) at heat `H`.""" @@ -498,8 +503,9 @@ class PinchTargets: (`utility_targets`) priced by biosteam, the balanced composite curves, and the targets of area, units, capital cost and total annualized cost (`summary`). Build it from heat utilities with `from_heat_utilities` (as - `hensmith.hxn_synthesis.synthesize_network` prepares them) or from - streams as `problem_table` takes them. + `hensmith.hxn_synthesis.synthesize_network` prepares them), from a + facility with `HeatExchangerNetwork.targets`, or from streams as + `problem_table` takes them. Parameters ---------- @@ -1776,6 +1782,302 @@ def __repr__(self): f'(TAC {self.optimum["TAC"]:.3e} USD/yr){bound}>') +def _profile_area(x, dT, inv_U, ID): + """ + Area [m²] of counter-current heat transfer over the duties `x` [kJ/hr] + (ascending) at the driving forces `dT` [K] there, linear in between (each + stretch at its log-mean temperature difference), with an overall 1/U of + `inv_U` [m²·K/kW]; inf with a RuntimeWarning naming the exchanger `ID` + at a driving force of `_DT_ZERO` or less. + """ + if dT.min() <= _DT_ZERO: + warn(f'{ID} transfers heat at a driving force of {dT.min():.6g} K', + RuntimeWarning) + return math.inf + d1, d2 = dT[:-1], dT[1:] + with np.errstate(divide='ignore', invalid='ignore'): + LMTD = np.where(np.abs(d1 - d2) <= 1e-12 * np.maximum(d1, d2), d1, + (d1 - d2) / np.log(d1 / d2)) + return float(np.sum(np.diff(x) / LMTD)) * inv_U / 3600. + +def _stage_profile(curve, H0, sign, f, Q): + """ + ``(knots, T)`` of a stream of `curve` through an exchanger whose duty x + runs from 0 to `Q` [kJ/hr] while the stream's enthalpy on the stage + (fraction `f` of the whole flow) is ``H0 + sign * x``: the duties in (0, + Q) at the breakpoints of the curve, and its temperature [K] at duties. + """ + knots = sign * (f * curve.H - H0) + return (knots[(knots > 0.) & (knots < Q)], + lambda x: np.interp((H0 + sign * x) / f, curve.H, curve.T)) + +def _check_targets(HXN, targets): + """Raise a ValueError unless `targets` are those of the process + streams of `HXN`'s network, in its stream order and current states.""" + hxs = HXN.original_heat_exchangers + units = targets.units + if (units is None or len(units) != len(hxs) + or any(u is not hx for u, hx in zip(units, hxs))): + raise ValueError("targets must be of the network's process streams " + "(the units of HXN.original_heat_exchangers, in that " + 'order), as HXN.targets() makes them') + if targets.force_ideal_thermo != bool(HXN.force_ideal_thermo): + raise ValueError(f'targets have force_ideal_thermo=' + f'{targets.force_ideal_thermo}, the network ' + f'{bool(HXN.force_ideal_thermo)}') + if targets.force_ideal_thermo: return + for c, hx in zip(targets.curves, hxs): + H_in, H_out = hx.ins[0].H, hx.outs[0].H + if (abs(c.H_in - H_in) > 1e-9 * abs(H_in) + or abs(c.H_out - H_out) > 1e-6 * abs(c.H_out - c.H_in)): + raise ValueError(f'targets are not of the current states of ' + f'{_unit_label(hx)} (simulated since?): make ' + 'them again with HXN.targets()') + +def _network_areas(HXN, targets, h, h_utilities, agents): + """ + ``{ID: (area, area_biosteam)}`` [m²] of the process exchangers and then + the utility exchangers of `HXN` with duty (see `network_report`), with + the film coefficients `h` (an (N,) array) and `h_utilities` (as + `PinchTargets.area_target` takes them; `agents`, more agents its keys + may name). A furnace takes no area. + """ + curves = targets.curves + is_hot = targets.is_hot + stages = {} # unit -> [(stream index, life stage)] + for lc in HXN.stream_life_cycles: + for st in lc.life_cycle: + stages.setdefault(st.unit, []).append((lc.index, st)) + process = [hx for hx in HXN.new_HXs if hx.total_heat_transfer > 1e-12] + utility = [hx for hx in HXN.new_HX_utils + if hx.heat_utilities and hx.total_heat_transfer > 1e-12] + of = [hx.heat_utilities[0].agent for hx in utility] + areas = {} + for hx in process: + (i, si), (j, sj) = stages[hx] + if not is_hot[i]: (i, si), (j, sj) = (j, sj), (i, si) + # x from the hot inlet (the cold outlet) end + Q = si.H_in - si.H_out + knots_h, T_h = _stage_profile(curves[i], si.H_in, -1., si.fraction, Q) + knots_c, T_c = _stage_profile(curves[j], sj.H_out, -1., sj.fraction, Q) + x = np.unique(np.concatenate([[0., Q], knots_h, knots_c])) + areas[hx.ID] = ( + _profile_area(x, T_h(x) - T_c(x), 1. / h[i] + 1. / h[j], hx.ID), + hx.design_results.get('Area', 0.) / _FT2_PER_M2) + for hx, agent, h_agent in zip(utility, of, + _agent_films(of, h_utilities, agents)): + if agent.isfuel: + areas[hx.ID] = (0., 0.) + continue + (i, si), = stages[hx] + hu = hx.heat_utilities[0] + # x from the process outlet (the utility inlet) end + H_in, H_out = si.H_in, si.H_out + heating = H_out > H_in + Q = abs(H_out - H_in) + knots, T_p = _stage_profile(curves[i], H_out, -1. if heating else 1., + si.fraction, Q) + x = np.unique(np.concatenate([[0., Q], knots])) + T_a, T_b = hu.inlet_utility_stream.T, hu.outlet_utility_stream.T + T_u = T_a + (T_b - T_a) * x / Q + dT = T_u - T_p(x) if heating else T_p(x) - T_u + areas[hx.ID] = ( + _profile_area(x, dT, 1. / h[i] + 1. / h_agent, hx.ID), + hx.design_results.get('Area', 0.) / _FT2_PER_M2) + return areas + +def network_report(HXN, targets=None, **options): + """ + Compare the heat exchanger network that a `HeatExchangerNetwork` + facility synthesized with the targets of its process streams: energy, + utilities, number of units, area, capital cost and total annualized cost + (TAC) of both, side by side. + + Parameters + ---------- + HXN : HeatExchangerNetwork + A simulated facility. + targets : PinchTargets, optional + Targets of the facility's process streams, as + `HeatExchangerNetwork.targets` makes them (the same units in the + same order, the same thermodynamic basis and the units' current + states). Defaults to ``HXN.targets()``. + **options + Options of `PinchTargets.summary`. The film coefficients `h` and + `h_utilities` and the economics `interest`, `years`, + `operating_hours` and `tea` apply to the network and the targets + alike (the operating hours default to the system's, if it has them + and neither `operating_hours` nor `tea` is given); the agents, `P`, + `heat_exchanger_type` and `material` to the targets only (the + network keeps biosteam's agents and costs). + + Returns + ------- + dict + 'T_min_app' and 'targets_T_min_app' [K], of the facility and of the + targets; 'status' (``HXN.synthesis_info['status']``); 'applied', + whether the network has a process exchanger; 'Q_hot' and 'Q_cold', + the heat of the network's heating and cooling utilities [kJ/hr], + process side (`HXN.actual_heat_util_load` is utility side); + 'utilities', ``{agent ID: {'duty', 'utility_duty', 'cost'}}`` + [kJ/hr, kJ/hr, USD/hr] (duty process side, a magnitude); + 'utility_cost' [USD/hr]; 'N_units', the exchangers with duty (a + furnace included), of which 'N_process_units' and + 'N_utility_units'; 'area', the network's area like for like with + the area target (see Notes) [m²]; 'area_biosteam', the area biosteam + sized [m²]; 'exchanger_areas', ``{ID: (area, area_biosteam)}`` of + each exchanger with duty [m²]; 'purchase_cost' and 'installed_cost' + of all the network's exchangers [USD]; 'annualized_capital_cost', + 'operating_cost' and 'TAC' [USD/yr], as `PinchTargets.summary` + computes them; 'targets', `PinchTargets.summary` of the targets. + + Raises + ------ + RuntimeError + If the facility was not simulated. + ValueError + If `targets` are not those of the facility's process streams in + their current states, or for an invalid option. + TypeError + For an unknown option. + + Notes + ----- + The network's 'area' is sized as the area target is: each exchanger + with the film coefficients of its two sides (1 kW/m²/K by default), + counter-current (a correction factor of 1), and integrated along the + exact temperature profiles of its streams (their curves in the targets) + against the process duty; a utility's temperature is linear in the duty + between its inlet and outlet, and a furnace takes no area (as fuel in + the targets). 'area_biosteam' is biosteam's own sizing: heuristic + overall coefficients (0.5 kW/m²/K, but 1 for condensing against + boiling, which no film coefficients reproduce), a correction factor for + two shells, the log-mean of the terminal temperature differences + (which overstates the mean difference along a glide) and the utility + duty (the process duty divided by the heat transfer efficiency). + + The capital and utility costs are those of the whole network + (`HXN.new_HXs` and `HXN.new_HX_utils`), not the facility's costs, + which are the differences from the original exchangers (and zero for + a network without process exchangers: the facility then keeps the + original exchangers, to which the utility exchangers of the network, + reported here, are equivalent). + + The targets are estimates, and the network may beat some of them: + + - the network serves each stream with one agent at its outlet (biosteam's + rule: the first agent of the list that can), while the targets place + the agents on the grand composite curve, so the network may use other + agents than the targets (with Linnhoff & Hindmarsh's problem at + 10 K, medium pressure steam and chilled water where the targets need + only low pressure steam and cooling water); + - the area target is a minimum only for equal film coefficients and the + same utilities: that problem's network takes 54.06 m² against a target + of 59.68 m², with hotter steam and colder coolant; + - the units target counts S - 1 per region between zeros of the + balanced grand composite curve: a network needs fewer units where a + subset of streams balances (6 against 7 in that problem) or where one + agent serves a stream the targets split between levels, and more for + repeated matches and splits. + + Examples + -------- + The liquids of the second example of `HeatExchangerNetwork` at 10 K, + with stream splitting, so that the network reaches the energy targets: + + >>> import biosteam as bst + >>> from hensmith import network_report + >>> bst.main_flowsheet.set_flowsheet('network_report_example') + >>> bst.settings.set_thermo(['Water', 'Ethanol'], cache=True) + >>> def process_stream(ID, T_in, T_out, P, **kmol_hr): + ... inlet = bst.Stream(ID + '_in', T=T_in, P=P, phase='l', + ... units='kmol/hr', **kmol_hr) + ... return bst.HXutility(ID, ins=inlet, T=T_out, rigorous=False) + >>> units = [process_stream('H1', 400., 320., 5e5, Water=60.), + ... process_stream('H2', 390., 330., 5e5, Ethanol=30.), + ... process_stream('C1', 310., 410., 5e5, Water=150.), + ... process_stream('H3', 360., 300., 101325., Water=200.)] + >>> HXN = bst.HeatExchangerNetwork('HXN', T_min_app=10., + ... stream_splitting=True) + >>> sys = bst.System.from_units('sys', units=[*units, HXN]) + >>> sys.simulate() + >>> report = network_report(HXN) + >>> targets = report['targets'] + >>> report['status'], report['applied'] + ('mer', True) + + The network reaches the hot utility target [kW], but serves each stream + with one agent at its outlet: medium pressure steam and chilled water, + where the targets need mostly low pressure steam and cooling water + [USD/hr]: + + >>> round(report['Q_hot'] / 3600, 3), round(targets['Q_hot'] / 3600, 3) + (102.821, 102.821) + >>> sorted(report['utilities']) + ['chilled_water', 'cooling_water', 'medium_pressure_steam'] + >>> round(report['utility_cost'], 3), round(targets['utility_cost'], 3) + (5.496, 3.58) + + With hotter steam and colder coolant, its area [m²] is below the area + target (biosteam's own sizing in between), and it needs fewer units than + the estimate: + + >>> round(report['area'], 2), round(report['area_biosteam'], 2), round(targets['area'], 2) + (50.62, 66.33, 70.3) + >>> report['N_units'], targets['N_units'] + (7, 9) + >>> round(report['TAC']), round(targets['TAC']) # USD/yr + (62041, 39396) + + """ + if not hasattr(HXN, 'new_HXs_hot_side'): + raise RuntimeError('simulate the heat exchanger network before ' + 'reporting on it') + if targets is None: targets = HXN.targets() + else: _check_targets(HXN, targets) + options = dict(options) + if options.get('operating_hours') is None and options.get('tea') is None: + hours = getattr(getattr(HXN, 'system', None), 'operating_hours', None) + if hours is not None: options['operating_hours'] = hours + options = targets._check_options(options) + get = options.get + crf, hours = _economics(get('interest'), get('years'), + get('operating_hours'), get('tea')) + areas = _network_areas( + HXN, targets, targets._film_coefficients(get('h')), + get('h_utilities'), + [*(get('heating_agents') or ()), *(get('cooling_agents') or ())]) + process = [hx for hx in HXN.new_HXs if hx.ID in areas] + utility = [hx for hx in HXN.new_HX_utils if hx.ID in areas] + hus = [hu for hx in utility for hu in hx.heat_utilities if hu.agent] + utilities = {} + for ID, group in bst.HeatUtility.heat_utilities_by_agent(hus).items(): + utilities[ID] = {'duty': abs(sum([hu.unit_duty for hu in group])), + 'utility_duty': abs(sum([hu.duty for hu in group])), + 'cost': sum([hu.cost for hu in group])} + utility_cost = sum([hu.cost for hu in hus], 0.) + exchangers = HXN.new_HXs + HXN.new_HX_utils + installed = sum([hx.installed_cost for hx in exchangers], 0.) + annualized = crf * installed + operating = hours * utility_cost + return { + 'T_min_app': HXN.T_min_app, 'targets_T_min_app': targets.T_min_app, + 'status': HXN.synthesis_info['status'], 'applied': bool(HXN.new_HXs), + 'Q_hot': sum([hu.unit_duty for hu in hus if hu.unit_duty > 0.], 0.), + 'Q_cold': -sum([hu.unit_duty for hu in hus if hu.unit_duty < 0.], 0.), + 'utilities': utilities, 'utility_cost': utility_cost, + 'N_units': len(process) + len(utility), + 'N_process_units': len(process), 'N_utility_units': len(utility), + 'area': sum([a for a, _ in areas.values()], 0.), + 'area_biosteam': sum([a for _, a in areas.values()], 0.), + 'exchanger_areas': areas, + 'purchase_cost': sum([hx.purchase_cost for hx in exchangers], 0.), + 'installed_cost': installed, 'annualized_capital_cost': annualized, + 'operating_cost': operating, 'TAC': annualized + operating, + 'targets': targets.summary(**options), + } + + def plot_composite_curves(targets, kind='real', utilities=None, ax=None, file=None, dpi=300): """ diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 4078b59..654205d 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -21,7 +21,12 @@ integration, the units target on references with process and utility pinches, the capital costs against biosteam's own costing, the economics, and the sweep against a closed-form optimum. -D. Plots: stable gids whose data equal the curves. +D. The facility and the report: the facility's targets equal the + synthesis's bit for bit (before and after a simulation, with approach + contributions and with replaced heat utilities), the report's areas + against terminal log-mean temperature differences, and no side effects + on the flowsheet, the agents' stream dumps or a later synthesis. +E. Plots: stable gids whose data equal the curves. Reference A: H1 400 -> 300 K and C1 330 -> 440 K, both 1 kW/K, at T_min_app = 10 K: 50 kW hot and 40 kW cold utility, shifted pinch 390 K. @@ -35,11 +40,13 @@ import pytest import biosteam as bst from numpy.testing import assert_allclose -from hensmith import (PinchTargets, Supertargets, supertarget, plot_composite_curves, - plot_grand_composite_curve, plot_supertargets) +from hensmith import (HeatExchangerNetwork, PinchTargets, Supertargets, supertarget, + network_report, plot_composite_curves, plot_grand_composite_curve, + plot_supertargets) from hensmith.hxn_synthesis import _problem_table, temperature_interval_pinch_analysis -from hensmith.targeting import _exchanger_costs, _economics -from test_hxn import build_system, cp_units, KEMP, simulate_HXN, _gid_artists +from hensmith.targeting import _exchanger_costs, _economics, _profile_area +from test_hxn import (build_system, cp_units, KEMP, simulate_HXN, simulate_strictly, + _gid_artists) from test_hxn_contributions import classic_cascade import test_hxn_targets @@ -209,6 +216,14 @@ def kemp_hxn(): sys, HXN = simulate_HXN(units, 10.) return units, HXN +@pytest.fixture(scope='module') +def kemp_hxn_replaced(): + """KEMP synthesized by the facility at 10 K, replacing the units' heat + utilities.""" + units = cp_units('test_targeting_kemp_hxn_replaced', KEMP, K) + sys, HXN = simulate_HXN(units, 10., replace_unit_heat_utilities=True) + return units, HXN + @pytest.fixture(scope='module') def quickstart(): """The targets of the documentation's quickstart system at 5 K (two @@ -881,6 +896,201 @@ def flash(*args, **kwargs): raise AssertionError('the curves are rebuilt') supertarget(partial, [0., 5.]) assert np.ptp(supertarget(partial, [5., 20.], refine=0)['Q_hot']) > 0. +# %% Facility and report + +def terminal_area(hx): + """Area [m2] of an exchanger of constant heat capacity streams from its + terminal temperatures, with U = 0.5 kW/m2/K and the process duty.""" + if isinstance(hx, bst.HXprocess): + hot = 0 if hx.ins[0].H > hx.outs[0].H else 1 + hi, ho = hx.ins[hot], hx.outs[hot] + ci, co = hx.ins[1 - hot], hx.outs[1 - hot] + Q = hi.H - ho.H + d1, d2 = hi.T - co.T, ho.T - ci.T + else: + hu = hx.heat_utilities[0] + pi, po = hx.ins[0], hx.outs[0] + ui, uo = hu.inlet_utility_stream.T, hu.outlet_utility_stream.T + Q = abs(po.H - pi.H) + if po.H > pi.H: d1, d2 = ui - po.T, uo - pi.T + else: d1, d2 = po.T - ui, pi.T - uo + LMTD = d1 if abs(d1 - d2) < 1e-9 else (d1 - d2) / math.log(d1 / d2) + return Q / kW / (0.5 * LMTD) + +def test_facility_targets_bitwise(kemp_hxn, kemp_hxn_replaced): + def assert_synthesis_targets(t, HXN): + info = HXN.synthesis_info + assert (t.Q_hot, t.Q_cold) == (info['Q_hot_target'], info['Q_cold_target']) + for k, contributions in enumerate((None, {'C1': 2.})): + units = cp_units(f'test_targeting_facility_{k}', KEMP, K) + HXN = HeatExchangerNetwork('HXN', T_min_app=10., units=units, + approach_contributions=contributions) + before = HXN.targets() # from the units' heat utilities + sys = bst.System.from_units('sys', units=[*units, HXN]) + simulate_strictly(sys) + after = HXN.targets() # from the network's process streams + for t in (before, after): + assert_synthesis_targets(t, HXN) + assert t.labels == ['C1', 'C3', 'H2', 'H4'] + assert t.T_min_app == 10. and t.force_ideal_thermo is False + assert t.approach_contributions == (None if contributions is None + else (2., None, None, None)) + assert all(u is hx for u, hx in zip(after.units, HXN.original_heat_exchangers)) + assert all(a is b for a, b in zip(after.heat_utilities, HXN.original_heat_utils)) + # another minimum approach temperature (the facility's is unchanged) + assert HXN.targets(20.).Q_hot == after.at(20.).Q_hot != after.Q_hot + assert HXN.T_min_app == 10. + assert_synthesis_targets(kemp_hxn[1].targets(), kemp_hxn[1]) + # the units' heat utilities now carry the network's (H2 and C3 are served + # by process exchangers alone), but the targets are those of the + # original streams + units, HXN = kemp_hxn_replaced + assert sorted(u.ID for u in units if not u.heat_utilities[0].duty) == ['C3', 'H2'] + t = HXN.targets() + assert_synthesis_targets(t, HXN) + assert t.labels == ['C1', 'C3', 'H2', 'H4'] + with pytest.raises(RuntimeError, match='before targeting'): + HeatExchangerNetwork('test_targeting_alone').targets() + +#: Keys of `network_report`. +REPORT_KEYS = ['T_min_app', 'targets_T_min_app', 'status', 'applied', 'Q_hot', 'Q_cold', + 'utilities', 'utility_cost', 'N_units', 'N_process_units', 'N_utility_units', + 'area', 'area_biosteam', 'exchanger_areas', 'purchase_cost', 'installed_cost', + 'annualized_capital_cost', 'operating_cost', 'TAC', 'targets'] + +def test_network_report(kemp_hxn): + units, HXN = kemp_hxn + report = network_report(HXN) + assert list(report) == REPORT_KEYS + assert report['applied'] is True and report['status'] == 'mer' + assert report['T_min_app'] == report['targets_T_min_app'] == 10. + exchangers = HXN.new_HXs + HXN.new_HX_utils + assert report['installed_cost'] == pytest.approx( + sum(hx.installed_cost for hx in exchangers), rel=1e-12) + assert report['purchase_cost'] == pytest.approx( + sum(hx.purchase_cost for hx in exchangers), rel=1e-12) + sized = [hx for hx in exchangers if 'Area' in hx.design_results] + assert report['area_biosteam'] == pytest.approx( + sum(hx.design_results['Area'] for hx in sized) / 10.763, rel=1e-12) + # like-for-like areas: the medium pressure steam heater (efficiency 0.9) + # is sized on the process duty + areas = report['exchanger_areas'] + assert sorted(areas) == sorted(hx.ID for hx in sized) + for hx in sized: + assert areas[hx.ID][0] == pytest.approx(terminal_area(hx), rel=1e-9), hx.ID + MP = [hx for hx in HXN.new_HX_utils if hx.ID in areas + and hx.heat_utilities[0].agent.ID == 'medium_pressure_steam'] + assert len(MP) == 1 and MP[0].heat_utilities[0].duty == pytest.approx(72000. / 0.9) + assert round(report['area'], 2) == 54.06 + assert report['area'] < report['targets']['area'] + # the network has fewer units than the estimate (H2 and C3 balance) + assert (report['N_units'], report['N_process_units'], report['N_utility_units']) == (6, 4, 2) + assert report['targets']['N_units'] == 7 + assert report['Q_hot'] >= report['targets']['Q_hot'] * (1 - 1e-9) + assert report['Q_hot'] == pytest.approx(20 * kW, rel=1e-9) + assert report['Q_cold'] == pytest.approx(60 * kW, rel=1e-9) + assert sorted(report['utilities']) == ['chilled_water', 'medium_pressure_steam'] + steam = report['utilities']['medium_pressure_steam'] + assert steam['duty'] == pytest.approx(20 * kW) and steam['utility_duty'] == pytest.approx(20 * kW / 0.9) + assert report['utility_cost'] == pytest.approx( + sum(u['cost'] for u in report['utilities'].values()), rel=1e-12) + assert report['utility_cost'] > report['targets']['utility_cost'] + crf = _economics()[0] + assert report['annualized_capital_cost'] == crf * report['installed_cost'] + assert report['operating_cost'] == 8000. * report['utility_cost'] + assert report['TAC'] == report['annualized_capital_cost'] + report['operating_cost'] + assert report['targets'] == HXN.targets().summary() + # options: film coefficients and the system's operating hours + halved = network_report(HXN, h=2., h_utilities=2.) + assert halved['area'] == pytest.approx(report['area'] / 2, rel=1e-12) + assert halved['targets']['area'] == pytest.approx(report['targets']['area'] / 2, rel=1e-12) + HXN.system.operating_hours = 4000. + try: + hours = network_report(HXN) + assert hours['operating_cost'] == 4000. * report['utility_cost'] + assert hours['targets']['operating_cost'] == 4000. * report['targets']['utility_cost'] + assert network_report(HXN, operating_hours=100.)['operating_cost'] == ( + 100. * report['utility_cost']) + finally: + HXN.system.operating_hours = None + with pytest.raises(TypeError, match='unexpected options'): + network_report(HXN, hours=1.) + with pytest.raises(RuntimeError, match='simulate the heat exchanger network'): + network_report(HeatExchangerNetwork('test_targeting_report_alone')) + # targets of the same streams in the facility's stream order (KEMP's own + # order arranges like the facility's), and of other ones + hus = [u.heat_utilities[0] for u in units] + same = PinchTargets.from_heat_utilities(hus, 10.) + assert network_report(HXN, same)['targets'] == report['targets'] + with pytest.raises(ValueError, match="of the network's process streams"): + network_report(HXN, PinchTargets.from_heat_utilities(hus[::-1], 10.)) + with pytest.raises(ValueError, match='force_ideal_thermo'): + network_report(HXN, PinchTargets.from_heat_utilities(hus, 10., force_ideal_thermo=True)) + inlet = HXN.original_heat_exchangers[0].ins[0] + T = inlet.T + try: # a stale state + inlet.T = T + 1. + with pytest.raises(ValueError, match='current states'): + network_report(HXN, same) + finally: + inlet.T = T + assert network_report(HXN, same)['targets'] == report['targets'] + # no process match: the report describes the utility exchangers + rows = [('H1', 320., 300., 1.), ('C1', 330., 340., 1.)] + _, alone = simulate_HXN(cp_units('test_targeting_report_utilities', rows), 10.) + report = network_report(alone) + assert report['applied'] is False and alone.installed_costs['Heat exchangers'] == 0. + assert (report['N_units'], report['N_process_units']) == (2, 0) + for hx in alone.new_HX_utils: + assert report['exchanger_areas'][hx.ID][0] == pytest.approx(terminal_area(hx), rel=1e-9) + assert report['installed_cost'] == sum(hx.installed_cost for hx in alone.new_HX_utils) > 0. + # a zero (or crossed) driving force + with pytest.warns(RuntimeWarning, match='HX transfers heat at a driving force of 0 K'): + assert _profile_area(np.array([0., 1.]), np.array([5., 0.]), 2., 'HX') == math.inf + assert _profile_area(np.array([0., kW]), np.array([5., 5.]), 2., 'HX') == 2. / 5. + +def test_no_side_effects(kemp_hxn): + units, HXN = kemp_hxn + # the furnace case of test_capital_target_formula: the network heats C1 + # in a furnace + _, furnace_HXN = simulate_HXN(cp_units('test_targeting_side_effects', FURNACE), 10.) + report = network_report(furnace_HXN) + assert 'natural_gas' in report['utilities'] + furnace = [hx.ID for hx in furnace_HXN.new_HX_utils + if hx.heat_utilities and hx.heat_utilities[0].agent + and hx.heat_utilities[0].agent.isfuel] + assert len(furnace) == 1 and report['exchanger_areas'][furnace[0]] == (0., 0.) + gas = agent('natural_gas') + def furnace_cost(): + # priced with the agent's own stream dump (on which it leaves its + # streams), in a temporary flowsheet + with bst.Flowsheet('test_targeting_furnace_cost'): + hu = bst.HeatUtility() + hu(10 * kW, 450., 500., agent=gas) + cost = hu.cost + hu.empty() + return cost + furnace_cost() + before = furnace_cost() + facilities = (HXN, furnace_HXN) + Q = [[float(hx.Q).hex() for hx in facility.new_HXs] for facility in facilities] + agents = bst.HeatUtility.heating_agents + bst.HeatUtility.cooling_agents + dumps = [(a.utility_stream_dump, list(a.utility_stream_dump)) for a in agents] + registered = lambda: {ID: id(s) for ID, s in bst.main_flowsheet.stream.data.items()} + registry = registered() + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter('always') + for facility in facilities: + facility.targets().summary() + network_report(facility) + assert not [w for w in caught if issubclass(w.category, RuntimeWarning)] + assert registered() == registry + for a, (dump, items) in zip(agents, dumps): + assert a.utility_stream_dump is dump and a.utility_stream_dump == items + assert furnace_cost() == before + for facility in facilities: simulate_strictly(facility.system) + assert [[float(hx.Q).hex() for hx in facility.new_HXs] for facility in facilities] == Q + # %% Plots def test_plot_composite_curves_gids(tmp_path): From 2e42baaa85f8868e4aeec0c6cb8c8c8654d944e3 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Fri, 16 Oct 2026 22:56:47 +0000 Subject: [PATCH 07/52] document targeting and supertargeting Document hensmith.targeting, added by the three previous commits. - Tutorial chapter 5 (05_targeting.rst, script examples/ch05_targeting.py): the facility's targets (HXN.targets(), equal to synthesis_info's bit for bit; process side 2.828e+08 against the utility side 2.977e+08 kJ/hr of chapter 4, divided by eta 0.95), the utility targets with biosteam's agents, the composite, balanced composite and grand composite curves drawn by the library, the area, units, capital and TAC targets, the T_min_app sweep, and the network report beside its targets with the caveats. Every number comes from a capture. The script checks that the library's composites are chapter 2's as knot lists and that the grand composite arrays are equal. - The quickstart sweep's optimum is inside the sweep (1.848 K), but it sits at the threshold edge, not at a trade-off: the cold utility is zero up to it, and operating_hours=2000 does not move it. A short trade_off region therefore adds the spec's fallback, the closed-form pair of test_optimum_T_min_app_closed_form, with its optimum at 21.59 K and a second sweep figure. - API: a new targeting page, with PinchTargets and Supertargets :no-members: plus one automethod per public method, UtilityTarget, and the four functions. api.rst now says thirteen names and lists the seven new ones. The facility page gains the targets automethod and a sentence. - concepts: the curves are library functions; a subsection "Multiple utilities on the grand composite curve" (levels, list order, fuel last, the closed rule, levels with contributions, served intervals, biosteam pricing over eta); a section "Supertargeting: area, units, capital and total cost" (balanced composites, the Bath formula, units per region of the balanced GCC as an estimate, N C(A/N) with biosteam's correlations, TAC and CRF, the sweep and contribution scaling, the network report and its limits); a Validation paragraph on tests/test_targeting.py; the no-cost-optimization limitation now points to targeting, plus a new "Targets are estimates" bullet; three references. - Prose that became false: chapter 2 no longer says the curves are not library functions, chapter 4's sweep caption and the concepts caption point to chapter 5. - Wiring: build_all.py, the _demo_src README outputs table, the tutorial toctree and intro, "Where to next" of chapters 1-4, README.md, the landing page, contributing.rst (three public modules, targeting.py and test_targeting.py). One library change, cosmetic: plot_composite_curves(kind='balanced') drew a latent heating agent's label left of its flat, which runs off the axes over the tick labels when the flat starts near H = 0, as on the quickstart. The label now sits above the flat, ending at its warm end (a cooling label below its flat, from its cold end), where neither monotone curve can pass, with some headroom. Gids and data are unchanged. Validation: - Only ch05_targeting.py was run (13 s). git status shows only the new ch05 captures and figures; the captures are identical across two runs. - SPHINX_OFFLINE=1 sphinx-build -T -E -W --keep-going -b html (sphinx 9.1.0): build succeeded with 0 warnings; no unresolved cross-reference on the changed pages. - tests/test_targeting.py and the targeting doctests: 33 passed in 12 s. - Full suite (-n 4): 1030 passed in 221 s, as after the rebase onto dev (1030 passed in 207 s); no test changed. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- README.md | 6 +- docs/_demo_src/README.md | 5 +- docs/_demo_src/build_all.py | 1 + docs/_demo_src/examples/ch05_targeting.py | 197 +++++++++++ docs/source/API/api.rst | 17 +- docs/source/API/heat_exchanger_network.rst | 8 +- docs/source/API/targeting.rst | 70 ++++ docs/source/_generated/ch05_report.txt | 15 + docs/source/_generated/ch05_summary.txt | 14 + docs/source/_generated/ch05_sweep.txt | 13 + docs/source/_generated/ch05_targets.txt | 5 + docs/source/_generated/ch05_trade_off.txt | 8 + docs/source/_generated/ch05_utilities.txt | 7 + .../tutorial_05_balanced_composites.png | Bin 0 -> 80858 bytes .../examples/tutorial_05_composite_curves.png | Bin 0 -> 76850 bytes .../examples/tutorial_05_grand_composite.png | Bin 0 -> 59673 bytes .../examples/tutorial_05_supertargets.png | Bin 0 -> 72674 bytes .../images/examples/tutorial_05_trade_off.png | Bin 0 -> 107933 bytes docs/source/concepts.rst | 210 ++++++++++- docs/source/contributing/contributing.rst | 22 +- docs/source/index.rst | 6 +- docs/source/tutorial/01_quickstart.rst | 2 + docs/source/tutorial/02_pinch_analysis.rst | 14 +- docs/source/tutorial/03_network_anatomy.rst | 2 + docs/source/tutorial/04_configuring.rst | 8 +- docs/source/tutorial/05_targeting.rst | 332 ++++++++++++++++++ docs/source/tutorial/index.rst | 5 +- hensmith/targeting.py | 24 +- 28 files changed, 955 insertions(+), 36 deletions(-) create mode 100644 docs/_demo_src/examples/ch05_targeting.py create mode 100644 docs/source/API/targeting.rst create mode 100644 docs/source/_generated/ch05_report.txt create mode 100644 docs/source/_generated/ch05_summary.txt create mode 100644 docs/source/_generated/ch05_sweep.txt create mode 100644 docs/source/_generated/ch05_targets.txt create mode 100644 docs/source/_generated/ch05_trade_off.txt create mode 100644 docs/source/_generated/ch05_utilities.txt create mode 100644 docs/source/_static/images/examples/tutorial_05_balanced_composites.png create mode 100644 docs/source/_static/images/examples/tutorial_05_composite_curves.png create mode 100644 docs/source/_static/images/examples/tutorial_05_grand_composite.png create mode 100644 docs/source/_static/images/examples/tutorial_05_supertargets.png create mode 100644 docs/source/_static/images/examples/tutorial_05_trade_off.png create mode 100644 docs/source/tutorial/05_targeting.rst diff --git a/README.md b/README.md index 10d8f23..203fc40 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,11 @@ see the documentation for the limits with real thermodynamics). Each stream can also carry its own approach temperature contribution (`HeatExchangerNetwork(..., approach_contributions={unit: dT})`): a match must then keep the sum of its two streams' contributions instead of -`T_min_app`. +`T_min_app`. `hensmith.targeting` targets what any network of the streams +can achieve before one is synthesized (several utilities priced by BioSTEAM, +area, units, capital and total annualized cost: `HXN.targets()`), finds the +`T_min_app` of the least targeted total annualized cost (`supertarget`), and +compares a synthesized network with its targets (`network_report`). ```python import biosteam as bst # hensmith units plug into BioSTEAM systems diff --git a/docs/_demo_src/README.md b/docs/_demo_src/README.md index 3afd13c..9ea7a3b 100644 --- a/docs/_demo_src/README.md +++ b/docs/_demo_src/README.md @@ -26,6 +26,7 @@ directories themselves. | `examples/ch02_pinch_analysis.py` | `_static/images/examples/tutorial_02_composite_curves.png`, `tutorial_02_grand_composite.png`; `_generated/ch02_threshold.txt`, `ch02_table.txt`, `ch02_compare.txt` | | `examples/ch03_network_anatomy.py` | `_static/images/examples/tutorial_03_hxn_flowsheet_light.png`, `…_hxn_flowsheet_dark.png`, `tutorial_03_pinch_diagram_minimal.png`; `_generated/ch03_flowsheet.txt`, `ch03_life_cycles.txt`, `ch03_stage.txt`, `ch03_pinch_Ts.txt`, `ch03_accounting.txt` | | `examples/ch04_configuring.py` | `_static/images/examples/tutorial_04_T_min_app_sweep.png`, `tutorial_04_ten_streams_pinch_diagram.png`; `_generated/ch04_sweep.txt`, `ch04_splitting.txt`, `ch04_contributions.txt`, `ch04_ignored.txt`, `ch04_ten_streams.txt` | +| `examples/ch05_targeting.py` | `_static/images/examples/tutorial_05_composite_curves.png`, `tutorial_05_balanced_composites.png`, `tutorial_05_grand_composite.png`, `tutorial_05_supertargets.png`, `tutorial_05_trade_off.png`; `_generated/ch05_targets.txt`, `ch05_utilities.txt`, `ch05_summary.txt`, `ch05_sweep.txt`, `ch05_trade_off.txt`, `ch05_report.txt` | | `make_hero_gif.py` | `_static/images/demo/hero_light.gif`, `hero_dark.gif` (8 s loop, 20 fps, 2000 × 720), `hero_light_still.png`, `hero_dark_still.png` (the frame-0 stills served under `prefers-reduced-motion`) | | `build_demo.py` | `_static/quickstart_demo.html` — the interactive quickstart demo, filled in from `quickstart_demo_template.html` | | `make_poster.py` | `_static/images/examples/quickstart_demo_poster.png` — the README poster that links to the demo (2400 × 1260) | @@ -82,9 +83,9 @@ Three mechanisms, all enforced at regeneration time: - **Regions.** Each chapter script marks the code the page shows with `# [start:]` / `# [end:]` comments, and the `.rst` page pulls it in with `literalinclude` + `:start-after:` / `:end-before:`. The reader - therefore sees code that *was executed*, not a retyped copy. Chapters 02–04 + therefore sees code that *was executed*, not a retyped copy. Chapters 02–05 each build the quickstart system in a `system` region whose body is exactly - chapter 01's `build` + `network` + `simulate` bodies, so all four chapters + chapter 01's `build` + `network` + `simulate` bodies, so all five chapters provably describe one system. - **Captures.** `_common.capturing(name)` redirects everything printed inside a block into `_generated/.txt` (and echoes it), and diff --git a/docs/_demo_src/build_all.py b/docs/_demo_src/build_all.py index efd8883..fb5fa58 100644 --- a/docs/_demo_src/build_all.py +++ b/docs/_demo_src/build_all.py @@ -30,6 +30,7 @@ 'examples/ch02_pinch_analysis.py', 'examples/ch03_network_anatomy.py', 'examples/ch04_configuring.py', + 'examples/ch05_targeting.py', 'make_hero_gif.py', 'build_demo.py', # consumes the chapter-01 captures and figures, and the logo 'make_poster.py', # consumes the chapter-01 pinch diagram and the logo diff --git a/docs/_demo_src/examples/ch05_targeting.py b/docs/_demo_src/examples/ch05_targeting.py new file mode 100644 index 0000000..3eb45cc --- /dev/null +++ b/docs/_demo_src/examples/ch05_targeting.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +# hensmith: Heat Exchanger Network Synthesis, Modeling, Integration, +# Thermodynamics, and Heuristics +# Copyright (C) 2020-, Sarang Bhagwat +# +# This module is under the UIUC open-source license. See +# github.com/BioSTEAMDevelopmentGroup/hensmith/blob/master/LICENSE.txt +# for license details. +"""Tutorial chapter 05 (docs/source/tutorial/05_targeting.rst): targeting and +supertargeting with ``hensmith.targeting`` -- the facility's targets +(``HXN.targets()``), the multiple-utility targets priced by biosteam, the +composite, balanced composite and grand composite curves drawn by the +library, the area, units, capital and total annualized cost targets, the +``T_min_app`` sweep for the least total annualized cost (on the quickstart +system and on a two-stream problem with an interior optimum), and the report +that compares the synthesized network with its targets. Regions between +``# [start:x]`` / ``# [end:x]`` are literalinclude'd by the page; everything +else is plumbing. + + python docs/_demo_src/examples/ch05_targeting.py +""" +import sys +import pathlib +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1])) +import _common # noqa: E402 (sets NUMBA_DISABLE_JIT / DISABLE_PREFERENCES, Agg) +from _common import capturing, save + + +# [start:imports] +import numpy as np +import matplotlib.pyplot as plt +from hensmith import (supertarget, network_report, plot_composite_curves, + plot_grand_composite_curve, plot_supertargets) +# [end:imports] + + +def knots(T, H): + """Knots [K, kJ/hr] of a polyline without its zero-length steps and + without the points inside a straight run, so that two polylines tracing + the same curve on different grids compare knot by knot.""" + T, H = np.asarray(T, float), np.asarray(H, float) + step = np.r_[True, (np.abs(np.diff(T)) > 1e-9) | (np.abs(np.diff(H)) > 1e-6)] + T, H = T[step], H[step] + if T.size < 3: return T, H + a, b, c = slice(None, -2), slice(1, -1), slice(2, None) + cross = (T[b] - T[a]) * (H[c] - H[a]) - (H[b] - H[a]) * (T[c] - T[a]) + bend = np.abs(cross) > 1e-9 * np.abs(T[c] - T[a]) * np.abs(H[c] - H[a]) + keep = np.r_[True, bend, True] + return T[keep], H[keep] + + +def main(): + # [start:system] + 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() + # [end:system] + with capturing('ch05_targets'): + # [start:targets] + 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') + # [end:targets] + with capturing('ch05_utilities'): + # [start:utilities] + 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') + # [end:utilities] + # [start:composites] + fig, ax = plot_composite_curves(targets) + fig_balanced, ax_balanced = plot_composite_curves(targets, kind='balanced', + utilities=utilities) + # [end:composites] + save(fig, 'tutorial_05_composite_curves.png') + save(fig_balanced, 'tutorial_05_balanced_composites.png') + plt.close(fig); plt.close(fig_balanced) + # [start:gcc] + fig, ax = plot_grand_composite_curve(targets, utilities=utilities) + # [end:gcc] + save(fig, 'tutorial_05_grand_composite.png') + plt.close(fig) + with capturing('ch05_summary'): + # [start:supertargets] + print('units:', targets.units_target(utilities)) + capital = targets.capital_cost_target(utilities) + print(f"{capital['N_shells']} shells of {capital['shell_area']:.4g} m2, {capital['type']}") + summary = targets.summary() # 8000 h/yr, 10 % interest over 10 years + for key, value in summary.items(): + print(f'{key:<24} {value:.6g}') + # [end:supertargets] + with capturing('ch05_sweep'): + # [start:sweep] + 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 + for r in sweep.records: + if r['T_min_app'] in (1., 2., 5., 10., 15., 20., 25., 30.) or r is optimum: + 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) + # [end:sweep] + save(fig, 'tutorial_05_supertargets.png') + plt.close(fig) + with capturing('ch05_trade_off'): + # [start:trade_off] + 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) + # [end:trade_off] + save(fig, 'tutorial_05_trade_off.png') + plt.close(fig) + with capturing('ch05_report'): + # [start:report] + 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}') + # [end:report] + # plumbing checks: the library's curves are chapter 2's + from examples.ch02_pinch_analysis import composite_curves, grand_composite + ours = targets.composite_curves() + theirs = composite_curves(targets.table, T_min_app=5.) + for (T, H), (T2, H2) in zip((ours[:2], ours[2:]), (theirs[:2], theirs[2:])): + (T, H), (T2, H2) = knots(T, H), knots(T2, H2) + assert T.size == T2.size, (T.size, T2.size) + assert np.allclose(T, T2, rtol=0, atol=1e-6) and np.allclose(H, H2, rtol=1e-9, atol=1e-3) + T, H = targets.grand_composite_curve() + H2, T2 = grand_composite(targets.table) + assert np.array_equal(T, T2) and np.array_equal(H, H2) + assert targets.Q_hot == info['Q_hot_target'] and targets.Q_cold == info['Q_cold_target'] + assert report['Q_hot'] >= targets.Q_hot * (1 - 1e-9) and report['status'] == 'mer' + assert abs(report['utility_cost'] - target['utility_cost']) < 1e-6 * target['utility_cost'] + assert abs(pair_sweep.T_min_app - 21.595) <= 0.02 and not pair_sweep.at_bound + + +if __name__ == '__main__': + main() diff --git a/docs/source/API/api.rst b/docs/source/API/api.rst index f3803ee..d7d2be5 100644 --- a/docs/source/API/api.rst +++ b/docs/source/API/api.rst @@ -3,10 +3,13 @@ API Reference .. currentmodule:: hensmith -hensmith exports six names. :class:`HeatExchangerNetwork` is the BioSTEAM -facility most users need; the functions and classes of +hensmith exports thirteen names. :class:`HeatExchangerNetwork` is the +BioSTEAM facility most users need; the functions and classes of ``hensmith.hxn_synthesis`` are the pinch analysis and synthesis machinery -behind it, usable on their own. +behind it, usable on their own; and those of ``hensmith.targeting`` target +utilities, area, units, capital and total annualized cost, find the +``T_min_app`` of the least targeted total annualized cost, and compare a +network with its targets. .. autosummary:: @@ -16,12 +19,20 @@ behind it, usable on their own. synthesize_network StreamLifeCycle plot_pinch_diagram + PinchTargets + supertarget + Supertargets + network_report + plot_composite_curves + plot_grand_composite_curve + plot_supertargets .. toctree:: :maxdepth: 2 heat_exchanger_network hxn_synthesis + targeting .. note:: diff --git a/docs/source/API/heat_exchanger_network.rst b/docs/source/API/heat_exchanger_network.rst index 02367bc..1659727 100644 --- a/docs/source/API/heat_exchanger_network.rst +++ b/docs/source/API/heat_exchanger_network.rst @@ -13,7 +13,11 @@ 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 ``_HXN``. See :doc:`../tutorial/index` for a worked example and :doc:`../concepts` for the -method. +method. :meth:`~HeatExchangerNetwork.targets` returns the targets of the +facility's streams (a :class:`PinchTargets`: utilities, area, units, capital +and total annualized cost) without synthesizing anything, and +:func:`network_report` compares the synthesized network with them +(:doc:`targeting`). .. autoclass:: HeatExchangerNetwork :no-members: @@ -21,6 +25,8 @@ method. .. automethod:: HeatExchangerNetwork.plot_pinch_diagram +.. automethod:: HeatExchangerNetwork.targets + Constructor options ------------------- diff --git a/docs/source/API/targeting.rst b/docs/source/API/targeting.rst new file mode 100644 index 0000000..b81e101 --- /dev/null +++ b/docs/source/API/targeting.rst @@ -0,0 +1,70 @@ +Targeting and supertargeting (hensmith.targeting) +================================================= + +.. currentmodule:: hensmith + +``hensmith.targeting`` says what any heat exchanger network of a set of +process streams can achieve, before (or instead of) synthesizing one. +:class:`PinchTargets` holds the problem table of the streams and derives from +it the composite and grand composite curves, the targets of several utilities +priced by biosteam (:class:`~hensmith.targeting.UtilityTarget`), the balanced +composite curves, and the targets of area, units, capital cost and total +annualized cost. :func:`supertarget` sweeps the minimum approach temperature +for the least total annualized cost and returns :class:`Supertargets`; +:func:`network_report` compares a synthesized network with its targets; and +:func:`plot_composite_curves`, :func:`plot_grand_composite_curve` and +:func:`plot_supertargets` draw them. A facility's targets come from +:meth:`HeatExchangerNetwork.targets`. Nothing here changes the synthesis: +:doc:`../tutorial/05_targeting` shows every function on the quickstart system, +and :doc:`../concepts` explains the methods and their limits. + +.. autoclass:: PinchTargets + :no-members: + +.. automethod:: PinchTargets.from_heat_utilities + +.. automethod:: PinchTargets.at + +.. automethod:: PinchTargets.composite_curves + +.. automethod:: PinchTargets.grand_composite_curve + +.. automethod:: PinchTargets.utility_targets + +.. automethod:: PinchTargets.balanced_composite_curves + +.. automethod:: PinchTargets.balanced_grand_composite_curve + +.. automethod:: PinchTargets.area_target + +.. automethod:: PinchTargets.units_target + +.. automethod:: PinchTargets.capital_cost_target + +.. automethod:: PinchTargets.summary + +.. autoclass:: hensmith.targeting.UtilityTarget + :no-members: + +.. autofunction:: supertarget + +.. autoclass:: Supertargets + :no-members: + +.. automethod:: Supertargets.to_frame + +.. autofunction:: network_report + +.. autofunction:: plot_composite_curves + +.. autofunction:: plot_grand_composite_curve + +.. autofunction:: plot_supertargets + +.. note:: + + :class:`~hensmith.targeting.UtilityTarget` is not exported by + ``hensmith``; it is documented because + :meth:`PinchTargets.utility_targets` returns it. The private helpers of + ``hensmith.targeting`` (those whose names start with an underscore) are not + part of the supported API. diff --git a/docs/source/_generated/ch05_report.txt b/docs/source/_generated/ch05_report.txt new file mode 100644 index 0000000..7ee3b63 --- /dev/null +++ b/docs/source/_generated/ch05_report.txt @@ -0,0 +1,15 @@ + 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 diff --git a/docs/source/_generated/ch05_summary.txt b/docs/source/_generated/ch05_summary.txt new file mode 100644 index 0000000..aec0649 --- /dev/null +++ b/docs/source/_generated/ch05_summary.txt @@ -0,0 +1,14 @@ +units: {'above': 5, 'below': 1, 'total': 6} +6 shells of 991 m2, 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 +purchase_cost 710660 +installed_cost 2.28172e+06 +annualized_capital_cost 371339 +operating_cost 1.47169e+07 +TAC 1.50882e+07 diff --git a/docs/source/_generated/ch05_sweep.txt b/docs/source/_generated/ch05_sweep.txt new file mode 100644 index 0000000..78d7e21 --- /dev/null +++ b/docs/source/_generated/ch05_sweep.txt @@ -0,0 +1,13 @@ + +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.848 2.808e+08 0 6836 5 2.482e+06 4.04e+05 1.454e+07 14943211 +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 5168 9 2.272e+06 3.698e+05 1.532e+07 15690339 +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.848 K, at the bound of the sweep: False diff --git a/docs/source/_generated/ch05_targets.txt b/docs/source/_generated/ch05_targets.txt new file mode 100644 index 0000000..69a1df1 --- /dev/null +++ b/docs/source/_generated/ch05_targets.txt @@ -0,0 +1,5 @@ + +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 diff --git a/docs/source/_generated/ch05_trade_off.txt b/docs/source/_generated/ch05_trade_off.txt new file mode 100644 index 0000000..187be33 --- /dev/null +++ b/docs/source/_generated/ch05_trade_off.txt @@ -0,0 +1,8 @@ + +Q_hot 4.17333e+06 +area 719.848 +N_units 3 +installed_cost 439986 +annualized_capital_cost 71605.6 +operating_cost 20032 +TAC 91637.6 diff --git a/docs/source/_generated/ch05_utilities.txt b/docs/source/_generated/ch05_utilities.txt new file mode 100644 index 0000000..f862e20 --- /dev/null +++ b/docs/source/_generated/ch05_utilities.txt @@ -0,0 +1,7 @@ +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 diff --git a/docs/source/_static/images/examples/tutorial_05_balanced_composites.png b/docs/source/_static/images/examples/tutorial_05_balanced_composites.png new file mode 100644 index 0000000000000000000000000000000000000000..1e726f5d3fa6baa94b53b7798d1a040998c4a038 GIT binary patch literal 80858 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%0AUjAaiQM3ZrleG$o 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 W`KX&}s~Z0hB1aU}so4sry#5Em(h>>) literal 0 HcmV?d00001 diff --git a/docs/source/_static/images/examples/tutorial_05_composite_curves.png b/docs/source/_static/images/examples/tutorial_05_composite_curves.png new file mode 100644 index 0000000000000000000000000000000000000000..34e7a358587f23f8c81082f411c506f7e2598efe GIT binary patch literal 76850 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 Dd^fhlFYaBiwF2g%zs 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 OsVZwJWhxxK@IL@xxT|ph literal 0 HcmV?d00001 diff --git a/docs/source/_static/images/examples/tutorial_05_supertargets.png b/docs/source/_static/images/examples/tutorial_05_supertargets.png new file mode 100644 index 0000000000000000000000000000000000000000..69030d4727bb7eae37a5e6b065439c3a03452d69 GIT binary patch literal 72674 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 literal 0 HcmV?d00001 diff --git a/docs/source/_static/images/examples/tutorial_05_trade_off.png b/docs/source/_static/images/examples/tutorial_05_trade_off.png new file mode 100644 index 0000000000000000000000000000000000000000..4e5461d95313ea755b321b44ac1aef3da480591a GIT binary patch literal 107933 zcmcHhc~s78`v#2P8IlH?2PqXrX&@?+3Pl=dxKpCiM1w{}nnN^+&>*6sQAt!(nu`X_ zqedYOL^RMK?{Q`C{e9MNt>1s|+gf`)>)HGM-1qglhVwkm<2cSMNc+$MCi*q>6bgmu zpqh#{38*_oPRxW33 z938evZrv&=EiPc~>gsexUP{XTzfVXyx}1{oyZRMhLRsmgcJvH|!e2)Ip>b$*Q>4&P zCpITG6S+$~%gLm4*LUm7!A4lbePcgh2E-hMxHm0GeB9Vd1 zIoQ8!GGybO?%Q=CP^gem^Y_Ad{j!Iv>M0i+95d1xPu0I2S}yxx{7Hy%#*tGO1ONH6 zPsmpU+bn{y+ZuAiI2u(7vjMhS!xLTmh7KA3r|i40+oYv^bV3CwBVf z_Y}?xQTz_Y%BPBpi<`XAS`g82YbGD|o}J7}ACdn%>fu4Qb?feT6r6Kx3&|}X(fPmk zJb=rZi-NUn3zaI8Et#CB3$j^Pfz9%4Y!{i2d?qQpK7>A zVf?vG&5hU8qB{CX@r^9A`@$CHn!j^~J>Ml1-z=iLEKA+UVO*ZH=OV4snKLbOyKkur zYieoHP{P8(a%L+prijcviua*Sb@*T>&cilsAT9glnWbiEPVuOnu|8Rehv$wQIT9`B zcyD^^;`EP%k#!h}jg95=`89AQMsjhft5Tc=&Hw)|)#TTv2M6%`7w_ER<=^c=Z5do# zdLH4&Hds85WUAQr@qv9~7K`7|%y;1bot^PMvY(89)v8sUFPtCz|BOE%VEO#--*)+o zi@*8cRbvwS+qZ9jy$_MneD?I|GRhYB@e`^}PEYnm$@?_1H9ZPW5`DJ`S+k?FoGtN2(mObU%$rbhM6 z4jl?BX=~Hp?ebAg72lBl)c1RRO-&gVRk+~n80$u1;gu2+5^ZB$IeJ?66pku-{Tf&A zaGC$*JdmdUNK?`{f5V$MZ`ik6H!ANw-MNK)MNeg=s`1;i{~QDp*(EtfvBoW`ts)x_ z-(#m;zWiuUp{GIIPFc_C(=#`OHR#29z&Xs1#sexiCwRUPR^^}bGdPJdz1 zx-DHfUww1qh30#!SFQ~I^ZoU@?R=GNchJhU>o^_|EM%!*Q-#d5q?2#*1u4rtg zHx667Q;43PUiz@Qx>ydUudlDFp<#4ubMwSOI@yB&)ojGh!07Rrl@rlpXT&dpEF-{N`7g)$RSQAFnMS zEWDiZ?*02ES<$}h1$xJ2B12csZRX*fJ}NEps@3%7jT?n5R_s}+BhP(^N**3{6?%*-?{lZp_Is{P(&mw9`{rT6=X*8zQfeVn?f>?cp2 zq@$y|e(rO-*6n-uBr%`YdtNy2JkPF6i~gmVMyN-uBk z!9tHI-A^As(j_D$Y~YeIVRz`tK6qSP`$Uo3=Od}whO04GaM| zjjc13Z`YA5n--DOyu7^J$me|?8nRx$oI+k-TgPy+{;rMB@7by2dU`FvMcMy^P$>?F zncqqnzd6PI_f*H=#zS|j>{^npI_x`5>B)D~7;iH$y}hKQEP#Q5SN61_8E<%a_x&3!AD+O(#V;ujRO^2p=kM;o(k^kgVrvm?ogNxb*Hr*Df=w%d163ta|l68xDc zafrWZsv(>ll!pB+DypgxbYISnbqUKmzrQr>JI2Aubef{Pfpy?{zt3jt{-Q#9isGO7 z2^TxW3f;_O&mUW#SW0m^ch0~j=}vF4uPH*wkx^I4zY#PciswMr{I!gX43Fv?!pmuB zX!K1i26H-0wneEY4!5S>v@N}`{HcsxJC%Ds-dk zqB`%)&?Py?KK5*fUZY)dU%PVb%0jrL=xI*;_*ngPXdmmXFs!6xrAAbWvGG zVcU4Gh8#cFk9#{=tk?mRm8QaAOI zZt5Q%L-j=6UEcM~V{9XO$YcpWx3o40wAp)I6}Bi_P=9sVJ2EtA>y*L1*4K@nd=K4M zj7{dL)8%+{B;&?f1$XTYtU*sszm>MJ;_RiSQg^tI$8l3$zI^!+=h*7ruJb=EXADfe zt(H&<&VA#2j+f_ksd)9uc4((o%=XkbjnzFpYfVf{e&pD7s84_EmpYP4-IOmrJLiHnPqzQ`S}whSR} zMyhz;+~s2xi_V@sG&OVc_mhuWzK}+8I1xBvAQq9EZ*Ix{iLOu7j zr}SLE_lkfr0bb?~qqQ~xypQoxU%Yrh<(}NJ zb7!iap5AgiD$40(t$5bOC2fnli?egny)$#u_GZsc*4?5weQuYCwaat+%=xvi@K`!$ z-D?c(?c1ZC^|8CoUE=5Gk1#o~Nl-Apha=(U&6~>!Tw3PmF*RD+)Rgs9%fP59-f`-4 zM`qix1{VEuz7pc|Ly3*rN*AfmzyD80;6$89s$U-|X&kJVe29i}nUQD7;lmAU9;dsi z_AmX+uDWV_)rL6N48#d5VDjbgb{Z&;cymUdZnXzu$$K8RGiSl!H zP17?Id-N+i45dZBWLlo?Je4ZD%XdK`Qpz+8F*Kqx{QO`|#Q2XNY4J5RrvmQ_UzU|p z7K?a)RjzN#Qi_sNvF`Ktl81eKieyuqoSag&Txz^~^Uj@P;_rYwS5P{;yA3+`Bbj6m zvD+tGOGMZl(9^(<&N6zaD@rM;uRqjbBqkpl7#bK7!obSLmUC@!ZEJ4E#$ILOl|4}z z%(26J*7f#KWkrR+&Ye7uA3tt0G_D(v7m|8+^RVL9!ZSl*jrH}blzva@`9JQsbLWoT zzL3z+Q2GNgmM2frQ%cIp-p`y3-fem>;b+#9Cyjl@WDLn_5|w2?La$?*{>iDeT( zRqlb6<}dQ{O6%&FB92DK5}ab9Xq&SJ(fjqA+uH_4M}s>|eG7X^Xx(KCrGO7&#L~}u zZ8TOT$#LP=hif7f60SL3UlxyzwuO>e`I6?dHnT{!CGhJ<=DqEGBOfbmTJa8ee?jt zC+lLYM51b&GEJ2R?J^6Gd}?nWv}sR2+7zck7uREaXZxOmElFA-5}n--53+GkbgcH? zN!W7``7dXTJtJ{=r&ZIkT@4)xGlm_M)T7T%MwncR4zw+vpZ7wpqflOj@zplDk_08S zGsoy^@t+0W%}3IesnlEbA=9~b9jh`6r`IGMilz7T^jxZcbZ$#_>V>iH=OKshDTpq) z7r?gfGNb6!^YBZTmI+e6ynS{+!Mag^f%5C!#nto&!q%oP^E;AZl=`NwPB22<`Ag^4 zrcj=3HH^LveJ@2DmkIL8yKdridQnjD$nTV36~1=4#IaoQGp#iQ3YI(*)t4~JZoD`2 z{(Zt(S66CsQWE|941 zW{JCXnE2pTl{klyPZGj@;+ayQ3RTLMGoLiahFQbaF3~Kd-8B8>?fSl#=bwEfS#%55 zEa&R@xKq%l?}04qgC*SOYc^+YqD0%)&rP-*wo1$)NEL6VMmf1?QxWq#OF%cjxYjZ?tLPpB!#lir}{Q#0ddZd)H%PY~$4J zlrGEazx(?2vB$TAM5~QCaBua~WkErAJg( z{p)qU$9K4mQYWzS=wB8VN&sF(n7X#sFZ`ZUT|~;1IN-0egEvJY=&s-c9DKXZQVCZ6 zwJ`VXPOfd6`rzPTA`=z0h{&P4);@zZ9DU?@Jjeo=*>2un zFzO77*ky-?biboq94^oh4mt(tLyjK#eGGe5}~VTl0=AQ+#~9b$4!tzgRjicUS9@^2X72uw_N!;6FW4tydly2l>_078H_T3^yUNcX}%U3Ij=PHMRRlx5E@&TLQ z{!okjpBNk#R)1gKEp3LtqDKaqkq9pNh7y@Ce*{b8&r~SjKPlPaJyXtLyH_ z1>E=gaWKm$Jw1IK1?WM{;ZB+0#YL>5Q%lrUIdH%NYxoQA=vIXNJ`gQq#|Z?f-R|+2 zx^~|kmu0lHH4F?4P53!6t%QjmKXe~Hd`LGt_1R!x%E!kiSkZg-BPyulPksORd?zp~ zGBVP-<8iEQ%`MTV2&_jsFGoahT_eHt8$2M;r9R(@F&XGhqv z^NCr2Mce*s{24DjrfmC=o`g}lyuY$~9X(D&W4@c6J{X9e*RPYy;woEP*#`y&mJ13B z{=hOEBo$mueLW{ISPCuEnulIfZ3b3Y@;3XPwTr99qF=zkB}q0bQs0%6v8^>OZf@WW z=uW7FyUvQE65Q%B zX&qRpzfKD5l#Hn8%ByQ+mQXfq*zi%-q32h?3NfdLgjO=XNXvBn)-7s_6(NYiSGC&8 zARm1ny6-C_EzNrV{CRof{`8~UE?vHCK07($3a$DKBG`v?$`v009Lsn4LOh@ND_wMEjkx^SK-lU18;y(Ob-x05ULX3yxg)msbsE(R@$uu#tgIJ!ww;W#s*S{0(*EZGbq$SjzNVZcIVtEzHbFr~e8z_X< zeCr4QuE2hdzc5T_eY|d})4LLybi=GA$8t~mYl^gt%wi|5$4nvZUVQZEk$>6-Z?k_1 zU23hch=_Sxnm)-qU;B$QTXK?;lE$Z|RIRNyVb3p%*S3xpI{1wc$>Dvs@7yVGXgE9< zO%A&2#+%hoz$lQP!jY19Z0k+c+eo%e^TUGx)mRcsH8M?V->}42$hmRlArNi<2>$=C zi3X*tvtvfF&qs5!vi4|eGh+icWgL^ETef7W{=qi?f2T}`skXYh^wm|~#T}}sXfpJ9 z#j3S|x3`@tYiL-_$;nBZa@}^xza)0T8XPwQ(bA9Sv4VK0s;zAq@!;a(B9V04$v4VC zB1}}_whe!ISKvSYs3AZsHp}PLzwL~U|j+uXYb30nzP3-xo zPtcZs*?H;f$DTWo4Q*KHAp^qpVM`9G>+y++ePrfWt*ufGF#SKyesYtP)M_0a9Wrgs zzsAjCGrzTG@RBg|?%j#bw{J&^m8IAGwdf_q{e5iwd*%HQeifi#{zy>Z5ql|c9^M4P> zYj0vl^;2FGu{q0o?rbaU%b>c-{{2F7at6tt?d|PZ)~*dDo2I<{;xRY9 zi2r1aCD(7TQpRzVw}5k&6w{>rFAkZ|-@bhtPbeE2av!>}>;-5YW&|Fuagm{ zy@{EN>k5W=y1$6eCGYs(UC7S9xFnuGzcSM;qfNV&;_-LqZx$D?08dLp0Z)T6EMqYe zrN~0JSVI;HNAo?vuDtPPW;(J_@R}#eF0I@mCnu*Jq0;tuyvDY+Vzbj@3=}m@%}Zc2 zm<-4(=V&3Rf~(r&bIQ zA6)bMP+u-AEYPi7xd#OfuqzW)RA;Q@--O9HittRr7&7`rAgfWY-+nYp5eSwjDJdBq zJH{KgH=%1W!INJYF$P}`L=uRpsd)YRvsWP$t0b*Em%)QRAo(xlddl>Fd)p(PD&|ZQ zno$lt3ZC}!vp2CSBAU^D*RL}(^2#y_g;yy@u=1?{3_x}bGq3b%^f?zHX zq3)L72gn0H2oqAeIrAlilj0k!5m%H1i5BMI;Gq4eO~#m(ylXWIfh+(|0H$2^0;MUG zvjEu#P|mJhyA~l#r}sadRlTaJ3M%fhBbg>w7uTr}~~HHl*TYC6*DEzcDO5U0mGo35Arvi|d`j2|xltaasFg z;}B>wqh4f~B8TsVVwk19y}c9;*2PD0@DrjgX^NDie9iKjarx5yd^LT*-sI0(T3Q2x zg8}%s^DLW=%)7(fWLL20@FgL0&raH))T0H!RW>q;skcWV%ZueGuLxo@LxJNb7L>C6 z->5VihX8x&$`z8SUz{C_sqZMr%dYYQ=gMr=bu@V~h>ZT*iQKb~0Ca+3F*`}ysP zQAYcS$C)!{e*OA&0tcL1+LBI1DBNF6IHBgmKS9;9f-1(burTLHhJ(agRae)RcNL7u z2}m{LKYw1idi5$z+J=O*{~ox6eHTO$3D{$IK>hj-IRt+)_{ou$CeXv!eJWDHe7BLPB})7i71*f%8@g~n^`{4p^h zc(alygAoQ> zKrgs^dX}TKN=;3rO^Xbi42sv{DJv`6;8--1bkxLzpK=0m;|GS%aqFyuhlhNh_fO+z zq!`~*s+1gkhhSIg^k9@J=aKDFgg(MtiYP7odQeTu!k7<2;dJW{gi+_`CmNQ6X-nC% zCC_^<_Z?x;-`?BpS%K`}*+XREb6=0YCEU*5_$LV`2{Wx;H1~B^OGSl>y~N&lW^jNy z0R0Wi!QdI_x4wd^vdpn?D)OkNW~iZLl-;#mPz3%=W%^bt&X4cAZTLn~4R5Y8so;IO zg~-w*oFXe;0K}elZ|Y|YOga0(@l6A$?ov>Sb?q6)3{v9-$SkkolW%>GgWBa(-qp1R z3xVwV3oodd@Zf6s$liFrYuB#LfDoxr?FN#)sGC~x*tnnqlw?+LNJ#DH z-hy+t&8m?XR52B~KzTId;+9$2*(#v9Zh+$dGt=O!d5wR!7%Ke8NSV?7iMnQ>h$&75J^EiPAlOr*lJ4j!OmQt z^ZfZ0s#pLBMb##q9jb#LKb|0b6Aus1l5N|ziJQE9A+T%Lu8sHyuzFV>v_yK!4v4GP zUwiUSTKV_~Raqw{CYJX1uR{*ipRE;OVQH$}fWd` zow@eBSibKqhm}eJSN)lE_(~d+0tNS7Htd7IYHeobPfp9}&L{D4FLeZnXm|XjyAB;; z5S!)tz4X4mdq;WqmOTz{QsXoK{q>S}Z#PAQ#b7NzMpP~`*;CHR0ch~MCLVZ`R z{T zyE~1kCFBPbi*q56HWqn%wgs_m{6VOq%;9;YzYt0sAgI{7b?a1ZhEQ(kIUpEQc3=kv zHJ+ULF(`tKOYtM3bYyPd8${R$i&vpK6kzn|X)mT!YA~d}JCu^>trqNwTE`Fs4YRFv z1S!DegeDR;sY67UXwuA>g++it!?W~TZ^3ih_Z2}d6fjj6H*DVgsB=O+k+n8TDvDA9 z6m`qW+n7_rh$_HN4f4VcYDfi_uV0qkblLli=Bm)RS3lFHwLjTVfFI9K9k)JJv&r!C~J;)t8QS0grwy* z)+I9l?5D9o8r%WPQe^}y>Y&6|UYI2M^Ufsi*u0s6_*ky-${pgpTv1sWBx6?l@ZilF z#yj$^!*Pf#H%H%BhLjP3pa?b}^V)hlTZlZj^G%C=tb7jb*|P_H=pxCQ>)0=O4?7_@ zvi!>G5FB`+#=8bbc)?id{`}T&dLE&s3CV1O638I4qeGFOP~HrNO=%uF)UKFG6=QnZ z83UnPJ+WC)B&`m*FK6ME|Fu}9RpD#1;O9s;E#`;T)jH!*C)a9m=Te?}{p>x*LCb_iZoDLe(TH)@z$^9+_;xQy^Y5bPEUyt2 z9@z{Z*#RR$ina9vo>GgGCojY95+(mswY;Olz~}pW)fQrT0IwN$JNKdXCZ~n@^DGq2 z=q>R*M*Bm#0}C7e0Qp^q1@o zlCx>9NP9X2X}h#F`;C=Xnr5PI{IJ@CnlCk~R6X+Y#;;%fuTUEy0uBs0f3K_S_EqZ? z8FTWR9o=XY=01FTS9)>a+qY{h>-U#Hqo+(hJ+k)SX)vq}kH6}lVVJcyf#>TEv$9L( z{;0zYF2!(tLje*|5RjXgD17|*@mBbBE}(>E|C|^jV@B+AkF9TM*G5X{mplve1;uR! zA)iWd8mi}uKUv^Xx6_Ixq|&-IC0uO%`hBqM44Uf|ZO;K^k@^`&CDq99RDn}oet!Gc z&RZm@q^zY6SmOKVw;Xa|7Pu7aKGV--y}f)0orF+Z%g4v0EzH8hc3GD8*7fTGjpFUQ z%q!>uu3R~gwFPYV#;$Ql-g#xiz17tRBk%#S{K6gMr-wGxO>pGsOe)5F_B$2AY{y>! zSv`qUhN55^a?^3*9Vh8;*k z%SxW{Jrc8hU@f4R9h;Ctm3GphVYd025svz9q%p}_;^@(%@&U4_;~r1`(hXO$e6sFz zU8BOjzKd$6Y0ru|?e8nCqw4=9kY|4!R8uQK2}_7KNL^Y(pC!b_-+lfZ0bhWC zpX1^%HGE}M>>2GDmE*Nb`uqFm0bqsvmHmhkqQStkJw%iyV(yb|y{LzA(=|7n4>M<} zVi9pwJ2_~DE~f_shTaD=56&@}pj#>z6m21S`89;@==c4TF)_8op8`&*{FbQRyAL1A zeb)!2fahWq*uK5>H1dM^ySL_*NP>xytJ2@nucd$&SZ3dqE&ANJu5~@i7xVbwPZo6( zgE$~f_w*(gRJ*BwwuW3ZZQsv8+FGT)m zT_hl-G(~O-%F1$r>hDAXh)7a$9c^8ym!wHp$ThO3tx1*f*xdTTDMZ&T#t;FQQQ)-O z?1?00WflAR-Mcs@1u?c4*6k&t9(?1cKRd^{d@f^WQU|S>EIHI8 zUhmqmgNPiqlul?Xc{D~q&pVWq)=}OK4CtJ;`+{;s)c;h-I*7pqJ!f-IzZIhOL-C(B zHa3>?8l_>5zlLhg{O_OrU3}^Jt|Pj(q%=X{A9<^z9($^?(V!Zbh#hOL>Kw z87|9uF!S+A_Wj8LJx8ypmtdK5`QKU{gHC;J>+UWvlpw-g zS67#9{pN{~#VX8y>57VyXV)Jk_=N<9h5*|mXThUeZr;5eHp4br6b6O`XC?NBtqnq< zW)78p$C*!|hwNQHw=+TEctuj2U56xmY2`r9${cJ>wZX-r#c{~sA=putkEvoP4OS0A zYniBf^G3}3H!LB8sB{poP`S(Atktj-g)Wk;wr#c&wEn_;LLsO&EOo@vhW*W;Id26{ zSxNJpY`AB?N`c`R*k+yg`+6VCz?+T668H%y$0p z%KgvppU$xK-c|HogV_yu_UswWBawtCKDdjB>J^W=NEKS`abx2)KqaXv_;z7*jhyM9 zuy_61b5sUQ;Lb%S2WXblkB?RuB`zy-Lafqdlky^<^O@y!WRSQ;4`;%LBT zQL=a26rFqSCde00W`G^xmO%Y_KKW3r`uyis)bLX%<*X=9lfydU7{Ym+w%kvs!a|66 z4yp{I@dZ-WwiKVFG!`b=GK+?~3>Yb_T@(i-LkeHUs_VHJyz73fx3gg=K<;gJU`BS6W#W z>VsvrIr0TbAt`m3UP165Po6!~le2D&>)aI8{oIk^qf6eQ(z=ObmP%59=U`tscYFQP zwzjv=_}UnMF%73j-tmg}K-uyEg695ZeX#Z7j0Ijig%yG9D;;z5gvOd0_1+{cUVT`f z-5w7jnJt=GQ5PrSNr`NSMw_{ZwDx)r@;F*@aa?1-v)}Lq$L#Kv%Gzj zHruw||9s<@=l%~X*&PBGO^_WzF`0YqM{fPsvk_2<*X6>63)z3pwz(~KN2C^Wdau5F z{rdH8h%#J#!na!s7B^?FF^U0Z^L!=^rbknF=*b#vAK|q>K^9CS*YKlH9%gBmF+H=>6 z6)R4|(oJ-|VO6X-OtH+UPRo&k;*T2Wa)Gh{3HTZ*ajVliWx9alq89>f;Y%U}Ff>X0 zx6Bdgz`&|NoNE%qkW&wUGzFM(btp%bk0TJh-G&uIU5W`_N>x{}-0=^unbHn26ELLH z4+8N!1aEm$DUkkPCP<`z{dz^uuM2^pJb%X-us8m9ND3I(3iE|Cpk=LKGN3_VeS+HO zu99z&`eL>qg%2211n0E=B|(9Pv3{mwMvYJGbO_?i7SngJ0T<#0;Yd zqqZo^|7iq_NpW$+E&Ph0PpksbsCtNl!^_KS3+A7ZPySYmL(p(ysll$r%+S0U{5B{! zbNJ8o`x3e_XV@mHKxpv#A$)^PRac=F{38h^a3w+aMpQ z6CIm~=DYgCDwECs^5^;qAe%qEA$qg`Tn_+~`nJhn!0A@4x?EhWggR&PxG+oj_kTN| zWa8Ma69?Srh!FHG)io)d;3gI=GPX)a~JF zC$=Cudio1MaQ8NE0EqglPUXFLA+UAp;zkSAGdC9^!U!J2p2=6Y-?3|#1-Jn`wiiX! z3spJTHU2&pwCMX>6x6B9(HgNIr5@Qbgb7V$g4BV~O}q{8dc5cCRZ-EeFgQ$xmJrp0 z1fF$%;v}{=3pf%|L+3Feu%WktEa3Y6p>XF1(TE8PdF1_-rfM4+4rppJQcis7gcS)hpqJ=pdDFCWN$}9Ny*SX}a%`nPI8_^|I z;;Ndux=IkES?OD3p0L(TAL`~{GFiQhNLsww*p!g*xZ(PN% zKnJ2`&12$P3k|1=sh&P9P9_4*HS2zh;WVU@=pD{N_--T4&QAX(oUj8 zMxxl{cIC2h!PxqdK|%Kb>GT&!(0?Aiibv62MGFB+MOhi`k;8FAVnKmw{bUmTaCj|{9Z(*x z*45L~P#_&l<*>M5OJy#+51d3dMm$a&ky;<_@?I`OjIXpZVE*1)n+OT5FT+w^&bDq^ zg80j=;JyU}tA>ISd~9gBqG0Ey`;P{iX#S5qiH8qg0(p54hC(E~)vH(2%D_MZ5k-e- zwCTZ$cE>=K@$|eZUzg<0Gnf@P$9A|5GlPIO+6NtqB|Ls`XlOlZJt#O*Yg>QK&6R_O zMc>T{?R_fL71-Ztdkpw3h33iWMn&D`PuhNj3_JZ+%cx;e~&(vy>t+D}(3Pv3m z99)KaOP$Z{7!RXuVBn}M=2b>HQTIbKUq{xb662Isia@Rw`dI`8MP z9w*>=LZitg?M>})fx;SrX-(;c{CPu9@#%RsukW?AFL&B(fKOKYeGD2uA7F~n zJ!t|P1ddTd-nno5s4FkP34!CjFXM=TmyReb!Yd(0|C*U`_H;nTyG(WvE8VhR0wsLO zj>&@0-`$UI(U=yon8@X3TqjKdGcEl1j*_n;5^^Iw)xKP+h#@Qc6sqTAN2g9bD7=wxgU~(1)PxAt1a=> zFuozlY{4{+ibo-yCX$2%nZ#cE3^JbB^ES9$Fxt|N4lX>`VG#i;#emGma?Cy0qWRO96$+ zqi2>0F8^l(OUb=iZHe|E0oa;DVMTur7DM0R5fUl5t&7*Lt&WkgOL~m}GHeA~OG>2m zN9!QptM(UFk7e3i6IQ4$Z|*#XrQ-*V%yR$!p0#5)e9$1&OF@AW@+MZ8Eg}_NfSuSE zT2XscsE-kYK|Gs*Z$$mQ;_1_;X{}+}n^ceoIriN&)>0xvp z;vaC?n>5{-_WRs-^(b26UJi!^a#X5}OY*oM7SG(=Tp(O6Ez2D7^TQ2zp@<0?fR7l` z0UYroDng~G%?CbwxCoGM%f$*Wf9t6{{LBruc2^pe$<^W7eupp0?-8FpwPIzW`qjhj zrk8Y?h^C8rTxtr0-!o*>~Y?A@o*rVf)1syA|;VWSO(=ts?y0Hh9Gr^F9C zC-+wKWpKz&lEbPqLk{@fZKgIsg+qEyb2?$uo$IomZ^?ef5VLdF36c_e3*-Q9)sGjz z>;RHQ0OAa2qqd{6;gRqF;HZ-x8AdG>V+e*_ai365=sUpKw-V3{D(7n2)%=VowSIHp z3_o0o(3EpsMZ6-pGl?U3t9dmDL#+N1YN(z1H_ijW|CB7dxkyPNe2>Sft{rfGL zNH7*9m6eGxk2WF+@}un(dALn-!`;ZpBZIj_Wy6n^;6NeI`>Snewn#*{&|ln_qn#D2 zZeH159<%gk_UrooI^~by%a$>n4o0je+$GTZ_1Zz<$4Z{l;xO2R0CAw@FJE2*KO`MZ z%z{w>5y!wEP|ZAayi%uG_+Y(d*Hq3nB&y1;q<*LN|g zd?|MNree4SAhKh95uQ5ExGcGK8>hsF6t*M$pQbLjz)nEAGJtFH% z{0%T<(E>n9HtjbuT61|7UoeQtP_W7JwPY4q>t?sd#C^iAVYQ%o5P10fxf}?;*AjU_ zfmJEwl?FX>AFu8&y2|V=uH*TobBg+9#|-la&pa9mYFp-Y>#mU;h{{R03qtq!^ziB z)s@Ezdu)%yPj~*Y&kHUZ8+}l8m4_0CuAm(rlbZ+^*_)G$nk@i41s{loic|N!U!9-B z0(`h}<3{!a8{oHJdkgB&@LG!X>Fev`Mo%$X9{D%Ju9n7wIxN&LCvVxh<*pUSY7g68 zvl^=XuGPxnrS#y=v>~E%7ChMr zS6l+_JV7=Gf)$j_pL@sLzV`4yr6z9&!QhpW8Z(PdEH+`48spdDP#ylqfxGXd@6P?{ zML%uqerX_<*ZSH~Lqh@VLc$G^o-N&OdIkn~cIW{)s{}t-GNV}~TFKXk1jrm^NdH)^ zE7&+fA|gqxQypR&fPG*#yW*0-%X*|kW=WX_thH3>o3g+5K~6XP_Rj{+epAQ%3->Zc z%U`{}ud~fOiyMlVKk8$}+2IGe-H2XtIH94@(cGv>cwe4{{&=2{QK*F~@Lu0Zmsw-> zMiM^wc5wnXy-v}2u)_mc$V80HXW+#g(Y~oMzn>jQ=V&=fdKP6 zQ1dFFM_Jaap-c0w*D;$2Hk!JC{O{-S*7(Y~t9RO@TL{LU+zzOB9#U{k&BGdTy}ivPTV zIy?^QfVDx%dzKx(lRcJ}8&Ehg87Z-_@N7f_8#qk@v^#U`xNrl2?&zla8fiJX*GNEw z8N~5i1xm()V3HIEq;bnj$G_BVZ77f^3VoTrJMyk%Wy3Cqce{&MC7QIrQVken0kQuD z?Ds)K6B83;-V!yUkd9hUnCEnVGahMb{KpJ<^=k)Off{Bch1p&S%S zyMrviBigTcmBmwpC)x4Owzb~n@pHqyNs6@R#J$bP5&wYbPPX=;yE}B=pT`-*sT=@j zN-{3o31&}bgRuK_9<{yj`JFHOa3{d}N0;-JT#TUiadF{ zhrqv_v}Z5;d45HElYz}OUv?jU-oCHb_J>6#nhXsO2O<#=pXjc$Up7tM1#!m*lh*-v zcZ7psv+Lv+&%*st4hcDDPaU$cY07T6qC(g1WJO>nJR&5JNxe<5WoW1wwk&7XX_Ik& zTH%#>wlZu`jl=NC4{>@+tITaD;JZM0{eXr^iHW<=BoeSk$!!*}de$gltA{oK0*%A9 zVTRdGG05acEV?4B5lP+o#HJ~M+}02RxHtgk5AX~d$gEc-z>CAnogXrVhu_%|x3a#s zPWj7Q%dsrp)z20q&YGUoH=irax8m5Z#&$GA&5(HS0bJ%z1AD*p74kx808=9|X+k!R z391&fgi8S*5qaymW4}-QG#;M)`7P5}1*_kFcN$Y_Ng)UFE{f%id-$9u%w)eR*0=HZ zyC|?2$8?t$oO|Gs`5RG&RIYFC?dF9}nRt&2K^({Y18hKLNEp9f-3?5ML@UD!r-3EF z@5Jbedf{bb*LKP~^qc~LTEK%rRF>1HPft=MfcGN;VEaOT+$;}k%=G68uzy^U=N903 z4FGz*0fG$9)6^2!5M;2$E0I0f(TNa|f6B*45oPR#ppXzY;f)&^GSbsq(Q&B_n5WfO z|GI$es_c|Z zS>yf3ZuIuOB>o1NIwkbEDL?~xN>83>P%dIWm6N&>oC3+ zMG>KL4K1xo@X{=pI>Zv(!7&)n#TKMOYHLz@f!-BcAYjee(!Z$+$Wh1NL7sRR+!OXG9;Dd`*C*-aWM7G2|Hi z-fJ%p!VG7Rd)rAy!pB2c|D4*DfAx1nDAPYa2QYost;26MufRdwgY#M3sr2TAwcZYDu#XJZK^dfTb6FHxpG zqGu`^cpYDwO`}gvgA#{+FI!Moc^5BUoJG(~Aj*=y7zb1XCpfHL!Z&zpO%GZX50Fc8 z0LQ5QIK`lPZ=oMT7yI5EWzh|gdUO<0y8nO~mHn1}Exy}vmYA_9eh(fz=z>3GV0btM z;_3}+AQN-QVUSD~uT@E4P8Bn0Z=|IZh2@6^?ps+I#xyb|9(MQByqy(({8ddu5%EVf4+L>kVJJXRXxsux8`o5naW|xP1l6Zb~Hz$cL)Qdr{eF ztz~9DA$hZU3p$DiadI@oxf66#8=-L4Ld~Sd%|2NOar01B?5^ziI5=3^(9obDD|>{I zf-*p2+N){yz|}QYG!zuGMAcs zfcu1yO3~cU2wH8w8*Cm5S%$^irc6FH?tiQ;_%Yf2xO$cNS+B3Za_M+XW&$q3mYj8> zbcy(}-1MFaqvsBF8Kqa1af1g=`Q6=~oD}Hj{xyf#gqu?`GPBww)~_c{w+d`UqP61w zmCpg)uCA`IfUhS7aVYoJC18m<_9aTm{deD8gwpCVMZsE z=Dut@sv0{$?kHI9M|2ige}ugkqFAM*q$ zbEh0R_#=R1(NQGF#V=Iy@yhCdB9nvrAds{RT8)T|Y2USd28O}Y=<(GbfEV-2pZJv8(%ifZc{oZlR$f23e_Ey`yXMB-yXoF0sno~< zMGbv)KwG!3l2)0MDr`!d-%0EUPYhnhOsJ8Ev9@U%wzEI?pE%V6r7-4Ymd*h>z zGR+ZU09oFy@dA}WdPJVnW(NF^Vee60X4dROh&O0QfERfkklSk<;o6?+!(0GcugN|} z1qf~QMdzWzwmJESe6{S#YMV_{zn=Je4_h1a)Yj~o`&c3UMLb|NmV#7Wb^l_zJha^+!R1=j)rStqP7TY#9wpU`s?pkypGo2Qd5_^ zb9UWoBLWrq(BcILZ=WTrNW{#-igDy*XF}dZn;=twQzV}`|FU=6-)c0Az3<3wvS2JSAdEq4ZP}e4Phz~XaGztBJRh@;My~23E zZTRTdOdA`UO8zq*>?3Kp#jlD2p##6ic_U;v9Dt#ye2SdzK$slvFIFOj9I)^MK=ZC4 zl#)B&*OiRA_$KBD{k;^ZBjKC>RN}NxZrhvQ-oj>F!~_xR=rRhH!+?WtN;d{ZH@@gnMi-Z2L%aVn|JC=^iEutfOMGb?SN=NKLJ(2MTZLge-BFJy0}k z9f78Gcs1yQ+mA)Ucbj?#6k%#&C)XJ0Fd@p8ty@bc9t7W$gWkEEQe`Q9D@Tu`F#6pB zQ)A)z@8JefY-d=eJNCtPgS;i8F%I^M)vK>bH^AOq4tGB+Wf!4&N1W$}$r@cxO96+< zR;=^#Lw)mr=rUj%0R?~T^}kwBunV-?MexLgR6!&9e)JY+5l9{w%6p)T+Jt4jM0xk+ z%kcqf8hT+<{7;pKJL1j{AnyWt3TeFXTDbcI1@R-?1-4u@bEzMgna{@=C_OLF>*10d z3oFqa>We!!;msIDiWI6Vl-X{|b>P4OH&wytkPwY5Bh)$65#xt2O}o^3;%cFzB(ZBy zP&=69**&G||8pfz4uiS3T=}ZfkT5~v5BKYe5)LWs|2F-YyQ})>&@?1>((p=}(|>*Q z{d4RSGjR|Q{7n>UfQ)KUJ(=|Uxko35c9%Gj`+Rt6?M~akXipkRiLg!Vmi70Q{*)ba zUaE{}M=~sFt%AYm3OpP9X~N!HJ}szAZT#m(xR9b1?Pv(sf5@}-!^e-*+fP(^_rtJg zci_3DmDNi1UCDRPw&Jw!J9Oy#)pSN}jes%hulm_n6UL}XkqPm26Ju1BDE$}YP9L8q zGNrc!zkN>_eH(4j6zUMYV4?7_#y9^hn7(+oeaE&ti+LKE990owQABD&)JrPMC`BH) zM7U<6dmUKkU7;#vr|mY~kucWr>Hg}p-p~OVPIuWdVhSV7^s2Qx{1CZ^tqv97G?TrL z@bV$Tz+?}0#*VRU&TWwk>JLlFEm4NEj|6Qkto2mU1P{ynt%t$&5*|QzVbi^lNlcU! zxR~)ONe4LW_jMQr_a&zrxz5~$gCe9Y;i!U+p55{scBbpN7s$lVlWo`h>*U=F1tU+V z4ITWKIA$tbl$n?-?RjV`C5aPJ25NmV%NyrsznS>xveO6mA6^Ow7=Ur*^)bi7fTx__ zL?Gk=CV@Z8WU_%#cxB$pHjTF}@)T#(v|bZmkg-y;-098ZXA=_8ExX;p8&y#2Z> zf=3WnLI6wHN1LH_$9uk*MQo6kFXrsMKUM}&M!{hzqK`O@6_XR}DUFh{v-8$*>aDs1 zGn3}?b8@fpJV!d+q@{(8T&IH$g<1tUut7*f zint0zjyS4F{gZL1CMXac`panORvSdV}QrK;PYTSC4FJF{BjV-mej6rW^lW5%%Ky7T$CGzn^VroMt;X-1UZE(VMqedk(e!i}MpA zoKhyCxcZATFV9M97n%ewp^uO#X8qnj`QXeh75w8CiVK-c+Q$XbR)Ryu^>!sl9z>!d z-HKMc4iqQcyA}432P*(|C?)DFq_GqS@*eOG9wE1*7~b|&LtZ{y;%pQ0I@_IFe-#=3Ng=0w7@mlE7=bX#`e9G`E&BU;WR z^Z6|gcrO$wU6?QP&F}8eF=%`oF3~0P)H{BftOlyV0k9F3X!8z38M`-rlcz;CtbyjF z2Ey=2f0OH2*HpmQ!zxq`++qOpX|(LarViJQ6bOtQ^85U;0g8BV%oxS-GX zI|py7ZjAh~foJdPE5oYU#oqjxV0=0;_5ZN-CeU2=U-$4gR1~5zSE9+ROi`pzi3SN7 zGetrOCG!v>AxT1oLdMLAOeqnWGK36CA+wA{y!+Gd_pImt{@?dr>t5?tZob#`8O}NT z?7h#l>h19GO&)(|&+8Z3uLdyA@+7n4m>WtPVgjMc0J@9wpBaAAy})@Qw|iDWK^4yZ z6u%c~p+itu|CLt+_0Q$(djv({rX#e-nu+?co!oRsZ*LfTntl5i= z4wY7q|K(tL zW5Jw$FD)+Ir0-NG9)*Eq8O0QE#OMN3U2^5 z%U*aOK!;n2kQ#{9_?KLO(d4;2%}s2o8di?@;*AX*g}E3nHq7evtkqtZngfyJn9VK8 zz_fy?E>^ymo}!y?nfPmVRyb@FFG+h?B3tgL)vOL$znmbNdEDXC-$JE-r$a1y)d%NKZ!6>OnfL(Hf9DjwmVlUc~e9Qij?xVunGobB-Q+A3ON-=jCmiHtksB z1qTp5Wa%QrjMflhKH{7dV{vSWSkU0*wd9;n&3HAG?IbL6ALO*&_HoA5kXPGghRm1Z9;1@CjAY-Ld%t*PWsuX6~sh_sWy z?|XHsI0W_B?*BGE{#jk?U3S;X(AvSnvkFUEiGH^o!nc0$jVZsR{c;WO-;JI^aMSLuJwYqF+zEE^Pb%;cqUdiNo_rV{<2^94CiJ=LgJ$d#tWp2`0KeYHI3Q zhTX7k6mcHODELUKoCacFTZq4Ob9G%*k6r6EGvf&L)Df;O*#;<=h;iZ@+(R{wL*Spq zQXV*ANX%nRb^$wfzw?e5caxFpLzp3Ruo{j|JbR2C#&Gk(}=YEBNLAwJ-P#6lLiu}^cm3$aJD8uF3`9p)Z#bW&Ywq@$Mo5M z+_X^_aUfN%1PVzK99fqc<;?jM!?IGTIF$I?v2GVzG6yxphd;SexyWQS+}pP=X}P8` zDLO;HQ2nXSD<@>hiO>0Oyezkce()mjfO}{wtV4^2P;OJP9|d~wnNg_K3Cso;L1#OO zLlgli{h3EFfz8Xy(}25|7Gb;p1}^nILgyoFr%iD0eFOIQ5XguDBH13~D`y$F59t$L zvvZ8p6@gj3DPjXxw)89LguebGxQpsQR2q+pCSPFhUbRDT2y`|Z9EC#d_IUt6g7Y~pS!^D1VDB6{1}Z@=LuA&E8z$NPr(c?Di)V^*O0#=t z)Pi$7+2cB43=|-u^cde};?xH4lMz+)djp*sST;MY3*l57vpO`1-1E% z2k6|Lame&L?uV<#;&)UBY*U83Df|A?ZUf>hu~wO@71UyrMf&03Lwg&lxTQH?HV@lzgnE zb)H7UDf_>B8V36j?-~ax6D}(6yEe5?wqB_8fv=P8emBmRlw(P(ATDe~-MBYOdh~K4h6|3h(U4|(GUbCw9$+o7q z_sf0Jay^CiEIRm#2pw54FbB#?k7VbliVD~Q$P@?xgE%(KCKonU z3(hGL&8x^GuP8eJRC`Q?g^-bn#|Iqh%oijr2Vz(O;>(&sHuSvCUa1VjaDb}`!5)uZ z$AopGImHn-x8nNG>uIPE(AVOvKyG>@*IaT% zhNGso(eQ-lr{?DTzB>aeFr4<){afKFBoG0v5CC6))PQ760og5tu7)r|WfYs(847(O zjB)?T4kbIoq#{KPS@DcFdkFF)0FLm@o|{8v&H07Yr7`2)&Vr{EHNXEDcwKk?Y(wYf z9di0sFQjE-=CBsNa7lb5>z9n|ikC z#xN#T^ZCOKZ4kyhzzZIxdei20M^Z?s#<5!@b=8T#5!t2tL-2uvDW% zrnR?yW_K{M7>nZ#1Of)UmX(*6K3Np`6V}Jv|A!fV@O60TaqN#R4Gsf|Vb^OL%g_%I z0v`=L)(G|v2fws=DXDF66$o;b>qN;dm^`zcf1&>djUi^xFHx8cx76b=r%O7c z>ITi(P(V|J!!U&WGw|#xrTn9omX_H@1Nai!opnf$1xpuJOJ4p&gU>ZNIe8dSg9zLJ zJaH|R;xU_~Rz&H0m}ke2L z4n!Sfq4)?}PSf&xdF?l}{D6ID%=MFG4}9Npl{257k$R5l`j$JQ~Q<)EQj)iXQd zmG?+t^z~#z$f+~A>-V{SIFbJ3%UZ^^BuQ=K-=KE}I`ufOohY7W1jmOM4U$vU7_W$U z$g*Ee2{t2y6@#9`DhgWSM03M)8kfLVpnm~pp0(Yp{XzI!$ZUN;CfMw1gGLR-Yre;I z1IhT&qVq%wI3&DIeHe(41ezoiJcr|LL6el)0_;!?xu}7Hn@Q!q$?C6unVWfdOYV zMYmS-&rxmA<-ijmVG)Bs)>0bU-ZlA3Bd8olMsUZ&;870QzhKan1F95|okvOr;|U6y zxZVKBAe%1X6iDxe`HkH~j~qr-6lm{t=zq^JHh!YDvaOAEe0+SUJ^Ptd!c`}yTTxLu z`E5v3!JyTsRd|Px!G_BM+?F(u01x_o!}?W>j#?wLR>y*~vtKrrb+s;2K4YE>V}h1H2RhT-B-}H#_?cn%dyZ9xiE7*Ms_% zxaNW;BRDHxH=lR_rm%77IpUFafQy^VV2S37<*8Q-VrB2|P5mEM@M%1(ye}Sr9apq3 zZ(s2qoz)LtF0kKgPOJI_t!si`H+~&4b{-;4k`1=6l`}Z!g*PHX+;O zEL(QifS3#TET4z8orC2%o}W5m{tpxX5EHW zYaX7Gpn{mjF&3rfS$8FuIZ71_@oJHkCjDsfOJPmo`k00oFcQOs4 zVuGZw`O9CTSc3u-I8*972OxhiDD{Kbo*;4V`ZN8=7R$livYnN+MH$yw{vKY*XkkoR z&u5@tOTR^$iAeQvT!?igy7uhp7Z~7phMoil0P)DlZG|NI2Hs!b_G8o83h{eyoGQx7 z&p(PBO=anGj+yE|Dk7u}H%eeBc8J3Gu_G7t4@l-$J>}T8FrU+`mby%L zFYkRjrG)h3Ell6*=WdsHyV2Wl_U+x zE15a*!MYj8&>)11z}={*6b3_N5J7(X68}cxt8PFMI@K#5rglEmz$f`-mSU6AoOa}JKp{-&D2!f2#^N_z`}Vf8_|UnNL=46 zmS4Nhi?j@PhUr*U+^ZS2oI(0>o za@HzladKs9@RZz=Lf1eWYOm92C!RozICsoUNdx0kINyF$3J85G8drUie^hps$66Y@ivsG;Y`~%5ioO+qi0F09h53fvu&d$`E;~hZT$^<3MsRBcUohN+q!(g=+o$BL zz%IzXn}mc^lQEYpSAfLKwocM@!M$-QY2tcdwe*cSyPHu;zE0<3Zz}vgW1kBq>ALTi zvMl`9p4|2Ea_l7=n-FATail9A!p6$_4Ok)(9Bw=0M!JTqdP4I+Q^1!?i+Va`?L81( zd?6MG{L$mb*gMgOlZLAD-L*v!u9;i6x@@=tW6%~#Bf#EP6bwID9&ex!hZycdZVxBC z0*3?o3^>0Y52RhM_2fwWQf&TlMnh*G8Pzj=&vm+ujcYohB8)99*CF*loQeEXjQxss zOE%NsZ?)so+9H`PsyU~zd$Axn@E($umM(wuW))^6H6IcgDO1uz>9mU5BBzsd;?!rvr(;cqp z%LF2QJ72hrJjs$~L-O}zm^f$D9qxcE&>ixCnQHin-OB#YX*x~zKJJKEY_wy3@D z?2K*>C!e}{h~xX2+Xx*sxRq|Blh}uhC_s-XjCrV|=+N?wXL|;OymCzUe z8KGNRK6a*EC%VPRPjmw`j)=_9fDekwmJDJ?4mHM8Q@(-=R*s7^t=P2%-t+N)7f?U_ zSVI_J773Oq;6}L+iBZ&9ae5l2g!Zw z#XS#&JnvX7sllO1;IVz^&Qi$i0m5_=$j=4!WbAq`@C#Eyi$Ggv%S~S?Agp(H!F_rT zMX$Qv6Ns+_Q8}!$Z1%aYlpsIG+3~HxbN+}sSF=f#t$fXq>N8bg_joemYm!rYZuB#Z z^&|T_Ggx^S9y*g?k4AQGBb$y{?k(Mll7_IZRMz_%&w1}4_o=!c+Y$x{lV)d9TNk5p z!fV7lB4DL>0e^-`aOS5sr;z_GEiKtWU_&8dI${KMvz5q&lrF+mpXqG=b$3z3YpH*qHG=p!YYLuUyWSTfojQZ`+gs1niM;z58`|%puEn5vtY{d?3PC3r zY)GLCz~?D=jYJhry7tq94gClW~B?)7i1F7FPc9y`#Y$WwGyO6RgLk?#OZ|c*=zA%5{Bu?%X10`dtC5ujSr^ zF%OdcRXA8s${{o&cU`2b0bUWwq1XIpOyG~?xiBdrktaarwcf}8OSZLE=Y(RLxgJM% zvK)M}L-T8X@9D2I8F|tCcN_=C$3p-hz;1yP8$xiti)e811*(?+?hcJrr$a$(G&SXU z!)-gGF#mwDGGvfVVCp^ho~h4?RoG-dVOCNOsWTq*EzAlr#ERP@))gq<)L20;sX5vU zY`FnqL$gzJIhdTH01CL@8gGT_-YtyHJ{jum$!?!gC|u`o{?Ys4zfQo6t&$I<-X;hT zSRkD^9t);RgYqlnm|JBDpjQIj4{_eLd=wQ(8+7UV+pK!rk7=#t8@{|S?fVmS_d|rF z#@2tUdbSRvWTkwcKqcRaB&W)SCpNpDeDUC=%SCyg_o#w1`9H%9lFfk>KGjte+W2ky z#N$cZ2B}?74tnmV4rkk1nely8{l(6IzxAIG_-qC*B;F26szQuoYaBM6%^pNP`mkt$ zosDld`L3st#Nj$M!vDJcn_*}7J`-E5(HkD|8}WF@+7F4S5gSRAfn4O9@;fE+-uzkp zx2Z72AiLjEaSe!Z$GgslA$D=HUIU^Oz7TQF?;M$rMy6X~0O{1P6bJWeNkdq~P&WQ`ljJkAEy*kw@bdTl1;*{9O(X@ASv})Kn8I zpPXZ3<+U2ol0F%urP!)jX1U=CvEfV|cinildtXBM`nHcFL7t)qgO78p6bGo9seD|V zrtg`tzRB@c@9^Z(-7TqFWX=sOErqmEkcs1WJQYhxP`^0Q>31hUd9!|z#&}_rUIEdi zr`GB2&wEp}`tRPA261UmQAJn%j5QuTSE-%O*=O&~+HrE%Q+r+V6L_CSYup%Q6yx0y zmVb>)qvMCD({@J(#ge$~?Tddj&Rxq_4EC2od?9YY(H3YL{Sqx*P|ym`X9~NTa>?}e z4+odN>1x&XtG|r12d_VI30Rxrti7d^VfM<$8Vm1#rz15+cei4Fse5%mfYS1Z@E@^4 z8{b$CsTs8>2gu19O?;;b8~X9*tu=lRd>q*x%eiW`gNagax}EoNl(N!-iTb5HyX^O8 z4!(Fc#OTGgQgC=GG`_p(Mc9=(7NT8NQo6fKPHEtMaP^%Ke=*^VE{94Hk*xKTw;pG5 z`_7K=NJ{2Mr>47GO5_oXkzo|P6}YPG%uJv#LeX=#Pq^`8$GVA3nOkUdv+|w(z*l~s zI|KqsJ*cp1uG#$O8Nzf^l*Z!~@eGH1;@|Skoo^eYqsiLR$UW!noy955L^YNBN&%UZ zd!1_BF~u0*y0wHkeh9Ao3;4nZfiiM>>rP&QFJD9IwdD##dvrZGKm1$~QWH9#I(cPA z^U$vBV9@&aTpIKI>gU2ZSp4n;$Hd*3N$zqu%E*{CJ?m%SC4Z7rGBs&k z@bTafJNSFtu_qJb!1FwbDUU+p;<>wFOt|B7Q9MHh+l*r5z}&7UizB~G7d#!R;l~;&*6!DDXBQ`$er4XY)7=~EeTu`s@Y zD>~*^znM>6K07#WzWwX#w+j-P&+V@lhtzFbfK^{YnibHdT}?Png*>e-40 zNVk9UYqh{7tFaYo7W2|i8|8G{-o8$2VEJOOjv}?q*~`I6d~z)Bg+)_&k$-Bso|w4! z6nr|VDR2^^J$UeSwD7+};P~|3hr65DZrx#EzWYQ@Z~4PUI_nD@jIYZNYtIa=g6pc> z(NY?`s1tB$My+X?L&NJ5=xAvkTD?MF&K4G#HK;!oF>1SOaQnjIjvH0asofT(zR637 z^8J6tW`u>^mAm0}r&{^ql^!ayW5)^yRrC#v_h025b#vd2sR+>mZiOut7VyQHc=%Yx z;g|&a86BhuptlRCZnPHKxnSdRe&(_sckeNSBhVJt4X^ty;KuAsx5q}dvdVj_u_+J# zbK(!r`?TmthT8V|%(A=LJXFrdLmznNsL`e!xoO0`nYPyB@Aj{!P!tC{iGee(&$}=UKkhho5S$_3{<(CK&rGN`9^6z*rlp z-pB8u+&gAJ_u9F(+wH7#&h08c_FbEM*1Rm_!Q$N0rU_LeNxSXTF_voB2Y^F1BOyA! z>M(q!(BrLy{YX9;soDx?_mT_rBK2ps^WMBXZW26+EB@+X$hmesS7rWnP8N+t1M4Xh z@@TxKNm?m)UBC0)>v7vuaEtM6v!SQox2b-yhJvAPE(Xsf<_yLyUd?*8N4^c~2Dhr? z0!AnQHkyy$@lJQ!!a_EOP!CsqMN#I1Mt)lY5=1RvlzD)+6JbkGuv>N4(*re#xTwZ) z>u0=k<#Gkp-$*v85T z=k?z&3@ma4=%t<=Q`p}3X_aLB`{&13dBL~Jk_bMKF%%LPS3*G6)6rQ)L4GO}E#r_B z1-s)}5c59b4?a$vhy9J>!9t_v*VK&}eu{yEr+ZJ|>fL`U%W`Ea-~Cke7yq)ERCkPM z_yri|t<&<&FVt)MKCHNJ_G(^1}ir+M~rDe_cL+T~>+qHcb7)!Js=q*cH(zWZaN4|H4LTOLblkS2!Qk zw(CHr&&YZCqOn8sUwGm)kB1@D*{lck_3X}84r=~+j`0hJ2&ExY@fGGl!KF4f zQ|DEo1nH_Va!Gw8)(2+(0>G;7i9)*(_fdgNAnJ9*>Dg^2hb~84NN{l71mr(a`DAJ| znSxOZ#k!akrouiYHb?65YAYRm&`;68@+6z;zYC|G894uKz)b+YLXoXEn45r}|J`oCrC3?A@Bp`meOd z3#kHW+MXSMb7(KqfWd2p?a6;#S71weN<~EhR2_`6hY2cZj2?YDyYuZ6PC+b$nQxy)FJ4u``xMTTCb`YPh?IdKtgsi(B=J#E8~{jHgRNP>XEk#MPY$Okt1$T9 zLB@3hzQAVCVSM=avoXVV8HB<66xa{(FN3m_w%=^WC-1~4_KDxu%2NR&6y|CM4sLq# zoxl=q9L{uwpK5}WcaUrN)2)iv^OFlBwsuyHMzSs|f9V1{oU+=}Bd-xaQ6*XXwO%AW`e(*}$TJofPVY>NZRx(>y}#ATZ1Sc- zpj?De8nm!*BP>5(67AQB5fJD{M_|n62aHk=PEhN?i%Ga08rqPXmBn{G7G?@fWJxh^ z0d)Kwi+BW;mS3pzqlFX&*P!FwcSSv-@YuUn=2(cm+1)fuv??s-s z=lYIqUPnWLdF%_fLs_dR+1hs?PFD85Wc&$+3^iMRD{AOHM6vg|T`FMVjOVNU*@9C` z;ptSx`AZVZxx+%A>{TvCi9SZw9|!&>5vo3cD5?1eNq#vbo$bY*EG_J?Vc2^6mcw8K zS9`$Y$7en3x8%uYaodzFQKfnOy(5(s&)Fk4jJ1_AfGv8qVLNPJnVFcjZ=j2;$y~If=_sUsjLkvl1@2X2mF0;Q$3MdJ z$Uc5}#KNB?zX1c@`6B)p?!~xkg+%qs+|9M|g}J*bmb=$8-`X_!<%J&KU6z%es>klW z{hwn^yq)y!jE(DB`9Icj@{CYPlwK~|zX^4e{Eh_i|D9cJwWUR6*RPOmN41o8{$k`_ zqw!EOn`f7f-%Wt0BKLM8sB!-$R+y=uk4N_b8s|+@Mt1$hVEx)*b!u- zKSTohMn(jok^5W+sRD)t2Kh-vGs}YYM=a3`w&Lhldaf)QhLgb&m>6VaK4;B=IXQl~ z#)Bm01#GK3aKKprti$#VMnc6yg^kA+kJ8f9r)NC?u5lY#?hbf)vOp)TMc z(EHRC9_gTz!?9U}`pK2tzdxVHK3E7&Ktl_8k|ebk($LhB;^N!EvV=;f7S3+Tsv#1T zks*w(P7B43n4$*~vvtr+ndQ;&`_a|~*Tr3vLaU1L{d0?+^Wz5I=BekNGx^7=M~Pary!)*jz97gW9o~XYH$&hPwB*9-7 z%--q@S%*~e4Ue;5KapYPAb%0tLJ_H6h3V|@@N<66A8z+Rnt$|5Jz`K^zC}g+ZA*Ta zi9sS}O}7`02!j=wo*y8FKeK zI;wkKGU#yaSGYcZp>PnoVq-ZE*-@$`2pE&Ut~CmcPi|X_q1w3&j||HrZZaHXPq{U!(n4)8QnG zgGa5JXvBU%oi~Uvp1#hy;9@~Y8`+n9s04%qPj{5XYtMvM1-8}bYH#N=w9pBy&y{p8Lj)RN$Ziy9J z%j(rXo>+Ch*g8s0Z9F0~x6<)?A}eY9?}o1v4ms4`z6@038PX#?u6k@rV_7AwZEbq_ z`6F#}bDf=2BUxi(+X~urS0O-86wMpiDsMkLATv0BTzMVUDwc}LX_swJcJAqY>nv1chi)dkEz)%pfntHA}gZ+bXW(ptW- z9lXnj(I3B&uW?O0ZhmjR=$dyQ9*ui_?Pgl|Jzjr{O#F14%@}h%>lgV;xnt@E(#r%a z3!Ne|bEF5F`K{!ATQsS6_%!Ahik5cWU`PwydB4b``NPH01Cjoo^jB^_UnAwMuj^{) zNXz5Pq@di|w|;%tYqRw;`?B{Ml#b>I?+AZm^>4)Haq2eomT$XBb-cswuX6}3BPsXUT)?Q*6tJ)`& zx-bUjG~e%xi^BC%=#R5V?n$UuPpX{9-%A^O7bBOY-d*gQ9DBfZ!)#1wYxFbs<%!}Ev8*sDn>*zNJxbHmi_E{Kj;lEi9rmu<@j`3$ z4sfJ{Te4-RiE}jcTlN`a9$^r}U`q3;-LzE5+}?2`fW$34k^~X20%YoR6f6UXuzm5( zKOYj!1Jso!j(JTM67^>+=j$2Hu^zX02Fku$g@gW!kB4p-@O!KvEL9Fh8n1e#?^nw# z#?pIys@6={)+6&K?AOPA4((p!_x#Lj8zELj$&0iSy2AInU!PT0 zR7uv1C%L~&)v#iy+?;mC1uu6vCYT(zy$AMMmqw5MOQdmwmmt{q{i{|q5ydLYB{Q>q zC^@Mpkng{j(HG$1dGo_Fsm|PC?%rh>$p;#1AzvOqWe^B~(}RBL$o?VJE05%^-0tN(r5_ntC9u_?rEd z?Ntg>V{IdFJ7k`}p8k6zgt7UM{GIG>g{fZ;A56|)Uvb;7c?yLAG>j=f#r`dESIxcq zH15_yGgfUrwI8l_Xl>a@f9m4Smhjz2P6kQcwQou7JByarGXy3NG4wJ&E}%88`8Tgt zvpV~u)Z4v7JtePe%i|YjF)KwV+vL8f&M451!bddbpjYu$$;u#W5MsU$m*l{;qv3QD z9l2FMXgF}Wu~HM2yPDfK*hqMTDl~cYihi3t_%m%ys=#`lzB8TZDiq()ercg6?{avp znjPBdCgq1DQqu#Y=~B+Pm0Y}Wp3D2;=Ra{);<4(V*HdLpDQ$dTTE<;ATD&?p!v3TJ zp6_na;j9j$_SbB5;+Oj78jY)$e@FhJ=JNNxw6aY5cln^im;Ch}qQ!^38Lp;i`ANZA z9D>h0qV{>&DZ4|0k|iWa@!C$y!l){6h<>Q&g$+W?%T*xDtyK!?5j6XanX0?M=MI9y z2>VFMA{tz~IwN-R^9Q1cjH9KX4Aiq6ihA|{?c|*K`ZBB`Ws-1P+Z#{>TB{`HROrsQ z$dQpYYrN2;ZTnNUpGPxMGkL$q?@JrpmP6|;zUme2iEgUsD~rkOpQ>D&HIk_jJ>k!U zhf8>nlXPkO^y>*bt7^5KB?}wZF>X~z5otZDR9ZUezU(aVrDVOa{4%Ar+}mhKoGHau zm!-31SEXwMnRoU>^z&8%s(AE()d}s@TI_L>-j$aZh~11J^I8vEB}At6U)+jT|jUkQ%qV zhN6a_LeIX}%L?Mcl)wO#0oJR%!1oHo=GPezZz9=`Q@ZiWNAZUHseX6=+%aaS|H*Xc z+RAWfp{d>xe!;)C_wJd#^XaYs#%;)5)%vcBGx=8^Ksaw>_Pz8sffQke!@pOeLMt2U z^Xz!o%rE^eP6=8a!xgrdd3p9<1YciMyt&IT?ssVk?L+wu_tr3UX$z-p;LCG945{)U zI(c=g%Arsdawn5I3JZ71Yl9CM{1{DoxBHSPVd&`+P5F@LlDda3i@I_4Ylf6mmiRXtH=mQdsCqu6eAJ;0(Tj{O1Rmfn6X^m$%S5@BFZnSN32Yzf_4hWs{3e$!nIWTxTC+`S(DW~Q*kjj*q5#S;wM%hSfT~xr0ScsWy@3R>wMDg5=Rmn)auJ)baYD5AjbMrE}G=q=rfF%D^ z!sn4y6d;yg3C!`e|D4Nd21+%|sDVt#p&3?1HTT<`IHdge@nc`*t&{G+e+CeN?tf7< zhhWbnLo3aWehf2tIVJ`N_ds}Zv+lzOW6E~SJAe*W+uQU)Y$|35l}lK+H*O+U_VAz6 zZFO3O`#LI$jso?(lgug!o`|cG`_S>6TUXyB;!2;VzPn-*HD~_^?^9nB9c5-U_{R@j z2)}oa34^HP{w%LO@%LCmc7jNJe?*I<<43KK?X-KJ99-*ulK$7rE4PfU${jx3Q!0mu zRD3L$X2j$OtG(y&rzzbfIXkc!JZ{`rQ1ooe$ZmvTh}=^GwYUO)^b49#K#VJ1`c?Z2v$xJwbcb(S zTwGjFvY1n`he|4Rua9@}yI>+53$WD0KO63FO4g7K8_xn75YTw7;CzbFbeo$LIvT;gfnDc-pF8!53VzR3s`aYB1}aR=KVJTl0+Z=D=O~} zu!=T*K9Q&n8F#zcL`d{~$YD{62B1k*4+*n4inu<#Jc~DQ;GeX2{t-D5jNN{HtaI0C z>|ywOG@;nCfq}&%)2DqkSsm8xIE_P4DLe%r0GlXU&{^}l53Za%3hHYVB4Gfk7Y=w) z=UKMi#-vjAJtd(p+_zL2L*Mrlzc&kpc_*ROB$lLlz!lQ+=Qoq57T1#z>UCl(8!hrv zaOg0wEs5Cpk-ckkrw2p){X&nBxJLgL$(b#x!y6A=IANezW!-V|bEYEO*5KmcnDX9f z=a~zi%umHPo%WkwxXP5GVUs%9q!AhN*@d~Yc;=I84iKu->)KtLWD)k-;6bBHo|tW7 z73lC4I84C?ITr(4vQJ7lRvwe>@Rmz@%-Nlw(>rv4l|7z4IJt34AvLg2!)kfweLl)91j6QOHFx#OG{#Z z{sD!U8mHc5DpY@)4pPN{aX1Oaq2x<2gFf<{!ERN+SYC{=*xnEN+|dTv={vk4A|X0i zM&53?`1C=jx9nEqSD;O?T<)&*rGt#i`_egf z&(h!2Px=Tq|e-CUibT-W?ujYc%hx z@DnRPL~jY$$uDB-3vr`z3L*W!$Hx(r33&_Qt>70lcRIr5P$0;)>{RwU6Z`W{)Z1T^ z-7mS?op?$#Vm``>AG+Yqp*r97{-bf-zS84-J9%|?-X$@NhC-9Z;$N~D7aDxzO_S*h zu3RD6wT!;U()^B3TrkphIdjy~|BFHl7+$$hh#0T>8YEh}w|inq0LAqHz(L#Jqqz+U zBmspPkSWmW;wsneyY1|MA~szu11W+W4ei|3-4C0_f|~rh&FZn zyvELH&;XFk7CDy|=O_OHYLfQ3bMc1x>;7n_8P^K^?Mac&E|YILmvnfm%<6k_+xKfK zEC23d?S!YUDt&w8T@L^r+!m*!K@Qd-z8fhxdXS*Ki_ke0$fPj*OscPEVZhHu+VlLa ziI+X)QzJTwojWgaO^wI0P1e?bc_>-Tf8f?_9+U1(y^uFS+tVyOf0QhZy{Px#S-bDF zj!pZj;q~*?py5SdQyN=KCtva!Mw)(;RzroZx-0Rg5KE{j=+g}73%if0fcor1bD&d+uf&HPto!mEs~h76kQqh4&R?MHTGacn)|tE>u#P}-^!8kuMInX-f*$o zbmwPhT<2~fshBz0x zf5TeUiL=VWgD)H3Tjq;Vwnx@-3*=4B%+OHSgC#cX}oj zl2YL2qMokP6Zw*(va9%g=uK?MVl!Z7%GGZjMuyL7dK)L-WZ?0ARk4@erGV~mX4|r~ z9X~aB!6P4>6Fbd*L`}^Oj4YUa7Ct%35wd5E#IdZD zR6LH?Ungewy^L=Ut~GuCHOZo*Jng?fouA8Ag4cFLYskdJxadZcncgu0zt7cSjVD_b zdg7_wr_KyZ6x@hOoVr$wy^Du-?5576((qdhmmKVd&41Db z$y_LJYvylvIj~wz@3%32*HBMIWY5sQ^YZ?oZLhz`Zq~w)&o}q8>%WYUvZl?~T0iSs z#iO3qjlI(PaV)-$TwHKJdcHjG%k<5h{_WT7q#pLxXB;o~4EE2LIAu*|Zk{yzC~zoRv}GI6xThC;=whFLQjt_{!7>X(gdyc?|vO06nM1nL--4>ph`7{ZS=uX>uR>i z4b|>epJ<%qQ=oOG$)}rtzeoH)U#zK~$?wwF+D@{}yVsvj?aoh-E&jbI<5u$Xz!Ua% zcI1Xf_@6~Gk{z!hr>ZVCBY0k+%$(uXsCDF($|FbAj-U2+`E^m5imFS4hi827rBe^$ zm4y6!m&1e9PD7G@WGLXI($LOoeyjU%WvZfjAV6+&=+MopjXxqiQcr)__id$3|ILOk zHoU246fG7dePbhw|Fb~K?!0zeeqo@9$nG_(*KSzl6BO!9PaRt79@k{q*OAH1zRx{O zX;{8s>kqUG+#rX4J&x6>c2{#%o>~eRxZ$F<$@)NqubJz_cAnfUjn~Brf*w82>&wOp zgP9*I?#(yck!O{B-Sf@Aoz`p}71fxjL)~2yk(HQGWG(n>%G>4B&g31>4|B2(kgc|r zFM21IWoP8=y@^hWWV5=nHD@`_Z`Hr^Inwe{KaKUFh&y%9?=d$iGhFFC#@@M)7UegE z2^Pd}TKAig-{Sn8*2k-2)1J7XKtJPon{so9jtd?6f-UFr)fl#}(-OFLF2c7(WOFCJ zmH+5FG4@Y9>^FD#On%yFmCp982PZpR_z|P3zW4Oeerj}^COyICK+l8(HhbLPwq`DJN|PQL1BNryAuzAYw_8#^auLrK+%-UmESzt&oY zLm&c)ho%dd%osevf;{eh@D`a*Z6GoH8GgW|(B*5)?r1Z7v;LI3-JUuxyiSvmsEZFX z5@U-T=vkMFr&*T^7N!0atUs*Ldu*t!i%m#1O`2@_P|hPvMn!h3iiX&~U-`1;jrH}8 z3q3D|CC=qfa!RgCSO1wLK$r5jYg;FzP)cBuq>uvO5NNaxh}9}}H=N;6aUHgUHL&s? zJop!AgW@+>6+*q$v+zCqOwsu9)Ai(aooQfqLz`iSz`b!cH$rNr5@lOAreP#xN&-o0Cd zz!KbQS&3dbi+L_F|0eS9Q)rHevJ?{%a+tZfxtLJxH_}R#TUnacqG0SmCLg?J!otJf z7K33WmZ4>($!AedgIkv0ggMCgvCqErb9-%)*t34wZIOC>(!Z%9!qO^xs@-MxYQ2Vp zxU*hschAwkeP|OusrkV&&i>zxu)4?E-?)mA+@P|qZP!c#X)Y+XaL#yv2^Z?tcK*(0y$$46L@md@M_k?NYpX24hqql1G_sOVCT zBTa#-UzvtNV8@zu))XT^{~LoU4^`HoI?%Fw5rb>l0gk%gAr;&WAy1hS1!W07(7SUy z-Y*Sz%-9GiEbN)Pvhjg%ZbtB?tPy?A>yK3x<8(u=b}Q^Fb>*vAlIzk&#AQ+tP$aNZ z!G=FW?3=g94HxZuTb}FbiA=Yz>G=lsuxh!Z8`U=y3g;C<*SekpC77_&#y;R?fve)+ z2;@e(HTs%l7*H21G?c)65m-D&!AIddUtBwV`?E{GwOH1G1=TmPnr;10UhMB1d|WU& zU9UIvsby_{(Jd2;Dx(|KRu{t*$>MF!PI|6bX3sK{ruF7QSReA2h7H3X!sDXERudcq z*ZFMBmmY@aNl7^rrp=o*2JUrB#V{Z|u~C3D!r4j`nK;fmvdt<)5N24?;wFS6X@Z`_y|B}&q(7{4povyNhI+-KgkCKQW(i4h{Z z`+ZXQ2ii~7`%+b@?~JukPWfoqydWRPS)$$X0NE_qGuoRr>c2EADT()3x|;m#>2|A6 z?Wb5a{u;WfXVDCAqGi}YJq5QTQYLPS7$sA@U~EO`Yg^-<7e_VDhh&F~Yti&5i@N z%6s)LWQ5+tCrg>2IdZr!O1!c1T}77rL7KFY%Ze;p58$v>Sbe9>vUl0KJD~_$hXV#P zW2n3%;XH88jZEU3G9so6qnHuIzsd_V8@*bWn;R(L*nEUgtMZ>M(r5P>?1+|!@W+L# zLc01DJwKOcfEx!wt^96Aif8pHZ4A+`qp6DQj{l`%BJyJAz_*~;^Gz9k+{YXl-^2wR zzTtZRZ|4zm;y(HMP(5@SQ&@VCk=0fF6VqX4C20y5GykM&4vT7UxE2y4meRIrfv){hxCu->9sS)B9tr{hW#3f}2)! zSMc*a*c>$`MfR&@P%m`n(d~^wDkf>mIIAICTort?y1AKrz}pUb7H@NeO8?I3HA7*3 z-=$<57F4t~a{T6oLwgG>TT@%Io`XN~0S)Z)bPZoJqv3OuSWFj;ytu{P4gmZG8FU^bxHmg&*gnPM60EMxn~uw2?XJLdM^{xN+Cyt@nNIL$!qjOsSrW z3Oz5!knz5CT=uDbc}Iz=wVJunB45pMSi|I~vO>UO;Wc40gU}u?P52>bUWBny-x>?~ zF98;tERK|PUXMlG6W z-MD7@eg69oTsrOc2RK~opT{@q&++L9R`r)B(r^=N6f4{6g%>QiH zJ3OlFfsan(B>n&UhvBP(gTc!P-&=~CSXNI2Zra{vK`xL4UK`JOG&!x7fqkqdp2pp! z?lB;QF%!>^l-6N;+h)+Vmp|-@{SNAYh3nE(Rjq-}ZPdqOMiQLaXlzl+(GX9)7|Z+M zNQ2h$P)7~7?Qwfl5L*wfs8sd|a{LhCwn30`;@P?=lAtJJ+*ELd;(u-3k=~c9)Rb!< zvBi?3(@c`$64&^kkRyG8oBmTeGx(UNvTl=e^)c4@JVwW3bAE@ROpZ_xDx9kTb5k`I zk#D@6HBu$(ve{3=m-+FSpfbxt>wmwyKIvO^>q+gx9O^CM5mhf7F1KG3VX#l3#oIBu z@tkSKW^9AZYH4h?VQDLSF~>BmBV{fOuhNB(Gkbe8g>uq$B%h_VZeGL7T*A0fUGJ>? zRkEq0Z|UDTYrtis@p;W&_KbGVeOnJMt5jxNMP*$zJL74dW7mFFisfK^k^=vx_%kDv z8C!0z(koOJECS?z_AkF$E$>MC=b`p@Zduw%GtaAiUlCN0{qg;oJNn@CAhIo<4L23I z@XfRSDm-u6ZQ|L#2yb@w(;6+g>;Kp@@!Pvuq4pXbh2qAK#)E|#qq33fm}}!4&T-RQ zum!AQ_C^X{^qU#)lZPeO%#1t{ih+vgd*5<0l%FA4R!ZKngNI?mT8f>1$8-g=f+aU@ zZPuXGZmT~`l(74IpWL06bemi=moBK#ZJS{BuX6Q0VrcZHXZmvc)Omr^J=F>_zZs-2 zl&8dWNM94@ihc3g$NY@|y)}2hsxj)O-5a;)`LR- zpzx=vY!-QE)fl)LwNy%!=~B`V*JY>PZMu!7dH4KZXBU^J_^&RQ8d>8ogZrE3D2Ow$ z_-yX3iAkp2$x)UNh;p#^jjK8~KPr4o9sh{3mSU60X(!m~73_aL@_yLQeK8y8A6>&)zMtCM7CQYf9UO(3*yZDqOus z!MSB6R!uqRu$V$MKb~33NOHb+=XWVyjP(x~ZD#6lHsA8p=%HBu z?!o4AvBS@I9~$~1MJdr+e1?>bUh!=*R=rEUG>fTF@8x0ct<=pCf+AnU#5e$1lT-L> zZtQ3Nt-CI_N#_7_X#?IEl@nlg1XI-)eAERtRhAIQndbH?K5 z@e;djOj~pRq3W_iXl5g-@;bd;Gx__s$QKo^{r<azqc-OrS{{11>)Hu}1OJ1` zlZ>YUSgq$!{qB5^8=EP~8Umz`zxxOinbdItL3K}0?xj!*=wT(sWXSsLG2w8u{C4y7 zDYw0zZdUIS2TfNJ9YgTljG8aVD>zDHoKl{Z(W8`x5WR z9bYd$V>uRz$i$rd;Pup5a@H7x0s|W}_38db=YO@g*MG@!i$+o0Y+XzE9>vj#39`ZS zPdr|C1^jzhOjM)WeZ~=$3kLs9-@ zrD>gelR!(1Kluz>FP&=e@|YDEI-lV$EL>-%7nUhXUWA<4x`g7<8ut=b$ zm|T4<_?RHQ@AuMkuDKCseAx8lNH4p&;H|RpA-fsN-T2k-GBj@1l9crSq3b=sxorP8 z;IAzsBSd6WDl@61vQs1)L@0YC*($4$j3TQ-8Zs-D$WBB?6haXqdygU+rSYDZ`n~`0 z{@>&1c%GgoeaC%Y_jP?f=jR*|TzPbq7_}?XI+Ijyt)$L1DHzhls-Y-IpQTOE=$-Cw z+*4ha<+16|%ea+qoIWMSKMb;;GMCZ4S)9PGt9xg^64Z_5)G976GE(0GET(?Ax=($i z=^dM*@sHk^>SJq7PE?X_37oI`$%%6F+-JolFYPLR?;maR6I)G5oQuM=-0Z}`+pjJp zY~>Ke9=%5H&hlsDA9f4zGw(lFT$}$mZe_8M5L>tbv(dHn=t0?aSGJ@#4uNAf0a}(i zr4fYDA06F5hc!!9JUDEw)*z!j!bi_v-S4I*O}=Ti5@#js3;@MkK4{Gu|99; zc;33`^?IVjCDgb4=XI_=G|sn{1yK6Uh6&1zN{C&OCvFn(4#4)8r4E zb7p<+=3Dii7#a=YfWdek^wr$)6Y4mdQe!#83{TrVRtv)@L-Vx75QHk0cQGIhU8DAeD$8Hhqr$^RGmr}fLZO48}Gc!Am zor;OBxcW-)PA%p=&-0hQyIJ#(##fe4N%CBBoT>M|&%CQ<;VxnbpB&YS6mrHf+p~$G z4tbSzU`{_@)=fwL{Fdh%YhLfwJ!=uPsQErVYt&j+?oRGO%H*S~NmeUZcW!G+)LS8x z%Q73FuFkxPQ>~^##-tP@k*#0loT$x4empb!P|VkS*w`~th#^{QkUV&^;Q;&zK?akj zGiLKAk6dj7xP^;OEviTaOpCEnqUMTIe;-v>xopP(REW75@e6yStGK}-{DutyC z>t~rlgc%F3#eSK%Jkz?`Bu!1gl=4J$u-8~uFq!QY6-DhF*Ia_U&V-`tO}aSHr zos0T*s~!gcK>#Y4`dIDf84YX=13j6$b`;(+ovwUEiMnc~u5~~*|8{;u*jj8!D1j#V zA2vNnT7`dcq(ZTY(?ZFz5V?0-*U7`=|PAhL=yCyMCcPoA86b_6z^ z!aVP5!=HmEA>+pu_}$0+`1~P?zjSxNj7ON-?T#IVw{I}>ADuq;dp&bVPJz42+}Q~C zTX*AFCc~(2pAB!e;~&y-??-{V?tcwnJjUinTA1z8zSA%SL^sX|ic~6&m~17-U|ei$ zFm#?26-;B)cJDq_n5=ONIN87bnBv3hzC$&VY>uA~EPwl{<)wM9zP*`|mn}IdfwfXm zSZKB`|CYJz>Y@8bvzn z{hOVZIf4ybfg%Bptg7p;>5sm^be%V)=@LxnRQK#3#kv&XzPf+U$h*3gz~QJVo~C7! zTWNk5IerOu7dc>-Zu5#th24MG$?sSdk*pgggnS1$c`_=Bo8*p%8zh<9tr!a5dm(}S z>DI@g?7CZMRsE;Gog7KFiYhp@(mb!oA@jdRGRLa!nlXwD_!f%hj<#mr)c}7BGxPWW zpkqfzt;-K8&R(|!7a%p+0=B6D5uhZ7#vK&r2Q@GtY#5?88jqNvSz0}kc-55n$ zE^XnBdhSbuVj`=vXfj*s(UI~~KjrOmjcbnOpVkWkz`cq!at&FcVoKtCKk``BOY`SM z^aF&2m2xO6`@a?PW>%+pDqy`bvK6%iHPSe&tcf%SYF&D z*|tLW@`Ig%oFW4pmc^&}if-5qIJPsrv^(8y@ZljhAuPZ)vgUmS>9{oq3%BJqjh@y9 z#~@}dJl~)(FPbzOY>^hcTbZWRQz!?oOxoT}sc{%|Q50ia**~fJwrY{rfR4h$A6dKX zS>f%d@y_+OlaDO&md~m^xT$(gMO0AlWY3_g>T;3FZC!TiT^}+Ca&me)8Ey3uCTJj% zKI~O(db&0vRD&@+4`43~pRD@m+r_oyyU#}NQ@?~WMBOUf{T7~y{V0tVUsjloik8qRxGCLv{NKIY$3SmkxF=ADQBIp>XZ4xaW zxBKN`IeZN9HfLRt(fCL=JrK${H&j%4baZt3PyX!iN13%ALOVC4mVGdnk-;$hCvY)G zf@rEIzd5h>dgk18b%nR5CnMn&LK$Vx%F3E?S_+9$+E4)zFPAkXrVXGbOaX*_8qT{p zAVt(46XM@Zo?4{S_>s@*;#4bl!&OSGL9maDZpZ8PCm|A@5@OHU_q7M1{r>b!M}#}c zD2qvI6|TXhU%B|Lsg`kQT-1*PS%&6qCHy!zxDs{_7CqIZZRvU0XqS=*n2%+@`$-P9 zvE_T&1B*j%Dc*(*^-`U!dtR3Yn62M?dU~`%mg$e;@u*vIP$7wsY|_=CHnhSM9Sf>( zgJfK7?Wbxq2Ws>*fB*LWU>a=vA|5~P_JHHs_mPp2 zau5p(e=2?|@rm>5yS#ympeeJSu<`5i8eTmE){B$_-6l`u{*_pRvG%pR{GG{QNAAfA z=;ipKxq5io(jIYNwD({{lAtmxwY|<&0P1QgGUt8ZAiP&o$7O!{=Jo69`+Zl$xiL%5 z32zWvd342$i|FQ+%i`(uv%jzl(cKJb^XcEZiOF5<*Y&NVo73gKC6t|)-1qu#vAwM) zJI5rSn}JRh6ZNru*F=`FxU|n5-d!!3zW;ymdroj+iyO;9J9H1oupoqeER@_+tr5r% z>>(2hqKBCER;2W#-%rT?4dWY|IkrgfRS3m4Iw9dLMFr&g1@I|cApfa@2B``{4T}FC z3=)Q5I9bryGxW)$JLbio-;1&o#3@#SL*@wY{BkHT+le59A}lX&NbZd)I>%ZCPxy7- zfcvy3(hasypyhe#G5#TZ3olC@0QB3DC(uvE!fqnDbKPff6rx_>Hpx=p?1+4ULMdTk z74)EM#u*DhUkU_+jw>H{+nbC0Opc6VuRm2ks<+(@UwMk~#*K{4IT1(pzGw=?Ai417 zjgZ=9-p3;vR4wzDo#Q?yx|XMXc;F=Gz~y_Y;#-;X@3PIzC(48FH73ekj=QEW+@~ z`W5^$NnXMw3tmtuauc!Y($peA9StRxA+-Jkdjcs|z$v?k+-aj_VxjjpQ+TNw^dxIgxC5kzooFKwp{QRjJRPr`lbZ;Df z_}5p=55y&vQtj~~ec*PAGchwaJ##ks+90r#;g8 z_=3}*R-3La7V!546&%RTIFO}&l&Qbb__uzwl60hUptYVRsf?5?`xfzj*it&q=OYkJ z9bZ5ZB;N{*+v0XXb$L0<8n*q(kQr{I>VbLw_D#nNvznhb!om9<=%RuUUDm-|_ZdXP zaZJbQ;abrN-}D;nq<}c=Rj12dkrb3TqO6mpAOZDgFIcu(w9c_w@(TZJdUEN)t!bR@4fG!H_Dzcu&b)NPhF!4df-M#$ z>zw*EVeVCQp2D5oG;U(Q{zP3J*^xi?KS#tvKctesKCMo~VYTh_M#J=G9sLjIPuE>7 zN|j!n*~0Phkl)LYCD?(I2qM&RucV8*>6OWQ!nEOwADt!1eC>5$pL>U-*^A@UZNnL0 zaUF;^Cdv(q#wq->HQ>ybkN^czv_#>b5(G|v9meq6lB)Ut8 z3K<12-aXpzikXyasM^Y&l@@GaVSju$(ZZi$)pwt^gEW)Fze;R!9j4ZXcCAoQIB++t zn3X@#>>1~WrMYdP808l}8Ttzu_j`6V3 zy@Pk1B$ON;782?*l{qHXa(TukpOwfJ`~~yQRD}jBa8Ubj@ppc4$2o7AWasb&?Eg0z zds3Rr7p8ji$sV%$rIst`SuqrnLLh%sLLKbLPes7v#LR`pz9HOt2TcN@3QFq7$=#ZKF;wjGEjj%P+#YMJv$eiF&-P5vaB_Mw#XD1<7|}c zUweH2YO-FHqU&}=6LgKXyvLlaVyTj=7HT+#Bs3mGvLwBjn_)dS5Ve0~Te4EhJT2Q^ zc3PLCndQY6Yb9Zi%hOsRxJT-knw*nl>P7kshc`wsP@o0Aw)mgJ?54>}+EKOuwOb6| zpxW7Sv=R<7H(0ZTw8;bNj6gn^IF?&QD!vK|3X0+P1;KkW9(nEQ^VjdgdJ`Uh!KRtj zNQcuBNF(zkDxZydAI@#B0!mny@7llD6?7}T;_3rHWj=S6)Kbcze>VB>4r7TIIKS1- zol|gkmLl%7RCD_GK2N@O&xA8Z2Nv=A&8xzR!*B=gq`lETj3sJP>i3K6eJ*n_msuiy zewfcuvrbk-srJ%B@}UjCsVW30GRB^rS`w8Rx$oY;qVwFxF($E_CR{*7NjA{Vnz5;d zBQ;h{{7H$k(z5jBYDpZiiq9>&vN$?3jy>V8wT@aY$kJZu%3feUiovu(V z&R81`0Rc&bnd^LmhNRXR4^`itqo6^4C-PfROXqbjRN}e2;~2)~A$|}fv&`1=Z)_F5juDHr!TacuYv;uT_TJHO3sMu?LTw~Nl-$Y5CX9Ga%f7t!7C&T9!DuGPUN_$vX{xQpw$e5 z%0IYY{&>2OO`<3R&57mpki0oSw^Hm-cGyPJsm|pWsdjvV}4=05>nzm)`{Bkks zVc%PPoZq8=E-E~@=^fGXKz$_nYRZ21QqB(Mdb!FIT*A^Rlxe>2Xz~3ES$q<$vK88R z&*JGivp#i9Tiz?@bljLqUZ-H+f{$=r15NL5tw0>I;z0u!K3_Y+hU!J`|KePz0S3h} zVHfehe(%x|yQGXeY&YZob!Kw4A`3muOgZzWnlxQ8RKC^;$Pw9-VUU8HN+=Gl!&SoU zN$dDDc+hW%-yiqg-0jNB-QTs2bPc>D#o)4t#p3%s^b}kO7hiONVhgO z8mm%y7;B#MGRs-$TN6Q!nhA0Q2@)uXWo#%^g! ze8~Gi#JLo1Ky`C>>jIKxs(qEVeCd5v$xIz}Z;Ob|iSqc4G@nt;D#?JYZnUSk80QWP z8>XmdMgDTT6v=tPe#dOHNQ|w{{)@gX_CU}Bq}EmdK`#{|2s$PT>vFS&0m?mq&lK^}NA0Fkg@r5rZ2MB0!aQGYyRcQSCB=+x6V?Qe3sHUc-Rkwr8o2e*h)+9PLyuFKNRvS*+lvI?L zFIYN?=iHS&a7kwIDfxg)}K$L07S&6?c# zxBKeE)%QMGR=)d%y|IG=rz~@Hl+oD+!(ghPB?F8fYB(mym!}rlLv=7}?*(*x3Ik`p zK3CS=14RF2bV_5@D`CHGUw?PS%xl>iY=B@M(6dpmP5OQV&ri1fw-&h#8$vEeMxOY5 zg3~zG=H-k61SO|(4?3fQxMQ(r&m$@ByU(jEzdx72d^yAOvO4vnmQWC>LKDt?eeG6p z7ns1SrY&hu)`$jNC4d2?GC~eHP=g<4aLPWV!0D2>><;_lwB3k0ZY>I5w8tx`Hf~oZ z(U+*EjaKGqV(W0~Dt=A){hh|J6dh-Agy)E$1HFctW(Jplz?CJL)y1ft`^8qI>;aiz zV@gOw)f@_h40yV>w%jtN(anr`8TD}VqF9@ zyU{-21|26%T7vyS>{=D65Z%2m51hBEeXWXz9?yQ@w$0$@e(2yIs}>5`DbAF=KKv8- z1%b_*&Wy!e@0VW+pY7}(Q*B9t6VThJUL+k%pvZs2x?1{pVGqj&xHo)Z*A>{7X6n#{?X{GRfAcsEF)wdx{eFMHKLCq%36C;g2ZzVB6c|TUoZ5Qx z>rDbRrt+q%2kxaxu;c168k(g~;l?%66lZb^W!NRQWABX&ZfQ;G{qbla}K55xUy$IL`G znZ2lcfxPp#Cq6?cl|{)yBRYzb)#L*n4pIfJ->_i|g*XPq?Y{c(82a`g>=9F_f=>7$ zcySaJ;$utK(Gc^T_9G!MF%~_t70yBCJlTzP1 zQbpY%f3%iF5){nP0t}M6?6{4++qfH;Yh-Uk-vovp!EthD%CB=xC+#CR=!PEpgz3kd zYoxJPbk_>@Ecn!F-Dcak^UJvCMRj%ei9-IkyOEvf2O=*AeIL?^bL30o?6Rs>Tfw~y zQAcF_qXyD(upBfwb@1xgpSBFTC2jaFq-{{-h4G8dqm~bLb~F?`ra1&4_(}vk#JTu9 z##KXo7R#z#Jty;8ts_MCVLl?&GHwmiqF4k(so%VFr(OZJM4^xear2n*2vAHtd+EMo zzGg@v2KJKkbFVlqtKU%edohTp(kK$kHi3MfK@whd`TJ6V_Z9(gRBdtJepf61KKw9t zLxN}d4?sS(*gN8Z*GNF!X3_}qAZMPWVV26!0{`3iE!DaP&bP7W8YcMt_WF*C-F`p8 zTkEN16_eHU&Hn!Wc=+7Ynct;+uUcuA+87+=y*IPf3brs5f09Rn1Z7+1HGve0VXUfu zfdD2=r|pgx#XZ7O<#p6N6S!kXkY)>CbgY#d{jT=lkX!4%o6@JS(pxEgP92Wx2)3Yv z-{-<#!foK_k#bM)pjGrWm9Dl!>h{vwe!pgsIaZTV?Hx{|0iMJK{9;B+3toqE zORV!hf8Z`C8eCjvXWm@sm;d&q+l(Mm9Oic)c%iK}fH(*0BjyR>_=9|1D~kE+tD^Vb z98f#2OfCf6LcFQ}OH7#`XfBS>eQy3nB5QG zUOIhe*{Ju5ci;A6P^j6;7bbn{p|$B9hF(SwoN*um_YBX@M}isRE6(?`c=EoW+h*Q- z_V+X&vsFtt%ZfOU%Z*rCneX&_330`m3-$wyIOR41P|5CI>w5QxOWjos4G=5BN*Ul= z)Q}l^(gZks=Qym6V*bE3`YJ(qe+3rg}N+k^R9tPwreHiz~(Ln3--~ttxHNd$f6XHOC0#Z&PABXK}Im#V%%Q z8C#U;#YG;Sx%0fS`jtwllj4Fxs}S?L-d-}~_UWU&bW*8&XNI&;&OnnM)Ocfzj`pXA zwuAM7A?hQ)QEvDkEMZt=$Nf%%83E^W{-sNvo@k5qnQK3h0bi+yQzoym$Q(&*T!Hm# z&QX{RsqefK5v`R*k}H+rtvL>}1<9{W>)n`HS-Hs#oW(1(30`^PrmQgaw$$r>9mS@=*3hYp$~cMLn~{J_FlamZzs@VP^=`;4Wai zmIE*~$OcL=3NsqRoOTvOv%y)uedz6-Q+!+P*fV&EjH`LKRy=PwwPMr8te?HI5#}4# zwg%Q6-y%DsUDMdnaDr=>^Wa03#|l?N*E>A9bfk`l*-hYOgkt0L^mWyK5qgK9 zLlUBm3sZT&OESzsm+{yHMc76K*YYz~(TopOX_7vGVx6<$h8s`4FnB47_PO_U&g&mK zTZYqg=EF|_Wu-r+Jm*-qEn4X+FQ`%j&=XyPos&WR1I(NlBsewA;tpzeaF zR?_r!GMD?j<3+~n*Nqv*bic*`@n?fi!$7YF{z$Xl`rVwoBe6d&EjGIU!}z~=f8;um z(|+i~*nS`%^1eR{iI5tN_=c6x75kV**G^|$VD%SYw%suH{74zSO3Vb!gPYarxS~-S z3>7kmeA+HX42hTy=J$J2{h(&fo~f&;+p>07!{GpzD#f+W!g-dzO15c} z*pDa6gv%~naowKbumdr=3vFL}{m!jaIO|A#Eqch!Y)v@**uE7>5my4YD*Ut%T4Kl@ z=}#hT{`CL_8d51Ph$(MEUX45jsT8}dty5){lwMRmc5Z5Fx(YpqEy|%7KYcO@1pR8x zR0<7S;14kghhuj6A{)B_MfJ-cp(RO0S%|ue=`f*%OPGmmA19W+9mJvwOUNCLT2kxWu1H4`>Ij7EzN@pi{ zJ5cwJ#m5u*({6-}5{pj&sDaIU@Lb_n(u*fFf)Js{CR`|1<2QW9^ejkjwAi(LGo5S+kFu#gOXu z=}#*dD2DV?M>G?4FZHHHspxYJwKc4Cl=$(KLDBL=Pk(xpiZlW$TtKI%z|#oI?@gQu z!%fdUoSl$D6;!o>%NP_8ct=DEIr|LX@sOl6!cp`+nc|W8Un~m1NuW*Xw@nDcQWc{n z!#sO^ojCBzoz`0N*(r!l%^%gbzp4#=k<7-vB1Dh@NEPhi@t zFv#gCUb)92g7;x^ZGziVMJ~77;-O|d6}u0-WD^-^I)eLyBf^k!10$fkg|J&NalnzJ zPGRTf*6-tn!O%PJo-(Q?lu##2RIzxl)oj?z(l6lKER*OKW;j*s-+BA^fI}zs`=SI5*N1l$86gVo`7?rkuaP{e!x-_o~F2*b5<@f>WMczdXC#$3(7O z|7d_{h574@mK^7=3kNo{;_8l^-bqhzyZzr-6|qCqSRyk55pZUfGEP{=UKCAj?bI9K zq2a?QaMo>iRwLyUD}UMWaE1PWuwy4L9^hgjKY`W7C7UbWh&0%J5xsWIZjaLiZu*p8 zTMHTk0AsL3jE8!^AK)=Gmx)EL-g|jdm$^AFL)m4_Y1%Ao@oVcicI;jqnQX2hwyDT; zeXy>y_|Ck7!m@bGCT>@+Ix@9BJp5f|qs zZVVq;D9BH0PL3m=w#vrZ+8j~s6o25j!otFd!4hlW+Y(ZMVe&I(unBk%-gW(CC>d-& z!fuzG3o@j^8kHz5^{H!@dWO&VJ<4PX@Tb1Uic&K;xv|*KQUE8m|6|N1D zg{xQ{fr?hslx3Fw7WW!Wi<5-%3Oh(ewoUCiiVVoL9VG=u=wxUqtF}97qecj9*2VY^ z#CMMbNDp+qVk^~0jy?(WtvAN36?B!|0ZEsy1b{ky>DRAExyyqsdW^P4yU2X@)Af?5 zUfXTA`^D3Zb(@VQd8FAK-< zuz5_x4eI(7_RD8RUC;UC6(6KtnV49{^em9Bf%!q@R&6MSbg_ofuS%kPcshZhq61V! z3c+kbP91rt;^en&i=px-O+)+qQ=o&yTjVtMAxp{)_}*8{hd}{}dy~q;?jScHBu{~2 zpJ}v-|xpeFKzv|?3-jA3G8tQ8` zs&&x2<@4)ErS+giwsE4wgBOcYmkevtB&|7pvesV9p3}R`D7$Q&#UKC8AC%j6hLa;T zLT~eU=V$qt&^=N64K+pm?!Kv%oE2V^dKKp6P_w)wwxqD8i2Vsy{t1GIH;iCg%m?AC zg)gIY`W1gR0*d!CQ2=ZVzZnbKy>#?wnpplfPG|k>yN)$xW)C(T3|{ZXw2pOEVS4#Z zQI4Fc!>@Z%ivDnChUT~`Fx3lOvE^r~ljFLvgX?DK+m&?6bnD;xdVi4%t8f4Clud8- zDAf@ogNTrpnxp#Q&~)(df)$&;K*yxxx;-7%meDgmOImk19s7B1w06y~ z4M&hhYAnp~=_v{4=ZpwRdjZ9kuJ=$K-(mQ7PC@W!Y<7OOh4a@ahWz#CF}dSK{ZxP~ zaJASpzHYF)z+3sWc1nRMQYb`vCq48IRBz90Vz()9B8uj1o-(|n7w3(=u3z3GS$rM8 zu8Ow{RWr-nbljNRc2O`T{^HY-b4l&4_T8p-VmqaFeqr>z!n5e8&GXjR=h3g9Ddlgb z=R{85yZKAnv{E*WTJY(mAoV&G@$3*q&g%3wrQABfA2NQ#UwS^p->*-TXJ6tY=$_YY@v0>h!qH6j=KVx)}M&e zP*(wZka;+zO#HLoS2Nv$r;>WP)cUjMwaIeQF<6&oTV~nco1|Ou<|W^^8yN!H27Ykg@EskT_cK=!9F9%l&JI|~KDb4(a?Gt zBgU|@e7AIPMio|+Zvd&+={o`ZFiy1>})>!I2#>*(NzUhNqvVid&d zkp1J)B0k{QN&6zJ(dL-Zj4eKTucEJjx_AuMLS>0z?1d|NnR0YTCS9`MaMJoPnm_%- zKjMOADp6fwpHqK|hD422eaWQ+Kg~=G% zsWGMZv-xbp$@EDDwulwxQfIroj8AtNOq?5?EI!|LH~DVgWT!McE`{=1%{3_O`|WTsTK51?K-BK-cZ{sus2~1T(89(*;P`eU}^FL zJDij;-kB+ zy-Q<8$g#qba}kK@#NWGps4EmNd1SAyE+_1MC=?hyAeTL(7ZRm`-v4fIQU}JO06S&& zy4azE@htleMPbR|0}CO1V+B9I1pP#kw$}w6S3-6MQf`#(Gw*a5uy5d!)CxanvV`}q z1%qGM?~kv)M+tR!;r^8i%HL^=8QAF8B5y|kSD^V4$_tEW*Himb(a;~+d5#Y%Xh)dk zC1aAS_eb~dRT^~Iv4hwYoQFL1+EsLq=`B+?r{oJ??iOZpq{Nge-_UqGu7nMA49O$m zO?G!<*UQLcy>r)MU|?XT95Q^#8M<_W2OfzfQ1T8MoZu|GJk+v`P4bVP9;2;4B6Xx9 zWH)v(FV5l0h5xIRndn!*ep(EG{D&W2CF zvmsmPb1!Dvl<__LY%+{hQy_J%KYq)ktQ&aTzF&21gb38Fkk zD8Uw;N1%dW|5|{XQe0d-g>}1Xadvz-IyUwd%c`xK?VwHhlCJ}3?W^TeUeq5GxITMe zZ_Z9dsgN-QVi+B)v9z|M-pF*nUD*pTRS>EOK|M!Snkn_`azE zD2^#AxG>+ho%+D__SW`;fUq3rfbQbgi8Px$*5%82lz{WT2eI>Hj|>)!CD?{YTYEq@ z$--L%_3+uggnFELvxIuQyqE-;2u4d|`ZxztKRlR5bpboR3f9#-b9>(kQE%vpq#l;H5gJJXbQy;DvL=Y}DG+Hj+ir>at zcMGjx-hccye8SSjxbL#4RUmT-qa?z9EM z-G_AFmH8*%2YYyAEUn|p^ProL{`7(aLv(>7KdpPb^NKrg+fdu0&%9J`#5g^?iVu*k zv`z4%_6-?TJ|Uq7BpVRhMTt|q*I^=|cTmxWSb1%Qhb|vZ;jbv4zgymv2bh3W)78t& zL>|J^n>Wjtm5jm2Hm>4S^G0IMW!K$BxZA)j4yUA7qm|eLHjWLI@cQ+yf zUU?2ldqbevw9~T*J}!12UcO@WYAIp|Wx{6p_b56OJoyAVJf=a{B*3&WPB za!;MvkbL<2*x}o=EH2Vg@VMDZY|0XY5u5LAgGYJNAhu;&RM7vGyFmua?<`l5WyL_e z-*_^19+h5lr1-~ms7aKOKsoCGhCPg~9 zu<&s1FPQ!ppK6tEA|$8f*LEU6_c?f$!wT}B)5$`B5c98P)<#GGmK+z3M+P6Ao1oIU z&hzl)#WKxxpT(9I%%J^g(;LJsU>5?&k4f@q-`u+II{@Wk`I}N$#-;I->w+fJ29w*lO{G0^QyiMNcdYpSd>%y`4id{*0=?gGJ-DwOyiTU^?`9Io112r`aX|~}YLP#UA|okQQGQdx!i>@# zKHo-ZU}cp+L+{V={@6b9(RVgUNMXf^Z-~)C5Dpt5YfM_zz6tQ1T}6f~z6eYfro!uXUJA}psM)?rp?X~;xW(u~L7c8?s({9?7#tfYb z3Ze^X6gVLyt1>}deXf^;?V7+eqfTLDIS zYqHbckTwZGSMGJJK#iDA#O0l2g_aWGJukgI|K5eb!hats4vtJKO+Be7Q{cRmwYC-j z`5I_?iJVD~S{_^t^CCBVslDqjwmvw#WLEYq3X&Nto@@~ z{Sp<};PtXhfZ#>u<~^2{ zKVv%6lBpGn7OH^yU>y3m%6ne+R`?5F@BSay(G|^s7i#1N>Dp|}O%HOA;|G0-chja# zFKcVZFU((qaX60G^&2;8vWE#x9ISU0(ACr90~t#77rRv#wj$UjlWG;C8-^}6yw2AL zPw$;jDfK2cHx;q5L$JANT?w0;q|3Ld_g$p_Td#<;1Z9j;k3B+ZD-MR$0NfeYHo%Fb zR^0s-q1*;9U;XgNMy`iwuMUEkppOaHaOv;aO-7*8qR+Q`;E zOMBks2|dUF*R0WGzwLHJHbBspUnF}yJ5uR~rg-Q3mK~cnM}HqliA*JJ5H{E6G71XC zoi7@}VW`8A5kowMI9|0o$Pw$tT(mNSWQgbsdnu_W_AmlHnEK~OYpJzDLtI0i7={3K3VHaUt#(oYTxT6NIZikaV~B4_-`@wd??ChWd!cD*X`2Dw*`|4akSALE ztHjAxoRJ#QY*m?tPs=^=eSgA>XJuW2V#ga97Q$GH5L7fi2~tjH2vB;^cF0$=%T^9obh(|t_*u^J^4QcB>%nY{*uY2EoY2e~W!+Yj_II$)-6p$KFPDQw$kP+K}qAdi`i0_ZLV zIwa+ou7)oV32yiN>#KklQ-WRWsUiV2`)#W1hdk#tr4~=qJIxCDU-`4B9CsnS_`DD8 zCetfJ40CIzG72_k;kv1fZ5}(RSB3LpLn|b62-%_q^9ziexDp5WApM@r^m|Q71g-xk zc+AWKGi_T>eb6R;?CBszc7oY60QQ0<8Pbhn0B-v5ejO}UQvaHU;%0BF$2A-)d;)`; zd;~tVLz^<`I{Hp@u6}#>^*wv{8Y7fw{{-QVW)^=96`+vA|1Nkz*r{4w^BN(qaL3Yr zhSEX8sryB}FFakcal5jP9E`u~`(vNmShE-h<=jGV$WTF-cdd~#X4MYR`Q)++Dj0gf z=N-h7FzbT~(}COM<=d(pLIL{k%`oT(Z}S1X>C;;Ze^L)Peq_X6R@a*O+@W?O*OcHE zdhEPhjvYmRci!f$xIjUO4OkP3l8B}ZJb_c+=H{NrTv~voSQ@6_RQ|ZXua11EtRNma zc_>^3DCfQp#=Y|ik#ZFXLEmD`L?Nc$p$u}%f$}ea$FEI^f_WO5W~F%-5=8|u!ljfE z*jbvVO&&H~^>UV#jQo^yi1yOWdG>AO9!W!@O2lRV2~^^+xVf%)%mQqWbo|dRPk_B{ zgzb9Nz+Y=*Or8ff`;~?ubVOuQIqW#ST=q~s`@&nbt;(L1mtQs|ml15ytMbsnTjIJH zT0@etk)rab#aJVJRaFUy3EW*y(5zTe?3y1$H)fhCKPf(olD0r_*IDiBcuI-CS#znM z--!+=0z;8W#;5W8>s%B5&y#-)c-(|l)7Y4d*Bruq59g&2pKkCUKECN_Kx=anzCF+7 zK@cPqE~Knvg-QNL-(O8(5`65 z>pHbjQw|?KoDcR?00@H6mZ{sh!w!psa;eG^m0K)-zWnE5Xnw9GD4jR9erhNj^ZQfp z5Xfy9c3?Tzw`6x>M=21C(cuaZ8DSD&L)?)#LT>e-RVdWt(7LOhogLFRxc^=? zrK!-~R1HHn3B`ST&z_aD&R)D}`#|?UDhw|wZjrnTkhDOybVXnXC*di}(bCf!jQ@)M zasP!vwcF*(GR_}qYv)$8zlmpldx3S?1&0Y#+IWu%&vo#U+JrLY_h;U3sI06^tG>YvpY4Nf&M?UtmtMO=Sy@>GXQ$?Y z1Gdon9qHRxa_)OJBL#_iboisVdE927{_W*w=N^y0 zl4Rc3kPsCv#NgWc1a=+#he?f8C=?{e_S!bb0vtG4C(Fdp_tc2PYL)Q^33X6B;iI=7~qQ8x!agSvn2 zXv?g!=2$_|g1{*qojsq&w$&U1)M0zCHh+zupXb2@+Apsm$7FKT3ra0&uIi6b`1l`n zQ0>i`SqqTvqh27q5Il_&M)BH1w~rTOgUbcAQ!iB({lz5pvfj4H(o(-5)qT?{_ZECZ zH+7#^?mYcsqrHljU3G6-rWEgti*&RAf+ zUu*APdiO7r{-qahXnnvv{-8Td-okMWBlVU02Vro2vDoA}<vYj%<>~f_IV%qJh|9p-t^nKJD2rNPM+^x+*ovH z=J%KMAi5q|6}LdSs``#OAlw;)-{4rt=0?w*n{`mM<{KFy0ZOCn_rX zjAe&sg2%r+7?m`Da-E+)J>P??AlWb@9j79p)awlW&z<>hR9p!byo>>dETkw=cs6!A z9X;n0#v|of*7be}&(N2_8@UTz#(2`AK9+fo$`YH!j%@CLrjhK7$NCZkY76J1iM}6F zn~!V5C=#=0TuaHHI8LrsG$0Zvut^?fFELB0%fLju?fCoW%)t|fJ`iF&=OGAProKUF*Mn)dgiQBE2HobQ( zLTP$FU)QYENx0J4a_SAM18+P_SEO>p-px&R#ZFS_yqO%9dXqFHc~AllomuO(H$vbf zjXy@eVJ7u7Ip9@-zqQQ=u_XC@t>J=-ABRRv`t6R!*41f~qy)=q$2X4#K?6+&OEmUa zEh^bN^P8PI3pp2wzoL%8R;O-#DXiqSVy~PwSN^9rcAn?Zy^DtiS?zRs_q5ZS*Np}? zZ=IR!&^-22#A$9kvbkU-+y^Y5Ii`iL66vYm-uSc3H}Kp~4zAU^0xUkC;vgfXz$h)E z`2z9z_AZwm6^lK}78VxN=4_$@9l&=?E$!ZuvzU&8a*qhJ>x`H-nG{vNdi6?+Vh*>d zU=x`q@?wZIp(`QQeOnQ&M+ zDI*^;_&vT%ZA5!>LO2(auxSum-aH-#PzzVVLy zUZ5#{dFZUwQ(g!(I9`0)zO_A{`^6Sp72w)=rsNwbct7x|pg}Mn7xYvn2b!5Fh-0Lb z;oM^auRtQ_7n+V|su$^9G1cZK;S-#Gety5Z|NJhfYH}ZfNqZgP>XM`rUV5Q(Q>vq{ z-It2Td%&Z;2L=Z%h(RzUa6AOWp-jy5l8DcDB!-mlCySs$;SojTGVvMU5 z-KUJiI6Hk^QT*Sk6Ox!aVDBL7V|GLIj2Y8xt-8M;Vq=IQu_FzF1SJdB4sOj zzF0DBg}lQG?U8u4;e^C3!ir5IZG#S&cp+P56C4^|CZf9r5dQ@HT3_)&sQ~^pKa+zZ9v45}yz`mIo zXga_^*{;+Ko_1Z@ZCZlWZW#-2-FieGpx8^aM;Z7{)Cl&vIy|X&{P>ah2+D2cR-}%^ zRa_#SjS-@9re5(o*Pz#5E+BuX&%_pnDbHZl@QD|s$j`g<@li=`MiAzWkIzBSLR>Z0 zi;2B93R6oeLvK+>+LkJPDPlp;Qd_&<6J|NbG=)4!i*sh;_$TjK6cey}b0tl+buM-T|5H3@WUvwQ-wrAV)*ehm5jA zo6IpXCd*UPBWVRdA>g{StC~z~Zlf9!kI`D1)nKfffw8V`9pFjE6&u%51xto z;&8QM1G1M5-g2__S4N*BT`NGqt*oljAJux8YRecvNht8UTbnJ3>=1)Tqx~`Pg|TmD zbbD4@o?q2XQw^Cag5{OJtyn)x@&ME{?})+^aiz2hDFz%ngm2W|UfaE|>&a@I4W8kv z=wf42Hl)6jkJt_6`{lB|>o}W2e(kKAE=VmENoNKho+*G5(SLgwMnR2G0Iym$DE9AV zD^_v|d(7|zhqT^kYE%c4qWdinOx;pm7J9>sxlwLpF(E9Sa#>xsLO+^8 zlrg~F^Om9Hy`ErL<(+|O*#J*{h27S2`@C{<8I=r!=Yuc8Qizi>G4uRCMk1Jg`uvo? zB)((2fJmwXUDfYyXj2Iw(Ch;_!2Uh`w2p!L0)4(u(&t)^Jfy zPngVOz{_Rw{9QqH;1P~%uW1pSrkZr9*<~Aye*~F=s97r7LabIQvK8?!`3F8c4v{dV zBX@?Ed9feYm94uaC*o1Pe9F57i~)Mq#E8%(^4j_HB*Gw)y?R~Z)<{;7fx`%UP+x-q z$gLT##!tj7n_YJh?kZq=3tsvwcs$+Iv{B)oRhB;$++c`VDR1nVgV9SngVR6E*#7o5 zDOLa)@B=fDsKCJg?56We*G)4@2-sXQH25(11}5boRfjl5-D7<=S09DYB&J!nND= z7nh9^KLP7v_~;BcwNoc8F=77uAb^T;oNlIr-1|TUrhjh{)1$L~!0{73%2Vy(O@$7% znrVpYq8{5Q#dT+AfCca0cTkW|7`Bj&w|k+~C-)bq9Z0(Ir85X7^gqswzZ@O}RDY6p zIq=`E6@np#DXvSXF4o0UyQVcjmso(8cPilV)K1w?g` zXn7&gqYy8R_BCNUkZtA*l^KhGJUjTGk4D`zxh+o>QOGB6Rt%^LD4y)#`16gWlW!Z- zj?zsM%2^zj6Iboz>ix6GtyAE5@k>s_Axw_aJ+ACss|3W37|HABH-K5ti`*y;UEO!P zzesG@z<>yOW;y4s<8(1CeyE)n;MI|3RlR&zuEaDFrqXy&NlF_1BZ*01Y2;5`0M^}< z9!!GoKOg8vJK!>;_dcz8RrC9$^|9izx?N-9g&Bo(<;VYD5LqjV1?;p&M0dWSc@eB%Z$1d2?E znMxmZv#0xu=|Ah~=@I{K5Y@c=-(bYbG8%)CvYEew5j@!ni}MU??-i`VDjSPLgWr65 z*#O7xFR8e$?7HiZ*{(<7tXAqK`dCa1= z#zVLkId~*6R@(&TD2%0OVOeo{q>&09XI#J=9t}KQt?12#E`|tAMR>xR?&a`4sBVw1qAGlijKa3vXBi4U!qTH>+7q4x-+0oC!r%@Nyym?cO${|rVokD>|Kps=b+KzC9SU{*J^|anW&I+}x z{FyN>Bw071CA%W1xC&l8k{-%@e0-2LN*%Z@PXz`%&Z z68nV3#hDOLBt0*O5~KodJuaZH047d`-3;$`r`xsNz1Z5aPBa~|QJ_##f9+FOJ<=eE z6H-K2m>`wqFccA%{^3T1aBW3rBp}sgrYyjlB?}-v0_oVnPdeXIeRLCJK$M2^H@$9O zd{|ZrEd@Dy$%j^Buf`%2M;0HG+^r1b&oDAjtCmQYOc)OO*}oyM(rYGA!m(r7QR95K z@9rKSPpg)?UMq{Q*EeP$kenA+L@RWri~x~2&?OzlL!*V+NI-wdiRrsoHM=@MQzyeo zoBZ+j_aj00?r~TIq#D~!Qqtbad6$@?*w&7pysnkz0`3^+wuD_CZ@tVE8kl*)_GB4K zQa8-lD6LPynSm8)^Vtj}JxpY!nv|rZbST27$3N&0WS1`1W`Ad}jZrTag5()xw`a!@ z=*CW(%9_Eiy7R=M^LmjE1ikpySWD^SKydL}fS+0p4R79XV)Uvz)Ia_8D<>hOCjSC{ zPDd&Kf5>_hXs-JGd;B0nh71vrA(bSAl#H2`sYntfl#);sWhy0Op+b=cL&h>@icBFz z10kYhNTwt*B*}ljJ>B*Ht@VA@z3bkm@Hx(TpVzSWe(hH<=Nf&dlkXqJQtCSl3{Jbm z$sX2?os;_e6Rkp2`&-mj0eIJ<9P4DR${c>qT4k3T<>dq)1#-*{KBeHrd4_InJ;ecT zde5*&zoOfh;6_ZJ$c@aCAyF{7BvSyPCHDs^pbj}U3JDTJ0<&7D=`LdNz1R5PjOMq`Y8kD= zx21y3f*W4zd=F$JHh890UoJcfVtiT`5o9Sq?|Eg#qT;VT|C(2eGbzkKj8Ir z7HTJ6p(>Vk>%a*p%6B3{8!2rQAn!rz&SgjUDZTp>i&8484l=>Ji5MOu)h`{Q#Y;xcX0*0{50Tt11p6vY=jU-RrDMqvH|EY2b}-Tk6k#7 z){al~JyH^pj_% ziig+y%S>2nh$Vnas2_O4c024bfQfH&?G>CfvR{iyEV5xbZ);`();6hn$y>0qld2{8|^fz$LL9$0)Tx0Yy z1TjW7Ya`t6vkPuR!4QEc-h*|`HF)W9tan8fAH?6I!Nu{o+er$H15e!O!F<%=;(kP2 z_>)xQJn{4AIJ)pYaAId%ym2alR7C)Fpg}@BpVk;S;0du6jcGbJsfHHirRbPQn!;y5 z8?_W3a{L(bEgqXMa{9mp*@tAV6zCN|->ay(%kMxQp0%EVaE=@xID}SVht8paY5Wq0 z!Pz>rX7G?xc6V`YAjUp(knbhuAKoB? zAJxwPIj60yO}5EwJJn5X3kURl32s&x9SB?;E zi{ewODI3VLqPvcvE;oN<(ZlK#dfKPm$k_ON;|*CH34(Fh8$TU!T)@4C(V=vKFGdO| zKP00l&kNwdD#kj^R=}-H%~xsh`!zxzhJN1XdP#)1AwC0~A#>rc5426`HB$h=>1@Wr ze6kT}911LBULS?$((5O_C~7Ic3-cp~l3ki%m;s_U>)jLvZV51{pUjnAHf@uB@V$Cz3pJK!`^ivLv&-wC(!gtTZ5}I_Y=Emz*mmIsHhuA(d05Y2TR<^JH z9V!aYS=h!oT0V0(D(W(VO*bMVTERLD+@1p8Mftrx>ir|}%8?^Sx-riVM?8t%-riH# ztp%f)ijVU_fq^#_Yp?{Dow|1G*1ln->pRSj#`F$vWyfZ68qBO$VJ?q@mp2ILL?5)L zRL0jC`u6quT@WZ>XIKadr3W?cDsTJXd%ru~Y@OFD?g6FVQ?b zu|>ccuWW8lCm!)tuB(8??XzP#krpEM({f@zFxPMYiGzv^SgI8a)s7@u0l;^AmZ;q~Y_dZ2a=-@I`|Wj=_`-^{x6i2HxY%BsOkgzw`2lCIarY z8#gXNh)&8fi%EJ0eyoG15%R|P#p8G(>c|qu| zEAgOm09+HqwAgoUD;!)T4%Q_FFN&}p-1~nf)!nEhbTR1iM5l zgpRf*>*=6AJoWvf;vE%FF1)%M-RDus*ldl1Q|5sFl(ZtoZAl;UaNnkHpGu2bobw(( z7S8AbwAu?Zd~$xU9Q{B>(TnQu3$}`1>FeLTk>=sp=5?9-?2m4ZV>W#;d_~0>=TtBk zhWWVwNj)Ru33T}YN;Qg#e4#1rW(7CpkmlDu5{mgQD3V`OrGnsI; zfXKYqXI2hf=H1AJ6}Rr+XD60W{os&jDC2u7`@gDr-XkkV4D!ov9e(x|tbPypjKS82 zyim7{!_c*=t}gZ6)OJS)Q9Hzx2jP!Xur7!clg=+KXPAL$+jMv_I)N2C%U-no~{q~;2GVFuA6Fnurt#1qLGV>OGvA? z(B;DmJv}|iv;wGzz$>Y?{1rAwBBUrJ)J^UO_eUx!3+>t;x%HU6ee~_MGa9e%D)V9! zd<_cZA8(Y$ik*~^vMBK_pV@52ROOGoS@je)o^XtGa}nO+$W6`3p2REZic2HILEUzFj&qGI$x-73$gA!p0l#7U|qT6wAUr_dvN1X2(ba zi|gR!5EKkU$%r~EzD7;ZVC@1j-3}uo_i3gfP&Gi;5O0If^eS)fwtjk!^YHVlu8LPv zSdu`A|sRP9v z=vsaO-nfDS^waDNc1W%ug(KMx{tJA9{-mELs(1vDAE;^i?w;+^fQK(+29VbhgNf<| zwIBt!CJk_0sXYf*8`PkOMAodK!`(n%KpHSyz!lVs>SB>JIqV~rC=4JCgibCPEi(!n z&D7=|{JPbk5ZEZx@gbXcm_2L_4hyTrX0~8b`y$Fv-x3<3l8A2}f+w*_V$t=!m_xaC z^dM5MM@PF?TnPWI$8lNZcf$SO)60d0Pf{Wjfj$F<;em7+W?IlS?8PeYRyX0xuycWNlkn{n<^ImmWnf7Vp2!To|u`L$)tY9&5i5W ztC-ca9K2GN4>k&Oa^nBqy?d9<J)uMi#JnRP*89VudLyiCqBt*RHHM{@%^I>zkmG- zrpVAfzsk+PZ@~BRA3tt(ehaX51`OmU9jG_=so_bZRm;da2+Zo!*oqY^yhe>ifXg*` zpPZhaF8OQ_EwPSa$&$h73*)^37LP1s55_X{NE~@sR=!J7aUmcuaCle*1D*WkHNiC1?FFtKJO`o_GxeXLqLM7Niwk zpiEw>OkjEl3IxCBrQq=@k%vv5xY^6QeB{IHela=OK4o<8BMVMaCO}aR0`f9CORY?E zEs1D5)E&f?Wvf%WUuC6wdwV-k1i}$)(L~nxczxa{P*lid=4gznZZxdl~3rLcESmkvnQA z#QNa1?w8R~XBoYEAXr4;L|R^$k!cq=w#vAC+!nQQ-y1yLx$nvZ--HBS6Q$m_(b1;# zJ?jncY~ut8cM*rlr9C>=H-b-A^On5j2xe+1;R`^m z(~lx90$}I2`ES+%{>YBcx)c$?j`Khprt1&X%nnmvfG+}yiWE?m+2;$bEP{4=4a^3* zQAwYhomeNaFN+oVv$(YM7{Ay0TMfBz+`yXg<>*1{kdAF^Z7*WCN8mdG6U$bE72tE3}3CCG^z-r}_E#n4q{39cm6R-Ad2|lwEv8me{5+W21=xA$?8NmWY`ehgV;Y{+5 zyrj)CX}YnB!_VQ+p_81S}xdsRNQ)lyr%4#t7M)ny&Dw6)Fr zOz*2|<$vB<^Sy(9%IawLHD$%BuEQKob5s70EOLD^R^t;mdAJ#zgYjm>rkRQUy}u`T z{HIQDz?J7L25WO1+m=n=yO-nif%cO2jqTBE@uL%I>*CoE+0{$}gVr3K^Qdw9Ow)Q{ zr@>>4PG&TVAJH zl4jk+q>5a5zoM0u6_Tvs%v_MMpm0RHsyFB8z?GKxI^70+NlBaLQr>T>s+Hj|GI~5 z@#@6?yE;aBX8PV`f|__-Yd@Er$bV#GM;qtz$p)AAxWs)mlcrkh;;*i@gp~v_doBIl z9EtOVt3BQdIrR30-Q1GCo5lkVVWgAMRJrs|x(In@zNh%|SBoa9@In)?e*Jn%-N8W| zh4H@1EnW(CjuO4$!g^P;g>Ph8Z?U$J2yhQtmNPSPS?BT5Z+r1Hw6&i+UwnoP>9)4P zOLFV96bFgwC3>uW%8;d zN2D^(eIpLuvkW5vCb{!>7w>Xc-(E3XU`y2X>%pI%EN|*8)Z!6x$$yk@A=^GR#;zQp zA+sS-tDM}~v_Tv40M(Qd#YKlE?6pr$>HK*a>8Ed>|5T^%dCt#CKlfDQYcVlj_DGRC zI(T^T==sXG$jSzPc+4nUetw7V^ecz`Aq88Uyk`Q-QW*{J-u;=1-A?#tXz}C;%bRdt z|27sem{}gI?89fLAKEl=w7{{dtC?==r&29D+v~du$gvi!ze?V--y+AkmUDV{izNE` zUZ3c`*pzoEcBRut@5-*~V3v(Lhzti6uxwRgi_Slt?_;3Rjo*4vC-DO7yFD9y;?pKtAomXvW~@itQ5U zKk>~g$orJ`LZ5A!?!VaNZg{mRZz9j$cA$r*^zXdLMr)M|5<3t#I3x9ww}ImU-Ek3t zllP3fng!`X;qu=7{d?BTZxBY%#RTF529z9rCj0`@2sC3^OUB+Si_h~MtT4NuUr@D% zO5j>5FLR=Pz^^^29DO4}H9rT!cyI2o>Ho^$y^GzV(2-u_nMoB6&g{z%A6CA(6EI#T z0mKk<$;>7EllK-+ljnEz0;Kz-pr_&Pua9n++8Vv-km%7)5(k&J$!7ykNURWmCb%Y&k$c?eO8c0u5EHQ~KhK(*m&@(9e9;HaV4K?)Cj3V96rm+iRbRr_^kBB)MdZxyuHZzzI^n-Q4%-1& zj#Ju{or6Q#FEtzsSrwH7tnBQ(dnH=EKQNkDzZ{$9=jWIBTKhR(H*-bb+k4Bk)D{jr z9kbl?^iXC=)BR&>bc1t)B7$$Xw5a$fm~os7Pfq-D*daSYNRwjTe`H0^cl zX1%B#YfHu|C%2P-N6>@<=ZP z=2O$-zo$-c`dQ@K>6Ep7`t-?ts?`!bW7TNN!b+txXfG9NVhlEfZuEYjQL;&2%K8)BLiI*b;c$$nw5Q^SV@Ni!JNl8hyxXmjiNeg5B z#7!sr4`5W>Q6OtIuwIgFR^kfLh6J`e&HUlidja^yGO2&ev%iAhg19^S&yg)NE_Y!a zbp=@c7K1$~@AS;U*?Q_Sf%_i_(-D=On1>=T%F=FQWA9{onqI#)+9y3)(T*Wdyhvc!K) z%UsBEPLqVB&~V+Bv5G#|HWwyARsN;xjcFYVs`9w3thfbaX`|T%H3PQ)^)qUd=J+z< zlO@a9RA-RHIWDG_yko&Z-YzSm8ld10f;7E)#fs2d0|c2WCTA@K#1&*SLhHL&Fgxr7XKRLzI2+{{42lDayDrSs#^?z6vtzsO*K+`Ta} z^K-Q5vmKg%Q)kW)7Kf9ECjg$c;QwhT8HYv@gwChUg$NT@FD%R}G)LUOFKlRNC=#tN z3+}DV9d>CE7>pqi4TUTh49ESUen>(iMBWxSu7Nm?)(53XhhV9NGfY}2{LF5kO)?4! zbvS*e&5i+e0i4l|9-j}Eo4AS~yqjeC4{5_70JsR~b|iB%LK#Q`KK$l&NDZ6-)D0Co~Uuo6-imk20HndM&NUFPu|7ZvqkeneoNAF;k&9ne-*Q6U^OR-zAEk98JpLJ$lB(q&6%vX8~NU0!%Q5WZu+2lA7ZHe~-G`U}D>>Y@9T z8_kAT1%V*o`vPXrv=1Cug~y$H^0OK|)Ym`p+74!}X8nDiOR%aQYW2hs*aq7YY^FK| z_uZ>}vZ8gJ9&T=zA|oTaPB<3uL#3(h=qRz+-kxYPE^>kn^%qK@CMeujK_oxp1?2M- z=)dBU5<7%?A~61LkqIdzW~y`{rq4Y)X1ycXlv2lyBn-oD+X zF!vxXP9R)(dpM3rHJ(NGp$gp)u5}cJkl5%TPblv*)h{1qX37F9C7A^w@;MOf?yp}{ zin2jdAI3&AGQ{}30Ozl|ww4}1twoLv4TfBqPoF;R@80_Tkg|N5(}z4Hq*ke2Mn<)$ zz zv_&_=_ zgi07r>`3cZJlE?D!s{hI;K(NHryP$SJ=$IB;p`4NY46ROH$QagqEhr2>yj$>Dm#)NNp1u_9;m!}95SLNX6c*4%d_ z<)jDdVm-hIY|_nLtJE(1{n$MLP4*G5Oq54BpY%vaQAzI9ktfE+P`j|9+}K?F&3SC# z;D0>O+$K0qhXY6+k(xvDr%~S&oM7+h7z$c@72;wwP_;7}aj2c?b~Lmlv9&v3fsQTs zbc?6wTnk51N6X|vgC4BZZRqIWhrx0i%T>xRHTBp{Rr9NYTAMd-hU7}Ic?5%uI=DvH z@m?K5iQ`{id(1a)MsR>g7Vu87VFOAyQgy&YxEG4_G4A}G#>UTa`)uv)128LSZ)uGx z2BBtHFdKwkFTx;#UaEDa?g?@=C1*B3m!83E_>Lh9hQygH9h6^Yrqp?eM^?3!$=EfD zvBN0CObJ!iV+^<%R=+-Q@ZgpT$QRu3HnYx*EbKzbB7-T+pMaC~Zo9_by(@Trwx72a zbb{H*hYbh;U%I-wj^a6aWdk>D%Ln=Ih$#~t9Hy}81!UP|A~kr7r+vgx7$;SJt|v(N z#(KjTn?Ib}{P2t$NG1Ci>80#(2jFh!jzk@nDV%*CG`Vkl51ENd+_*9JSr1KhjgShH z?1q;P`#~F%ogs~L`c!^&qx~?9Uj>Bf5|U02o*NT{fS5j}qjsmW9(p5i+sndx z4?i#Otz#okuRu@&hL;V`MGw+&AvK?1$Zcp|{GR{Pf)bW&{~|aNlr*PP;)Ay`K^kO& z>Orab_r(R(NitHK(uViI@zi~?5y@jdN`DjiXuzbf4rV0&?Pf z>Y;CgfTP*4sw6Q~BksV&)D)Tih^6xj30aDuk_5+>r@!DT_7dFvTHKObNOSvFDY~sq zM^<8>Q$QiDiL)SiU)5WTQb!|H>oql;{`slr8dO`>{L7d7BM-;bJ%28?>$d!c2Ah|7 zMI`S7?OjO$?ag=kV0sXM?l@}pcjyh6@ZTsH?mVA>LLwwtKU$6t`_Mx!#>K_ypdwsC z)!*8*X{$q{x*#?w-BCVkd^;v47(el&q=bY%S&{aYCT8J7{_#kkFE6eirBHHUyqTrI z-{1cnRyDuGE3g)=@-^%@te*gO8v?D|vF^aZH3=r-8~}<*um9&Wy@vUc<;yM3j?#MX z+7yGmPD8ZeXj5e{oAnE&ey;ln(UOl?Z$eXuW-`8Xr^4F4t437U@3I-`kuNpiCdrv5 zQ^03`N;A<)?9I3gil!GGk!Rk0kk)n>qLGo6twJBT8`;s&o}3TL0(Qeq&gentb_lo$ z0?vswJbKETl6(E}h#^27GYOvhFQ;_tvFU;69ibGsD4z0Z0_H+y+17$tSy{0Yj~${V z_scanJB|o)aotENIdTSt6ETwEji-6{ef3D|!yv3Ih4K8iyG5DvPu<*}<~q(nox{S>G)OtE%n4x>ynv7Ey|-vRMKfJ_b%U8@f4jLYOd=g zu^tvGF$K<@<_9tHZ>V(zmHfI{4A8i{z?kYyGfG6o*be}z8s`kVXPKewP7M+hG=yqJdDXA616 zQL4MQH}OO1+3YUYS?FNCVD?yn1uYlSvg3K&w!Vn=khB*D09|CypAY^}#(29(Wj^xO zCU$Rj_V(!!me?}iMO3gW`fl=4)JVDU53g_ZxcQ_aT|n*#6OvmPE|~E18c_HVngNu6 z4XPwv|E;q+0^45fNP2naNj#z@e+OqjCV(fc+m>muFfkEjtt)K4A#xEWx+|QipKE2$ zY9NL154;Ar- zSJ>0hvb(Y$dcUm*r2$W#C<3I&1|4y`%XOuq+XFc;VuWlpM6<9_6@G~M=u35jA~;Q& zOtTIsI+*U^yfCpgao|;LWW%knY?0_^CBEM6o?exCo~M}@?g^pH?pb@C>4|Z#Rm-~d z>-&JNkg^}ZiX8n?bi}~?j^~FQWoumnh1t%Pfg2bZ7|6?#W}3{5GkrfjRnxlcK5EK7 zz+sqNu7s@ep8XSCe-B<|?00nlida}U`t93QoQ*!{e+JPLr1XQ%srOKQ{ra^@f+oF+ z_cSZa8JimJCPrVoMm;>ERs8n;b{GpVVShV6Uey;ZEcNZOJ^+iLVzLpxmA2L5X0vwv zkf72TCMdx%gtZbM6M5XzW&3A*Gz|rfsXydCF)L$aMEk}wtjm5or6_b^lL!!1oH-T4h4bt2gs zq0p-(X9Yj*9WYu{5sKGt{y2_)i6KY;;G6X-AbW0T+$PXlys)k~FBK(jqRAWOi6R4| z2C^$Dz&YZN`Ca6Fe^0g`^v3~e0V(xjGlMhf@_BVQ!GJ>mL%3h(!NOfJ6s zg7~$VorZqCTMKn4p12!D>L`iBD5S6nHXTCMyT`F+9ksI|_WauB4D2$;0<5dk^FE*~ zU7lcPqSBT0OQ%5=j@;9K8Zo-OKlzVSc0F%T@TYRY+)@vX`QHUid3mPEHp=naoqo>* zJvG}v`RQ^$zS9+_CZtklYBC|4zt4o*y-YrDICEUqV8;&91K?ILDl3;7);WN5E&0qQ zSTR}Gw)@G)tiIk}T8gyO&<;H)a`|n|)X`R;FluaUe66A1s}sf;?by$fqVy%doQ!SY zy)-HFaz_VW>MtNC0%4*ls6l92eXF4fUJDlc_uB$MgwQeJd-3s^p@D%Q ztWY94Gk`Z7I-J+9Uq^*53rv8RrIz`kJmK29b!+gID_Yo@n)PNVVyE#N`;M9U!qb1| zel4>>aARQQi+2L_nt{x!drUJgKcAb12_;J`EgJxkC>nKq-d=D3`wMR)!f#Y?Y8QbJ z5s}KziHy=tb~bJe$6p+a{rSAAO5z7lQ z@5pw7uD`^Wc#Wj&7Vom;-MDchc+`h*5IF)m_j-Ek@Tzs88M_lS3pZca)zPVHksrJN z7f98EUB8?h4#=9=j+b{nOf%PV?`Jlh9u1n9u&-2C7@0g0CQzM!cGHWvEkU|sI;_-} zcor$&U;jfA)6hYJy!T_YKu!v+0Rzj0%0U||{k~7D#@`VfgNGcJLXdV>s zXw~gXHnlIg*kb+pn9A1AhpI7N{h z-d_3)A~=QCUx=r`@Vr_CVFw- z3Gz(3=BQ1Fgb2-#ze*{cdEpZG7m=O_rHNDr=M7nt3#DiT@WMWvcWnS=kTJ4gUpYdG zyh9^}6a>AKHiB&Capa34r-08ZCpahi>bOE@|xMii(F z*S4;?iDg$Vs?KpcDx^d*6^zk^kohfQ{sbK#feF~OtkAOKMGZnH1o=TP(v_F$J=RvK z(z+9flMe45EJg3dP+-XhnjK+~7Jj@nM3N8PG>|QrZsJ18p952`!Odud2pOKRLU_M1 zs=iC}Q6Ei2Bo!MYkN**v?lV32UIz#iA3=m-cIqgt|8uSG2BM#m>oj>H! zgS-z+Pu~#AyUAs;3~Vd}0WB>pMayQrgv*B2o}P^Ikw=nl#k0@)hMMGNPd;)@58hBN zD$TMWL#!_ktt>Oum}#YuBvk-tSR?wx-5OkX>@_5e$aQn!ryiMfhbGWupq zj(0u1XxFf!hE3?wDVs*3pFg<$_bj1 zh^{DY|6?&?bxpjC6nyj>K7L$9j23YC;etb6p(x4yTqH3PnKU4g1FZwNPl2Yf(>`r5 z=my&LYH~BYt`2%)`5+2!dFKvpEbG91lR(qB8)22yLO3A^y{e9FS(!6J2nxFko%RD< zq6fSh5EjNpN=?MPvU^)U*q%HYffg_c%`6c%b))e_lL}!!>Y!EXsI&;3I8wh4g(6T0 zEgv5h{Ifg5Yy&wrDEc+gQiGO>?k`89=#K^GcKBaF!rF-bO>p>pdVZcCIYVA4O0o=C zgofz#ZdgVK0jtldGr*f6V^c|y0tBC=iTzo)oa~0`7QE@J1O>Ek{b9gpMpEXb(PlY0 zI|HBFjKZA`TvH_sv?kxjLJEsPE;8tGW~@sPRH+_L{y4;58e);QNJw7QeJDAR*_Qxk z!_rk@yyUv+rpdc;s|i+(!yG&D0g;0NDkCyE2Uar8z~MklZt83ALTrLwj@JhS2gIUg z*#j>TMb1?5I&-Wg^(9)ZqwA|N3s?Qu-a}VOy3G#p zU!B9W6jJGXOp)v?n0xQ_>qi1Nr5`1}x?IZOY^RZ?+Y!I__mpclWhVM#rpvXPt!j=9f)MR1*6h>q1lm3B_JKXis)*bLEoxk-yG5 zvx+@il!o%P%=hbi!I?Asapeusdu8`0*x%-wMftFfZozm11|u$ss4MTJA3E{5T$SLr zP{8#dIwAi7J+6bw7s6x$=_5`I3s&G=(Ms5@E`96RaKc{PIWi3hNto9N>q@yLz%s=l zhfI0XB^BK@I)QnYlou&et9SV@W!3or%@jC@yrka`>!sP*6Wx%lz+jZ|MI<%vu zMnzCYk?4pz#TBTfitlf3)P{QaU`()Z0Hi3JK<}Uj{CFJ4m+;yk%`opFh=q?V1aL)~ z0z+Bove0olNQ8+L5M&S?5%Y3TP!gKt!H$BBzo$pz(ydW7`=OD5FfM(@8r$weiO3(4 zN%eE*w&D3FTYX1G0%DH%vT7pg6KD_BeIFpFTJQnOfbd31AGF+8j-Lu`tQ-*e=+VA@ z7`;GU)0Onyi;bqON#Ugt!Q>B603{KrND6N-+s>5V04XF+19=#i2 zB!=zZKUYM+0+x-)y&$pLjbuq?l)(1;Lz~*AhL?j0ULk+5#SP%uC4YZ9SVJ_eOo1V? zw&+DeG4Byyd;QQV$u&9u%aA0QZ-S2&BM0B+ieQ0`1a5&bcs z13h|b;m^+qf+((_h&uK`(K-f?vu71h9mYRGyuFPAY^~!V*quKnlXS^o1m9!OW<7uB zb5JE~AzRB^;s^7IHH!1svOh8}UCNoSl?FO&vpwHi7RLh1Hq|#(RXzSs)sxuutn>G| zJ4xEjeAj_+$@IR8tmRfy>7dR}%2e0q&mz9<|ErTOUc88@jB78Cl>8AlJCiKjdt+QI zhWQO%tR^Vn2t|Y@jQ-$V&7}Cw6UJ{Y08KgN=0=PI6GQw?OG;v%dwk9&PgbDOXh2m z<2?42hE2}Nra%)QN@1b+Nb5RTSt;Yl*Cm7X4}=+wwF830LwA&)W+Wug*+Gt)A{AAc z)9yxbG#S>a-4(HPM356a_({g$rx0i*O<2{$>6n>C0jiR91!=;p)qE#;%={*JUP^|4 zu+g~oR_@464`nbvCo`=~O>aoQxN%-tF4nMUY+?9D=ID{Vx4xZWX=XUb>Kqizd{a!8 zGnbKFw4;r8!;CPW)W(?G&r%D3&JZ`&c1hkkPYi*wt?`q5=(9ft7<|YWj8bGFIMF+EFNQ)vBY~55#F(- zXFC7WeGT?i#TyA>x8WrSUuz+OUW@4`yd&m(aj(_NM(Lp>O&&Dt*sTEq{_ zq8@yX@KB}1tqLDyNe}sNP|Uot^~os@+8w}WTB3Iz;uWnwbi2i@Mp!iv!awn>LkiXw zV*c)_;W;xuF6%Ult)kln$}>t2)eP1uFokQ1sIqo^;Y2@$DUq1_z*gbMiK?*Z3n+Gh zyKFC8TlcPkONseI*MT>J8~h|2D$dVaDyW`AMK?*Sfj-D=tUXt2g^-Z6wvNuqyT|?_ z^qY!EZGQ6b;X~=&d+@q9A%=q$1s;8mTm9zFi*V!MShY&>Wrj5M1*INDl`AC7(yrOd ze8snx#2X=RcinCd3Z5``+`(j5=&beX&B5JzdIHe*@8O^JSFvfU5I!LD$>!u*CLogZ+T)nxMVB@)Z9@UqWAB+fEHcOS?%^C z1Jc1Azr5em2x$22?JN{8npW5b zwvVf?&R*FD%>5-q)&xv*_*mj`fH(^0%YR7+N+YF%s>J~6o|Ikp@7@07j%5X@OZ>q7 zG2_2)+|IuMllkIe{{GmHA3sjguETr02XXDd5kuoqaKv{F}q%zR~J9xMKft25^pyS724k>5>|F5GO(ZVOG z-Ut?-LBFEe)8}uoY17kRc5={HHsn3ZKj+FVtg!6O`ta)GpDZm(pJ=@3nJhpAsX?hk zI3<*uaz(A{iRSIMxU55@0lGPPvf|Q+vcUvHIxz$A|W23qOXUej(vAvEG zvBP466d;>79A~bdf4_kOsz9*uJv7}wCg0(H#UA~r3-?`^0?3@E(6R(1+*1cMv&S)Y z0!=m#&@s(sS^v%wzl8jm3Vwl)V!zS0pof98+)Fv-ek2^ z_=LgYb6Y8!Y9#&0y37(>pU)lgnj{)&XtZ0v0?>#_h|C7Q5Qe z9zgv!fIh|n%3VtRz|`tm0sinK&dy7OduyUfhEvD1*9Xsa@tmJDeJ{#a$8^Rlkd8qXxbG#&FV0QpTDTN4g&>2P-V#LC59 zeXL=cVBR>oW&J<}`i5RKwY54=VXa53DhwC?&YdJx`w|i7lr%K94-p3Ly~81wOciOf z%(2mM1ku(?rg>h1c1$wx`-bC&MHUjkX75iF3do>!BqUe}WF2KgDE<3auU<_{As=F; z5cZ?pyQ74@`D@elVASj7aDDqU+%S-j7hw;o9bJr3O?Rf%%& zkM#`n=7?GecDv#~weq^o&1gkN9m#)`er2;0qo+uL=Vm5z z^VxUS&D-tI9lGE@Y1^lGGxSD8LRntw>!9R#Z)uKxZH=OR)APLbL@NCgK1fXtXB4;> z-)l2@BF8j)k*wky%2(kW{HaV}xZNVjzTen5XNGS{=Xpp-%_SE(e|-HIo&Q#C?SO0l z^z9HZKe%%1D$#znW(g9lH~QoyJMTT{ATbdFcOs59S)ItruJ>wsH26a~T}Bb>u^Wop zDqXtO+u8V;o+KUqS0V&Yu#g{3Z`k5+?cDcO5ZYe*F;?NHdpQ58{goAq0_IW=jxO1k z+el@@$#b4aym>hOkGQ=cfQZ|1<&mL0&@Vr+0gn%MZ1DVIm`=Pyl;q;VA&WBio&x-WwWDdu{b+K8zPTDi8IVgX(+e$~;piKoibCAf7P zZkuN>!`HVLmuflM$Nu{kZ&F1ghy}nAts5l0)Zj1&&wld#{jy=27mKxmI7UZ;z9^r) zdXvY{$hJ9C05{vlyhwV9#*0+T@12Q;j&w3-qJK z>g(7TI}jz}ioAO?^d8SfbM>Sq8}yO%2YDp4i4eYi%?1sP;OWjli236~A`~p&Kd{pw ztV`A|;T@yjCQ>iW?f@8#*j(}%O7g+#(~x2r-d7U@cKDV%e}91^58A~*6dsG8fE2Bj zY>E*Iuf-ouFS~`uwJu@}lqEa&W%F;6jI1irVkEU|;_+tolzl`UZ=wsBicjU-DUE&p zX)nn08WemG7_j>JNdY^L(Mw7a^$z>@54|G;42AP^TGn|-{yRd<^<>Bo#^30XE795_ z?Q5WlS$%!SWIaLHL9C#dltjI;3~d-_3rv>qf%bLiER<4$jbHzH7@fhL@Cq=cZnQff z5_OSaSf^bS&kS-91rz%0Y5=bId(4>>5j-49AftWX(d`o5?!UQn(BeraKbHehPs;iW z^-d-F(_nN`6b05P0%wt;5R#UlJVE=mjDVa$%LF08UPfVDvKBaG&){G%8020AF{}mK zPDI)0V-uhp#hkYL&9NE$#s6fnU|A-ITUk(2KS%I$#n7G)ruBemId0v)j!YYYQ^P<< z8?ah5%^?sJB|f-5Qh0j&k{#n7;R-8xL#GNPf^MtVTKih*v1 zc&N@w9+%59$YSf%lK^$8VkkTl+GVlW2a3YzXI<5s@5Dn0)+U z3{=kP0b??h$*1fgX`XdpDZr$%(SV?!>V^guB#8IkzQDbR!3qJX3>p^hx0CXT{2|1^O|(f#Iv=N}?V#MAj$ z?kfQ8tRH5Y-fgah54e|-7JPoEz1KqD!Mn_uB4Q6$D3Z*H*+rh#5<15G;gQ{KX z{GV|>%+pVd3L7gyys8Y5rR~w9!1K0t18OHyb@CP9gb0R*D*m0-F_gRQ<-W>5uKe)B ziB_2Is7ieg+D~U|c?i9W^DQ zhr|0pH@Hof4_4RQLuOz=>N$RAb9|sG=b2lc)%UR_W_~L4>#c%-uX4U zR~xNnaR2#a?UsNv%Jy~!=171psMg5w$Hb#an*q!#8L^BiFg103q06TXt2zuYAW&+y zX6((ylYm}@nIJ`-`ylfq%HxA?1!E;gZ#F_41G3~YWGu*VnHxVt!+?f@A3XpeuVkG& ziO$)+8_XZOGA&Dd!PQ*qGa=oS=u!Sg{`yrBH zo*9Z>mlsNH>YZ9A#?}(>73A>0&pLPo1EaL0)EyseT0qQ)0nRDI3s`~ukZZowU zVVLELB{eQPA}k3)mKJS=q#vlW$<4_Erpz)OJGjGW0{ zKn$QTwLfsovhMx+6(HI-ATC$gT7rIP5!jl%7hMPGZMSHc4BsC>oH*kYFc{1|3l) zr?9j;z~JNH?hW(D9y-$jgM$!0w@ZnrkFk@=1kuZ>El$mv{QknK7#s7M+lq<~3Ne|Q zC~k}rW8f7dW*8+7s(B`Q-*JCF_eOJ8U=q#q#-!M8UN-bn;(FHf6sD#O8zdzqcY|B^ zAK3z)8NIuXjZOUb0z4WGj1A+tG8{`{WoD-JGpwQ&k=hk13xFOyU=<+BA75TB$6LGv zD0iz=0}BES1Dzfy0E#NRqtro#Cr~U@Ol)iws8X~MZ{L@Ho>qL>?*{X=Rhi9DOhB_! z_O=i>AW1Qpp16qcMU?1-EeBm0mvrQ%Hj1<+J%h-Y;P!b&j?sajsD-23UxeBmWe}JV zB2bj4vl@@508Sj-BXi;(Y51Pr6Cr*~|DjUvl9#*s0I4z;B1AE|?mW5B`c`H?$11`WF@{W9g9}bQe7AcS@B>Y2BGSe(GT0eG`*@V?LVHkymYfqrFU zEj&7K?eF8VQ$4iE)F}r1cdb!*AiBF7?2?Gq%rqYuoNo+@z%b+Q?qCTXGTHkgZP$3Q z!M>-npBLi8Lu3zFh+f<~H+{;oV%gqsVJ)aP$^7J@5dwkfd?q@LY-?3gwWBc7gJeP5 zL>osPR`=oXDyRaSp|I|Z7uc*H*zOMJjvt7ju-GTx0G^Lj_YmZqslH4RYZfT}<^KL2 z*+lB~k{;5jR09qrPB6qlEH=<~C5b>(C59M&bXOd>fZ;&m$FxHFfWs+4=eP0Xj1Z~` z<*P`jUExfuAXrsYmn6RuJpz&;sYZzdAi9A7h(;2?sDJ3X2ZV@JxUeb*2}V42Aax_x zhDbnEx8!U=?O7Ba6vRl34iO~c5%Nf@plwovf(nKK=bS#3@Bm?hOIeAFpauu2;SKL9 z@xZuF11vX1JYh=QRvxQ%b9KEa9!f^{AXjo9X{-yIZw(prPq-Sa{%TqffB@u|hpTDS zhj-Ir%JZ2^CH3O;wP1*agI~Z;?GiCdsjj9Yes}$ z>|?CaP+{H^Of(s*Am+Zs_=Liu0o3PU9>^aIf*%BEBhSx<9&?EX*-RlvZwApADR4pY zlVZl}z6lt$?ty_+g;sB3%s_m^P!{881sIC?L(wA5(F`rjC$k<)yv~+Y!kbELG=NZ&`7hlFA-2t%A4|63)<}{Tk(9n@=F^}@1R^g36@@<TsV=Va5}+cM{ekkU1wnRFn1SBUg2Gb(M7M>k{M~a*14@SvL{`QGIKmWF*Il z$iz_J%f?)usk}%>hIOQ^pE9MJF(;cYrP8d5q)~qppW1`_pBx|-9u`(s0*|y1M7nW{ z@va}We2>2+Mt@Lll9NiL*Jx_1L4<&IZkQhATIF`VZ^?7b#r@$;y(9y zvlzKNGQ9w3m5czOUwhuo{T1k>EVU&>lyV$A|-6rHpU{ev)iY1Ah7#)ytF?+v^&j_8N#j@=r5EdXPQ z0EY^AGz9WXZOknQIQY3d=O^EiX8mYn+5m&|MQvyeKGpWl{T6t@F7Q7+0?2ffJMgEm z$vfbsfZkf%BykCmVZ@m$Z4XXNYj`ah`7%^{S;f`n2dVq;>R|qjoeXc`SNr+o=wvz1 z8SmFGXE)7n!GO{!O+=p4s@B(3oiS97PAL>*IO#X^A~gSu?KC!jHS#{-?ydDsB_07a zFCwLIhD0TvqAw4D_wh2krA_@^O-dM*&AP=&w{>#09k3f_Rd*j1u!CA42~BC~!fx`w z7^3s>`fp__wnuIBjTV1~B_t3RF*TBZ^0N>OC_`KvM}ajQ#-qbU0EIoRu`hD4j5r)H zOVyTn2aiu_eXH{4h`e$2=9WxL9wh3Vp%2SVH$MF5!%=q9;HIPoewlINtqRt}tAWfA zkU2ZJEz+AQC(e+Wb_uip3?+2)?+f3UpU1Ts8rn2xZv3XAVryMteVs32K9|UG1}{7b zL{H07j5bU=08C#k+$F{}zG~E;z_f2Af_gVnFUGGi3TFm?8=Pu?!{?orzfa}5FE>fJ z2JtTjBRETsZIX9={xhjzV^mOC(0Zr2>5H$r)Bvq2u8w>e;{(ohP-=;W`hOgsPN12D z4%oTJcWAB*YSjQ@;o$>#hSpCM?kYF34#+OrQOr&Mv!mcxdt=drfBAH#e9#4iU?Wi5gf|B(Ecu6?an^UuBrT zUL01!rSJ%%FgM9U(+t#|JbZ z<|Z$;yx;mi)O^yG9NjMc$lIa6lIqYTu+$cepvpXw^SzyuT#G};MdTu2> z5dKjl79I8*w#aiY4p2N{86I>d!mM$rX&*92Pu^wUZ45A|^B zaP7aD7MVA1cDp}+uJNyO`oGLudK%Q*gq~rgh@B(h4WgtVc>*R>4IT6k{0*PvW~5IN zPw+Hhg}wgvm^}?dVNB=O^3 zM=B0dGvi--QBZ7ygC-HAQAEa$I^7jTz$G7nSqYeEp&o8y(=k4YVbJy`r`O{N{V-v0 zLInx1`4k2Y$?y^G51F#Y_LJ_Oo{L}th&%S~R1p5~Fd-ULlu)`T{V;YovTAfsvMF(U zEWWUyM!-X$c92Q-;3kj>L{QjasIK~7&XuB(#ouF=SH^SV2vI_N<{gwEjr(su!u`R# z_nzmu-sAVnakTx#HoUaiuOS^2R;n!Vr+_|sdQ>ZSwU$Uh;hO<|2yvV04AadLL0Xi+ z9pV2g@5_U^eA~V+(xzmI7A>?`BeMP!q7b5N5uucQiG)Iyv>`3Z5+Y<5${P7bdq{Rd zXt4{~L-Kr1-S;#1AJ6mGJM+#v^P2h1nETFmxvuj%&*S)PN2bXctPcY8vITrS#(^>% zXzf?<5=4gQn_H@hNC*fV$1;Ylgc|QW0F}&_{(htCBLXb=(^qKTv~w&qVGoK2r~n-o z!RSa;MCM8WDSe4L(Peb|f_s`2U@9RtNo*zY-c{c zPo69NzzN>w*zs?I4iaOpqKykT9#2qqv_eyDE!!Tq9}4hcIsp-r_5Qt%EO>micB{~w zE+ES!XMxa?U1v`unr*PfD{G6Ygkt8B{W9;r?5i1EQi(J$bwziQOr+l!IfrSy3 zuvo;O!A!X0(Jq}qiZK3m5k%&TZ#Ra4g1L;R#mbIry#qx46j5zkwpgJpLQiuIA6$X` z&AAqNiX<%%zKR4gKz{>6z{tZ&gYhRQT2lPkLe*bC_`S5o`?+e-`Lgq70Sx+stuMeK zVeSutlw`9aFc!bx6~J)Y=ldqVnI|mxwhKX(q%2F8x>wSXFs&j5$l zCB-PbtQkOQ7|lR2;W@lW?h$;KWVI3eeTNSmfuya0@Aw*G6u@=3RDfDt3T>=0WU|M^ zuaKk>C~3-^-NDvTrMpd-$52C9?BNfofxwPDT*OTQQSYv!Nw97sH)wW`1D9%rSv}H! zh*TGn+EQ$(XG1>S3zTXvWaZdZ{?ke6&*%zzSRKzgIzFV^U|4pCA4}BfPtW%I`Wb#l z2J}~c(t1v`gy05=2>ssKN#t9K!6mq~$cAQOY%DgxUzUk=`VuIkg3)a>mfThS_jIwZ zzk}M2Sc-|Q1ox6;7lF>XjD`FX$u=h(uS3~F6y*fX&OBan6AujuqJ`9)i1LAcr0m#$ zhaV3HJq1?cFXQvro~wie1{0Mo?h&#F7%&!vVd>I`AWh-Z;vyRm>*j*%OHAmXsd2;_hYeit;^ub(IKUC`gowj3s8k^)O2 zr=aLZ#QNdP{)5WQo-HgKbn!+vkfcRQw&lr(7tPCEEDN+C(udK@c~KiK+sg)Xm)k*k z)}*;#O-af8J&F;R60~yi6&s}-w*{+`QyfH@NkJQ6!5+0uXGuB=!6=9V68uE(W+MWo z;=xt{?MjS;;J{Ddpz;`aPRwZ_e+In7kUm2e40v&}@^O{?37-xBLs5(rFE20QWN!A$ zp#3KvYP6oH?g+n0l9_N%PNJ>+IXX&DeQIg>2Hv|A)eY&5z~Mod()n#!BD{n=ga?P) zi%xamVlHCRU=Lu2(*%JsiU4fsD3b4i(H1178)S|d?16zbpF4 zogA9N0nllCtgq>CN!#ySSehteo)Vi73&n{Jm(zIp|H#*jCd9PFnH#||Obn=$d8A|+ z%*o*)Q#di*vwfgUH!04x7`R<@qgRVk*%eKI+ zNHlyimRiTs+qfz#Oyt^V$+G8Lx+ex6eqcWM^+LCVdgU)Rk`H;@g5(9ZYhSofJalfr z_~VHPZ?8cOz3D-0^_19B`P|&B4V=#{AUw1f=5|mJ6crT}NsAERr&nmID6770>99GX zm~jpJ>px8h6@$9%Wlaq$>h!wBilh5#eZ=!{Jx(GvX`Pt4|4zlnz$VFqWo2b$O!MHt zGA*ykpMwPl6~*TgA1;)`FYyG7;AH7GY)iF9Ta|Jfnw;;I-NNTi_$~O>E{U<+^F&HQVQRQQ67J1AUgILX{iOjhP%wLMHj}P>` zVTn06f~*Fr`k=)9m_#&|4h|#m!Y3pp^$YJX%r*~eb#-xJ_&Hyw%Wnm|^eVukQWPer z>6G5cdY9T*StZY&0}~t(hS*>;lzEgdcHwU8)+1?pFVHz?!(&8rsKjFg?2*`z2Rw9Z zUfh?0^65X4Fy}2ssse4Wg-nfKM$ML>FB4^Qk$-PuQqng(jtj~gF?FsK0RN0AR3|>R z7J&kIG1#4)ZBelmbvt^8#>Gap2f=H=V+1s8Irb`=#oNtJFrn!b%)R7I7kDJ;SXOfH zI1#pBCISPDSlE@6F}!i~0#^&S793gGLJ#MD)K&$CZbF6HxR}fexk)e-$QDRCKB?`G z9cw+8F2NxHrcY1dB*JIwE?)X+UrN0m`V?VNsv;_R#o>W`O7Am{P`6>y880FbieBeL zf5XA4Sx_Y%(<;Xw*7Q~ig?K-kR@+l7;6(}0fbJ`@D_?pQi*ZNU8~?cA5TmLqZy#9) zkDN_%)koFVffQ~N_*@Az3K2B!L^XE9sq6<>Ngv;M{wtTIb15rMG6r z*h{c63;+DlERI{A|nqE_s(ylg(YlxbJ$QC1$S%F7jfKH8RL zC+{_-CvV^)1b|+BLyGqPbK5o{Fbc=j^Ex+Tm<9Jk*@&}py#Am~k!FM^aOEL?*SAGAA{mfu7o$3mmgP;3Q{pFDX10ptx> zV0IX^K8#1=t%oDd9B|n@j)<3n3%@WAZWLEwSdYMio$w~65yhsg*k(@;OK)um=)-sN zm-I2~$zNFZ=0K3|c*&V;e0L?T$Z^_Y2-HFasGnp40>ZxXUkr8?5J3^@b>cE5f)S(! z0&&4LLC3lgkNKtR*H@5}*2V+`C^>G(j24^#kPzWpFGV4RN9!btNRPs!fQwH+V}U;= zkt3gi=}VB4IvQqG}+-=*W1rmv|)$WDDgHTXfkmsWCFLFuAOlv?6EAYTJR9+1+1&l)E86?yac;#i}E5Z$L=F2IN`ln|9fO|^5aN8EEtcbvh>lklgMmh&V`~w8FmIOXncvx2b(m? z0I^SZ`%Rc|x|^DsqIZ6UvPy>O*V3B-{sL7t5}X*cQS}XS0UZkOW`LpAM%ZE1DgkID zVkwJb80;OG4m;{=YP3PIJo+kdN1lF_ZjyqA=9MF@QY;BvUOj}yn0XJ%k&I0u)?4g$ zq!d9fLM-$|gLhi6cO1M6c~-$+4A4t~DJG+B%(MMyv{aCA=-GW2LO=i2;vw$=kJL$I zr>P`ILTdlN(I3oXWDFIN)F4kp1+*7Poh97D6!OHQ1(tp!awJw(GbcA;#B$&|GQuMn z5ypfOk_77r`BI1#gF#?BiM9pjN9oI#^u(D7{^JQEHX-M36BXokSR(|wgVvM_u!VFK zP_A^PR*FEU72haI9i|1#3Mr+hEU+Lz&=_1;C3FOY}P5&3za|5Q9LK`2wA4qT|~PEx6WD)c*lqHO4*a3*x^KCY?=6Z}31kRZK(b752W*1-6k zj{&oPdKJZ(=XW{_vG<83OCSh2#E*QYuL6zEzH$!yRl=A-$-dxoA*QjAeQmJJqwF_8 z$?#ch_ij!QUb`aD^7k&abDW;Sqf!2-C`HPyQ9~EvfJ$h`c+e|{^hj8*W@9@kQ9SeQ z&5AbXJfk_&x9(|J6(6XL2h^{aco!HD6%z)hIL<4{yf`bw!NaxRlS7#m(gJTEE+X~> zgmH=l`k`71`pAnB7)uG)Oi;N_=V{OMy4kSj?bFknN57A4FW+$`j%GXX^WU)yS4Zms z@ud$i%fi9=u+SD;YX~v7(7a~zx9{Bf@yoB4N7mQmEe@dR1f!Zm8~jmgErIzJ4RamU z_w3?d`VEE#a{epqJu)(K@`CW3=#?o&76zhLB?u`BR*b{;#d1*e%;^2{g*tcc9L(F7 znB947HUrhA`olT7?c0?wNWdb0>VLBm4?0*G%vd{Oas9G5c*W@NA||{KMQRJ)tw|gI zt=xb6=8YoOG0SE82XTG-@3t8RkLt{9;nh&mxMBh+zak5FuqsRz?HbMpsB^1f-9bi+xZGQcz-8;KQlDB>(A;7@yJ9_G(uD5QCAXFs?=zK&J;r3{tN&0hAQBjd) zzw#JRm?$Wg=qb$IjQ=nd$X4Uo_2r8%9t>hHh>x#?eHxYbRh?9Vhod{&5~?n9#Dz6! zP_DGJrgi;B>J%=%r9z^5E9Y=zLf_fz}<4B zs1zesvJ;{n9d3unVbqcbwY6?MKpW_vjlm-;!Fu}#1Q4M|yHp%T5!kJkx9;GOw9nBCVNaZl{8J=7CuegSH;5|% zovPxjC+3(jW(z_^`G<+j1gLq3d5JnCE%aXNpwsz<{SE)K_Ll9)!ZxT51w=$r#NleA z6i^i^M+gn?38le=Y~5a5$U_m(@CgjJg*W5bXd?Dmdi9Shb+lhjsw*?K;sjAnrKRZ! zwx!$xBxemu*qNre>Q+4S9ptHjS4%3(nuDNF1@Rjf;^f>}65sv%@BaNlI_xcWrYi%~ zZ@qd*Rtx!I+(IJi|Ni`+{r)dC1=*;`Pv5D-|K4=s_xY2A`h}nW_kLH^ujb&MxeXy0 z0xXu}?hqy2+qZ9%V$+ql0#J`(MX7=WkaRBm9i2Y{J>wQ6vp~mjxwp5MUwwPfK;0|} zLBZLAiinIt`S#3yKW5iK`HP4wKlD5aPxA6A2W;88QlTOx@4|RxAE|5JwzL%CT|#*B z4ayHAZ47dKHbi-hm}IR0;>p6swsjW?wh!IGhcmj{RdU73toH#cNiT)C1-sKIJu~KZ z>C5Be{2H%2T#3if%mf2{(y^Z#!g^+iCamQfP~Y0$Y0_TC=#NB^7Oe~aMmkPf!)HZB zMn;$*)|75gRa;dha!k=3iatQx?}4$}l3G6yT5{E5{V2$fF?=dv&<@wU1ie~>7CH?3r#{T0Ig{v+JQ4Qz^LfX9?|+rp zEF8RGitnykFZNm%g~;$NWA-=a_jln`*@-b~QEvdmWXlR*&z9~2MXOmkP*nSF03B^D zf;l2efMi#=k+EnE5a4kbP88#E5@n$_F zaYz+pXO}xilY~Ijx;C3PZE|>A$>`^c$PIZ5SSyNEV$UV%37P@f&CpNpFv=%~59(?@ zP9ryHbJd_l7eY$rT?r%q<&DBzk+1)A`O+omQbrE~?EdWGHl4Mu`Ousk&TqL%B zj1>$g*Ag+Ms|k0EnB$AfQPHTAx`9lZ;#^1*?L#Bj?(l(8dkLs=0#Qzbw8F3hJ18q9 zx^RsHun`eIj0Q0FC`Jl$8vbMOs)l*(-;2;)afebeO*;+Og-=;o`3*u{#gXy&Rh@!l z=&Cs^NGcD?S!tCd4K$Z73WR0~)QI=T(Gy?Bi*5&C!VlRPw_vFHh3T$vOL#}5&O3}k zcLUn)7-VLQK;##qu`fJB1h_KD2}$ZP5cRuI>;1fN(ETH<*b-i)-42j`?<8TNvb%R1 z5ex(kx^9fD`yEI-f{EGn%Ju8lRoIoG6mE3^y7GfZWfc|o2$PLT&B_vK-O*JJUMLX< znV7C%C^@=%zkh#-mQ(}B)ZyQtNAGOfxKRng3_w%wVFA4O^6CvN461o_QF!nJz-1nR z#GMcM6+&i!)5-wgqtj4EYa>jo#R;CD;j69jfvmy`^H8_yQr7h;!7WWq22b1ujqZX- zDq|4+T-S|QJ5kW&$2Fn91j@kyNJTI4_-5pJ;Vv7=pZ}?#gJ`B*{QUecKCPCC{{b_2 z7^F1^H9_0_0VkUPmduKACp^cIvj|iGe1fYQL*krhBQJ1k!bp>-!>)`m&jJ{deCPhI zYn5QlHGMF-5W#bWXOrVW?RgAF@$cCze}NzQRz*A%SQMi|%n#9!UWr`deGornAf2-Y z6BQZ&uyd@hz+wHy1-k_=MdlhSgL@xuIfe9ZoX3{#!|ifvXhU9;%P*R>t}7H83^EAa zmX1P$uZ4(M9maLTHsXjY&VdMJ|ISeLGr%_|q{A#!(`t1y<%O28ceV z{+Pnn*5T(`l4cQ zUCoyc=U(;;gR57rI)>ChzfVAcQE5T*T(?>h#V*L;NXi$%CkVS-LLtf>Zo@!x?u@ma z5D=A(33mm+DF8(J#YT!`A@;+Z8)h<*hy8HT;JN4F;o)NB?*J}cMF&0R^L~7(h)ZHHXb1*&D@ig#4-# zIG_T9@O4ON+%LeP36xFh4pdO%9_2q;3l2kIv7N3q(?EQQJw?o)wu9Az5cj(y05o{e z0b02lAABO-q2`FO+m`igI{=`*J2js=Id8B?SxeN#_h92~fJ^3?2N+g-nwWdK;PzlJa^8jS?wZT$&1_7;xy!0zqt!ljCh|KIq?Q6e52*q>ejLo)FayjWx8Fs0C zc77y&C53`Pc|XlQ7p9)r!M^vC>ceETGR_+@ij1v*dh9h5W;y^Br3A-WD&o>RAszu$s0{09W}c=uf=m6A?$F5pC3 z&o8*&xN>Er_)|-=8^Crlx}~aM{3H5FM5mle(n+m`hI|50c*kxb^@FXJkTFPzMT}f~ z)*`h5qd^hI7wHh@EcDMmNB^dakopK{owOZhE+iu0;Rs+YAy@@$ZSC{b96Vj!+#1a# zzbAy{)8OGTI>BsQo{C~XEtggVqhT7IFV3I$SVI;=KYrNv;XfHr?_=;19*Jpqq=OGw zG%IjQ&eb<}b`Kh5rdXf3WA*IKJNGFq*4&eisfTwQz52U*dsB9-#(|s)m@zoU?IvRZ zC;>Gx#BnvX4iQ*7QQ;~BxTQtch4!EFt;<1-47{K0jEhk^m&G!5MXD$Im6Vj=@Fki4 zckp#8P;?6+$QQMQF2oaZx(g5wJp}ml5mf~Ul9TPI-9sWgy0A<`ksf~nJ{HQY zmK|R?6|rcP@;$~_Y%o=nQNifYV`QWqO&YfN1ux83tTA2)2!gX9uq%YzEW%?)_s>ZE zf)T((TfR#!3EqVnmDSOz2SfPd`kJyHOryk_ILQW)Ll`t$Cf(n^pBjbbT2M^>M3BEf zi_)%)X4LTg8MKN&JGM!#c`Eb+`ZtdQ5SP;59=v;HXocn;p)0*!Kr3EAsqz8Eo;Jc9 zN06ja9^$hF`CnA+)jQ@r-KD@hRzjoy$xT26GLdCC?dk`5>>V9N0O{_vK+3hcZrz8R zdQ70!Y!8p#z!>8wAVSb>+^)R!)Q9^r;$03jHu2hg+7AZKy5k3c60K>DViY7YNH*?4 z&RE>&NPL-eAeBiZ3kT;WpBx~jA2??Ap^~YD{UIXc$1b} zL;EuhW$2)3+d>5g6|cl?Kq-#|AifYpif;9M8T(Qg1M6_=K3TSG*=-2CNWr<1XM26V z349g~*(Xoo+*79LTX)h#2+xJ{%H|{}I!xcO=x5H*No7!IT65#ublB)i+?CU7P)gzZ z*>-=3xNGs}@uQT6{`e2;=P))H{TT;F{v>oH)UGFdsfN)E=w zh@o*rIK_i|e`X}FHRTp{paDp(U3v(0y|5jKOAmBKj1KMVDFP++6N*&~`rbOM8#5FQ zmLLY}eA;EN!)-(_! z-cVNx5K+i(ne@(Z)RkK)*q^M@`Cj6h@=&ciyKVq>^2d4_!S^3QyQ_}$znpmRE|GXk zRr0CHaAXU0wy;-Y8JR0vg=p8I)p!qRn|n0_9U^NEe*CDZsM6Q+<5*T+v!y@*o_#^| zJj!jrsYp3NIuToFi*ZgL3dTm&FZUG96n*JgA)1d)s$P>N3I^f{TKkx;cC;ERO;=j+ z!~CDAz3ww{bA79st*qYHp^>>f-?$@tFjR3v{JDhaQ7zA{qAGjz|C#epa_`Hjk{8EZ zu<(@>4#Ec~h0H8xq<-a#JcCsbuF^#mG!D&f0*ApJkpmK7Tm#ALbValD8f{uCbpl&~ zv%^pA9c|N-8BM}{XGJiJQ^O)PTaJhG%jQC}uTMYZnfQX)^lXNEB}(XWtKt(!*| zjdErn`c&TF#HLJ}v8Q3)D3#qEReM(A~S^mbo^*QkIIp{~-dJRJn01Wf$tL-Erf~4v%qRtI{ zu9+S337@ouL9KPOwwnw_;(mm+zd@XnJqwUTrWZlrnk+XVC&-~xbkCGoix|qE9oef_ z1L+&TiRLkV*6*43Tv1I)BFdrii@B+L5imdY&|^IKXuf?!8EHePQiLZJVmHtTxF!yV zMT`ndZDs{f4o8WCW>g=p(p8SL&OGHz1lYgLBPyyjd>=HhiRA6c+#djQ9rtvU`{t(F z<(d2m$gaR>rd0_%Y?+2RNfy9PABp`zuW~@zv>d5SX=pn1QDHPiT{msDtS;930OboB-LyHaOm4Sd$M4Q&Yhq=0hRcSUU|Zli~6LPqy@V zdP7usNJQai@}W(Vw;MOtRfUCVqlP6Dzsh_D*qgbxp(q+o!EsXbm2W-r-=-jo4vjkWb(CFyC`3Ez*~WCzI0qyk90<@yQGZ>!->T+BVV<>ZXT z`WCyeo=;@4`;lRPhO1ujma79ViHp85j4P|cwK0i{4Z;0#G^zBto6IE-z@NR)^LoF~j)A1{fj$05 z%Xt2H)b{--MRqukz9y5Wwds+C`t&D=*~oQ*-|B9!Wj> z4bAZ=PzLj9JqPSl*)UG8pkn0;Y%!r*8wSe^PKwA3?gp96(zQ)Du2l65#Pl2|oP6)d zC+{5|b{w}t8h<0$p6A8~F!@aOE}p{QSi7=735PXkjG2+N-m6S=;=b`w{D^x!4!1k3kU*$V(Ta07#Lz81N;i_G*AoidcWgky+0h@BlF* zHqOr7H@neizr-=6LB#_5i^ThIG;jW1OwU(-1%*;F0>jXak|6AFM9o?vfDR6N?R&Vy zhnG-zIQ5gbqSVI8v?t+-@o@=c(SxqIXo<5|O=gpuG_n#BC)HXFj(b@E?BjHw8A?R= z5OxCt9XF~T___9tTeF=F)un*C8U&{2yNAi+{{ z7=1AgbL%X-A`1&<5t1O!1m+MxtYkK*Wy|%wpcyRQCOa!N6WP5c_Uapd5GtqAgb_Vy ziK*Cq-ZnSO=cm%CdN3l6&+cXke5eFlHGf4TkVi)N{q%8nAmROsfl0;l1GV& zSb~dup$rlP&RdRdz#L1ph~YI%E|T*gqsKkGz#tgbLIr$q@Mj9tCOlRfcN+mXXv=$v zGBhzJCZ-!lZc!J3M4(a-%FQ|C1#OFf^7Pk-+=1U(dpvn&RJ{O+CGDLXzq=foqjioc zA4rTGK%nFS&4m>R0tky#q~>PBBV==Rqf!dMD1vDN_Ze=C3n3w+jcA#v+Lq6)&o5() zYaGU!9RBcY2AJn|9M1Xmrk_JG69Xfqvb%u__(>)E10aQ<^$v=yqvj6|X}y?*9#gZn zhfd%~S_sV97McieC5{<205rb2QMX73CuLjz{zRz-z%yR#8y{Vs)6VwX>_jz$%-VJl zeS#gH#~jq{k_YV8DJx+A4NllV$;|NK=cirRAlP=Dy3=+u)5fS;t079K`{)ciId2&_^`s~zn9>wPR!Fl+Pj7OE6j zQx`_>iPm|?*(~?-g;ODo2$0C6iuSV(eW##_BLL07{YT%iITXT6ofOMNk%5h37?37D zD4H@(5=iG~JLc~k%d>ss%hc6^=fRxt29grFYAg(}BdX?M0D}3cIv@H{E}!399!NPv z+Pr1B>c>rV^=a4(dBgd&{SN!~(aO}Smr$ftng-rnnJdj_1Oq*3*Wt>~DuYUBIP2xO zHX%a@fKUV-?drkGv@i|cSHP-tP;uNwEi6m>1SGEWxYtCe;Y|B(g%%k%kdWnMO^fp% zawoCicgXf(bG9b22Z@C^Z+YFZ!>No_lR+pTTrJ7>5$m!BDNnLfbOA<&8HIQ@Q>fe5 zb0EsNruzg``_F1N7+@!^@)o)s3RUY`ly$gvX^W>DOBkr=MZwr(B&E@|m&pg!O# z-`#&-!xe(OAo6~jWzWa%=>Vp3LUGosVl??E{SOP9hVPHi;| z`Ow01tO-D0kBkP3N|ePA3SJUUg**tkfPHZ+#GKsMl9!$a1flsfVJnL!)4=8r=79sU z&kE^*!$(lYYbI9+6+4Mqw8K8uhk%Uo+FG&J*7JXU7b2^g&JZO8S~Vq?GiPexWz!9P z)pP&Kn*Stdx#9?6N`5%@;adLv_5Z8iwS!4zF0RP(sBT`u{!{g+*N*`h$?gQrvN60M z(!*kJ-mUQ$w(aO;B$ubF01U*v5|0T%1L+PFJV)RORJv>68edGp>_NI4Ql;<;ha#^) zk8?)}7>DM+b!mIMDQSx^UqpUynmS}6`U}^)<^T^Iaxe;2Oh{rDbm;5i6y`2toC)8#+wF*Kgi@i$@a2J3etFtRU-(`=rIiSA%0%mn*;x znKJ@_-ZzYtfPzUt=-nFdq7b2zA;vnuM_uKTYlkriYSSdeNu*m5(8ms2ZZ(>z_Uyg$28 zaE(tbUj*>0Eso#zDj+OvEtDqqpfnCb7=^Z89&-%-s9e*=zvN*5OPl{Wi;+*MlT#># z#L9jy#qlOh!eZXwC}CJg1JZ>m)Ivgg7c>Bv6-F*b?_N0T!su=Gr#MH6UttU<56N(Q z8<`}Hgj>=+WB>Z+H=-#RCI7z3?J#@&)yo#1MsX zo`8bbRpp~lEI@^&$M5gu;}32Hr;o^SG&up2lBnsmw|?aj#rJ3W{g&|g^kn2!o2xdF zrPGo%otz$P!TFjFQBGv{)bqW1ge--$vt}h}W9ca5pGe3!cly`&24Wfd47Pb{G)!>4 z!^u7?ckR_zr?cDHbZZ$7?Ov>VcQi6MPJGO-nO>r<@y(D#cz0w@UjMD`phWwRVY`pz z8*6`iw7l}peStGKBV!XddX@HTD}0o3I$g5&w^zU`#r?(6Z^lM?!pG)DWqP-Aicb&giM%PQqU@llXgT;PtQ0I0+o^u zk;8lEatoh7j|t?Z6y9&kr9(EA>A{27f1|WW^H=rom~mLH39aRjjkUG4cUP#vCu^5Q zaDltaBNm|t!>R|-rDW(Tb)v~AM+NlKajKc3eo2!L6Wf&2IvCmHS-7p=<0=c(=HdZ5LK%-!jXYPuhvPFO!( z(iutGtWdOw$p>B>*Mi^>WL+oH{g8^e4f$CR6*8<0CFxE-nkMj=DLzxA} zD>-16%+HrAL@rMaG>O<47sk_ki><7rzBGa_x6K=mvr&=hVa20CtqT2t{(Yl zC=rbiKev&5v_b&2$8@?z-*3Bh{)6S&(mYfkMR*Q6*RmR77RY%_Tkp6=M{T*jSBKoM zx}IiiYMhd3$Kr0(^@YG6n2KmuG1&L~mvUXG9z)KgOo!(%P5(g^UlwiB=)9R(+(;SU zgP`|iWoOoMaLAxqZfX4Vi61)M3kr9fpziO2+_xNMIlXs5rDdIxQW(;vQMlbR($e|@ z2eb1>^pJ0kVF--cDzYQ5R}Yb4 zf+QN!01n(a2*Tc?dtJO(u3(>wORjx;adWCoMD$JfKD^a(ml(c1P+6~bMGd#v zK~aqJhK`9zTkZ7Bsata0eQ56W@I5ErZg9*!-Xv{@l!ITGDwZ7f{(Z$_MZ|=*V^*k>ui&V5OT1>ZLCW5#|Kg3IxNDn7 z1O0y^0%wrR_+6C>-Ui>>R9U+dNe@LC9s3b()9i*;%~M$k@{QH`a2YrHE~56WT6oSI zAa8ioKe#Kp^=_sLvl!}XE(z9Os3{`T(&7zbTwE|@P*PsLoeL1w_#=c8<}H=&>gtO0 ze~+{Mv&yM2#h=Mn05oxO(;Is+%04008_$&_=NTNuer8%E2`Ji zKACus`ulAqw{3gX+OXSM?w|hiDsyvipl`Qc9ft3yS(h;8S=6v{$t56!Z`N~g@OYOT zCox+{y8rDUx0<4&S^r-9`1p9*%lLGyZ|e3^QEN7B61K6iQJjKXMN3mnZKRJwx(M>k zG6Y4|f3$lu==Ln^-o4(#s;bM4O2K;3IhEB z@+BS<*KcWQNxnx7tC zF`5RKf}hzY_h*KidI%J!rm8(@wiib0w0zkisneg=HBJ}KSZ3V3sB-{5Own~WRNS%N zqQjhkq7C&JD+Bxi{{sB?K%zaPLJR3y{K+*WDf@iN1EyhejQWO z%;`Dr!l`!8Jq8^q*wO!hIc8YAjQs!c{7tzr#!9chkEj|NMo1xI-?67ODJ@OCjik|? z5M|K&*_&|)5?m%D5mOzV_|WihJAlJ(1B0d>9MzB$Y|^V4bo!?g0NW~%1P}HCib=`% zq4M~s&?qzjOY$sCDuK9r>VBlBN+;sw7%A!tr-|^rLL&|hg9dRE+H;J5M#U0mYV-DH z+vz5Rz-nbJT?claM8Vldc za%`{3J`d|L(Dv(qAXIzP@cF&^Qi%wOf^|^Ju!Wo8#eIY@rD`t*Bfl}0ME@P<$YTM19zj3kLy&g!-=*w9oM}4ofPFXXnl(w)hDy+;loo6 zjg6*7!Bx4<_G!)bVIQNCGCMu=gBMZ0Cl(6jULBN7!xxus{>izrZMNK6Drydh*QmL< zISY0wr$wT5SbF&3X4v)Lzma{I{9NYVw_)KIUlt{@CE$m}Z<)iwFVsF3a@OESVcEic u$8XCN7v2)TZSe=_Lw>j!{_nlmpQXFsSn>XPVx~miSxxznQs%xBe*Xp8f_j1g literal 0 HcmV?d00001 diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 85368c4..69fd553 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -5,13 +5,15 @@ Key Concepts This page collects the ideas hensmith implements: what a minimum approach temperature is, how the problem table turns a set of process streams into -utility targets and a pinch, how :func:`~hensmith.synthesize_network` turns -those targets into a network of exchangers, and what the result is and is not +utility targets and a pinch, how those targets extend to several utilities, +area, units and cost, how :func:`~hensmith.synthesize_network` turns the +targets into a network of exchangers, and what the result is and is not guaranteed to be. Everything below describes the behavior of the code in -``hensmith/hxn_synthesis.py`` and ``hensmith/_heat_exchanger_network.py``, and -of their two private helpers, ``hensmith/_curves.py`` (the stream curves) and -``hensmith/_planner.py`` (the network planner); the :doc:`tutorial/index` -shows the same concepts on a running system. +``hensmith/hxn_synthesis.py``, ``hensmith/_heat_exchanger_network.py`` and +``hensmith/targeting.py``, and of two private helpers, +``hensmith/_curves.py`` (the stream curves) and ``hensmith/_planner.py`` (the +network planner); the :doc:`tutorial/index` shows the same concepts on a +running system. Heat integration and the minimum approach temperature ----------------------------------------------------- @@ -329,7 +331,8 @@ contribution; the shifted curves carry the pinch). utility of 1.936e+06 kJ/hr. The curves come closest at the cold end, where the pinch of this system lies -- 298.15 K on the shifted scale. :doc:`tutorial/02_pinch_analysis` builds this figure from a - :class:`~hensmith.ProblemTable`. + :class:`~hensmith.ProblemTable`; :doc:`tutorial/05_targeting` draws it + with the library. The **grand composite curve** plots the same cascade differently: the heat carried through each shifted grid temperature once the minimum hot utility is @@ -339,11 +342,165 @@ curve's minimum may be strictly positive and the pinch is placed at the top of the grid, ``Ts[0]``, by convention. Its shape shows where in the temperature range utility has to be added or removed. +Both curves are library functions: :class:`~hensmith.PinchTargets` returns +them (:meth:`~hensmith.PinchTargets.composite_curves`, on the real or the +shifted scale, and :meth:`~hensmith.PinchTargets.grand_composite_curve`), on +the knots of the table's own grid, so they reproduce the table to rounding; +:func:`~hensmith.plot_composite_curves` and +:func:`~hensmith.plot_grand_composite_curve` draw them. + Both pictures express the three rules of pinch design: **no heat may cross the pinch**, **no cold utility may be used above it**, and **no hot utility below it**. Violating any one of them makes the network use more of both utilities than the targets require, by the amount transferred across the pinch. +Multiple utilities on the grand composite curve +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The MER targets say how much heat the utilities must supply and remove, not +which utilities. :meth:`~hensmith.PinchTargets.utility_targets` places +BioSTEAM's heating and cooling agents on the grand composite curve, heating +and cooling separately, and prices each one with BioSTEAM. + +**Levels.** BioSTEAM lets a heating agent serve a stream only below +``agent.T - agent.dT - dT``, and a cooling agent only above ``agent.T + +agent.dT + dT``, where ``dT`` is ``bst.HeatUtility.dT``, read at call time. +On the shifted scale that limit is the agent's **level**: the limit itself for +a heating agent and the limit less ``T_min_app`` for a cooling agent, which is +BioSTEAM's rule exactly. With approach contributions the streams have +different shifts, and an agent's level is the highest single shifted +temperature (the lowest, for cooling) at which it serves no stream beyond that +stream's own real limit; streams with slack are then served conservatively. + +**List order and fuel.** Non-fuel agents are placed in the order of +BioSTEAM's lists, BioSTEAM's own "first feasible agent" rule: for the default +lists, the lowest pressure steam first, and the warmest coolant first. Each +takes as much of the remaining duty as the curve above its level (below it, +for cooling) allows, given the agents already placed. What remains of the +heating goes to the first fuel agent, a furnace at the top of the curve; if +there is none, or if some cooling remains, it is left unserved, with a +``RuntimeWarning`` and a nan cost. A level equal to a grid temperature +serves that temperature's point loads; BioSTEAM's own rule is strict, and the +two differ only at exact equality. + +**Served intervals and pricing.** Each agent is priced as one BioSTEAM heat +utility over the stretch it serves: from its level to the next zero of the +**balanced grand composite curve** (the cascade net of the utilities), which +is the pinch or a *utility pinch* where a lower agent is used to its limit. +The process temperatures of that stretch are its shifted temperatures plus the +largest shift of the cold streams in it (heating) or the smallest shift of the +hot streams (cooling) -- exactly the streams' own temperatures without +approach contributions, an approximation with them. A sensible agent's outlet +therefore follows the temperatures it actually serves. BioSTEAM divides each +duty by the agent's heat transfer efficiency, so a target's ``utility_duty`` +is utility side while its ``duty``, like the MER targets, is process side. +Each pricing uses fresh utility streams in a temporary flowsheet: the costs +are deterministic, and the user's flowsheet, the agents and their later costs +are untouched. + +Supertargeting: area, units, capital and total cost +--------------------------------------------------- + +Energy is one part of the cost of a network; its exchangers are the other. +Supertargeting targets the exchangers too -- their area, their number and +their capital cost -- before design, like the MER targets, and with them the +total annualized cost, which a sweep of ``T_min_app`` then minimizes +(Linnhoff & Ahmad, 1990; Ahmad, Linnhoff & Smith, 1990). +:meth:`~hensmith.PinchTargets.summary` returns every target at one +``T_min_app`` and :func:`~hensmith.supertarget` sweeps it. + +**Balanced composite curves.** Adding the utility targets to the composite +curves -- the heating utilities to the hot curve, the cooling ones to the cold +curve -- gives two curves that span the same heat +(:meth:`~hensmith.PinchTargets.balanced_composite_curves`): a latent agent is a +flat at its temperature, a sensible one a line between its inlet and outlet +temperatures, and fuel a furnace that takes no area. If some utility is +unserved, the curves cannot balance, and the area, capital and total cost +targets are nan. + +**Area.** The area target is Bath's formula (Townsend & Linnhoff, 1984) on +the balanced composite curves: heat is transferred vertically, from the hot +curve to the cold curve below it, in counter-current (a correction factor of +1). Split the heat axis at every breakpoint of either curve into intervals +:math:`i`; in each, stream or utility :math:`j` carries heat :math:`q_{ij}`, +and + +.. math:: + + A = \sum_i \frac{1}{\Delta T_{LM,i}} \sum_j \frac{q_{ij}}{h_j}, + +where :math:`\Delta T_{LM,i}` is the log-mean temperature difference of +interval :math:`i` on the real scale and :math:`h_j` the film coefficient of +:math:`j`. Every curve and every stream's share of it is linear in the heat +inside an interval, so the sum is exact. hensmith takes :math:`h_j` = 1 +kW/m²/K for every stream and utility by default, which gives any pair +BioSTEAM's heuristic overall coefficient of 0.5 kW/m²/K (condensing against +boiling, where BioSTEAM takes 1.0, is not reproduced); ``h=`` and +``h_utilities=`` set others. A zero driving force anywhere makes the area +infinite, with a ``RuntimeWarning``, as at ``T_min_app = 0``. + +**Units.** A network of :math:`S` streams and utilities that forms one +connected component needs at least :math:`S - 1` units (Euler's rule). The +units target applies it to each region between the zeros of the balanced +grand composite curve -- the process pinch and every utility pinch --, +:math:`S` counting the streams with heat in the region and the utilities +acting in it. It is an estimate, **S - 1 per region, assuming one component**: +a subset of streams that balances on its own needs fewer units. + +**Capital.** The area is divided into as many shells of equal area as the +units target, :math:`N`, less one furnace for all the fuel, and every shell is +costed with BioSTEAM's own correlations, :math:`N \cdot C(A / N)`; the furnace +is costed on the fuel's utility duty. BioSTEAM's specifics carry over: a +double pipe exchanger below 150 ft², whose cost jumps at 150 ft² to that of a +shell and tube (the floating head correlation is not monotone below about +126 ft², which the double pipe hides), and no largest shell area. All shells +are costed at one pressure, ``P``, whereas BioSTEAM takes each exchanger's +highest inlet pressure; for high pressure steam heaters its pressure factor is +up to about 12 % higher. + +**Total annualized cost.** With a capital recovery factor over :math:`n` +years at interest :math:`i`, + +.. math:: + + \mathrm{TAC} = \mathrm{CRF} \, C_{installed} + t_{op} \, C_{utility}, + \qquad \mathrm{CRF} = \frac{i (1 + i)^n}{(1 + i)^n - 1}, + +with :math:`i` = 10 %, :math:`n` = 10 years and :math:`t_{op}` = 8000 h/yr +by default, or taken from a BioSTEAM TEA (``tea=``). It counts the bare module +cost of the exchangers only: no Lang factor and no working capital. + +**The sweep.** :func:`~hensmith.supertarget` evaluates the targets over a grid +of ``T_min_app`` values, reusing the streams' curves (nothing is flashed +again), and then refines the grid around the least total annualized cost. The +cost is not smooth -- the units target jumps, the double pipe switches to a +shell and tube, the pinch moves -- so the refinement only narrows a bracket, +and ``at_bound`` flags an optimum at an end of the sweep. With approach +contributions, only the unspecified streams follow ``T_min_app``; +``scale_contributions=True`` scales the specified ones with it, the +contribution scaling of Ahmad, Linnhoff & Smith (1990). + +**A network against its targets.** :func:`~hensmith.network_report` compares a +synthesized network with its targets: utility cost, units, area, capital and +total annualized cost. Its ``'area'`` re-sizes the network's exchangers like +the target sizes them (the same film coefficients, a correction factor of 1, +the integrated driving force along the exact stream profiles and the process +side duty), so it is comparable with the target; ``'area_biosteam'`` is +BioSTEAM's own sizing, which includes its correction factor, the terminal +log-mean temperature difference and the utility side duty. The comparison has +limits: + +- the Bath target bounds the area only with equal film coefficients and the + same utilities; +- the network gives each stream one agent at its outlet, BioSTEAM's list + rule, while the target allocates the agents on the grand composite curve, + so the network can use costlier agents than the target (on the four-stream + problem of Linnhoff & Hindmarsh at 10 K, the target is low pressure steam + and cooling water, the network medium pressure steam and chilled water); +- the units target is an estimate in both directions: a balanced subset of + streams, or one agent per stream where the target counts each utility as a + stream, needs fewer units; repeated matches and splits need more. + From targets to a network: the pinch-outward MER planner -------------------------------------------------------- @@ -778,6 +935,24 @@ the repair must act on each pair's own requirement. The key rules, the validation of keys and values (for idle units too, and before any stream is flashed) and the ``cache_network`` rule are tested as well. +**Targeting.** ``tests/test_targeting.py`` checks ``hensmith.targeting`` +against references computed independently of it: the composite curves of +the four-stream problem of Linnhoff & Hindmarsh in closed form, and the +composite and grand composite curves of constant heat capacity and +real-thermodynamics cases against their problem tables; the multiple-utility +allocations and served intervals against duties computed from the agents' +own attributes, each priced again in isolation; the area target against a +midpoint integration of hand-built composite curves (with approach +contributions and per-stream film coefficients too) and against closed +forms; the units target on eight reference problems, utility pinches and +balanced subsets among them; the exchanger costs against BioSTEAM's own; the +capital recovery factor against BioSTEAM's; and the swept optimum against +the closed-form total annualized cost of a two-stream problem. The +facility's targets must equal the synthesis's bit for bit, with and without +approach contributions; the network report's areas must equal independent +log-mean areas; and targeting must leave the flowsheet's streams, the +agents, later utility costs and a later synthesis unchanged. + **Doctests.** The examples in the docstrings are executed as part of the test suite, so the numbers printed in the API reference are numbers the code currently produces. @@ -820,7 +995,11 @@ What it is not: - **Energy first, then units; no cost optimization.** MER always takes precedence over the number of exchangers, and an unsplit MER network can need many of them. The branch and bound reduces the number of exchangers - among MER plans, but nothing optimizes area, capital or total cost. + among MER plans, but the synthesis does not optimize area, capital or total + cost; ``hensmith.targeting`` targets them, finds the ``T_min_app`` of the + least targeted total annualized cost (see `Supertargeting: area, units, + capital and total cost`_), and :func:`~hensmith.network_report` compares a + network with the targets. - **Some networks cannot be represented.** Networks whose match order is cyclic are outside the planner's model. A side that needs a split without a pinch-rule proof spends its whole MER search budget before the best-effort @@ -842,6 +1021,13 @@ What it is not: pair and is not such a sum is not (see `Approach temperature contributions`_). The utility exchangers use BioSTEAM's approach rule whatever a stream's contribution. +- **Targets are estimates.** Beyond the MER targets, the targets of + ``hensmith.targeting`` are estimates: the area target bounds a network's + area only with equal film coefficients and the same utilities, the units + target can be wrong in both directions, the capital target assumes shells + of equal area at one pressure, and with approach contributions the utility + levels are conservative and their pricing temperatures approximate. The + optimum ``T_min_app`` is the optimum of these estimates. References ---------- @@ -855,6 +1041,14 @@ References - 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). +- Townsend, D. W., & Linnhoff, B. (1984). Surface area targets for heat + exchanger networks. *IChemE 11th Annual Research Meeting*, Bath, UK. +- Linnhoff, B., & Ahmad, S. (1990). Cost optimum heat exchanger networks - 1. + Minimum energy and capital using simple models for capital cost. + *Computers & Chemical Engineering*, 14(7), 729-750. +- Ahmad, S., Linnhoff, B., & Smith, R. (1990). Cost optimum heat exchanger + networks - 2. Targets and design for detailed capital cost models. + *Computers & Chemical Engineering*, 14(7), 751-767. - Cortes-Pena, Y., Kumar, D., Singh, V., & Guest, J. S. (2020). BioSTEAM: A fast and flexible platform for the design, simulation, and techno-economic analysis of biorefineries under uncertainty. *ACS Sustainable Chemistry & diff --git a/docs/source/contributing/contributing.rst b/docs/source/contributing/contributing.rst index 14bac38..9228163 100644 --- a/docs/source/contributing/contributing.rst +++ b/docs/source/contributing/contributing.rst @@ -9,8 +9,8 @@ change has to respect. Where the code lives -------------------- -The library is two public modules under ``hensmith/``, and three private ones -behind them: +The library is three public modules under ``hensmith/``, and three private +ones behind them: ``hensmith/_heat_exchanger_network.py`` The ``HeatExchangerNetwork`` facility unit: its ``_run``, ``_design`` and @@ -24,6 +24,12 @@ behind them: splitters and mixers, and verifying it on exact stream states), the per-stream bookkeeping of ``StreamLifeCycle``, and ``plot_pinch_diagram``. +``hensmith/targeting.py`` + Targeting and supertargeting on top of the problem table: composite and + grand composite curves, multiple-utility targets priced by BioSTEAM, + area, units, capital and total annualized cost targets, the + ``T_min_app`` sweep (``supertarget``), the network report and the plots. + ``hensmith/_curves.py`` (private) The piecewise-linear temperature-enthalpy curve of each process stream, built once from a handful of flashes, on which both the problem table and @@ -42,8 +48,8 @@ behind them: lemmas, the constructions and the proof that a split side always has an MER candidate. Nothing in it runs when the option is off. -``hensmith/__init__.py`` re-exports the ``__all__`` of the two public modules -and holds the biosteam registration block described in `The import +``hensmith/__init__.py`` re-exports the ``__all__`` of the three public +modules and holds the biosteam registration block described in `The import contract`_ below. The public API is documented under :doc:`../API/api`. Tests live in ``tests/``: @@ -89,6 +95,14 @@ Tests live in ``tests/``: per-pair exact approaches on corpus and real-thermodynamics cases, key resolution and validation, and the ``cache_network`` rule. +``tests/test_targeting.py`` + ``hensmith.targeting`` against independent references: closed-form + composite curves, utility allocations from the agents' attributes, + independently integrated areas, units and cost references, the + closed-form optimum ``T_min_app`` of a two-stream problem, the facility's + targets against the synthesis's, the network report, and the absence of + side effects on the flowsheet and the agents. + Development environment ----------------------- diff --git a/docs/source/index.rst b/docs/source/index.rst index f1f5bf0..53de057 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -42,7 +42,11 @@ synthesizes a network of process exchangers that reaches the minimum energy requirement (MER) whenever it finds one without stream splits -- or, with ``stream_splitting=True``, splits streams where MER needs it -- and reports the utility savings and added capital cost as part of the system's -techno-economic analysis. +techno-economic analysis. Its targeting tools go beyond MER: they target +several utilities, exchanger area, units, capital and total annualized cost +before any network exists, find the minimum approach temperature of the +least total annualized cost, and compare a synthesized network with its +targets. Watch it run in the `Quickstart`_ demo below. Quickstart diff --git a/docs/source/tutorial/01_quickstart.rst b/docs/source/tutorial/01_quickstart.rst index 134c0bd..391f473 100644 --- a/docs/source/tutorial/01_quickstart.rst +++ b/docs/source/tutorial/01_quickstart.rst @@ -208,5 +208,7 @@ Where to next the diagram, unit by unit. - :doc:`04_configuring` -- the constructor options of the facility, and applying it to a larger system. +- :doc:`05_targeting` -- utility, area, units and cost targets, and the + ``T_min_app`` of the least total annualized cost. - :doc:`../concepts` -- the pinch concepts and terminology used throughout. - :doc:`../API/api` -- the full API reference. diff --git a/docs/source/tutorial/02_pinch_analysis.rst b/docs/source/tutorial/02_pinch_analysis.rst index 8fd3970..487a76a 100644 --- a/docs/source/tutorial/02_pinch_analysis.rst +++ b/docs/source/tutorial/02_pinch_analysis.rst @@ -149,11 +149,13 @@ designs, and the dashed line drawn on the pinch diagram of chapter 1. Composite curves ---------------- -hensmith itself ships one plot, the pinch diagram of a synthesized network. The -two curves below are not library functions: they are computed in this -tutorial's script from the fields of the :class:`~hensmith.ProblemTable`, and -they are shown here because they are the standard way to read what the table -says before any network exists. +The two curves below are computed in this tutorial's script from the fields +of the :class:`~hensmith.ProblemTable`, to show how they follow from the +table; they are the standard way to read what the table says before any +network exists. The library returns the same curves +(:class:`~hensmith.PinchTargets`) and draws them +(:func:`~hensmith.plot_composite_curves`, +:func:`~hensmith.plot_grand_composite_curve`), as :doc:`05_targeting` shows. .. literalinclude:: /../_demo_src/examples/ch02_pinch_analysis.py :language: python @@ -306,4 +308,6 @@ Where to next temperatures behind the diagram, unit by unit. - :doc:`04_configuring` -- what changing ``T_min_app`` does to the targets and to the cost of reaching them. +- :doc:`05_targeting` -- the composite and grand composite curves drawn by the + library, several utilities on them, and area, units and cost targets. - :doc:`../concepts` -- the pinch concepts and terminology used throughout. diff --git a/docs/source/tutorial/03_network_anatomy.rst b/docs/source/tutorial/03_network_anatomy.rst index 2ec1886..1794270 100644 --- a/docs/source/tutorial/03_network_anatomy.rst +++ b/docs/source/tutorial/03_network_anatomy.rst @@ -391,6 +391,8 @@ Where to next - :doc:`04_configuring` -- the constructor options of the facility, what ``T_min_app`` and ``ignored`` change, and a ten-stream network. +- :doc:`05_targeting` -- how this network compares with the targets of + utility cost, area, units and total annualized cost. - :doc:`../concepts` -- the pinch concepts, the MER planner, and what a synthesized network is and is not guaranteed to be. - :doc:`../API/api` -- the full API reference. diff --git a/docs/source/tutorial/04_configuring.rst b/docs/source/tutorial/04_configuring.rst index 22c3249..645f688 100644 --- a/docs/source/tutorial/04_configuring.rst +++ b/docs/source/tutorial/04_configuring.rst @@ -128,8 +128,9 @@ makes the split instead. part of the cost curve: 2.977e+08 kJ/hr of heating, 1.936e+06 kJ/hr of cooling and 1.112e+06 USD of added installed cost. Choosing ``T_min_app`` is choosing a point on these two curves; the economically sensible one depends - on utility prices and on the cost of exchanger area, neither of which the - network optimizes for you. + on utility prices and on the cost of exchanger area; synthesis does not + optimize them, but :doc:`05_targeting` targets both and finds the + ``T_min_app`` that minimizes their sum. Splitting streams ----------------- @@ -443,6 +444,9 @@ the real streams. Where to next ------------- +- :doc:`05_targeting` -- targeting the area, units and cost that this + chapter's sweep traded against the utilities, and the ``T_min_app`` that + minimizes the total annualized cost. - :doc:`../concepts` -- the pinch concepts, the MER planner and the validation behind everything shown in this tutorial. - :doc:`../API/api` -- the full API reference, including a table of every diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst new file mode 100644 index 0000000..895caf5 --- /dev/null +++ b/docs/source/tutorial/05_targeting.rst @@ -0,0 +1,332 @@ +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 any 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 +(:class:`~hensmith.PinchTargets`), sweeps ``T_min_app`` for the least total +annualized cost (:func:`~hensmith.supertarget`), draws the composite curves, +the grand composite curve and the sweep, and compares a synthesized network +with its targets (:func:`~hensmith.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 +---------------------- + +.. literalinclude:: /../_demo_src/examples/ch05_targeting.py + :language: python + :start-after: # [start:imports] + :end-before: # [end:imports] + :dedent: + +.. literalinclude:: /../_demo_src/examples/ch05_targeting.py + :language: python + :start-after: # [start:system] + :end-before: # [end:system] + :dedent: + +The facility's targets +---------------------- + +:meth:`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). + +.. literalinclude:: /../_demo_src/examples/ch05_targeting.py + :language: python + :start-after: # [start:targets] + :end-before: # [end:targets] + :dedent: + +.. literalinclude:: /_generated/ch05_targets.txt + :language: text + +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 :doc:`04_configuring`, 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. The same object can be built without a +facility, from heat utilities with +:meth:`PinchTargets.from_heat_utilities ` +(as the end of this page does) or from streams as +:func:`~hensmith.problem_table` takes them. + +Several utilities on the grand composite curve +---------------------------------------------- + +The MER targets say how much heating and cooling the streams need, not which +utilities supply them. :meth:`~hensmith.PinchTargets.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. + +.. literalinclude:: /../_demo_src/examples/ch05_targeting.py + :language: python + :start-after: # [start:utilities] + :end-before: # [end:utilities] + :dedent: + +.. literalinclude:: /_generated/ch05_utilities.txt + :language: text + +Each agent has a **level** on the shifted scale: the warmest temperature it +can serve (the coldest, for a coolant), its own temperature less biosteam's +approach. Low pressure steam, first in biosteam's list of heating agents, is +at 407.19 K, 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 warm end of the process temperatures it serves, 303.15 K: +biosteam prices a sensible agent from that end, so its outlet follows the +temperatures it actually serves. 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. + +.. literalinclude:: /../_demo_src/examples/ch05_targeting.py + :language: python + :start-after: # [start:gcc] + :end-before: # [end:gcc] + :dedent: + +.. figure:: /_static/images/examples/tutorial_05_grand_composite.png + :class: white-bg + :width: 560 + :alt: 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 +:class:`~hensmith.ProblemTable` in its own script; +:func:`~hensmith.plot_composite_curves` draws them from the targets, and +:meth:`~hensmith.PinchTargets.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. + +.. literalinclude:: /../_demo_src/examples/ch05_targeting.py + :language: python + :start-after: # [start:composites] + :end-before: # [end:composites] + :dedent: + +.. figure:: /_static/images/examples/tutorial_05_composite_curves.png + :class: white-bg + :width: 720 + :alt: 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, to the + right of the hot one. The script checks that their knots are chapter 2's. + +.. figure:: /_static/images/examples/tutorial_05_balanced_composites.png + :class: white-bg + :width: 720 + :alt: 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, :meth:`~hensmith.PinchTargets.summary` +targets the rest of the cost of a network. + +.. literalinclude:: /../_demo_src/examples/ch05_targeting.py + :language: python + :start-after: # [start:supertargets] + :end-before: # [end:supertargets] + :dedent: + +.. literalinclude:: /_generated/ch05_summary.txt + :language: text + +- **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. +- **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. + +Supertargeting: the least total annualized cost +----------------------------------------------- + +Chapter 4 swept ``T_min_app`` by synthesizing a network at each value. +:func:`~hensmith.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. + +.. literalinclude:: /../_demo_src/examples/ch05_targeting.py + :language: python + :start-after: # [start:sweep] + :end-before: # [end:sweep] + :dedent: + +.. literalinclude:: /_generated/ch05_sweep.txt + :language: text + +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 +2 K the system becomes a threshold problem -- at 1 and 1.848 K it needs no +cold utility at all -- so the hot utility no longer changes and only the +area, which shrinks as the approach grows, still moves the TAC. The least +TAC is therefore at the threshold: the refinement places it at 1.848 K, the +last point evaluated before the cold utility appears (it is inside the +sweep, not at its bound), 6 USD/yr below the TAC at 1 K. From 2 K on, the +cold utility needs a unit of its own (6 units), and 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. + +.. figure:: /_static/images/examples/tutorial_05_supertargets.png + :class: white-bg + :width: 640 + :alt: 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.848 K. + Capital is a small fraction of the TAC, so the optimum has the least + utility and, of the approaches that share it, the least area. + +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 +:meth:`PinchTargets.from_heat_utilities `, +and ``heating_agents=`` and ``cooling_agents=`` are passed on to every summary +of the sweep. + +.. literalinclude:: /../_demo_src/examples/ch05_targeting.py + :language: python + :start-after: # [start:trade_off] + :end-before: # [end:trade_off] + :dedent: + +.. literalinclude:: /_generated/ch05_trade_off.txt + :language: text + +Here the annualized capital cost (71605.6 USD/yr at the optimum) falls as +``T_min_app`` grows and the operating cost (20032 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. + +.. figure:: /_static/images/examples/tutorial_05_trade_off.png + :class: white-bg + :width: 640 + :alt: 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 network against its targets +------------------------------- + +:func:`~hensmith.network_report` compares the network the facility +synthesized with the targets of its streams, on the same options. + +.. literalinclude:: /../_demo_src/examples/ch05_targeting.py + :language: python + :start-after: # [start:report] + :end-before: # [end:report] + :dedent: + +.. literalinclude:: /_generated/ch05_report.txt + :language: text + +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. The comparison of +everything else needs care, 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 exact + stream profiles. 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. +- **Units.** The network has 7 exchangers against an estimate of 6: it gives + each stream its own utility exchanger, where the target counts the steam + as one stream in a region above the pinch. The estimate can be wrong both + ways: a balanced subset of streams needs fewer units, and repeated matches + and splits need more. +- **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 :meth:`~hensmith.PinchTargets.summary` with the +same options; ``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 +------------- + +- :doc:`../concepts` -- the multiple-utility rule, the area, units and cost + targets, the sweep, and the limits of each. +- :doc:`../API/targeting` -- every function, class and option of + ``hensmith.targeting``. diff --git a/docs/source/tutorial/index.rst b/docs/source/tutorial/index.rst index 9817616..9e0f57c 100644 --- a/docs/source/tutorial/index.rst +++ b/docs/source/tutorial/index.rst @@ -3,7 +3,9 @@ Tutorial A staged, hands-on tour of hensmith: the quickstart system end to end, the pinch analysis behind its utility targets, the anatomy of the synthesized -network, and how to configure the facility and apply it to a larger system. +network, how to configure the facility and apply it to a larger system, and +targeting utilities, area, units and cost to choose the minimum approach +temperature of the least total annualized cost. Every code block on these pages is included from a script under ``docs/_demo_src/examples`` that also produced the figures and outputs shown, so what you read is what ran. @@ -15,3 +17,4 @@ so what you read is what ran. 02_pinch_analysis 03_network_anatomy 04_configuring + 05_targeting diff --git a/hensmith/targeting.py b/hensmith/targeting.py index f438167..397c234 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -2170,20 +2170,28 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, **text_kwargs) continue T, H = (hot_T, hot_H) if u.heating else (cold_T, cold_H) - if u.agent.T_limit is None: # beside the agent's flat + if u.agent.T_limit is None: + # above a heating flat, ending at its warm end (left of it the + # hot curve is below the flat), or below a cooling flat from + # its cold end (right of it both curves are above the flat) T_u = u.agent.T - if u.heating: i = np.searchsorted(T, T_u - 1e-6) - else: i = np.searchsorted(T, T_u + 1e-6) - 1 + if u.heating: + i = np.searchsorted(T, T_u + 1e-6) - 1 + offset, ha, va = (-2, 2), 'right', 'bottom' + else: + i = np.searchsorted(T, T_u - 1e-6) + offset, ha, va = (2, -2), 'left', 'top' x = H[min(max(i, 0), H.size - 1)] - else: + else: # beside the middle of a sensible agent's line T_u = 0.5 * (u.T_utility_in + u.T_utility_out) x = np.interp(T_u, T, H) - ax.annotate(label, xy=(x, T_u - 273.15), - xytext=(-6 if u.heating else 6, 0), - textcoords='offset points', - ha='right' if u.heating else 'left', va='center', + offset = (-6, 0) if u.heating else (6, 0) + ha, va = 'right' if u.heating else 'left', 'center' + ax.annotate(label, xy=(x, T_u - 273.15), xytext=offset, + textcoords='offset points', ha=ha, va=va, color=hot_color if u.heating else cold_color, gid='Utility:' + u.ID, **text_kwargs) + ax.margins(y=0.1) # headroom for a label above the top flat ax.set_ylabel('T [°C]') else: shifted = kind == 'shifted' From 697c4568d6900b11d87aa94c234f7bb10f5ba361 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 00:48:03 +0000 Subject: [PATCH 08/52] address review 1 (f1-numerics): accept all-unspecified contributions at any T_min_app F1-1 (fixed): the planner's contribution validation is now one helper, _given_contributions, which checks the length, then every value, then, only when a value is given, that T_min_app is finite and > 0; an all-unspecified vector (None or nan) returns None. _approach_contributions and _approach_shifts use it, and _approach_shifts then returns the literal default shifts (no x/2 + x/2 round trip, so subnormal or non-finite T_min_app cannot differ from the default). problem_table and plan_network therefore accept [None]*N / [nan]*N at T_min_app = 0, as their docstrings, the spec and concepts.rst say, and as synthesize_network, the facility and PinchTargets already did. PinchTargets now uses the same helper, so an all-unspecified vector of the wrong length raises instead of being ignored. New test test_unspecified_contributions_need_no_positive_T_min_app (T_min_app 0 and the smallest subnormal: plan and table bit for bit equal to the default, shifts literal); length checks for all-unspecified vectors in test_invalid_contributions and test_targeting::test_contributions. F1-2 (fixed): concepts.rst no longer claims the '> 0' rule keeps the HXprocess dT guard positive; it says zero is excluded for a positive driving force and that a pair whose contributions sum to 1e-6 K or less still gets a guard of zero or less, as T_min_app <= 1e-6 K does by default. F1-3 (fixed): test_split_cases_with_contributions_reach_mer checks info['min_approach'] and info['min_approach_margin'] against the walked match ends (within 1e-9), covering split-mode reporting with non-uniform contributions. F1-4 (fixed): test_exact_check_and_repair_per_pair takes each exchanger's expected dT guard from its pair (hot stream j), not from whether it was flagged. F1-5 (fixed, partly): test_planner_targets_match_classic_cascade also runs pair_ends_ok on the 40 random problems, so the validation paragraph of concepts.rst holds as written. The optional pinch_Ts case (a stream moved to one side of its own pinch by its own shift) is not added: it needs a new hand-built facility case with a computed pinch, more than the nit buys. Tests: test_hxn_contributions.py + test_targeting.py 67 passed; test_hxn_planner.py 96 passed; doctests of hensmith/_planner.py and hensmith/targeting.py 8 passed; the new test fails on the previous code. Oracle: 29 cases, sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (unchanged). sphinx is not installed in this environment; the docs change is plain prose with inline literals only. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/concepts.rst | 15 +++++---- hensmith/_planner.py | 51 +++++++++++++++++++---------- hensmith/targeting.py | 12 +++---- tests/test_hxn_contributions.py | 58 +++++++++++++++++++++++++++++++-- tests/test_targeting.py | 11 +++++++ 5 files changed, 112 insertions(+), 35 deletions(-) diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 69fd553..ae8c06c 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -215,13 +215,14 @@ so that Two streams that meet on the shifted scale are therefore exactly their pair's required approach apart, and the problem table above is built on that scale with no other change. A contribution is None or nan (unspecified) or a finite -number greater than zero. Zero is excluded so that every pair's requirement -stays positive: a match allowed to touch would have no driving force at its -pinch and need an infinite area, and its ``HXprocess`` guard, the -requirement less 1e-6 K, would turn negative. Whenever a contribution is -given, ``T_min_app`` must be finite and positive as well, since it sets the -default ``T_min_app / 2``; without contributions ``T_min_app = 0`` is -accepted as before. +number greater than zero. Zero is excluded so that every pair keeps a +positive driving force: a match allowed to touch would have none at its pinch +and need an infinite area. (The ``HXprocess`` guard is the requirement less +1e-6 K, so a pair whose contributions sum to 1e-6 K or less still gets a +guard of zero or less, as ``T_min_app <= 1e-6`` K does by default.) Whenever +a contribution is given, ``T_min_app`` must be finite and positive as well, +since it sets the default ``T_min_app / 2``; without contributions, or with +every one unspecified, ``T_min_app = 0`` is accepted as before. **The default is unchanged, bit for bit.** An unspecified stream gets exactly the default shifts, ``T_min_app`` if hot and 0 if cold diff --git a/hensmith/_planner.py b/hensmith/_planner.py index 8fd3382..f00c86a 100644 --- a/hensmith/_planner.py +++ b/hensmith/_planner.py @@ -1882,42 +1882,57 @@ def _contribution_value(x, label): f'finite number > 0 K, or None, not {x!r}') -def _approach_contributions(N, T_min_app, contributions=None): +def _given_contributions(N, T_min_app, contributions): """ - The (N,) approach temperature contributions [K] of `N` streams: - `contributions` (one entry per stream, see `_contribution_value`) with - every unspecified one, or all of them if `contributions` is None, at - ``T_min_app / 2``. + The validated contributions of `N` streams (a list of `N` values, see + `_contribution_value`), or None if `contributions` is None or every + value is unspecified (the default path, for any `T_min_app`). The + length is checked first, then every value, then, only when a value is + given, that `T_min_app` is finite and > 0. """ - half = T_min_app / 2. - if contributions is None: return np.full(N, half) - if not (math.isfinite(T_min_app) and T_min_app > 0.): - raise ValueError('T_min_app must be finite and > 0 with approach ' - f'contributions, not {T_min_app!r}') + if contributions is None: return None contributions = list(contributions) if len(contributions) != N: raise ValueError(f'{len(contributions)} approach contributions given ' f'for {N} streams') values = [_contribution_value(x, f'stream {j}') for j, x in enumerate(contributions)] + if all(x is None for x in values): return None + if not (math.isfinite(T_min_app) and T_min_app > 0.): + raise ValueError('T_min_app must be finite and > 0 with approach ' + f'contributions, not {T_min_app!r}') + return values + + +def _approach_contributions(N, T_min_app, contributions=None): + """ + The (N,) approach temperature contributions [K] of `N` streams: + `contributions` (one entry per stream, see `_given_contributions`) with + every unspecified one, or all of them if `contributions` is None, at + ``T_min_app / 2``. + """ + half = T_min_app / 2. + values = _given_contributions(N, T_min_app, contributions) + if values is None: return np.full(N, half) return np.array([half if x is None else x for x in values], dtype=float) def _approach_shifts(is_hot, T_min_app, contributions=None): """ Each stream's shift onto the shifted scale, ``T* = T - shift``: - ``T_min_app`` for hot and 0 for cold streams without `contributions`, - else ``T_min_app/2 + c`` for hot and ``T_min_app/2 - c`` for cold - streams (`_approach_contributions`), so that a hot and a cold stream - that touch on the shifted scale are ``c_h + c_c`` apart. Unspecified - contributions give the default shifts bit for bit (``x/2 + x/2 == x``, - ``x/2 - x/2 == 0.``). + ``T_min_app`` for hot and 0 for cold streams without `contributions` + (or with every value unspecified), else ``T_min_app/2 + c`` for hot and + ``T_min_app/2 - c`` for cold streams (`_approach_contributions`), so + that a hot and a cold stream that touch on the shifted scale are ``c_h + + c_c`` apart. Unspecified contributions next to given ones keep the + default shifts bit for bit (``x/2 + x/2 == x``, ``x/2 - x/2 == 0.``). """ is_hot = np.asarray(is_hot, dtype=bool) - if contributions is None: + values = _given_contributions(is_hot.size, T_min_app, contributions) + if values is None: return np.where(is_hot, T_min_app, 0.) - c = _approach_contributions(is_hot.size, T_min_app, contributions) half = T_min_app / 2. + c = np.array([half if x is None else x for x in values], dtype=float) return np.where(is_hot, half + c, half - c) diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 397c234..47068e1 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -27,7 +27,7 @@ import numpy as np import biosteam as bst from ._curves import _T_EQ -from ._planner import _approach_contributions, _contribution_value +from ._planner import _approach_contributions, _given_contributions from .hxn_synthesis import (_problem_table, _pinch_cut, _format_H, _arrange_heat_utilities, _process_streams, _explicit_contributions, _contribution_key_level, _names_unit, _owner_chains, _unit_name) @@ -776,12 +776,10 @@ def __init__(self, streams_inlet, streams_quenched, is_hot, T_min_app=5., raise TypeError('approach_contributions must be a sequence ' 'with one value per stream, not ' f'{type(x).__name__!r}') - x = [_contribution_value(v, f'stream {j}') for j, v in enumerate(x)] - if all(v is None for v in x): - x = None - else: # checks T_min_app, then the length - _approach_contributions(N, T_min_app, x) - x = tuple(x) + # the length, each value, then T_min_app if a value is given; + # every value unspecified is the default path (None) + x = _given_contributions(N, T_min_app, x) + if x is not None: x = tuple(x) if labels is None: labels = [s.ID for s in streams_inlet] elif len(labels) != N: diff --git a/tests/test_hxn_contributions.py b/tests/test_hxn_contributions.py index f5ba516..fbbb8e8 100644 --- a/tests/test_hxn_contributions.py +++ b/tests/test_hxn_contributions.py @@ -279,6 +279,37 @@ def test_default_problem_table_bit_for_bit(name): inlets, outlets, is_hot, T, curves, approach_contributions=contributions)) == reference +@pytest.mark.parametrize('T', [0., math.ulp(0.)]) +def test_unspecified_contributions_need_no_positive_T_min_app(T): + # every value unspecified is the default path, which accepts any finite + # T_min_app (T_min_app = 0 included): same plan and table to the bit + streams = streams_from(LINNHOFF4) + N = len(streams) + is_hot_plan = [s['kind'] == 'hot' for s in streams] + default = P._plan_numeric(streams, T)['plan'] + reference = plan_record(default) + for contributions in ([None] * N, [NAN] * N, np.full(N, NAN)): + plan = P._plan_numeric(streams, T, + approach_contributions=contributions)['plan'] + assert plan_record(plan) == reference + assert plan.status == default.status + assert plan.info['shift'].tobytes() == np.where( + is_hot_plan, T, 0.).tobytes() + inlets, outlets, is_hot, _ = water_example() + table, curves, grid = hxn_synthesis._problem_table(inlets, outlets, is_hot, + T) + reference = table_record(table) + for contributions in ([None, None], [NAN, NAN], np.full(2, NAN)): + other, _, other_grid = hxn_synthesis._problem_table( + inlets, outlets, is_hot, T, curves, contributions) + assert table_record(other) == reference + for key in ('Ts', 'shift', 'Hl', 'Hr', 'k_hi', 'k_lo', + 'contributions'): + assert other_grid[key].tobytes() == grid[key].tobytes(), key + assert table_record(problem_table( + inlets, outlets, is_hot, T, curves, + approach_contributions=contributions)) == reference + def default_network_units(name): """(units, T_min_app, stream_splitting) of a facility test case.""" if name == 'r002': @@ -434,6 +465,8 @@ def test_planner_targets_match_classic_cascade(): Qh, Qc, zeros = classic_cascade(streams, c) assert_targets(net, Qh, Qc, streams) assert min(abs(net['plan'].pinch_T - (z - dT / 2)) for z in zeros) <= 1e-9 + pair_ends_ok(streams, {s['name']: cj for s, cj in zip(streams, c)}, + net) @pytest.mark.parametrize('name', ['kemp', 'smith2005_exr17_2_dT10']) def test_problem_table_matches_classic_cascade(name): @@ -516,9 +549,19 @@ def test_split_cases_with_contributions_reach_mer(name): Qh, Qc, _ = classic_cascade(streams, c) assert_targets(net, Qh, Qc, streams) # branch temperatures: the parent's at the parent-equivalent enthalpies - pair_ends_ok(streams, {s['name']: cj for s, cj in zip(streams, c)}, net) + cmap = {s['name']: cj for s, cj in zip(streams, c)} + pair_ends_ok(streams, cmap, net) assert net['plan'].splits - assert net['plan'].info['min_approach_margin'] >= -1e-9 + info = net['plan'].info + assert info['min_approach_margin'] >= -1e-9 + # split-mode reporting against the walked ends (linear profiles: the + # ends are each match's minimum) + ends = [min(m['T_hot_in'] - m['T_cold_out'], + m['T_hot_out'] - m['T_cold_in']) for m in net['matches']] + assert abs(info['min_approach'] - min(ends)) <= 1e-9 + margin = min(a - (cmap[m['hot']] + cmap[m['cold']]) + for a, m in zip(ends, net['matches'])) + assert abs(info['min_approach_margin'] - margin) <= 1e-9 def test_exact_check_and_repair_per_pair(): # hot stream 2 (on branches above the pinch) needs 5 K more against @@ -551,7 +594,8 @@ def test_exact_check_and_repair_per_pair(): is_hot, shift)[0] try: for n, hx in units.items(): - required = dT + 5. if n in bad else dT + # the pair sets the requirement: every exchanger of stream j + required = dT + 5. if plan.exchangers[n].hot == j else dT assert hx.dT == required - 1e-6, hx.ID assert internal_approach(hx) >= required - APPROACH_TOL, hx.ID finally: @@ -770,6 +814,14 @@ def test_invalid_contributions(monkeypatch): with pytest.raises(ValueError, match='T_min_app must be finite and > 0'): problem_table(inlets, outlets, is_hot, 0., approach_contributions=[1., 1.]) + # the length is checked even when every value is unspecified + with pytest.raises(ValueError, match=f'{N + 1} approach contributions ' + f'given for {N} streams'): + P._plan_numeric(streams, 0., approach_contributions=[None] * (N + 1)) + with pytest.raises(ValueError, match='3 approach contributions given ' + 'for 2 streams'): + problem_table(inlets, outlets, is_hot, 0., + approach_contributions=[None, NAN, None]) # nan is unspecified; numpy numbers are accepted default = plan_record(P._plan_numeric(streams, 10.)['plan']) for contributions in ([NAN] * N, np.full(N, NAN), np.full(N, 5.), diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 654205d..bfa4c94 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -291,6 +291,17 @@ def test_contributions(): assert none.approach_contributions is None assert none.table.residual.tobytes() == t.table.residual.tobytes() assert none.Q_hot.hex() == t.Q_hot.hex() + # ... at any T_min_app (only a given value needs T_min_app > 0), with + # the length checked all the same + t0 = targets_of(units, 0.) + none0 = PinchTargets(t.streams_inlet, t.streams_quenched, t.is_hot, 0., + [float('nan')] * N, curves=t.curves) + assert none0.approach_contributions is None + assert none0.Q_hot.hex() == t0.Q_hot.hex() + with pytest.raises(ValueError, match=f'{N + 1} approach contributions ' + f'given for {N} streams'): + PinchTargets(t.streams_inlet, t.streams_quenched, t.is_hot, 10., + [None] * (N + 1), curves=t.curves) assert targets_of(units, 10., approach_contributions={}).approach_contributions is None # KEMP with contributions against the classic cascade (hot T - c, cold T + c) tc = targets_of(units, 10., approach_contributions=KEMP_C) From f8791053a7637259d27ca49a3d5a7fa79c4c1413 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 00:56:49 +0000 Subject: [PATCH 09/52] address review 1 (f1-facility): stop targets() calling the contributions callable after a simulation F1F-1 (fixed): after a simulation, HeatExchangerNetwork.targets() called the approach_contributions callable on original_heat_utils, which replace_unit_heat_utilities had already overwritten (copy_like of the network's utility, or empty() for a stream the process exchangers serve). A duty-based callable gave other targets than the synthesis (15.5/55.5 kW instead of 6.5/46.5 kW on KEMP), an agent-based one raised AttributeError, and network_report contradicted itself. targets() now takes a callable's values from _resolved_contributions (the values the last simulation resolved, in original_heat_exchangers order) and never calls it again (F1 spec section 7). A Mapping, which depends on hu.unit only, is still resolved again, so a mapping changed after the run applies. The targets() Notes, the class docstring and concepts.rst say so. New test test_facility_targets_replaced_callable covers replace_unit_heat_utilities with a duty-based and an agent-based callable: targets() equal the synthesis targets bit for bit, the callable is not called, the report is consistent, a callable set since is ignored until the next simulation, and a mapping set since applies. Before the fix the test fails. F1F-5 (fixed): the HeatUtility-key TypeError no longer offers a sequence to facility users. A sequence is now named only for use outside a HeatExchangerNetwork. F1F-7 (fixed): the concepts.rst cache paragraph now says that T_min_app is never part of the reuse test, with or without contributions. A reused network keeps its unspecified streams at the T_min_app/2 it was synthesized with, as does synthesis_info['approach_contributions']. F1F-6 (not applied, optional nit): key resolution is quadratic only with about one key per unit (0.18 s per simulation at 400 keys and 400 units), which is small next to synthesis. Indexing keys by normalized name would also have to keep the error-reporting order and the str-key rule (top-level units only), and that is more risk than it buys here. Tests: pytest tests/test_targeting.py tests/test_hxn_contributions.py: 68 passed. Doctests of hensmith/_heat_exchanger_network.py and hensmith/hxn_synthesis.py: 5 passed. Default-off oracle: sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (unchanged). SPHINX_OFFLINE=1 sphinx-build -W -b html: clean. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/concepts.rst | 17 ++++++++++----- hensmith/_heat_exchanger_network.py | 34 +++++++++++++++++++++-------- hensmith/hxn_synthesis.py | 5 +++-- tests/test_targeting.py | 34 +++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 17 deletions(-) diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index ae8c06c..d173432 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -283,9 +283,12 @@ leaves a key holding an old auxiliary object to be matched by its owner's ID and its own. Heat utilities cannot be keys, because BioSTEAM replaces them at every simulation; a value per heat utility is given by a callable ``f(hu)`` instead, which is called once per heat utility at every simulation -and should therefore be pure. The keys and values are checked against every -unit available to the network and its auxiliary units before any heat -utility is filtered out: a key that names none of them, a bad value or two +and should therefore be pure (after a simulation, ``HXN.targets()`` takes the +values that simulation resolved instead of calling it again, since +``replace_unit_heat_utilities`` overwrites the heat utilities by then). The +keys and values are checked against every unit available to the network +and its auxiliary units before any heat utility is filtered out: a key that +names none of them, a bad value or two conflicting keys raise a ``ValueError`` even while their unit is idle, but a valid key whose unit holds no heat utility in a simulation (no duty, ``ignored``, ``hxn_ok=False``) has no effect, so that a Monte Carlo sample in @@ -293,9 +296,11 @@ which a unit falls idle does not fail. **The cache.** With ``cache_network=True``, a cached network is reused only while the explicit contributions resolve to the same value for every stream -(see *Reusing a network* below). Without explicit contributions the reuse -test is the one described there, so changing ``T_min_app`` alone still keeps -a cached network. +(see *Reusing a network* below). ``T_min_app`` is never part of the reuse +test, with or without explicit contributions, so changing it alone keeps a +cached network, and a reused network keeps the streams without a +contribution at the ``T_min_app / 2`` it was synthesized with (as does +``synthesis_info['approach_contributions']``). **Additive requirements only.** A requirement that belongs to a pair but is not the sum of two stream terms cannot be written as one shift per stream, diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index 84ef075..fcdc6b4 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -300,7 +300,9 @@ class HeatExchangerNetwork(bst.Facility): no effect, so that, e.g., Monte Carlo samples do not fail. A callable is called once per heat utility at every simulation (also to check a cached network, and again if a failed cache synthesizes anew), in the - order of the facility's heat utilities: it should be pure. + order of the facility's heat utilities: it should be pure. After a + simulation, `targets` takes the values that simulation resolved instead + of calling it again. ``synthesis_info`` reports the contributions as used by stream ('approach_contributions', in the order of `original_heat_exchangers`) and the smallest margin over each exchanger's required approach @@ -984,12 +986,16 @@ def targets(self, T_min_app=None): `original_heat_exchangers`, in the network's stream order, at the units' current states: right after a synthesis, the energy targets at the facility's `T_min_app` equal ``synthesis_info['Q_hot_target']`` - and ``synthesis_info['Q_cold_target']`` bit for bit. The approach - contributions are resolved against `original_heat_utils` (the - original heat utilities, kept even where - `replace_unit_heat_utilities` overwrote them). Before any - simulation, the streams are those of the heat utilities the next - simulation would take, prepared as the synthesis prepares them. + and ``synthesis_info['Q_cold_target']`` bit for bit. A mapping of + approach contributions is resolved again by the units of + `original_heat_utils`, so a mapping changed since applies; a + callable is not called again (with `replace_unit_heat_utilities`, the + heat utilities it would read hold the network's utilities by then): + its values are those the last simulation resolved, and a callable set + since takes effect at the next simulation. Before any simulation, + the streams are those of the heat utilities the next simulation + would take, prepared as the synthesis prepares them, and a callable + is called once per heat utility. Nothing is stored: with `cache_network`, the targets follow the units' current states, not those of the network's synthesis. @@ -1039,10 +1045,20 @@ def targets(self, T_min_app=None): hxs = self.original_heat_exchangers hus = self.original_heat_utils # in stream order cold = set(self.cold_indices) + x = self.approach_contributions + if x is not None and not isinstance(x, Mapping) and callable(x): + # Never called again: with replace_unit_heat_utilities, the + # heat utilities it would read now hold the network's + # utilities (or nothing, for a stream the process exchangers + # serve). Its values, as the last simulation resolved them, + # are in stream order. + contributions = getattr(self, '_resolved_contributions', None) + else: + # by unit only, which the network never changes + contributions = self._network_contributions(hus) return PinchTargets._from_exchangers( hxs, [i not in cold for i in range(len(hxs))], T_min_app, - self._network_contributions(hus), self.force_ideal_thermo, - None, hus) + contributions, self.force_ideal_thermo, None, hus) if self.system is None and not self.units: raise RuntimeError('simulate the system (or give the facility its ' 'units) before targeting') diff --git a/hensmith/hxn_synthesis.py b/hensmith/hxn_synthesis.py index 7e6266a..8b5452d 100644 --- a/hensmith/hxn_synthesis.py +++ b/hensmith/hxn_synthesis.py @@ -799,8 +799,9 @@ def _check_contribution_key(key): raise TypeError('a heat utility cannot be an approach_contributions ' 'key (biosteam replaces the heat utilities of a unit ' 'at every simulation): use its unit (hu.unit), the ' - "unit's ID or (owner.ID, unit.ID), a sequence, or a " - 'callable') + "unit's ID or (owner.ID, unit.ID), or a callable (or, " + 'outside a HeatExchangerNetwork, a sequence aligned ' + 'with the heat utilities)') if isinstance(key, (bst.Unit, str)): return if (isinstance(key, tuple) and len(key) == 2 and all(isinstance(i, str) for i in key)): return diff --git a/tests/test_targeting.py b/tests/test_targeting.py index bfa4c94..652bc79 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -963,6 +963,40 @@ def assert_synthesis_targets(t, HXN): with pytest.raises(RuntimeError, match='before targeting'): HeatExchangerNetwork('test_targeting_alone').targets() +def test_facility_targets_replaced_callable(): + # replace_unit_heat_utilities overwrites the heat utilities a callable + # reads (the network's utility, or none for H2 and C3, which the process + # exchangers serve): after a simulation the targets take the values that + # simulation resolved, without calling it again + # (H4 ends on chilled water) + cases = [(lambda hu: hu.duty < 0, (None, None, 2., 2.)), + (lambda hu: hu.agent.ID == 'cooling_water', (None, None, 2., None))] + for k, (read, expected) in enumerate(cases): + calls = [] + def f(hu): + calls.append(hu) + return 2. if read(hu) else None + units = cp_units(f'test_targeting_replaced_callable_{k}', KEMP, K) + sys, HXN = simulate_HXN(units, 10., replace_unit_heat_utilities=True, + approach_contributions=f) + assert sorted(u.ID for u in units if not u.heat_utilities[0].duty) == ['C3', 'H2'] + info = HXN.synthesis_info + assert info['status'] == 'mer' + N_calls = len(calls) + t = HXN.targets() + assert len(calls) == N_calls + assert t.approach_contributions == HXN._resolved_contributions == expected + assert (t.Q_hot, t.Q_cold) == (info['Q_hot_target'], info['Q_cold_target']) + assert HXN.targets(20.).Q_hot == t.at(20.).Q_hot + report = network_report(HXN) + assert report['targets']['Q_hot'] == report['Q_hot'] == info['Q_hot_target'] + # a callable set since applies from the next simulation; a mapping + # (resolved by unit) at once + HXN.approach_contributions = lambda hu: 3. + assert HXN.targets().approach_contributions == (None, None, 2., None) + HXN.approach_contributions = {'H2': 3.} + assert HXN.targets().approach_contributions == (None, None, 3., None) + #: Keys of `network_report`. REPORT_KEYS = ['T_min_app', 'targets_T_min_app', 'status', 'applied', 'Q_hot', 'Q_cold', 'utilities', 'utility_cost', 'N_units', 'N_process_units', 'N_utility_units', From 7c95ff9a8320589966c45254f4ccab5b37f62b54 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 01:27:58 +0000 Subject: [PATCH 10/52] address review 1 (tg-core): keep sensible utility lines inside the gcc TGC-1 (fixed): a sensible agent was priced by biosteam from the warm end of its stretch alone, so cooling water serving a condenser near the cold end of its stretch left above the condensing temperature (impossible flow, utility cost about 3x too low, balanced composites crossing, area and TAC inf). utility_targets now bounds each sensible non-fuel agent's outlet by its line against the process: at each position p of its stretch the agent takes at least x = q - B(p) below p (gives it above p, heating), so its outlet is at most agent.T + min_p (T(p) - dT - agent.T) q / x (at least agent.T - min_p (agent.T - dT - T(p)) q / x), T(p) the lowest real temperature of the served hot streams present at p (highest of the cold ones). biosteam is called with the inlet that reproduces the bound, so its own rule still applies. Exact without approach contributions (the line stays inside the grand composite curve), conservative with them. Reference A, test 9 and every documented number except one are unchanged; the vacuum condenser at 316 K now costs 1.98 USD/hr (the series-flow minimum, outlet 311.15 K) with a finite area. The Bath warning says "non-positive driving force"; concepts.rst, the class Notes, the UtilityTarget fields and tutorial ch05 describe the rule. New tests: test_sensible_line_condenser (316 K binds, 330 K keeps the limit; finite area at 2, 10, 20 K) and test_sensible_line_constant_cp (closed forms for cooling water and for a hot oil against a concave cold composite). TGC-2 (fixed): T_in/T_out are now the real temperatures of the served streams (cold streams with heat above the pinch, hot streams with heat below it) overlapping the stretch: coldest and hottest for heating, hottest and coldest for cooling. T_out is within the agent's limit by construction (10 of 24 random contribution cases violated it before, 0 now), the uniform case gives the stretch's ends where streams reach them (440 K, not 445 K, for medium pressure steam on H1 455->300, C1 330->440). I used the coldest cold-end temperature for heating rather than the suggested max over the streams' bottoms: the max would raise uniform fuel costs whenever a cold stream starts inside the furnace's stretch. With contributions the fuel flue is therefore priced from the coldest served stream (lower than the old largest-shift offset). Latent costs are unchanged. New test: test_pricing_temperatures. TGC-4 (nit, fixed): _isolated_pricing restores biosteam's flowsheet registry, so no '_hensmith_targets' flowsheet lingers (asserted in test_pricing_is_isolated). TGC-5 (nit, fixed): test_composites_consistent_with_table also checks the utility targets of the real-thermo cases: duties add up to Q_hot/Q_cold, the balanced GCC is non-negative and zero at the pinch, the balanced curves end at the same heat, the area is finite, with no RuntimeWarning. Docs: ch05 regenerated alone; only ch05_sweep.txt (15 K: area 5168 -> 5158 m2, TAC 15690339 -> 15694680 USD/yr, cooling water bound by a cold stream starting at 298.15 K) and tutorial_05_supertargets.png changed. Tests: pytest tests/test_targeting.py hensmith/targeting.py: 38 passed. On the previous code test_sensible_line_condenser[316], test_sensible_line_constant_cp, test_pricing_temperatures and test_pricing_is_isolated fail. Oracle unchanged (sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf). Sphinx -W (offline) clean. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/_generated/ch05_sweep.txt | 2 +- .../examples/tutorial_05_supertargets.png | Bin 72674 -> 72805 bytes docs/source/concepts.rst | 36 +++- docs/source/tutorial/05_targeting.rst | 10 +- hensmith/targeting.py | 178 +++++++++++++----- tests/test_targeting.py | 138 +++++++++++++- 6 files changed, 305 insertions(+), 59 deletions(-) diff --git a/docs/source/_generated/ch05_sweep.txt b/docs/source/_generated/ch05_sweep.txt index 78d7e21..f956f81 100644 --- a/docs/source/_generated/ch05_sweep.txt +++ b/docs/source/_generated/ch05_sweep.txt @@ -6,7 +6,7 @@ T_min_app Q_hot Q_cold area units installed capital operating 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 5168 9 2.272e+06 3.698e+05 1.532e+07 15690339 +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 diff --git a/docs/source/_static/images/examples/tutorial_05_supertargets.png b/docs/source/_static/images/examples/tutorial_05_supertargets.png index 69030d4727bb7eae37a5e6b065439c3a03452d69..375d406692c9787c5d79e571ba41e9ccefc2d8c9 100644 GIT binary patch literal 72805 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 literal 72674 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 diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index d173432..8626ebe 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -393,13 +393,31 @@ two differ only at exact equality. utility over the stretch it serves: from its level to the next zero of the **balanced grand composite curve** (the cascade net of the utilities), which is the pinch or a *utility pinch* where a lower agent is used to its limit. -The process temperatures of that stretch are its shifted temperatures plus the -largest shift of the cold streams in it (heating) or the smallest shift of the -hot streams (cooling) -- exactly the streams' own temperatures without -approach contributions, an approximation with them. A sensible agent's outlet -therefore follows the temperatures it actually serves. BioSTEAM divides each -duty by the agent's heat transfer efficiency, so a target's ``utility_duty`` -is utility side while its ``duty``, like the MER targets, is process side. +A target's ``T_in`` and ``T_out`` are the real temperatures of the streams it +serves in that stretch: the coldest and the hottest for heating, the hottest +and the coldest for cooling. Without approach contributions they are the +stretch's ends where streams reach them, and in every case ``T_out`` is within +the agent's limit. BioSTEAM prices a latent agent and fuel from them. + +A **sensible** agent, such as cooling water, warms (or cools) along a line as +it takes its duty, and BioSTEAM checks that line only at the process inlet: a +coolant leaves at ``T_in - dT`` or at its limit, whichever is lower. Inside +the stretch the line must keep ``dT`` from the process too. Cooling water that +serves a condenser at the cold end of its stretch, say, has taken most of its +duty by the time it passes the condensing temperature, so it must leave well +below ``T_in - dT``. At each point of the stretch the agent has taken at least +the duty less the balanced heat flowing there (on the grand composite curve: +its line stays inside the curve, as a non-isothermal utility's does in pinch +analysis), and that bounds its outlet. hensmith prices the agent at the +highest outlet the bound allows (the lowest, for heating), by giving BioSTEAM +the matching inlet. Its flow, and therefore its cost, is then the least a +single stream of the agent needs. With approach contributions the bound takes, +at each point, the lowest real temperature of the hot streams there (the +highest of the cold ones), which is conservative. + +BioSTEAM divides each duty by the agent's heat transfer efficiency, so a +target's ``utility_duty`` is utility side while its ``duty``, like the MER +targets, is process side. Each pricing uses fresh utility streams in a temporary flowsheet: the costs are deterministic, and the user's flowsheet, the agents and their later costs are untouched. @@ -442,8 +460,8 @@ inside an interval, so the sum is exact. hensmith takes :math:`h_j` = 1 kW/m²/K for every stream and utility by default, which gives any pair BioSTEAM's heuristic overall coefficient of 0.5 kW/m²/K (condensing against boiling, where BioSTEAM takes 1.0, is not reproduced); ``h=`` and -``h_utilities=`` set others. A zero driving force anywhere makes the area -infinite, with a ``RuntimeWarning``, as at ``T_min_app = 0``. +``h_utilities=`` set others. A zero or negative driving force anywhere +makes the area infinite, with a ``RuntimeWarning``, as at ``T_min_app = 0``. **Units.** A network of :math:`S` streams and utilities that forms one connected component needs at least :math:`S - 1` units (Euler's rule). The diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index 895caf5..37fb5c4 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -96,10 +96,12 @@ 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 warm end of the process temperatures it serves, 303.15 K: -biosteam prices a sensible agent from that end, so its outlet follows the -temperatures it actually serves. The utility cost target, 1839.61 USD/hr, is -the cost of the MER utilities with biosteam's agents placed this way. +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 diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 47068e1..4e0b586 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -60,8 +60,8 @@ #: Default minimum approach temperatures of `supertarget` [K]. _T_MIN_APPS = np.arange(1., 41.) -#: A vertical temperature difference at or below this is a zero driving -#: force [K]. +#: A vertical temperature difference at or below this is no driving force +#: [K]. _DT_ZERO = 1e-9 #: The options of `PinchTargets.summary` (and `supertarget`). @@ -102,8 +102,11 @@ cost : float Cost of the utility [USD/hr], from biosteam; nan when unserved. T_in, T_out : float - Process temperatures at which the utility was priced [K]: the inlet and - outlet of the stretch of the process it serves. + Process temperatures of the stretch it serves [K]: the real temperatures + of the streams it serves there, the coldest and the hottest (heating) or + the hottest and the coldest (cooling). biosteam prices the utility from + them, a sensible agent from the inlet that keeps its line ``dT`` from + the process (see Notes of `PinchTargets`). T_utility_in, T_utility_out : float Inlet and outlet temperature of the agent [K], from the priced `HeatUtility`; nan when unserved. @@ -264,15 +267,22 @@ def _isolated_pricing(agents): `utility_stream_dump` is replaced by a new empty list (so every pricing gets fresh utility streams, and the costs do not depend on what was priced before) and new streams are registered in a temporary flowsheet; - the original dumps are restored on exit. + the original dumps, and biosteam's registry of flowsheets, are restored + on exit. """ dumps = {id(a): (a, a.utility_stream_dump) for a in agents} + flowsheets = bst.Flowsheet.flowsheet.__dict__ + ID = '_hensmith_targets' + missing = object() + previous = flowsheets.get(ID, missing) try: for a, _ in dumps.values(): a.utility_stream_dump = [] - with bst.Flowsheet('_hensmith_targets'): + with bst.Flowsheet(ID): yield finally: for a, dump in dumps.values(): a.utility_stream_dump = dump + if previous is missing: flowsheets.pop(ID, None) + else: flowsheets[ID] = previous def _unit_label(unit): """``unit.ID``, or ``' - '`` for an auxiliary unit.""" @@ -289,6 +299,11 @@ def _utility_member(u): return (np.array([T_a, T_b]), np.array([0., u.duty]), np.array([0., u.duty])) +def _sensible(agent): + """Whether biosteam prices `agent` by its sensible heat: a non-fuel + agent with a temperature limit.""" + return agent is not None and not agent.isfuel and bool(agent.T_limit) + def _euler(regions): """Euler's S - 1 per connected component of the members of `regions` (sets; members of one region are connected).""" @@ -365,7 +380,7 @@ def _bath(hot, cold): exact on the stretches between the breakpoints of both curves, where both and every member are linear. A hot stretch at infinite temperature (fuel) takes no area. inf with a RuntimeWarning at a driving force of - `_DT_ZERO` or less. + `_DT_ZERO` or less (a zero or negative one). """ if not (hot[0].size and cold[0].size): return 0. end = min(hot[1][-1], cold[1][-1]) @@ -385,8 +400,8 @@ def _bath(hot, cold): zero = np.flatnonzero(np.minimum(d1, d2) <= _DT_ZERO) if zero.size: k = zero[0] - warn('vertical heat transfer at zero driving force between H = ' - f'{x[k]:.6g} and {y[k]:.6g} kJ/hr', RuntimeWarning) + warn('vertical heat transfer at a non-positive driving force between ' + f'H = {x[k]:.6g} and {y[k]:.6g} kJ/hr', RuntimeWarning) return math.inf with np.errstate(divide='ignore', invalid='ignore'): LMTD = np.where(np.abs(d1 - d2) <= 1e-12 * np.maximum(d1, d2), d1, @@ -608,15 +623,29 @@ class PinchTargets: Each agent is priced as one biosteam heat utility over the stretch it serves, from its level to the next zero of the balanced curve (the - pinch, or a utility pinch), at real temperatures: shifted ones plus the - largest shift of the cold streams there (heating) or the smallest of the - hot streams (cooling), exactly the streams' own temperatures without - approach contributions and an approximation with them. So a sensible - agent's outlet follows the temperatures it actually serves, and biosteam - divides the duty by the agent's heat transfer efficiency. Pricing uses - fresh utility streams in a temporary flowsheet: costs are - deterministic, and the flowsheet's registry and the agents' stream dumps - are untouched. + pinch, or a utility pinch). Its `T_in` and `T_out` are the real + temperatures of the streams it serves there (the cold streams with heat + above the pinch, or the hot streams with heat below it): the coldest and + the hottest for heating, the hottest and the coldest for cooling. They + are the stretch's ends without approach contributions (where a stream + reaches them), and `T_out` is within the agent's limit. biosteam prices + a latent agent and fuel from them (the flue leaves at least ``dT`` + above `T_in`), but checks a sensible agent's outlet only against + `T_in`, while its line must keep ``dT`` from the process over the whole + stretch (cooling water serving a condenser at the cold end of its + stretch must leave ``dT`` below the condensing temperature). At each + position p of the stretch, the agent takes at least ``x = q - B(p)`` of + its duty q below p (gives it above p, for heating), B being the + balanced heat flow at p. Its outlet is therefore at most + ``agent.T + min_p (T(p) - dT - agent.T) * q / x`` for cooling and at + least ``agent.T - min_p (agent.T - dT - T(p)) * q / x`` for heating, + T(p) being the lowest real temperature of the served hot streams at p + (the highest of the cold ones): its line stays inside the grand + composite curve, as a non-isothermal utility's does in pinch analysis. + biosteam is given the inlet that gives this outlet, and divides the + duty by the agent's heat transfer efficiency. Pricing uses fresh + utility streams in a temporary flowsheet: costs are deterministic, and + the flowsheet's registry and the agents' stream dumps are untouched. *Balanced composite curves.* `balanced_composite_curves` adds the utilities to the real composites (heating ones to the hot composite, @@ -637,7 +666,8 @@ class PinchTargets: biosteam takes 1, is not reproduced). The sum is exact on the piecewise-linear curves, a stream's share of a stretch being linear in the heat. Fuel takes no area, and the area is inf (with a - RuntimeWarning) at a zero driving force, as at ``T_min_app = 0``. + RuntimeWarning) at a zero or negative driving force, as at + ``T_min_app = 0``. *Units target.* `units_target` applies Euler's N = S - 1 to each region of the balanced grand composite curve between its zeros (the process @@ -1048,17 +1078,59 @@ def _allocate(self, levels, Q, heating): remaining -= q return allocated, remaining - def _offset(self, lo, hi, heating): - """The pricing offset of a utility serving shifted [lo, hi]: the - largest shift of the cold streams overlapping it (heating; 0 if - none) or the smallest of the hot ones (cooling; `T_min_app`).""" + def _served(self, heating): + """``(top, bottom, shift)`` of the streams a heating agent can serve + (the cold streams with heat above the pinch) or a cooling one (the + hot streams with heat below it): shifted temperatures [K] and shifts + [K].""" grid = self.grid + above, below = self._side_heat() + if heating: j = np.flatnonzero(~self.is_hot & (above > self._tol)) + else: j = np.flatnonzero(self.is_hot & (below > self._tol)) Ts = grid['Ts'] - overlap = (Ts[grid['k_hi']] > lo) & (Ts[grid['k_lo']] < hi) - overlap &= ~self.is_hot if heating else self.is_hot - if not overlap.any(): return 0. if heating else self.T_min_app - s = self.shift[overlap] - return float(s.max() if heating else s.min()) + return Ts[grid['k_hi'][j]], Ts[grid['k_lo'][j]], self.shift[j] + + def _ends(self, served, lo, hi, heating): + """ + ``((T_in, T_out), overlap)`` of a utility serving shifted [lo, hi]: + the real temperatures [K] of the `served` streams overlapping the + stretch (`overlap`, a mask), the coldest and the hottest (heating; + the hottest and the coldest for cooling); the stretch's ends plus 0 + (heating) or `T_min_app` (cooling) if none overlaps it. + """ + top, bottom, s = served + overlap = (top > lo) & (bottom < hi) + if not overlap.any(): + if heating: return (lo, hi), overlap + return (hi + self.T_min_app, lo + self.T_min_app), overlap + s = s[overlap] + cold = float(np.min(np.maximum(bottom[overlap], lo) + s)) + hot = float(np.max(np.minimum(top[overlap], hi) + s)) + return ((cold, hot) if heating else (hot, cold)), overlap + + def _line_limit(self, agent, heating, q, T, B, served): + """ + The limit [K] on the outlet of the sensible `agent` serving `q` from + the positions of its stretch (shifted temperatures `T` and balanced + heat flow `B`): the lowest outlet (heating; the highest for cooling) + at which its line, linear in its heat, stays `dT` from the `served` + streams present at each position (see Notes of the class); None if + no position bounds it. + """ + top, bottom, s = served + # the heat the agent has at least given above each position (heating) + # or taken below it (cooling): the rest of the heat flowing there + x = q - B + present = (bottom - _T_EQ <= T[:, None]) & (T[:, None] <= top + _T_EQ) + bound = (x > self._tol) & present.any(axis=1) + if not bound.any(): return None + dT = bst.HeatUtility.dT + x, T, present = x[bound], T[bound], present[bound] + if heating: + T_process = T + np.where(present, s, -math.inf).max(axis=1) + return agent.T - float(np.min((agent.T - dT - T_process) * q / x)) + T_process = T + np.where(present, s, math.inf).min(axis=1) + return agent.T + float(np.min((T_process - dT - agent.T) * q / x)) def utility_targets(self, heating_agents=None, cooling_agents=None): """ @@ -1113,40 +1185,57 @@ def utility_targets(self, heating_agents=None, cooling_agents=None): Ts, arr, lea = self._gcc() positions = _positions(Ts, arr, lea, [l for l, _, _ in heat + cool if math.isfinite(l)]) - zero = _balance(positions, [(l, q) for l, q, _ in heat], - [(l, q) for l, q, _ in cool]) <= tol + B = _balance(positions, [(l, q) for l, q, _ in heat], + [(l, q) for l, q, _ in cool]) + zero = B <= tol T = positions[0] + dT = bst.HeatUtility.dT utilities = [] with _isolated_pricing([a for *_, a in heat + cool if a is not None]): + served = self._served(True) for level, q, agent in heat: g = _gap(positions, level) z = np.flatnonzero(zero[g:]) - lo = float(T[g + z[0]]) if z.size else self.pinch_T + end = g + z[0] + 1 if z.size else T.size + lo = float(T[end - 1]) if z.size else self.pinch_T hi = min(level, float(Ts[0])) - o = self._offset(lo, hi, True) - utilities.append(self._utility(agent, True, level, q, - lo + o, hi + o)) + (T_in, T_out), overlap = self._ends(served, lo, hi, True) + T_priced = T_in + if _sensible(agent): + limit = self._line_limit(agent, True, q, T[g:end], B[g:end], + [x[overlap] for x in served]) + if limit is not None: T_priced = max(T_in, limit - dT) + utilities.append(self._utility(agent, True, level, q, T_in, + T_out, T_priced)) + served = self._served(False) for level, q, agent in cool: g = _gap(positions, level) z = np.flatnonzero(zero[:g]) - hi = float(T[z[-1]]) if z.size else self.pinch_T + start = int(z[-1]) if z.size else 0 + hi = float(T[start]) if z.size else self.pinch_T lo = max(level, float(Ts[-1])) - o = self._offset(lo, hi, False) - utilities.append(self._utility(agent, False, level, q, - hi + o, lo + o)) + (T_in, T_out), overlap = self._ends(served, lo, hi, False) + T_priced = T_in + if _sensible(agent): + limit = self._line_limit(agent, False, q, T[start:g], + B[start:g], + [x[overlap] for x in served]) + if limit is not None: T_priced = min(T_in, limit + dT) + utilities.append(self._utility(agent, False, level, q, T_in, + T_out, T_priced)) return utilities @staticmethod - def _utility(agent, heating, level, q, T_in, T_out): + def _utility(agent, heating, level, q, T_in, T_out, T_priced): """The `UtilityTarget` of `agent` serving `q` from `T_in` to `T_out` - (process temperatures), priced as one biosteam heat utility (inside - `_isolated_pricing`).""" + (process temperatures), priced as one biosteam heat utility from + `T_priced` to `T_out` (inside `_isolated_pricing`).""" if agent is None: nan = math.nan return UtilityTarget(None, None, heating, level, q, nan, nan, nan, T_in, T_out, nan, nan) hu = bst.HeatUtility() - hu(q if heating else -q, T_in, T_out, agent=agent) + hu(q if heating else -q, T_priced, T_out, agent=agent) return UtilityTarget(agent.ID, agent, heating, level, q, abs(hu.duty), abs(hu.flow), hu.cost, T_in, T_out, hu.inlet_utility_stream.T, @@ -1358,8 +1447,9 @@ def area_target(self, utilities=None, h=None, h_utilities=None): Returns ------- float - The area [m²]: inf (with a RuntimeWarning) at a zero driving - force, nan if part of a utility target is unserved. + The area [m²]: inf (with a RuntimeWarning) at a zero or + negative driving force, nan if part of a utility target is + unserved. """ if utilities is None: utilities = self.utility_targets() diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 652bc79..0a9542c 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -279,6 +279,22 @@ def test_composites_consistent_with_table(name): assert np.all(np.diff(T) <= 0) assert abs(H.min()) <= t._tol assert_allclose([H[0], H[-1]], [t.Q_hot, t.Q_cold], rtol=1e-12, atol=t._tol) + # the utility targets add up to the MER targets, the balanced grand + # composite curve is non-negative and zero at the pinch, the balanced + # composite curves end at the same heat, and the area is finite + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter('always') + utilities = t.utility_targets() + area = t.area_target(utilities) + assert not [w for w in caught if issubclass(w.category, RuntimeWarning)] + assert_allclose([sum(u.duty for u in utilities if u.heating), + sum(u.duty for u in utilities if not u.heating)], + [t.Q_hot, t.Q_cold], rtol=1e-9, atol=t._tol) + T, B = t.balanced_grand_composite_curve(utilities) + assert B.min() >= -t._tol and B[T == t.pinch_T].min() <= t._tol + hot_T, hot_H, cold_T, cold_H = t.balanced_composite_curves(utilities) + assert_allclose(hot_H[-1], cold_H[-1], rtol=1e-9) + assert 0. < area < math.inf def test_contributions(): assert 'approach_contributions' in inspect.signature(_problem_table).parameters @@ -547,10 +563,130 @@ def test_pricing_is_isolated(): assert fuel.cost == pytest.approx(fresh_price(fuel.duty, fuel.T_in, fuel.T_out, gas)[0], rel=1e-12) assert registered() == registry + assert '_hensmith_targets' not in bst.Flowsheet.flowsheet.__dict__ for a, (dump, items) in zip(agents, dumps): assert a.utility_stream_dump is dump and a.utility_stream_dump == items assert fresh_price(10 * kW, 450., 500., gas)[0] == before +def vacuum_condenser(name, T_sat): + """A vacuum condenser and a liquid cooler of water: 40 kmol/hr of vapour + condensed at `T_sat` [K] (from 0.5 K above it) and 20 kmol/hr of liquid + cooled from 345 K to `T_sat`.""" + test_hxn_targets.setup(name, ['Water']) + P = bst.settings.chemicals.Water.Psat(T_sat) + vapour = bst.Stream(name + '_vapour', Water=40., T=T_sat + .5, P=P, phase='g') + condenser = bst.HXutility(name + '_condenser', ins=vapour, V=0., rigorous=True) + liquid = bst.Stream(name + '_liquid', Water=20., T=345., P=3e5) + cooler = bst.HXutility(name + '_cooler', ins=liquid, T=T_sat, rigorous=False) + condenser.simulate() + cooler.simulate() + return [condenser, cooler] + +@pytest.mark.parametrize('T_sat', [316., 330.]) +def test_sensible_line_condenser(T_sat): + # cooling water serves a condenser at the cold end of its stretch: + # priced from the warm end alone (biosteam's rule) it would leave at its + # limit, 324.817 K, passing the condensing temperature with most of its + # duty taken (the balanced composites would cross) + t = targets_of(vacuum_condenser(f'test_targeting_vacuum_{T_sat:.0f}', T_sat), 10.) + dT, CW = bst.HeatUtility.dT, agent('cooling_water') + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter('always') + utilities = t.utility_targets() + area = t.area_target(utilities) + assert not [w for w in caught if issubclass(w.category, RuntimeWarning)] + (u,) = utilities + assert u.ID == 'cooling_water' and u.duty == pytest.approx(t.Q_cold, rel=1e-12) + # the highest outlet of a line that has taken H when it passes the knot + # (T, H) of the hot composite at T - dT or below: the series minimum flow + hot_T, hot_H, _, _ = t.composite_curves() + knot = hot_H > 0. + bound = CW.T + np.min((hot_T[knot] - dT - CW.T) * hot_H[-1] / hot_H[knot]) + T_out = min(CW.T_limit, bound) + assert u.T_utility_out == pytest.approx(T_out, rel=1e-9) + priced = fresh_price(-u.duty, min(u.T_in, bound + dT), u.T_out, CW)[0] + assert u.cost == pytest.approx(priced, rel=1e-9) + warm_end = fresh_price(-u.duty, u.T_in, u.T_out, CW)[0] + if T_sat == 316.: + assert T_out < T_sat - dT + 1. and u.cost > 3 * warm_end + else: + assert u.T_utility_out == CW.T_limit and u.cost == warm_end + # the line keeps dT below the hot composite, so the area is finite, at + # any T_min_app + curves = t.balanced_composite_curves(utilities) + assert min_vertical_gap(*curves) >= dT - 1e-6 + assert 0. < area < math.inf + for T_min_app in (2., 20.): + assert 0. < t.at(T_min_app).area_target() < math.inf + +def test_sensible_line_constant_cp(): + dT = bst.HeatUtility.dT + # cooling water: 40 of its 64 kW are below 316 K + t = targets('test_targeting_line_water', [('H1', 340., 312.372, 1.), + ('H2', 316., 312.372, 10.)], 10.) + CW = agent('cooling_water') + (u,) = t.utility_targets() + q = 340. - 312.372 + 10. * (316. - 312.372) + assert u.duty / kW == pytest.approx(q, rel=1e-9) + T_out = CW.T + (316. - dT - CW.T) * q / (q - (340. - 316.)) + assert u.T_utility_out == pytest.approx(T_out, rel=1e-9) and T_out < 315. + assert u.cost == pytest.approx(fresh_price(-u.duty, T_out + dT, u.T_out, CW)[0], + rel=1e-9) + curves = t.balanced_composite_curves([u]) + assert min_vertical_gap(*curves) == pytest.approx(dT, abs=1e-6) + assert 0. < t.area_target([u]) < math.inf + # a sensible heating agent against a concave cold composite: 110 of the + # 180 kW of a hot oil (480 -> 420 K at most) are above 460 K + t = targets('test_targeting_line_oil', [('C1', 390., 470., 1.), + ('C2', 460., 470., 10.)], 10.) + oil = bst.UtilityAgent('test_targeting_hot_oil', Water=1, T=480., P=5e6, + phase='l', thermo=bst.HeatUtility.thermo_water, + T_limit=420., heat_transfer_price=1e-6) + (u,) = t.utility_targets([oil], []) + assert (u.ID, u.T_in, u.T_out) == ('test_targeting_hot_oil', 390., 470.) + T_out = oil.T - (oil.T - dT - 460.) * 180. / 110. + assert u.T_utility_out == pytest.approx(T_out, rel=1e-9) and T_out > 455. + cost, utility_duty, priced = fresh_price(u.duty, T_out - dT, u.T_out, oil) + assert (u.cost, u.utility_duty) == (pytest.approx(cost, rel=1e-12), + pytest.approx(utility_duty, rel=1e-12)) + assert priced == pytest.approx(T_out, rel=1e-12) + curves = t.balanced_composite_curves([u]) + assert min_vertical_gap(*curves) == pytest.approx(dT, abs=1e-6) + assert 0. < t.area_target([u]) < math.inf + +def test_pricing_temperatures(): + dT = bst.HeatUtility.dT + # the hot stream's top, 445 K shifted, is above every cold stream: medium + # pressure steam serves C1 up to its outlet, 440 K + t = targets('test_targeting_ends', [('H1', 455., 300., 1.), + ('C1', 330., 440., 3.)], 10.) + MP = by_ID(t.utility_targets())['medium_pressure_steam'] + assert (MP.T_in, MP.T_out) == (pytest.approx(407.189, abs=1e-9), 440.) + # with approach contributions, the temperatures of the streams served: + # the outlet within the agent's limit, as biosteam requires + rng = np.random.default_rng(11) + for n in range(24): + rows = [] + for i in range(int(rng.integers(2, 6))): + a, b = rng.uniform(290., 520., 2).round(1) + if abs(a - b) < 2.: b = a + 5. + rows.append((('H' if a > b else 'C') + str(i), float(a), float(b), + round(float(rng.uniform(.1, 5.)), 2))) + contributions = {ID: round(float(rng.uniform(.5, 15.)), 1) + for ID, *_ in rows if rng.random() < .6} + t = targets(f'test_targeting_ends_{n}', rows, + float(rng.choice([1., 5., 10., 20.])), + approach_contributions=contributions or None) + for u in t.utility_targets(): + a = u.agent + if a is None or a.isfuel: continue + if u.heating: + assert u.T_in <= u.T_out and u.T_out + dT <= a.T - a.dT + 1e-6 + if a.T_limit: assert u.T_utility_out >= u.T_in + dT - 1e-6 + else: + assert u.T_in >= u.T_out and u.T_out - dT >= a.T + a.dT - 1e-6 + if a.T_limit: assert u.T_utility_out <= u.T_in - dT + 1e-6 + # %% Area, units, capital and total annualized cost def test_bath_balanced_pair(quickstart): @@ -757,7 +893,7 @@ def test_capital_target_formula(): with warnings.catch_warnings(record=True) as caught: warnings.simplefilter('always') zero = t.at(0.).summary() - assert any('zero driving force' in str(w.message) for w in caught) + assert any('non-positive driving force' in str(w.message) for w in caught) assert zero['area'] == zero['installed_cost'] == zero['TAC'] == math.inf assert zero['Q_hot'] < t.Q_hot with pytest.raises(ValueError, match='heat_exchanger_type must be one of'): From 9e9ae322b8dee9071754a642ac88153253c86e86 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 01:45:12 +0000 Subject: [PATCH 11/52] address review 1 (tg-super): validate P, warn on a sweep with no finite tac, test sensible lines across process streams TGS-1 (fixed): the root cause, a sensible agent priced from the end of its stretch alone so that its line crossed the grand composite curve, was already removed by the line limit of 48f0447 (tg-core TGC-1): each sensible agent's outlet is bounded by the duty it must have taken at each position of its stretch. On the reported two-stream problem (H1 518.9 -> 272.3 K, 4.99 kW/K; C1 297 -> 450.3 K, 4.85 kW/K) chilled water now leaves at 293.48 K at 1 K, and the area is finite: 162.2, 128.9, 115.1 and 102.9 m2 at 1, 2, 3 and 5 K (was inf, 190.7, 143.3 and 113.6). The balanced curves' smallest driving force is at least min(T_min_app, dT) there, on the cooling water cases of the verifier and on 720 random 2-6 stream evaluations from 0.5 to 20 K. What was missing is the regression test: the new test_sensible_line_process_in_stretch sweeps 1-10 K on that problem and on its heating mirror (a custom hot oil serving C1 290 -> 470 K across H1 up to 450 K): no RuntimeWarning, a finite area that falls with T_min_app, the smallest driving force at least min(T_min_app, dT), and the agent's outlet and cost against closed forms (where the opposite process stream starts). Both cases give inf on df45d85. TGS-3 (fixed): P is validated by a new _pressure (a finite real number, not a bool, > 0; a ValueError naming P) in _check_options, so supertarget, summary and network_report fail before pricing anything, and in capital_cost_target and _exchanger_costs. P=nan no longer yields a nan optimum, P=-1e6 no longer costs less, P=True and P='2 bar' are rejected with a message naming P. The capital_cost_target docstring says P > 0. Cases added to the test_sweep validation loop and to test_capital_target_formula (capital_cost_target, summary and _exchanger_costs). TGS-4 (nit, fixed): a new _lmtd, used by _bath and _profile_area, takes the log of the ratio as log1p((d1 - d2) / d2) for a ratio between 0.5 and 1.5 (where the difference is exact) and d1 at equality: the relative error at d2 = d1 (1 + 3e-12) drops from 1.5e-5 to 0. test_lmtd_nearly_equal checks it against a 60-digit Decimal reference to 4e-16. No documented number changes (ch05 regenerated alone: no capture or figure differs). TGS-5 (nit, fixed): test_exchanger_costs_match_biosteam_design compares _exchanger_costs with a biosteam HXprocess costed from the same design results at 8 points: A = 1 (F_l < 1), 2.1718, 149.999, 150, 2000, 5000 and 1e4 ft2, at 14.7, 150 and 600 psi, every shell and tube type and five materials. TGS-6 (nit, fixed): supertarget warns (RuntimeWarning) when no point of the sweep has a finite TAC, as its optimum is then the smallest temperature and meaningless; documented in its Warns section and in concepts.rst, and tested with the cooling unserved (and its absence on a normal sweep). TGS-7 (nit, fixed): the class Notes quote the jump at 150 ft2 as biosteam gives it at its default CE of 567.5 and 1 atm: a base purchase cost of about 3,211 USD just below 150 ft2 to 20,516 USD for a floating head, an installed cost of about 8,055 to 64,658 USD. Tests: pytest tests/test_targeting.py hensmith/targeting.py: 48 passed; hensmith/_heat_exchanger_network.py doctests and test_docstring_references_are_cited pass. On df45d85, test_sensible_line_process_in_stretch fails (RuntimeWarning, area inf). Oracle unchanged (sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf). Sphinx -W (offline) clean. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/concepts.rst | 4 +- hensmith/targeting.py | 65 +++++++++++++++----- tests/test_targeting.py | 127 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 177 insertions(+), 19 deletions(-) diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 8626ebe..ca8099b 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -499,7 +499,9 @@ of ``T_min_app`` values, reusing the streams' curves (nothing is flashed again), and then refines the grid around the least total annualized cost. The cost is not smooth -- the units target jumps, the double pipe switches to a shell and tube, the pinch moves -- so the refinement only narrows a bracket, -and ``at_bound`` flags an optimum at an end of the sweep. With approach +and ``at_bound`` flags an optimum at an end of the sweep. A sweep with no +finite total cost (an unserved utility everywhere, say) warns that its +optimum is meaningless. With approach contributions, only the unspecified streams follow ``T_min_app``; ``scale_contributions=True`` scales the specified ones with it, the contribution scaling of Ahmad, Linnhoff & Smith (1990). diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 4e0b586..2d1db2e 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -403,11 +403,22 @@ def _bath(hot, cold): warn('vertical heat transfer at a non-positive driving force between ' f'H = {x[k]:.6g} and {y[k]:.6g} kJ/hr', RuntimeWarning) return math.inf - with np.errstate(divide='ignore', invalid='ignore'): - LMTD = np.where(np.abs(d1 - d2) <= 1e-12 * np.maximum(d1, d2), d1, - (d1 - d2) / np.log(d1 / d2)) + LMTD = _lmtd(d1, d2) return float(np.sum((y - x) * (hot[4][i] + cold[4][j]) / 3600. / LMTD)) +def _lmtd(d1, d2): + """ + Log-mean temperature differences [K] of the positive driving forces `d1` + and `d2` (arrays), to machine precision when they are nearly equal: the + log of their ratio is ``log1p((d1 - d2) / d2)`` for a ratio between 0.5 + and 1.5 (where the difference is exact), and the LMTD is `d1` at + equality. + """ + with np.errstate(divide='ignore', invalid='ignore'): + r = (d1 - d2) / d2 + log = np.where(np.abs(r) < .5, np.log1p(r), np.log(d1 / d2)) + return np.where(r == 0., d1, (d1 - d2) / log) + def _check_exchanger(heat_exchanger_type, material): """Raise a ValueError unless biosteam knows the shell and tube `heat_exchanger_type` and `material`.""" @@ -438,6 +449,7 @@ def _exchanger_costs(A, P=101325., heat_exchanger_type='Floating head', compute_shell_and_tube_material_factor, shell_and_tube_material_factor_coefficients) _check_exchanger(heat_exchanger_type, material) + P = _pressure(P) if math.isnan(A): return math.nan, math.nan, None if math.isinf(A): return math.inf, math.inf, None P_psi = P * 14.7 / 101325 @@ -484,6 +496,13 @@ def _real(x, name): if math.isfinite(value): return value raise ValueError(f'{name} must be a finite number, not {x!r}') +def _pressure(P): + """``float(P)`` of the exchangers' pressure `P` [Pa], a finite number + > 0 (not a bool); a ValueError naming P otherwise.""" + P = _real(P, 'P') + if P <= 0.: raise ValueError(f'P must be > 0 Pa, not {P!r}') + return P + def _economics(interest=None, years=None, operating_hours=None, tea=None): """ ``(crf, hours)``: the capital recovery factor [1/yr] and the operating @@ -684,13 +703,16 @@ class PinchTargets: one furnace for all fuel) [Linnhoff90]_ and costs each with biosteam's correlations at one pressure (biosteam takes each exchanger's highest inlet pressure: its pressure factor is up to about 12 % higher for - high pressure steam heaters): a double pipe below 150 ft² (whose cost - jumps from 3,208 to 20,516 USD at 150 ft²; the floating head - correlation, not monotone below about 126 ft², is not used there), a - shell and tube of any area above. The furnace is costed on the fuel's - utility duty. `summary` adds the total annualized cost, the installed - (bare module) cost times the capital recovery factor plus the utility - cost times the operating hours: no Lang factor and no working capital. + high pressure steam heaters): a double pipe below 150 ft² (at + biosteam's default CE of 567.5 and 1 atm, its base purchase cost of + about 3,211 USD just below 150 ft² jumps to 20,516 USD for a floating + head at 150 ft², and the installed cost from about 8,055 to 64,658 USD; + the floating head correlation, not monotone below about 126 ft², is not + used there), a shell and tube of any area above. The furnace is costed + on the fuel's utility duty. `summary` adds the total annualized cost, + the installed (bare module) cost times the capital recovery factor plus + the utility cost times the operating hours: no Lang factor and no + working capital. Examples -------- @@ -1521,6 +1543,7 @@ def _capital(self, utilities, h, h_utilities, P, heat_exchanger_type, """`capital_cost_target` with resolved film coefficients (see `_area`), and the units target.""" _check_exchanger(heat_exchanger_type, material) + P = _pressure(P) A = self._area(utilities, h, h_utilities) N = self.units_target(utilities)['total'] fuel = [u for u in utilities if u.agent is not None and u.agent.isfuel] @@ -1560,7 +1583,7 @@ def capital_cost_target(self, utilities=None, h=None, h_utilities=None, h, h_utilities : optional Film coefficients, as `area_target` takes them. P : float, optional - Pressure of every shell [Pa]. Defaults to 101325. + Pressure of every shell [Pa], > 0. Defaults to 101325. heat_exchanger_type : str, optional biosteam's type of the shell and tube exchangers ('Floating head', 'Fixed head', 'U tube', 'Kettle vaporizer' or 'Double @@ -1600,6 +1623,7 @@ def _check_options(self, options): get('tea')) _check_exchanger(get('heat_exchanger_type', 'Floating head'), get('material', 'Carbon steel/carbon steel')) + _pressure(get('P', 101325.)) self._film_coefficients(get('h')) _utility_films((), get('h_utilities'), [*(get('heating_agents') or ()), @@ -1695,6 +1719,13 @@ def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, ------- Supertargets + Warns + ----- + RuntimeWarning + If no minimum approach temperature of the sweep has a finite TAC + (each has an unserved utility or an infinite area): the optimum is + then the smallest one, and meaningless. + Notes ----- Each minimum approach temperature is evaluated with @@ -1794,7 +1825,12 @@ def evaluate(T_min_app): for x in np.linspace(lo, hi, n_refine + 2)[1:-1]: if np.abs(evaluated - x).min() > 1e-12: records.append(evaluate(float(x))) - return Supertargets(targets, records, options, scale_contributions) + sweep = Supertargets(targets, records, options, scale_contributions) + if not np.isfinite(sweep['TAC']).any(): + warn('no minimum approach temperature of the sweep has a finite TAC ' + '(each has an unserved utility or an infinite area), so its ' + 'optimum, the smallest, is meaningless', RuntimeWarning) + return sweep class Supertargets: @@ -1882,10 +1918,7 @@ def _profile_area(x, dT, inv_U, ID): warn(f'{ID} transfers heat at a driving force of {dT.min():.6g} K', RuntimeWarning) return math.inf - d1, d2 = dT[:-1], dT[1:] - with np.errstate(divide='ignore', invalid='ignore'): - LMTD = np.where(np.abs(d1 - d2) <= 1e-12 * np.maximum(d1, d2), d1, - (d1 - d2) / np.log(d1 / d2)) + LMTD = _lmtd(dT[:-1], dT[1:]) return float(np.sum(np.diff(x) / LMTD)) * inv_U / 3600. def _stage_profile(curve, H0, sign, f, Q): diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 0a9542c..4ce3d24 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -44,7 +44,7 @@ network_report, plot_composite_curves, plot_grand_composite_curve, plot_supertargets) from hensmith.hxn_synthesis import _problem_table, temperature_interval_pinch_analysis -from hensmith.targeting import _exchanger_costs, _economics, _profile_area +from hensmith.targeting import _exchanger_costs, _economics, _lmtd, _profile_area from test_hxn import (build_system, cp_units, KEMP, simulate_HXN, simulate_strictly, _gid_artists) from test_hxn_contributions import classic_cascade @@ -654,6 +654,67 @@ def test_sensible_line_constant_cp(): assert min_vertical_gap(*curves) == pytest.approx(dT, abs=1e-6) assert 0. < t.area_target([u]) < math.inf +def sensible_line_sweep(t, agents, T_min_apps): + """``{T_min_app: utility targets}`` over a sweep of `t`, asserting that + the balanced composite curves keep min(T_min_app, dT) apart (no warning, + a finite area that falls with T_min_app).""" + dT, sweep, areas = bst.HeatUtility.dT, {}, [] + for T_min_app in T_min_apps: + at = t.at(T_min_app) + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter('always') + utilities = at.utility_targets(*agents) + areas.append(at.area_target(utilities)) + assert not [w for w in caught if issubclass(w.category, RuntimeWarning)] + curves = at.balanced_composite_curves(utilities) + assert min_driving_force(*curves) >= min(T_min_app, dT) - 1e-9 + sweep[T_min_app] = utilities + assert np.all(np.isfinite(areas)) and np.all(np.diff(areas) < 0.) + return sweep + +def test_sensible_line_process_in_stretch(): + # a process stream of the other kind inside a sensible agent's stretch: + # the line must keep dT from the process where the agent has taken little + # of its duty, not only at the stretch's warm end (cooling) or cold end + # (heating), which crossed the curves (area inf at any T_min_app) + dT, T_min_apps = bst.HeatUtility.dT, (1., 2., 3., 5., 8., 10.) + # chilled water serves H1 from 312.372 K (the utility pinch of cooling + # water) to 287.372 K, but C1, from 297 K, takes most of H1's heat above + # 297 K + T_min_app: the bound is there + t = targets('test_targeting_line_cold_in', [('H1', 518.9, 272.3, 4.99), + ('C1', 297., 450.3, 4.85)], 10.) + CHW = agent('chilled_water') + for T_min_app, utilities in sensible_line_sweep(t, (), T_min_apps).items(): + assert [u.ID for u in utilities] == ['cooling_water', 'chilled_water', + 'chilled_brine'] + u = utilities[1] + T_C1 = 297. + T_min_app # H1 where C1 starts + below = 4.99 * kW * (T_C1 - u.T_out) # H1's heat below it + T_out = min(CHW.T_limit, CHW.T + (T_C1 - dT - CHW.T) * u.duty / below) + assert u.T_utility_out == pytest.approx(T_out, rel=1e-12) + priced = fresh_price(-u.duty, min(u.T_in, T_out + dT), u.T_out, CHW)[0] + assert u.cost == pytest.approx(priced, rel=1e-12) + if T_min_app <= 5.: assert T_out < CHW.T_limit - 2. + # its mirror: a hot oil (500 K, down to 300 K at most) serves C1 from 290 + # K, but H1, up to 450 K, gives most of C1's heat below 450 K - T_min_app + t = targets('test_targeting_line_hot_in', [('C1', 290., 470., 5.), + ('H1', 450., 320., 4.85)], 10.) + oil = bst.UtilityAgent('test_targeting_hot_oil_in', Water=1, T=500., P=5e6, + phase='l', thermo=bst.HeatUtility.thermo_water, + T_limit=300., heat_transfer_price=1e-6) + q = (5. * 180. - 4.85 * 130.) * kW + for T_min_app, utilities in sensible_line_sweep(t, ([oil], []), T_min_apps).items(): + (u,) = utilities + assert (u.T_in, u.T_out) == (pytest.approx(290., abs=1e-9), + pytest.approx(470., abs=1e-9)) + assert u.duty == pytest.approx(q, rel=1e-12) + T_H1 = 450. - T_min_app # C1 where H1 starts + above = 5. * kW * (470. - T_H1) # C1's heat above it + T_out = oil.T - (oil.T - dT - T_H1) * q / above + assert u.T_utility_out == pytest.approx(T_out, rel=1e-12) + assert u.cost == pytest.approx(fresh_price(u.duty, T_out - dT, u.T_out, oil)[0], + rel=1e-12) + def test_pricing_temperatures(): dT = bst.HeatUtility.dT # the hot stream's top, 445 K shifted, is above every cold stream: medium @@ -853,6 +914,42 @@ def test_exchanger_costs_match_biosteam(kemp_hxn): with pytest.raises(ValueError, match='material must be one of'): _exchanger_costs(500., material='steel') +@pytest.mark.parametrize('A, P_psi, kind, material', [ + (1., 14.7, 'Floating head', 'Carbon steel/carbon steel'), # F_l < 1 + (2.1718, 150., 'Fixed head', 'Carbon steel/carbon steel'), + (149.999, 600., 'Floating head', 'Stainless steel/stainless steel'), + (150., 600., 'U tube', 'Stainless steel/stainless steel'), + (150., 14.7, 'Floating head', 'Carbon steel/carbon steel'), + (2000., 150., 'Fixed head', 'Carbon steel/brass'), + (5000., 600., 'Kettle vaporizer', 'Monel/Monel'), + (1e4, 14.7, 'Double pipe', 'Titanium/titanium')]) +def test_exchanger_costs_match_biosteam_design(A, P_psi, kind, material): + # biosteam's own costing of an exchanger with these design results + with bst.Flowsheet('test_targeting_costs'): + hx = bst.HXprocess('test_targeting_costs_hx', thermo=bst.HeatUtility.thermo_water, + heat_exchanger_type=kind, material=material) + hx.isfurnace = False + hx.design_results.update({'Area': A, 'Total tube length': 20, + 'Operating pressure': P_psi}) + hx._cost() + hx._load_costs() + purchase, installed, kind = _exchanger_costs(A, P_psi * 101325 / 14.7, kind, material) + assert list(hx.purchase_costs) == [kind] + assert purchase == pytest.approx(hx.purchase_cost, rel=1e-12) + assert installed == pytest.approx(hx.installed_cost, rel=1e-12) + +def test_lmtd_nearly_equal(): + from decimal import Decimal, localcontext + d2 = np.array([10., 10., 10., 10., 10., 10., 10., 1e-3, 7.]) + d1 = d2 * np.array([1., 1. + 3e-12, 1. + 1e-10, 1. - 1e-7, 1. + 1e-3, 1.4, .6, + 1e6, 1e-6]) + with localcontext() as context: + context.prec = 60 + exact = [float(a) if a == b else float((a - b) / (a / b).ln()) + for a, b in zip(map(Decimal, d1), map(Decimal, d2))] + assert_allclose(_lmtd(d1, d2), exact, rtol=4e-16, atol=0) + assert_allclose(_lmtd(d2, d1), exact, rtol=4e-16, atol=0) + def test_capital_target_formula(): from biosteam.units.heat_exchange import compute_furnace_purchase_cost t = targets('test_targeting_capital', REF_A, 10.) @@ -868,6 +965,17 @@ def test_capital_target_formula(): assert capital['furnace_duty'] == 0. # a higher pressure costs more assert t.capital_cost_target(utilities, P=20e5)['installed_cost'] > capital['installed_cost'] + assert t.capital_cost_target(utilities, P=np.float64(101325))['installed_cost'] == ( + capital['installed_cost']) + finite = 'P must be a finite number' + for P, message in ((math.nan, finite), (math.inf, finite), (True, finite), + ('2 bar', finite), (0., 'P must be > 0'), (-1e6, 'P must be > 0')): + with pytest.raises(ValueError, match=message): + t.capital_cost_target(utilities, P=P) + with pytest.raises(ValueError, match=message): + t.summary(P=P) + with pytest.raises(ValueError, match=message): + _exchanger_costs(500., P) # the furnace: one for all fuel, costed on the utility duty t = targets('test_targeting_capital_furnace', FURNACE, 10.) utilities = t.utility_targets() @@ -1021,9 +1129,24 @@ def flash(*args, **kwargs): raise AssertionError('the curves are rebuilt') (dict(dT=5.), TypeError, 'unexpected options'), (dict(h={'H9': 1.}), ValueError, 'name no stream'), (dict(h_utilities={'steam': 1.}), ValueError, 'name no agent'), - (dict(material='steel'), ValueError, 'material must be')): + (dict(material='steel'), ValueError, 'material must be'), + (dict(P=math.nan), ValueError, 'P must be a finite number'), + (dict(P='2 bar'), ValueError, 'P must be a finite number'), + (dict(P=True), ValueError, 'P must be a finite number'), + (dict(P=0.), ValueError, 'P must be > 0'), + (dict(P=-1e6), ValueError, 'P must be > 0')): with pytest.raises(error, match=message): supertarget(t, **kwargs) + # no finite TAC anywhere (the cooling is unserved): the optimum is + # meaningless, and the sweep says so + with pytest.warns(RuntimeWarning, match='no minimum approach temperature of the ' + 'sweep has a finite TAC'): + unserved = supertarget(t, [5., 10.], refine=1, cooling_agents=[]) + assert np.all(np.isnan(unserved['TAC'])) and unserved.T_min_app == 5. + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter('always') + supertarget(t, [5., 10.], refine=0) + assert not [w for w in caught if 'finite TAC' in str(w.message)] # every stream with an approach contribution: T_min_app changes nothing # unless the contributions scale with it monkeypatch.undo() From 08511d1fe37442cd96ba27aaa710b5efe2efcc83 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 02:04:30 +0000 Subject: [PATCH 12/52] address review 1 (tg-report): check report states and make the plots readable TGR-1 (no change needed): already fixed by df45d85 (F1F-1). After a simulation, HeatExchangerNetwork.targets() takes a callable's values from _resolved_contributions and never calls it on the heat utilities that replace_unit_heat_utilities overwrote. The reviewer's case6 (a duty-based callable with replacement) now gives (2, 2, 6, 6) and Q_hot 39600, equal to synthesis_info, and test_facility_targets_replaced_callable covers duty-based and agent-based callables together with network_report. TGR-2 (fixed): network_report now calls a new _check_network on both the default and the explicit path. It checks that every stream of the network enters the network (its copy of the real inlet at the entry port) and leaves its last stage at the end states of its curve in the targets. The network's copies share the curves' thermodynamic basis, so this also works under force_ideal_thermo. A stale network raises a ValueError ("... not of the current states of C1 ...: simulate the system"). This covers a stream or a unit changed without simulating the system. _check_targets no longer returns early under force_ideal_thermo: it compares the curves with ideal copies of the exchangers' streams. A shared _same_ends also compares the inlet pressure and flows, so a flow change at the 298.15 K enthalpy reference (H = 0) is caught. On the reviewer's corpus, the network-vs-curve mismatch in fresh states (KEMP, replace, cache after a feed change, the docstring liquids with and without splitting, ideal thermo and cache, the quickstart real and ideal with cache, 8 SPLIT and 8 NO_SPLIT cases) is at most 4e-11 of a stream's duty, against tolerances of 1e-9 (inlet) and 1e-6 (outlet). The docstring's Raises section and the 'T_min_app' entry (the attribute as it is now) say so, and so does concepts.rst. TGR-5 (fixed, all four): (a) On the balanced plot, the fuel label stacks above the top flat's label when both anchor at the end of the finite hot curve, with more headroom. (b) kind='shifted' marks the pinch only where the composites touch (both reach pinch_T). There is no marker for a threshold problem whose composites end apart or for streams of one side. An empty composite stays out of the legend. (c) An unserved remainder is labeled in gray in the upper left corner ('Utility:unserved': "unserved cooling 4.45E4 kJ/hr (not drawn)"). (d) The heat axis of both plots uses scientific tick labels from 1e4 on (a plain ScalarFormatter only). The GCC labels sit just below a heating step's end and just above a cooling step's end, off the curve. docs/_demo_src/examples/ch05_targeting.py was run alone to regenerate the docs. Only tutorial_05_grand_composite.png changed (the label offsets): the other figures and captures are byte-identical. TGR-6 (fixed): new tests in tests/test_targeting.py: - test_network_report_independent_areas integrates each exchanger independently: flashes of copies of its own streams at 41 duty points, log-mean in between. It runs on the split liquids network (branches 0.52/0.48, rtol 1e-4) and on the quickstart network, where process and utility exchangers boil and condense (rtol 1e-3; the curves' glide tolerance accounts for about 3e-4). The quickstart system becomes a module fixture shared with `quickstart`. - test_network_report_current_states covers a cached network re-simulated after a feed change (areas against terminal LMTD, targets equal to HXN.targets()) and stale T, flow and unit-alone states on the default path. It also covers a flow change at the enthalpy reference (explicit and default) and stale targets and a stale network under force_ideal_thermo. - test_plot_readability covers the four plot fixes, through text bounding boxes, legend entries and the GCC polyline in display coordinates. Replacement with a callable, plus the report, was already covered by test_facility_targets_replaced_callable. Before the fix, test_network_report_current_states and test_plot_readability fail. Tests: pytest hensmith/targeting.py tests/test_targeting.py: 51 passed (18 s). Doctests of hensmith/_heat_exchanger_network.py: passed. The default-off oracle sha256 is unchanged at dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (the synthesis path is untouched). SPHINX_OFFLINE=1 sphinx-build -W -b html: clean. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- .../examples/tutorial_05_grand_composite.png | Bin 59673 -> 59961 bytes docs/source/concepts.rst | 6 +- hensmith/targeting.py | 173 ++++++++++--- tests/test_targeting.py | 244 +++++++++++++++++- 4 files changed, 375 insertions(+), 48 deletions(-) diff --git a/docs/source/_static/images/examples/tutorial_05_grand_composite.png b/docs/source/_static/images/examples/tutorial_05_grand_composite.png index 6b071d0dfb7e772f81d4eb73ff2fd695ff5ce76a..a078e8fbf5440dea52dc7229cf617007a138cd49 100644 GIT binary patch literal 59961 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 OsVZwJWhxxK@IL@xxT|ph diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index ca8099b..955191c 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -513,8 +513,10 @@ the target sizes them (the same film coefficients, a correction factor of 1, the integrated driving force along the exact stream profiles and the process side duty), so it is comparable with the target; ``'area_biosteam'`` is BioSTEAM's own sizing, which includes its correction factor, the terminal -log-mean temperature difference and the utility side duty. The comparison has -limits: +log-mean temperature difference and the utility side duty. The report checks +that its targets and the network's streams are both of the units' current +states: after a stream or a unit changes, it raises a ``ValueError`` until the +system is simulated again. The comparison has limits: - the Bath target bounds the area only with equal film coefficients and the same utilities; diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 2d1db2e..65685e3 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -26,7 +26,7 @@ from warnings import warn import numpy as np import biosteam as bst -from ._curves import _T_EQ +from ._curves import _T_EQ, _copy from ._planner import _approach_contributions, _given_contributions from .hxn_synthesis import (_problem_table, _pinch_cut, _format_H, _arrange_heat_utilities, _process_streams, _explicit_contributions, @@ -1932,6 +1932,25 @@ def _stage_profile(curve, H0, sign, f, Q): return (knots[(knots > 0.) & (knots < Q)], lambda x: np.interp((H0 + sign * x) / f, curve.H, curve.T)) +def _flows(stream): + """Molar flow rate of each chemical of `stream` [kmol/hr], an array.""" + mol = stream.mol + return mol.to_array() if hasattr(mol, 'to_array') else np.asarray(mol) + +def _same_ends(curve, s_in, s_out): + """Whether the streams `s_in` and `s_out` are at the end states of + `curve` (on the curve's thermodynamic basis): the same enthalpies (to + 1e-6 of its duty at the outlet, which flashes reach), and the same + pressure and flows at the inlet (an inlet at the enthalpy reference + has the same enthalpy at any flow).""" + span = abs(curve.H_out - curve.H_in) + a, b = _flows(curve.stream_in), _flows(s_in) + return (abs(curve.H_in - s_in.H) <= 1e-9 * (abs(curve.H_in) + span) + and abs(curve.H_out - s_out.H) <= 1e-6 * span + and abs(curve.P - s_in.P) <= 1e-9 * curve.P + and a.shape == b.shape + and np.abs(a - b).max(initial=0.) <= 1e-9 * np.abs(a).max(initial=0.)) + def _check_targets(HXN, targets): """Raise a ValueError unless `targets` are those of the process streams of `HXN`'s network, in its stream order and current states.""" @@ -1942,19 +1961,37 @@ def _check_targets(HXN, targets): raise ValueError("targets must be of the network's process streams " "(the units of HXN.original_heat_exchangers, in that " 'order), as HXN.targets() makes them') - if targets.force_ideal_thermo != bool(HXN.force_ideal_thermo): - raise ValueError(f'targets have force_ideal_thermo=' - f'{targets.force_ideal_thermo}, the network ' - f'{bool(HXN.force_ideal_thermo)}') - if targets.force_ideal_thermo: return + ideal = targets.force_ideal_thermo + if ideal != bool(HXN.force_ideal_thermo): + raise ValueError(f'targets have force_ideal_thermo={ideal}, the ' + f'network {bool(HXN.force_ideal_thermo)}') for c, hx in zip(targets.curves, hxs): - H_in, H_out = hx.ins[0].H, hx.outs[0].H - if (abs(c.H_in - H_in) > 1e-9 * abs(H_in) - or abs(c.H_out - H_out) > 1e-6 * abs(c.H_out - c.H_in)): + s_in, s_out = hx.ins[0], hx.outs[0] + if ideal: # the curves' basis: copies with ideal thermodynamics + s_in = _copy(s_in, s_in.thermo.ideal()) + s_out = _copy(s_out, s_out.thermo.ideal()) + if not _same_ends(c, s_in, s_out): raise ValueError(f'targets are not of the current states of ' f'{_unit_label(hx)} (simulated since?): make ' 'them again with HXN.targets()') +def _check_network(HXN, targets): + """Raise a ValueError unless every stream of `HXN`'s network enters and + leaves it at the end states of its curve in `targets` (which are of the + units' current states): its copy of the real inlet where it enters the + network, and the outlet of its last stage (the network's copies share + the curves' thermodynamic basis).""" + for life_cycle, c, label in zip(HXN.stream_life_cycles, targets.curves, + targets.labels): + entry = life_cycle.entry + if entry is None: s_in = life_cycle.life_cycle[0].s_in + else: s_in = entry.unit.ins[entry.index] + if not _same_ends(c, s_in, life_cycle.life_cycle[-1].s_out): + raise ValueError(f'the network of {HXN.ID} is not of the current ' + f'states of {label} (changed since the ' + "facility's last simulation?): simulate the " + 'system') + def _network_areas(HXN, targets, h, h_utilities, agents): """ ``{ID: (area, area_biosteam)}`` [m²] of the process exchangers and then @@ -2017,7 +2054,8 @@ def network_report(HXN, targets=None, **options): Parameters ---------- HXN : HeatExchangerNetwork - A simulated facility. + A simulated facility, whose network is of the units' current states + (simulate the system again after changing a unit or a stream). targets : PinchTargets, optional Targets of the facility's process streams, as `HeatExchangerNetwork.targets` makes them (the same units in the @@ -2035,8 +2073,9 @@ def network_report(HXN, targets=None, **options): Returns ------- dict - 'T_min_app' and 'targets_T_min_app' [K], of the facility and of the - targets; 'status' (``HXN.synthesis_info['status']``); 'applied', + 'T_min_app' and 'targets_T_min_app' [K], of the facility (its + attribute as it is now: a reused cached network keeps the one it + was synthesized at) and of the targets; 'status' (``HXN.synthesis_info['status']``); 'applied', whether the network has a process exchanger; 'Q_hot' and 'Q_cold', the heat of the network's heating and cooling utilities [kJ/hr], process side (`HXN.actual_heat_util_load` is utility side); @@ -2058,7 +2097,10 @@ def network_report(HXN, targets=None, **options): If the facility was not simulated. ValueError If `targets` are not those of the facility's process streams in - their current states, or for an invalid option. + their current states (enthalpies at both ends, and pressure and + flows at the inlet), if the network's streams do not enter and + leave it at those states (a unit or a stream changed since the + facility's last simulation), or for an invalid option. TypeError For an unknown option. @@ -2156,6 +2198,7 @@ def network_report(HXN, targets=None, **options): 'reporting on it') if targets is None: targets = HXN.targets() else: _check_targets(HXN, targets) + _check_network(HXN, targets) options = dict(options) if options.get('operating_hours') is None and options.get('tea') is None: hours = getattr(getattr(HXN, 'system', None), 'operating_hours', None) @@ -2199,6 +2242,19 @@ def network_report(HXN, targets=None, **options): } +def _legend(label, H): + """`label` for the legend if a curve of heats `H` has points, else none + (a label starting with '_' stays out of the legend).""" + return label if H.size else '_' + label + +def _sci_ticks(axis): + """Scientific tick labels from 10⁴ on the matplotlib `axis` (six-digit + heats run together on a narrow figure); left alone unless it has a + plain scalar formatter.""" + from matplotlib.ticker import ScalarFormatter + formatter = axis.get_major_formatter() + if type(formatter) is ScalarFormatter: formatter.set_powerlimits((-3, 4)) + def plot_composite_curves(targets, kind='real', utilities=None, ax=None, file=None, dpi=300): """ @@ -2233,8 +2289,13 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, Temperatures are shown in degC. Artists carry stable gids: 'Hot composite' and 'Cold composite' (or 'Hot balanced composite' and 'Cold balanced composite'), 'Heat recovered', 'Hot utility', 'Cold utility', - 'Pinch', 'Fuel' and 'Utility:'. Fuel, at infinite temperature - on the balanced curves, is labeled rather than drawn. + 'Pinch', 'Fuel' and 'Utility:'. The pinch is marked where the + shifted composites touch: not for a threshold problem whose composites + end apart, nor without streams on one side (whose empty composite stays + out of the legend). Fuel, at infinite temperature on the balanced + curves, is labeled rather than drawn, and so is an unserved remainder + ('Utility:unserved', in the upper left corner), without which the + balanced curves end at different heats. Examples -------- @@ -2276,20 +2337,23 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, finite = np.isfinite(hot_T) hot_T, hot_H = hot_T[finite], hot_H[finite] ax.plot(hot_H, hot_T - 273.15, color=hot_color, lw=2, - label='hot balanced composite', gid='Hot balanced composite') + label=_legend('hot balanced composite', hot_H), + gid='Hot balanced composite') ax.plot(cold_H, cold_T - 273.15, color=cold_color, lw=2, - label='cold balanced composite', gid='Cold balanced composite') + label=_legend('cold balanced composite', cold_H), + gid='Cold balanced composite') + above = [] # anchors of the labels above a heating flat + fuels = [] + unserved = [] for u in utilities: - if u.agent is None: continue - label = f'{u.ID} {_format_H(u.duty)} kJ/hr' - if u.agent.isfuel: # at the end of the finite hot curve - x = hot_H[-1] if hot_H.size else 0. - y = (hot_T[-1] if hot_T.size else targets.grid['Ts'][0]) - 273.15 - ax.annotate(f'{u.ID} (furnace) {_format_H(u.duty)} kJ/hr', - xy=(x, y), xytext=(0, 4), textcoords='offset points', - ha='right', va='bottom', color=hot_color, gid='Fuel', - **text_kwargs) + if u.agent is None: + unserved.append(f"unserved {'heating' if u.heating else 'cooling'} " + f'{_format_H(u.duty)} kJ/hr (not drawn)') + continue + if u.agent.isfuel: + fuels.append(u) continue + label = f'{u.ID} {_format_H(u.duty)} kJ/hr' T, H = (hot_T, hot_H) if u.heating else (cold_T, cold_H) if u.agent.T_limit is None: # above a heating flat, ending at its warm end (left of it the @@ -2303,6 +2367,7 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, i = np.searchsorted(T, T_u - 1e-6) offset, ha, va = (2, -2), 'left', 'top' x = H[min(max(i, 0), H.size - 1)] + if u.heating: above.append((x, T_u)) else: # beside the middle of a sensible agent's line T_u = 0.5 * (u.T_utility_in + u.T_utility_out) x = np.interp(T_u, T, H) @@ -2312,7 +2377,23 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, textcoords='offset points', ha=ha, va=va, color=hot_color if u.heating else cold_color, gid='Utility:' + u.ID, **text_kwargs) - ax.margins(y=0.1) # headroom for a label above the top flat + stacked = False + for u in fuels: # at the end of the finite hot curve + x = hot_H[-1] if hot_H.size else 0. + y = hot_T[-1] if hot_T.size else targets.grid['Ts'][0] + # above the label of the top flat if that ends there too + stacked = any(abs(x - a) <= 1e-9 * (abs(x) + abs(a)) + 1e-12 + and abs(y - b) <= 1e-6 for a, b in above) + ax.annotate(f'{u.ID} (furnace) {_format_H(u.duty)} kJ/hr', + xy=(x, y - 273.15), xytext=(-2, 14) if stacked else (0, 4), + textcoords='offset points', ha='right', va='bottom', + color=hot_color, gid='Fuel', **text_kwargs) + if unserved: # the curves end apart: say why + ax.text(0.02, 0.97, '\n'.join(unserved), transform=ax.transAxes, + ha='left', va='top', color='0.45', gid='Utility:unserved', + **text_kwargs) + # headroom for a label (or two) above the top flat + ax.margins(y=0.16 if stacked else 0.1) ax.set_ylabel('T [°C]') else: shifted = kind == 'shifted' @@ -2321,9 +2402,9 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, ax.axvspan(cold_H[0], hot_H[-1], color='0.92', lw=0, zorder=0, label='heat recovered', gid='Heat recovered') ax.plot(hot_H, hot_T - 273.15, color=hot_color, lw=2, - label='hot composite', gid='Hot composite') + label=_legend('hot composite', hot_H), gid='Hot composite') ax.plot(cold_H, cold_T - 273.15, color=cold_color, lw=2, - label='cold composite', gid='Cold composite') + label=_legend('cold composite', cold_H), gid='Cold composite') T_all = np.concatenate([hot_T, cold_T]) - 273.15 H_hot_end = hot_H[-1] if hot_H.size else 0. H_cold_end = cold_H[-1] if cold_H.size else targets.Q_cold @@ -2334,14 +2415,20 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, ax.text(0.5 * targets.Q_cold, T_all.min() - 1., f'cold utility {_format_H(targets.Q_cold)} kJ/hr', ha='left', va='top', gid='Cold utility', **text_kwargs) - if shifted: + pinch_T = targets.pinch_T + tol = 1e-9 * pinch_T + if shifted and all(T.size and T.min() - tol <= pinch_T <= T.max() + tol + for T in (hot_T, cold_T)): # where the composites touch: the cold utility plus the cold - # streams' heat below the pinch + # streams' heat below the pinch, if both composites reach the + # pinch temperature (not for streams of one side, nor for a + # threshold problem whose composites end apart) below = targets._side_heat()[1] x = targets.Q_cold + below[~targets.is_hot].sum() - ax.plot([x], [targets.pinch_T - 273.15], 'o', mfc='w', mec='k', + ax.plot([x], [pinch_T - 273.15], 'o', mfc='w', mec='k', ms=7, zorder=4, label='pinch', gid='Pinch') ax.set_ylabel('shifted T [°C]' if shifted else 'T [°C]') + _sci_ticks(ax.xaxis) ax.set_xlabel('H [kJ·h$^{-1}$]') ax.legend(loc='lower right', fontsize=8) ax.grid(alpha=0.3) @@ -2378,9 +2465,11 @@ def plot_grand_composite_curve(targets, utilities=None, ax=None, file=None, ascending level from the temperature axis, each a horizontal segment at its level as long as its duty, joined by dotted verticals; cooling utilities likewise by descending level. Fuel is drawn dashed at the top - of the grid and an unserved remainder in gray. Artists carry stable - gids: 'GCC', 'Pinch', 'Utility:' (the segments, - 'Utility:unserved' for a remainder) and 'Label:'. + of the grid and an unserved remainder in gray. Each label sits at the + end of its segment, below it for heating and above it for cooling, off + the curve where a level touches it. Artists carry stable gids: 'GCC', + 'Pinch', 'Utility:' (the segments, 'Utility:unserved' for a + remainder) and 'Label:'. Examples -------- @@ -2431,8 +2520,12 @@ def plot_grand_composite_curve(targets, utilities=None, ax=None, file=None, key=lambda u: u.level) cooling = sorted((u for u in utilities if not u.heating), key=lambda u: -u.level) - for steps, color, end in ((heating, hot_color, Ts[0]), - (cooling, cold_color, Ts[-1])): + # each label beside the end of its step, where a level touches the + # curve: below it for heating (the curve rises to the right above + # the pinch) and above it for cooling (it falls to the right below) + for steps, color, end, (dy, va) in ( + (heating, hot_color, Ts[0], (-2, 'top')), + (cooling, cold_color, Ts[-1], (2, 'bottom'))): x = 0. previous = None for u in steps: @@ -2446,10 +2539,12 @@ def plot_grand_composite_curve(targets, utilities=None, ax=None, file=None, ls=':', lw=0.8) ax.plot([x, x + u.duty], [y, y], lw=1.5, gid='Utility:' + ID, **style) - ax.text(x + u.duty, y, ' ' + ID, ha='left', va='center', - fontsize=7, color=style['color'], gid='Label:' + ID) + ax.annotate(ID, xy=(x + u.duty, y), xytext=(3, dy), + textcoords='offset points', ha='left', va=va, + fontsize=7, color=style['color'], gid='Label:' + ID) x += u.duty previous = y + _sci_ticks(ax.xaxis) ax.set_xlabel('heat cascaded [kJ·h$^{-1}$]') ax.set_ylabel('shifted T [°C]') ax.grid(alpha=0.3) diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 4ce3d24..4c027f9 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -24,9 +24,14 @@ D. The facility and the report: the facility's targets equal the synthesis's bit for bit (before and after a simulation, with approach contributions and with replaced heat utilities), the report's areas - against terminal log-mean temperature differences, and no side effects - on the flowsheet, the agents' stream dumps or a later synthesis. -E. Plots: stable gids whose data equal the curves. + against terminal log-mean temperature differences and against an + independent integration on the network's own streams (branches of a + split, phase changes), the report at the units' current states only + (a cached network re-simulated, stale states detected, also with ideal + thermodynamics), and no side effects on the flowsheet, the agents' + stream dumps or a later synthesis. +E. Plots: stable gids whose data equal the curves, and readable labels + and markers. Reference A: H1 400 -> 300 K and C1 330 -> 440 K, both 1 kW/K, at T_min_app = 10 K: 50 kW hot and 40 kW cold utility, shifted pinch 390 K. @@ -43,6 +48,7 @@ from hensmith import (HeatExchangerNetwork, PinchTargets, Supertargets, supertarget, network_report, plot_composite_curves, plot_grand_composite_curve, plot_supertargets) +from hensmith._curves import _copy from hensmith.hxn_synthesis import _problem_table, temperature_interval_pinch_analysis from hensmith.targeting import _exchanger_costs, _economics, _lmtd, _profile_area from test_hxn import (build_system, cp_units, KEMP, simulate_HXN, simulate_strictly, @@ -225,14 +231,37 @@ def kemp_hxn_replaced(): return units, HXN @pytest.fixture(scope='module') -def quickstart(): - """The targets of the documentation's quickstart system at 5 K (two - auxiliary exchangers of the column D0).""" +def quickstart_hxn(): + """The documentation's quickstart system simulated with its facility at + 5 K (two auxiliary exchangers of the column D0, a boiling and a + condensing stream): ``(sys, HXN)``.""" sys, HXN, _ = build_system() sys.simulate() - hus = sorted(HXN.original_heat_utils, key=lambda hu: hu.duty) + return sys, HXN + +@pytest.fixture(scope='module') +def quickstart(quickstart_hxn): + """The targets of the documentation's quickstart system at 5 K.""" + hus = sorted(quickstart_hxn[1].original_heat_utils, key=lambda hu: hu.duty) return PinchTargets.from_heat_utilities(hus, 5.) +def liquids(name, **kwargs): + """The liquids of the second example of `HeatExchangerNetwork` (and of + `network_report`) simulated with a facility at 10 K with the options + `kwargs`: ``(sys, HXN, units)``.""" + bst.main_flowsheet.set_flowsheet(name) + bst.settings.set_thermo(['Water', 'Ethanol'], cache=True) + def process_stream(ID, T_in, T_out, P, **kmol_hr): + inlet = bst.Stream(ID + '_in', T=T_in, P=P, phase='l', + units='kmol/hr', **kmol_hr) + return bst.HXutility(ID, ins=inlet, T=T_out, rigorous=False) + units = [process_stream('H1', 400., 320., 5e5, Water=60.), + process_stream('H2', 390., 330., 5e5, Ethanol=30.), + process_stream('C1', 310., 410., 5e5, Water=150.), + process_stream('H3', 360., 300., 101325., Water=200.)] + sys, HXN = simulate_HXN(units, 10., **kwargs) + return sys, HXN, units + # %% Composites and grand composite curve def test_composites_closed_form_kemp(): @@ -1353,6 +1382,131 @@ def test_network_report(kemp_hxn): assert _profile_area(np.array([0., 1.]), np.array([5., 0.]), 2., 'HX') == math.inf assert _profile_area(np.array([0., kW]), np.array([5., 5.]), 2., 'HX') == 2. / 5. +def flashed_T(stream, H): + """Temperature [K] of a copy of `stream` at equilibrium at the enthalpy + `H` [kJ/hr] and its own pressure.""" + s = _copy(stream) + s.vle(H=H, P=s.P) + return s.T + +def independent_area(hx, n=41): + """ + Area [m2] of an exchanger of a network with films of 1 kW/m2/K on both + sides, integrated independently of the targets: the process duty in n - + 1 equal parts, the temperatures of its streams at each boundary from + flashes of copies of its own outlet and inlet streams (on a branch, + the branch's flow), log-mean in between; a utility's temperature linear + in the duty from its inlet (at the process outlet) to its outlet. + """ + if isinstance(hx, bst.HXprocess): + hot = 0 if hx.ins[0].H > hx.outs[0].H else 1 + hot_in, cold_out = hx.ins[hot], hx.outs[1 - hot] + x = np.linspace(0., hot_in.H - hx.outs[hot].H, n) + dT = np.array([flashed_T(hot_in, hot_in.H - q) - flashed_T(cold_out, cold_out.H - q) + for q in x]) + else: + hu = hx.heat_utilities[0] + s_in, s_out = hx.ins[0], hx.outs[0] + heating = s_out.H > s_in.H + x = np.linspace(0., abs(s_out.H - s_in.H), n) + T = np.array([flashed_T(s_out, s_out.H - q if heating else s_out.H + q) for q in x]) + T_a, T_b = hu.inlet_utility_stream.T, hu.outlet_utility_stream.T + T_u = T_a + (T_b - T_a) * x / x[-1] + dT = T_u - T if heating else T - T_u + d1, d2 = dT[:-1], dT[1:] + LMTD = np.where(np.abs(d1 - d2) <= 1e-9 * d1, d1, (d1 - d2) / np.log(d1 / d2)) + return float(np.sum(np.diff(x) / LMTD)) * 2. / kW + +def changes_phase(hx): + """Whether a stream of `hx` leaves it at another vapor fraction.""" + return any(abs(s_in.vapor_fraction - s_out.vapor_fraction) > 1e-6 + for s_in, s_out in zip(hx.ins, hx.outs)) + +def test_network_report_independent_areas(quickstart_hxn): + # The report integrates each exchanger along the curves of its streams + # in the targets; an independent integration on the network's own + # streams agrees, across the breakpoints of a phase change inside an + # exchanger and on the branches of a split (a branch stage takes its + # fraction of the whole stream's curve). The quickstart network boils + # and condenses streams in process and utility exchangers alike. + _, split, _ = liquids('test_targeting_report_split', stream_splitting=True) + branches = {st.unit.ID: st.fraction for lc in split.stream_life_cycles + for st in lc.life_cycle if st.fraction != 1.} + assert sorted(branches) == ['HX_0_2_hs', 'HX_0_3_hs'] + assert_allclose(sorted(branches.values()), [0.4799, 0.5201], atol=1e-4) + for HXN, rtol in ((split, 1e-4), (quickstart_hxn[1], 1e-3)): + report = network_report(HXN) + exchangers = {hx.ID: hx for hx in HXN.new_HXs + HXN.new_HX_utils} + areas = report['exchanger_areas'] + assert len(areas) == report['N_units'] + for ID, (area, _) in areas.items(): + assert area == pytest.approx(independent_area(exchangers[ID]), rel=rtol), ID + if HXN is not split: + boiling = [ID for ID in areas if changes_phase(exchangers[ID])] + assert any(ID.startswith('HX_') for ID in boiling) + assert any(ID.startswith('Util_') for ID in boiling) + +def test_network_report_current_states(): + # With cache_network, the network re-simulated at changed states is + # reported at those states + units = cp_units('test_targeting_report_cache', KEMP, K) + sys, HXN = simulate_HXN(units, 10., cache_network=True) + network = HXN.HXN_sys + C1 = units[0] + inlet = C1.ins[0] + inlet.F_mass *= 1.05 + simulate_strictly(sys) + assert HXN.HXN_sys is network + report = network_report(HXN) + assert report['targets'] == HXN.targets().summary() + # (the reused structure no longer reaches the energy targets) + assert report['Q_hot'] > report['targets']['Q_hot'] * (1 + 1e-3) + exchangers = {hx.ID: hx for hx in HXN.new_HXs + HXN.new_HX_utils} + for ID, (area, _) in report['exchanger_areas'].items(): + assert area == pytest.approx(terminal_area(exchangers[ID]), rel=1e-9), ID + # a stream changed since the facility's last simulation (or its unit + # simulated alone): the network is not of the current states + T, F = inlet.T, inlet.F_mass + for change in ('T', 'F', 'unit'): + if change == 'F': inlet.F_mass = 1.5 * F + else: inlet.T = T + 15. + if change == 'unit': C1.simulate() + with pytest.raises(ValueError, match='not of the current states of C1.*simulate the system'): + network_report(HXN) + inlet.T, inlet.F_mass = T, F + C1.simulate() + assert network_report(HXN) == report + # an inlet at the enthalpy reference (298.15 K) has no enthalpy at any + # flow: the flows are compared too + rows = [('H1', 400., 320., 1.), ('C1', 298.15, 370., 1.)] + sys, HXN = simulate_HXN(cp_units('test_targeting_report_reference', rows), 10.) + inlet = HXN.original_heat_exchangers[0].ins[0] + assert inlet.ID == 'C1_in' and inlet.H == 0. + targets = HXN.targets() + inlet.F_mass *= 2. + assert inlet.H == 0. + with pytest.raises(ValueError, match='targets are not of the current states of C1'): + network_report(HXN, targets) + with pytest.raises(ValueError, match='simulate the system'): + network_report(HXN) + # with ideal thermodynamics, on the curves' basis: stale targets, and a + # network older than the targets + sys, HXN, units = liquids('test_targeting_report_ideal', force_ideal_thermo=True) + stale = HXN.targets() + inlet = units[0].ins[0] + inlet.T += 10. + simulate_strictly(sys) + with pytest.raises(ValueError, match='targets are not of the current states of H1'): + network_report(HXN, stale) + targets = HXN.targets() + assert network_report(HXN, targets)['targets'] == network_report(HXN)['targets'] + inlet.T -= 10. + units[0].simulate() + with pytest.raises(ValueError, match='simulate the system'): + network_report(HXN) + with pytest.raises(ValueError, match='targets are not of the current states of H1'): + network_report(HXN, targets) + def test_no_side_effects(kemp_hxn): units, HXN = kemp_hxn # the furnace case of test_capital_target_formula: the network heats C1 @@ -1476,6 +1630,82 @@ def test_plot_gcc_gids(): finally: plt.close(fig) +def test_plot_readability(): + import matplotlib + matplotlib.use('Agg') + import matplotlib.pyplot as plt + def boxes(fig, artists): + fig.canvas.draw() + renderer = fig.canvas.get_renderer() + return [a.get_window_extent(renderer) for a in artists] + # fuel above high pressure steam: the label of the furnace sits above + # that of the top flat, which ends where the finite hot curve does + furnace = targets('test_targeting_plot_readable_fuel', FURNACE, 10.) + fig, ax = plot_composite_curves(furnace, kind='balanced') + try: + fuel, = _gid_artists(ax, 'Fuel') + steam, = _gid_artists(ax, 'Utility:high_pressure_steam') + assert fuel.xy == pytest.approx(steam.xy, rel=1e-9) + fuel, steam, frame = boxes(fig, [fuel, steam, ax]) + assert not fuel.overlaps(steam) and fuel.y0 >= steam.y1 - 1. + assert frame.y1 >= fuel.y1 + finally: + plt.close(fig) + # the pinch is marked only where the shifted composites touch: not for a + # threshold problem whose composites end apart, nor for hot streams + # only (whose empty cold composite stays out of the legend); a balanced + # pair touches at the top without any utility + threshold = targets('test_targeting_plot_threshold', [('H1', 400., 300., 2.), + ('C1', 330., 380., 1.)], 10.) + hot = targets('test_targeting_plot_hot', [('H1', 400., 300., 1.), + ('H2', 380., 320., 1.)], 10.) + pair = targets('test_targeting_plot_pair', PAIR, 10.) + assert threshold.Q_hot == hot.Q_hot == pair.Q_hot == 0. + for t, pinch, legend in ((threshold, False, ['heat recovered', 'hot composite', 'cold composite']), + (hot, False, ['hot composite']), + (pair, True, ['heat recovered', 'hot composite', 'cold composite', 'pinch'])): + fig, ax = plot_composite_curves(t, kind='shifted') + try: + assert len(_gid_artists(ax, 'Pinch')) == pinch + assert [x.get_text() for x in ax.get_legend().get_texts()] == legend + finally: + plt.close(fig) + # the unserved remainders, without which the balanced curves end apart + t = targets('test_targeting_plot_unserved', REF_A, 10.) + with pytest.warns(RuntimeWarning, match='left unserved'): + utilities = t.utility_targets([agent('low_pressure_steam')], [agent('cooling_water')]) + fig, ax = plot_composite_curves(t, kind='balanced', utilities=utilities) + try: + label, = _gid_artists(ax, 'Utility:unserved') + assert label.get_text() == ('unserved heating 1.18E5 kJ/hr (not drawn)\n' + 'unserved cooling 4.45E4 kJ/hr (not drawn)') + finally: + plt.close(fig) + # the grand composite curve at its default size: heats of six digits + # [kJ/hr] in scientific notation, whose tick labels do not run together, + # and every label off the curve (which meets the end of a step) + t = targets('test_targeting_plot_readable_gcc', REF_A, 10.) + fig, ax = plot_grand_composite_curve(t) + try: + lo, hi = ax.get_xlim() + ticks = [x for x in ax.get_xticklabels() if lo <= x.get_position()[0] <= hi] + assert ax.get_xticks()[-1] > 1e5 + labels = _gid_artists(ax, 'Label:') + assert len(labels) == 4 + boxed = boxes(fig, ticks + labels) + tick_boxes, label_boxes = boxed[:len(ticks)], boxed[len(ticks):] + assert ax.xaxis.get_offset_text().get_text() == '1e5' + assert all(not a.overlaps(b) for a, b in zip(tick_boxes, tick_boxes[1:])) + gcc, = _gid_artists(ax, 'GCC') + xy = ax.transData.transform(np.column_stack(gcc.get_data())) + curve = np.concatenate([np.linspace(a, b, 500) for a, b in zip(xy, xy[1:])]) + for box, label in zip(label_boxes, labels): + inside = ((curve[:, 0] >= box.x0) & (curve[:, 0] <= box.x1) + & (curve[:, 1] >= box.y0) & (curve[:, 1] <= box.y1)) + assert not inside.any(), label.get_text() + finally: + plt.close(fig) + def test_plot_supertargets_gids(tmp_path): import matplotlib matplotlib.use('Agg') From dfb8441fdd02395fda21f70eefb9a9b77c202bd4 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 02:08:19 +0000 Subject: [PATCH 13/52] address review 1 (docs): define agent levels correctly and quote the costs the code gives DOCS-2 (minor, fixed): ch05 now defines a heating agent's level as its temperature less biosteam's approach (the agent's dT plus HeatUtility.dT), 412.19 - 0 - 5 = 407.19 K for low pressure steam, and a coolant's level as its temperature plus that approach, moved onto the shifted scale by subtracting T_min_app, 280.37 + 2 + 5 - 5 = 282.37 K for chilled water, matching _levels, concepts.rst and the ch05_utilities capture. DOCS-3 (minor, fixed): the PinchTargets capital note now quotes the purchase cost the code returns at the 150 ft2 jump (about 5,486 to 20,138 USD for carbon steel at 1 atm, CE 567.5) and the installed cost the target uses (about 8,055 to 64,658 USD), instead of the correlations' base costs. DOCS-4 (nit, fixed): "as the end of this page does" now reads "as the trade-off example below does". DOCS-5 (nit, fixed): "ends the hot utility target ... beyond the end of the hot one" in the ch05 composite-curves caption and the PinchTargets Notes. DOCS-6 (nit, fixed): concepts.rst, the PinchTargets Notes and the code comment say each agent's priced stretch starts at its level or at the end of the grid, whichever is nearer, as the code clips it. DOCS-7 (nit, fixed): the dpi parameter of plot_composite_curves, plot_grand_composite_curve and plot_supertargets documents "Defaults to 300." Text only: no computed result, capture or figure changes, and the synthesis path is untouched. Tests: pytest hensmith/targeting.py tests/test_targeting.py: 51 passed. sphinx-build (docs/requirements.txt) of docs/source: no warnings other than the unreachable intersphinx inventories (no network in this environment). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/concepts.rst | 7 +++--- docs/source/tutorial/05_targeting.rst | 19 ++++++++++------ hensmith/targeting.py | 31 ++++++++++++++------------- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 955191c..72fe63c 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -390,9 +390,10 @@ serves that temperature's point loads; BioSTEAM's own rule is strict, and the two differ only at exact equality. **Served intervals and pricing.** Each agent is priced as one BioSTEAM heat -utility over the stretch it serves: from its level to the next zero of the -**balanced grand composite curve** (the cascade net of the utilities), which -is the pinch or a *utility pinch* where a lower agent is used to its limit. +utility over the stretch it serves: from its level, or from the end of the +grid if that is nearer, to the next zero of the **balanced grand composite +curve** (the cascade net of the utilities), which is the pinch or a *utility +pinch* where a lower agent is used to its limit. A target's ``T_in`` and ``T_out`` are the real temperatures of the streams it serves in that stretch: the coldest and the hottest for heating, the hottest and the coldest for cooling. Without approach contributions they are the diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index 37fb5c4..4824ee9 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -63,7 +63,7 @@ are those of the units' current states, which, with ``cache_network=True``, can differ from the cached network's. The same object can be built without a facility, from heat utilities with :meth:`PinchTargets.from_heat_utilities ` -(as the end of this page does) or from streams as +(as the trade-off example below does) or from streams as :func:`~hensmith.problem_table` takes them. Several utilities on the grand composite curve @@ -85,10 +85,15 @@ and prices each with biosteam. :language: text Each agent has a **level** on the shifted scale: the warmest temperature it -can serve (the coldest, for a coolant), its own temperature less biosteam's -approach. Low pressure steam, first in biosteam's list of heating agents, is -at 407.19 K, 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. +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 @@ -151,8 +156,8 @@ target below is computed on. 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, to the - right of the hot one. The script checks that their knots are chapter 2's. + 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. .. figure:: /_static/images/examples/tutorial_05_balanced_composites.png :class: white-bg diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 65685e3..7a363bd 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -609,8 +609,8 @@ class PinchTargets: *Composite curves.* `composite_curves` adds up the streams on the table's own grid, whose knots hold every breakpoint of every stream, so the composites reproduce the table to rounding: the cold composite - starts at the cold utility target and ends the hot utility target to - the right of the hot one. On the shifted scale the composites touch at + starts at the cold utility target and ends the hot utility target + beyond the end of the hot one. On the shifted scale the composites touch at the pinch (if there is hot utility). On the real scale they are at least `T_min_app` apart, or, with approach contributions, at least the smallest hot and the smallest cold contribution apart (unspecified ones @@ -641,8 +641,8 @@ class PinchTargets: equality). Each agent is priced as one biosteam heat utility over the stretch it - serves, from its level to the next zero of the balanced curve (the - pinch, or a utility pinch). Its `T_in` and `T_out` are the real + serves, from its level (or the end of the grid, if that is nearer) to + the next zero of the balanced curve (the pinch, or a utility pinch). Its `T_in` and `T_out` are the real temperatures of the streams it serves there (the cold streams with heat above the pinch, or the hot streams with heat below it): the coldest and the hottest for heating, the hottest and the coldest for cooling. They @@ -703,12 +703,13 @@ class PinchTargets: one furnace for all fuel) [Linnhoff90]_ and costs each with biosteam's correlations at one pressure (biosteam takes each exchanger's highest inlet pressure: its pressure factor is up to about 12 % higher for - high pressure steam heaters): a double pipe below 150 ft² (at - biosteam's default CE of 567.5 and 1 atm, its base purchase cost of - about 3,211 USD just below 150 ft² jumps to 20,516 USD for a floating - head at 150 ft², and the installed cost from about 8,055 to 64,658 USD; - the floating head correlation, not monotone below about 126 ft², is not - used there), a shell and tube of any area above. The furnace is costed + high pressure steam heaters): a double pipe below 150 ft² (for carbon + steel at 1 atm and biosteam's default CE of 567.5, the purchase cost + jumps from about 5,486 USD just below 150 ft² to 20,138 USD for a + floating head at 150 ft², and the installed cost, the one used here, + from about 8,055 to 64,658 USD; the floating head correlation, not + monotone below about 126 ft², is not used there), a shell and tube of + any area above. The furnace is costed on the fuel's utility duty. `summary` adds the total annualized cost, the installed (bare module) cost times the capital recovery factor plus the utility cost times the operating hours: no Lang factor and no @@ -1202,8 +1203,8 @@ def utility_targets(self, heating_agents=None, cooling_agents=None): cool.append((-math.inf, remaining, None)) warn(f'no cooling agent can serve {remaining:.6g} kJ/hr of the ' 'cold utility target; it is left unserved', RuntimeWarning) - # the stretch each agent serves: from its level to the next zero of - # the balanced curve, the pinch or a utility pinch + # the stretch each agent serves: from its level, clipped to the grid, + # to the next zero of the balanced curve, the pinch or a utility pinch Ts, arr, lea = self._gcc() positions = _positions(Ts, arr, lea, [l for l, _, _ in heat + cool if math.isfinite(l)]) @@ -2277,7 +2278,7 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, file : str, optional If given, the figure is saved to this path. dpi : int, optional - Resolution used when saving. + Resolution used when saving. Defaults to 300. Returns ------- @@ -2452,7 +2453,7 @@ def plot_grand_composite_curve(targets, utilities=None, ax=None, file=None, file : str, optional If given, the figure is saved to this path. dpi : int, optional - Resolution used when saving. + Resolution used when saving. Defaults to 300. Returns ------- @@ -2565,7 +2566,7 @@ def plot_supertargets(sweep, ax=None, file=None, dpi=300): file : str, optional If given, the figure is saved to this path. dpi : int, optional - Resolution used when saving. + Resolution used when saving. Defaults to 300. Returns ------- From 28b41bdcf022a32bd82ae6a5c723d04c16b5b884 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 02:23:29 +0000 Subject: [PATCH 14/52] address review 1 (e2e): validate T_min_app, price facility targets over the system's hours, explain the flat threshold E2E-1 (fixed): PinchTargets.__init__, from_heat_utilities and _from_exchangers (and so at() and HeatExchangerNetwork.targets) now check T_min_app with supertarget's rule before any target is computed: a finite number >= 0, not a bool (> 0 with approach contributions, as before). nan, inf, -10, '5' and True raise a ValueError naming the value, where they used to return wrong energy targets or fail later. The docstrings say so. E2E-2 (fixed at the root): PinchTargets has a new settable attribute `system`, None by default. HeatExchangerNetwork.targets sets it to the facility's system, and at() carries it over. When neither operating_hours nor tea is given, summary (and so supertarget, through _check_options) reads the system's operating hours at call time, as network_report already did. A system with a TEA is priced at the TEA's hours everywhere, and supertarget(HXN.targets()) no longer optimizes for 8000 h. Without a system, or with a system that has no operating hours, nothing changes (the quickstart captures are unchanged). The tutorial (TAC bullet and report paragraph), concepts.rst, the summary, supertarget and HXN.targets docstrings, and the ch05 script comment now say this. E2E-3 (fixed): rewrote the tutorial paragraph and the figure caption. Below about 1.85 K the quickstart system is a threshold problem whose targets are flat. The difference of under 7 USD/yr (4e-7 of the TAC) between 1 and 1.848 K comes from where the problem table's grid falls on the curved profiles, not from physics, and any point of the stretch is an optimum. The real step is at 1.853 K, where the cold utility and a sixth unit add about 12000 USD/yr. The ch05 sweep table now prints that next point (regenerated ch05_sweep.txt by running ch05_targeting.py alone; no figure changed). supertarget's Notes and concepts.rst state that a flat stretch gives an arbitrary optimum. No tie tolerance was added, so the sweep's results stay as they are. E2E-4 (no change needed): already fixed by 1729e59 (tg-report). The fuel label stacks above the top flat's label when both are anchored at the end of the finite hot curve. Re-running the verifier's v_e4.py gives a fuel box (y 926-967 px) above the high pressure steam box (y 876-917 px), and test_plot_readability covers this case. Nits: E2E-7 (fixed): utility_targets, summary, supertarget and network_report accept agent IDs. Each ID is resolved among bst.HeatUtility.heating_agents (or cooling_agents). An unknown ID raises a ValueError that lists the known ones. A bare string or a single agent in place of a sequence raises a TypeError. E2E-8 (fixed): from_heat_utilities raises a ValueError if a heat utility has no unit (System.heat_utilities) and names the unit heat utilities to pass instead. E2E-9 (fixed): a sweep with no finite TAC is not refined, so it evaluates the given grid only and does not repeat warnings on nan. The existing RuntimeWarning (and its docs) says so. Supertargets keeps an optimum (the smallest point), because the warning already marks it as meaningless. E2E-10 (no change needed): already fixed by 48f0447 (tg-core). _isolated_pricing removes the temporary flowsheet from biosteam's registry on exit, and the reviewer's side.py now reports no change to the flowsheets. Tests: new test_T_min_app_validated, test_agents_by_ID and test_facility_targets_operating_hours, plus a refinement check in test_sweep. pytest tests/test_targeting.py hensmith/targeting.py hensmith/_heat_exchanger_network.py: 56 passed. Default-off oracle: sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (unchanged). sphinx -W (docs/requirements.txt, SPHINX_OFFLINE=1) of docs/source: clean. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/_demo_src/examples/ch05_targeting.py | 5 +- docs/source/_generated/ch05_sweep.txt | 1 + docs/source/concepts.rst | 17 ++- docs/source/tutorial/05_targeting.rst | 32 +++--- hensmith/_heat_exchanger_network.py | 17 ++- hensmith/targeting.py | 121 ++++++++++++++++++---- tests/test_targeting.py | 72 +++++++++++++ 7 files changed, 219 insertions(+), 46 deletions(-) diff --git a/docs/_demo_src/examples/ch05_targeting.py b/docs/_demo_src/examples/ch05_targeting.py index 3eb45cc..cd144b1 100644 --- a/docs/_demo_src/examples/ch05_targeting.py +++ b/docs/_demo_src/examples/ch05_targeting.py @@ -111,7 +111,7 @@ def main(): print('units:', targets.units_target(utilities)) capital = targets.capital_cost_target(utilities) print(f"{capital['N_shells']} shells of {capital['shell_area']:.4g} m2, {capital['type']}") - summary = targets.summary() # 8000 h/yr, 10 % interest over 10 years + summary = targets.summary() # 10 % over 10 years; 8000 h/yr (no system hours) for key, value in summary.items(): print(f'{key:<24} {value:.6g}') # [end:supertargets] @@ -122,8 +122,9 @@ def main(): 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: + 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}") diff --git a/docs/source/_generated/ch05_sweep.txt b/docs/source/_generated/ch05_sweep.txt index f956f81..bb7f153 100644 --- a/docs/source/_generated/ch05_sweep.txt +++ b/docs/source/_generated/ch05_sweep.txt @@ -3,6 +3,7 @@ T_min_app Q_hot Q_cold area units installed capital operating [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.848 2.808e+08 0 6836 5 2.482e+06 4.04e+05 1.454e+07 14943211 +1.853 2.808e+08 1981 6835 6 2.555e+06 4.158e+05 1.454e+07 14955185 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 diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 72fe63c..add32f9 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -492,17 +492,24 @@ years at interest :math:`i`, \qquad \mathrm{CRF} = \frac{i (1 + i)^n}{(1 + i)^n - 1}, with :math:`i` = 10 %, :math:`n` = 10 years and :math:`t_{op}` = 8000 h/yr -by default, or taken from a BioSTEAM TEA (``tea=``). It counts the bare module -cost of the exchangers only: no Lang factor and no working capital. +by default, or taken from a BioSTEAM TEA (``tea=``). The targets of a facility +(``HXN.targets()``) take :math:`t_{op}` from its system when the system has +operating hours (a TEA sets them), as the network report does. It counts the +bare module cost of the exchangers only: no Lang factor and no working capital. **The sweep.** :func:`~hensmith.supertarget` evaluates the targets over a grid of ``T_min_app`` values, reusing the streams' curves (nothing is flashed again), and then refines the grid around the least total annualized cost. The cost is not smooth -- the units target jumps, the double pipe switches to a shell and tube, the pinch moves -- so the refinement only narrows a bracket, -and ``at_bound`` flags an optimum at an end of the sweep. A sweep with no -finite total cost (an unserved utility everywhere, say) warns that its -optimum is meaningless. With approach +and ``at_bound`` flags an optimum at an end of the sweep. Where the targets do +not depend on ``T_min_app`` -- below the threshold of a threshold problem, the +utilities and the balanced composite curves stay the same -- the costs differ +only by the discretization of the curves, by a tiny relative amount (4e-7 +of the total cost for the quickstart system), so the optimum is an arbitrary +point of that flat stretch. A sweep with no finite +total cost (an unserved utility everywhere, say) is not refined and warns that +its optimum is meaningless. With approach contributions, only the unspecified streams follow ``T_min_app``; ``scale_contributions=True`` scales the specified ones with it, the contribution scaling of Ahmad, Linnhoff & Smith (1990). diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index 4824ee9..9958ffc 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -206,7 +206,9 @@ targets the rest of the cost of a network. (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. + ``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 ----------------------------------------------- @@ -228,15 +230,18 @@ each ``T_min_app``, then refines the grid around the least TAC. 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 -2 K the system becomes a threshold problem -- at 1 and 1.848 K it needs no -cold utility at all -- so the hot utility no longer changes and only the -area, which shrinks as the approach grows, still moves the TAC. The least -TAC is therefore at the threshold: the refinement places it at 1.848 K, the -last point evaluated before the cold utility appears (it is inside the -sweep, not at its bound), 6 USD/yr below the TAC at 1 K. From 2 K on, the -cold utility needs a unit of its own (6 units), and 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. +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.848 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 by 2e-5 of +itself. Every ``T_min_app`` of that flat stretch is an optimum; the refinement +happens to report the last one it evaluated, 1.848 K (inside the sweep, not at +its bound). The real 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. .. figure:: /_static/images/examples/tutorial_05_supertargets.png :class: white-bg @@ -247,7 +252,8 @@ inlet, the units target jumps to 9. 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.848 K. Capital is a small fraction of the TAC, so the optimum has the least - utility and, of the approaches that share it, the least area. + 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 @@ -324,7 +330,9 @@ network: larger area into equal shells. Neither is a bound on the other. The report's ``'targets'`` is :meth:`~hensmith.PinchTargets.summary` with the -same options; ``exchanger_areas`` gives both areas of each exchanger, which is +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 diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index fcdc6b4..7de31ee 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -965,14 +965,17 @@ def targets(self, T_min_app=None): Parameters ---------- T_min_app : float, optional - Minimum approach temperature [K]. Defaults to the facility's - `T_min_app`. + Minimum approach temperature [K], finite and >= 0 (> 0 with + approach contributions). Defaults to the facility's `T_min_app`. Returns ------- PinchTargets The targets, with the facility's `approach_contributions` and - `force_ideal_thermo`. + `force_ideal_thermo`, and its `system`, whose operating hours + (if it has them, a TEA's for instance) `PinchTargets.summary` + and `hensmith.targeting.supertarget` then use by default, as + `hensmith.targeting.network_report` does. Raises ------ @@ -1056,17 +1059,21 @@ def targets(self, T_min_app=None): else: # by unit only, which the network never changes contributions = self._network_contributions(hus) - return PinchTargets._from_exchangers( + targets = PinchTargets._from_exchangers( hxs, [i not in cold for i in range(len(hxs))], T_min_app, contributions, self.force_ideal_thermo, None, hus) + targets.system = self.system + return targets if self.system is None and not self.units: raise RuntimeError('simulate the system (or give the facility its ' 'units) before targeting') hx_utils = self._get_original_heat_utilties() hx_utils.sort(key=lambda x: x.duty) # as `_cost` sorts them - return PinchTargets.from_heat_utilities( + targets = PinchTargets.from_heat_utilities( hx_utils, T_min_app, self._network_contributions(hx_utils), self.force_ideal_thermo, self.sort_hus_by_T) + targets.system = self.system + return targets def get_original_hxs_associated_with_streams(self): # pragma: no cover original_units = self.system.units diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 7a363bd..0f61c71 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -503,6 +503,36 @@ def _pressure(P): if P <= 0.: raise ValueError(f'P must be > 0 Pa, not {P!r}') return P +def _check_T_min_app(T_min_app): + """A ValueError naming `T_min_app` unless it is a finite number >= 0 + [K] (not a bool), the rule of `supertarget`; > 0 with approach + contributions is checked with them (`_given_contributions`).""" + if _real(T_min_app, 'T_min_app') < 0.: + raise ValueError(f'T_min_app must be >= 0 K, not {T_min_app!r}') + +def _agents(agents, heating): + """`agents` as a list (None stays None: biosteam's lists, read when + used), each ID resolved among ``bst.HeatUtility.heating_agents`` (or + ``cooling_agents``) as they are now; a ValueError for an unknown ID.""" + if agents is None: return None + kind = 'heating' if heating else 'cooling' + if isinstance(agents, (str, bst.UtilityAgent)): + raise TypeError(f'{kind}_agents must be a sequence of agents (or ' + f'their IDs), not {type(agents).__name__!r}') + known = bst.HeatUtility.heating_agents if heating else bst.HeatUtility.cooling_agents + resolved = [] + for agent in agents: + if isinstance(agent, str): + match = [a for a in known if a.ID == agent] + if not match: + raise ValueError( + f'no {kind} agent {agent!r} in bst.HeatUtility.{kind}_' + f"agents ({', '.join(a.ID for a in known)}); pass the " + 'UtilityAgent itself for another one') + agent = match[0] + resolved.append(agent) + return resolved + def _economics(interest=None, years=None, operating_hours=None, tea=None): """ ``(crf, hours)``: the capital recovery factor [1/yr] and the operating @@ -550,7 +580,8 @@ class PinchTargets: is_hot : Sequence[bool] True where the stream is cooled. T_min_app : float, optional - Minimum approach temperature [K]. Defaults to 5. + Minimum approach temperature [K], a finite number >= 0 (> 0 with + approach contributions), or a ValueError is raised. Defaults to 5. approach_contributions : Sequence[float or None], optional Approach temperature contribution of each stream [K], in stream order, as `problem_table` takes them: None or nan (unspecified: @@ -603,6 +634,10 @@ class PinchTargets: force_ideal_thermo : bool Whether the stream copies use ideal thermodynamics (False when built from streams). + system : System or None + The system of the facility of `HeatExchangerNetwork.targets`, else + None (settable). If it has operating hours, `summary` (and so + `supertarget`) uses them unless `operating_hours` or `tea` is given. Notes ----- @@ -822,6 +857,7 @@ def __init__(self, streams_inlet, streams_quenched, is_hot, T_min_app=5., if is_hot.shape != (N,): raise ValueError(f'{is_hot.size} is_hot values given for {N} ' 'streams') + _check_T_min_app(T_min_app) x = approach_contributions if x is not None: if (not isinstance(x, (Sequence, np.ndarray)) @@ -861,6 +897,7 @@ def __init__(self, streams_inlet, streams_quenched, is_hot, T_min_app=5., self.heat_utilities = None self.units = None self.force_ideal_thermo = False + self.system = None #: Grid index of the pinch. self._k_pinch = int(np.flatnonzero(table.Ts == table.pinch_T)[0]) #: Threshold of every heat comparison [kJ/hr], the table's own. @@ -878,10 +915,13 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, Parameters ---------- hus : Iterable[HeatUtility] - Heat utilities of heat exchangers (``hu.unit``). Zero-duty ones - are dropped. + Heat utilities of heat exchangers (``hu.unit``), such as + ``[hu for u in sys.units for hu in u.heat_utilities]``, not + ``sys.heat_utilities`` (sums by agent, of no unit). Zero-duty + ones are dropped. T_min_app : float, optional - Minimum approach temperature [K]. Defaults to 5. + Minimum approach temperature [K], finite and >= 0 (> 0 with + approach contributions). Defaults to 5. approach_contributions : Mapping, Sequence or callable, optional Approach temperature contributions [K] in any form `synthesize_network` takes (a sequence is aligned with `hus` as @@ -901,6 +941,13 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, """ hus = list(hus) + _check_T_min_app(T_min_app) + if any(hu.unit is None for hu in hus): + raise ValueError('every heat utility must belong to a heat ' + 'exchanger (hu.unit): take those of the units, ' + '[hu for u in sys.units for hu in ' + 'u.heat_utilities], not System.heat_utilities ' + '(sums by agent, of no unit)') values = _explicit_contributions(hus, approach_contributions, T_min_app) arranged, N_heating = _arrange_heat_utilities(hus, sort_hus_by_T) if values is None: @@ -919,6 +966,7 @@ def _from_exchangers(cls, hxs, is_hot, T_min_app, approach_contributions, """Targets of the process streams of the exchangers `hxs` (stream order, contributions resolved in stream order), labeled by unit by default.""" + _check_T_min_app(T_min_app) streams_inlet, streams_quenched = _process_streams(hxs, force_ideal_thermo) if labels is None: labels = [_unit_label(hx) for hx in hxs] self = cls(streams_inlet, streams_quenched, is_hot, T_min_app, @@ -936,7 +984,8 @@ def at(self, T_min_app, scale_contributions=False): Parameters ---------- T_min_app : float - Minimum approach temperature [K]. + Minimum approach temperature [K], finite and >= 0 (> 0 with + approach contributions). scale_contributions : bool, optional Scale the specified approach contributions by ``T_min_app / self.T_min_app`` (the contribution scaling of contribution-based @@ -947,7 +996,8 @@ def at(self, T_min_app, scale_contributions=False): Returns ------- PinchTargets - With the same streams, curves, labels, units and heat utilities. + With the same streams, curves, labels, units, heat utilities and + system. """ c = self.approach_contributions @@ -960,6 +1010,7 @@ def at(self, T_min_app, scale_contributions=False): new.heat_utilities = self.heat_utilities new.units = self.units new.force_ideal_thermo = self.force_ideal_thermo + new.system = self.system return new def __repr__(self): @@ -1162,12 +1213,14 @@ def utility_targets(self, heating_agents=None, cooling_agents=None): Parameters ---------- - heating_agents : Sequence[UtilityAgent], optional - Heating agents in order of preference; fuel agents (``isfuel``) + heating_agents : Sequence[UtilityAgent or str], optional + Heating agents in order of preference, or the IDs of agents of + ``bst.HeatUtility.heating_agents``; fuel agents (``isfuel``) take the remainder, the first one only. Defaults to ``bst.HeatUtility.heating_agents`` (read at call time). - cooling_agents : Sequence[UtilityAgent], optional - Cooling agents in order of preference. Defaults to + cooling_agents : Sequence[UtilityAgent or str], optional + Cooling agents in order of preference, or the IDs of agents of + ``bst.HeatUtility.cooling_agents``. Defaults to ``bst.HeatUtility.cooling_agents``. Returns @@ -1183,10 +1236,10 @@ def utility_targets(self, heating_agents=None, cooling_agents=None): If no agent can serve part of the hot or cold utility target. """ - if heating_agents is None: heating_agents = bst.HeatUtility.heating_agents - if cooling_agents is None: cooling_agents = bst.HeatUtility.cooling_agents - heating_agents = list(heating_agents) - cooling_agents = list(cooling_agents) + heating_agents = _agents(heating_agents, True) + cooling_agents = _agents(cooling_agents, False) + if heating_agents is None: heating_agents = list(bst.HeatUtility.heating_agents) + if cooling_agents is None: cooling_agents = list(bst.HeatUtility.cooling_agents) tol = self._tol heat, remaining = self._allocate( self._levels([a for a in heating_agents if not a.isfuel], True), @@ -1610,15 +1663,21 @@ def capital_cost_target(self, utilities=None, h=None, h_utilities=None, heat_exchanger_type, material)[0] def _check_options(self, options): - """The options of `summary` (agents as lists), after raising for - any that `summary` would reject, before pricing anything.""" + """The options of `summary` (agents as lists of agents, the + operating hours of `system` filled in if it has them and neither + `operating_hours` nor `tea` is given), after raising for any that + `summary` would reject, before pricing anything.""" unknown = [i for i in options if i not in _SUMMARY_OPTIONS] if unknown: raise TypeError(f"unexpected options: {', '.join(map(repr, unknown))} " f'(the options are {", ".join(_SUMMARY_OPTIONS)})') options = dict(options) for key in ('heating_agents', 'cooling_agents'): - if options.get(key) is not None: options[key] = list(options[key]) + if options.get(key) is not None: + options[key] = _agents(options[key], key == 'heating_agents') + if options.get('operating_hours') is None and options.get('tea') is None: + hours = getattr(self.system, 'operating_hours', None) + if hours is not None: options['operating_hours'] = hours get = options.get _economics(get('interest'), get('years'), get('operating_hours'), get('tea')) @@ -1646,7 +1705,11 @@ def summary(self, **options): `years` (default 10) and `operating_hours` (per year, default 8000), each taken from `tea` when not given and `tea` is (its `IRR`, ``duration[1] - duration[0]`` and `operating_hours` - unless None). + unless None). Without `operating_hours` and `tea`, the + operating hours are those of `system` if it has them, as for + targets of `HeatExchangerNetwork.targets` in a system with + operating hours (a TEA's, for instance), so that they match + `network_report`. Returns ------- @@ -1724,8 +1787,8 @@ def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, ----- RuntimeWarning If no minimum approach temperature of the sweep has a finite TAC - (each has an unserved utility or an infinite area): the optimum is - then the smallest one, and meaningless. + (each has an unserved utility or an infinite area): the sweep is + then not refined, and its optimum is the smallest one, meaningless. Notes ----- @@ -1745,6 +1808,17 @@ def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, least TAC at the smallest or largest temperature evaluated is flagged (`Supertargets.at_bound`). + Where the targets do not depend on the minimum approach temperature + (below the threshold of a threshold problem, where neither the utilities + nor the balanced composite curves change), the TACs differ only by the + discretization of the streams' curves on the problem table's grid, by + a tiny relative amount, and the optimum is an arbitrary point of that + flat stretch: compare the TACs of its neighbors before reading anything + into its position. + + Unless `operating_hours` or `tea` is given, the operating hours are + those of ``targets.system`` if it has them (see `PinchTargets.summary`). + Examples -------- One hot stream cooled 400 -> 300 K and one cold stream heated 290 -> @@ -1817,6 +1891,8 @@ def evaluate(T_min_app): return targets.at(T_min_app, scale_contributions).summary(**options) records = [evaluate(float(x)) for x in np.unique(T)] for _ in range(refine): + if not any(math.isfinite(r['TAC']) for r in records): + break # nothing to refine around records.sort(key=lambda r: r['T_min_app']) i = _least_TAC(records) lo = records[max(i - 1, 0)]['T_min_app'] @@ -1829,8 +1905,9 @@ def evaluate(T_min_app): sweep = Supertargets(targets, records, options, scale_contributions) if not np.isfinite(sweep['TAC']).any(): warn('no minimum approach temperature of the sweep has a finite TAC ' - '(each has an unserved utility or an infinite area), so its ' - 'optimum, the smallest, is meaningless', RuntimeWarning) + '(each has an unserved utility or an infinite area), so it is ' + 'not refined and its optimum, the smallest, is meaningless', + RuntimeWarning) return sweep diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 4c027f9..3add9ff 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -1066,6 +1066,50 @@ def test_crf_and_tea(): with pytest.raises(ValueError, match='years must be > 0'): t.summary(years=-1) +def test_T_min_app_validated(kemp_hxn): + # supertarget's rule everywhere: finite and >= 0 (> 0 with approach + # contributions), before any target is computed + t = targets('test_targeting_T_min_app', REF_A, 10.) + units, HXN = kemp_hxn + for x, message in ((math.nan, 'a finite number'), (math.inf, 'a finite number'), + (-10., '>= 0 K'), ('5', 'a finite number'), + (True, 'a finite number')): + for build in (lambda: PinchTargets(t.streams_inlet, t.streams_quenched, + t.is_hot, x, curves=t.curves), + lambda: PinchTargets.from_heat_utilities(t.heat_utilities, x), + lambda: t.at(x), lambda: HXN.targets(x)): + with pytest.raises(ValueError, match=f'T_min_app must be {message}'): + build() + # 0 K is the limit (an infinite area); with contributions it is not allowed + assert t.at(0.).Q_hot == pytest.approx(40. * 3600., rel=1e-9) + with pytest.raises(ValueError, match='T_min_app must be finite and > 0'): + PinchTargets(t.streams_inlet, t.streams_quenched, t.is_hot, 0., [1., None]) + # the system's sums by agent belong to no exchanger + with pytest.raises(ValueError, match=r'must belong to a heat exchanger \(hu.unit\)'): + PinchTargets.from_heat_utilities(HXN.system.heat_utilities, 10.) + +def test_agents_by_ID(): + # the agents of utility_targets, summary and supertarget may be the IDs + # of biosteam's agents + t = targets('test_targeting_agent_IDs', REF_A, 10.) + heating = ['low_pressure_steam', 'high_pressure_steam'] + cooling = ['cooling_water', 'chilled_water'] + by_ID = dict(heating_agents=heating, cooling_agents=cooling) + agents = dict(heating_agents=[agent(i) for i in heating], + cooling_agents=[agent(i) for i in cooling]) + utilities = t.utility_targets(**by_ID) + assert [u.ID for u in utilities] == heating + cooling + assert utilities == t.utility_targets(**agents) + assert t.summary(**by_ID) == t.summary(**agents) + sweep = supertarget(t, [10.], refine=0, **by_ID) + assert sweep.options['heating_agents'] == agents['heating_agents'] + with pytest.raises(ValueError, match="no heating agent 'cooling_water'"): + t.utility_targets(['cooling_water']) + with pytest.raises(ValueError, match="no cooling agent 'steam'"): + t.summary(cooling_agents=['steam']) + with pytest.raises(TypeError, match='heating_agents must be a sequence'): + t.utility_targets('low_pressure_steam') + def test_optimum_T_min_app_closed_form(): from biosteam.units.heat_exchange import (compute_double_pipe_purchase_cost, compute_floating_head_purchase_cost, p2) @@ -1172,6 +1216,7 @@ def flash(*args, **kwargs): raise AssertionError('the curves are rebuilt') 'sweep has a finite TAC'): unserved = supertarget(t, [5., 10.], refine=1, cooling_agents=[]) assert np.all(np.isnan(unserved['TAC'])) and unserved.T_min_app == 5. + assert len(unserved.records) == 2 # not refined around nan with warnings.catch_warnings(record=True) as caught: warnings.simplefilter('always') supertarget(t, [5., 10.], refine=0) @@ -1285,6 +1330,33 @@ def f(hu): HXN.approach_contributions = {'H2': 3.} assert HXN.targets().approach_contributions == (None, None, 3., None) +def test_facility_targets_operating_hours(kemp_hxn): + # the facility's targets price over its system's operating hours (read + # at call time), as the report does, unless operating_hours or tea is + # given + units, HXN = kemp_hxn + sys = HXN.system + previous = sys.operating_hours + try: + sys.operating_hours = None + t = HXN.targets() + assert t.system is sys and t.at(20.).system is sys + s = t.summary() + assert s['operating_cost'] == 8000. * s['utility_cost'] + sys.operating_hours = 8760. + s = t.summary() + assert s['operating_cost'] == 8760. * s['utility_cost'] + assert network_report(HXN)['targets'] == pytest.approx(s, rel=1e-12) + for r in supertarget(t, [5., 10.], refine=0).records: + assert r['operating_cost'] == 8760. * r['utility_cost'] + assert t.summary(operating_hours=100.)['operating_cost'] == 100. * s['utility_cost'] + tea = types.SimpleNamespace(IRR=.1, duration=(0, 10), operating_hours=7920.) + assert t.summary(tea=tea)['operating_cost'] == 7920. * s['utility_cost'] + t.system = None # settable: the default 8000 h again + assert t.summary()['operating_cost'] == 8000. * s['utility_cost'] + finally: + sys.operating_hours = previous + #: Keys of `network_report`. REPORT_KEYS = ['T_min_app', 'targets_T_min_app', 'status', 'applied', 'Q_hot', 'Q_cold', 'utilities', 'utility_cost', 'N_units', 'N_process_units', 'N_utility_units', From c62f0f3c29b8f67dd61dbc41fc7f49773973cc9b Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 03:57:08 +0000 Subject: [PATCH 15/52] address review 2 (f1-facility): match unit keys by identity, read the units option once F1F2-1 (fixed): a unit key named every unit with its (top owner ID, ID), so a key on one auxiliary of an auxiliary also named its namesakes under other intermediate owners (the heaters of two auxiliary flash drums), and two such keys conflicted. A unit key now names that very unit; only a key that is no longer one of the units at hand (a stale auxiliary its owner rebuilt) names the unit of the same ID path (its ID and those of all its owners), so a rebuilt HXutilities exchanger still matches, also nested. The same rule applies to the film coefficient keys of targets (h). Conflict and value messages name a nested unit with its intermediate owners. The docstrings, concepts.rst and the API page say that a tuple key, with the top owner, names every auxiliary of that ID under it, and that the heater objects or the drums tell them apart. New test test_contribution_keys_on_nested_auxiliary_units (two-flash unit, keys and conflicts, h keys of targets, the facility, a stale auxiliary of an auxiliary). F1F2-2 (fixed): _cost and targets evaluate the units option once and pass it to both the heat utilities and the candidate units, so a callable is called once per simulation and a one-shot iterable is not exhausted before the keys are checked. F1F2-3 (fixed): new test test_contribution_keys_with_the_units_option (list, callable and one-shot iterable units=: a key on the idle D1.pump simulates, a key on D1_H2 outside units= raises 'name no unit', the callable is called once). Tests: test_hxn_contributions.py 42 passed; test_targeting.py -k "bath or film or h_ or facility or targets" 20 passed; doctests of hxn_synthesis, _heat_exchanger_network and targeting 11 passed; oracle sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (unchanged); sphinx -W (SPHINX_OFFLINE=1) clean. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/API/heat_exchanger_network.rst | 2 +- docs/source/concepts.rst | 33 +++--- hensmith/_heat_exchanger_network.py | 58 +++++---- hensmith/hxn_synthesis.py | 88 +++++++++----- hensmith/targeting.py | 14 ++- tests/test_hxn_contributions.py | 132 ++++++++++++++++++++- 6 files changed, 252 insertions(+), 75 deletions(-) diff --git a/docs/source/API/heat_exchanger_network.rst b/docs/source/API/heat_exchanger_network.rst index 1659727..ec6eb93 100644 --- a/docs/source/API/heat_exchanger_network.rst +++ b/docs/source/API/heat_exchanger_network.rst @@ -72,7 +72,7 @@ each of them changes. - Allow a process stream to be split into parallel branches that re-join. A side of the pinch that no unsplit network serves at MER is planned with splits and reaches the targets exactly on the planner's knots; sides that an unsplit network serves are never split, so a problem that needs no split gets the same network as with the default. Each split is a chain of ``Splitter`` units and a rigorous ``Mixer`` (adiabatic, no cost), listed in ``new_splitters`` and ``new_mixers``. With ``avoid_recycle``, a split that would repeat a stream pair is not used, and MER is then not guaranteed. Stored as an attribute of the same name, and part of the ``cache_network`` key. Defaults to False. * - ``approach_contributions`` - Mapping or callable - - Approach temperature contribution of each process stream, in K: a match between hot stream *h* and cold stream *c* must keep :math:`c_h + c_c` instead of ``T_min_app`` everywhere inside it, and a stream without a contribution contributes ``T_min_app / 2``; the utility exchangers keep BioSTEAM's own approach rule. A mapping's keys name units -- a unit, the ID of a unit without an owner (``'D1'``), or ``(owner.ID, unit.ID)`` for an auxiliary unit (``('D1', 'condenser')``) -- and a heat utility takes the value of the key nearest to its unit along the unit's chain of owners (two keys of the same level must agree); ID keys stay valid when a unit rebuilds its auxiliaries, and heat utility keys are rejected (``TypeError``). A callable ``f(hu)`` is called once per heat utility at every simulation and should be pure; a sequence raises a ``TypeError``. A value is None or nan (unspecified, an explicit default at its level) or a finite number > 0. Keys and values are checked against every available unit and its auxiliaries before any filtering, so an unknown key, a bad value or a conflict raises a ``ValueError`` even while its unit is idle, but a valid key whose unit holds no heat utility in a simulation has no effect. The resolved values are part of the ``cache_network`` key. May be changed between simulations. Defaults to None (every stream contributes ``T_min_app / 2``, so every pair requires ``T_min_app``). + - Approach temperature contribution of each process stream, in K: a match between hot stream *h* and cold stream *c* must keep :math:`c_h + c_c` instead of ``T_min_app`` everywhere inside it, and a stream without a contribution contributes ``T_min_app / 2``; the utility exchangers keep BioSTEAM's own approach rule. A mapping's keys name units -- a unit, the ID of a unit without an owner (``'D1'``), or ``(owner.ID, unit.ID)`` for an auxiliary unit (``('D1', 'condenser')``; with the top owner, so it names every auxiliary of that ID under it) -- and a heat utility takes the value of the key nearest to its unit along the unit's chain of owners (two keys of the same level must agree); ID keys stay valid when a unit rebuilds its auxiliaries, and heat utility keys are rejected (``TypeError``). A callable ``f(hu)`` is called once per heat utility at every simulation and should be pure; a sequence raises a ``TypeError``. A value is None or nan (unspecified, an explicit default at its level) or a finite number > 0. Keys and values are checked against every available unit and its auxiliaries before any filtering, so an unknown key, a bad value or a conflict raises a ``ValueError`` even while its unit is idle, but a valid key whose unit holds no heat utility in a simulation has no effect. The resolved values are part of the ``cache_network`` key. May be changed between simulations. Defaults to None (every stream contributes ``T_min_app / 2``, so every pair requires ``T_min_app``). Class attributes ---------------- diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index add32f9..165e2c9 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -268,20 +268,25 @@ contribution. **Keys in the facility.** :class:`~hensmith.HeatExchangerNetwork` takes a mapping or a callable, since the order of its streams is internal. A mapping's -keys name units: a unit object (matched by identity, or by its owner's ID -and its own), the ID of a unit without an owner (``'D1'``), or -``(owner.ID, unit.ID)`` for an auxiliary unit (``('D1', 'condenser')``; a -bare auxiliary ID such as ``'condenser'`` names nothing). A heat utility -takes the value of -the key nearest to its unit along the unit's chain of owners, so ``'D1'`` -covers a column's condenser and reboiler and ``('D1', 'condenser')`` -overrides it for the condenser. A None or nan value is an explicit default -that wins at its level like any value, and two keys of the same level for a -unit must agree. ID keys are the robust form: a unit that rebuilds its -auxiliary units at every simulation (``biosteam.HXutilities``, for one) -leaves a key holding an old auxiliary object to be matched by its owner's ID -and its own. Heat utilities cannot be keys, because BioSTEAM replaces them -at every simulation; a value per heat utility is given by a callable +keys name units: a unit object (matched by identity), the ID of a unit +without an owner (``'D1'``), or ``(owner.ID, unit.ID)`` for an auxiliary +unit (``('D1', 'condenser')``; a bare auxiliary ID such as ``'condenser'`` +names nothing). The ``owner`` of a pair is the top owner, so a pair names +every auxiliary of that ID under it, at any depth: if a unit holds two +auxiliary flash drums, each with its own ``heat_exchanger``, the pair +``('U', 'heat_exchanger')`` names both heaters, and the heater objects +or the drums (``('U', 'flash_a')``) tell them apart. A heat utility takes +the value of the key nearest to its unit along the unit's chain of owners, +so ``'D1'`` covers a column's condenser and reboiler and +``('D1', 'condenser')`` overrides it for the condenser. A None or nan value +is an explicit default that wins at its level like any value, and two keys +of the same level for a unit must agree. ID keys are the robust form: a +unit that rebuilds its auxiliary units at every simulation +(``biosteam.HXutilities``, for one) leaves a key holding an old auxiliary +object, which then names the auxiliary that took its place: the one of the +same ID under owners of the same IDs. Heat utilities cannot be keys, +because BioSTEAM replaces them at every simulation; a value per heat +utility is given by a callable ``f(hu)`` instead, which is called once per heat utility at every simulation and should therefore be pure (after a simulation, ``HXN.targets()`` takes the values that simulation resolved instead of calling it again, since diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index 7de31ee..f7f5d21 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -282,16 +282,20 @@ class HeatExchangerNetwork(bst.Facility): agent as usual. A key names a unit: the unit itself, the ID of a unit without an owner (e.g. ``'D1'``), or ``(owner.ID, unit.ID)`` for an auxiliary unit (e.g. ``('D1', 'condenser')``; a bare auxiliary ID names - nothing). A heat utility takes the value of the key nearest to its unit - along the unit's chain of owners, so ``'D1'`` covers the condenser and - the reboiler of column D1 and ``('D1', 'condenser')`` overrides it for - the condenser; a None or nan value is an explicit default that wins at - its level like any other, and two keys of the same level for a unit - must agree. ID keys are the robust form: a unit that rebuilds its - auxiliary units at every simulation (e.g. `biosteam.HXutilities`) - leaves a key holding an old auxiliary to match by its owner's ID and - its own (heat utility keys are rejected, since BioSTEAM replaces them at - every simulation). The keys and values are checked against every unit + nothing). ``owner`` is the top owner, so the pair names every auxiliary + of that ID under it: the heaters of two auxiliary flash drums of one + unit, say, are told apart by keying the heater objects or the drums. A + heat utility takes the value of the key nearest to its unit along the + unit's chain of owners, so ``'D1'`` covers the condenser and the + reboiler of column D1 and ``('D1', 'condenser')`` overrides it for the + condenser; a None or nan value is an explicit default that wins at its + level like any other, and two keys of the same level for a unit must + agree. ID keys are the robust form: a unit that rebuilds its auxiliary + units at every simulation (e.g. `biosteam.HXutilities`) leaves a key + holding an old auxiliary, which then names the auxiliary that took its + place (the one of the same ID under owners of the same IDs; heat + utility keys are rejected, since BioSTEAM replaces them at every + simulation). The keys and values are checked against every unit available to the network and its nested auxiliary units before the heat utilities are taken: a key that names none of them, a bad value or two conflicting keys raise a ValueError, even while their unit is @@ -498,24 +502,26 @@ def _get_units(self): units = sys.units return units - def _candidate_units(self): + def _candidate_units(self, units=None): """The units that approach contribution keys may name: each unit of - `_get_units` followed by its nested auxiliary units, whether or not - it holds a heat utility the network takes.""" + `units` (defaults to `_get_units`) followed by its nested auxiliary + units, whether or not it holds a heat utility the network takes.""" + if units is None: units = self._get_units() candidates = [] - for unit in self._get_units(): + for unit in units: candidates.append(unit) candidates.extend([i for i in unit.nested_auxiliary_units if isinstance(i, bst.Unit)]) return candidates - def _network_contributions(self, hx_utils): + def _network_contributions(self, hx_utils, units=None): """ The explicit approach contributions of `hx_utils` (aligned with it; None where unspecified), or None if none is given (see `hensmith.hxn_synthesis._explicit_contributions`). The keys are - checked against every candidate unit (`_candidate_units`), so a bad - key or value raises even while its unit holds no heat utility. + checked against every candidate unit (`_candidate_units` of `units`, + the units `hx_utils` were taken from if given), so a bad key or + value raises even while its unit holds no heat utility. """ x = self.approach_contributions if x is None: return None @@ -526,10 +532,10 @@ def _network_contributions(self, hx_utils): f'has nothing to align with), not ' f'{type(x).__name__!r}') return _explicit_contributions(hx_utils, x, self.T_min_app, - units=self._candidate_units()) + units=self._candidate_units(units)) - def _get_original_heat_utilties(self): - units = self._get_units() + def _get_original_heat_utilties(self, units=None): + if units is None: units = self._get_units() ignored = self.ignored if ignored: if callable(ignored): ignored = ignored() @@ -603,10 +609,13 @@ def _cost(self): flowsheet = bst.Flowsheet(sys.ID + '_HXN') with flowsheet.temporary(): self._restore_unit_heat_utilities() - hx_utils = self._get_original_heat_utilties() + # once per simulation, for both the heat utilities and the keys (a + # callable `units` is called, an iterable read, once) + units = list(self._get_units()) + hx_utils = self._get_original_heat_utilties(units) # the explicit approach contributions by heat utility (resolved # once per simulation; None without any) - values = self._network_contributions(hx_utils) + values = self._network_contributions(hx_utils, units) value_of = (None if values is None else {id(hu): v for hu, v in zip(hx_utils, values)}) use_cached_network = False @@ -1067,10 +1076,11 @@ def targets(self, T_min_app=None): if self.system is None and not self.units: raise RuntimeError('simulate the system (or give the facility its ' 'units) before targeting') - hx_utils = self._get_original_heat_utilties() + units = list(self._get_units()) + hx_utils = self._get_original_heat_utilties(units) hx_utils.sort(key=lambda x: x.duty) # as `_cost` sorts them targets = PinchTargets.from_heat_utilities( - hx_utils, T_min_app, self._network_contributions(hx_utils), + hx_utils, T_min_app, self._network_contributions(hx_utils, units), self.force_ideal_thermo, self.sort_hus_by_T) targets.system = self.system return targets diff --git a/hensmith/hxn_synthesis.py b/hensmith/hxn_synthesis.py index 8b5452d..d3a5704 100644 --- a/hensmith/hxn_synthesis.py +++ b/hensmith/hxn_synthesis.py @@ -769,26 +769,52 @@ def _pinch_cut(table): return 'below' if arriving[k] <= table.residual[k] else 'above' def _unit_name(unit): - """``(owner.ID, unit.ID)``: how messages name a unit (``(ID, ID)`` for - a unit without an owner).""" + """``(owner.ID, unit.ID)``, with the top owner (``(ID, ID)`` for a unit + without an owner): the unit's tuple key.""" return unit.owner.ID, unit.ID -def _names_unit(key, unit): - """Whether the approach contribution key `key` (of a valid type, see - `_check_contribution_key`) names `unit`: the unit itself, or one with - its owner's ID and its ID (a stale auxiliary that its owner rebuilt); - ``(owner.ID, unit.ID)``; or the ID of a unit without an owner.""" +def _id_path(unit): + """The IDs of `unit` and of its owners in turn, ``(unit.ID, + unit._owner.ID, ..., top.ID)``, which tell apart auxiliary units of the + same ID under different owners.""" + path = [] + while unit is not None: + path.append(unit.ID) + unit = getattr(unit, '_owner', None) + return tuple(path) + +def _unit_message_name(unit): + """How messages name a unit: its tuple key (`_unit_name`), followed by + its owners below the top one when there are any (the tuple key of an + auxiliary of an auxiliary is shared by its namesakes).""" + path = _id_path(unit) + label = repr(_unit_name(unit)) + if len(path) <= 2: return label + return f"{label} (of {' of '.join(map(repr, path[1:-1]))})" + +def _names_unit(key, unit, live=()): + """ + Whether the approach contribution key `key` (of a valid type, see + `_check_contribution_key`) names `unit`. A unit key names itself; a unit + key that is not a unit at hand (its id not in `live`, the ids of the + units at hand: a stale auxiliary that its owner rebuilt) names the unit + of the same `_id_path` instead. A tuple key names every unit of that + `_unit_name` (every auxiliary of that ID under that top owner, at any + depth) and a str key the unit of that ID without an owner. + """ if isinstance(key, bst.Unit): - return key is unit or _unit_name(key) == _unit_name(unit) + if key is unit: return True + return id(key) not in live and _id_path(key) == _id_path(unit) if isinstance(key, tuple): return key == _unit_name(unit) return key == unit.ID and unit.owner is unit -def _contribution_key_level(key, unit): +def _contribution_key_level(key, unit, live=()): """The number of steps along the owner chain of `unit` (`unit`, - ``unit._owner``, ...) to the first unit that `key` names, or None.""" + ``unit._owner``, ...) to the first unit that `key` names (see + `_names_unit`, which takes `live`), or None.""" level = 0 while unit is not None: - if _names_unit(key, unit): return level + if _names_unit(key, unit, live): return level unit = getattr(unit, '_owner', None) level += 1 return None @@ -823,18 +849,20 @@ def _mapping_contributions(hus, mapping, units): `_stream_contributions` of a mapping from keys naming units to contributions: checks the types of keys and values, then that every key names a unit of the universe (the owner chains of the units of `hus` - and of `units`), then that no two keys of the same level for a unit of - the universe give it different values (None and nan are equal); then - each heat utility takes the value of the key of the lowest level on its - unit's owner chain (None without one). + and of `units`, the units at hand of `_names_unit`), then that no two + keys of the same level for a unit of the universe give it different + values (None and nan are equal); then each heat utility takes the value + of the key of the lowest level on its unit's owner chain (None without + one). """ items = [] for key, value in mapping.items(): _check_contribution_key(key) items.append((key, _contribution_value(value, f'key {key!r}'))) universe = _owner_chains([hu.unit for hu in hus] + list(units or ())) + live = {id(u) for u in universe} unknown = [key for key, _ in items - if not any(_names_unit(key, u) for u in universe)] + if not any(_names_unit(key, u, live) for u in universe)] if unknown: raise ValueError('approach_contributions keys that name no unit: ' f"{', '.join(map(repr, unknown))} (a key is a unit, " @@ -843,19 +871,19 @@ def _mapping_contributions(hus, mapping, units): for unit in universe: first = {} # level -> (key, value) for key, value in items: - level = _contribution_key_level(key, unit) + level = _contribution_key_level(key, unit, live) if level is None: continue other, other_value = first.setdefault(level, (key, value)) if other_value != value: raise ValueError( f'approach_contributions keys {other!r} and {key!r} ' - f'give unit {_unit_name(unit)!r} different values at the ' - f'same level: {other_value!r} and {value!r}') + f'give unit {_unit_message_name(unit)} different values ' + f'at the same level: {other_value!r} and {value!r}') values = [] for hu in hus: best = None # (level, value) of the nearest key for key, value in items: - level = _contribution_key_level(key, hu.unit) + level = _contribution_key_level(key, hu.unit, live) if level is not None and (best is None or level < best[0]): best = (level, value) values.append(None if best is None else best[1]) @@ -887,7 +915,8 @@ def _stream_contributions(hus, approach_contributions, units=None): return [_contribution_value(v, f'heat utility {k}') for k, v in enumerate(values)] if callable(x): - return [_contribution_value(x(hu), f'unit {_unit_name(hu.unit)!r}') + return [_contribution_value(x(hu), + f'unit {_unit_message_name(hu.unit)}') for hu in hus] raise TypeError('approach_contributions must be a mapping, a sequence or ' f'a callable, not {type(x).__name__!r}') @@ -2055,12 +2084,17 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, / 2``) or a finite number > 0 (`T_min_app` must then be finite and > 0 too). Given as one of: - - a mapping ``{key: value}`` whose keys name units: a unit (the - same object, or one with the same ID and owner's ID: a stale - auxiliary that its owner rebuilt), ``(owner.ID, unit.ID)`` (the - unit's own ID twice for a unit without an owner) or the ID of a - unit without an owner (a bare auxiliary ID such as - ``'condenser'`` names nothing). A heat utility takes the value of + - a mapping ``{key: value}`` whose keys name units: a unit (that + very object; a stale auxiliary that its owner rebuilt, no longer + among the units of `hus` and their owners, names the one of the + same ID under owners of the same IDs instead), + ``(owner.ID, unit.ID)`` with the top owner (the unit's own ID + twice for a unit without an owner; it names every auxiliary of + that ID under that owner, at any depth, so the heaters of two + auxiliary flash drums are told apart by keying the drums or the + heater objects) or the ID of a unit without an owner (a bare + auxiliary ID such as ``'condenser'`` names nothing). A heat + utility takes the value of the key nearest to its unit along the unit's chain of owners: a unit's own key beats its owner's, which beats the owner's owner's (e.g. ``'D1'`` covers the condenser and the reboiler of column diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 0f61c71..a1f2c96 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -30,7 +30,8 @@ from ._planner import _approach_contributions, _given_contributions from .hxn_synthesis import (_problem_table, _pinch_cut, _format_H, _arrange_heat_utilities, _process_streams, _explicit_contributions, - _contribution_key_level, _names_unit, _owner_chains, _unit_name) + _contribution_key_level, _names_unit, _owner_chains, + _unit_message_name) __all__ = ('PinchTargets', 'supertarget', 'Supertargets', 'network_report', 'plot_composite_curves', 'plot_grand_composite_curve', @@ -1438,6 +1439,7 @@ def _film_mapping(self, h): N = self.is_hot.size units = self.units or () universe = _owner_chains(units) + live = {id(u) for u in universe} items = [] unknown = [] for key, value in h.items(): @@ -1455,7 +1457,7 @@ def _film_mapping(self, h): raise TypeError('h keys must be stream indices (int), units, ' 'unit IDs (str) or (owner.ID, unit.ID) tuples, ' f'not {type(key).__name__!r} ({key!r})') - elif not any(_names_unit(key, u) for u in universe): + elif not any(_names_unit(key, u, live) for u in universe): unknown.append(key) items.append((key, _film_value(value, f'h of key {key!r}'))) if unknown: @@ -1468,14 +1470,14 @@ def _film_mapping(self, h): for unit in universe: first = {} # level -> (key, value) for key, value in unit_items: - level = _contribution_key_level(key, unit) + level = _contribution_key_level(key, unit, live) if level is None: continue other, other_value = first.setdefault(level, (key, value)) if other_value != value: raise ValueError( f'h keys {other!r} and {key!r} give unit ' - f'{_unit_name(unit)!r} different values at the same ' - f'level: {other_value!r} and {value!r}') + f'{_unit_message_name(unit)} different values at the ' + f'same level: {other_value!r} and {value!r}') values = np.full(N, _H_DEFAULT) for j in range(N): best = None # (level, value) of the nearest key @@ -1483,7 +1485,7 @@ def _film_mapping(self, h): if isinstance(key, Integral): level = -1 if key == j else None else: - level = _contribution_key_level(key, units[j]) + level = _contribution_key_level(key, units[j], live) if level is not None and (best is None or level < best[0]): best = (level, value) if best is not None: values[j] = best[1] diff --git a/tests/test_hxn_contributions.py b/tests/test_hxn_contributions.py index fbbb8e8..750b047 100644 --- a/tests/test_hxn_contributions.py +++ b/tests/test_hxn_contributions.py @@ -38,8 +38,9 @@ stream's pinch temperature is the shifted pinch plus its own shift. D. Keys and validation: the value domain (None or nan, else a finite number > 0); the mapping, sequence and callable forms; keys naming - units, auxiliary units through their owners (the nearest key wins) and - stale auxiliaries; unknown keys, conflicting keys and bad values, + units, auxiliary units through their owners (the nearest key wins), + auxiliaries of one ID under different owners and stale auxiliaries, and + the units option; unknown keys, conflicting keys and bad values, checked before any stream is flashed and, in the facility, even for idle units. E. The ``cache_network`` rule: a cached network is reused while the @@ -51,7 +52,7 @@ import pytest import biosteam as bst from numpy.testing import assert_allclose -from hensmith import hxn_synthesis, HeatExchangerNetwork +from hensmith import hxn_synthesis, HeatExchangerNetwork, PinchTargets from hensmith import _planner as P from hensmith.hxn_synthesis import problem_table, synthesize_network, _pinch_cut from test_hxn import (cp_units, KEMP, R002 as R002_UNITS, planned_split, @@ -762,6 +763,104 @@ def f(hu): assert hxn_synthesis._stream_contributions(stale_hus, {old: 2., 'U': 3.}) \ == [2.] + [3.] * (len(stale_hus) - 1) +class TwoFlash(bst.Unit): + """Two auxiliary flash drums, each with its own auxiliary heater: both + heaters have the tuple key ``(ID, 'heat_exchanger')``.""" + _N_ins = 2 + _N_outs = 4 + auxiliary_unit_names = ('flash_a', 'flash_b') + + def _init(self): + self.auxiliary('flash_a', bst.Flash, ins=self.ins[0], + outs=self.outs[:2], V=0.5, P=101325) + self.auxiliary('flash_b', bst.Flash, ins=self.ins[1], + outs=self.outs[2:], V=0.3, P=101325) + + def _run(self): + self.flash_a.simulate() + self.flash_b.simulate() + + def _design(self): pass + def _cost(self): pass + +class NestedHeaters(bst.Unit): + """A `biosteam.HXutilities` as an auxiliary unit: its exchangers, rebuilt + at every simulation, are auxiliaries of an auxiliary.""" + _N_ins = 1 + _N_outs = 1 + auxiliary_unit_names = ('hxs',) + + def _init(self): + self.auxiliary('hxs', bst.HXutilities, ins=self.ins[0], + outs=self.outs[0], T=500., rigorous=False) + + def _run(self): self.hxs.simulate() + def _design(self): pass + def _cost(self): pass + +def test_contribution_keys_on_nested_auxiliary_units(): + # a unit key names that very unit, not a namesake under another owner; + # a tuple key names every auxiliary of that ID under the top owner + bst.main_flowsheet.set_flowsheet('contributions_nested') + bst.settings.set_thermo(['Water', 'Ethanol'], cache=True) + a = bst.Stream('a', Water=100., Ethanol=20., units='kmol/hr', T=300.) + b = bst.Stream('b', Water=150., Ethanol=10., units='kmol/hr', T=300.) + U = TwoFlash('U', ins=(a, b)) + hot = bst.Stream('hot', Water=400., units='kmol/hr', T=430., P=6e5) + C1 = bst.HXutility('C1', ins=hot, T=320., rigorous=False) + bst.System.from_units('contributions_nested', units=[U, C1]).simulate() + ha, hb = U.flash_a.heat_exchanger, U.flash_b.heat_exchanger + hus = bst.process_tools.heat_exchanger_utilities_from_units([U, C1]) + assert [hu.unit for hu in hus] == [ha, hb, C1] + assert ha.ID == hb.ID == 'heat_exchanger' + + def resolve(x): + return hxn_synthesis._stream_contributions(hus, x) + + assert resolve({ha: 2.}) == [2., None, None] + assert resolve({ha: 2., hb: 6.}) == [2., 6., None] + assert resolve({('U', 'flash_b'): 6., ha: 2.}) == [2., 6., None] + assert resolve({('U', 'flash_a'): 2., ('U', 'flash_b'): 6.}) == [ + 2., 6., None] + assert resolve({('U', 'heat_exchanger'): 2.}) == [2., 2., None] + assert resolve({'U': 3., ha: 2.}) == [2., 3., None] + # a conflict names the unit with its owners below the top one + with pytest.raises(ValueError, match=r"give unit \('U', 'heat_exchanger'" + r"\) \(of 'flash_b'\) different"): + resolve({('U', 'heat_exchanger'): 2., hb: 6.5, ha: 2.}) + with pytest.raises(ValueError, match=r"unit \('U', 'heat_exchanger'\) " + r"\(of 'flash_a'\) must be"): + resolve(lambda hu: 0. if hu.unit is ha else None) + # film coefficient keys of targets resolve the same way + t = PinchTargets.from_heat_utilities(hus, 10.) + j = next(j for j, unit in enumerate(t.units) if unit is ha) + area = t.area_target(h={ha: 2.}) + assert area == t.area_target(h={j: 2.}) + assert area != t.area_target(h={('U', 'heat_exchanger'): 2.}) + # in the facility + HXN = HeatExchangerNetwork('HXN', T_min_app=10.) + sys = bst.System.from_units('contributions_nested_facility', + units=[U, C1, HXN]) + for x, given in [({ha: 2.}, {ha: 2.}), ({ha: 2., hb: 6.}, {ha: 2., hb: 6.})]: + HXN.approach_contributions = x + sys.simulate() + assert [given.get(hx, 5.) for hx in HXN.original_heat_exchangers] == ( + HXN.synthesis_info['approach_contributions'].tolist()) + # a stale auxiliary of an auxiliary names the one that took its place, + # and only that one + bst.settings.set_thermo(['Water'], cache=True) + water = bst.Stream('water', Water=100., T=300., P=5e6, units='kmol/hr') + V = NestedHeaters('V', ins=water) + V.simulate() + old = V.hxs.heat_exchangers[0] + V.simulate() + stale_hus = bst.process_tools.heat_exchanger_utilities_from_units([V]) + assert len(stale_hus) > 1 + assert all(hu.unit is not old for hu in stale_hus) + assert stale_hus[0].unit.ID == old.ID + assert hxn_synthesis._stream_contributions( + stale_hus, {old: 2., 'V': 3.}) == [2.] + [3.] * (len(stale_hus) - 1) + def test_contribution_keys_in_the_facility(): # the facility, simulated twice: every key resolves to the hand values D1, D1_H1, D1_H2, F1 = quickstart('contributions_keys_facility') @@ -902,6 +1001,33 @@ def test_invalid_contributions_in_the_facility(): assert network_record(HXN) == reference assert HXN._resolved_contributions is None +def test_contribution_keys_with_the_units_option(): + # keys may name the units of the units option and their auxiliary units, + # idle or not, but no other unit; a callable option is called once per + # simulation, and a one-shot iterable is read once + D1, D1_H1, D1_H2, F1 = quickstart('contributions_units_option') + calls = [] + def listed(): + calls.append(None) + return [D1, D1_H1, F1] + x = {'D1': 1., F1: 1., 'D1_H1': 4.} + hand = {'condenser': 1., 'reboiler': 1., 'heat_exchanger': 1., 'D1_H1': 4.} + for k, option in enumerate([[D1, D1_H1, F1], listed, + iter([D1, D1_H1, F1])]): + HXN = HeatExchangerNetwork(f'HXN_{k}', T_min_app=5., units=option, + approach_contributions=x | {D1.pump: 2.}) + sys = bst.System.from_units(f'contributions_units_option_{k}', + units=[D1, D1_H1, D1_H2, F1, HXN]) + simulate_strictly(sys) + assert HXN.synthesis_info['status'] == 'mer' # observed + assert HXN.synthesis_info['approach_contributions'].tolist() == [ + hand[hx.ID] for hx in HXN.original_heat_exchangers] + if isinstance(option, list): + HXN.approach_contributions = x | {'D1_H2': 2.} + with pytest.raises(ValueError, match=r"name no unit: 'D1_H2'"): + sys.simulate() + assert len(calls) == 1 + # --------------------------------------------------------------------------- # E. Cache # --------------------------------------------------------------------------- From f2bc9cde2ed8d2665d369599b857d54608d93042 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 04:23:34 +0000 Subject: [PATCH 16/52] address review 2 (tg-core): serve point loads at stretch ends, cap levels only by streams in reach TGC2-1 (fixed). _ends decided which streams a utility serves with the strict test (top > lo) & (bottom < hi). That dropped a stream whose only heat in the stretch is a point load at its end, such as a boiler at the pinch with the cut 'above' or a condenser at it with the cut 'below'. The same mask filtered the streams given to _line_limit. With no stream left, _ends returned the stretch's shifted ends as real temperatures. In case_two_stream low pressure steam reported T_out = 395 K, H1's shifted inlet; C1 leaves at 373.124 K. With contributions a sensible agent was also priced inside dT of the stream it serves (the cooling water of a condenser at 320 K left at 319 K, 0.154 instead of 0.218 USD/hr). Membership now comes from each stream's heat in the stretch. The new _position_H gives each stream's enthalpy at the positions of the grand composite curve; it is factored out of units_target, which is unchanged bit for bit. A point load at a grid temperature counts where the stretch holds both its arriving and its leaving position, as the balanced curve does. The streams with heat above the tolerance are kept, or those with any heat if none has more. If there are none, a RuntimeError is raised; shifted ends are never returned. The served streams' arrays go to _line_limit unchanged. The no-zero fallback, unreachable for an allocated agent, now ends the stretch at the pinch's position, so that lo and hi are always positions (_pinch_position, shared with units_target). TGC2-2 (fixed). _levels capped a level at R - s_j for every stream whose top passes R - s_j (heating), even one lying wholly beyond its limit, which no level short of it serves. Its contribution then moved duty to a dearer agent (LP steam 36 -> 33.2 kW, cooling water 27 -> 23.6 kW). Now such a stream caps the level only at its own bottom (top, for cooling), 2e-9 K short of it (_T_SHORT, beyond the snapping distance) if it has a point load there, since a level at a grid temperature serves that temperature's point loads. The level stays within the highest of the served streams' limits (the lowest, for cooling). Without contributions the level is still R (R - T_min_app for cooling), bit for bit. The class Notes, the _levels docstring and concepts.rst describe this rule. test_contributions recomputes the levels independently with it. TGC2-3 (nit, applied). _agents raises a ValueError for an agent of the other kind: a heating agent must be isheating_agent or isfuel, a cooling one iscooling_agent and not a fuel. Steam passed as a coolant used to be priced as a negative cost. Documented under Parameters and Raises of utility_targets. TGC2-4 (nit, applied). The tests' min_driving_force skips stretches no wider than 1e-9 of the heat, as _bath skips slivers. New tests: - test_point_loads_at_stretch_ends: case_two_stream's LP steam at C1's outlet temperature. A boiler at the pinch with a hot oil, and a condenser at the pinch with cooling water, each with no, one and two contributions: the served temperatures, the agent outlet dT away, the cost against an independent pricing, a driving force of the balanced composites >= min(process gap, dT), and the RuntimeError. - test_levels_of_streams_beyond_reach: B wholly above LP steam's limit, then wholly below cooling water's, at three contributions, gives the same levels, duties and costs as without them. A boiler wholly beyond LP steam's limit keeps the level just short of it. - test_agents_by_ID: covers the kind checks. The new tests and the kind checks fail on the previous code. Validation: pytest tests/test_targeting.py together with test_hxn_contributions.py::test_contribution_keys_on_nested_auxiliary_units passed (51 tests, 20.7 s). pytest hensmith/targeting.py ran 6 doctests, all passing. 150 random constant-CP cases with contributions, run with biosteam's agents and with a hot oil, showed no balanced driving force below min(process gap, dT) and no error. docs/_demo_src/examples/ ch05_targeting.py was rerun alone and changed no capture or figure. Python 3.12 syntax (ast.parse) checks out. The synthesis path is untouched: the oracle gives sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf, which matches. sphinx is not installed in this environment, so sphinx -W was not run; the docs changes are prose and a numpydoc Raises section. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/concepts.rst | 16 ++- hensmith/targeting.py | 220 +++++++++++++++++++++++++-------------- tests/test_targeting.py | 135 +++++++++++++++++++++++- 3 files changed, 280 insertions(+), 91 deletions(-) diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 165e2c9..8522a37 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -380,8 +380,11 @@ On the shifted scale that limit is the agent's **level**: the limit itself for a heating agent and the limit less ``T_min_app`` for a cooling agent, which is BioSTEAM's rule exactly. With approach contributions the streams have different shifts, and an agent's level is the highest single shifted -temperature (the lowest, for cooling) at which it serves no stream beyond that -stream's own real limit; streams with slack are then served conservatively. +temperature (the lowest, for cooling), up to the highest of the streams' own +limits, at which it serves no stream beyond that stream's own real limit. A +level serves only what lies below it (above it, for cooling), so a stream +lying wholly beyond its limit bounds the level only at the stream's own end; +streams with slack are then served conservatively. **List order and fuel.** Non-fuel agents are placed in the order of BioSTEAM's lists, BioSTEAM's own "first feasible agent" rule: for the default @@ -401,9 +404,12 @@ curve** (the cascade net of the utilities), which is the pinch or a *utility pinch* where a lower agent is used to its limit. A target's ``T_in`` and ``T_out`` are the real temperatures of the streams it serves in that stretch: the coldest and the hottest for heating, the hottest -and the coldest for cooling. Without approach contributions they are the -stretch's ends where streams reach them, and in every case ``T_out`` is within -the agent's limit. BioSTEAM prices a latent agent and fuel from them. +and the coldest for cooling. A point load at an end of the stretch counts +where the stretch holds it: a boiler at the pinch, whose latent heat the cut +puts above it, is served at its boiling temperature. Without approach contributions +``T_in`` and ``T_out`` are the stretch's ends where streams reach them, and in +every case ``T_out`` is within the agent's limit. BioSTEAM prices a latent +agent and fuel from them. A **sensible** agent, such as cooling water, warms (or cools) along a line as it takes its duty, and BioSTEAM checks that line only at the process inlet: a diff --git a/hensmith/targeting.py b/hensmith/targeting.py index a1f2c96..813e8cc 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -43,6 +43,11 @@ #: just below a utility level strictly between two grid temperatures. _ARRIVING, _LEAVING, _ABOVE, _BELOW = 0, 1, 2, 3 +#: How far short of a grid temperature a utility level is placed to serve +#: none of that temperature's point loads [K]: beyond `_T_EQ`, so that the +#: level is not snapped back onto it. +_T_SHORT = 2 * _T_EQ + #: Square feet per square meter, biosteam's own factor in `HX._design`. _FT2_PER_M2 = 10.763 @@ -514,9 +519,11 @@ def _check_T_min_app(T_min_app): def _agents(agents, heating): """`agents` as a list (None stays None: biosteam's lists, read when used), each ID resolved among ``bst.HeatUtility.heating_agents`` (or - ``cooling_agents``) as they are now; a ValueError for an unknown ID.""" + ``cooling_agents``) as they are now; a ValueError for an unknown ID or + an agent of the other kind (a heating agent is ``isheating_agent`` or + ``isfuel``, a cooling one ``iscooling_agent`` and not ``isfuel``).""" if agents is None: return None - kind = 'heating' if heating else 'cooling' + kind, other = ('heating', 'cooling') if heating else ('cooling', 'heating') if isinstance(agents, (str, bst.UtilityAgent)): raise TypeError(f'{kind}_agents must be a sequence of agents (or ' f'their IDs), not {type(agents).__name__!r}') @@ -531,6 +538,11 @@ def _agents(agents, heating): f"agents ({', '.join(a.ID for a in known)}); pass the " 'UtilityAgent itself for another one') agent = match[0] + if heating: right = agent.isfuel or agent.isheating_agent + else: right = agent.iscooling_agent and not agent.isfuel + if not right: + raise ValueError(f'{agent.ID!r} is not a {kind} agent; pass it in ' + f'{other}_agents') resolved.append(agent) return resolved @@ -663,10 +675,15 @@ class PinchTargets: dT`` and a cooling agent real temperatures above ``R = agent.T + agent.dT + dT`` (``dT = bst.HeatUtility.dT``, read at call time). On the shifted scale a stream with shift s may take an agent's heat only up to - ``R - s``; the agent's level is the highest single level that serves no - stream above its own limit (lowest for cooling): ``R`` for heating and - ``R - T_min_app`` for cooling without approach contributions, which is - biosteam's rule; with contributions it can be conservative for streams + ``R - s`` (down to it, for cooling); the agent's level is the highest + single level, up to the highest of these limits, that serves no stream + above its own limit (the lowest, down to the lowest limit, for + cooling): ``R`` for heating and ``R - T_min_app`` for cooling without + approach contributions, which is biosteam's rule. A level serves only + what lies below it (above it, for cooling), so a stream reaching beyond + its limit bounds the level at that limit, but one lying wholly beyond + it only at its own end (just short of it, if it has a point load + there). With contributions the level can be conservative for streams with slack. Non-fuel agents are placed in list order (biosteam's first feasible agent: for the default lists the lowest steam and the warmest coolant first), each as much as the curve above its level (below it, for @@ -678,12 +695,16 @@ class PinchTargets: Each agent is priced as one biosteam heat utility over the stretch it serves, from its level (or the end of the grid, if that is nearer) to - the next zero of the balanced curve (the pinch, or a utility pinch). Its `T_in` and `T_out` are the real - temperatures of the streams it serves there (the cold streams with heat - above the pinch, or the hot streams with heat below it): the coldest and - the hottest for heating, the hottest and the coldest for cooling. They - are the stretch's ends without approach contributions (where a stream - reaches them), and `T_out` is within the agent's limit. biosteam prices + the next zero of the balanced curve (the pinch, or a utility pinch). Its + `T_in` and `T_out` are the real temperatures of the streams with heat + in that stretch (the cold streams for heating, the hot ones for + cooling): the coldest and the hottest for heating, the hottest and the + coldest for cooling. A point load at an end of the stretch counts where + the stretch holds it, as the balanced curve does: a boiler at the pinch + with the cut 'above', or a condenser at it with the cut 'below', is + served at its saturation temperature. They are the stretch's ends + without approach contributions (where a stream reaches them), and + `T_out` is within the agent's limit. biosteam prices a latent agent and fuel from them (the flue leaves at least ``dT`` above `T_in`), but checks a sensible agent's outlet only against `T_in`, while its line must keep ``dT`` from the process over the whole @@ -1096,32 +1117,37 @@ def _side_heat(self): def _levels(self, agents, heating): """ ``[(agent, level)]``: each agent's shifted level, the highest - (lowest for cooling) that serves no stream beyond its own real - limit (see Notes of the class). + (lowest for cooling), up to the highest of the served streams' + shifted limits (down to the lowest), that serves no stream beyond + its own real limit (see Notes of the class). """ dT = bst.HeatUtility.dT grid = self.grid - Ts, s = grid['Ts'], self.shift - above, below = self._side_heat() - if heating: - streams = [j for j in np.flatnonzero(~self.is_hot) if above[j] > self._tol] - ends = Ts[grid['k_hi']] - else: - streams = [j for j in np.flatnonzero(self.is_hot) if below[j] > self._tol] - ends = Ts[grid['k_lo']] + Ts = grid['Ts'] + j = self._served(heating) + s = self.shift[j] + top, bottom = Ts[grid['k_hi'][j]], Ts[grid['k_lo'][j]] + # the point load at the end a level beyond the stream would reach + # first: its bottom (heating) or its top (cooling) + k = grid['k_lo' if heating else 'k_hi'][j] + point = np.abs(self.table.point_H[j, k]) > self._tol levels = [] for agent in agents: + # a stream reaching beyond its limit caps the level at the limit, + # or, lying wholly beyond it, at its own end (just short of it if + # a point load is there): a level serves only what lies below it + # (above it, for cooling) if heating: R = agent.T - agent.dT - dT - reached = [R - s[j] for j in streams if ends[j] > R - s[j]] - if reached: L = min(reached) - elif streams: L = R - min(s[j] for j in streams) + limit = R - s + caps = np.maximum(limit, bottom - _T_SHORT * point)[top > limit] + if j.size: L = caps.min(initial=limit.max()) else: L = R else: R = agent.T + agent.dT + dT - reached = [R - s[j] for j in streams if ends[j] < R - s[j]] - if reached: L = max(reached) - elif streams: L = R - max(s[j] for j in streams) + limit = R - s + caps = np.minimum(limit, top + _T_SHORT * point)[bottom < limit] + if j.size: L = caps.max(initial=limit.min()) else: L = R - self.T_min_app levels.append((agent, float(L))) return levels @@ -1154,34 +1180,63 @@ def _allocate(self, levels, Q, heating): return allocated, remaining def _served(self, heating): - """``(top, bottom, shift)`` of the streams a heating agent can serve - (the cold streams with heat above the pinch) or a cooling one (the - hot streams with heat below it): shifted temperatures [K] and shifts - [K].""" - grid = self.grid + """Indices of the streams a heating agent can serve (the cold streams + with heat above the pinch) or a cooling one (the hot streams with + heat below it).""" above, below = self._side_heat() - if heating: j = np.flatnonzero(~self.is_hot & (above > self._tol)) - else: j = np.flatnonzero(self.is_hot & (below > self._tol)) - Ts = grid['Ts'] - return Ts[grid['k_hi'][j]], Ts[grid['k_lo'][j]], self.shift[j] + if heating: return np.flatnonzero(~self.is_hot & (above > self._tol)) + return np.flatnonzero(self.is_hot & (below > self._tol)) + + def _position_H(self, positions): + """Each stream's enthalpy [kJ/hr] at the `positions` (`_positions`), + between a virtual top and a virtual bottom one: (N, P + 2), position + p in column p + 1. The heat of a stream between two columns is the + heat it has between those positions, point loads included where the + arriving and the leaving position of their grid temperature both lie + between them.""" + T, _, kind, k = positions + Ts, Hl, Hr = self.grid['Ts'], self.grid['Hl'], self.grid['Hr'] + H = np.where(kind == _ARRIVING, Hr[:, k], Hl[:, k]) + inner = np.flatnonzero((kind == _ABOVE) | (kind == _BELOW)) + if inner.size: + ki = k[inner] + f = (T[inner] - Ts[ki + 1]) / (Ts[ki] - Ts[ki + 1]) + H[:, inner] = Hr[:, ki + 1] + (Hl[:, ki] - Hr[:, ki + 1]) * f + return np.column_stack([Hr[:, 0], H, Hl[:, -1]]) + + def _pinch_position(self, positions): + """Index of the process pinch among the `positions`: the heat + arriving at the pinch temperature if the cut is 'below', the heat + leaving it if 'above'.""" + T, _, kind, _ = positions + return int(np.flatnonzero( + (T == self.grid['Ts'][self._k_pinch]) + & (kind == (_ARRIVING if self.cut == 'below' else _LEAVING)))[0]) - def _ends(self, served, lo, hi, heating): + def _ends(self, H, a, b, lo, hi, heating): """ - ``((T_in, T_out), overlap)`` of a utility serving shifted [lo, hi]: - the real temperatures [K] of the `served` streams overlapping the - stretch (`overlap`, a mask), the coldest and the hottest (heating; - the hottest and the coldest for cooling); the stretch's ends plus 0 - (heating) or `T_min_app` (cooling) if none overlaps it. + ``((T_in, T_out), (top, bottom, shift))`` of a utility serving the + heat between columns `a` and `b` of `H` (`_position_H`), shifted [lo, + hi]: the real temperatures [K] of the streams it serves there (the + cold ones with heat there above the tolerance, or the hot ones for + cooling; any heat if no stream has more), the coldest and the hottest + (heating; the hottest and the coldest for cooling), and their shifted + ends [K] and shifts [K]. A stream served there only at a point load + at an end of the stretch counts at that point load's temperature. """ - top, bottom, s = served - overlap = (top > lo) & (bottom < hi) - if not overlap.any(): - if heating: return (lo, hi), overlap - return (hi + self.T_min_app, lo + self.T_min_app), overlap - s = s[overlap] - cold = float(np.min(np.maximum(bottom[overlap], lo) + s)) - hot = float(np.max(np.minimum(top[overlap], hi) + s)) - return ((cold, hot) if heating else (hot, cold)), overlap + heat = np.abs(H[:, b] - H[:, a]) + heat[self.is_hot if heating else ~self.is_hot] = 0. + j = np.flatnonzero(heat > self._tol) + if not j.size: j = np.flatnonzero(heat > 0.) + if not j.size: + raise RuntimeError(f'no stream has heat in the stretch [{lo:.6g}, ' + f'{hi:.6g}] K (shifted) of a utility') + grid = self.grid + Ts, s = grid['Ts'], self.shift[j] + top, bottom = Ts[grid['k_hi'][j]], Ts[grid['k_lo'][j]] + cold = float(np.min(np.maximum(bottom, lo) + s)) + hot = float(np.max(np.minimum(top, hi) + s)) + return ((cold, hot) if heating else (hot, cold)), (top, bottom, s) def _line_limit(self, agent, heating, q, T, B, served): """ @@ -1215,12 +1270,14 @@ def utility_targets(self, heating_agents=None, cooling_agents=None): Parameters ---------- heating_agents : Sequence[UtilityAgent or str], optional - Heating agents in order of preference, or the IDs of agents of - ``bst.HeatUtility.heating_agents``; fuel agents (``isfuel``) - take the remainder, the first one only. Defaults to - ``bst.HeatUtility.heating_agents`` (read at call time). + Heating agents (``isheating_agent``) in order of preference, or + the IDs of agents of ``bst.HeatUtility.heating_agents``; fuel + agents (``isfuel``) take the remainder, the first one only. + Defaults to ``bst.HeatUtility.heating_agents`` (read at call + time). cooling_agents : Sequence[UtilityAgent or str], optional - Cooling agents in order of preference, or the IDs of agents of + Cooling agents (``iscooling_agent``, not fuels) in order of + preference, or the IDs of agents of ``bst.HeatUtility.cooling_agents``. Defaults to ``bst.HeatUtility.cooling_agents``. @@ -1231,6 +1288,12 @@ def utility_targets(self, heating_agents=None, cooling_agents=None): remainder, last), then the cooling ones. Agents with no duty are left out. + Raises + ------ + ValueError + If an ID names no agent of biosteam's list, or an agent is not + of the kind of its list. + Warns ----- RuntimeWarning @@ -1266,37 +1329,42 @@ def utility_targets(self, heating_agents=None, cooling_agents=None): [(l, q) for l, q, _ in cool]) zero = B <= tol T = positions[0] + # each stream's enthalpy at the positions: its heat in a stretch, the + # point loads of a grid temperature at its end included only where + # the stretch holds them (as the balanced curve does) + H = self._position_H(positions) + pinch = self._pinch_position(positions) dT = bst.HeatUtility.dT utilities = [] with _isolated_pricing([a for *_, a in heat + cool if a is not None]): - served = self._served(True) for level, q, agent in heat: + # positions g to end - 1 (columns g to end of H) g = _gap(positions, level) z = np.flatnonzero(zero[g:]) - end = g + z[0] + 1 if z.size else T.size - lo = float(T[end - 1]) if z.size else self.pinch_T + end = g + int(z[0]) + 1 if z.size else pinch + 1 + lo = float(T[end - 1]) hi = min(level, float(Ts[0])) - (T_in, T_out), overlap = self._ends(served, lo, hi, True) + (T_in, T_out), served = self._ends(H, g, end, lo, hi, True) T_priced = T_in if _sensible(agent): limit = self._line_limit(agent, True, q, T[g:end], B[g:end], - [x[overlap] for x in served]) + served) if limit is not None: T_priced = max(T_in, limit - dT) utilities.append(self._utility(agent, True, level, q, T_in, T_out, T_priced)) - served = self._served(False) for level, q, agent in cool: + # positions start to g - 1 (columns start + 1 to g + 1 of H) g = _gap(positions, level) z = np.flatnonzero(zero[:g]) - start = int(z[-1]) if z.size else 0 - hi = float(T[start]) if z.size else self.pinch_T + start = int(z[-1]) if z.size else pinch + hi = float(T[start]) lo = max(level, float(Ts[-1])) - (T_in, T_out), overlap = self._ends(served, lo, hi, False) + (T_in, T_out), served = self._ends(H, start + 1, g + 1, lo, hi, + False) T_priced = T_in if _sensible(agent): limit = self._line_limit(agent, False, q, T[start:g], - B[start:g], - [x[overlap] for x in served]) + B[start:g], served) if limit is not None: T_priced = min(T_in, limit + dT) utilities.append(self._utility(agent, False, level, q, T_in, T_out, T_priced)) @@ -1554,24 +1622,14 @@ def units_target(self, utilities=None): """ if utilities is None: utilities = self.utility_targets() positions, B = self._balanced_positions(utilities) - T, _, kind, k = positions - P = T.size + P = positions[0].size tol = self._tol - Ts, Hl, Hr = self.grid['Ts'], self.grid['Hl'], self.grid['Hr'] # each stream's cumulative enthalpy at every position, between the # virtual top and bottom ones - H = np.where(kind == _ARRIVING, Hr[:, k], Hl[:, k]) - inner = np.flatnonzero((kind == _ABOVE) | (kind == _BELOW)) - if inner.size: - ki = k[inner] - f = (T[inner] - Ts[ki + 1]) / (Ts[ki] - Ts[ki + 1]) - H[:, inner] = Hr[:, ki + 1] + (Hl[:, ki] - Hr[:, ki + 1]) * f - H = np.column_stack([Hr[:, 0], H, Hl[:, -1]]) + H = self._position_H(positions) cuts = [-1, *np.flatnonzero(B <= tol).tolist(), P] gaps = [_gap(positions, u.level) for u in utilities if u.duty > tol] - pinch = int(np.flatnonzero( - (T == Ts[self._k_pinch]) - & (kind == (_ARRIVING if self.cut == 'below' else _LEAVING)))[0]) + pinch = self._pinch_position(positions) # the members of each region (streams j, utilities -1 - i), grouped: # consecutive regions of one gap between two zeros (where the curve # stays zero, each piece balanced on its own) on one side of the diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 3add9ff..580d026 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -116,7 +116,10 @@ def min_vertical_gap(hot_T, hot_H, cold_T, cold_H): def min_driving_force(hot_T, hot_H, cold_T, cold_H): """Smallest T_hot - T_cold over every stretch of positive heat between the breakpoints of two piecewise-linear curves (a vertical run carries - no heat: each stretch is evaluated inside the segments it lies in).""" + no heat: each stretch is evaluated inside the segments it lies in; a + sliver of rounding, where the two curves' vertical runs end at heats + that differ only by rounding, would pair one curve's run with the + other's, and is skipped, as `_bath` skips slivers).""" end = min(hot_H[-1], cold_H[-1]) X = np.unique(np.concatenate([hot_H, cold_H])) X = X[(X >= 0) & (X <= end)] @@ -124,6 +127,7 @@ def T_at(T, H, i, x): return T[i] + (T[i + 1] - T[i]) * (x - H[i]) / (H[i + 1] - H[i]) gaps = [] for x, y in zip(X[:-1], X[1:]): + if y - x <= 1e-9 * end: continue m = 0.5 * (x + y) i = np.searchsorted(hot_H, m, 'right') - 1 j = np.searchsorted(cold_H, m, 'right') - 1 @@ -375,14 +379,17 @@ def test_contributions(): bottom = np.array([min(s['T_in'], s['T_out']) for s in streams]) - shift dT = bst.HeatUtility.dT LP, CW = agent('low_pressure_steam'), agent('cooling_water') + # a stream reaching beyond its limit caps the level at the limit, one + # wholly beyond it at its own end (constant-CP streams: no point loads), + # all within the served streams' limits R = LP.T - LP.dT - dT above = [j for j in range(N) if not hot[j] and top[j] > pinch] - reached = [R - shift[j] for j in above if top[j] > R - shift[j]] - L_LP = min(reached) if reached else R - min(shift[j] for j in above) + L_LP = min([max(R - shift[j] for j in above)] + + [max(R - shift[j], bottom[j]) for j in above if top[j] > R - shift[j]]) R = CW.T + CW.dT + dT below = [j for j in range(N) if hot[j] and bottom[j] < pinch] - reached = [R - shift[j] for j in below if bottom[j] < R - shift[j]] - L_CW = max(reached) if reached else R - max(shift[j] for j in below) + L_CW = max([min(R - shift[j] for j in below)] + + [min(R - shift[j], top[j]) for j in below if bottom[j] < R - shift[j]]) utilities = by_ID(tc.utility_targets()) assert set(utilities) == {'low_pressure_steam', 'cooling_water'} assert utilities['low_pressure_steam'].level == L_LP @@ -777,6 +784,112 @@ def test_pricing_temperatures(): assert u.T_in >= u.T_out and u.T_out - dT >= a.T + a.dT - 1e-6 if a.T_limit: assert u.T_utility_out <= u.T_in - dT + 1e-6 +def water_boiler(ID, T_sat, kmol_hr): + """Saturated liquid water boiled to saturated vapour at `T_sat` [K]: a + point load alone (water thermo and flowsheet set by the caller).""" + P = bst.settings.chemicals.Water.Psat(T_sat) + liquid = bst.Stream(ID + '_liquid', Water=kmol_hr, T=T_sat, P=P, phase='l') + boiler = bst.HXutility(ID, ins=liquid, V=1., rigorous=True) + boiler.simulate() + return boiler + +def test_point_loads_at_stretch_ends(): + # a stream served only at a point load at an end of a stretch (a boiler + # at the pinch with the cut 'above', a condenser at it with the cut + # 'below') is served there: the targets give its temperature, not a + # stretch end (shifted, with approach contributions), and a sensible + # agent keeps dT from it, as biosteam requires and the balanced + # composites need + dT = bst.HeatUtility.dT + # the reference case: C1 leaves boiled at the pinch, not at H1's inlet + units, T_min_app = test_hxn_targets.case_two_stream() + t = targets_of(units, T_min_app) + T_sat = units[1].outs[0].T + LP = by_ID(t.utility_targets())['low_pressure_steam'] + assert t.cut == 'above' + assert (LP.T_in, LP.T_out) == (pytest.approx(T_sat, abs=1e-9),) * 2 + # a hot oil serving a boiler at the pinch leaves dT above it + test_hxn_targets.setup('test_targeting_boiler', ['Water']) + H1 = test_hxn_targets.utility_hx('H1', 395., 5e5, 'l', 300., Water=100.) + C1 = water_boiler('C1', bst.settings.chemicals.Water.Tb, 10.) + T_b = C1.outs[0].T + oil = bst.UtilityAgent('test_targeting_boiler_oil', Water=1, T=480., P=5e6, + phase='l', thermo=bst.HeatUtility.thermo_water, + T_limit=300., heat_transfer_price=1e-6) + for c in ({}, {'C1': 3.}, {'C1': 3., 'H1': 1.}): + t = targets_of([H1, C1], 10., approach_contributions=c) + assert t.cut == 'above' + utilities = t.utility_targets([oil], None) + u = utilities[0] + assert u.ID == oil.ID and u.duty == pytest.approx(t.Q_hot, rel=1e-12) + assert (u.T_in, u.T_out) == (pytest.approx(T_b, abs=1e-9),) * 2 + assert u.T_utility_out == pytest.approx(T_b + dT, abs=1e-9) + assert u.cost == pytest.approx(fresh_price(u.duty, T_b, T_b, oil)[0], rel=1e-9) + gap = c.get('H1', 5.) + c.get('C1', 5.) + curves = t.balanced_composite_curves(utilities) + assert min_driving_force(*curves) >= min(gap, dT) - 1e-9 + # cooling water serving a condenser at the pinch leaves dT below it + test_hxn_targets.setup('test_targeting_condenser', ['Water']) + P = bst.settings.chemicals.Water.Psat(320.) + vapour = bst.Stream('test_targeting_condenser_vapour', Water=10., T=335., P=P, + phase='g') + COND = bst.HXutility('COND', ins=vapour, V=0., rigorous=True) + liquid = bst.Stream('test_targeting_condenser_liquid', Water=100., T=300., P=3e5) + C2 = bst.HXutility('C2', ins=liquid, T=330., rigorous=False) + COND.simulate(); C2.simulate() + T_c, CW = COND.outs[0].T, agent('cooling_water') + for c in ({}, {'COND': 1.}, {'COND': 1., 'C2': 1.}): + t = targets_of([COND, C2], 10., approach_contributions=c) + assert t.cut == 'below' + utilities = t.utility_targets() + u = by_ID(utilities)['cooling_water'] + assert u.duty == pytest.approx(t.Q_cold, rel=1e-12) + assert (u.T_in, u.T_out) == (pytest.approx(T_c, abs=1e-9),) * 2 + assert u.T_utility_out == pytest.approx(T_c - dT, abs=1e-9) + assert u.cost == pytest.approx(fresh_price(-u.duty, T_c, T_c, CW)[0], rel=1e-9) + gap = c.get('COND', 5.) + c.get('C2', 5.) + curves = t.balanced_composite_curves(utilities) + assert min_driving_force(*curves) >= min(gap, dT) - 1e-9 + # no stream with heat in a stretch is an internal error, never a + # stretch end taken for a temperature + with pytest.raises(RuntimeError, match='no stream has heat in the stretch'): + t._ends(np.zeros((2, 3)), 0, 2, 300., 310., True) + +def test_levels_of_streams_beyond_reach(): + # a level serves only what lies below it (above it, for cooling): a + # stream lying wholly beyond an agent's limit bounds the level only at + # its own end, so its contribution moves no level (it moved duty to a + # dearer agent). B lies above low pressure steam's limit, then below + # cooling water's. + cases = [[('A', 330., 406., 1.), ('B', 420., 430., 1.), ('H1', 380., 300., 1.)], + [('A', 340., 313., 1.), ('B', 300., 290., 1.), ('C1', 350., 420., 1.)]] + for n, rows in enumerate(cases): + base = targets(f'test_targeting_beyond_{n}', rows, 10.).utility_targets() + for c in (1., 2.5, 4.9): + t = targets(f'test_targeting_beyond_{n}_{c}', rows, 10., + approach_contributions={'B': c}) + utilities = t.utility_targets() + assert [(u.ID, u.level) for u in utilities] == [(u.ID, u.level) for u in base] + assert_allclose([u.duty for u in utilities], [u.duty for u in base], rtol=1e-12) + assert_allclose([u.cost for u in utilities], [u.cost for u in base], rtol=1e-9) + # a boiler wholly beyond low pressure steam's limit, below the limit of + # the stream it serves: the level stays just short of the boiler (a + # level at a grid temperature serves its point loads) + test_hxn_targets.setup('test_targeting_beyond_boiler', ['Water']) + A = test_hxn_targets.utility_hx('A', 330., 6e5, 'l', 406., Water=50.) + B = water_boiler('B', 409., 5.) + H = test_hxn_targets.utility_hx('H', 380., 6e5, 'l', 300., Water=50.) + t = targets_of([A, B, H], 10., approach_contributions={'B': 1.}) + level = B.ins[0].T - t.shift[t.labels.index('B')] # where B boils, shifted: 405 K + dT, LP = bst.HeatUtility.dT, agent('low_pressure_steam') + utilities = by_ID(t.utility_targets()) + u, MP = utilities['low_pressure_steam'], utilities['medium_pressure_steam'] + assert level - 1e-8 < u.level < level < LP.T - LP.dT - dT + assert u.T_out == u.level and u.T_out + dT <= LP.T - LP.dT + assert (MP.T_in, MP.T_out) == (pytest.approx(level, abs=1e-8), + pytest.approx(B.outs[0].T, abs=1e-9)) + assert u.duty + MP.duty == pytest.approx(t.Q_hot, rel=1e-12) + # %% Area, units, capital and total annualized cost def test_bath_balanced_pair(quickstart): @@ -1109,6 +1222,18 @@ def test_agents_by_ID(): t.summary(cooling_agents=['steam']) with pytest.raises(TypeError, match='heating_agents must be a sequence'): t.utility_targets('low_pressure_steam') + # an agent of the other kind (steam as a coolant would be priced as a + # negative cost), or a fuel as a coolant + LP, CW, gas = agent('low_pressure_steam'), agent('cooling_water'), agent('natural_gas') + with pytest.raises(ValueError, match="'low_pressure_steam' is not a cooling agent; " + 'pass it in heating_agents'): + t.utility_targets(None, [LP, CW]) + with pytest.raises(ValueError, match="'cooling_water' is not a heating agent"): + t.summary(heating_agents=[CW]) + with pytest.raises(ValueError, match="'natural_gas' is not a cooling agent"): + t.utility_targets(None, [gas]) + assert [u.ID for u in t.utility_targets([LP, gas], [CW, 'chilled_water'])] == [ + 'low_pressure_steam', 'natural_gas', 'cooling_water', 'chilled_water'] def test_optimum_T_min_app_closed_form(): from biosteam.units.heat_exchange import (compute_double_pipe_purchase_cost, From b4eeee621ebe4a2c2d673d84b7c7a73adde04724 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 04:48:53 +0000 Subject: [PATCH 17/52] address review 2 (tg-super): keep the shell and tubes the area fills in the capital target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TGS2-1 (fixed): the capital target divided the area equally over the N shells of the units target, N*C(A/N). With biosteam's jump at 150 ft² (about 8,100 USD installed for a double pipe just below, 64,700 for a floating head at 150 ft²) an added unit could push every shell below 150 ft² and cut the target by most of it, so the sweep's optimum sat at that switch (KEMP: 5.565 K, the TAC 4.7 times lower than 0.01 K below). New _shell_split: when the equal division puts the N shells below 150 ft² and the area fills k = A // 150 ft² >= 1 shells of at least 150 ft², the target is the costlier to install of the N double pipes and of k equal shell and tubes with N - k shells of no area (each a vanishing double pipe, 1,305 USD installed). It is the equal division wherever the shells stay above 150 ft² or the whole area is below it (so the tutorial's large-stream sweeps are unchanged), and the least change to it that never falls when a unit is added (checked on a grid for every type and material; the only exceptions are biosteam's own: shells beyond about 20,000 ft² and the 'Double pipe' type). Results: - KEMP 5.554 -> 5.560 K: 263,121 -> 326,790 USD (was -> 54,051), so it ranks the networks (124,788 at 5.5 K, 239,130 at 5.565 K) the right way round; the default sweep's optimum is 11.74 K, past a step of one floating head (lower neighbor 1.25 times the TAC). - supertarget's example pair at 10.05 K: 68,749 USD (one floating head and two empty shells) against a network of 68,940 (was 21,458). - 10 random problems: optima past one-shell steps of 1.06-1.12 times the TAC (were 2.5-3.9 times at the switch). Where it departs from the equal division it is high against networks (they keep only part of their area in shell and tubes), as the equal division was low; the Notes say so. I did not use the suggested min over k >= N of k*C(A/k): it would replace shell and tubes by double pipes up to about 2,900 ft² per shell (the tutorial's 3 x 2,583 ft² pair would become 52 double pipes), break the 9.9 K agreement of the example pair (15,300 against a 66,171 network) and put the KEMP optimum at the 1 K bound. capital_cost_target returns the new 'N_area_shells' (the shells that share the area; 'shell_area' is theirs). Declined: a flag or warning for an optimum next to a step; the remaining steps are one shell and tube, which networks have too, so it would fire on most small-stream sweeps; the records now show them (TGS2-3) and the Notes of supertarget, concepts and tutorial ch05 explain them. Docs: PinchTargets Notes and example, capital_cost_target, summary, supertarget (Notes, example rewritten: the optimum is the step where the area first fits in double pipes), network_report and plot_supertargets examples, concepts.rst (capital, sweep, estimates), tutorial ch05. New tests test_capital_target_split (the rule and monotonicity on a grid) and test_capital_target_keeps_shells (KEMP across 5.554/5.56 K, its sweep, the example pair at 9.9/10.05 K); test_capital_target_formula updated (reference A: one floating head; furnace case: two). TGS2-2 (fixed): supertarget checks each entry of T_min_apps with _real, the rule of PinchTargets.at: True, '10' and None raise ValueError ('each of T_min_apps must be a finite number, not True'); any iterable of numbers is accepted. Tested in test_sweep. TGS2-3 (fixed): summary (so the sweep's records and to_frame) adds 'N_area_shells', 'shell_area' and 'shell_type' after 'N_shells'; the other keys are unchanged. Tested in test_sweep. Regenerated docs/source/_generated/ch05_summary.txt by running docs/_demo_src/examples/ch05_targeting.py alone (it now prints the string key as is); its other captures and figures came out byte-identical. Tests: pytest tests/test_targeting.py hensmith/targeting.py hensmith/_heat_exchanger_network.py: 60 passed. Default-off oracle: sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (unchanged; the synthesis path is untouched). sphinx-build -W (SPHINX_OFFLINE=1) of docs/source: clean. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/_demo_src/examples/ch05_targeting.py | 7 +- docs/source/_generated/ch05_summary.txt | 5 +- docs/source/concepts.rst | 55 +++++-- docs/source/tutorial/05_targeting.rst | 17 +- hensmith/targeting.py | 184 ++++++++++++++++------ tests/test_targeting.py | 102 +++++++++++- 6 files changed, 296 insertions(+), 74 deletions(-) diff --git a/docs/_demo_src/examples/ch05_targeting.py b/docs/_demo_src/examples/ch05_targeting.py index cd144b1..1a02612 100644 --- a/docs/_demo_src/examples/ch05_targeting.py +++ b/docs/_demo_src/examples/ch05_targeting.py @@ -110,10 +110,11 @@ def main(): # [start:supertargets] print('units:', targets.units_target(utilities)) capital = targets.capital_cost_target(utilities) - print(f"{capital['N_shells']} shells of {capital['shell_area']:.4g} m2, {capital['type']}") + 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(): - print(f'{key:<24} {value:.6g}') + 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}') # [end:supertargets] with capturing('ch05_sweep'): # [start:sweep] diff --git a/docs/source/_generated/ch05_summary.txt b/docs/source/_generated/ch05_summary.txt index aec0649..8b28166 100644 --- a/docs/source/_generated/ch05_summary.txt +++ b/docs/source/_generated/ch05_summary.txt @@ -1,5 +1,5 @@ units: {'above': 5, 'below': 1, 'total': 6} -6 shells of 991 m2, Floating head +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 @@ -7,6 +7,9 @@ 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 diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 8522a37..60b26a2 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -483,16 +483,33 @@ grand composite curve -- the process pinch and every utility pinch --, acting in it. It is an estimate, **S - 1 per region, assuming one component**: a subset of streams that balances on its own needs fewer units. -**Capital.** The area is divided into as many shells of equal area as the -units target, :math:`N`, less one furnace for all the fuel, and every shell is -costed with BioSTEAM's own correlations, :math:`N \cdot C(A / N)`; the furnace -is costed on the fuel's utility duty. BioSTEAM's specifics carry over: a -double pipe exchanger below 150 ft², whose cost jumps at 150 ft² to that of a -shell and tube (the floating head correlation is not monotone below about -126 ft², which the double pipe hides), and no largest shell area. All shells -are costed at one pressure, ``P``, whereas BioSTEAM takes each exchanger's -highest inlet pressure; for high pressure steam heaters its pressure factor is -up to about 12 % higher. +**Capital.** The area is put in as many shells as the units target, +:math:`N`, less one furnace for all the fuel, and every shell is costed with +BioSTEAM's own correlations; the furnace is costed on the fuel's utility duty. +Linnhoff & Ahmad (1990) divide the area equally, :math:`N \cdot C(A / N)`: +with economies of scale (:math:`C = a + b A^c`) that is the costliest way to +divide it, and every unit added adds cost. BioSTEAM's correlations are not of +that form: a double pipe exchanger below 150 ft² costs at most about 8,100 +USD installed, a shell and tube at 150 ft² about 64,700 USD. An equal division +can then put every shell below 150 ft², so that a utility of a few watts that +adds units would cut the capital target by most of it (KEMP at 5.56 K: seven +double pipes instead of four floating heads, a fifth of the cost), although a +network just adds a small exchanger and keeps its large ones. So when the +equal division puts the shells below 150 ft² and the area fills +:math:`k = \lfloor A / 150\ \mathrm{ft^2} \rfloor \geq 1` shells of at least +150 ft², the target is the costlier of the :math:`N` double pipes and of +:math:`k` equal shell and tubes with :math:`N - k` shells of no area (each +costed as BioSTEAM costs a vanishing double pipe, about 1,300 USD installed). +This is the least change to the equal division that never falls when a unit +is added; where it departs from it, it tends to be high (networks keep only +part of their area in shell and tubes) as the equal division tends to be low. +The summary's ``'N_area_shells'``, ``'shell_area'`` and ``'shell_type'`` say +which shells carry the area. BioSTEAM's other specifics carry over: the +floating head correlation is not monotone below about 126 ft², which the +double pipe hides, and there is no largest shell area. All shells are costed +at one pressure, ``P``, whereas BioSTEAM takes each exchanger's highest inlet +pressure; for high pressure steam heaters its pressure factor is up to about +12 % higher. **Total annualized cost.** With a capital recovery factor over :math:`n` years at interest :math:`i`, @@ -513,7 +530,13 @@ of ``T_min_app`` values, reusing the streams' curves (nothing is flashed again), and then refines the grid around the least total annualized cost. The cost is not smooth -- the units target jumps, the double pipe switches to a shell and tube, the pinch moves -- so the refinement only narrows a bracket, -and ``at_bound`` flags an optimum at an end of the sweep. Where the targets do +and ``at_bound`` flags an optimum at an end of the sweep. The capital target +drops by about a shell and tube wherever the shrinking area fills one fewer of +150 ft², so the least cost often lies just past such a step rather than at a +smooth trade-off of energy and capital: compare the ``'N_area_shells'`` and +``'shell_type'`` of its neighbors (``sweep.to_frame()``). A network has such +steps too, but where its own exchangers cross 150 ft², which the target's +equal shells place only roughly. Where the targets do not depend on ``T_min_app`` -- below the threshold of a threshold problem, the utilities and the balanced composite curves stay the same -- the costs differ only by the discretization of the curves, by a tiny relative amount (4e-7 @@ -1072,9 +1095,13 @@ What it is not: ``hensmith.targeting`` are estimates: the area target bounds a network's area only with equal film coefficients and the same utilities, the units target can be wrong in both directions, the capital target assumes shells - of equal area at one pressure, and with approach contributions the utility - levels are conservative and their pricing temperatures approximate. The - optimum ``T_min_app`` is the optimum of these estimates. + of equal area at one pressure (keeping the shell and tubes the area fills + when an equal division would drop below 150 ft²), and with approach + contributions the utility levels are conservative and their pricing + temperatures approximate. The optimum ``T_min_app`` is the optimum of these + estimates, and often sits just past a step of the capital target where the + area fills one shell and tube fewer: a step a network shares, though not + at the same ``T_min_app``. References ---------- diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index 9958ffc..88e3826 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -199,7 +199,12 @@ targets the rest of the cost of a network. 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. + 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 :doc:`../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 @@ -287,6 +292,16 @@ classic supertargeting curve. 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 ------------------------------- diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 813e8cc..9f08903 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -494,6 +494,32 @@ def _furnace_costs(Q): F_P = 0.986 - 0.0035 * S + 0.0175 * S * S return C_b * F_P, C_b * (bst.HX._F_BM_default['Furnace'] + F_P - 1.) +def _shell_split(A, M, P=101325., heat_exchanger_type='Floating head', + material='Carbon steel/carbon steel'): + """ + ``(purchase, installed, k, type)`` [USD] of the area `A` [m²] in `M` >= 1 + shells, k of them sharing it equally, each of biosteam's `type` + (`_exchanger_costs`): Linnhoff and Ahmad's k = `M`, unless that puts the + shells below 150 ft² where the area fills k = ``A // 150 ft²`` >= 1 + shells of at least 150 ft² and these, with the ``M - k`` others of no + area (each costed as biosteam costs a vanishing double pipe), cost more + to install. k = `M` for an infinite or nan `A`. + """ + def shells(k): # k shells of A / k + p, i, kind = _exchanger_costs(A / k * _FT2_PER_M2, P, + heat_exchanger_type, material) + return k * p, k * i, k, kind + equal = shells(M) + if not math.isfinite(A * _FT2_PER_M2): return equal + k = min(int(A * _FT2_PER_M2 // 150.), M) + while k and A / k * _FT2_PER_M2 < 150.: k -= 1 # rounding + if not k or k == M: return equal + p, i, k, kind = shells(k) + p0, i0, _ = _exchanger_costs(0., P, heat_exchanger_type, material) + p += (M - k) * p0 + i += (M - k) * i0 + return (p, i, k, kind) if i > equal[1] else equal + def _real(x, name): """``float(x)`` of a finite real number (not a bool); a ValueError naming `name` otherwise.""" @@ -755,19 +781,37 @@ class PinchTargets: a bound: it assumes one component per region, and a balanced subset of streams needs fewer units. - *Capital and total annualized cost.* `capital_cost_target` divides the - area target into as many shells of equal area as the units target (less - one furnace for all fuel) [Linnhoff90]_ and costs each with biosteam's - correlations at one pressure (biosteam takes each exchanger's highest - inlet pressure: its pressure factor is up to about 12 % higher for - high pressure steam heaters): a double pipe below 150 ft² (for carbon - steel at 1 atm and biosteam's default CE of 567.5, the purchase cost - jumps from about 5,486 USD just below 150 ft² to 20,138 USD for a - floating head at 150 ft², and the installed cost, the one used here, - from about 8,055 to 64,658 USD; the floating head correlation, not - monotone below about 126 ft², is not used there), a shell and tube of - any area above. The furnace is costed - on the fuel's utility duty. `summary` adds the total annualized cost, + *Capital and total annualized cost.* `capital_cost_target` takes as + many shells as the units target (less one furnace for all fuel) and + costs each with biosteam's correlations at one pressure (biosteam takes + each exchanger's highest inlet pressure: its pressure factor is up to + about 12 % higher for high pressure steam heaters): a double pipe below + 150 ft² (for carbon steel at 1 atm and biosteam's default CE of 567.5, + the purchase cost jumps from about 5,486 USD just below 150 ft² to + 20,138 USD for a floating head at 150 ft², and the installed cost, the + one used here, from about 8,055 to 64,658 USD; the floating head + correlation, not monotone below about 126 ft², is not used there), a + shell and tube of any area above. Linnhoff and Ahmad divide the area + target equally over the shells [Linnhoff90]_: with a cost law of + economies of scale, such as a + bA^c, that is the costliest way to + divide it, and each added shell adds cost. biosteam's jump breaks this: + the equal division can bring every shell below 150 ft² although the area + would fill some shell and tubes, so that a utility of a few watts that + adds units would cut the target by most of it, where a network just adds + a small exchanger and keeps its large ones. So when the equal division + puts the N shells below 150 ft² and the area fills k = A // 150 ft² >= 1 + shells of at least 150 ft², the target takes the costlier to install of + the N double pipes and of k equal shell and tubes with N - k shells of + no area (each costed as biosteam costs a vanishing double pipe, about + 1,305 USD installed). This is the least change to the equal division + that never falls when a unit is added (or some area, except where + biosteam's correlations do: for shells beyond about 20,000 ft², whose + cost rises faster than their area, and for the 'Double pipe' + `heat_exchanger_type`, cheaper at 150 ft² than below). Where it departs + from the equal division it tends to be high, as networks keep only part + of their area in shell and tubes (the equal division tends to be low + there): an estimate, not a bound. The furnace is costed on the fuel's + utility duty. `summary` adds the total annualized cost, the installed (bare module) cost times the capital recovery factor plus the utility cost times the operating hours: no Lang factor and no working capital. @@ -830,25 +874,34 @@ class PinchTargets: target (the balanced grand composite curve is zero at the utility pinches of low pressure steam and cooling water and from the pinch down to 330 K, which leaves five regions of two streams or utilities each), - and the capital cost target, five double pipes of equal area [USD]: + and the capital cost target [USD]. The 24.14 m² (260 ft²) fill one + floating head, which with four shells of no area costs more to install + than five equal double pipes of 52 ft² (about 34,000 USD), so the target + takes it: >>> round(targets.area_target(utilities), 2) 24.14 >>> targets.units_target(utilities) {'above': 2, 'below': 3, 'total': 5} >>> capital = targets.capital_cost_target(utilities) - >>> capital['N_shells'], capital['type'], round(capital['installed_cost']) - (5, 'Double pipe', 33991) + >>> capital['N_shells'], capital['N_area_shells'], capital['type'] + (5, 1, 'Floating head') + >>> round(capital['installed_cost']) + 72819 All targets, with the total annualized cost [USD/yr] at 10 % interest over 10 years and 8000 operating hours a year: - >>> {key: round(value, 2) for key, value in targets.summary().items()} + >>> summary = targets.summary() + >>> summary['shell_type'] + 'Floating head' + >>> {key: round(value, 2) for key, value in summary.items() + ... if key != 'shell_type'} {'T_min_app': 10.0, 'Q_hot': 180000.0, 'Q_cold': 144000.0, 'utility_cost': 1.66, 'area': 24.14, 'N_units': 5, 'N_shells': 5, - 'purchase_cost': 23150.38, 'installed_cost': 33991.4, - 'annualized_capital_cost': 5531.94, 'operating_cost': 13247.77, - 'TAC': 18779.71} + 'N_area_shells': 1, 'shell_area': 24.14, 'purchase_cost': 21054.85, + 'installed_cost': 72819.1, 'annualized_capital_cost': 11850.97, + 'operating_cost': 13247.77, 'TAC': 25098.74} The same streams at another minimum approach temperature (the curves are reused): @@ -1665,20 +1718,20 @@ def _capital(self, utilities, h, h_utilities, P, heat_exchanger_type, if A > 0. and N_shells < 1: N_shells = 1 elif A == 0.: N_shells = 0 if N_shells: - shell_area = A / N_shells - purchase, installed, kind = _exchanger_costs( - shell_area * _FT2_PER_M2, P, heat_exchanger_type, material) - purchase *= N_shells - installed *= N_shells + purchase, installed, N_area_shells, kind = _shell_split( + A, N_shells, P, heat_exchanger_type, material) + shell_area = A / N_area_shells else: # no area, or nan shell_area = purchase = installed = A + N_area_shells = 0 kind = None furnace_duty = sum((u.utility_duty for u in fuel), 0.) if fuel: furnace_purchase, furnace_installed = _furnace_costs(furnace_duty) purchase += furnace_purchase installed += furnace_installed - return {'area': A, 'N_shells': N_shells, 'shell_area': shell_area, + return {'area': A, 'N_shells': N_shells, + 'N_area_shells': N_area_shells, 'shell_area': shell_area, 'type': kind, 'furnace_duty': furnace_duty, 'purchase_cost': purchase, 'installed_cost': installed}, N @@ -1686,9 +1739,11 @@ def capital_cost_target(self, utilities=None, h=None, h_utilities=None, P=101325., heat_exchanger_type='Floating head', material='Carbon steel/carbon steel'): """ - The capital cost target: the area target in as many shells of equal - area as the units target (less a furnace), costed with biosteam's - correlations, plus a furnace for the fuel (see Notes of the class). + The capital cost target: the area target in as many shells as the + units target (less a furnace), split equally over all of them or + over fewer if that costs more (the others then have no area), costed + with biosteam's correlations, plus a furnace for the fuel (see Notes + of the class). Parameters ---------- @@ -1709,10 +1764,12 @@ def capital_cost_target(self, utilities=None, h=None, h_utilities=None, Returns ------- dict - 'area' [m²]; 'N_shells', the number of shells; 'shell_area', the - area of each [m²]; 'type', biosteam's type of each ('Double pipe' - below 150 ft², None without shells or with an infinite area); - 'furnace_duty', the utility duty of the fuel [kJ/hr]; + 'area' [m²]; 'N_shells', the number of shells; 'N_area_shells', + how many of them share the area equally (the others have none); + 'shell_area', the area of each of those [m²]; 'type', biosteam's + type of each ('Double pipe' below 150 ft², None without shells + or with an infinite area); 'furnace_duty', the utility duty of + the fuel [kJ/hr]; 'purchase_cost' and 'installed_cost' (bare module) [USD]. inf with an infinite area, nan with an unserved utility. @@ -1776,7 +1833,9 @@ def summary(self, **options): dict 'T_min_app' [K]; 'Q_hot' and 'Q_cold' [kJ/hr], process side; 'utility_cost' [USD/hr]; 'area' [m²]; 'N_units', the units - target; 'N_shells'; 'purchase_cost' and 'installed_cost' [USD]; + target; 'N_shells', 'N_area_shells', 'shell_area' [m²] and + 'shell_type', the 'type' of `capital_cost_target`, as it gives + them; 'purchase_cost' and 'installed_cost' [USD]; 'annualized_capital_cost', the installed cost times the capital recovery factor, 'operating_cost', the utility cost times the operating hours, and their sum 'TAC' [USD/yr]. @@ -1803,6 +1862,9 @@ def summary(self, **options): 'Q_cold': self.Q_cold, 'utility_cost': utility_cost, 'area': capital['area'], 'N_units': N, 'N_shells': capital['N_shells'], + 'N_area_shells': capital['N_area_shells'], + 'shell_area': capital['shell_area'], + 'shell_type': capital['type'], 'purchase_cost': capital['purchase_cost'], 'installed_cost': capital['installed_cost'], 'annualized_capital_cost': annualized, @@ -1868,6 +1930,15 @@ def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, least TAC at the smallest or largest temperature evaluated is flagged (`Supertargets.at_bound`). + The capital target drops by about a shell and tube wherever the + shrinking area fills one fewer of 150 ft² (see Notes of + `PinchTargets`), so the least TAC often lies just past such a step, at + the first temperature evaluated beyond it, rather than at a smooth + trade-off of energy and capital. The records' 'N_area_shells' and + 'shell_type' show the steps (`Supertargets.to_frame`). A network has + such steps too, but where its own exchangers cross 150 ft², which the + target, with its equal shells, places only roughly. + Where the targets do not depend on the minimum approach temperature (below the threshold of a threshold problem, where neither the utilities nor the balanced composite curves change), the TACs differ only by the @@ -1884,8 +1955,10 @@ def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, One hot stream cooled 400 -> 300 K and one cold stream heated 290 -> 390 K, both 1 kW/K. Up to 10 K they balance with no utility in a single exchanger of 20 m² (215 ft², a shell and tube); above it the utilities - add units, and three double pipes of less than 150 ft² each cost less, - so on a coarse grid the least total annualized cost (TAC) is at 15 K: + add two units, but the area still fills one shell and tube until it + falls below 150 ft² (13.94 m²) just below 15 K, where three double pipes + take it. So on a coarse grid the least total annualized cost (TAC) is at + 15 K: >>> import biosteam as bst, thermosteam as tmo >>> from hensmith import PinchTargets, supertarget @@ -1906,15 +1979,21 @@ def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, >>> sweep['N_units'], sweep['area'].round(2) (array([1, 1, 3, 3, 4, 4]), array([20. , 20. , 13.88, 11.07, 9.73, 9.17])) + >>> sweep['N_area_shells'], [r['shell_type'] for r in sweep.records[1:3]] + (array([1, 1, 3, 3, 4, 4]), ['Floating head', 'Double pipe']) - Refining around it finds the least TAC just above 10 K, with almost no - utility and the area already in three double pipes: + Refining around it finds the least TAC at the first temperature + evaluated past that step, where the TAC falls to under half that of its + neighbor below: the step decides the optimum, not a trade-off of energy + and capital. >>> sweep = supertarget(targets, grid) >>> sweep - - >>> sweep.optimum['N_shells'], round(sweep.optimum['area'], 2) - (3, 19.89) + + >>> i = sweep.records.index(sweep.optimum) + >>> [(r['shell_type'], round(r['area'], 2), round(r['TAC'])) + ... for r in sweep.records[i - 1:i + 1]] + [('Floating head', 13.95, 12572), ('Double pipe', 13.93, 4930)] References ---------- @@ -1924,12 +2003,15 @@ def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, """ if T_min_apps is None: T_min_apps = _T_MIN_APPS - T = np.asarray(T_min_apps, dtype=float) - if T.ndim != 1 or not T.size: + try: + values = list(T_min_apps) + except TypeError: + values = [] + if not values or any(np.ndim(x) for x in values): raise ValueError('T_min_apps must be a non-empty sequence of ' 'temperatures [K]') - if not np.all(np.isfinite(T)): - raise ValueError(f'T_min_apps must be finite, not {T_min_apps!r}') + # the rule of PinchTargets.at: finite numbers, not bools or strings + T = np.array([_real(x, 'each of T_min_apps') for x in values]) contributions = targets.approach_contributions if contributions is None: if np.any(T < 0.): @@ -2327,8 +2409,14 @@ def network_report(HXN, targets=None, **options): (50.62, 66.33, 70.3) >>> report['N_units'], targets['N_units'] (7, 9) - >>> round(report['TAC']), round(targets['TAC']) # USD/yr - (62041, 39396) + + Its total annualized cost [USD/yr] is below the target's, which puts the + area target in five shell and tubes, where the network has one: + + >>> round(report['TAC']), round(targets['TAC']) + (62041, 82120) + >>> targets['N_area_shells'], targets['shell_type'] + (5, 'Floating head') """ if not hasattr(HXN, 'new_HXs_hot_side'): @@ -2734,7 +2822,7 @@ def plot_supertargets(sweep, ax=None, file=None, dpi=300): ... [H1.heat_utilities[0], C1.heat_utilities[0]], T_min_app=10.) >>> sweep = supertarget(targets) >>> sweep - + >>> fig, ax = plot_supertargets(sweep) >>> [line.get_gid() for line in ax.get_lines() if line.get_gid()] ['TAC', 'Capital', 'Operating', 'Optimum'] diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 580d026..87a0c9d 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -1100,10 +1100,17 @@ def test_capital_target_formula(): N = t.units_target(utilities)['total'] A = t.area_target(utilities) assert capital['area'] == A and capital['N_shells'] == N == 5 - assert capital['shell_area'] == A / N + # five equal double pipes of 52 ft2 cost less to install than the one + # floating head that the 260 ft2 fill, with four shells of no area p, i, kind = _exchanger_costs(A * 10.763 / N) + assert kind == 'Double pipe' and A * 10.763 == pytest.approx(259.866, abs=1e-3) + p1, i1, kind1 = _exchanger_costs(A * 10.763) + p0, i0, _ = _exchanger_costs(0.) + assert p0 == 0. and i0 == pytest.approx(1304.568, abs=1e-3) + assert N * i < i1 + 4 * i0 + assert capital['N_area_shells'] == 1 and capital['shell_area'] == A assert (capital['purchase_cost'], capital['installed_cost'], capital['type']) == ( - N * p, N * i, kind) + p1 + 4 * p0, i1 + 4 * i0, 'Floating head') assert capital['furnace_duty'] == 0. # a higher pressure costs more assert t.capital_cost_target(utilities, P=20e5)['installed_cost'] > capital['installed_cost'] @@ -1130,12 +1137,17 @@ def test_capital_target_formula(): capital = t.capital_cost_target(utilities) N = t.units_target(utilities)['total'] assert capital['furnace_duty'] == fuel.utility_duty - assert capital['N_shells'] == N - 1 + assert capital['N_shells'] == N - 1 == 6 A = t.area_target(utilities) - p, i, _ = _exchanger_costs(A * 10.763 / (N - 1)) + # 336 ft2: six equal double pipes, or the two floating heads they fill + # and four shells of no area, which cost more + assert int(A * 10.763 // 150) == 2 == capital['N_area_shells'] + assert capital['shell_area'] == A / 2 and capital['type'] == 'Floating head' + p, i, _ = _exchanger_costs(A * 10.763 / 2) + assert 2 * i + 4 * i0 > 6 * _exchanger_costs(A * 10.763 / 6)[1] C_b = compute_furnace_purchase_cost(fuel.utility_duty * 0.947817, bst.CE) - assert capital['installed_cost'] == pytest.approx((N - 1) * i + C_b * 2.19, rel=1e-12) - assert capital['purchase_cost'] == pytest.approx((N - 1) * p + C_b, rel=1e-12) + assert capital['installed_cost'] == pytest.approx(2 * i + 4 * i0 + C_b * 2.19, rel=1e-12) + assert capital['purchase_cost'] == pytest.approx(2 * p + C_b, rel=1e-12) summary = t.summary() assert summary['utility_cost'] == pytest.approx(sum(u.cost for u in utilities), rel=1e-12) assert summary['installed_cost'] == capital['installed_cost'] @@ -1149,6 +1161,70 @@ def test_capital_target_formula(): with pytest.raises(ValueError, match='heat_exchanger_type must be one of'): t.capital_cost_target(utilities, heat_exchanger_type='shell') +def test_capital_target_split(): + # the equal division, unless it puts the shells below 150 ft2 where the + # area fills k = A // 150 ft2 shell and tubes, which with the other + # shells of no area cost more: so a unit added always adds cost + from hensmith.targeting import _shell_split + p0, i0, _ = _exchanger_costs(0.) + for M in range(1, 12): + for A_ft2 in np.geomspace(1., 5000., 157): + A = A_ft2 / 10.763 + p, i, k, kind = _shell_split(A, M) + pe, ie, kinde = _exchanger_costs(A_ft2 / M) + if A_ft2 / M >= 150. or A_ft2 < 150.: + assert k == M and kind == kinde + assert (p, i) == pytest.approx((M * pe, M * ie), rel=1e-12) + else: + ks = int(A_ft2 // 150.) + pk, ik, kindk = _exchanger_costs(A_ft2 / ks) + filled = ks * ik + (M - ks) * i0 + assert i == pytest.approx(max(M * ie, filled), rel=1e-12) + if filled > M * ie: + assert (k, kind) == (ks, kindk) and kind != 'Double pipe' + assert p == pytest.approx(ks * pk + (M - ks) * p0, rel=1e-12) + else: + assert (k, kind) == (M, 'Double pipe') + assert _shell_split(A, M + 1)[1] > i + assert _shell_split(A * 1.001, M)[1] >= i + assert _shell_split(math.inf, 3)[1:] == (math.inf, 3, None) + nan = _shell_split(math.nan, 3) + assert math.isnan(nan[1]) and nan[2:] == (3, None) + +def test_capital_target_keeps_shells(): + # KEMP: with no hot utility up to 5.554 K, four floating heads of 202 + # ft2; a few watts of steam at 5.560 K add three units, and an equal + # division would put the same area in seven double pipes for a fifth of + # the cost. The target keeps the shell and tubes the area fills + t = targets_of(cp_units('test_targeting_keeps_shells', KEMP, K), 10.) + before, after = t.at(5.554).summary(), t.at(5.56).summary() + assert (before['N_units'], after['N_units']) == (4, 7) + assert before['Q_hot'] == 0. < after['Q_hot'] < 0.1 * 3600 + assert after['area'] == pytest.approx(before['area'], rel=1e-3) + assert 7 * _exchanger_costs(after['area'] * 10.763 / 7)[1] < 0.25 * before['installed_cost'] + assert (before['N_area_shells'], before['shell_type']) == (4, 'Floating head') + assert (after['N_area_shells'], after['shell_type']) == (5, 'Floating head') + assert after['installed_cost'] > before['installed_cost'] + assert after['TAC'] > before['TAC'] + # so the least TAC is not where the steam appears: past a step of one + # shell, not of a factor of five + sweep = supertarget(t) + assert sweep.T_min_app > 10. and not sweep.at_bound + i = sweep.records.index(sweep.optimum) + below = sweep.records[i - 1] + assert below['N_area_shells'] == sweep.optimum['N_area_shells'] + 1 + assert below['TAC'] < 1.3 * sweep.optimum['TAC'] + # the pair of supertarget's example: one floating head of 215 ft2 at + # 9.9 K, kept with the two utility units of 10.05 K + t = targets('test_targeting_keeps_shells_pair', PAIR, 10.) + before, after = t.at(9.9).summary(), t.at(10.05).summary() + assert (before['N_units'], after['N_units']) == (1, 3) + assert before['installed_cost'] == _exchanger_costs(20. * 10.763)[1] + assert (after['N_area_shells'], after['shell_type']) == (1, 'Floating head') + assert after['installed_cost'] == pytest.approx( + _exchanger_costs(after['area'] * 10.763)[1] + 2 * _exchanger_costs(0.)[1], rel=1e-12) + assert after['installed_cost'] > before['installed_cost'] + def test_crf_and_tea(): from biosteam._tea import solve_payment assert _economics(.1, 10)[0] == solve_payment(1., .1, 10) @@ -1298,8 +1374,12 @@ def flash(*args, **kwargs): raise AssertionError('the curves are rebuilt') frame = sweep.to_frame() assert frame.index.name == 'T_min_app' and list(frame.index) == list(T) assert list(frame.columns) == ['Q_hot', 'Q_cold', 'utility_cost', 'area', 'N_units', - 'N_shells', 'purchase_cost', 'installed_cost', + 'N_shells', 'N_area_shells', 'shell_area', 'shell_type', + 'purchase_cost', 'installed_cost', 'annualized_capital_cost', 'operating_cost', 'TAC'] + capital = t.at(T[0]).capital_cost_target() + assert [sweep.records[0][key] for key in ('N_area_shells', 'shell_area', 'shell_type')] == [ + capital[key] for key in ('N_area_shells', 'shell_area', 'type')] assert sweep.optimum['TAC'] == frame['TAC'].min() assert sweep.T_min_app == sweep.optimum['T_min_app'] assert sweep.at_bound == (sweep.T_min_app in (T[0], T[-1])) @@ -1310,6 +1390,8 @@ def flash(*args, **kwargs): raise AssertionError('the curves are rebuilt') hours = supertarget(t, [5., 10.], refine=0, operating_hours=0.) assert np.all(hours['operating_cost'] == 0.) LP, MP = agent('low_pressure_steam'), agent('medium_pressure_steam') + assert supertarget(t, (x for x in [10., np.float64(5.), 5]), refine=0).records == ( + supertarget(t, [5., 10.], refine=0).records) listed = supertarget(t, [5., 10.], refine=0, heating_agents=[MP, LP]) assert listed.records == supertarget(t, [5., 10.], refine=0, heating_agents=iter([MP, LP])).records @@ -1319,6 +1401,12 @@ def flash(*args, **kwargs): raise AssertionError('the curves are rebuilt') (dict(T_min_apps=[]), ValueError, 'non-empty'), (dict(T_min_apps=[[5.]]), ValueError, 'non-empty'), (dict(T_min_apps=[5., math.nan]), ValueError, 'finite'), + (dict(T_min_apps=5.), ValueError, 'non-empty'), + # the rule of PinchTargets.at: no bools, no strings + (dict(T_min_apps=[True, 5.]), ValueError, + 'each of T_min_apps must be a finite number, not True'), + (dict(T_min_apps=['10']), ValueError, "finite number, not '10'"), + (dict(T_min_apps=[None]), ValueError, 'finite number, not None'), (dict(T_min_apps=[-1., 5.]), ValueError, '>= 0 K'), (dict(refine=-1), ValueError, 'refine must be an int >= 0'), (dict(refine=1.), ValueError, 'refine must be an int >= 0'), From 4da77b061f610738871f24efe40354eac065c199 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 05:00:46 +0000 Subject: [PATCH 18/52] address review 2 (tg-report): align unserved balanced curves, place sensible labels off the line, target reversed streams TGR2-1 (fixed): balanced_composite_curves now starts the cold composite at the unserved cooling (its coldest member, at level -inf, which is left out), so the curves stay aligned and the plot no longer draws a cross (REF_A at 10 K with cooling water only: min drawn hot - cold goes from -7.37 K to +5.0 K, the same as with every utility served). Unserved heating stays a gap at the hot end. Served-only results and the area (nan when unserved) are unchanged. Docstrings, class Notes and concepts.rst describe the gap. test_unserved_warns checks the new origin and the dT gap; test_plot_readability checks the drawn curves for LP + CW and default heating + CW. TGR2-2 (fixed): a sensible agent's label now sits below right of the rising cold balanced composite for cooling (offset (6, -4), va='top') and above left of the hot composite for heating (offset (-6, 4), va='bottom'). test_plot_readability checks that no 'Utility:' or 'Fuel' label box holds points of either balanced composite for the threshold, furnace, hot oil and liquids cases (it fails with the old placement). tutorial_05_balanced_composites.png was regenerated with ch05_targeting.py; it is the only asset that changed. TGR2-3 (fixed): after a simulation, HXN.targets() takes each stream as hot or cold from its current direction (outs[0].H - ins[0].H; the heat utility may have been replaced). A stream with no duty keeps its synthesis side. Right after a synthesis this agrees with cold_indices, so the targets still equal synthesis_info bit for bit. The new test_facility_targets_reversed_stream covers C1 turned into a cooler, simulated alone and through a reused cached network; the targets now match from_heat_utilities (Q_cold 1.116e6 kJ/hr). TGR2-4 (nit, documented): the PinchTargets.heat_utilities attribute and the HXN.targets Notes say that, after a simulation with replace_unit_heat_utilities, these are original_heat_utils and hold the network's utilities. TGR2-5 (nit, fixed): plot_supertargets adds ' (at the bound)' to the optimum label when sweep.at_bound is set, and leaves out the marker and dashed line when the optimum TAC is not finite. Tested. TGR2-6 (nit, fixed): a zero duty in a composite plot label now reads '0' (new _format_duty in targeting; _format_H and the grid diagrams are unchanged). Tested on the threshold real and shifted plots. Tests: pytest tests/test_targeting.py tests/test_hxn_targets.py hensmith/targeting.py hensmith/_heat_exchanger_network.py: 103 passed. Oracle sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (29 cases) unchanged. ch05_targeting.py rerun alone: only the balanced composites PNG changed. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- .../tutorial_05_balanced_composites.png | Bin 80858 -> 80933 bytes docs/source/concepts.rst | 3 +- hensmith/_heat_exchanger_network.py | 25 +++- hensmith/targeting.py | 74 ++++++++--- tests/test_targeting.py | 124 ++++++++++++++++-- 5 files changed, 186 insertions(+), 40 deletions(-) diff --git a/docs/source/_static/images/examples/tutorial_05_balanced_composites.png b/docs/source/_static/images/examples/tutorial_05_balanced_composites.png index 1e726f5d3fa6baa94b53b7798d1a040998c4a038..15676004a5fa03719284285c534cb20027572a3c 100644 GIT binary patch delta 48054 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

l~4lB}WLyxG~pccAi^vNGzJCyt5|T<12CgLxX$ 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 TE9%}MdM^eWw(xH2S^57LGUGc# delta 47978 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 LX)n*PaNqhbi+K{Q diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 60b26a2..5264636 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -452,7 +452,8 @@ curve -- gives two curves that span the same heat flat at its temperature, a sensible one a line between its inlet and outlet temperatures, and fuel a furnace that takes no area. If some utility is unserved, the curves cannot balance, and the area, capital and total cost -targets are nan. +targets are nan; the unserved cooling, the coldest part of the cold curve, is +left out as a gap at its start, which keeps the two curves aligned. **Area.** The area target is Bath's formula (Townsend & Linnhoff, 1984) on the balanced composite curves: heat is transferred vertically, from the hot diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index f7f5d21..64cf445 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -996,9 +996,16 @@ def targets(self, T_min_app=None): ----- After a simulation, the streams are those of `original_heat_exchangers`, in the network's stream order, at the - units' current states: right after a synthesis, the energy targets - at the facility's `T_min_app` equal ``synthesis_info['Q_hot_target']`` - and ``synthesis_info['Q_cold_target']`` bit for bit. A mapping of + units' current states, each hot or cold by its current direction (a + stream that has reversed since the synthesis, its unit simulated + alone or the network reused by `cache_network`, changes sides; one + without duty keeps its side): right after a synthesis, the energy + targets at the facility's `T_min_app` equal + ``synthesis_info['Q_hot_target']`` and + ``synthesis_info['Q_cold_target']`` bit for bit. The targets' + `heat_utilities` are `original_heat_utils`, which with + `replace_unit_heat_utilities` hold the network's utilities, not the + streams' (see `hensmith.targeting.PinchTargets`). A mapping of approach contributions is resolved again by the units of `original_heat_utils`, so a mapping changed since applies; a callable is not called again (with `replace_unit_heat_utilities`, the @@ -1057,6 +1064,14 @@ def targets(self, T_min_app=None): hxs = self.original_heat_exchangers hus = self.original_heat_utils # in stream order cold = set(self.cold_indices) + # hot or cold by the current direction of each stream (the heat + # utility, which replace_unit_heat_utilities overwrites, cannot + # tell): a stream that has reversed since the synthesis changes + # sides, one without duty keeps its side + is_hot = [] + for i, hx in enumerate(hxs): + dH = hx.outs[0].H - hx.ins[0].H + is_hot.append(dH < 0. if dH else i not in cold) x = self.approach_contributions if x is not None and not isinstance(x, Mapping) and callable(x): # Never called again: with replace_unit_heat_utilities, the @@ -1069,8 +1084,8 @@ def targets(self, T_min_app=None): # by unit only, which the network never changes contributions = self._network_contributions(hus) targets = PinchTargets._from_exchangers( - hxs, [i not in cold for i in range(len(hxs))], T_min_app, - contributions, self.force_ideal_thermo, None, hus) + hxs, is_hot, T_min_app, contributions, self.force_ideal_thermo, + None, hus) targets.system = self.system return targets if self.system is None and not self.units: diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 9f08903..7f90704 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -666,7 +666,10 @@ class PinchTargets: `hensmith.hxn_synthesis._pinch_cut`). heat_utilities : list[HeatUtility] or None The heat utilities the streams were taken from, in stream order; - None when built from streams. + None when built from streams. For `HeatExchangerNetwork.targets` + after a simulation they are the facility's `original_heat_utils`, + which with `replace_unit_heat_utilities` hold the network's + utilities (the streams are read from the units). units : list[Unit] or None The unit of each stream (``hu.unit``), in stream order; None when built from streams. @@ -754,7 +757,10 @@ class PinchTargets: cooling ones to the cold one), so both end at the same heat: a latent agent as a flat at its temperature, a sensible one as a line between its inlet and outlet temperatures, and fuel as a furnace at infinite - temperature. `balanced_grand_composite_curve` is the cascade net of the + temperature. An unserved remainder is left out: unserved cooling, the + coldest member of the cold composite, as a gap at its start (which + keeps the curves aligned), unserved heating as one at the end of the + hot composite. `balanced_grand_composite_curve` is the cascade net of the utilities, zero at the process pinch and at every utility pinch. *Area target.* `area_target` is Bath's formula [Townsend84]_ on the @@ -1484,14 +1490,22 @@ def balanced_composite_curves(self, utilities=None): ------- hot_T, hot_H, cold_T, cold_H : numpy.ndarray Real temperatures [K] and cumulative heat [kJ/hr] of each - composite, ascending from 0; fuel is a last hot stretch at - ``T = inf``. Both end at the same heat unless part of a target is - unserved. + composite, ascending: the hot one from 0, with fuel as a last + stretch at ``T = inf``, and the cold one from the unserved + cooling (0 if none), the coldest of its members, which is left + out. The curves are thus aligned for vertical heat transfer, and + end at the same heat unless part of the heating target is + unserved (the hot one then ends short of the cold one by it). """ if utilities is None: utilities = self.utility_targets() hot, cold = self._balanced_segments(utilities) - return (*_curve(hot), *_curve(cold)) + hot_T, hot_H = _curve(hot) + cold_T, cold_H = _curve(cold) + unserved = sum([u.duty for u in utilities + if u.agent is None and not u.heating], 0.) + if unserved: cold_H = cold_H + unserved + return hot_T, hot_H, cold_T, cold_H def _balanced_positions(self, utilities): """The constraint positions with the levels of every allocated @@ -2468,6 +2482,11 @@ def network_report(HXN, targets=None, **options): } +def _format_duty(H): + """A duty `H` [kJ/hr] for a plot label: '0', or as `_format_H` (three + significant digits, e.g. '1.18E5').""" + return '0' if H == 0. else _format_H(H) + def _legend(label, H): """`label` for the legend if a curve of heats `H` has points, else none (a label starting with '_' stays out of the legend).""" @@ -2520,8 +2539,11 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, end apart, nor without streams on one side (whose empty composite stays out of the legend). Fuel, at infinite temperature on the balanced curves, is labeled rather than drawn, and so is an unserved remainder - ('Utility:unserved', in the upper left corner), without which the - balanced curves end at different heats. + ('Utility:unserved', in the upper left corner): the cold balanced + composite starts at the unserved cooling, and the hot one ends short of + the cold one by the unserved heating. A label of a sensible agent sits + off the middle of its line, above left for heating and below right for + cooling. Examples -------- @@ -2574,12 +2596,12 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, for u in utilities: if u.agent is None: unserved.append(f"unserved {'heating' if u.heating else 'cooling'} " - f'{_format_H(u.duty)} kJ/hr (not drawn)') + f'{_format_duty(u.duty)} kJ/hr (not drawn)') continue if u.agent.isfuel: fuels.append(u) continue - label = f'{u.ID} {_format_H(u.duty)} kJ/hr' + label = f'{u.ID} {_format_duty(u.duty)} kJ/hr' T, H = (hot_T, hot_H) if u.heating else (cold_T, cold_H) if u.agent.T_limit is None: # above a heating flat, ending at its warm end (left of it the @@ -2594,11 +2616,16 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, offset, ha, va = (2, -2), 'left', 'top' x = H[min(max(i, 0), H.size - 1)] if u.heating: above.append((x, T_u)) - else: # beside the middle of a sensible agent's line + else: + # at the middle of a sensible agent's line, off the rising + # composite: above left of it for heating, below right of it + # for cooling (the other composite is on the far side) T_u = 0.5 * (u.T_utility_in + u.T_utility_out) x = np.interp(T_u, T, H) - offset = (-6, 0) if u.heating else (6, 0) - ha, va = 'right' if u.heating else 'left', 'center' + if u.heating: + offset, ha, va = (-6, 4), 'right', 'bottom' + else: + offset, ha, va = (6, -4), 'left', 'top' ax.annotate(label, xy=(x, T_u - 273.15), xytext=offset, textcoords='offset points', ha=ha, va=va, color=hot_color if u.heating else cold_color, @@ -2610,7 +2637,7 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, # above the label of the top flat if that ends there too stacked = any(abs(x - a) <= 1e-9 * (abs(x) + abs(a)) + 1e-12 and abs(y - b) <= 1e-6 for a, b in above) - ax.annotate(f'{u.ID} (furnace) {_format_H(u.duty)} kJ/hr', + ax.annotate(f'{u.ID} (furnace) {_format_duty(u.duty)} kJ/hr', xy=(x, y - 273.15), xytext=(-2, 14) if stacked else (0, 4), textcoords='offset points', ha='right', va='bottom', color=hot_color, gid='Fuel', **text_kwargs) @@ -2636,10 +2663,10 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, H_cold_end = cold_H[-1] if cold_H.size else targets.Q_cold ax.margins(y=0.12) # headroom for the utility labels ax.text(0.5 * (H_hot_end + H_cold_end), T_all.max() + 1., - f'hot utility {_format_H(targets.Q_hot)} kJ/hr', ha='center', + f'hot utility {_format_duty(targets.Q_hot)} kJ/hr', ha='center', va='bottom', gid='Hot utility', **text_kwargs) ax.text(0.5 * targets.Q_cold, T_all.min() - 1., - f'cold utility {_format_H(targets.Q_cold)} kJ/hr', ha='left', + f'cold utility {_format_duty(targets.Q_cold)} kJ/hr', ha='left', va='top', gid='Cold utility', **text_kwargs) pinch_T = targets.pinch_T tol = 1e-9 * pinch_T @@ -2801,7 +2828,9 @@ def plot_supertargets(sweep, ax=None, file=None, dpi=300): Notes ----- Artists carry stable gids: 'TAC', 'Capital' (the annualized capital - cost), 'Operating' and 'Optimum'. + cost), 'Operating' and 'Optimum'. The optimum is labeled '(at the + bound)' if it is at an end of the sweep (`Supertargets.at_bound`), and + is not marked without a finite TAC. Examples -------- @@ -2843,10 +2872,13 @@ def plot_supertargets(sweep, ax=None, file=None, dpi=300): ax.plot(T, sweep['operating_cost'], color='#d62728', lw=1.5, label='operating cost', gid='Operating') optimum = sweep.optimum - ax.axvline(optimum['T_min_app'], color='k', ls='--', lw=0.8) - ax.plot([optimum['T_min_app']], [optimum['TAC']], 'o', mfc='w', mec='k', - ms=7, zorder=4, label=f"optimum {optimum['T_min_app']:.3g} K", - gid='Optimum') + if math.isfinite(optimum['TAC']): + bound = ' (at the bound)' if sweep.at_bound else '' + ax.axvline(optimum['T_min_app'], color='k', ls='--', lw=0.8) + ax.plot([optimum['T_min_app']], [optimum['TAC']], 'o', mfc='w', + mec='k', ms=7, zorder=4, + label=f"optimum {optimum['T_min_app']:.3g} K{bound}", + gid='Optimum') ax.set_xlabel('$T_{min,app}$ [K]') ax.set_ylabel('cost [USD·yr$^{-1}$]') ax.legend(loc='best', fontsize=8) diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 87a0c9d..7a6af69 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -513,9 +513,15 @@ def test_unserved_warns(): assert u.agent is None assert all(math.isnan(x) for x in (u.cost, u.flow, u.utility_duty, u.T_utility_in, u.T_utility_out)) - # the unserved heat is missing from the balanced composites - hot_T, hot_H, cold_T, cold_H = t.balanced_composite_curves(utilities) - assert_allclose(cold_H[-1] - hot_H[-1], (32.811 - 12.372) * kW, rtol=1e-9) + # the unserved heat is missing from the balanced composites: the + # unserved cooling, the coldest of the cold side, is a gap at the start + # of the cold one (which keeps the curves aligned, dT apart at cooling + # water), the unserved heating one at the end of the hot one + curves = t.balanced_composite_curves(utilities) + hot_T, hot_H, cold_T, cold_H = curves + assert hot_H[0] == 0. and cold_H[0] == pytest.approx(12.372 * kW, rel=1e-9) + assert_allclose(cold_H[-1] - hot_H[-1], 32.811 * kW, rtol=1e-9) + assert min_vertical_gap(*curves) == pytest.approx(bst.HeatUtility.dT, abs=1e-6) # so the area, the capital cost and the total annualized cost are nan assert math.isnan(t.area_target(utilities)) assert math.isnan(t.capital_cost_target(utilities)['installed_cost']) @@ -1509,6 +1515,31 @@ def assert_synthesis_targets(t, HXN): with pytest.raises(RuntimeError, match='before targeting'): HeatExchangerNetwork('test_targeting_alone').targets() +def test_facility_targets_reversed_stream(): + # C1 made a cooler after the synthesis, simulated alone or with the + # cached network reused: the targets take it as a hot stream at its + # current state, as those of the units' heat utilities do + for k, cache_network in enumerate((False, True)): + units = cp_units(f'test_targeting_reversed_{k}', KEMP, K) + sys, HXN = simulate_HXN(units, 10., cache_network=cache_network) + assert list(HXN.targets().is_hot) == [False, False, True, True] + C1 = units[0] + C1.T = C1.ins[0].T - 20. + if cache_network: + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + sys.simulate() + assert HXN.original_heat_exchangers[0] is C1 # not synthesized again + else: + C1.simulate() + t = HXN.targets() + assert t.labels == ['C1', 'C3', 'H2', 'H4'] + assert list(t.is_hot) == [True, False, True, True] + fresh = PinchTargets.from_heat_utilities([u.heat_utilities[0] for u in units], 10.) + assert t.Q_hot == pytest.approx(fresh.Q_hot, abs=1e-6) + assert t.Q_cold == pytest.approx(fresh.Q_cold, rel=1e-12) + assert t.Q_cold > 1e6 > HXN.synthesis_info['Q_cold_target'] + def test_facility_targets_replaced_callable(): # replace_unit_heat_utilities overwrites the heat utilities a callable # reads (the network's utility, or none for H2 and C3, which the process @@ -1955,17 +1986,69 @@ def boxes(fig, artists): assert [x.get_text() for x in ax.get_legend().get_texts()] == legend finally: plt.close(fig) - # the unserved remainders, without which the balanced curves end apart + # no utility on a threshold problem's other side: a plain 0 + for kind in ('real', 'shifted'): + fig, ax = plot_composite_curves(threshold, kind=kind) + try: + label, = _gid_artists(ax, 'Hot utility') + assert label.get_text() == 'hot utility 0 kJ/hr' + finally: + plt.close(fig) + # the unserved remainders, without which the balanced curves end apart: + # the cold one starts at the unserved cooling, the coldest of its + # members, so the curves drawn do not cross (they keep dT apart at + # cooling water, as when it is all served) t = targets('test_targeting_plot_unserved', REF_A, 10.) - with pytest.warns(RuntimeWarning, match='left unserved'): - utilities = t.utility_targets([agent('low_pressure_steam')], [agent('cooling_water')]) - fig, ax = plot_composite_curves(t, kind='balanced', utilities=utilities) - try: - label, = _gid_artists(ax, 'Utility:unserved') - assert label.get_text() == ('unserved heating 1.18E5 kJ/hr (not drawn)\n' - 'unserved cooling 4.45E4 kJ/hr (not drawn)') - finally: - plt.close(fig) + texts = {True: ('unserved heating 1.18E5 kJ/hr (not drawn)\n' + 'unserved cooling 4.45E4 kJ/hr (not drawn)'), + False: 'unserved cooling 4.45E4 kJ/hr (not drawn)'} + for LP_only in (True, False): + heating = [agent('low_pressure_steam')] if LP_only else None + with pytest.warns(RuntimeWarning, match='left unserved'): + utilities = t.utility_targets(heating, [agent('cooling_water')]) + fig, ax = plot_composite_curves(t, kind='balanced', utilities=utilities) + try: + label, = _gid_artists(ax, 'Utility:unserved') + assert label.get_text() == texts[LP_only] + hot, = _gid_artists(ax, 'Hot balanced composite') + cold, = _gid_artists(ax, 'Cold balanced composite') + (hot_H, hot_T), (cold_H, cold_T) = hot.get_data(), cold.get_data() + assert hot_H[0] == 0. and cold_H[0] == pytest.approx(12.372 * kW, rel=1e-9) + assert min_vertical_gap(hot_T, hot_H, cold_T, cold_H) == pytest.approx( + bst.HeatUtility.dT, abs=1e-6) + finally: + plt.close(fig) + # every utility label off both balanced composites: a sensible agent's + # beside the middle of its line (below right of the rising cold curve + # for cooling water and chilled water, above left of the hot curve for a + # hot oil), a latent one's by its flat and the furnace's above the end + oil = bst.UtilityAgent('test_targeting_plot_hot_oil', Water=1, T=480., P=5e6, + phase='l', thermo=bst.HeatUtility.thermo_water, + T_limit=420., heat_transfer_price=1e-6) + oiled = targets('test_targeting_plot_oil', [('C1', 390., 470., 1.), + ('C2', 460., 470., 10.)], 10.) + cases = [(threshold, None), (furnace, None), (oiled, oiled.utility_targets([oil], [])), + (liquids('test_targeting_plot_liquids')[1].targets(), None)] + for t, utilities in cases: + fig, ax = plot_composite_curves(t, kind='balanced', utilities=utilities) + try: + labels = _gid_artists(ax, 'Utility:') + _gid_artists(ax, 'Fuel') + assert any(not (u.agent.isfuel or u.agent.T_limit is None) + for u in utilities or t.utility_targets()) + curves = [line for line in ax.get_lines() + if 'balanced composite' in (line.get_gid() or '')] + assert len(curves) == 2 + points = [] + for line in curves: + xy = ax.transData.transform(np.column_stack(line.get_data())) + points += [np.linspace(a, b, 500) for a, b in zip(xy, xy[1:])] + points = np.concatenate(points) + for box, label in zip(boxes(fig, labels), labels): + inside = ((points[:, 0] >= box.x0) & (points[:, 0] <= box.x1) + & (points[:, 1] >= box.y0) & (points[:, 1] <= box.y1)) + assert not inside.any(), label.get_text() + finally: + plt.close(fig) # the grand composite curve at its default size: heats of six digits # [kJ/hr] in scientific notation, whose tick labels do not run together, # and every label off the curve (which meets the end of a step) @@ -2011,5 +2094,20 @@ def test_plot_supertargets_gids(tmp_path): optimum, = _gid_artists(ax, 'Optimum') assert (optimum.get_xdata()[0], optimum.get_ydata()[0]) == ( sweep.T_min_app, sweep.optimum['TAC']) + assert not sweep.at_bound and optimum.get_label() == 'optimum 10 K' finally: plt.close(fig) + # an optimum at an end of the sweep is labeled so; one without a finite + # TAC (an unserved utility everywhere) is not marked + at_bound = supertarget(t, [10., 20., 30.], refine=0) + with pytest.warns(RuntimeWarning): + unserved = supertarget(t, [5., 10.], heating_agents=[agent('low_pressure_steam')], + cooling_agents=[agent('cooling_water')]) + assert at_bound.at_bound and not np.isfinite(unserved['TAC']).any() + for sweep, labels in ((at_bound, ['optimum 10 K (at the bound)']), (unserved, [])): + fig, ax = plot_supertargets(sweep) + try: + assert [a.get_label() for a in _gid_artists(ax, 'Optimum')] == labels + assert len(ax.lines) == 3 + 2 * len(labels) # with the dashed line + finally: + plt.close(fig) From 8a26a32ad1b78ef2fdb1c134b416bb578330ef93 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 05:05:39 +0000 Subject: [PATCH 19/52] address review 2 (docs): explain the extra unit by the loop, list loops as the cause of exceeding the units target DOCS-R2-1 (fixed): the Units bullet of tutorial ch05 put the quickstart network's 7 exchangers against a target of 6 down to per-stream utility exchangers. It now names the real cause: above the pinch D1_H1 heats both the reboiler (HX_0_2_hs) and the flash's feed heater (HX_1_2_hs), and each also has a steam heater (Util_0_hs, Util_1_hs), so the four close a loop that Euler's S - 1 does not count (it counts the steam as one stream of any degree). The lists of causes in ch05, concepts.rst (the units paragraph and the network-report limits) and the units-target and network_report Notes in targeting.py now say that S - 1 assumes one component and no loop per region, and that each loop of matches adds one unit: a repeated match (two branches of a split stream meeting the same stream make one too) or two streams linked both through process matches and through a shared utility. A split alone is no longer listed as a cause, since it adds no edge between distinct streams by itself. The concepts wording of the "fewer units" case now matches network_report's (one agent serving a stream whose heat the target splits between two utility levels), which removes the contradiction with ch05. DOCS-R2-2 (fixed): the untraceable "2e-5" area variation in ch05 is replaced by what the capture shows (the area is 6836 m2 at both 1 and 1.848 K); no capture or figure changes. DOCS-R2-3 (fixed): the API targeting page's colon is now a full stop. DOCS-R2-4 (fixed): rewrapped the overlong prose lines of the HeatExchangerNetwork Notes, the network_report Returns entry and concepts.rst's test-suite (iii)-(v) sentence (the PinchTargets Notes line was already wrapped; the remaining long lines are doctest output). Tests: pytest hensmith/targeting.py hensmith/_heat_exchanger_network.py (doctests): 8 passed. sphinx -W (SPHINX_OFFLINE=1) of docs/source: clean. Only docstrings and rst changed, so the synthesis path and the default-off oracle are untouched. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/API/targeting.rst | 2 +- docs/source/concepts.rst | 23 +++++++++++++++-------- docs/source/tutorial/05_targeting.rst | 25 ++++++++++++++++--------- hensmith/_heat_exchanger_network.py | 9 +++++---- hensmith/targeting.py | 21 +++++++++++++-------- 5 files changed, 50 insertions(+), 30 deletions(-) diff --git a/docs/source/API/targeting.rst b/docs/source/API/targeting.rst index b81e101..1ab02e1 100644 --- a/docs/source/API/targeting.rst +++ b/docs/source/API/targeting.rst @@ -14,7 +14,7 @@ for the least total annualized cost and returns :class:`Supertargets`; :func:`network_report` compares a synthesized network with its targets; and :func:`plot_composite_curves`, :func:`plot_grand_composite_curve` and :func:`plot_supertargets` draw them. A facility's targets come from -:meth:`HeatExchangerNetwork.targets`. Nothing here changes the synthesis: +:meth:`HeatExchangerNetwork.targets`. Nothing here changes the synthesis. :doc:`../tutorial/05_targeting` shows every function on the quickstart system, and :doc:`../concepts` explains the methods and their limits. diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 5264636..d586ef1 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -481,8 +481,9 @@ connected component needs at least :math:`S - 1` units (Euler's rule). The units target applies it to each region between the zeros of the balanced grand composite curve -- the process pinch and every utility pinch --, :math:`S` counting the streams with heat in the region and the utilities -acting in it. It is an estimate, **S - 1 per region, assuming one component**: -a subset of streams that balances on its own needs fewer units. +acting in it. It is an estimate, **S - 1 per region, assuming one component +and no loop**: a subset of streams that balances on its own needs fewer +units, and each loop of matches one more. **Capital.** The area is put in as many shells as the units target, :math:`N`, less one furnace for all the fuel, and every shell is costed with @@ -568,9 +569,15 @@ system is simulated again. The comparison has limits: so the network can use costlier agents than the target (on the four-stream problem of Linnhoff & Hindmarsh at 10 K, the target is low pressure steam and cooling water, the network medium pressure steam and chilled water); -- the units target is an estimate in both directions: a balanced subset of - streams, or one agent per stream where the target counts each utility as a - stream, needs fewer units; repeated matches and splits need more. +- the units target is an estimate in both directions, since Euler's S - 1 + assumes one component and no loop per region: a balanced subset of + streams, or one agent serving a stream whose heat the target splits + between two utility levels, needs fewer units; each loop of matches needs + one more, whether a repeated match between two streams (two branches of a + split stream meeting the same stream make one too) or two streams linked + both through process matches and through a shared utility (for such a + loop, the quickstart network at 5 K has 7 units against 6, see + :doc:`tutorial/05_targeting`). From targets to a network: the pinch-outward MER planner -------------------------------------------------------- @@ -977,9 +984,9 @@ third on -- and for each one requires that the synthesized network (i) closes its energy balance without raising ``RuntimeWarning``, (ii) never uses less hot or cold utility than the MER targets computed on the same streams, and reports ``'mer'`` exactly when it reaches them, (iii) keeps each pair's -required approach inside every process exchanger on exact states, (iv) is planned on the -problem table's own cascade, and (v) recovers at least as much heat as a load -recorded in the test file. A network that improves leaves slack in (v); those +required approach inside every process exchanger on exact states, (iv) is +planned on the problem table's own cascade, and (v) recovers at least as +much heat as a load recorded in the test file. A network that improves leaves slack in (v); those recorded numbers are lowered deliberately by a maintainer, never raised to make a failing test pass. Every case is synthesized again with ``stream_splitting=True`` and held to the same checks, and the three cases diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index 88e3826..65b98ae 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -240,10 +240,10 @@ 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.848 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 by 2e-5 of -itself. Every ``T_min_app`` of that flat stretch is an optimum; the refinement -happens to report the last one it evaluated, 1.848 K (inside the sweep, not at -its bound). The real step comes at the next point, 1.853 K: the cold utility +(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; the refinement happens to report the last one it +evaluated, 1.848 K (inside the sweep, not at its bound). The real 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. @@ -334,11 +334,18 @@ network: 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. -- **Units.** The network has 7 exchangers against an estimate of 6: it gives - each stream its own utility exchanger, where the target counts the steam - as one stream in a region above the pinch. The estimate can be wrong both - ways: a balanced subset of streams needs fewer units, and repeated matches - and splits need more. +- **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 diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index 64cf445..0f1641c 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -249,10 +249,11 @@ class HeatExchangerNetwork(bst.Facility): 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 ``_``, 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, or, - with `stream_splitting`, one that splits streams to reach them. The - outcome is recorded in `synthesis_info` (a dict; see the `info` keyword + ``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, or, with + `stream_splitting`, one that splits streams to reach them. 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 diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 7f90704..40b8dc8 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -784,8 +784,8 @@ class PinchTargets: piece between grid temperatures is balanced on its own: on each side of the pinch such pieces count S - 1 per group of them linked by common streams, so a stream across several counts once. It is an estimate, not - a bound: it assumes one component per region, and a balanced subset of - streams needs fewer units. + a bound: it assumes one component and no loop per region, so a balanced + subset of streams needs fewer units and each loop of matches one more. *Capital and total annualized cost.* `capital_cost_target` takes as many shells as the units target (less one furnace for all fuel) and @@ -2309,8 +2309,9 @@ def network_report(HXN, targets=None, **options): dict 'T_min_app' and 'targets_T_min_app' [K], of the facility (its attribute as it is now: a reused cached network keeps the one it - was synthesized at) and of the targets; 'status' (``HXN.synthesis_info['status']``); 'applied', - whether the network has a process exchanger; 'Q_hot' and 'Q_cold', + was synthesized at) and of the targets; 'status' + (``HXN.synthesis_info['status']``); 'applied', whether the network + has a process exchanger; 'Q_hot' and 'Q_cold', the heat of the network's heating and cooling utilities [kJ/hr], process side (`HXN.actual_heat_util_load` is utility side); 'utilities', ``{agent ID: {'duty', 'utility_duty', 'cost'}}`` @@ -2372,10 +2373,14 @@ def network_report(HXN, targets=None, **options): same utilities: that problem's network takes 54.06 m² against a target of 59.68 m², with hotter steam and colder coolant; - the units target counts S - 1 per region between zeros of the - balanced grand composite curve: a network needs fewer units where a - subset of streams balances (6 against 7 in that problem) or where one - agent serves a stream the targets split between levels, and more for - repeated matches and splits. + balanced grand composite curve, as if each region's matches formed + no loop: a network needs fewer units where a subset of streams + balances (6 against 7 in that problem) or where one agent serves a + stream the targets split between levels, and one more for each loop + of its matches, such as a repeated match between two streams (two + branches of a split stream meeting the same stream make one too) or + two streams linked both through process matches and through a shared + utility. Examples -------- From 7c24a672c042268a3cb3b090c0675445a175dd7f Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 07:32:01 +0000 Subject: [PATCH 20/52] address review 3 (tg-core): refuse non-exchanger heat utilities, check is_hot, keep dT-free sensible agents off point loads at their limit TGC3-1 (fixed): PinchTargets.from_heat_utilities now raises a ValueError, naming the units, for any heat utility with duty and hxn_ok False. Units add these with Unit.add_heat_utility (reactors, mixers, compressors, ...), and their unit's ins[0] -> outs[0] need not be the stream being heated or cooled. The docstring, the hu.unit-is-None error message and tutorial ch05 now recommend bst.process_tools.heat_exchanger_utilities_from_units( sys.units), the selection HeatExchangerNetwork makes. Zero-duty utilities are still dropped. Not done: the suggested check of hu.unit_duty against outs[0].H - ins[0].H. With replace_unit_heat_utilities the facility writes the network's utility over an original exchanger's heat utility, so unit_duty can rightly differ from the streams, which are what the targets use. hxn_ok is biosteam's own flag, and in biosteam only HXutility sets it. New test test_from_heat_utilities_takes_exchangers: a heated two-feed mixer next to two HXutilities is refused in either order, and the recommended selection gives the facility's targets (HXN.targets()). TGC3-2 (nit, fixed): a sensible agent's level now stays _T_SHORT short of a served point load that would leave its line no driving force, at agent.T - dT (agent.T + dT for cooling). That is a stream's own limit when agent.dT is 0. The next agent serves the point load, as biosteam's strict rule does. Before, the agent's outlet equalled its inlet, so its flow was unbounded (6e10 kmol/hr) or biosteam raised FloatingPointError. This cannot trigger for agents with agent.dT > 0 or for latent agents, so default results are unchanged. Documented in the class Notes and concepts.rst. New test test_sensible_agent_without_driving_force, for heating and cooling; it fails without the fix. TGC3-3 (nit, fixed): PinchTargets raises a ValueError when is_hot contradicts a stream's direction (sign of H_out - H_in beyond the table's own tolerance). A stream without duty may be either. Documented under is_hot. New test test_is_hot_validated. Tests: pytest tests/test_targeting.py hensmith/targeting.py (with doctests): 62 passed. tests/test_hxn_contributions.py -k nested_auxiliary_units: 1 passed. sphinx -W (SPHINX_OFFLINE): clean. oracle sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (unchanged; synthesis path untouched). No docs numbers change, so no captures were regenerated. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/concepts.rst | 6 +- docs/source/tutorial/05_targeting.rst | 8 +- hensmith/targeting.py | 81 +++++++++++++--- tests/test_targeting.py | 129 +++++++++++++++++++++++++- 4 files changed, 208 insertions(+), 16 deletions(-) diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index d586ef1..bae78b9 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -395,7 +395,11 @@ heating goes to the first fuel agent, a furnace at the top of the curve; if there is none, or if some cooling remains, it is left unserved, with a ``RuntimeWarning`` and a nan cost. A level equal to a grid temperature serves that temperature's point loads; BioSTEAM's own rule is strict, and the -two differ only at exact equality. +two differ only at exact equality. The exception is a sensible agent whose +line would have no driving force left there: with ``agent.dT`` of 0 +(``UtilityAgent``'s default), it could serve a point load at its limit only +at its own inlet temperature, at an unbounded flow, so its level stays just +short of that point load, which the next agent serves, as in BioSTEAM. **Served intervals and pricing.** Each agent is priced as one BioSTEAM heat utility over the stretch it serves: from its level, or from the end of the diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index 65b98ae..d825688 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -61,10 +61,14 @@ duty by its agent's heat transfer efficiency, 0.95 for steam, and 2.828e+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. The same object can be built without a -facility, from heat utilities with +facility, from the heat utilities of heat exchangers with :meth:`PinchTargets.from_heat_utilities ` (as the trade-off example below does) or from streams as -:func:`~hensmith.problem_table` takes them. +:func:`~hensmith.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. Several utilities on the grand composite curve ---------------------------------------------- diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 40b8dc8..29b68b3 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -617,7 +617,8 @@ class PinchTargets: streams_quenched : list[Stream] Corresponding outlets, re-flashed at their enthalpy. is_hot : Sequence[bool] - True where the stream is cooled. + True where the stream is cooled, False where it is heated (a + ValueError is raised for a stream with duty marked otherwise). T_min_app : float, optional Minimum approach temperature [K], a finite number >= 0 (> 0 with approach contributions), or a ValueError is raised. Defaults to 5. @@ -720,7 +721,12 @@ class PinchTargets: the first fuel agent, or is left unserved with a RuntimeWarning (nan cost). A level equal to a grid temperature serves that temperature's point loads (biosteam's own rule is strict; they differ only at exact - equality). + equality), except where a sensible agent's line would have no driving + force left: at a point load at ``agent.T - dT`` (``agent.T + dT`` for + cooling), a stream's limit when ``agent.dT`` is 0 (`UtilityAgent`'s + default), the agent would leave at its inlet temperature at an + unbounded flow, so its level stays just short of that point load, which + the next agent serves, as in biosteam. Each agent is priced as one biosteam heat utility over the stretch it serves, from its level (or the end of the grid, if that is nearer) to @@ -961,6 +967,15 @@ def __init__(self, streams_inlet, streams_quenched, is_hot, T_min_app=5., table, curves, grid = _problem_table( streams_inlet, streams_quenched, is_hot, T_min_app, curves, approach_contributions=list(x)) + dH = np.array([c.H_out - c.H_in for c in curves]) + tol = 1e-9 * float(np.abs(dH).sum()) + for j in np.flatnonzero(np.where(is_hot, dH > tol, dH < -tol)): + raise ValueError(f'stream {labels[j]!r} is ' + f"{'heated' if dH[j] > 0. else 'cooled'} " + f'({dH[j]:+.6g} kJ/hr) but marked ' + f"{'hot' if is_hot[j] else 'cold'}: is_hot must " + 'be True for a cooled stream and False for a ' + 'heated one') self.T_min_app = T_min_app self.approach_contributions = x self.is_hot = is_hot @@ -982,8 +997,7 @@ def __init__(self, streams_inlet, streams_quenched, is_hot, T_min_app=5., #: Grid index of the pinch. self._k_pinch = int(np.flatnonzero(table.Ts == table.pinch_T)[0]) #: Threshold of every heat comparison [kJ/hr], the table's own. - self._tol = 1e-9 * float(np.abs( - np.array([c.H_out - c.H_in for c in curves])).sum()) + self._tol = tol @classmethod def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, @@ -996,10 +1010,12 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, Parameters ---------- hus : Iterable[HeatUtility] - Heat utilities of heat exchangers (``hu.unit``), such as - ``[hu for u in sys.units for hu in u.heat_utilities]``, not - ``sys.heat_utilities`` (sums by agent, of no unit). Zero-duty - ones are dropped. + Heat utilities of heat exchangers (``hu.unit``, whose first + inlet and outlet are the process stream), such as + ``bst.process_tools.heat_exchanger_utilities_from_units(sys.units)`` + (those a heat exchanger network may take, ``hu.hxn_ok``, as + `HeatExchangerNetwork` selects them), not ``sys.heat_utilities`` + (sums by agent, of no unit). Zero-duty ones are dropped. T_min_app : float, optional Minimum approach temperature [K], finite and >= 0 (> 0 with approach contributions). Defaults to 5. @@ -1020,15 +1036,33 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, first), labeled by unit (``' - '`` for an auxiliary unit). + Raises + ------ + ValueError + If a heat utility belongs to no unit, or one with duty is not a + heat exchanger's (``hu.hxn_ok`` False, as for the heat utilities + that reactors, mixers or compressors add with + ``Unit.add_heat_utility``: their unit's first inlet and outlet + need not be the stream they heat or cool). + """ hus = list(hus) _check_T_min_app(T_min_app) + selection = ('take bst.process_tools.heat_exchanger_utilities_from_' + 'units(sys.units), the heat utilities a heat exchanger ' + 'network may take') if any(hu.unit is None for hu in hus): raise ValueError('every heat utility must belong to a heat ' - 'exchanger (hu.unit): take those of the units, ' - '[hu for u in sys.units for hu in ' - 'u.heat_utilities], not System.heat_utilities ' - '(sums by agent, of no unit)') + f'exchanger (hu.unit): {selection}, not ' + 'System.heat_utilities (sums by agent, of no unit)') + others = [hu for hu in hus if hu.duty and not hu.hxn_ok] + if others: + names = ', '.join(dict.fromkeys(repr(_unit_label(hu.unit)) + for hu in others)) + raise ValueError('every heat utility must be a heat exchanger\'s ' + f'(hu.hxn_ok), not those of {names}, whose first ' + 'inlet and outlet need not be the stream they ' + f'heat or cool: {selection}') values = _explicit_contributions(hus, approach_contributions, T_min_app) arranged, N_heating = _arrange_heat_utilities(hus, sort_hus_by_T) if values is None: @@ -1208,9 +1242,32 @@ def _levels(self, agents, heating): caps = np.minimum(limit, top + _T_SHORT * point)[bottom < limit] if j.size: L = caps.max(initial=limit.min()) else: L = R - self.T_min_app + if j.size and _sensible(agent): + L = self._short_of_stuck_loads(agent, heating, L, j, s) levels.append((agent, float(L))) return levels + def _short_of_stuck_loads(self, agent, heating, L, j, s): + """ + The level `L` of the sensible `agent`, or just short of it + (`_T_SHORT`) if it would serve a point load of the streams `j` + (shifts `s`) at it that leaves the agent's line no driving force: + at ``agent.T - dT`` real (``agent.T + dT`` for cooling), a stream's + own limit when ``agent.dT`` is 0. The agent would leave at its inlet + temperature, at an unbounded flow; biosteam's rule, strict there, + does not take it either. + """ + Ts = self.grid['Ts'] + k = np.flatnonzero(np.abs(Ts - L) <= _T_EQ) + if not k.size: return L + dT = bst.HeatUtility.dT + T = Ts[k][None, :] + s[:, None] # real + load = np.abs(self.table.point_H[np.ix_(j, k)]) > self._tol + if heating: + if (load & (T >= agent.T - dT - _T_EQ)).any(): return L - _T_SHORT + elif (load & (T <= agent.T + dT + _T_EQ)).any(): return L + _T_SHORT + return L + def _allocate(self, levels, Q, heating): """ Greedy list-order allocation of `Q` to the agents of `levels` on the diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 7a6af69..dbc69d8 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -896,6 +896,61 @@ def test_levels_of_streams_beyond_reach(): pytest.approx(B.outs[0].T, abs=1e-9)) assert u.duty + MP.duty == pytest.approx(t.Q_hot, rel=1e-12) +def test_sensible_agent_without_driving_force(): + # a sensible agent with agent.dT = 0 (UtilityAgent's default) has no + # driving force left at a point load at its limit, agent.T - dT + # (agent.T + dT for cooling): it would leave at its inlet temperature at + # an unbounded flow, and biosteam's rule, strict there, does not take + # it. Its level stays just short of the point load; it serves the rest + # at a finite flow and the next agent the point load. + dT = bst.HeatUtility.dT + water = bst.HeatUtility.thermo_water + test_hxn_targets.setup('test_targeting_stuck_boiler', ['Water']) + oil = bst.UtilityAgent('test_targeting_stuck_oil', Water=1, T=480., P=5e6, + phase='l', thermo=water, T_limit=420., + regeneration_price=0.01) + B = water_boiler('B', oil.T - dT, 10.) + L = test_hxn_targets.utility_hx('L', 400., 5e6, 'l', 470., Water=50.) + T_b = B.ins[0].T + assert T_b == oil.T - oil.dT - dT and not B.outs[0].T + dT < oil.T - oil.dT + t = targets_of([B, L], 10.) + HP = agent('high_pressure_steam') + u, steam = t.utility_targets([oil, HP], None) + assert u.ID == oil.ID and T_b - 1e-8 < u.level < T_b + assert (u.T_utility_in, u.T_utility_out) == (oil.T, oil.T_limit) + assert u.duty == pytest.approx(L.heat_utilities[0].unit_duty, rel=1e-9) + assert u.cost == pytest.approx(fresh_price(u.duty, 400., 470., oil)[0], rel=1e-9) + assert steam.ID == HP.ID + assert steam.duty == pytest.approx(B.heat_utilities[0].unit_duty, rel=1e-9) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + u, unserved = t.utility_targets([oil], None) + assert unserved.ID is None and unserved.duty == steam.duty + assert [str(x.message) for x in w] == [ + f'no heating agent can serve {steam.duty:.6g} kJ/hr of the hot utility ' + 'target; it is left unserved'] + # cooling: a condenser at a coolant's limit goes to the next coolant + test_hxn_targets.setup('test_targeting_stuck_condenser', ['Water']) + P = bst.settings.chemicals.Water.Psat(305.) + vapour = bst.Stream('test_targeting_stuck_vapour', Water=10., T=305., P=P, + phase='g') + COND = bst.HXutility('COND', ins=vapour, V=0., rigorous=True) + COND.simulate() + H = test_hxn_targets.utility_hx('H', 340., 3e5, 'l', 310., Water=50.) + T_c = COND.outs[0].T + coolant = bst.UtilityAgent('test_targeting_stuck_coolant', Water=1, + T=T_c - dT, P=101325., phase='l', thermo=water, + T_limit=T_c - 1., regeneration_price=0.01) + t = targets_of([COND, H], 10.) + CHW = agent('chilled_water') + u, chilled = t.utility_targets(None, [coolant, CHW]) + assert u.ID == coolant.ID and u.level > T_c - t.T_min_app + assert u.level - (T_c - t.T_min_app) < 1e-8 + assert (u.T_utility_in, u.T_utility_out) == (coolant.T, coolant.T_limit) + assert u.duty == pytest.approx(-H.heat_utilities[0].unit_duty, rel=1e-9) + assert chilled.ID == CHW.ID + assert chilled.duty == pytest.approx(-COND.heat_utilities[0].unit_duty, rel=1e-9) + # %% Area, units, capital and total annualized cost def test_bath_balanced_pair(quickstart): @@ -1280,9 +1335,81 @@ def test_T_min_app_validated(kemp_hxn): with pytest.raises(ValueError, match='T_min_app must be finite and > 0'): PinchTargets(t.streams_inlet, t.streams_quenched, t.is_hot, 0., [1., None]) # the system's sums by agent belong to no exchanger - with pytest.raises(ValueError, match=r'must belong to a heat exchanger \(hu.unit\)'): + with pytest.raises(ValueError, match=r'must belong to a heat exchanger \(hu.unit\)' + r'.*heat_exchanger_utilities_from_units'): PinchTargets.from_heat_utilities(HXN.system.heat_utilities, 10.) +class HeatedMixer(bst.Unit): + """Two feeds mixed and heated to 360 K by a heat utility of its own + (``add_heat_utility``, so ``hxn_ok`` False): its first inlet and outlet + are not the stream it heats.""" + _N_ins = 2 + _N_outs = 1 + + def _run(self): + self.outs[0].mix_from(self.ins) + self.outs[0].T = 360. + + def _design(self): + self.add_heat_utility(self.outs[0].H - sum([i.H for i in self.ins]), + min([i.T for i in self.ins]), self.outs[0].T) + +def test_from_heat_utilities_takes_exchangers(): + # a heat utility that is not a heat exchanger's (hxn_ok False) is + # refused, whatever its unit's first inlet and outlet: here they would + # be one feed of two and the mix, 30 % off the duty. biosteam's + # selection leaves it out, as the facility does, and gives the facility's + # targets. + test_hxn_targets.setup('test_targeting_non_exchanger') + M = HeatedMixer('M', ins=[bst.Stream('M_water', Water=500., T=300.), + bst.Stream('M_ethanol', Ethanol=200., T=340.)]) + H = bst.HXutility('H', ins=M-0, T=310., rigorous=False) + M.simulate(); H.simulate() + C = test_hxn_targets.utility_hx('C', 300., 101325., 'l', 340., Water=200.) + units = [M, H, C] + hus = [hu for u in units for hu in u.heat_utilities] + assert [(hu.unit, hu.hxn_ok) for hu in hus] == [(M, False), (H, True), (C, True)] + mixer = hus[0].unit_duty + assert abs(M.outs[0].H - M.ins[0].H - mixer) > 0.3 * mixer + message = (r"must be a heat exchanger's \(hu.hxn_ok\), not those of 'M'," + r".*heat_exchanger_utilities_from_units\(sys.units\)") + for given in (hus, hus[::-1]): + with pytest.raises(ValueError, match=message): + PinchTargets.from_heat_utilities(given, 10.) + selected = bst.process_tools.heat_exchanger_utilities_from_units(units) + assert [hu.unit for hu in selected] == [H, C] + t = PinchTargets.from_heat_utilities(sorted(selected, key=lambda hu: hu.duty), 10.) + HXN = HeatExchangerNetwork('HXN', T_min_app=10., units=units) + facility = HXN.targets() + assert facility.labels == t.labels == ['C', 'H'] + assert (facility.Q_hot, facility.Q_cold) == (t.Q_hot, t.Q_cold) + # a non-exchanger heat utility without duty is dropped, as any is + idle = bst.HeatUtility(unit=M) + assert not idle.hxn_ok and idle.duty == 0. + same = PinchTargets.from_heat_utilities( + [idle, *sorted(selected, key=lambda hu: hu.duty)], 10.) + assert same.labels == t.labels and (same.Q_hot, same.Q_cold) == (t.Q_hot, t.Q_cold) + +def test_is_hot_validated(): + # is_hot must agree with each stream's direction (a stream without duty + # may be either) + t = targets('test_targeting_is_hot', REF_A, 10.) + assert t.labels == ['C1', 'H1'] and list(t.is_hot) == [False, True] + for is_hot, message in ( + ([True, True], r"stream 'C1' is heated \(\+396000 kJ/hr\) but marked hot"), + ([False, False], r"stream 'H1' is cooled \(-360000 kJ/hr\) but marked cold"), + ([True, False], r"stream 'C1' is heated")): + for curves in (None, t.curves): + with pytest.raises(ValueError, match=message + '.*is_hot must be True ' + 'for a cooled stream'): + PinchTargets(t.streams_inlet, t.streams_quenched, is_hot, 10., + labels=t.labels, curves=curves) + zero = cp_units('test_targeting_is_hot_zero', [('Z', 350., 350., 1.)])[0] + for flag in (True, False): + direct = PinchTargets(t.streams_inlet + zero.ins, t.streams_quenched + zero.outs, + [*t.is_hot, flag], 10.) + assert (direct.Q_hot, direct.Q_cold) == (t.Q_hot, t.Q_cold) + def test_agents_by_ID(): # the agents of utility_targets, summary and supertarget may be the IDs # of biosteam's agents From df74460e1b4593971db6a4e8520e605091271b16 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 07:47:01 +0000 Subject: [PATCH 21/52] address review 3 (tg-report): keep plot labels inside the axes, lay out new figures TGR3-1 (fixed): plot_grand_composite_curve put every step label right of the step's end, so the last steps (which end at the largest heats) ran past the axes and the figure. Each label now goes on the side of its end with more room in the axes: right of it toward the pinch as before, or left of it beyond the level (above it for heating, below it for cooling), where the heat cascaded is at least the end of the step, so the curve is never there. Keeping the label below a heating level left of the end, as suggested, would cover the curve where a level meets it at its end (the fuel flat, for one). A y margin of 0.08 makes room for a label beyond the outermost level. The figures created by all three plot functions now use layout='constrained', so axis labels are not cut without a tight bounding box. This also moves KEMP's cooling_water label off the curve. test_plot_readability now asserts that every label is inside the axes and off the curve, on the documented side, and that the axis labels are inside the figure, for REF_A, FURNACE, liquids, KEMP, threshold, hot-only and quickstart. The balanced labels get the same inside-axes check, and the supertargets test checks its axis labels. The ch05 figures were regenerated. TGR3-2 (fixed): the 'hot utility' label of the real and shifted composites is now right-aligned at the right one of the hot ends, 4 pt above the top of the curves. The 'cold utility' label is likewise offset in points, not kelvin. Both are tested inside the axes and off the curves on KEMP, hot-only, threshold and quickstart. TGR3-3 (fixed): the network_report Notes now say it integrates along the streams' profiles as the targets' curves model them (a point-load stream at its outlet temperature, a non-equilibrium end state as its jump), like for like with the area target. concepts.rst limits the independent-area match to equilibrium streams. Tests: pytest hensmith/targeting.py tests/test_targeting.py: 62 passed. The synthesis path is untouched, so the oracle was not run. sphinx is not installed in this environment; the rst change is plain prose. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- .../tutorial_05_balanced_composites.png | Bin 80933 -> 85383 bytes .../examples/tutorial_05_composite_curves.png | Bin 76850 -> 81262 bytes .../examples/tutorial_05_grand_composite.png | Bin 59961 -> 60081 bytes .../examples/tutorial_05_supertargets.png | Bin 72805 -> 75019 bytes .../images/examples/tutorial_05_trade_off.png | Bin 107933 -> 113207 bytes docs/source/concepts.rst | 9 +- hensmith/targeting.py | 96 +++++++++------ tests/test_targeting.py | 113 +++++++++++++----- 8 files changed, 149 insertions(+), 69 deletions(-) diff --git a/docs/source/_static/images/examples/tutorial_05_balanced_composites.png b/docs/source/_static/images/examples/tutorial_05_balanced_composites.png index 15676004a5fa03719284285c534cb20027572a3c..2e2d3dc43556db5c1ab04a5dbc332f8a8da29267 100644 GIT binary patch literal 85383 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 diff --git a/docs/source/_static/images/examples/tutorial_05_composite_curves.png b/docs/source/_static/images/examples/tutorial_05_composite_curves.png index 34e7a358587f23f8c81082f411c506f7e2598efe..5f027029ce358ffa33e7a5d9ddbed45e83dc2c27 100644 GIT binary patch literal 81262 zcmd43bySsG^ezkrCSU=IgaLvep@4KZNJ>eH9HgX68b!eZlD}2|GZ;d#yFmhzI(lE%{Akh&zf_+R#lcIJxP6%h=_<(UQSA#i0J4l z5z%3z<0SAmUXilS@IL`3X&tBgwvU}$jU7yhl#HG1p4d7)v3PXF#ni#k!q$e1{q}A4 zJGZWwJ2}}o3UF{(|MvvDt%DhdZ{Q*fLUh7T?tvo_5#1N$KVqA9XAvS|A|iPyagC>O z3&XCSn(O%b<;`JU!K)`O_e+Y2y*Q$Ap{)CvbK6D+t)-rxY&NZ~Yj$pmGllj%r8Y(G z`D4s)jF~-_NS`e@PjBNth=1XKb|%EpHl%lT`N#E5Z_ISUt~{Uqnipns)%l{o+BIYj z$j_OwxI*?nAK}jhdSj8lzt{(H(*OI}mrpi|{NK-Fao3xV{rmY!>)ENp|9(C*eS8&G zK=^s(_+tCtk3`@2zj*%r{EO!Q?(lJfJx8x>u&}Tcn|EBxU~pa>>bI+^xb^SrllZ@k z6}aFACf}!?y)Gkl{``5piR#qS_K(tUF_bJKm|J}X( zzpN_%FAe_-w9TW~eSPi*ZI%1FC9eNdMmVd42d7Eh>0y^aYu&;}(m9IaTxb5ZwJUjS zIt60qZ`^BVD;qjDul__$Uq8nizhlv!B)+rosco*#=;_!|r6^8u4$PjzaNl&IPoB6R zNtW$s4VcCo@e-^=@_V|cKZ)b*zMPorOnd83dO5v`1Eb#*!DiL7P06mGsmOuW`07u3 z-^HagNO(hM*ljwVg@a>sX?(S22<&}ly_f6SW`uKJ?!*XPTDHxc`bPB zn6FWwfor1Y%Ij8<-6y0MImL!6ox9|j;!}O_^G0u^t|{&FU;psx#=WQ{`Oq6Y+qZWe zYmZWye|d32_tWFR$vsJO;Z%>s3fZiztdLZTu5=6d7x%s8dcBG5j>@6`oK;`quV0T^ zVt5p1XJ>Qw^^y0DJRK*E_uA+en9up7yI!$aF->PZRIcVa>Qz?hg45--xhP|>wP43L zSI28RlJnrxo;6GfPbGQ{T;o4>WZvQ83XhM^t#F*4gNJ%PwTWc+ ze=^IK4W=WdJ5LtCs{s&xUt=-6OPA7OMb3JZWm5`7K^&W|+E}d?P z%^6Tcn^4NHU%yDkL8iJGZ?smfZrXVjA$!a;-)w4Vu_cxz*9cQ1ex=%BQUSfv z$hxz?yQG)0M&iBQWMtV8!WbjuR^+lclm!dOEGSUrsM-2RLl%&S-q6i8s4A78)`eH* zVfNiC^x2i-1vIN%mwHwvn@Sv~l?|Sby&R}^`;=Gy>txsX{#L!{oFBPJd!|}S@x$6f z)`O)=mw9cv;bG@*XJu#Ja@4dN9qec~O)qq3g}!<7h8iCu;B4tPKI*xugh*^|r~(!p zla{MjX1P9x)va>LtqZ)I!S6WrZ8Ae{Wo1P}R~L0(nV+#IEh+kM-16lMjgHQGcKF!c z**^G_g^TM`IBSuXp z#8vz+DondHm4OCtLP7#zn=C9WVs4xGpFDYzS}3n{y?xLYll$SiY)>FLosf=-(DDn0 z*+AZr0#D_PuB_tXVi`)VYbREb1;%yr@sj4W*dWj6`qUkhM-l4vWrId4chFTx2?3&ySws zw&-LScJ4KZSUuQo^Fe+?ueJ(?pmrlvVz}kAwQ@rk@M{^O*i`w@HFyJh@wT>sb2u{K zmoH!NBAcMOJn$v_f|+P21DipmM(7PClojzR<^Zo^7H98|JgLf@+SsTN?=}@3!)+0A z^Gv>`sw(0eW^+EZs%vUuy%T-Fg-{DPWw!E9)1;-QMol?7Ix@;DM%?OOa+^z33GpYV zw~0}dlT$s|$D3Z18Maq<#_S`ON$>gl+0NRmiMu|oVor2Qbs@hFTw%lXs>NWb^|8HS zS~+|BLU#0;x)Q7gOh7QzQ66htQ$OCZTu*PyZv<*0il0@X7Ievx38YSIVz0V)ii(|^ zR@(zDy4Ef^fc7Z!Ue7kN=qoUe;WYkowf7Z1#q3`QHE`rBrxUognRwR`H5nOy1x3XY zr&)FMdXJv_LSg+{lhJ-r?egU0WaZ9W_PmyLMrvy2$Bz<;UK`PL9t*dH_qWD(FbBJ$ z^6JLM$%r;T)oy{k-(MLUiiA}kJctJO!IhBCL@h{ ze%AW&n6<`L(#;&~Z(=B3OF2P&u-M<*4ngS!hebrparkU24sXNOf-mU)C0ZYL^FHER zI#Og6QDm!%ii+zHV`x??>VxU?J3G~dK|CQSZ9EkP1(>^r2KNz)C9dV zFHe&kdR9rvOJ(@fIF!y2UY4a8$?iUvl|kLD4OYRLS1w^{YFarJV--_vys$ayW3=i2 zy@hww(@s?Ex=r~Q$u{15*nK`1oQ}-!79iBMary(DkhK8Oz2yGs{LIYuOJ;G&5SHed zM7Lkl`RpxAvOO^JTz>gw%q|7LlIXF>@4Pf(pjY~&156h&L`C)rsbp0-%>H^l8?UbS zY76hAL&e(mbYgZ3Y z)}1jb@`xLM+?J5bZcu3s$`P<8)NAB}2AxiM%U&rbOY5u=N*DU!Lo)bj31qtKDfD&`g0GQLTx3{0Cpbe-t%xAA!xFheu87B zikmcKdUW)sNdUfd%V2Wyf?uAZ>M7wEsPU@0`=l?)oErsB9Xx(<-pkI~<0O6pn?sK4 zAoDp8YlqMNDm{NZ+xb!DL=gjskCxp~uK zjmCAvEd${4UF!jYW<&7j9d-&u;!x9gS65fbqwjBIFeGF&eLuGZ7t19PZG#*Tfyv=v zVTe0-b8{1z%S>7Oc3uTh+K5~W>id@`e742{=6Z5tA*gRto?1chXgI6(96a>IiTst{ zc9kaU_3J6pbCO+uGwdWKz9^UL5Vb-XOwG)A-S6RsdUEy1a|`YwD;HS#alHiaSxda2 zcE}p|19RL!3AL!V=deuivH7p$qTRthdnVhSB1*A$l_UemtXE7@Ix@XCt413*Pa{}N zbhp0^(o-h>cx5CkK;}?%LnR>xaO4c&3ClGh<&66_WqN4$^-f3zGV*J76wn@b0a z$wl|tovH_Mh_k$mUvWn}*DnrL+y{G4gsohMKsq1i*s2CTTZk^$!NFl|EP&n;0_M(2 z69>dXTb^w*#4jO3fi+l<)}U>c{Jk`_v@F{agdy#)U8%@WNo3DjrWEX~#Scp?oS7<1 z3cSoKP_r|~+6x#ZE-fvsMLIcVz#F)$HR8qIW3832;?Vrs==cL#(v1&17hc) z7V7nWl(b*>=~xJWJI6L*?GSE{jRilHg%ND>$s>mZrlL%^AATn4svcd+pU3!c@(9q7 z2`_&;zcUf4GO@btG#&3chu<9KrQQ5-J(!u5RT0spNjJ;<+MO}a`(HeZNndPJBKqel zBO~+X>zJH9AI(k7)_0D*UBUgAREBosv;y2&Dv2Tu5TEpaeUPP#4vUYEcNlv~b>qoU zd7=EXz1Da^+JjA>gXyKA3dhDDAFhW6iSBQGgBZ*}ev$LaY+*g!YH_m)!eK;}N5I!)fJd~Qj;4; zS81IL*wn_KrKY9L4p`-5`wC6gK^{LiPaAk|PeKx?J`X`TgLs}$u`w@7luEXse~qGmuBohFt$Cp zs0}$&u;_l@ihYY)UOGzH6?Y?7OTc+9wIP(r3Sg}J_GDy>v6ZW6RYL{9Tyi@352Oi-isSv&5(05$`fq+X_?LN z+ElFV97wk74F&7H;K54gY(ozmk9;R=w1=wbU=8FwO!5FQ^NRs{c6mae8m>9KZY6Y4 z6ac;RWVkj8GF?b0ZTwoCf&tRWV{IQURGs}@wN#T0k;M@0j>X+Cup5(@oSy!~f{DM< zoL}g*?tlSA;M<~Rp?vmLH@-^jQNZ4k)0I3LtaC`4<{eUFtff^`9C3x&k2xjFh zw{NSxeECuUqZrMmDEX$&wB(v1IWeMCT$!4tObZfl8WZ3;;uU!{fw}}r9d^GNm z%_|upz|*gO7#xMb*o*VrPBW??4Q${6e#s}2!2on<2WRV(4Z5NO@f8cs*BL<&AVpQk znl2YdU*fY%Lh@limIymixjEvo{+-Imyzbp4_n*&>al0(&;K0v<5hjC;KkaG*%$)^* zpt>V}fcH@994{FmN7d4r+Zqq^#`x^=*vth5<`sKG{(6foe zsI{36Bu)3;=}Lqom0m>OEYYhJ(fMDmnJnkJvvzjo^VffWCGW-eVU)e!PwmqTY}B^|^c>yw%UT%eJS}Q&Urq93pNS2bh5?2gl(R0u%51L1pBrrgnMX z?D69{7?2#c=$1QVE=-F9B~A9gr?}C3t*Lc2AT-qypjeuawIRngO+)wDH0MMN7{;hrW z4}9n*>j7@$%RnEO3FYzE1<__BaRJ$4XYdj7Arb5bmILoMy6>xKLNG(@etl`w z2)y@@xNsYif&ecik4?N+4uJkvu%fcE&2Nt^{5LU(WhfMCVpH3-q6UZ-O~`!&m#XiR zi)fgVAM?kxQk9U^)_~?9Il`>*X!+k(vP1^A}b;>jGR8f8-|FTC7_;9W_ zKXDoSj@HD)gcP~c9s>DPAlqh`tvyD6S_eNV1V(c$lu3mQgWnijJlLBS9RUm=`aUX3 zhnN1f6inzsJ_CZpKJ18lvqd(gK|We+2_$F3GcmFI>GzgLd=BtEOPmCcKvXR0M?#8t zBx=%b#PWbN|CD!Bfek_$g4YqQLF8c1(dz)e3E5hgnk;P^{oc=G3i9%WLZb*P1Lmc& zI^CuXxvxHZFR|4EHal_(cr}EA1a8ZrLLXsGc)a(Xvgwzr!uvZqWa)>0-bP?hV>s(N zgfffqxqj{;C1A1AlbhWQDeEAqn*O>9Vhfh0#Y)@jh%wI7DGWW* z-~1pE{U!=piUOXjvM>yOii++Sc;HHsk>#THmq9sRD$fw7x}k498|ZE^fvJodl7Ny#Hj3<}{d?^c$$K}`Dvu*m?H{Vq?p1hx0jyA;eD zx2gFQfOpXHcs-Lmw#t8Vp?~$E^5|pfYiDsIXuS|1D@?b^9j(@K4(locdxG<(2LjbW zU>p;wYjSX#SWd|!o@C%0at5Q{Wo}!Z-Z8?x9zz!Jl^Q&Jsm(A7a#QCh{mkoIz}`Cz zSY*sXOz#1Q9jO!P_+y}Rel3DcZ*F9vuiW0GADmFnwRkO^Xd@)K$rbpmhJ|A-004r( z2ST`D2ES~K|*OU}u-S28K6VJW?$n)GocZuigR z63K`E|7Th0S-oork=zo?YXu3<%C=`?TU%MSRB;ioH|C#xi4o{hwdH87nBSFKA{NDo zxdfRnE(ez;`tOSNBR~f7q-^mh41z!iiPa>Hv#~dBJjN1iIk1#Cgu1l9p}ASZ#bu*5uBPRU)J6?` zXP>O&uWee9heqq#6y;l%G7W;`o>0f%`19k%UO5f1ZU$;=l{S}gPxA|d-8e(%}S zt_Z^nE2`&Z++X${GqbVf(ofMB$Vvf-2#l%cxyZrF>ewAO?rZD~_B0=Klbu~8lk?!A z`<)8?yz>3G?VXliTkE_ROV9WpTE`lGQqK6Kq>rJUT_Y`|q&xGyBW-PMt{Pfei<|ts)&m-wW;{+a?T0sJ|DHz| z)4$kmc^?**3XVsL5lL1CZM*g$S8XhT1KI!vaE&bLJ~rTi&Sq5}+ebhj{%&jHUF#S< zlwr##9P()WJ3mh>weOj9uR+J%k7fqAyaTvju*hL0`2M!RLDiPn>X!q;GoFHWC*CfRLky6wFqs? zL=mrd2v%MBa*E>~Ga!F*@85pkAiua29UWZCDxmp$Xs*sAm-x_ ze^M5udm6vW?G+O2azbw0dtYYOrW5%G;?13q{fIg)rJ+Tk?xv|@s%fm*?a47Z?IHbe z$t$~XHCW`HPqp3C9G}#?Z#fUnFfcM&Kv13J9kTO7GRNN<0Iq>Hc20Defn(QYgj_}l z?p>Ut&xqUfOCTN$?OS+yx3s}U4Z#7G$?8-KSypB5KCmEE-hhcwz=HN|QHsS<^%vg% zKzf-$zT?fwaIgJ}Mv(*ko1x0uZ2|N_*7gJu7GL)_&b)I`&B9d2H2i9~mcNGXrMzv! zgHpGgoI5!yVmg|&5mpz}wZ6L~1w7~U5rs%>NEawWYf$B4y|zk!^X5%=KzgV}Q0&hD z%cqbNGp?IA8wh4|o=N##xqkZHtq5hI-q!&^apnY5+?iQy zHq})TUzh8AOl=NA+kGeQY7_KTY>(@-X;taTtKk|)H1*Zaf7V_g z9dAN?A+7t;vGtm=>BK7e3FCe5cb9qd8kFLI`hTPk$UMN42M#U@KbQmyN7QT)4dnhT z>oN9*Omsm11Y!LfSq6+o8gv@=ZgSr#u2O^_+dMIoZA-PpMfo~KY+p4@^W`H0iCBc} zD+oMG#>(weV==`0v_Qx}pg!qO#YzklH4g|)-Pld_rRh}u^CB_7UFk{jqY|_phjp~3 z=&7OD#MfFvF|v`FgEx>#<)J2lo4(@RV)RLNsihyq=D|Y!Vijll-BWSd!$4u9Dty9~ zwLu$?UaT3-O^AM7$X=*~6h-vi*us@XjLWYz5uD4ntY}4-xt)yZEaB-5y2$%kuj$el z%ELo`syRH@ySYENRPxO8qpd9`hBs-GxnW7YXf#DCQPC!nt4CC!im)Ih=C&b@(dekm zZymCANz?>+kUeP($Q~E@vFTypEbjScG&f92WgRzQH?pLr@TkSJ<*B8$dE(CxZR2m& zgMDvYL8f(0S2gK33Y)(CZFzk1x4VhzAChag>{Eey(_N~;4NrNIvd$181;sUHwubC( zso>5~_$3EzfEQ-7Si*H52^O-^`sR8BsnF3ovO1*`#6<7$t|go>p>}Sr$VJNyll$j} z8s#=vt~X@A`^RV<_fLLjD0=tE8c$iM&`Ntc$*wrEMbhmbzXvfeDkT~Hk+FzZ#XlL7 zTk_YdQ1_MiFq>Ak4--F3n3f3XBE=R-*h?Q?J$IarsY841HV%F=JIz8+yN3rXhm0q4 z2TZ8x2UwNJ@e`RBHC_Y-l?4zH<+4{hq`iCO7+jlrqW(Xb-LjpuIr>$tYis$dV0{v` zFH|W#^aD*}a|y91Bm|yU_l@p$avYQ!8$8K7l8H#)+&Kcjf;MCfx@6*Wo3K(EF>Xs8 z|LuMY5!w+}9uf*-kcY1pCEHE4r`rx)TORCJ6yTE+NSzVfZ@ z1k;a9wyPYXIfN`T`^41bgCH0O#EFcfn(dP2u4%1fbFw|fnb^Z*%!5iu}d3 zlSk%<^Qe$PPs-|EQ43B!@pUFLxbGV1ZZUNe=jddJS@Xr+w+O}N%SRa`mHl<;p|g(R zuJD;Rs5?4dqN-emm)FDTdNLwVk>A#SS zHX)VRQbjv1z^k6@X7>zM(FZM2%%3A{>HP7QM2?qV)9r0-Y)+$)X|^YH`!AFjyP0x+ zZ2Ua(u=-|pUp5)1;pOHsseI>R1H-cWR5XrJt&GjGG>C;3pWK;5w2)RW^Tw0>{gX?i zgNHrpQO`T(Dt?YEqjrM(nQ}%wMDN8$ku~C+e$3l9PP=IOY?e=~@=ZExkLK+g`qb=~ zD~gmkCe{m1I#*3>@=fN78m&gK`Wg_Qyf) zzL6r;B-uF8>_>Wd6kG@ghS`}Bg-|;N!Dzd2ZA5$%FY({OUHzy{3jM@Ep zTN3V#3@PX!>i7%Ivwgehq7CnY4exbab~kx@5M8IBEc&-->}7y_Gi^Zp-wK{YpJlrL=O4J*4Bt7>FkS1xroCr=4v2ySS4gZ?U4J=15_`2GUw}EE3_Gmw zy9Z+=xMahpwKV@wyYR4>o*3fg)RK>$N`2VQz1Y%Hg1HEb_!eh;5p`VS*zZZrtKrA4 zIUDbcToHjKWlnjZ3(ze3UY_WJgz%-p>3qG4#k^BJ^T&RlFf7|MH1pd|KeAeqJv#f9 zjHn0kV{DB@;OuQ}f(Vh~rz&Wl7&B@GRiKb9!IFP68}LgJs-O`i!87LhwQvO$GZ@a} z$YY|m;ySpEW@p^dS6Hje#_SG}e%bIR_V~K&<+uI9J-KRh{G;<}qHo6$jg~&TIu%t% zV`1Sye(9W_B}`u+U6nzXDl<~6=TtxWoiH;B&v_8Hv$2Q4%wjIuNLb55Gp z-;POi=o~QPLa>&bm4zh6)%*`t%+GZ9HoFLo!fKqOHK+-!B@4lE5uv~f(^!wRSexP%E;9NK7AxOQHHK_~f0 zzT&WetGdE-ZTbC}aVxFQRXR8_qE}Z5<69IPd6VH+h>64o&jeDlkiXdUU*gF|AQwv( zRe3M@Ow-1RxrSe{ZDXqIC|8W4JP)y_bCluvIz5tAtg(kRvyqqnx^?hi|HO2V)HO)B zBavjb@jH_uF_FDLB9m+HROOPlSCz&W#Sc$5H@_2?CL&5oL7kM*P`{)u|8jd?jCBfL zcjx#xc}&~5M#yOHKN!6RZ@?oj=62=1%hWCFLgtPxz7~lL!6%T^3ZqAeiOdOsUHV1D ztq*Ey#?+eyERZ*HOERC!B^zaxV9BGZqtZU2ss0I0>RfP7*9#fLukL)OKT_6}JaNQT z>C95rsZlGxr>i>XUGK7x^Qa~I=95ps@`MQH(qWF-(?@)1yPEVz|3;Qge;gx^v4^}{ zrp=@$UaZEZ=343-F@~PWdY%Buz`W=Aq(htjYk|SrLQiT=5{c;{7{&Z-GQZ zZb5w4NU#ueA}-`TU^LP{t8VXx09=X)O^f-wYk`0mV zIUxn5-^>+LE(*i0D#Q4RBf;u(&$s3ZkL15SLr3IGhDg@#$F(X~)@t_>iy_BuHHY%; z3=$&Wr^Xlk8?(Q#piWG_ZOZoB{J0Xc3y2r{jtU#gk$bwna;-dc#*ydvkd-YHQ+ar$ zEpwvI`NrMNpsol=YiMqrqa^x#^;Ao|75^eFZ|jr-5z!3a^JF;#W+RRBl}neu^kR%O z&T7cJ$&E29PNvOK@}!6VevK{LY{k(k=2G=A?G8Is9Umtq@cWdfUY5;_mHm&tq447N&^>3;0xj+}69noN}j zXk)HN80p(9sSCwA(Mxk(dU<&U1o&A<0UDcon&K3OQ>GRTC$21#d7u!!CpBnILh6Cgk@UiaQDl_r^zthAwIxCK79cdSSl&Pc7& zCGSeAUrOb1MvXHEnY;g_u)lH{(>+D#AW2Xu5}#Eqb_zXvc?Gec6dw9;onp zzy`dU@5WoiZ9Iu!b763+2S_O^1qj-pgIhSnQHBuvB^(fC({9-4__&E#8JdqeZ zIGIT{61s8X*CyJCYT(l&I&k4FlE|zmzj9tEI=&hYb+6cLf;%pL0j|A_cwt0ybvNf@ z{)w4WO@4*!W#1XMhvvf6X=_{+HgKfBtPbt$lyyM@vr;ung^+!END*`lW6|T>j~*ux zt)Y=1C3?m6Av5_)k?+b|E3Ky9+ywGPN71B@E2=q7)@t6t_v6()dB&=>FWnSzMpX=F zO}WSv<+Fb_!wSK8zbKH%^MKmAP!i)wRG`Fpp<^+I)7w>Etm^IIMvkn`#Sq^W#X|!I z`d(E@w=(65z1GT?%j43+SL4+;DhxQPRo0d<$Ux@wTO4{sVxJ(VV2>1%r8R{-qrFw* z$d^VAGILjXO(kXwZ)!f-Ty}94U(&BP8-;H~7;AHtZ;F_vZG}bux}Twuf5*zktXoey z5%%PcwKa^{E{-61GTq{j63NnU?~Ny%y*_d89^$#r96&6Sm)1FIdNr4=FZX$o>etUT zsZ%cYVqiNtO~U`&Jh50QCc^bSqgf;I&V7{R4&hBbM>cO3mm;7x<~icI+9g(}70)`h zOTDEfqWxfektOfAg|=Gw%G>PXPvwopsG7T(?_vIE9&!8#$02tZ zBWaQ_J!%MBP8Y&!U%}Tv7kCz-Hoc|a`VVv3>Y@W7E1*eb$T}DofyydbI07VI=mZG!2oCKWH5-6 z#@;9q(a$8EjV4!tNm{PD{YON_my_)@wizLdmew42pz*-f2bAo?-Qu{BSyh9!?Hq2* zolvxbQvmj6e~QTPr*1}J@dp-&c9=UO>BLW8#Jwp!{vQ}J?K@ydYjI7^j|SgMu6mgc zE9{`3c{vYWPQgvAAq;?EOMa^bmd!>WV;9Sh&2OFM+3ebz3;Pw?R8fXiVmPPf?Onrg zQ&3Qkor)Mq;(gN*D9ru9y!Oz|i`;=-kB-fhvm6?8&z@yv)9#@P4|rtoICNV`**Wjj zEPgJ|aCju$u@Yk=#ENCYL|#Gc{ZGJAdffj=HiVbA9wJxY)r)V;&D6qOE@)cF8k??b z598*NYKrgB%-@OqHv9h0eJ|%{o_LD&^{+c&M|dySBdowHMZ`PaI5pLBJCI-f{v_QU z1_<7K7dkSNhtr-_J{)1m8NE;a$~jsoB;==-ON^00XSQ~itNh^LT)Mk6+9-OrxSEh+ z5>%s|;RCJUh5qsm?nPQI8L}Ob!}i}fPeaHrLElIkeW0W1Rb_5#hsn-V7`s~#{X{0D z%pL3L?yQ5^WED5C)aqi|-d53tSrOsr52gVUp3BzU4w;~p#dv3AHp%N#|rf$Q(c4t#qhp!*-uXLob~TS|?TmENa7V-KFpIp4{QMFe8ZyW1B#KaWtX^JLGLVXF-2j@MT< z%P>AQAbq}_cWM3k&bv9y^%HBEi23`LOF?YlOL{LIwInC^e)`qe?MxDLVK5WHlb8Kh zvFQ11+a=~4b>?${&e0J=$^ilyOQcg(>Al6Dvrf!cnKCdl*23}b*?DzjtBG2W6sRwM zCPDaQzOFU(<~x1`vS21)c3QNxF5Owr%*TB3cC`?bi7U-+Q&`VZ+!=XTqlo`0a$P+7 zrbtEHpXIb0B0g<{mCr11l8?>hj4e1Pduh4B4UJ9IsBXJmXutJQ3YOe=ZIpZ>!JpVu zmqu=i4-06H=F!f_O@;PK?p{WwGV=p5)?F+h*f^S4R&iM~iXL0m%*+ZSuc~WjUc+kC z&kN6Koo{x{hJ~0dkm3Z>o?7@Ic23mCfg}Ve@8qOvE2hG?^G48}B*P0yvS%M8;w}~`adb1M)3Q{+MT{q~&7k?&5u~>lcdTLuFAV!s73q%^G z@0Qif7MC$9YrRoXz8irkBGI>tNZeG5Fs|ukZTpb&B>!k(uh!R{<_n3HkGC#AxiyHs z|7aKo_Kcq+3~L_@iu>@Ut~%;>r)j!Y@Qnwwj3DSLjRqf7pNPNVzS$6s(CkH&dUI+DnAX18$>@0=Tcu}{LpZ8n)r<>?EffR^`RXF( zPV(H%@m9U?i*4M%04~ja0v#UVIQ?!u>$R*?VZ|S!UKQyiO(M?r-VRcZpP=KsLW2EF zmMo{?<8J@#`B!s4^9qzWu0I1{^UzzXRVkQwrLBXPig3 zChZ&dqMe+p(Ovb16A2eJ2r5W4cRiYzsM{jvdk`-Z1e%AD-!H#iYi6>#HO-#br)@&= zctHOTIbMLQFw+G4NR})B_?LRh6F}B#C{8PS0fYw*CfY}PEuBf=H{k#Vlgw8~x2j={|3kBTx ziSGTo+J7D;djWE}(%`^6Rwy+z6{o!}u@JKS$xO zc=tvHh{QhZc}n->as_(Sz5DPL7bGri&X&kFl|+9W@&FUCiwTX_PjcGg3)9jAfV@AWWjH1r^qR95nmXgmCkh zWhh_lz)t-k>BSrg<5R{7s@yr<1yN(5MwJ1jXQZ1c#%vig4-tYlQIMNN^N#^d0Tu2` zmQ(1xoj`LJV?CdM|0g%XhNU#FYPIvMg7WYGLj7~ zpfpQI#ix3}VR7ZPS2!1Q)4%ihoJ<&+rTTH;yF;sWnZ8ggrXR)A^`g@ooHFT;Zm+<#5oQuJWeYcHzk2kkR=!;+ljbv`+!LBw(U9{dh5G;Q$=EefY?`$j6uCKgon*D?b7mQ&1sG<+m)aj8lR12{>QC|r z=N&}F++fNx4^MwBScqI|ZVbsb!XWcd)1d2)&u1@5YUe?lX77jN|NLlT72}MB4>q;=UWm`IEp!0wPTQt{l5Mwr9OF zv5Sjv#NdIeYktIKLRjFyDZ*5qkAedwu*~3vVyw08!}nL}&NWq-1U#M}T1Njj@f*m~ zV(-MnfYH4-zL+dIc~7YXbA|h@@x{vVaA}sT^BlLIiW=xns^(&$hloaGib>CuN=I z6;EAwBc{{1K%!gT2$*fi`+Uh;eNZ-9Gs6qz}Z1f_A!^{e6u*Ztw_k{kPfjwoWc5UB`K zu`#PY#ahCNWV4#XQz1}Br4MD6WrooewGNBZ8RXq#=*|VUKkEMN0_;_>V=X@fX9%Dm zoOIyjW;Y_K9<-7Q-cl0fPy28Gbs*96bzzU{)rnHU>^)sCJ7DkH_@dBN-ZtKi)_xZ< z_+1oISom?`P9-L44}!zn$wp78f^b-$q4RrVcZG{`sk_Qe@7A=pW6zm&T*wngX~OeY z=m_(sG%Qh&u`je3ifuyeJ=IX9ZjZm8J2?N2HY!UoXx_wD{LjNSe;#gb`b4vNW~!bR zIwsrZ72$F27GQ!Z=Znrd2+-cnL=I#CbRGr~n%Cdjo%BGj(D65J=oa`awGE=$C>IOj zs=SFUJ{Iz8v#2j2$pcn6V5rQ~>d~n#pgZKjb)pW9UNAxZEdF`K4DYfD({!V;^ zFc*p#UKPXzjl{)W)bOhV^fqYpAholxz=9o)!$pleU(k>Z%oFC*-hsG9L97nObBS+f zvOcue%(}ab)m5)`*uBHfG=1l$lGrpa9 z@{ep-U2{&EbG&)`w&dyZqh4rRk2B2IhEAVA8vAc&JD_~aPoNEI8{?sU)ro?NO3;06 z=BzN(rC&3sbh@pZ^@Iqg0SC~?Nj0V5@~?`v9?LQRYfTkZ?3a_FhyZ=`PxLC6iSl{X z*wnNhiP`(U31#3OtgNh;n@#`uXQp(}CVK~)?@h7tbc4PTt~40jUQIj@GKSh&QW3VX z<)=>lrjalAp^I26Ej_(-kDMsU;ABhTOyrV70}$sMREI;u^%U2>9m@s)Cs0~eN8a$) z>d!14UnLiZh7V}UdA|l7G=hvs3oq0XcG&UdAnhBcPMvaw&d(jp)~{2WP>`h8-{&T> z_gk`eq9OFWWb%84Ea<|Q(b9@2v>6sKgKn1uXmo7b+g>$*K5-VN_G5NeWHiGiPEkeI z4NC?zMu(j9d!uRuY+x=2yDHzK{dTNz|LaQcmG^(a=Pz2}Rq|RT-eEXOUGag^t|QRW zpcr}UfqD(JgsrgYm4+cD{>Jj|(8N82)NS|9(LfQYqdv43zJ`8E$UEwgirYfdrih}W zrzpeVdImJo%t5Bm`Rn`Jc%&ig+`{fGQSk%C2ss{GvL48FwtnZ6Yg=JU)w{~Ot`0kH zGc)cyP5g6fNfcB#-sh&qLhs)~cq$ZNa_vFO*Y)@6w%@HH*$v)7Q%RNsRKBUnyYFvL zv+XVnK?PGyNXWU0VH>2OhXNY2-*mS!GBVCWS3mjQN+jmALML=&jhChJ!*+m%tJsF(bmS-r%PI*J;3K^;g z%6<7acri$`%{CNE%P|yTz77V$Cv$FH*FgoGP&=hmikdXE;g#BOwYb&23%}a%>XTJv zO7)0br{vPD-t@hbUFCz5?FNy7@0)NkD-E#-;nGGylsxXmGR|WNs+nzP)bR+4~DQZ~nN{Ns-<<(=F(8#fH>jWP| zhq*>=wa^1EPbQ~tc2KO}Z_r8X9LC!`mCw;}*~qpiuIiBlujx}%SfY`sqvmq{|KUcN z*_aV%f69*I_4fJ2Hw~==*+zI4W_cA+eDO3i?%MvYlLM7R8iVjqSvM4-QT4&v3u(@h zR|i}C5EWHEIz12fR2ck1I^*|;h;$s-_5a~tyy%?T5}u?5b#ml*TffPnG2sxW+VZ@= zsmK5WlzKgfe#`3x_Tw`3a9hbl1ynYlL!65Yl=q!K5%3jm(TMQq_$ZBBtbp|K)UxJR zcXrE|K|w5;JlqelO03=AE1JGxuT%nkD@FJg+riS7+t9egAm8}E>wQrN_K{G&5A76P zx0mkATu6w=1BauKqk4da*o5Jy!$gV{L1) zs@}vYXF#)wj|LtLvUnlDsOqW>)FKmq-GiO)p}nuK@2P6Mj}(PZL*B>ZS{?9=VVKYD)sBl&NyW#~X))x+ zpuaZ}{a3_nJsD$@JMfEZ*y1|$@=qdVhM~xNT80qxxCgoWNqZBd2RIn`StB};6_8m z94cF)B(}E(=0bX|Bv~>*hA}HC>9d-uYQgBVUWujVa3c1sEX`Tr7oEh#lF1d0Z1om} zCK%u-D-V=bWxjiKZ#DwO`w7>Xc8YbCnETA$-8P4z*jN)sMp`{?%b7L~4t2LHPh#f7 zeqnQ|$THB#(FdG_QG)bPI6VwpXmog=%t~^vyStlY94Sxz=ka6ekTqy-;ijI-he>>h zjGPWhrQiP@Fh$Z#B*1r^$oGppi59g>QafvG*fzV-8qHPDDBr8^s8mpD zN>0>$a*C(tQGoZh3)DI9biF-w;dr#sMJ&o@NB>oxSqy#tqC zAa_JS)ps>CvOQIQ^yra-y1IHJ+@=$PGz6*e(P%;E=sTph{SF&ji&H{zF*mfV9b`kP zJ||rK0e9Lg&O=3ZQSZDG(recVoof0aP7ope;X)Phu2^ECGjUw?LNn`)aBht&anfj0 zHp)(mZB7h&pNb&Huorlnw||%#JFyqr0S`f+KLAdP7;)-SsvQ{Fg!tpHh8DGkPRBsF z?1`xW?l&m{{k~iI(HqLeJK!#X2)M=SrEy|@WugO)-+g1jVhApBdWImW&Uqf^G2-@G zX+;GJ!^&o$7Fdi=8iBB6Ln3A{Y8C zmXwqv#;TQL2k^(__s?3j?C`C?EQ47i=(L08C;qo@-_k;0;w2%azTjLzZQVeg0yDH4(*EsfIBh;)d6G{~Z)u|VmNk`$0`kPej& zN$FDQmd*w5T$KBH-Y@vwd;f9owXW-2XUxob%rVFCx=@~JGDy|r)hZ(*c6JWzG-Y7+ zDh|gUu&AQiC@r~;wReJ4PJ!fHv==ZTzH)mwZ2&+`Y#o9*X?15PjlKm-Gc_lCtBx9D zB{*+sk9F`ueB|aQ>$ftE`@Nani%ZxisN&aKI6or5iO(;~<##PC_I6#C=xCNE(07*I zdrP!Bas_0oM?WefubCh-Nsk-0{Sz_TTa?hoGH1?V)jS$IV!qT&yZ2#z<+$^x7U7di zbep4G5m3;zdbRyUqe`B*nPW~JdJbSEgU4%E4yT*w7&4rD!n&UzqW*Bp^_(9Wb=}TO z&bPl8qvxLM`i=Ha>_k{@oWUHPX3IUW>(t4sbiVgV5eQACOLI^udE4K`fcxuo+d#0_ z`-VD=ed`=;h1}B!u4Ef##ZG-UYvttKGz~-k8SOW7zAMj@bIIfY{;?!SuJWf&mb1ET zcrRNfk(g+x2sj~-%jN;(QW9(5a{zQq=pzV}*E{2Z_}U=)cys9{C64ft)O;5372_!$ z@@iV9M(;mNw9W>5`c~Yt-w(Q5ibNe@rOq^EnPv@tulro+@BIrh5z{HU8$lM%C65a@ z6E==UBZ!QqdXI8#U+tY$=)Y>#5P9|hbrH5{kBcojL|rEKu(|V$_etN3!Fm>-#tC93 zBRhrrSreEJeHto>?);T3!GTllBn;Px43U!w5Lj=+H1>)!cTN|7 zJ{e?TqsDZ9QcUA$iLN6{MEaF`8i2@tNKzRLbs95tXz!{Ta6plCDT|hp-qB}~yhbjm z0w5REVv$-EPh=#w0B*6G25WRqAB818-aMEU2r z;Q8-bOMO8yxSkW8!FW`cSS2hlMU>Nudgl(o98?D~l=F9PUOYMe9ZuTja!`s<@HP1# zJKUS)fhy%W{Va3VoR;&j)t3qmks5whD~NQ0EVNHq zwtcEu%gVp8H{mWA1nRtdHN0K4+yP){BqC}y@&KmL;}7DCg;r~$WTTdxPh_s|e;S#Q zx7w~+R3UmW>oaXa=@r->_k?CEPA}z6w!h&56v$>y#%lh({^76PLK0l&bVx32V*_DR zrxRi;c(d0u#n50@zEcEQ)ZomgT2!wbE$eZ_8r^*gAYe?Dl*RQu*d=ifcHyau1=!_; zng9M+_Oiz2bRl}J48SHIPizHKgk8dD6!0{o+l0fWPe_D{w&Q5lB@zCSd3|Ckl=<8T z6m9((M?iqKjBH;Xb-5ajl&UGm+AGEZtN%bREtarQ7ZP4#;fa3qMaU#jGGL-cZa9$_ zjF(Jvjq$sc)Y3H0IF8Q&V7Q;lGpEsk{E|OAcEU!r_W+0)?_i$d_n?e3QL7@GclkYJAFpD_G)xRU` z#{1|2(C4*(yJ;zPv^qh1TZ=qIZ-8NFFDf<>m$j9#kyJP4V7Go8e>=3LX7J_ys;D68 z^2#$ve4B2oTXq!Rn$(oSg*&nh++5u_M|c{hJ0e>V+J$^V9$1m~|rgE%R?ojcazJ*ICV7_8>4Tanoq()If)mu@Ddmy`{+)z6#}(-f?0SR$%?P zfQwNx$hEMiMz<@9qgL7*qv4 zk=*!y2vsu?4nn2OpRJQ}cl7ZSs+u&QKmbBGnO|@J??Ch5!&DiNP6WLF5~!a2GE5s( zFvPdvj6=3{dDmfv8a0E22aH$3wZVdmN(b2g`FTzLvNK*f!w-w7G{|h!Q6FcCkv>H7 zP?e{Uafs^lu?ex60F|I-toGN)XyMHZooNj8@<003MPAv*O-;3*?$zAOgrWF7eesU* zdn%`22|_e5@rX4iww2Lo($~ zNJ`^sz$pV6ANj+;_AMhaf5*rO74&x;AQ%6WpM#kh%=b1GBsQ8e=yJmeT5qZI@g_wb z+Sp2BWZeAvb(gN=;>Mff&|5FBL2kqQ8%=w`(D09eV4wGp+fd&-m(jzd7mvAI`**Lr zsb&;L;t&enHGb6OAWfcNahN7YRh~vh^1@fB+(G32OYRCtGJRN=QNsJ7{_v`5)9AVS zA61t(yAN`p{DcU*IZO!a>=ljA^>9J4cuz zGMZ^`JTB`u|4C|~77!z1tRol;RGkz>hQcBnpL;@Cy4PIi9IX5v(Au0E`k z<+c1^AhGZk6-)7ChaIg7!fMKttxeVh!q9x8Y+lzTaoLKNp&|8zy-+q{FO~{Z7Y{b z`L<*+?MmZ#82xw?(UACmQiv|=h-F|qGp3x|?Kd*Zk69#>6<90HQTPfPo=CPH61Odf zF#m;J@O@uSxoRnyI!!k=1*Mn`U6a2;kkQl|AJ|t&}|%=!A5bnvlA(jM}5f0OnolhZJXxlCji zWG1%e4{VPl*q&9DqC)u$$D9H5T=K)s>9>QEht{g)6k=s*vFMdPyzHO#}H#PjG82qXenqj4J`S2#j z--qm$w90?oATij?GB-7(nVy;oH;7Gl+WU5YoI7W&gx4fHdbEoVi5vV|$N@?thp^pq z5#9dW-Xu%=n(NNO&MDkLH%SXI-e4ztYd#L519l2Z9~+K_KE+oKs9-HAoXx3fx+=al z(J*=vB96Oh`9*OXM}gR0(5`T)F{gW*4ap&UXO@R3*koAA5uk{hGGQbEBA*OcgB6#Jl;-huV?oX)|O`3binB3+5qF;?==oXFr(Cw!$ z;_zwzg~HbBt_CGMs|~w9w;aAJKQ!jB!CZIzL0#xrYP-Lp7qE~U%~?9tnMBM=!;ps3 zCuhg(R`guuuTV}W{l7b08u{2aPslhFT1XBz5%Y{p^)SO-);m#a!mPbBiRl{acU?+* z%YiuX==*p47Aa&SVqs+{lf_1VfpcV_nX6T!|Zf ziFI342@uhgGJ|KJYMUtSfU3JSH@>PKZ)T=2$i)3H*-tJg5 z_I~gvJaNoPJN*rkH-uB|2|gSf94F>Wmb~o&6wE#Q&jt3F(b2Q?8pePFUn3QH^5h(i zp>0Ovo&}K{#uj6b8jyyvDvZ$nwB}mmz1aY{2i*x!jq=JJ;Xs5y$Qw^zU;rM&qxo_$ za12(1{j04E&MT|v()Ce=hC&OAVndxM`9m`_ajiSz0_ww;JLj%{(siF3Ow-@FEws3V zJ5VUz8o`CzQz!t0XxxFU3Z`0cGg5u?!6fHItafuhy9<6N$>yYM$cPH>rag>zEYDdR zyAUAahT)*gV%Q3-HOqSHgx5LQ*`7!v1=Y=dR4+RjexR(z?btZMR{u-at0>&@u2QS0 z(Zyzpw#M8P!oo@^KYRAdK#s`SQ(-G=O^s6M^Akhg_+DTlqq2miZ61B7j=-J_s>6&f z{kccI?+ZKIrnGiIajmyxIpvJgYEIM}@;55e+o<0@7thuuruxf1qK2?aSm=$Xd;QdW zAnQ;=gRcDh2C591+!6_4mQ@9oU2dVkc-}#_Jqm6hXz;etR%rBl_h3uFm%5SJ}@Wzsn*_73a@4G|2c0HhI&GF$4zzDMsuZ z_LjjZ_BMrftNz7iBpeQ9N{Y`#$1-R;a|^k^K5}OzY+8vO-P^tER`2kE3LksENZn4V942*oh1+ zT%+Ym;rue+uk7u4j-z+-mRtRisb2w8KU{nW)bZEo+J_+G9264plj+4k3$|z`yW!=# zcU~DYpnP$;0#Xf@h{Dq&C8deGnC14%kTzPW2mMGcW7Sq5cdwghK)&uh8KbOs965)7 zz^JH{KQqOrP3s5l;dRvLxKH(=A83H=6rN;&ej3-{7pZ|MU@J42{b;v6>{ZXeIIlIg zcGs$b12c-Yo*CLFUb;jWpI?HsourM4FQ3-8*R}TZrHXz*L3u+poPHArwG_M-(OjK{b3&JK%MTtgc8?=?P#LLE?TVRx^DwP7JOWV!FHC5 zM^3EfscHxbwwu*-TK*>aB+xc|oFLbPyQS(^o&hRy9E%|N_9)!C*Weup;8}8mg8pEb zX;EE?&J~w5fF6`q>uuuVX;zxJ$bCex;eTe5qVp^DzZOI_%&eZxFIcYQ{#l4mnZu7e%L|jmk z^Q@LvGh0NHo*_4UYjm=-gTrg}>HZnbdaN2!4%$$`dzWY2 z^KDV)hO6genXmI@0L{9c^JC^2*m?O(bW!}J-ADys79u!JElFhAhqNq|l`+;5nV;y& zWlo$~>goKEM4nqN-T%_7kUx49ijWMU?7{8ZopU_vgPehnLNyiJFAWs^@+`|0-1p;e zN&CKSgSwCig#q-R@CIGEr%`&7ZJ(@W?LZmvX|9Yd>H}#v;CN=YwVYI!c5F626z6rL z=h*osVx{Vc51j(PBg_oQR^J!&2Nfa>I1*1k-3J0YBK522kG8NN)5yrD5I^zEHx)2` zI2FX4EI_#^%)?cf)QmFD4d*g=9`8-^;gBzd>JB#1+5v^xSjZ&4d(Z}&hQIniWai$Q z(K--+X2L)O?xFwfl0V;M#Jm2Do>8 z(SiNuaKrUJUdkfH=4d{vnPahl2m4;A@RaWp`Jd|j&B4!|$=VpjZHr#NTD~qM;}3aN z`*9Gb^LN@=irVT*Q`IYcz<1%_2}FF!i!*Z7L2u5<+FsYMbAN(l?Ei^&E`%Y`@#m^=x2Cgyg5J~ zXscOZCOyjpd}1ytPvPTRTapFSNnUk}`XeS7$}7uCw~QQwoG~B{^)IMJp2mf2xYrNw z;NV~*U_>-Q-meA&dZtB)M&8wtEsT>ov^@cU;%ZP7h&cKQOJje*1NyVh`wyx)&n*nH z+7(JRSFF?e6-@DMM2l?Ed{EDS7^=AlSEXKGgr^a6fT^X0EdB5{$Q}2CV!AanAE{6V zBmCz%H^RMQ1ft6X=>FYAWVH%F*M4j?7WL$2NEejWs{!@=WJ+G}bQMFsN>c=TdW!I} z)FGIIA{1@3VQZ|bdp9;Razb1Ev`c&rT!J9`h>pWl{fs_QAv>_!q#_hEwkCqpusEH3 z*{9 zgxjB-z&j9U^5$53LCt8(eDfrOj|Yz7elS_d+}9T7?tT_g*gu1KonJW4*EEVO4K|?Z zPrYg3XwYLI=zx+f*Dh>ALhFv>KYt2!n#r*64MEgh=U4GnbLcs%nMu}QM1BK$ZkKXZ zeyHi)A!Ebkm9A>YE~K&xU?=^&cK6xi<@y;m{*xgcPy$bxTj zCm-6$1d~Hoq8o+_GAQ#eNMhjYKY%QC-+{!yEW^L2S&W2U#544oaI7r_;5_t^ayl}g z7Cm@lAWPfoH#1|bz3{uL3G(+%ijXjd#6Rfn_9MFUwm^1kx;yNdslI}`d-p5oamdsn zGnrL>jZj9`Ruread=NdC=#<21FL9J$`|>f9zEK)fNkP6e8RX+5pZ`8%1`^=F)_PE% z;j}{Q42y&aTYmv#1s!}UlVm^y5w6@&)bt#=TyFdl25&e-BDq+&+?TVNrL`||#804Y zL#QyZ`_TUi>I3j+no}Qr z`tUiX1wE&ju%ahuF9{%$pel>~AdtJOMyr@^iS)ku1zkFFY;U)mMF`@1 z(ikreq%ktH(=s1OI~kh3As4$viKe+xq*3b{1X=r~<( zlvtQ3YTxQU0ANun@{M+5`L`HK{=D4kD&MiX4{EyxrV}k6L8{L&7iligKTmKSQO@07 z%`aa8cffwl8RY#-$V9w75t-pY;0uqopUzZI1Uz}KFsXW*-OLToj0W)GS!K+7|1aWu zrK2FPp{M$737%V+Z`{)s!?Ve5+h3#G__M8oubW(-;9LLsY7k-Q@rr>GE~2FU4dKQjlM7mV76_e&1Eg9j zL3uge0Vu!py~+RHy?_*!y*^><4e4*g%nYxMK)Y3o=~YfW;tpkw<86oOB2?P9ygR$l zZ(y}|Ma?=CB9f)}Fw;fxwoI*x7!XY~7xE$InfkVh&_j_(r7|YwCL+v!2E?Cli%Uot zBhVHehgsn$Ueum?;R^6~};F6p0a{dWJ2 zVj5C^$N2yoCoHL21y*gxzzEkpWN3K$!(2OS0^t$X*@CZP`KbgqqswR=9t$dg}Z4SAe>j zcF&hV3_654tpmULjA$F8`)UVT=##<6`c90qv>`+y^IsjxeYt2=z~xCNZS+|yoo%xl~anmxGe86Gp`&STPZ5W2G8QXs-g4Hh3fJ&(`@RjeW zMuU1RBKd17#tF9Mz8jHsh%Q!yQF9+&3bMm&Btt&NYf9bUjBDk6QaKh)pvxs(VE*lwH%TK&PA_B}~oe!>S4-79m3loik z+G+d$7pXRN(KH?rjVBWW#!u0RzxaJGqH#;k6=0spD}P65c5^a`n;(G^Z4f&y3QF)G zLVq1Ph$W8?`|w#qz{e3Gxho?C-39`}>}H@jA2e_H;4qg*2OS-E;;R(O+krRDCGJiu z%#xhgF_u42l*Ga)OGA@gN-2*e6tg+q7l!-p!_e+@&{&&t1qK>1aVB=XQ!>CZyL-d> z!{YxNAC$*_HMib&c23q3`;XsoOL>k(*46~}L_b5=;RZ|ai zD{q4kcmH0CHnXm9r_ITWVn`<=2uQTc78p2PR;EAMG5CHaj1{SgzV>6SI{Y7#-pedV zE?C-IETHSV_v;F^UlB~n+nQESIYXv`t0Wy%KY9QM5=gaEFU)NHIV!DCx?a- zG+bQ8NeQOyQ_lSqw6qQA`8Us<+%i5tx~+LTg;=@nvUeam&%hj7Mth@^NpdS4oX_vr z;;Fa1V*YW<%gb!$7`D!sbN~NzTC;c{Z*x#k!z=E>_4*RDZ<_Rvd;R(Z1tldu^y@Hx zeV-KR5jO}5*4-H7UOrQN&s0{{PXHP=;Ve6V*k&46{KSpEFqTe_A@_z+_v!L~>C@O| zjAzD8v9$D!&M2EWO1Ye*`slt8;eBmR+*Dx8D_Q7ZB4hq?w)v{zPBp zN}r#GSqt}RI}m`z|E~Zv!UHy-?XooLz}pp*4u@3>Bz6QaY-qqG zh=X%c&qL^Oh&n8t7+5Cj9e{EE%-ehSKH3KD)YHcJ&;&&N>JVt}w+I|hqfyaFZ~0Au z)Vy3oD>>AkSY2NmSIyP+DRd0(n{1&UUk{dZ`c}1qiEN+K$~B3Js-J+}(k~p@;Ei zZQeJRs;M<>tn7(`1DaI#jpi&u-33*DJ{mFDI$p&30d>9SO<#R|{Axj~!c~YO2|6JX zKzwY553%RVqFdJ)p9sd95u@B_vTbu>`tm`t5aD(F>ov=!C+Ro1YZ^S-FD*UusUrK* zA@GlaGP5BG5Ty1HZ9{}|U%1#-89PGzJM#zj6X zZa6;i@$qaqhCjdD_xjxZQL}9J0d0ldPabfSWcCRYZiVWH2s$bY5j#et_{BTIe(gYv zWXke(7wMye?6wDExGTs75^%#V-{gwzUP-I96{N9J#Bj+{6V~SzAC%JBvC+`eMA)`9 z-k-}uu;;RT*z~27HZn>e<2v6ock5Qv`~At9xC5a*i0aPavl%P3Aq{MFx-9bIhrPLO zGlAtvB!^~u&Wp_vTwTk>(0(^|7zDhNhVGn$5EHo2=}e!{(2!1>-ybP4DS`iITVcEa z?E58hYM@xc52x4SRq?v`FV#8*iRqzoyS;6OA@TT@q3sJ^^^zL&T<=5s6jj&B(fz9) zYBr=V{T3WPD>~J@#>ut(C=`%fBa^uS?DjtkHASEfPl-s(e+9?D`+IH63$&YyiJ>)x zCKwFzNof0+2EjnE!Ei4$OW<4vY2)W(O%c;}&}f4PPy_EMXt3}V3JA2nzC4*;j+r$p z+Y_{&gw~$P&~OYZY$&GVH6DFXaxzd9%nKP4&^hFrL%-MWJ+6bi9i=>=w$^yr`Iobyep)gCB6`JHb`2(PjYuT=h z>3)ld34-F!@ff`wQlx=P1#CX*VrW`%pT}-FQI$0ciU1J%#BDZMvWT?OxR;Q#plJoE z3onpBe-GVrFfX7VrO3eC6dfOQGKB`JPoG1%11>Z#*xj+|IjO>br@3S!jf_Ki4$#Sa ztVHd4;;R!WxNjf}##!o<{jl`t()s*L{We}pv5na<(gU;uI1dcaEQ$u-i-vmgXWl6Q zX^OyHy@Ez`7I!YaLwaz5aB`YF<72$KW<{L=PNbId8b^PLd^c%1d7=m(6zJQ`_@0Y! z5R5-4b3@%Q^=ZjBHJIELHS=qAiY$ ziS^eXslN}XI8}hUqEU8V*dPfCFT)6+)FEg;$(DSSWSexHIjERInb)ia0^BSOU9lLr zek|RMD8V_F`yQ#O2MW>DU}(-mEcOpWqJD8yl29m9&(P}+PvQ2KmGKY>*!d1({1$; zPER(5G}0^YrjYpD`$OOK^2A7&wDi;wZ|1_h@$~Q=hxI?T2%Pb(P|cc$!sYneRLu;1 zC(x%33!~i=1nw;lF6)nH<)V=omU6Rf7;##(f9|y(Ub%zbcog?`u4vAdZE zbkQMv1!MYl+MGOlC6`$O&xsu$KI`V|umEv`xd=zkKkNi>a&K$f};Gw$)dH@6r#C}>U?)y`s( zEeE1=1==XXqCPIzGzCyEeinhDK`y`mFjJsZ+bA`uo2%iRp9#`A8jaDvOE0i#ekhf1 zn}c9Q+g_!!bFT^nJ*_|I#EM2jO!K-9er+C5P9~y3wE!VGO3vMb5@^Y-l^+RVxutg= z-DC$=C8CL3~ z4l_=u+(&WN{HL$%Lk6l6|3#4y-0+ZD5ls(_QqJhd8j(=cI=-)$3Mn-f9&Ip;ec(6`iao2^lWKB&B+8+iLS>^DCa{sKXI)TZg>YY^z%luuGVCZ+WmbTYn z=p@f7tLi|Zj&$x(g15v;tfvCPg<%7~e(3p~4t&g-*F`N`8$)VBqa&bjF!%;!@Ge&E zly#=;l?GWBdQPhLDqP;`R$Oek+L_x9+&Op2_CGpeb!F*=!wrK0&U|z6*tc{|wvQ)g&{U;W_f zLMLoTy`z!fz({PvG3(|WQV}qX_^keQI`yue=#0q}DZ`lPglBG>;ke9OVv1 ze|7IWfp}ckpTH+1)ot5mmDVWlC94ne{T<4EWGJu~RcC5-{6A^EN|mMzevr+@J>e4(Wa!3TosQ{7~-3(D!k1l40k~n+-cgE?)3mIWsC35KPqNXVG=e z94CM!)#5E(q{I9DvvI>h_QgMocc$}9BYbt9s>uK^N=_RIQ zm0pIwgA#;6RrKd|`{@`6GV;-GS{TNU0z?^P-!Yf3S2O5DAC@v_7r{}*_NzuF96IFeh z)L*{TGs=$TO#N@q{Ym#7l5IS_NVPdSRJec@W5q^UcVTUHxrHF&mG0^t-J|7fMwZ66 z-Bhv7l}eyrE$d|?g*dVsYl<+zD{EsGrZuNN(`s?`&T-9wuem5IF(e& zBSO`q^lAH5=R&iL%L-pLBM265><<^!^9qK0y7kF;lfzQ;MfCzh)=DfT?q$1#3$`JL z3`3WhdoaVV{5uP&$5iPS&*C&Ms-Znbi*M^Mcw?E?L>j2y`}9xA(dPcSD+533n#c@p zkM%6D8?>*R?OrmWO)e;>INTU0 zPh{HOwol5pZJu{65P5frNioqEj*jv8lgleBd?EwhtQi`DI6r8D4vi;j4q1;2TYhBd za2}(#syFdlX~rmFFtIz_sRe??T6EFT2^xlq@4EIglqil$t9ergof!~(iN)z&ZeP~v=_`skhC+X5uM-*d zYi81Wo%E`3V(?i*5T7@RtcYZHr@NOkl8DD(L3CtsD5G?w2!Dt)PwSE;VVl4LQ&=M?>u}8iHtNe zUgN(xrE_SG`SFqIcpGO`?X(g9?aiq2XG8EkOWj0LUfaXbsn!Wvsq!xQkxrQoYzMWt zsJQdUgk(nX(r>lKx)yx8cRaLl47|^HXx3)jYPhEb*Q#C3W8CF2s{>Eigm~KH{om_@ zCxWA9eoo$%RCf9{tJL*Y?re$@)zMnExgq&yV!?sOG?+T?e9ST zCJ|h$+EHUJo4V_IGbhd7y{&aMMh6YlWK0Ej+#2v+F;-FOXxP*}JY8QR5Qm9Q`rKVN zk6FB&hVA9aO~pQ7bww>Znz%S$;?Ulp`blA7ZU@6wWt{apJ3ltn)jyj7L<=g9m#k{*qzKH$nXe*oQ$+Zj`KRjvl$au!`KpQ?o zcq3nPXU!wVY6=}gYP!H;pfr9Vlt-e+_aGbh%my}MA*h7Cw|Zs1pz721N%yuY8v#?j z69Kkb=Pp|$xV2~@jz9*D8%Kj-LXJalHpyM8nl~P>`m$u95*uc#8yzRYz=zk)D$OqL z*pbJ`A8kb)LS#UJDjr1Q!`nR!yL}rXRy*z;7uFUQTc(L#860q&UDUi_`Nb*H*ul#8 z&}G=IWb=j^ug(vOt7NW}^kpRw(`B>?Ic6XF*BjcfanVR1mfDwh{kO(vqlVbWUtt?= zRaUC9ZaTXScz593j>BFNKeCwo)yOfuFBZlo&K3q%sMeX@x_ZY#F!(pPHHXsg_`Rfe zHPa6oZMl>n+YzhKB#W$$HbxeVd}+{ZNwhAPmj_C`&6f>VJ|@%V?~lYrex#1o!$x*o z>YL3iEylH!_G%@D9U>7vS(P$IHqi8Jh3m!KIQ5oj}KLUXK@Lc#fu3YG}F~y zt_6A>$%|BSf)aVqft$D#D^D~!Nbp#9=jru(Z)@kMmZDV67!M+Ln!C(25tQhKX?q^h(Y zMP;>|5KO35PCZ>vefag|VzM$bL3SPIzFGY7ncPh)HKaT^(mwGhK1Av4oxfiN&+g@6 zw$7VpY_8}oE;qV*W;c;cH|tNg(!z)w?<(z_TwGn*zuusbSKKtfoPxkiaL_c%V&)sG zq?$wHtWt5&3-?4q-@~98)#HL}N%Fld37&pXeYKxQjI*cY6I%zIKMqxu6X8qj}^*X69I5nIl@m zo3U~U?GZtyH@q^WTQlHoWvV1+MT^^X7)H3v2!Ihi2@cvUYdBCub*6g;F-hbp0|wV< zAO28%r4rQOn!4{S4nCVYw>fHWTX%^#a&JM{MiwLX z+QFP;#Y~p%@UDf*CIO@2CXVicf;lu^z|m-ajQ`q?9Aqdrwt8~1Ntp18!HCq4OopDnuA6EO5$tU?h+q-heMpx_okM=E3`Ah2 z!y2~HRZSlpgIUZPIP$e$sRlj_jp6Q1Z znnTXhwZ1tXv_|2Rx8tkI8x1z~37JdNU^xl}(Co;-{S1LC*2ysR%W`Uo4~Lq! zu6wIIF)BFHOqFpuU_Syxp*fIG{z|X%ZhUInkrB00*Z+o(J-l^{47tZU!aW}MPveoP z>-T}vdHn9>OF9EZ5GSO{3J~w?^!T<$aF{dRyNpKy0jA9JXnaL#|Dyoh_Bj07)0w6{ zkgK}>yD+KB`+wcy^;CkJkL+W~3*-fHy$t2DbJSzId8@hBgvC?V#`-mnH#QFIE>Hyz zk;(p|UheSl*SEz~6;^_5v2wEU-z)8MYqM6IY*ZgNzrOjqVa*_5)M4J5WX>kxF@(uu z@vSp%a`kK*x3iBB$nK4B&5FJ`XqSz9v{8zYu=r_GUUwY@X3<&MO3q%o`ucB5F>n$J z$HbeQZycQU5la|{aZv7HH(1XS^u6-!R|&(*OYWCVYl_%zf8xYFdla?u4SME3CLv-G23N5k1b_D5J8Y*Sh{yrm3^o)zVhdxewf zu{8I}raD-t8Y~WW>LM2GR5my^*+rAV^B1p>gBypfH!G>+AaRR<0{^$h(}2N=D|FJ# zB)9gbg#YQs%~LwZx4ynYs}(#(E|hgTOyKtO^Co3HZ?cD*b9I&rDLoPM7Ox^N z5lyR-l9~x73D$2PcP2t}DdeO^6|u%2i1wg-dyr*803>bb`Jsr}RE}yi@l?H#mcs~u zW`4pxpefR{MR~AKe7nH>Wrr;FvMT4BuveBEcKN$yn1RVGvf5&pj`}{{?|t6%k(92- z#@~o|2iY%Az)guO05BO?p)ymcku&Kb-#h(UkuFZ^y!g{2m%oE-*C(c`Gosmof)K^! zuYNBfD|*(k-ty6ItyTrS&_?dgN<9<64!NK1+l9bJqIchR#109TIg$1NyhlLlf63+KusqC@0QIg(`=X zj$~-Ay)F@cw{^ODz07ed*SLpiX`(fj9<=oyOn@E?J@baB%g8xkK_>cst%<&%C-3>4?H z-nr9KXwt{F1SI;>+v-I+Q$T5Z0jhhqc3zw&&eW3{t34|=ZDH*}1S)75JG`k43)SPo zWrI&kdq4V19J$nN!m90aS4c=QVE9*jX+x8`om)I^f*1g$=Hm;Ry(2(0` za4;Q@vJ)6ayp!+(s^_aS%IzRhhXqX4t>mgFbtNAB@qo@61BEAuFnfKrE#hh}dW|Aq zyE#8C`pf;lZxm>nnD!GX0S@<|8x9E@F14H>!U?;tvT6&0i6s=&R8+Mbwu>Y1!~Nrc zn#u{hm%bBXoR$;528$zglOI-m!Yd3u*B6-wkj^Q8%I0>zQs?R^$LS1^^e#ZsGm*(c zKa#B%j=*CVEaoLnX9lw@z8@ilp+b_;S_)kkl$wo zENq1s!87BK@RelNYg)$VexAw7Qz#@NGPFqW54+Q=FQ)korp+V~4l~xpgxGG=^K;}r zdMBn=XVKL)1iv-B@O39_=Uzh{!bCatCl0vc&8%VA=(9?P$yv z1{3m>1O{XUgu1c5S$5|ghvt#r`oKSyqi?advrD#Lo9i?y$8=^tsXA*RC+FYRZkTZY z&DpuAO9ke?zMUM{o^>gy|MiTDlCpXw$1UunzP7eD^PB^%LXp?4&cuZ+Ma+%`|@ z(yyg)T^P1DZgH&A&90&jdnky1mk_(Es;bb*+D3YurEv7)@oW(l-h>zZuMY-{KW8w) z+TUJlgl^|?1d5UbhiOykZ#5n0fvq7cEBn;U%nSn+C}#{x{jBHuCrQQ1%F2>8%XjE$ zRla{<%@K!IY#l(kdOn0E9UuE%{rDJ3l3_=x^w_EKbfuM{!0`d^frmZPlJ_x3&Je|} z{d7m&-P@bZ>l))X5&yyG!(Nq;mQS$#FbSGc+~i4u?=I!fs>@~Th;^}@#wPre_%K20 zL$d|QVW#Bc^Vu%OC5Z)4mif!4Dlwisbt-~vV8g2St#59|ugODr0XN)f_G`b#vUPQDKjb7}@}I#@ z;N4pCz0{xEb%su-HRnGmll{jddVRbJkf{iyQ*PS;F;R;CI!37K4EMKOo$2W-!vO}0 zHSQ7ygK6()JKfWq=Ug5hd>de$J)m>iNz+E9FE*oqhybkQw!_4i{p1j8g#60V{1<4f z+z5=DKmKuy+STM&sL^_}qBM_^M||Ue|2b&=bO#t*YJDe8pH8q{P}SixN~X8YP6#1! z-1*?7hHjkgu2>OIyeG)F+orE{3n5Lo|l+F+-6Dxe)eWMUy8}=#( zX=&xD&hLwAFkQks<}wP!3k^i~jt$x+KiZ&c-fzf^`Z6oqSbp5x+}xiWJOryzXTP<< zZ^TWC%JK8_yFZ7bSmQcpq(izQ)P7zXm^I7kSw?n+m|T0jCMfa<{#yNl9sxw8gIm@7((g$&}M(clmy@TDxN@xTHCXsb%&ymWf z?*+8?p5~bLlJ|HSk?Dav?km0DthyU#^0j>g-^(dg-9Pf9F1axw&Zi@xhNutk$5lF| z$5OY)Kt44<3_8N2`|!sHY=$IfKppu7*oDyEd^62wK<{5TX%>=IiUbdchZB_pTN?qZ z5fc*SQGnodu+_zvG9lEZTa4Zdb+fA+SpK}oR{pSG5{PnH{umPVozrScXBs#{E5Vvh z3o1Y^M27jk-fjUb5+|Ca`6(^^>%kKY5rHPUDKa2K_5r#IMkb-IezL~rc@+%j}Z`ui5VFg zgTGx3s^M?aR*%B$fi{ktHG7e;`-_u5=ADl2(uK#6cjP>ktN}gE z_r8&iD!KYvX00(NbLN-+wV}7xVXAA(ycaZj$6(T2D2jd1^(hVn#t@$;hy)#Sxqddu z)*WYh!Ava%nCm_eLU!YZIJ(<*#@$I>+jxmg%<)5@Lg0-dy1D~w?#lomOFI7`Fvv@D z;gV0Xv2+}}pUI1bg^Ax64@!LebT(OfsZH5`A5BNOcp}&M6JkkF|MlU?j*qzaWM!40 zK!1aATV@mKBFj?B!OD+N$}hV}_~te#HL`DP*5^F+lHgk#t6=<)v*X#9<+K*~=+mW> z%sdB=MP2uR8{*gftY9=`hsz-=7+O4LsTLy{wQCC~_X?BKl)1D_Jk+G`{UI?Zj?Nt; zLwf6*H<>W~Lkymsp&{hMg*=i7nAAudFVTDur|-egI(J|W$?Gop6=D{6wtUpq%`BQq$X-8e?*wm}W~9(|cH^7~mHAjb=SXTyQL6 zugMWGNXC%ayMZ8z)s@!(3Jc|;-2>cz9RTkClXp=29j+MHsrGWz84@$W<%Z2d^!?Vr zd}zlBQk=ZW8)|L3|MO7FND}cy|7t_Nl1>sqB=xw*yZot!!UL4uZ92I2{nEh|s#XQ= ztLJ_fpoQ|(i(x}^TInp-t@kV-i1K%y7!Ve!2+7D-7Wi+%?}juoeHQ?P_8%~PtICvA z)dYd+=O=4I`Pu@#@-pwf4ThpM4#$Gv1NZi_!S8CTy!%>{nNEH+n@PZe(fRx+R% zo2c@GF6bk4C)@k;Wky!g1t0?fV_MHE-^21}6=4p4~!GlNW zU=E9+x8QrL20VfO92HjED>U!5#y5tlb9$dsb)^)a68`21@3UY? zhKRj;=UPB}7t_c(0I zIIdPX{Jh=W92toDqb1Ov0{KDi`LbH4nQegC!QU!v*}X8lg8!^*eE;((Uj!DMyT1n@ z236Y(F$~Q;Gt?O&rRWKF&GLUkZ?}2x$SiG~L>{ix^{I`aymer$A!=owdY0F`W47Cf z=+&c^K8L?gb^I_K;vI83gaQ5RQR5pq=2WG-|3wZ?~pDU)rL2rwVvY1Uq z#fa__nH}KTi7sze_gUR2ekZ{13k1XflSlIFSjEHIb6nRG3{s>3UGKar%G$6ow2*Z; z5DjD?Ki`(#?3){KEl_-3ATc;-W7$X^hVVh^=kDZnC0$VB$Edz9jU7Hbbr@ukzTo3h za1g>pb|C`hrevnTI~+1Jn!pL5^SD)>zCYpoPC`}}3Xbnx=8qJa~M56~Qf5Qw=-Z+6|%pw8ngC#}? z3n=NlfdrNkUQYau%1Wg0U^;4x_lA+KS(YS2YO9d;VN835k0|4K!ti->5(b>oR_c($ z9kK_b8|d&k3a`eBoHrM*IK>qD-GH}gn|{2%`ZTwU)F+S4esiaTGqZRb#)|)`_sK0H z%wm8sHvOPEOzy&>41g6j;Ro)(rQ|zv@nsa+BW?wz%zq@%+iAmSRlP0Hh)rO7=R=kJ z7ys2B%-$1ktid8fal=Dn{LRkVLxcehfgMDB{vTu{VXZbCxBHU@)x z;ld5+<{8?ghmEJ(z8K`x;eT$u!0vXUL9OKZ2OnkzU>M=NVW!=4jV{|?;8x4YsRGR7 z287Zo)iq?o2I%03U5~AFpD`N+Ir#*%eMnZyig9qrPI$xyNZonkV(U8Fm1NS$NIb6b zC;x}K_l}A>>$(Q9Z6CXh-7TV`fB^#_(vlQOwvi%;5+%2YNDxqR#?U4VU|}MO0m(sf z23tiy1tgXdl_($}p+NHN1Ngr0H#6U?HEaHwUaQv<1*+%>)z4tlCkJI7L*T0FV zR~}4xGg7ZP^9i+4pqVy{Mg||NPhV@Zdw?UA1DkZcwkS{Z#Re zX!r1t7S_Ssmqh4Yx?i`}IUZUPzP({n%+9HZ>GR{e^c6Db1tX?OI`{!*NJxl`jHsuM z67YXuw`;T!aG=G~LU!W7gH+wvU1ys9A#ezG35APW)~;Rqwj|{Gywm5z6?EIf?)Sn5 zu*;!5IS))jws8Am*qV=qVy{<^*8jpW*f0U8D%9zLp*9NQcN8g@{mwx>L;BYzw*Yx!zTGv8k ztcu#Ww6V4{%4uhCKwi}c=`I#=(G!44^Rw|ct;L?K5Q_9SB z4e7RLA!qmOlK=6&^RYpfpNZDWx}nFtKh%6`WUKp3muQZ)7x3QHeowQwv(9wEFTN-5 zyh*Dcwm7dMG^{WFYOcF%)t2Pbms)1tqVI|cZT~7H_VZvM~^y&`GjfBo7!vhnQC9UfsnhSiQQPhT_55KfDr>I)# z;6qj)p8bVh@_xmp>SksU0g_gNenl-cn-E=)Td|7#Hg9Cqrdp}*-hCF_f_3eJFCP+) zBY6xF)Q*ekZ+&Zt@>OqdZ#93>6O}l?oVBkD3h3{4o&I!o&z?P;yu91w<@5A-<+p7+ zgr`yl!dL@Y*8Y9_B2ItSi~orF2y?5t^dm?WH77>d%m4U;+2o37b9bxGdWJ!H ztOVNT?#M_zac#|+*ZyonbF+lwPCHY~vJ&Ehaldh;c%M+1y)Z`pa(B;04Z~yk$?q?h z5A^q=WB3Mf_wT1)4rxVe#^($LfZz}cADF5ZjnRl?=1z`Urr0z#JS)Xxllc2N2hT41 zE&H=ENM{rUhni{D28{CfyvZn#d6}+r@RFlK`?E2}AYl9POUS7=Fhu>8xwjktqZMx$i<)6nM;Tr^4!T4Y zRW>0%{q&Q4XQhDl_^YeSIzi5B#T>n5Jchwxb&N(Sk-FLvBo7+8Ko(7gHPTNwPe*GsRz*sBnlLjpw`xI zza8Pq{*O`P8i9&N{D#yw_e6TR-B52Oe(c51cZBt{=FVTDfi{n+HjQ!MuIbC${CoeJ zLQ^SLvU!C@l!gGiqv9awuW}p}8Utnd>dNn#wyoZBQ{#55A#{wOZE=%NJ`T+-qR?MN z5WEMMh+zy$khZK}zvtQtk!soDx((EPC6AU6io-{%?Os?}W+>iehG`F~9`}Hks>-ly zugvvuW6iyd9lUoT*VgxVxp;hR0o=d&2{>FRUV5^AzxS7`n;#s$wSK<_dTKCOeBAr? z?fZgFjM}biycm`6@swoo16e&Z+qvi1UGo&r{OSEOd3fL{*7Y^)26PiryC-5)9ix*3 z1!wciv|nr&MqPWZQYY+bDR_7{Y-aV2jg?%vR?7}x969}Wc5tUtPY@M=TZoBa8{ZKz zGDIPJ0*+7@XZ|?2M+*$x!XCSWzYx(rlYt&HwA;W!h5mbCzGcBpp18Zx*k}tDEKtKN z9Cc1~M(7a+`G123d8r; zL~SaF7)=2lJ$e)%>y|2MRnsEaQjnidG3ideaoPRt z$Mr9co0Q5=W#bq;iHO)DY?yttob4!!=@<8)IH4(x+NnIm1jGXcoIN$rdi(~xFXd0X z;na*&+fQu}KJSvB7*uz0b0gwWQ9t1LHaB0sZol|s1iTLZne`qb$ue&%qn_<2O z!f1fFIUFdLh0@TrAkwz=jcdo#dum^s@!!xyiST{@rFCn28olaY3#aK6?qhP<$cqHN zG{2IzL}**m<)Y%*>|a3z4Z0R5v{Z{1Ei6-HxfS%*q$q7vx~rkDACzicZ}CAyTwDWz zpsKAnuqxBRQd`EQOdGXpD30wyp)=jY7=SaM!5SMnc^kxj<_%0?GvMXr71U3ER$KS> zj?iX?fa^f-hlIV%O7!MPD}Zc#!aHQRyLL11I3QJHRc@Op9u*5mGWmGH5kobVb{aB= z8p_e{u@Z;UXgppEMW%Fp5AQz;Fw5})fq{k7_=6^VjXsN z{x0m}GU@(iw3segKF{xef%aG2u2;um4VN%;=Q(9auUWz)<9v9@<-IBa(EKKgG71U` zy6~13{PN3TXnYm+JuNn(>3Ulvb^4FmZr5i9?68S@b4N%BF#;v`NAOfkQgkKk+LT>f zTq5y|@ksLW|HON)-6rZbFt#?s%Y*47g)ODxFWQ<~5Q8W&)Y~Z1F~xg6T|L*`1pwQ+ z&3>o7L&_P`+=Kq2qW1*s-g*p&++;lxd%Zd3*z$4B%!od|*==ERz2&jj-+wWAOy-dUBsYkaaempfgSfN_a zmFzcf{(L1Suo1$-o%{Fiw+(P^IlP-`ouS(Q@_Hbj$zX4zkxdT!ZrGvVD0FBzOy~-2 zv?^09wU>(+)}zD;fH`M4n~F#No^d4gZ(i?q-0-pnB6LS-b4YIy2M?V?v0 zI{*8AX#)>7^^qqBiopFtL%xq-!DPY(K+DtPMLKt!DC?To4dQ*|KCI>FDx zj!HW&InTLv8K$nvALqAF;rHKvS5#J3PRUV1G6Ed_B>>jT?`J54{rm5~NVxeY0^ekM z7kzactKU2^C1G86lsSkQhv_^L*BLBc8G_q zpaX4M%d4WbtHM?Jg5^AFB;crJLmTQMaCCVIPmBGoiou%Of|`mVIfKk?`svgzOMk_- z@y362fhcj)!2sY1@5*H%UT*HFh8#B`q$*e(kFvYMG4SaN`Vt%gP^y0rjwKKcEasMVO-6hML<`R< zk=?RI0l(4@Wk8Uak;TQ~G({Z>-bHH#GE3a~+utc>WiL`x0e2)EzaIVA?dZ^5qlxFE zjd53mt?+;gaZ<}e8OV;Ey2Va^+Kq)&L9VHeNLh)90l0POufP5Z5IcDjSnlbq4F@xe z6VRPW5S9ef(g(sFu6!O&Gf zrbe`uFus&4ZZBnk-C{PeV(-Oh?HzZ74w1h}p!4tR>kPvpeBkbV`)quAB{=%8!AT^9 z8yKg?x?u0#y$1V7R_Cy*^>lG`F-Fa>yFN=B^H?Cw~RxuUX~E_qtwH14)xSW1H2ey*tXtfsi&Vl zcJb1sOw*#Q3W&M;_wSEHn@D`%+omN!kr>q%g}@Q8^UUK~n+1!w#T6ip#v}KCS`n^F zzmKkCwAl!U8-try!P(jQ3(Lr>3VF3A1WF*@Y(!-DoES9+E{@XMapL3+z*V|d!3g*v0?~?EHDv3+oK=Pq zpa#SDfos{aMOT#%g^NjM`RyJeUyNqN;eS<`XbKz#C}HZPsPn=)4TrcZsgtKUPx~F_ zYFK$Ik1`%ay6liFy3e_p@9ls={6TuPbpW%*DIFHdA6E$!+<3YaoQxklrbK zI%&0hIBtT)MxTGSUHw1oEz9Zh4lIVCU8-^I(W?9o`!Mr__t{_Z4h4}G2Y7~v7-Yh6 z;PUpoo7OI`$!wx7;*1B7c4=fg_lf7RKnByju-#4m334dRC}x~H=hXP``jH2R6cyFL zd}Q8qfsqMRPD`BSM)L_CInVKQgu2a)UFaiHg@+U#8~@(Q+4R+4XY00krl8=+uCGb- z@;{-OKE#y9g7xFw6zQ;Xp5pXoJ^R&uAZAogKrK{ZlPS~FNpwUwXP(`)d}fDOg{-Ws zSg5bphHLqc&!?GR^GL$6IbpWsohr zAJpks{JB|r{(11j`#$cana6T!(`}MW)9QNpxN>K`CBFN&v~6fivbrMrAC*lbk~y2J zJWzI;a;kma-a6DPx<$3c#Gm*<^Fw^uU}UEon`m8Qa%HSCVqK};R_8?9;r#|mwkw;K zR&Lt{I&`*(m_w9H^I(f*DvxyN&&M0x8ryIki;1LgNgqr8F#G#_`{?&2#WjAC>~X@t zx&MU?mD=JVWIoG=?$$m0?qIHI?cY#0zmG=kK6*52Z0jr?y4znlY|-19Aut>q%p>^5 z6VgtGLv!aZI@9PDP}tJi>d1GwB66PI4PnQ3yqi*@3}jx~&6bgIpp3RZ;k%c1){QYq zm7M&NyDt-VE%P(EvLkNILk^?i*?W?M+ULy1ehv;3 zF7J`TdqGPsH%&=!4G6=>uan|&qz-g?3LCidPl5sKpz>W@d%UX&Ol z8fBVXX|&LqOblLe$Hb1iX=$~%z8u&z%VzhKHX)->kLi{X z$o9-|%Lz2iU&>pFe`r0uGf^NZ2xRbXK}Wu8W&ff+)A{H;uRGhC{1*RcX|O@UqEZ3G z%iUsAzmKO%dhGk3isXLZ1D|IlNI>;hKyscuAS3WCS^(Nf0$HSY`+65T6x6@|FGYjH zcO6o)2!vHrC+>`&^4*``;Ey)&IvD&Xl|0+qupfW)ohg#rJxRQ?)yKsB`@K1jAarYj z`O&lH1Aqra6GATL&{=r|cS382f8rwU<;$PmzrPZUC4)wRpO6e(zGh7fko;4rG~Dkn zOR4Pg7)=I;T!}VoFEgxxuN=O8`isb7Pft%^sa$+>t79#GcNczvMS18|L`-qGM7uvG z9bC6=9g#C#fj@)3tOAsC=xKRFGBPS-r(j2`dL03{5(NNuED-SR3z*O-oun{&>zIiC z1$-eAPG5ffSs8B>JbBRPoP^r$njE(@u(>f{DbX1p*55C8rCLon2j=X!+r2XyJ8hk( z(G6q4zds9U&ulV(VL3TNY@#wyU!T2^Q<{SbTL#b1-CT{NRQI*VC=66A}_&4GR~?j84uzTt#2d%t;M)rp05Y z>N7QAd@D!$ZS_~wfh@#xlmW(F>32|iEWo_=7^Zswt$w2>Cx(q!M}Z88m7)(wKi2y8 zp22ajTV=>SB8gux4%}g*h3t9|%{U2N6HiXR`?`=S0?urguZ16o# zuiOJ{M$gVNOwl&`a1tHBxp$s^h$nx0J@4ARIgyJ9gkVFIQx@_>6T zaeXbUe(KbPwvkQVxNrl8Q53rj^?j)F>I;Wx6;0b20SKMxaX z0ZwKkONb^^4S~eFyB1&y0#PLHP3o3){=$V6Bq&Jr_tUwCWB&;T1!ytF?Y|smch_oD zM{=;tVMGE$B3;Zao^EB>*Vji79Zkr>K}`~%#x!{(uhE0xz@pJ2oTLKv>*QF>`{U~0n7p5=H+Es!_y@$rGu0NP-T;-L|dNhATc zyk|E361)q+?c0wudXB|{PKrXJ66SpL7CK*gP$ofw2_+D3-zuTStPeP*aLlqjh<0_D zJaFIA4XF(hOC!*y>VW6dl8cb`A>?;uctTYmAm|AIm^yUZO^&+|ae~3gEFCo*~SVTHwyoJv_)I4?aQX<)y_H*j^H@gJ#RO9zBfsamH!@&P*IW!~Vg+ zO2{G6_<$hf)CuVx@ZpUGWl6J=(>z4%R+ENMqJQXpURLR>&+ZVm!O1@AYR7RE;^*H-e+%X#3^2M% zq8j`ik^&1bhLj0BYJEqS5pE32H@5}ix;#E#Tro&=FzHA%dL92-n=*k22C9A+L^(5P zo^cn=(3zJ(S`En~xGN~KAkk7{wv~p&HPoS2pq6bT=)nu& zWfM-B(EfEsnGIZ~@9u9;Ji3*R3d%)ncAo5)*sNqIy_*R^>BLG)oKOB$H6scvzD3v9 zNR@y4)FeMO;sYTo3?(TCK?K<5`x&ut)4tFwRINr6SUSdFGs;?u{0#cgB{{vsVFR*q zAcP5=>PW8s3S&qT#CsySViJ#yBx&^1l$Tfw7=-L*yA}7@4ySh-GBM{JwBht!`*XOF+G6}=&Q*DWqUL0O$Ye%OjmXkG2qA{ z!d>H_wxxR>k~@jUrghdvP@W`g8uSq)NZvgrGZJ8Pd;GW0ZE|BweX$nELZ&ODHKXs= z&iiK}=az?1nH5L#=5K_ndYAk6XLR{N0Ld(_ukRW~iv9-S0(7|_kqsjQ?O-}ex+k61 zkLZKpd5=xH*o(=x3q?OtFH8K@$$jvQ{cR(UtmXLWNIw0e=PqvjZ%~1h#ff1(7IX06 zL7{c){vwJfC3;W4@;BVQb()8YF7^z*+p79jF71uT2I>9jLM@w>Jk6eYO?k4Wb_JK% zw;SZRI;aHi(gbB$4JK>w{obX}w{`=ER6uC}!|kfg@(pSP-4+9u`DF;QW`>&gJkNmz zu26zMI5m08$SNn+SDw6l(PkQn?l$d@Yx2B19Q>wa-svP=ctiN%54;*_?dR*emeJ2< z7Z2Li7s;)<)7YIJ7#p%RBVX0l@<~=v7AZR5o+O-l^wIG7Fir;xH$@7@4UxxKH`hI5 z>s{%S@8KsxuD|JZT(V}TZ|70lYauN|e*ru7L(PVgb|OW0vFx{}dhG_!{{9PlslLkl zB(wp#->Tybq6O5$%-%oz%gKIm(Y$dyJlTPFVovtg>%7UN;63UVZF5aAfPKL3#%g^f z5qW(Em#XLN*iYN2O)3F}rb)O^J#D4sppCK|dp>jzi3f+96fqY7C!mocA&<0zjt>b> z8c;Z(E$#pM#%d*uu^2g-Cf2+IPzqrq3g3=GF|~mHbaklwWgue~_klw)hd>DRmf+?_ z1Fv5yAO1BL5YGY!&Dy8E$Duyyqz`^uI1D18ABMmcV(f$mKLgz3A-j?tc{{+^GD9&H zKE2)(e(+H=#^hYK%&*O^g-c`0IgR_un>2szukw(%zVPY{R$JTt{d==dE7(oE@73N_ zKpYRawjO;5px6O^P+|Ms?8DuN(&g@P@P2DB#lePkWAMT9G~HNN%)uB0`9ju`IRCH5 zjvecq0Zj4WP{Ou+;gfYGNOPB;dOD*K!}tL~Rav%%xA7=W7Lp1PsWnTW_}US{WqGQl z@D?*}I2=@Cj!+L6O7Pbfz~TfeG99}+xyN7{kboRH(JnuACdHK*3c=U{QJ)!#v)U+G zTG!LwE%D8iJN4 z_S!T){Lqd_iTaO}L0qU&WY?%ojfM0c254jf!M3v@Ckq#A$Qcm@LEOHZy}Z1L=Z3;~ z7{CM)$i`TsJP6IKYFv8$#$!M+h~vkIlU0@@d5j#k*Jqf-%9QsV#e4SdMLu-u%F@-6 z%D6K$0Fms!yk9qgn-}+4=f|XQLU=%81w?1CxBUjB!k8q%N~K#q+|mo9xN7n!pJ z6zu}kstBvOJwdIhfVg;Ncg-BGYx@ZfqEzuA)Fm_IkAiBUSBM?NQx3a-C*&*c<1%29 zr&q50LJ1iz$MEdz>`G`aR>(K7Wdu^=k36}Aw)v6hvMC@bsRemT3j&Gv3X!-{xiMv2 z-dXQVlPt$5V*=61)u1v29={6^SQ{&aaT`w&05V^b0|*UgI}uCnrlqBUe@ua-O#Ua5 zZM%A1ZWMLczczLHqtLLdtc*1J?~HN~uNp{CzaA1$`q1qNgnp=;z4XRv`F+e_c`v!> zil2JuCI$A~$e1%{&ZVhe3a{dQJ*XF0J74K-ySQ&4)wa!+F~-ns0|F1QCX;Jb@zxRZ zIr>h_Dfz^#Ci??Py0P9jZfGVLo7+^mx4u~p0r{N#mT&(kU5#L z0zoE@RcM_iBq=Fbe9;z;1?0k!hac@hEX#OOXCtF!F>G(^<>}=$m9?v&JCyD89h5EQ znEZ6VYvWGoRd#5$FC5>bJiD!YF9P`_h5}XqId%xPDES-NA6l67F{pmtbyksy z+SNZcmIvx)w*JEs6KWYZ51jp3juyNBI_4ahDwj0Nk#V>j1;Ug#)3gty>$)uUe+^(> zoc$S>qjZeWA^q#2xlUIlrT^>)`%>*Rtpa)Vz37TCk(Y!6XEZ)S1?kCPXY) zv280O;Ll^{Ner1z1rci#WmqO;8A2dNtCcuk-{06h_0k4oBJc#DODqvDnkb5`iZjsj zk?FtU?%`44ejT1BMABR)1Q8@9Y=DSWsWke?dhFX1v$Dq|{MX;4G#Q(?`s%XHBrIIK ze7OtbAK*Y>*gfcJD3HcU*-CN%kF=u(g55E9vZBCMC4mtH^obBs2$O_^U?2TH6J4jf zyxpKXue&kN2(|=8yX{k0a&BqI7~M4MVDtQOdTXfs4)Nv`cuZu)%35flq(9;1;D#&F zeE$G4m}754?B)l~FEoEP1W6AGHW`U3%ru~uz91(jtrmeUK!A0+IRkSQr05Yc2cOSf z8k%w6tFLK41&RP^r?_JuI&fCF0kCeG7^F)8K9A2u@JWU|+LH-!tsBSUPD8Ywm0USgbac%0F= zA88zvzt+1n2;R86*qG#leIE@TNEe_c^X4u6LJ{?_>kXb`R&)<+R((*3)J7a24ckHw zRD?PX2R;vCO()Qb;(!Z0&>@{KgCQSr8-rVUkBWS(p48%1qWf?v{HiRfDBE`#Xq(QH zz%x~hKi?vRM#<)QU(j(%tc+&4LLgt?L-Q7Jz6Zc~5N37Z{CP5ync5!cA%KYJnF9Y< zJYu)u3K2tP?d@JTTC@nX)bkc{r7tK!&P_#%Y&7Nmg4(+>&_dC}4Q?V;%^yxlQli8R zrNaSkC8Ik-FWKx?+vPQo;3}|Wpyzb&>Uu<)6ubvie z$O}!)&1~R#(;w%O7{E_lFw~}?=$ZmKXeGckEnINcQhre2UXH?dl;Sbwh#F%O3^Va= zTD!Z04u-z3_rw!H4v_6Jp-47AzFK3G1NvDwz!!LSD-Uc$tW-~I%a4s25rDfuZT5<~(;VJbsK_y+Fded>{CZG8p@aktB|jDGoOm znSOw6kpTH2^gqVVkHCdSP^I>`b#1B&>iN__d$9{Q_|)}})t_v5O}pC5LB_h zGB|7Xvm8}mKG4EblrVe0R#+!-pVxTbWs(hlUAD=j&|&6`wET>GQ6mOEWQ52{1S7Rq zm_IWYb>n;RdWU{@LYdSK>mULuIrDE4zD z^6Ofz0Tu!^{WB8%OcD$_v08A#8Oz%D^uPr0?YM0G06_@ZdRl%>867Y?Q0t(9l3qa%~^(8%qoqLM2Ds z2QUla$O%H7jbPv+@{hn3W%lqfbaRj4FW?=fD0Nomtl-rvGj#F?1(G&#_k2i5qd_aQYzB;(g>K96m;+zqhHJ>Ujmqn%$w zbZ#8}9Jf9KIyIrWV+BiKIz4>YH#*)88vf_+M3EyWk#HM21xiyY{{zwFMBE}Nuiz?C z(Lx|(l*1fLba|nP=+l)Ua1Em027iW!f*243?MLz@4)6FG7Oa5IrWZ?PzMZ9N{9U~` zx}2Pxp;ME?d9E1B)pB|#wRa$m1f%sT5ili?S`#JK@n+ped`Z)Rwt&H=(L2Fo)-n1S zYD+F}s0xA==n>9isM)_2p^Rl@ltu@M_sFm+p`~+<=_3*$Bf9W;wb%?ibD1YLa4vA) z!Go5B{J}ERzW6kEsmw$0q2^fVxDU`Q9s!s{fhUgJGkF7(I2}mAKm-w}xs*PD`(BAj z)(-7;@;ay;P_qhWYJ;XYf<76vc>rI0jZcB3W5%r*7em@uuK|M`JPt5W8mhtg7~0i+ zn`}!6D6iO)-@ktk{V!n^}7Bo%^RvFbCeU&uMqHxK`be{YyItc7A~> z|JP)2L(EFHYsWR|pop54L+o+zhs;oov7jAh@1Iv$%8B$345$Ogut86K!7QKm&(F;T zpHY|yx^~{Yd65V%{tqt4$H$YIo*Wc#F$KT^OVt@jped`u>Gk;uWa{E@WRet$$M>Bl zI1MbExy1%t>&IA)HmBx0Ji&U9og8jd%S>BmFgN-@)4h3OG4<1)otDDi>0aXH?a5nn zw`R=EF8WFG%^7gsG7lGRV4cN~jRm|q>xw*j2Th6!7A{((?64X>3Mhd>n>HPQ&~<4@ zxSY+kZ54d6CSUqQ_rO|3Y9~QT(CdU{a5gtGD5ELYN8_egQibpn(mS|rEaj8i3X4XQ zEMh=JXjj*q>#KKc%TK^Hu$4gu+uX4pX-fTJyUl5?gPe(0jnGQfyHA9S3o|RZI#g6B zV}UiiBcXt|BI3vsvU7oh4gX+@vq0<<*aa1p`+u%eDKNkT3`)br9M3%^}%Xr>QC^yq}!FY~c=<+Ijo@^saxcNqO7-9kh;3##AwL zMUunt!+$MOI)Mc`b{T3hdalXaG{{gn2_np4^o$aXu^H>8K!p8Tg@~h{fObf9C~vNj zdQ5WD{bLWtg>L}tQ6+@HBrzLIv_56gmr#A03|f!k#A}ZQB7e-PH-&D+RI@Sg3!rC6 z9&_k|>yb#qC;z(YCqW(XwaGZ68ogsJ5=*f?+Pq1~{3Cz50Dbq69{L!7La2~9(~qc4 z#bkhyqT9tnO|fWn54{HZua7jDE>Hm)di?3r-!uYN(>c4WueksOm?jc|SIgZ#otux9 zAmJ{HOEISmc7Kv2@PXdGecN@n9W!Jy_^IE_j_WAbBxu!<`JiQe54hFE$8m`xjgWZd zcdY`ur(>74?nb^Tyd7}Nb%##ka)$Nz0YadY=u6xHiy9B7Q-KIDUJeXyaP|tk#sFn; zP+5%E`OXD5R^RIcy*a9onft8^$gRBUrJMw%w)`CkE8^F|A9|o1jTs70_S3HPqKS4s zx3AkN`vJJ88NS|QSul5z`xE!B1B?iqV@A2|AKAO3@Dp+FAoLK+1S8!?`a5RqyJxQx zry5OZTEI>h^v@zO4|$;RCeurmaK$sIN(G-iWW6WqzbGXJ`X~x}mxxZtpEkk2OM=Zb z+$O0j`hM2QQeF~G(Ro9_`xfzwAmY%oCmdX1=4cOGoMs4oG=0@XGH@}Uc#jOoDUzK$ zrzV^T<-{$0gsdV0ov*B~^`_b4kiV&@P``ES7Ppuw!SfgdKMRB(R}2rYOPvsabrxim z{jA^MPFB zI-gk1o-d9Rr5bOSKs#B?p|McS9Cj)(RT3Cb44k8UCL281)6sC$5ln+Ja!b-pUmHZZ zJH8`*A_-5FvlhNHBfL7wIr6Bm1o88zWS|NLdud?MBV|1BIhcV{bsFbJ99P5R!q&Mr zecNUuPln6>3E&0cGX$K%&!@j2B2(ut6YNGkKcZKh^Kp~RA%5h-EDZe160@sD}ZLG z!tbcZQ6w^CAO%E=4pBX%{gOK!&i+TyrK5z^L8^TLI7HnW)C|lIe1s$kj4dw{mo~mV z_}D~e2r5a6?{D9W%~1*Y47Lwh)HFJulHGA=Yu5!(gZJ&A|3L1bsadhA;wCQixJh}ez%XODuo?-3Ku(%eoz6%r0q{%>o z#ePc6DL&1DrAwiy=TJS^-rv)}Xc3o2B~9RCsGf>QY)JDU*?Uu2ZkXdum&i}^7KB~_ zHq!;DsKmp6NM0O#u1th)5+2?dCq$s|3fn|0AifpEiw2c=BJD*IOTrD&*i=9UWrmE) zm&Ur`|2RyT$hprLx}7GB5#{WrGn4@1YUiw_F{;EIMIX$w&+)CxadXlRykmk5PVtcJ z!022TUSmroXo5$dJp_5to0)$IV7yj;bf`NTHV$I(V`VuqIeozhv;`!7?st_;ggdT9xND zW#8J-<8eDqBvcyI=mf~>nhd*eO3xuOM>+R3k;b>_P*4Pdwjlga07_c;NjB;*!ksyi z5DWZ^O~Kl|>&`%cj3a{qmdLum67Fbx5tcrjlao^gvoGo{WQtkS#p;E!be0H7;D#Y4 z2D`>u#|ee})JE{}~J9vqbn)ZrE^8<2QRN@ zt;Z5Rxfm=akfIV5)lqpeu#j;eLJA2mf_kYjME}EkK=df#dwUPrw=ljSixXJC{>AB= z_$P8RGf6=aSII7B%zB0kyD7kdt7=3oyJj1b&Oj^4Ky0qID_5>O^I5rleFAi*w73!m z?o*;mScnh82(p6;C?`TOWDsCOavqEt3?x0p>AahFT>Zb8kgFqYIkW0}0&elHT=_*% ztC6}g=mc|FpT@<4ocn{OMnNoYU)G)ihztT;ZN=7b07dC%BmxdRfnrjyJp30QJ;wio z;K049=!AH9MTf=euN@u!=$KuqOo_Z$sSM#`7^`5iCdvo}STK5_?hEOtXmW%Vym2%F zk$Y0!49^_GzYMrkMSK#6e3T3~p!}GwO@ZnUmiSI8J*Auw;$As8mkN7f)Len|DaXa3 zp3FUjR(l|6qJH0SU}ZxH>4|7LN}8$KD;$+5P$pX-LF{*jG>L$DpTap9CNuO(j7y;U z6+C~^n^Dgg)WjA*IV)zoZhqNiF6s}7Mu&1V;^J^N4UXfssl=Ma<1}REG=SRbq}is} zy+WoSREQBkQZJ$3g{Vbjfd!220-K~@R?s3h0>v6u*G}Zr?5`iyRb~4A0W%UxrbsBp z5-q1BVomAHodsu_Q??tBWNjUqFC?d)3A>$XhIEKzp{O=mQfr=Z_7w5f<>Y4 zx{lljNCPa_WOqHZzQnl%;lvfSdMKBqqa3LwXfB({rS!;=s~nOZA+j z0K=*g1^WDw9o>ykhO1G8rlDBG?4 zbFci2b@B-M9yGYL^H8Y)R-=L%gC3hJL1FVsc&)X2M1c z=N!W*Y$c-Aa)ukCp#bP2>Bg3g7PoCtpodnQW}}+&q2Yg56drTmGLSAFz`9HbT~v_* zVp9Ra2v#JE=wR7z&*#=89KTEo*YIo8x!xmApOF@Z74W*n>;@4|kyYE!Wn%(ZN=K-& zI1$w}>s}Wu$=?8Or_Uj6dX04}Ju|Q!$PQ1mNw5Vawq=rdAeJcNI1U-34W)A~e7o!m zsYO9Ah&rkHi&pyPR;ixTZ-W{@%Y+Vhk=V%U0GFtIGI9*Rm<8@u9r%2Qdi8WmpR?JY zo<|DRIJVe2hTq!Yj{4*pI4S5{k&;leY#7jVy6y&=CWvGBZXB2&Pu)c^0#!C+mwDg= z3z%9g<=I78fQq2m*c_|44&{7AWMLy{WG;AvkK%L#(`JpZDyo@rC&11Hz(h@k0vzmU zy!W_-AJx@V)C@^V5EVGg>Dn-CE28v4pnkTH5CcBO-FS!!q%pbvA^yyrdnXU91JOX| z&Yhbs1dAYm0tE5qu5dIig14oms^#pI1<=u+AmEU=?TQ(xYHNreqTLP2v3FMkc^FXx ziPKEvYaDHPQ27idRq0Snp>2YDOEbJ=O19DkpWC6)W7r(Xfm__nx647suI)Odx6q>j zxAr3fGVOCDQu6Zh2o=&Z2Pp!T5MF^~$8KHH7J=0y-QRwqLNs7ua2gSumVYmS>;x!W z4tN+!^S~t{#W%l|l3i*0i25|Ydj0xMW1|t+z-$`pzDX7cY6$VL2K@MgWL&N3h`>9S zEn7BS5N@R_i&h8mc*ubp%_09VkRh?)Ofv(Lz1oZ#?F8!&&pulqx~ySRvAN6UwD%nS zbXLug6^V>~C= z+N{h|Oe(u4Du>YSk(fJiX)8y8p<0`ppHM2G9alG6yOgTZpv>c?LSU6MFnZfvv^hkX z#de>rE{FRjd`pXx+Ue8JP(c=p`wSh3kV2F4g7Q|XRNGtnol54hrz)sb3dEZJ2h-y(~F!z4Y{2?YH^S(yj%`PPBPq#2tyY+FS;|R z&l2RrZqE3t{H(Njbt&gv$v2I}5PPG<%G&&QNF_j0QJ@)#Zc)4Lue^y40;=w4Uq=gT zqNI6h2^1p);5X{b2e|r)mfzd>1RF>iFCDUm4>A!4j!K#Gd4y}M_l;_des1-cpKt^kD%{o^wv=C|&Wy-Q_phjpb(0rzKS@SuI= ze(x8<2>lq$9ZHreoLAs=^)+J$u#I-Sa(9m_-JYAkR!(fG7VYW0tJcXP3zCwWH*3I= z)w!(~Oym|EUaD-M_7mWFx)2qthx3-fitY=&9XJ)uHzXl8O5}%tBti!cMQVt#gjeSN zvG(Gv$saTJQXkMi{guI%Rv?u7bsi4{aIe`|WRYTlH^aLlwdP~0RbT;N0~#f(hR9V{ za%y0tz)^6mh!+E<}evMqQ=m(>aTM4sxP3lRJS`ek0ruQ0-C|d!6~L?zlz^* z)*Yw?JW{s%sG0zmbwJ!^VU|~ON_tj^bwlrKt8y_dxIL*z>~8!^EQCRG(Zz|%hRdKI z35=6H6TaIPxC)a##=zWTK&l|JG%_CIbw&{hqp{;$sE=^BM5c5>abHUi!so3fy`Ou6 zAT5C^&S;g~@t|32O3S1=Zvz?x9|ja$qisE$vxb3G!g2W<6v(O99m6$ar`d~?&*QD( zcR-|P#KYgkDfDkf&3BbkCIX~qT4QG>62_K5$(5+J2c;7iV$-vI69Z(Ej{hI0wb3vE zEVRCSR#v!OKwba)IUhF&hg9sge(~Q8!xQ(Ym9t0u!OOfa-;f?^b7{r*q~mkyJ~4Bh zs^a)FVwF)g65!(}Gcb-@JhBIWs6#D(ib{XiaV%p(OMo7()!KYpKl#mdIhT8!U0g6d znV}Ueq0+mzpWWE0i_CE&1F_%U%C5O3H*i?#?ftAS28!}v(5_S2eQ4XYhWmSMl|s|g zcc!uTgI&ZD>1^e~xn_9+8$gi{qztDxSXvv*k`@GAs(1v)`WI$OAHjFDf}t%QetkJ% z$7?^MQXF0*#A1mS`zJ8v)(y$3l&r%WA}r~U1xMzp8^)fS0+Es54Yi!bA}Wm z84*`P3*)nODIFO{`WJbjei1a0GupEpo--o_7j9_ixs_mLY7-=dfR&W|&}#?MHbJuZ z8f6Y=Xf6zHNocWBUxtEY9CHhJ9VwU{laY-*3hNam$uk7~QPioY)nnf=W!$D5S+;3_ z8Q^D|9Q!`D*!;;qJgQpK4pEVcN+InUe`v=oOl=piw-zr|mX(pI%yzLOdvQ6NH4+*A zpq#vgpa?DDRwMhUSohqYei}ruZ!Ftx`-k_4zZne#KrxQ1V!2(Z>^=Jd6<6qGe*AD1 zG^GBCvUrAeg8Z&syA1Njk=_{QRDB1wRe|c?cPDPRB-7={wd~H<@mZW`P|&S@z|?r{ zj*K&R-O%niF5Ro1-8Gl_wAGHgP~}ILayYrPuH3kB2c=S|{s=$WcDL0!sI}MnOj*#h z!xSkQShsjw>8o5*(I|pE-INVM`4vWw;Cj4A6pT%)6@bayyL3;%PXJ;;8{ms_O4n{b zqh(1Szj_nd2NJIe$z**=%L6!JEE|)vFOyvX$uk>nmk#_daXk+e#NJo0mH`KneG&dQ za=8*pL`si}sAkSu_$e|L)B+;;2wiUBK8rRB>*ABIKXsI)LYjko9=F${LY-O{F<3_N zEfXZU12uG!<92zgjktL^Kg+Rb3{?pSKniH$K19`aTTLqN70S|3!TJbKmS|EG*;cvR z#GI>J8IQ^m^lg3!cTUF)7TV_>M5ca7EL0BO?K*ddn7?uyt4~X!^3}eVl7k_{j?!M^?zD zbd@a5Km6&yj>);ft9u&92Qfy#E(>D>(*DO70smqX(ToL!I!E;L`t&oN-WOq@p+V&a zADPFX|J8ur!N{`mQvOC~Zy-h?6k#LW#0~ADMLg~!8wM*gvBLn;cNcJN-NGO{zAJhL zF-WY}x6-z@Z|)j^W0l+}B(Ra{B8wUEaotp}Q5+&KpYNF_2C-QsM9u&uR!0EI^shZv zBw<(FqH+tV%F`7J5m2FyFJAfQZpcyTaJ}^gl!c&N8aESiesyBR5SmXG=GVJ3DNBM>q4lAYz}-lI1meM@KR(&2ZdKU04y5DE()$a^)AhXdDnLDtwq z1hPM#|AMz$=n=hArrN+ioFXroT_GAIKaw|476CV7KLS_}QU-{VY%Q(Zdb2|71)eo@0l z-5JyZ;-8|&%_trdHAJFTONfdA*!fS1n7}PYd;|e%+@V=RE=^ZAQ(a+kkd!)JR2xf6 z@*xou^l^cP2Vq7Fm%1!XY&YyD(V!EkUZ48dV8eG|VbMe8JzQaaj~^Q1N2&D~6|?)U z+e@1R^?>GpDU?c~#2z53T`*gz92S)*(y*vI6;26dVgwwhvEmy{jwrqT(`uRQSkrLL883=(#P^yqjB*! z)oNT%q{;#MRX|je00WNtg)Yz%m1pP7<-~0Ty7=jcE;WLhO^m7mu*KoAfU90_sqCOH z#V=y`J~Wt!Qu_zmU{uS7{t4iCRAIxZ02>j5FVU4vGG7h^9Oz9=5POT-MW7tv?yKMS zMGwj%#a?i?)^>alr7kVyi^E=|E)F0I)M3v_!7-rtf2o^FCy`)AVf6{0!UypL50NTP z=`rBFUgfM^fz~?2mJ7vaH;s;=?-b{X72!lGP(dANPJ`O9(NQF`mG7Iva5O2@5EqEc zSrGr_ug{?$hK7sFU?V*tvQ@`uMMM31f@A{^^ni7|eE4>$;F1qt53P@XrW@M{3FI88 zhFWzd(HZ{$A?m5u=EB!9Z*qJjS~ifMHYJcfj?Eh zxM2BsXqS;}=E%#n@#qp3r%4SF>OCh~XZXz11;A`j(Y6W<5580FmgnLSu=+nEwikHa z6G6yO0h;Bq#5WuWFMR5IAf8{vWoU=u!HF?-|E)*A@Vm- zJ?g-ejVdr0tY%1>Zy%GYyNSyhjb)?PZZnAY!@l2YFkBAusGOY(wn;(F+eVQ8ZG>Bm zMT+32O>H+V{+Ml{&~=UsMDbzai$`PMn{K{1)VUnc5b$bYNh$>z^*T_$VVgvS+pn!}NIZ-f~}a?nTaTwQScT8R`T!!qj9_~2Zu}vW8YsI#;uo7a9qTG_7Knb zCX53oKm1pFfYV8EXHegqkHP=3-jjD%IvF*hK8OR^XJ4Ml@tA-T_6}Ege@>MgZluIX z6tytQl=5&4Zi0>Gr?30K=1M~7sPBc@C7C)_(J(2W#j7RnKkd3EuN)}Zt*fh@@t>J! z#?iuq^oDe9StfHbew)`c>QwJ^Qvf<}(7VdF-FP1pej8#Hd%u5o2JIE*jM}-#ru*<- zr=Y<~!fY#G4vv<+lqn&l|B8-MngL_YO03$JP)SJ?a!~Q8uGdKGic;+DfaAu-$_Rcp ziI+hc@yUUHMF$W4**CPC_aL|d*eRv44Ec);uBYPcm%!`$QSj=1>)9F{j&0(9&#p@% za^`nEoE$BCe)#RrZvE8ZlEb)EZ+)*y@%Vq%?!e^_QG>}3j3q1u5Aw__C7uQM)YZHB zP|#s_cbTNYbNZP6M7eleh=+$q(#EBzhnx9M9rTCX-Ci0(UHK4o><7ObL1PT%K#anC z_#Bhk-t?V|8JeoeDt*2sD&<^r>ANA7J@cOquT{IfY5!BFzcM+0-}m%NeRO%x{&}}P z?f-56Z{HS_uiO1wDC5@poO>LMpZ6U6_2+WQkE5Q2(%aAR^RHXC`umieOHz_pQXTkm zmo}-=qFBk;6D^kg?WI$@lxzD+00jKGFpMnk6X?pwi zX=rYI=p$3AS7VR+^c5xxy-E4?$7fo41w18Up%S_ff;3Y^F9Ef)qS7uhym(scncsgq z_qSYsX5Fa@G)u(4@q0~&Bn5j~*B`Un}xRrTN(2z}GJ? zN#3};*ou2;V0LAgU%^qo0>j=F|GF1~FNigpns_Hx+R_#~yCa|PZH6yE3+U!k(M>xS zubNG>|G?q1GrzfSU+a*{S|^OpDxxBr-*2JLz-4gs;PHQ7L+rZ^)@1a`6^v4oPQ}2g zkNy8FDilsOmpNrt;r!u!Sl_e%`F>OkEh>%nx{B83~&DZ|BpW(3aQ<}QHKQ2x(Jzx**Hl=C_2zk+2mKy*w_d<)CMqFHYm!~ zt272&!Z=D+FM;7(P4e@VD6utQxA8fGJ_^Q#F$ z819_Wy_W1%}pjlGC;>w@%kB} zdz<^oCi^RFCxa$K+`C<=`?FrQbAsH?okyKe;gUkS0*puLjqU>~ZonJ8Sve5C-ylO? zgy?)}puJQmDoPRmP!DSBG~^1_u|HQPHXr9;pxHwe9Is`kn_m4|4~t3_l~!9;Mtr6Q za!-(~^^8dkL&WkMofyQ(uQxEmty0~f7lhKL5wIXPz^+(;>emI2r;0?p9>~sqsH395 zC9gdXEl5OhvAzVQG>DY>$Uy7ccJy+~jP{zEyooy}J+WDgqo^K-AMRia8QYdCJ&4&+ zKt{-CGVpm?pf|ktxUm??-ATt?y%QKF})~^2pFdY8xD(*FFf-C}~f#uz> z%4cxMOVk4Cd)-Cr^dU2pC!JKq zteVvhkvZsN;p2u@-X>s&1ePsrxev^{1qe75)C(Uty){eKde(%C-`Rf7OmA``y4{p8M1JRP&mFrSNQXVhl zX9YQMucgDzAo}372h0=jKy0Z%!gf5lw%0lpp}hf{VhpNm@ZaLxN%l9uT7UoN(xnDF z83>WbR6)5Z-`)1pIEE?;qj|zrGCIHh=by@fdC)Vzl4co;->=hoP#b9pbyX1teXhu6 z!%igyp@PRto^fKgEk1&zJ9L3INQ&X3&`7K5p=>5l`-O_*4M^{wLQTnCB_$a>P*#T^ z(;ovA9|_7Y`M`k#-eqRD>DN*5);;i8m{9c+c-{dgx{4aK-bE;v-Ila!kn$G7XR;`& zdA!9NE{A*|PNndI=34+oUr4uv*3ofwjf>IDM{z&k_Pl$);`@^n;z1 zMIWJhhCklY4<4+8;LHTFwG_z}_wHQQpy088ANF8t9Nt#IxkFk(Yo%%O*CiBk6t3ap z6G08>zCW~OPh5QY*_Rdrk0ff@@8&R{M1w+Jf)SNCNjvt zFIP{|$kPeS_#D%l*RN~X?+X4tGQ!YZ_UZagd@jNDYOoHdLM*CK+3*JJ%p6sdKosiK z%78$)a76)Nkv#RM?+R+ZS%x;6;NDoLQOnRuy&NL(lpi3Cmlk!K#CE68oMMPxokuh^ zk>a+YZ9zswt8EcN6dUiW3NtDXTcP{315)?|WVWS~ORpY$1At~e4iD7z_3P2jp@&!2 zb*GS4%ojO4yF|E~_Tsoh3f4g5x4SVhp@00Ifpd;`jn+8Yu1mE&Jj6Jaek!N3-U@6w zD^7;~oDXO`c^MFms+Zs38Xcs%UKlx$^C*evKwDaV@8hX9gpMyDuB?Dsn%dhp!xNi^ z1+V2n{leCx&z_$KR&#(8=Raw+v0HT#`Ag@?e)d2illgauGR5oHgY&)(Y+8d>4{fO* zPbI=P?*aeRB%nUo*kU0Z`3M|-xI3)G3kHEulSYI=X`IiXwhtLNeJ-LS*WS6e=-|Fi zLfF$hDH(VIiT)WU)4y->%GT_J{^ob35H5m0=oBlT;G`0@HdI7>e7MD%-d}Z=Q!l4q z7Z!fCpgnb8?2w#wX-gfG1U5tMqC)6=gTz%S#v>azy6A31f)wP7ID2D?Qdvgh;%C zRNi_$W3r*(s~O_b&->-O;3up=lzBm`7zZx@DNe*EQymL7uxh^#=^>$g%-t0uIpeY-e!rw+emmNBN_9ATHg8OLllcKpVSW zRIi-u9mNFysJ1V|buaTn?kR8lm2@C7|8#N8C~}OF@p~^yQzrdWpe9AFlR`eILsQr( zr`T+|&0rdJS#rPBDTwCsQ4rQMIIx_C-E`;_-1LsAg%TE_SysAvztxGKt>V`L;xPdj z(rv_K(zKOWw!6j=b{jK%8~C2FhguyhgwC){p^Lps!_!Yp7IZ*7j4#?^V~b2*12(t^ z;9~=uO&Z5c$-q6#YIxP>`xz#a)I;wv9fZ&KGpb(Rc&8Ug$;nf z=Pl#t#Qg;o+Ju8n-9Lvcc9)f(0jGZykTh4%_2r_TUfLvOqms6Eb)c?61hz=p2*&?p zH0uQe{sLlUDJaLI6|QHom&|@(i0CHXv%rfibLp4t?CljL>^N8De8n{Wu~lc*5*G=8 zy4J=!_)r#gb!KiZ=g9OlPX9Yd$0+}RwMi)*F4CLNV;cgFatT>1vGERl?uf$k3>;P9 zl*=#0|JuPHV^(_4?c+lBpg2{u+rD8H=HUP!jIMezI55yOilWzNFdAWzNN5@bptKUU zx8q7|y07?42Y!f=XS|01!-mp{3nf8{H0P@hTojhKhpfWb(@Jz0TFQPK=m6D(M{Er` zVlr=PJ`BF;93%4UBsUm$eVqS6b;gn6{VnC)>Wy<8csGLdY&&ar!>;zuaa&~tyaHie zIt#!GyuZlBDZSJ1x83BkXzQxBscEu?9BtvEMUr)0{oi#d+*7a`#GRe;(V&l-@Tu1f z@OVyVy64vm>OU61ZX<)qUV9Pyj|z;r;Y3 z`z=j!4J09=pD)r2+C%i*@~CVW0U8YO<>H)>TvvVueWdY|S3UvBcZ}G=5FY`4JREap zw=Bq4P2uELbTj+vzBWQwlByltG!*`Oh|-$htCTA#QAZThG+-xh6{pQfiHQLBUTq6H z?eH?bEJ9yr)bMZ|-;Ud4Iceg_WmpbZ=|2uLqOPAse*Ehj^9c$VY8 zBO2tMny`e#i@wU!Cq1B8P-4en&=Sb|`hG3g_$0V?Ip!^`4U+EO?=%e}fbr!%1h`^p?uM$D@)Rhn+2acM z+;^BvQ z!6-QqfnI^mwJ4SI+b_ANji>o7W-_rU{}ZEp9~5N^D7wAVs9-U_OBv<5I%t^}w=^AT zxGB^1GV}*G%>KC-)Ls%pW_LxUEcb1JPeDHpNB+uOXnT3M2c%W{5=ke^PVkF2v zYB~~Io@!ZvLnrNQ{tm=s6PmAp@LO>>cBv@lfRw7lK-1l$s8Fz%>qJASRi_Gzb+(4( z6=abgUu>6gTkrO14|{h3cv&)hxhMuk$!;xA{Yo?jAa50wPz+*{9 z*B43oKjg%smosC@4?} zpXN$r*tg%uGv5RsgRNuMX>D93H9^;4yuY)sDpDqRtzgN8)1A~PqLb=@R1fZ1lF?0} zn1{#4#>S(oh^BC!@TM8Pju@6KW^0OMquaQHnCqoMJLg@e3^dN~myy zzXEI8F4nXTNMDU*MIY-$YXz#4h*?Zz)7AE{Yco5d&aJP>kYjdkE%&qQNl4QSL zwdG?!3i1eqnYEO2AdQK>kHN^oLas)B5F8g@mjQ>V#~} zU<1_ZQ)-p~gM<0=6D#y07tX6s0_oH z{T78VY+u%8Ou)!q;lV+F4p1=*z3T|5=JO(1i|qff<$Lg3zRc2r?=ux>hMM~Fg$vLo zggQ+;mCxz|`w$m)^((Y<$!uliQdHn_`Z?`~{4#wwRwX@Q(pCPq@omFuP*3{)&6G9g z-mHT`kf2FQqp;{Dy4?F7*1qaS!L(C*UKB?hsz4n{exQrNl#fpGwV-@iZ3t1x+RZGc|*taoLZ!?*;%^H*6_??eG!$0bk1s0lBxhwCnp> zY^)G)3}N{&yQo{tbxWa@aoW9Jt@=j^7Hc@V-L5ape@SDKUa!%BMioxi;JYQI+m9&@ zr-wI6b;C~dUMjRr`#kz4kROj*F;AjSY7qE72aJLrNlHH-V!N&s3uZ3&a$424?xGJG znBHT1ej#FC79fy?2h_J>R0q(em+u2D{AHBKMgu4y{1z$Do&x(7^55`(Q&bq$}-nO{)+q0j@RL zJnJzopmIfbXA90h`76wy*|2!M`~ouPttj@=!>0XZ`GMWS&n%(IN%PQLMOQ*8v00Jt zdEMQiquc7+BBH7OFc@!oE{O;%UcgU{bd!!n(xLdHvdYKr*{s&SyK#T1G0bP^a5L-$ zE!l&+4CW#^10}FIhwLj-ZF{!Jjjl?Fg2*CQ?K%J$YXA$`kYpErxSSJ@R=pqG<7+ds z5MxXm!2u5kWV&mooRPYEj`xe{L!8@bpoHtO3FrHHnum!bcw>#2knUx0yfcGNIqo>R z=f=@eNKv{VnFw&AF$M5>6NX5OX84?JfPx$X*sqI=(k$zw((*z(p5#cbIr*kZDsT%3 zU^M;3jhI(Rc-048b2a#yw~anUEOVHHxCO-1S{;ho3yNJYn2E* zG=_$WVc{i*;jv3ndYyay0u)HX-0Zv7DJq6t$|0NmT@+DzRa8 z2=8D0ds!!+Z7f-eg9P`RhD*I`hrz3@Pcug(tJSU%|E}+1=@v-ErdA)~*I&O{P6e;w za@!Y3Vd6xo5ZSdg*>T|Sq9ZF`ED~63``fKnyQxXd`; zfwzQb*^!BxbAGZc*dq4TVGmF!yOhS=%sKyv8b`32x}qtu6^2(MDegKWUBc<&#f#}n zo9`&cUg%TUQ*`LNuB}>DjnCtCgdH3&Vn*6m;EHp1zp$|I{YeByJKyNFvFuhLB=B2J z9Wci0qtZJbLPyUpuW<1ph|Fv@n~ZO92YOmDsQ2T`3Ab|s&dUPjT!Zdk9nuN4i~$8S z&FdGN{k-zoA7ec6P&Fj%Ms~fb2i9o@(Uv>MXUge?Arw9J()c}{Uw-)|d)eY9b`d@; zBmkpl?N$X`Cv=#oZ)G-nCtCb*Qa?GANJ^z7LawLDz!C7n*t?h4EbktoBGs@k8a>w* zsOZR^R*#tFIvXi9yzpt*K?Xcdd(2KbD{az(QR1U9&r(-d;_*B0XSa{);H<5lvBkdc zSt6;cyG|@D=FLj0G50(~l)V*U@~vHaZ|woq3arCmRxFGINA?QpOM%Yd(MN^mGpqZ1 zzNGRqxb5M_wLW}wowNZBQ&ZCi#iVU`6J#=czU3MS#y9{32bK6z?%^82^Xh$Ja7=_l z_IaF5+asDlfiFk95rN@&p;N8;U_o==eJF(LcLu#Jxzt;Kr)25#KS9#0vJF3JV5`Xv zYKk;|7D|MAW<_UJ%o%tI&A$8YyS=b^$W)BX;T)L)v?IctQ~&!Ld)yyq5_(^RY+D;4 z*3~`)w?Mw6^yXV06);N$YjnS>!&#lM8ov+WXLW~Ty!?qtsPIF8pnOtdLrkXJ?r*aO zC*=H+1fQ}E9`WaBY%)KW-W`ChO{aOyN5n6xI)8renAJhA?kD^-zppxextCPG8s;hj zl-+Slr0%nZGRzXT2G!-WWaG3zn_UE9S8TnVP71CS%)39Rr2WlvR3}ds6hB9xfOUYG zEMfGn5zPB-J?b`A;g41RQ>Y6czZo20kMFax+(r_1pU(V8h&c?UR1VCLLEQ^u1_h8* zUXKBKgTiFTniQ!sY87a5PKY@ikhGne>wUk8s45`9S}Af`!w#xI|3+FC?IOs6P<^^G zRE^yMk%d%cn8en?(ArK%RWyIQ4T@gV)TwMzI#iEV#F<9&Dm*Itm57HLibIR-7!7TY z44aK(Z49(TZg26(nYpRZ9&BlVFDo`n|KnYt#u>UG3Lm-!j)GMT4&eM*lpV*D5z|oU zsbgNQz3rO|pVD1fNiWS9`6L?1>!ClGY{~kAt*Z<7-yI#cDR%A+94PH$uhOj>#9Q(F zE!Q(dAoA#jhUDQ+Nr6m^LGehosRvW^r6v5gxSAeQU=Yigw>C8)0{WF>x6nmkrS7(W z>lAoQJ=DYB;KQqpZkd73GO8r|V}lIj`ks$TZQezghuS*`C&%j6(C2 zTi_;OiDCBD$_T9?02M!aAN#WId@>ge0596DD_s3sONurk9!U%O=_CWz70T4_Cr*RS zmvXS!pWo@^;0V3Awbv|t5XiI!XFY}GK0rzYk5<`pXhWhvMqu8mss}Wp{%uVF?#Yxw zl!^gDc4A~trhFQkihDPMbKFKn>`NA=gI26V>-DOUI{6UXqA-}DiBL;a8AP8Ep}pK2 z!|Ra)@DSbhRRQ22=d%4d?6HOK1_lPeiwTZIMev9QJPt7yMof6lEsXJy{E}TDFHgl? zuT+_p*ruZ&-}oQCcqUcK3wR6+AjZj17_>Q;L$X{)aHkUAgb=^<#j}1=dBk8sgdS?SxrOZ5~1hMnyrNQp(Vh+PKm5nF9*@6}Ce@7ldNcpB4Y)V>mLK;eX z$1_01MvvhV50H|u#8Q8PlV1}`3W5!cMSuH%XV3=Djv5m|Zg|bpULG%u$HlEZ3kxB!RgD4Gke}q^*0#H2-pP-00|5he*e&{7$l)-q6 z8y22m8H?X8{2&7y%?@9UyO=0aX6qaS2=l!a;$>G&3VAn?%j%?)tD zmtp!IC3Tgow6wHR#1=S!huY`9W~d5Fj7YOsEQ2R+M1tNXpj)t=#a430eyK)y-pk|g zDOS-Ji{56#AAj)O>_vZX`8QI|mCt_H_y@#*dSZdb2;0^ov*m2-#K_19hz}tpVU^x0 zB{pFiUog+;3FGLw%tUR8>Ew$Afu2_+Uu_zohxW}vL0Yb8IK^>lMmn-Cmtq1H{enA;r z33-(+n+?vh^Aw(7>BDanx5F8YuV14&GlLF48ZRl{^e)m=DX!|NVTQPfxCDUcFOU}Q zNYe+Njf+QyYL0#MgzID+THjrTM-o7%^q?x5IZJQ;BQX)utC#vnr#lIi&vRHCTHEqG zf}eVQcztWSZR^ehrWZo3Uu;6uy$68vO*EbN0ynycCaxE~chnympeUQNpr>t)y6VaC zk(yjPX`z$V;kRDwQzJH<4FnQ&0ONv-)(4|U9zTX$-WsA#sfCl=RcKA_V?!5HwtqdI z+hcknmfv`4U|F}KwYsAw=G13wn+-f+zAw%jD%#Wq{PZIUmzyEp_8SQmGpv_1DO#~9 z?FWuwP=<-^NM;9mbEHzq^;1>((>TvS5C*&@OP4}yJCAh%ed6daHg3e-;xMajT z+|1FtE(hyzKXFexR(F7>P~RT#=E7RBtqN!cT+({r=gy>A5&G}F?T|#!=RXH!tH|9R zkbWj$0~QZEw69ol?ax!m%zj)wvLA=YKNm;3G&&)|&3>X03KNv1T}$S5uo|4#ev7~( z2-qI!sx1=0`00QI$+zPW7KG!PkvIxiETEE8u#|lBvnU|xxf>t}lN4Ni6<`wxl>Bqp zwNvN)#G`F@3IB{^(X%M?)L{~|vKr5@kk2||XfR*weusiLE zK%VNEbGY6k+#AJXia--^LMH$vzqbPoV-3glcKNd47yY@5$8*X9}Rc*+?d0 z4{X3zxJNWOGWHqGX6PppNJm21EP3cPEbnTNbKDTb-d6zApHRc*!Q*JJas39g>U}u8 zD}#eqx;~?P2GfjY$=t`00I}60r6rWc4Pov?<%oHJ37})yci;JHSvz&1QPxLpoczw6 zTzKBT)z#Idn+fTDUl-D-xOE>97ZRnbAbHTIuyl{PV#^OfQP$&wg`-c84%u8>5QE{r zU>H3yZH#JyF?x#PQM6F`+Q7G>Z8EKPJvb?I_48jY1IErkw{MWZ8_=8%_Q&VP25E^D zkn@!8mTU}oiuIv@-Fk#jB|MCD>rI_)2&MQr9Lm~rC~Vo7$rJG|M@}Re)U{%GFGiDC zbxp)H2dxnjld^8mQHsOf|5I;oFQ^#59JKIH;e3{^{&aNWb7L&70dJ#Vya{^af%QNR z9f;j~g~Q+y&D}WLw3E*(5qP)-rQ}k=m=SmR|0#PL?l+lF5g`Ge0OS8whxq^6&yy~a zK8fqwtkdE$xc?*@R?1c=h-=ZIK87{`G{QOLux5FA;HS8Y{(TaPtC|sa^c3w)z3dz! z^PPB?@m~X%9ygyC#yXyYCqNFgjU%i-TfY)pORYus_#yl)A^ZQ;QPJO(I|d8C7hR(H z=+UDK3JL`+`MA*hik!&q-Rk@@j&U+jK!aZ%?csey`+Qf-OAhaG3JU5CS~$>dpeE1( zCP(@v2%8V0*&Fz!F0vx(VL4sjRJcMz4+SU$B&0681I>zy+2vPt zq#Y_i7^%r{0o?-=B^Ty{4yF^XK$J0r(wv4WP^Td?6xC1!-dbpbh!-zamzu^NGX>dj zkih}`mdnrw$nxo10RdFdV=IlXu{CF^ne3o(8^Y30YRYBOM9BR+)s=jG1zA~GCNG#$q zuyqac&YMHtgsidbK`>Pc+8sbAgm16fa?mulfi{eQInrqy@rHnylp)J&-5kCN=?^@o z%dXi7Xp3r%-VgzNKt)BY*Bjc*yLiKGuFxBeK~Tya43kLtcfY+jv>V4L?#15xDlb5N z6lhKJzybOmuH?|N%Y!<~&|J^TinI%csOdk!9qtT@3(0ye1S!&1jMLpdi~&gX$yVUr z;a&StN&9gp=A(@pHy)c`vkM%{&~o1&#H(AlW?jPY=4b<*>%H~fPPRBEYEGdL_kvw!N%q~nrPOa(|}3LD3UzNjc~rYpc$-L@q+reU{5B> z%ax^7XZQNP418Z0#AL!^-q~moGbRHK=PqV7#1Y;0bbhhd8c zk~oO@pLR&_C=&1xz_n5^(HeyQ%02^lysDMtCQK$Bf-S{I)uHFcx41w?9HEr+0}&EV zDy`B=!Fur=dyR21^&<}wQIX0tXf6+^@m&yQuYcTiVrPaOUB1+a>+dXY0I+xjnLTjCdfhn0fH}Q;5 zB?HYFS~RHjlB%3U;6WC=su+d#LAW#xoIW)n73>%WVN$8m)k*9Qh;9?dGe9p#X78gd zMmbn`j23(hHxN>NE{lgDl|Z0jKVsru-xjVfrj2Q|e}KyHci(^iobJvbp#NixTNl6J zle$kQAu((xe4!^S`An%{dN9Q4@daWe8hV?2ykwv@ezGt&7Xy55DHX9Ejtll^gcWE(R%jb>YKqkw{CF=BZMx=>12KV zED{&he{GH$m-+@u?6~bn!dH}{bU7t9xhE(F0=Zy&D@*{I_N-dIT!-jvrjR`r0g)&q zZ)CI4*YaI!Ig@Kb_5p$$vHK*gtX>@ExaTi_3at3ssU+48<=mD`}! zahT}?E{|>Kxx^yU34W31hZMz`e493jqAHGNVMxZfh{C@k9Y6b6xdPTmD=h(T12w)Q zElyQ36lcDA`SLQ3eyBExe+Y5s9iAYK`=A>eCN`UeyXVv{T)5y5nf%iL&h>fcK;Y7zF?VQUkKbT< zlb;b=TB>f_OPQBo3}GTXkF@D(2u?^R44hW-=)y@Rlg1_5iN3y>t#&e)w8WFndgHHD z&m4FHd%vOVnaUOseB}bdwlPd7qv4<`{pW2K9;QeU2kQ4bz?(7BRLU-z#9a-gt}u5B z%+aPL1e#}O)F>4A1YWlfa3uUuMg#zATj@ z6P3w|`M$nTMCJepsYK|8#u;oPEqr}7xyDFlt=?^BvUdYpc<3tL-hyFy&^H+>=b7bb zhd!Lx?*dK{f1pJTKoTEWUxYE=B1}3H3w?D1vT{Ch6At8^T zJUw_+Q20^eE+DumFxwVJ%6lCoVP$PJ)nIDBw=wy)Y!TBJiFd`%#MMEFl^|PmVy)8J zZ9{{&m2R{CI<@(8MKPT+=ens2BsTt*FKPXdzFNBPCyuwvd27x8OWSmLVAUqN>WL0}$=**$(= z_YMdWR2wyVLQ3hm?1S1S2#a;QuP=&W@m^e>@G7bpYh;qdzm&y`3F-EIoEN>R4bY#) z;RpcqOka}%!mf0F{`qgChDKyO97K>Q_+_x2gl3kEyH7G}=TE`F3L4573<^-SfKDqN z)$SBt1-bK=l0T#Cs&8mGP&fqTj}v6IKWm7--NST51gx&G!?P=RiVS)c#T&U0#g60L z+kmew5e-N!a!VH;0U;EH`a4Q8ie)QS?4v_*9m5TzS)@ySQ4GuM*bI;$XIukz95Q8l zposaD*Q1E^yLDsa>l8( ze_r5>XZ|2n(VK>Ul=_U;NpI{rC@t^wwigr}d010I8W6ntMr7kzlP&#@flvX}HguiM z#>nlr(DYjW-?#aQ^q3R4A=D5kfj7p-DV$duaGAV$gRu|dYd@pAlO(x1Kg-mY(3@p= z_6`tfxKc0`$vgC)7#&2uOHRXn93AgC@j(1>AYW#12tCo3Eka8GP0a`B13FJZ*Z33s zF?n9o>BLI9U_rOg1r9uj@QQc=-d5c$l|1RQ=Oh|3t!ZqVX_xnOn7>>b%M|7i_aPrfSP?gzGdQ1`AH)vng>8xyZ zyGuu`clUD(JZ-rqU^v)%@<)~l5-^HAvAc{{LR2k4XY+yyj?A^O$M``-f%B~J*Qif_ z>RI3Dy|{73lA*D&b+~BZ_}b1kd+x$KEp@h@15A#saf#_Y!B z4QzE3s3>$YBK@-f9xlTrC2ohLJN?qqle+tR+Nwq0nC#n_=F|M=X|j=WkNXw#Zk6^$ z;NJ`0@~Qz3G%9cC3=x^Zb7?zlGj17Q%<;ofM;r;D3h|J$)t(=9v`QJa1X*?Ii$D8q@7 zM}rd+F7$Mjlu5un>4LZ9%04dnzkcKedycj$;RWXZm%I7j4wL@R|9AgxnfvB&#+uT} T+_)d;^Q$UpvY-BZ_SgRb*N{Z5 literal 76850 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 Dd^fhlFYaBiwF2g%zs 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ℑP=XkJ=7g!ppv3PA^E~rAC~DP$;p$gfI*k3HuI2m{DKA_|L$~6n%Xo4B{%xa z$Q;38#-{Lj*^(XWtqmv^fN6?Cjf413;PJg^KBzH`2JjO)t3c5cdfiF(bJDo9*;(`T zWhgsT%SS8fT-?oIaLFx9&}DZwl{B`wlegB_%)t+f0!oz3UqN zzh{_U$Fr=4?tqOlb8;%dk}a=U@6Y|0t1IA)ysAc8fezenOF$e{+&`=FpY!m8)%vfA z3*JAb7{CQK&wAw;_M^YCCP%Zm;&8lgS3Gyva+&>h8gLhC@d6^R)$JCE&L4# z(U4B&m8Fxya|0!4$i5(PRlR@zWr6Pt*Un)kJkm!B6hLO~N8L+{Nzg9S6u~J5{iBM& zogcVt9vL4!XvD9ZN~_Riq5;V@iDa1;lEZ`6$QL>7!nH_8WgX7azyjQI{T0C9-&el7 zX(kNV201ln1(ZI{HuhukQ9Q``7_Hzxe$9QP4&E%Ch$fUN^g4cqnmP?2h8gtiB3%gk zv*#sY0^mDGGrf3wsc~j5YG3M^&dkh=Ein5u4=vkR5Zf5{7uq1zo&w(vdVoywAkBwP zn&mY`cZnAYI95(Hf!YoEK46%EZZi<)K*I(K z%FQgZ0Y>CLkgqZU7XZ2q+bt+N{iNC%0h=9~aHkk6I_ zLZCKH0pSR=1@KaO8bU6#ySrsYMn(qWrUzs5*>51q7(?JQ1%}^S#5V_&JNZ^a8E`TO z4jda$#3A+e0+xc%>Ia;OVL{(@>03_os!-M3dTQESDwLiB>X--800M7x+-gfG+O>rX z1>B-X5?4iTC|aulOwTA=)0DpMkqSWU-Me?AB=uU51_?tesWQ-7a70lSw5&Mv+tf}$ zKv)#8obAa*dIu#WeCSgllZW@r-5j%d-8%cd%D`%y0xypor-4YAh2gY;)uq9t2eDWX z*rmYth2}mZN6d(T!V0#hk5rd0mwuh1o^wJ&!ohYxeVPciZB}wgr?|&=76rT+xyP4g;eTl z1);MUR1)DZ5BT(l{ILMF5qjWyxR&y~a7h9{epp?!vvVO*rL`O=My5rx+}$Oh2635T zz~?7}>IPVdhp=vFNFQqgX>gEZp`Dwfc+KoV6T@=t8Y0(HhHh`@S~)Er$(7jN?K0wh zsWZn=)?un$9MMEUqi!a6Let+rtLOERMWeC%&N<&b*PZzRmJTTgbxxJ>Ue{sME!v#M z`8{clG1-BHAq_fNlAxa|elr&_-nflIsOsi{X2gp0*Q?dZHZfp{AH+5j!xI5EP6VW5 z0u92`?TLQP!s}YnODSI{AP*gMonZrU*vwBq4mFUStU%+F3G~z=Ng1dflEDAShQJ|N z=V*ZVa)nAKN>MQmQusR4JW~)U&d&@$navd*=)##ZamaB)@c7-&j+|Hl_c;USG`y{- z*gKn^A^>?Wa-;&7L-FJ%KYQ49OmuW&U<-ICrRqWt`rht(#aMG6;9)oeMh>}KG2}8x z5(TN1HhB4LI8_DqZ<*}`Vmfd@!W-zoha*#P&?5nTT2GJ?kv*K9WemrjIXu104(>D; zDWyYz)^-`gASYJA86i!NpWO-T4gk5mN7ka(OZ9ML#$R z;RMn?2ImHdrq`5093-aXk-FtLs|fCh*Zq$*u%Bk&$v0t#4I zwQcLKgG+BuO@5Ds&}9bq--Iv%K#<>LtO2t*{DSnh&IAaITY;j$0u-EvnO1!K_|xw8 zvVsTX^g=Ufxu4;x$Sy$6wt?jBlhdG!Bxpg4r;?&CoICdk7=z5?gpkHtdTz@g~FGSF@%!C7AF>1uRWuYRqoum9cSlp<9H?Yb*H?}LN&>_J8i zN*R72BY$RF!S{XOyqHg)J`qJXjSt>|a}&fuY+$QXz?nF}7x6*5Mx5NCVOa__aS3Rq zA`Y!L`2JqFPk#tpO8`g@s#;oFz&Ti3ec*l)xU~!c`;u_8M)qTGIG+IdGDQEDGLNPx z-g!IAk~zMh>}_yU5&*NV)~grUCc3$SGT>^xl$;zZzh%`;e<)y3g!ltzQdEH8DsYxB z&R)1+unjC1Z7t-|3qJJ*T$*{NW7#|2mvnB@Qy@%)+e1@REL;Rm zxZvD1!ypgjn6Rt$0$|Ag07%yzeXI2RFN+MmHPC_`O*q~_IM1etvSO_VUWkEiQy+fXc znB4&(kaN|hB*#?YF#1F8%K@-~8^}Q*q7O`Y7rS=xOA-pMZ*WKTx#ChWA0itq>#xVw ze8~5y@Gk|z5OG5i;OIaZ@E_|v{E#zwfV0^I%4HQu^l7l|j5SwK9&N%BH~{9i^(X=k zAOli26L_j0B~FKTuFYzcvsLR5=cc@+JK&V0{X4)c?t3;k)1w5LJOtH!{P@uof|T%P0gw<#c#7iG z_d!A?a!L)vKgjA_L4a?VrwI(Vl}pTd@S#9c$b+WLBG02m3(gh#2r>vMkP5lNb>WDN z1x_EJMx<+L-O`$cW?0wPfZ0GpxbH$L_sH39zg!Wu0$`F%IJ;)DP4Y4xl#I_P1;UUDFChn5W-%+M|wS`snLE-AEidaoK%%zQJj6_-+RzU8Xz?zEw*77b>%V z{(OvV<_qV~>%$3%of|>`l$jYBZvw|vIb4H)?$&C-U6J$E>cUAy;r%EIHUmJ1g?cBx11KnfSz_eRf|L^WYQ3QJlta;n?q1g z0}fe1@Cc#+1)9)yVtrvq7m=@k%g#U~9neSsI4{_Th~g8F+V5_a!`dPt4>^@E{&%`Y zuEc|6m5Ep<9q-fkzSY!BL%YQPL)V+Y<(#*D|Hce+jb+?pWJzjfWGVX+l`Lb1oMmmN z4OvoDTC_=K3}!4fP96gp7)Wt|IhM&p6m6xU)Md?Q0Mvk zeZR-@`FxJgQGfR|g|K2kqti5D;DLr52@OcH2JTewg2W9DZ0p#(i0Rd&sX`1}C?N;! zyz%Qui(l-zW>RBp=ie7c=U7b}Uz}?OP(X(~;t@I1bF(j5CI|7LdHA(fc%u1j){coN z+7_!hoxCf<5IV&+4*PUhFJ3*%;*Bmtps*f_Pa-e2Cw?fD3{rVj>{)(~@Av7*_p@~m z-|N&S!Ek&oH8t2ET5Mmls*oZyvkH+jI;}0kk5XQgoJoX95umZ>8qIsmF5Jby!nQtR z#FQu=0t!K`J;^J%XN&aQr1!26Rj^&Ev@ z3R%wXDMGb->kBpfl+NJx2PPo>P=0`C%cJV)dY9|(72=G@7Fzsi2(0a$cCGEVv z>09fkLAzp&I^~X~eLxf~)B@vOLvzv0q#(zc*!(*ee9b3nyoz&nFDX9$Okcq*vTfMFE{}Z0k_sL&_+6#hzu6&nud*M~_gIn^#wCzAo zI-D@XVB8?u-Yr0|Byw}iCl)o_MYMW5YIe84acXLPhgvHaA(=NCARB& zj}gP@Az z*+0$+m_ZL;anrJv{Ct|fJx*Q)4f-hNx{iGul5qCO*@WWKlS{X*;6{8>j-|-V6j;e{ zyXw-H6eFW#?1B=(&W)VZ%Q)}%4xNXh?u{FHe(l>_!!M<>8@=y#6HQY+sDJg5NdZl| zY51I;r>$5eHnq<1c#qw5NP%&wA-JT$Ap0Tv0_uN~%+MAg@JJ8+~wRk~A69w!mE@ zvtULTEJ;Xdh2La~7KhY@L&Z+%PV`awOFgtH948e4Q;{81S{`nbp5ZwC*(OaX63V}o zyD!YqAVrvs`(H1fC+7?wf0Ps6SF{8>%to8n^QDhWy>pY6h(0$I=}>H_h9u*;y%O$3 z9jW%E%Q>rA&BYviCXm57iSf)kj$QZhy{c`#PFcdrAD+1Bj$L&5E54ulr3rd#Td-C3 zT{*tXFZ@lPbe-#oLoCblu&8@FD%_3uD}~t2e)c7mpdW7oTN?v)?8=iyivgaxU)7KF zkD_f=89w~c;&J;LP#zpVetbr;jmP%UW5!H;7s&CFuk= z^`9R{jFaWM-{Y|LyR4Ti zlzYhVk!C-;J?6>_E$^(EmN2!nS-p|6!618e%h0UZ(cAv|YlqqEgAo;3wHZ;2l4TpL ze)7*nj^gB3`#rLg>=9pFUA}+yrYpTH%%r`rBbpw;L^3(MaPe@;06=)05$Tsoc7%kO zE&A0tWq}Uisuc6iN3FEW^QlN?(F}QLOu#yCaq&^IM4)v?Ayg-=Ju8?3z3ML@^t;du%48v) zY<_Dm$Ty|riKI?hfjm-G-}89Fxf*RT9Ns&2g>n+|XE?>#bXPlh@bIQ0Pmdv^i8NLF zV`}PY^M-nZr`yI*|3{KWXG^r7zODD+ix;P0tT2OWE{qfR)GqYIkyq^-M%ra#2l^>H!1Rf3Z*HC&=Mc0CGMRV+`IsY)$c`0@mLxcv21Jt`0~QNZ7{VcT?W=ZyAo0> zRJ&?Sx*w3uNM9j9SqrhfhNG~ln??fiMkkoJz2jXEaHr)?1?Mz!=ZZ;~hv}4S%)UAm-AW6II*5J;fo9U0U1-@jBu2fBDiBq)=^KYxA~ z`ap%~Ea1X(62~d7Vq%Kl_Z!j9?9^^nTpte+ATmp4YHBmE6v*l%A zt111L*q92Ir8KJB{4{97Y}X4Pj{4UuT_LXmJzOGpt%m!W3#cY;y0L(@dnb4&U>@C* zigp1OxVy|wp14TB_vGkRQAY15bMdy%SIIlKZc|j?juB}K9MJey+}aai?UFi;Ad^pB zIV?KzuaEarJsN06ohbd?b=B%>(-JB@^@p4{wE1F2wTDe`XqjcgsIc^k^baP(O|%;U za!%^LL+yIpHKWCX3!4zBv|EUFx|l+9UR`~>5!VAHSQMaM70pNi5shy;xPm8M%s_KI z?>^W$9Hu}-3T+S+7V_efuNloSjUQcyC!spj{{?>C;u0dcMxsdLyLBy1)j(J>xoJ?h zLB_(QQzhBO;9>i#;Ji`_I6chCg=0gl1YlD(xps?M{QS*0zGTf=w`w7;YFr=&=If-@ zIXGmByPhcMC|Xli-P%3sbj_}XtNw$`Wy?dE8W^wwY%NuUSMz+v`@dUR3(V46Q$g&y zJsD3=?XIUm+1s~of4R$*I{=5)A4#m|(th~1_sAc7sLpmVNqi&8vpTO+-*;&SjHrJC zps`v{A-`^H8J&R=~QL>c{G>kGp^OU9My6m8jDYf3bXJMNG^8 z4HNThScCDvh~f>M%ArDUU|E8Z^<_|rm-Int1;EdR&%AGNef#OUO(6nXRPSNqgTxOJ zUDH4nkn&WwCwg|+c6G5cP&nH94SpO=Ig1#~tqvM|m}S_Uvr0 zlqh`@JE3FYE4-+`&Q$SI2FuV$xoDd9aV_nkZ?GzMn$MY9O3nKl2Q%r#;zxfDbGmB5yWrtCPcIMTqh0jhZ4UB&f!_m(9%ll5PHn(&7|2b#RGff?loSY2p zvUK#G^PjdlIou&pXg8)!{d+G|P~C<3ONsnRML*7|Ug2#Ko02uMfkytw`gok^4Q^%tN}GFLZk zy3hrew6_Ob9YGYxM)HFnh7Ga)xnahKf`?~mk0p1Ld9&S~m6xkz17%Y9A8aD)Uf;WO zXGFJ2?=QX-EU%WTw_a3|wqAwir>PsqZ@KYaED18cu{~^Vj%s^5G_)yVoXm%Z5?(rY z4}!Bf8yxNEwJob)pl^!R(3DTX-7KHj{Xc!F_g)TIF?!GaFO0m*hdHVlwHkcbbJ_BJ zO+dBk9P_}<8u1wq17aEAR?Om*Mnf9OeLxqu~55cw~N7XTJZ>!LFG$ z&_{0pvlcK^;t)>uO!2&q%f?$2Kl7Q$FL(qB_Eo?5H|Lt>`UUy(E>eCDt=)aH{_kI= zPVJ%)JGXbLUw2dY)c9Uq{lQ@E=hDK6o#UKYv7RCUghY@v7%>mQ*($&L6VkXZUio`o zD2<0F33jlHzT`?E2xNuScN;cr_)<*mr;hkG-9*MD-D+SwkAkdP^sz|2oP-HY@KZR? zZD1{X0cPf|(v?|2@AvcH>3{ysuMTI?2SpR@O5BO69attZdL{ht&t&hEp1peAHk@*a>vGJqMEg=cL2}os>>0dI%mR* zQ;%?|&lxdhjH$99-QT;Cip?JFX~KP4>zGHd8E2Y5;8gEQA0WGJMA^v1yHth64^5}Z zh6gvDAb5cT_U4(gAfc{>`tqICo@8cdAv8gM0FqO){(eEeNH)q#%2cm)jGLv{*Xgs0 zJIuQ0=VdZJVU`;r(Pa%eM8E%|m}azc>t-p@Fhzk=yr*zkvbo=L5>w3}1+PtY^pZafb=NSXb2my^ZfToozJ%JW z0$z5{fNFBa>EbKa7r%3)8Uq7I-Z;g`==DfBw4njNzru(q3_NGNf7SmTcRz zZimbHa3^PoSN5t;pAKy9%BWOswCSK@n&~)Qe?sS^)vM#Z7i%WIx!(T^6U9|!t=DHo z(4!lC8WPBUN+ZWyYQBAG3orftkvxB2y+;qT+FI~`=}?1&6d0+cb;CJ5g(Nv!-ZRH` z)4ST@5t`%27dx~H0O`^2Uuj?)UovcF8=RF`elyHkY#0`j1Yp@Fa`MY}Jsi95w&(=6 zL@FOF6>p2-W&1ObXtpgb0a9@ejTD@^h)>`2c`;2pvx~8@=Ay*BTBuRz6K9i}_l8qt zt+|3Cs{&Z=fnV;lUa7~%E{L~4gAwrv;PKlVSbQ+5r|}-%vdJ6Kd@9{`zUUdzWGoq7 zO@1HZDBSvCaie`9nB)uM#JjfhOtAi@AA-gHh??G0H4h^Tk5`xgS%nL`aieg;zJ32U z=QVl1e?KdCi;J!X+hH$;83;ZFt|`-hS-0b#0|9$L;`-8nPP@ZZybbN^-q>+xB*GJi ztPm|ypUnTM3ad5SkM0Len#|i#z%o>XW-Ieo#;hs5>z2Jd(QkKqIK<{LNka zF7uvscxWsrMmC5=g`|sMYF) z>165gS=ZmLX6u}d4irn?mh%MuG&Gy{d4)hbv&>^h&1XZayP*{bTED)X19jn~@lpTI z^vsgKhYVEk(&~es|IYf3k4+Ksnc*N^&FNY5KZV2po1K@u>~>kN{B$YP$K{io;)!xP zm}c&k-6({K=@ibZ>G5=75uP|AJb3(Kb(^&C(JVrk=yM76qLh5_iGflrBBl_2)Su^d zr_O<(xW|MJa+@9+8ZTcwR@=eduom|yR=^wqK8bjv3P4~E;Q_*<^4)i<&FsEZsb8S` zj`M0Qb{FfRpsi!{vsaF*6&RyULoY((|SB8ao z3yESIs&zNkIgZl&ev}d-1>YTsa5UjEyYOU_`gk`@PGbs);^$$nq0BppM{N2+~oj<6ohLP zJwa7fl~d-zg~}5G4lzW3Y~nXno>NFW*BSLTK`YSj2}Vh9mu^am3^g z0J(G%>JfrcP_TIHyNA^y+h)fcN7AgBpmHkUC(B^9_<+&9h&JUo1CCGc@;>Ap zS=#wV5nGPZGe%?SB2|ZA1ESxfV1vYz1k4d+4D%~qO@9d_r&4* zXJ_j^{t9-_y!Q;Dx#>7mb)3vn1Gw;(Jc2^V!|*ltR_N2u7NR*45so`t)%SU4TYR>P8g-2&swr_;VALd>l&*4NxdQcG!L0FRk?8LP#3L72 zkmH2vQ{A3ElqV})KCw|$MvT?R(Uw&SbOrP~#Go?N(r%vYy#R)*Va_aH;11f8TJqiq z@8=K&aR?AZr7UU}hA#a@zar8q&dBhLkM5AiAroFQ6~bp#PWY;pd`svUT>UCw*Jm&? zJ76}{8q?nDRO*uP1l~js*7oECTgCrI&!?(3OU{~ zo-RULLa8Y$-UQ5}~)@VimH^ctpAxnCrUuYc?pS zS?cHrLoPu(yqk(X2uQy|8Xt0kFs(x>+hWx&4J z(u(@Qt}caDL$Na1B{@#))EFX>m0?14q`>qGJX@}P(WqG(D-uN&OYTh2{tAw(koAOR zndDtP`2ff|qh3GFjOAkAL;5tS<*g}w?`-aYKJB_wL!Z#o;1XTc4XlWtay10<1utg~ zYK|ar?06$0j}Gp11Gv>h8ceCdF_ddSwm^)^r>Z*6fd9|H;@Z8GQlAM{FKhszX$DYK zp5VkH6_J@oF&jY7FsqlX=MFjiqPWM$7uKfr7{4{#Jxj}LU2xZ^OP7<_fnzm53lK8mYAQ-+}yvBF(k^a>)M-;|d#v<-luVlW{OnM9fDRE=^{aeExMjyE$r~@A!x0g90U}rF;Zi4G zp}C#IQ#Qe>O;$SzbgVSe-cB(voT>3t3x{>kzo*cjh@4M!PMK!sQBYS4;))RUFwbtj zQXIKJL$yfaLXiGQm<9f|K(CC z#C@&qb0{xqHsux#UaqxkdKC7~Eo}X4xSO%;wkl<}J?l?4a&yW@^=pLbD3F2)#-irW z%SrHC_MEnJ&rjZbOEg6uFh?n|%NasI{4|9-^3FJgtgOthHvS|#Bv~`+Nr{wpY#M8z z=J%hz?T!ebBrYBcU$Zw-Ssl~L);^pZUVb~wR^PiWj-GUZkQ)>_0s$+OlbWB8fl#bL zXt8qav{_xp#k-Tq2Da_sQ%lgWS|w1VBgEB@3U1}9SE1wDivI_)pp0dZ0qU1_Yf_b` z{;~;ZGHe3!ygA}!V>m@g75(iN()i0x_Alw8UR7CB5+9}*OwICelJACq2AxB*!SAEm z#!Y9V@It8$7F_+O-LX?51E6v;mGcCy?f={#ZSDYtpy7ri2Zow07Y(oAYMbAa z0J+qAToN%sDErr^0@>o)ehOZiQX;^WDh%WEY#RGBsWu*^8&n1vHWZ*_?IO7tJa!+f-h@7V~>dpg<(G+DX ztzwMJ!wa(Jpe`T^lu)dA;$Od)G6|iFsDf)K7*bmRLxDRFQDWG**jML_q`9IyDf->{QY<6VI&1WoOq7EpExABsUs!)zF-eP|KDZxvC?v#sl#J6`PL zBuf&*jZg=>SvH!bG}%vk{2S(6v!p{VkmWm=tx37h_KV^V3zim^ z*Sm-3bL%=aG6w{Z&P?V*U^Uw7kKl<-U!nnQf)TyL&q3VJAjSm|!i*l$vEYMDzD^4CcvDD${(&sqSh; zFhZi5_c+x&{;L|*#vNwUgp8qBFXT4LGBeZJdeiV;nkRa1pH~Z|Twdc}R(ia+ygufJ zUc#-Yx0N2$D%Iq-B4HJ|{LVL&0~Ubx33*M`HXG70os77k_Vs(^^j>XF3R65#SOlHj z5nw7Ky={g1mMwYE(c%jn+>)TuJN>GPIE1GzH%JvN(}YiwPJmwusi#|@rOUO~*9@uh zfArhe6grbv&|9mwoHH_J@`E*j%&Cx}x2vi|;nnan(GT5D*rMJl2a;juRfF?Y5?0b# zUaJ(Xw9vCyQ6&oSYcnS<=JK%@rDrV9z2sAensvWeR0z%&!~f(ARU-0Lm7YB-9xU%Y zJ^uGATMEkSg|wn6=_ae9W#H!Ki!-G!jF=0uva(9uk_z50Z%c*%287N`loTm7lI&^| zrGL<)G9P>9=SXoCea0*+_~3bXfbU8l1vmkZ}@Ta02DvuG$F|p87gy zZa{CR@Hp8@wn8`gb%Z3scgc+542W+@CAY@+>6JEmfB?8ug( z#dmS|S`)l*ruO_>mzE{paEkZWq>RvDy_=kU$H2Egab;9>FFpzIx#zX;bZd%T*-EhJGrE4UW#FM6_IM1Bz5mRsKy( zZ87pHtEleVvr$&Sj4c_xNBBk8ZlXML^t1_3Qc_zdeBE$rMgDm^y&|rmB2PYsXxHCS zk)}3x3q#A*R>l4Vj1--fb#(7tQj922zz~@9NWT}_-*!lkAd0rK>knX@*X1(jaGb&? zc(sgix0#CgeLV2gQ(vrounvsB_OtOUnLO2?XU9qSA3I7dXO*K^dYvxpY25d^0 z*4~E0Ef0@A->k`xRRLlRR(88QN~yjG1GcT2qT7u>{rhl2JMW#7-}ruyrOxU5_wW17 zXJaiF00#yXODC1sIl7cG=H($r-kLWnnGlP+~a0!d0g#*5TqVkASfeXRV?x?jcUQ;Xj6}oifi~;cc|Hy zB$s&NHsL$AK12Q#)fo!GrP76sb$>R45lm^y#HEbgpUZ@(&{3}kX&ehRFLHUg^g4_G z`Hx&Tk)LwdRr>XNQKvUZ!4mac(H`)^Eu~luh%uAL&TXnCxFl$$ zDBO95pd=HOHz}zA+a%QvR`6MZtE~I-( z2%v!$`)5v4F=9&@E0!Z7)pWD8j)tU%TtW*t(jaN(saA6(Snz{yP||jr`1bG2vmmL_ zR8LhP)kS}P*MBS`PSM5~zO@TJO$JpDc$<5K-czGw&V>+kU1kp^wt0}6a!QYrSC!FaixE!Oq)V`DjUpjXKl=cr@ZpC8OhjO5v1|S=J9w7)6=>6JdQMx z8wNiGFar}AM7p1+vG&z(&HJI5YUp;%*jgOVP+wr+Fg)-1Atz)qEx6{W2&x4eF$|R{ zy88zolXf4wTzI+QkZ76W$Epe4E%#c$7kU1g3Wi6|J=rouB!dLHyw z4&-77hc?iFTPD2_?8CBJ1(4mJ(#nRz$53Yb`s+@5!L^ZE9*YW*ntW*1_+YfVfdYIE(tV)5 zutKL$lnL%z9uV54<<=fI&H{+bYfj&{N7+8Ls^p|hfXggwN?-zrC(ErLT?lOX_%X<* z3kZcNu5R=Oa}&l}t=P245#Uky4}mkm+AaN_GuWBX3X+l!87sa_)4`so%FUTVV&V7v z%GC_@vB}7qGWgB=`K5GjW#`Nx<_n~v{gi&<0>k)7Z|5>8(bKoHQx zqbnr4IUOrA8W6id+6PV~YI=vz$=??@P!=5GMVgn|#{>KRJE(-2*6D`4yW2inSFE2t zVA3R6-ugg&dX$XyBg-y#`YVd!x9NWCl;EvfO^F2CgU?tE1+}tF55h?1+wz`6j{o`D zG3M#0^5mn;{DF#_ggy5CPv_3DBmYJxA{CC|nC6H}i<3fPzlxhJ+hn&|n+;?MO__D# zK$xHcqUnO|H>}TV7=9+y+Sxq`%^F2!p)a`ooGOw~W^s1{;~Q7s(r3Jvfs7|FU%I}1 z`S77Qot7Y@yzf)g{$FNmj`ORmqyfgetmv@%>AO9;_&1j&>9U)PC=`&5PkP2C_AbJi z#!)mhrXikdFD6`fXr>-rA2ht}L#$tR49Mmj&2g5_4#WDQL!l1#0S%#EB115bzs^tn zBA*UlOyr|kOSD-G4=aFed$9meNvwp1g5Q8DU6f z>??P#{T^|{(K~7b{-c$1{3KXM$vsN=SWT`*q1mR}9gNJ885AYjCyI^>m+QjYpQ+Ta z5bHKsNkp4*vdP>%8$0*fJD{43dq#lWhqglne7S9>;ijV-&~!>r3LzHWR_FgO->>~q zVt(HN(MZ4EeGIZ5D#FQoLlqRm+9RiI-L~zN!er&jp|a>o{*y2xwZqJyxwTxnO&^2A zvGpjTStD%&HO}Y$pOu82H(g6U|Jn6Vw9mLbCm|#&@`NM*(C;7;j4(~}Xp^fCxLIq9 zHFrjGKlf-x6TCjmPWzVv-V)10B08Z9pnjJgxYBB7AhG9~*xec{Nt7(idJ*p0Z*w<| zab%}%%M?z4UB7+tSuoOWu3CA|Ad0P9*outVr`b@QpV^8o^3?;J2VPUAzO8wXo)!o@ zur?r|fxli^5@n)fxP3_#SPsTt^DrzR(LcWR#%aRmuaYq9!|c^VJ5P_8$8DM;dr4Y( zX$^SR-&(dZuCYS{8&NlXXiG}1XVo-9rNRuJ4i9fm#sY!}+Dhh=Kr`@2lO z)$%}g?gQF&NIwCWO_%Dwg7=W)J@zi;&Gw*$E9s^B$dC2Y8_U`z`A_j#_r7`+cMkph zWhvx9s4xt&k8V5f`h;v;P^H{`qz4v#8mV>WILAAOZZX+$qNsO*KJ0+|SLfS)%Ef>X zf8B~ZD2AZd?z@LOjO;Cv;OWEKXXQXw>j2cY34uQw1=&cb5)fa&Y;c|0nu>Oo z@$Q{71#Qj1bU5VwYu-iSiABGZ;%7Ey(5$^7k6*m7?RmQ`PF;M#E;>NIKHI1F>9R>J zQ(UOSbyrVSj&-=!#d7S+7>AT)FJ3Pny~pN$_p{%hvG*8Cb;r@ynZ)LxNpOMRGFf-5 z^h}?&FPJwH>9&l8?AIHUlLRVzP#hJeZ&xkgU#<+kge|*_W0LP1The{@tpD!)6XnT(L%cKCN5V9Qq<=L80 zYu#~F$~RGP8=tPaJAh_Zq%&)og02hSzdw$iqiML+i62rK3tAU}M@*@AD zRIeYlbMM|52z)JuS3|YUmA9AW!=rnWQ7IYY?$+99U}$k{&Or-I=E!RMUH71G(dIGm zuR};)@`tPL{K!AeB>L)Q0U22cFN*4bR38rqs+PG^sgxfG-!enV9;FEMz&xhHxcT|M z_7JjMZPxz`(sF)R2%0YV7Q{Zr)eAY^3~?}Iw?m3P<_0%z-W-coj(-;?Z6!u4uv$GnRV+ycV&Ehj zfzb(MMwc5$og=kuc1nhOGcb{Px}TT)b-tN=$pxnz52JzZ|L_fGtB5I~2$Y@zM{{Vx zV=UrhI9GfrQ{~B`K3k#q8A{Yt0B>0E4Y&=b5cDsetYMRuwd$GG3Lon7d}*NTiJe`^ zkm1ExuI_4e{a$X2P5Horz5C za;pVQD?}P;P$?WRUl^D(4^hoe8!jE<2q4>ADy+%6))w!|!CDHK4~EkcrN4vJWagfd_vtJ(vr;a zN-B{l8%~Gxi4x#&mXJ+dB35ZT(v}G}amCeSUHP-fQv5_KfUbk=lq~IrB9E4BU&KeI zGIePBU|nnIXDs0UJ>OQ%6Fv~cP%0m&EF)v z9J2z4xlD)Xb&q3c(gelTKvIh3^h)PPlnpOu1)Gegpf31Xeh!@jYvyH!gGn5FE~NaW zr6YTfU6H4WG}eeo5Hq@@hTy=DzbM}Xv_A4ph;bXrOd~?G6V&rrr(o7%7Z>#XLGj?h z12&<=*N@}H&b2A(-0M_Ez}J@JpKd9%KVwm06q;~6h`C37L$i1^fPM58c>;EGRqbW4 zgEOmbZ~suOayVGK@QaLZKt_$wat^N*%>-r|vd#btun z$`~3IyR}G3MCT^#48`*=Kn=s+Im;807O}OdIAedKY;BcwKGJ#%{`{dS>ew;i-eiJ9 zcs0O*`@Y%KJJOGVmGo#%!mLszTSO^FH=0^^q1mM}3G((h*G^`dgkBXzgz_!@aupSe zJol8fAKb;LF55%ykZ?!18~j02RlR>|8d$x&M!|nzjh@{9?Twg*KxD_D=~zXl#5=kJ zT6YSGo6rDY_(lBVmX@_{FVbq-AEj-3psm1gCbj0?w>bc~M`ij93Kd&C4=*vlULTtW zi!wkVZg)WoXDUI~?F8zUa{}8~zu$TE9zljCY#60g+pU;7DJ(HDQHa8DJ&$X?D^W$F zO=_Yec@sBk9D-LakIYXO5<=b>6lHvB7$IyuPi#aGvj9Es~8PdN6Y2$siFcbf5SRc;X0%BHv|%;m8Wd zV)l$T2k%|{&}HAQ7h&_!{a$mV-)n;wCi;9<6Ms|A12 z<5^5+ETWZNl+Rwgxb4#B`uONaq)T$ZVBDAg9a4IHUWBFsENl4z2l+KY&$5P#?&_T9 zjzXtGqKalq`dNS4G;{h7ab9^p0)HHO-4IRs-cfVHdDTN+`F_>0qyCrr^!2n=LK!hR zkexZaD`pcnzU!^0JM}7xTiutX)HRwi-S?r0iTR6sV-lHWOUt@vMZkojf`~yo5ahu| zT90!ME+5jR9|n#_?wKsxJ#-&VR$%3&J#XLRl7w?^E9v^DD~Ri=pX+^}KRG?Jw}nxV zhj$E>B#S``y{X+dGq>8lHfYNhr6wnCL}u$51Zfv9UR>Mi@yGn2X%CZ!4BcG<@+9_a zIzUz6&dltZ38nxH)jlUxt;354)sFkhJ~di8W!B5E%7x0WzWS<%bC~gFG&V*i2XX^C z==r{hBm`gq_Nsl@;Ttd0(WocCO6hMnj>3^f8G`M*P^I-UAWLN=r@KP$G{Z*}MniI1 zKyoC3bGoSx1}mhxBT*3x9=Gpx9nAcp@u^97DK@Zc`q3O{h<0Gk0XJ7#$&Cl>cbR?# zUOPe|6P)#3FX2kn8hU(E=1=dzK+G>>h7GemN@p}G^JqJ+@!wY$=kmJVOdOQaIF7QK zaWxBcPW6-M705|4hqoi7wKmJs_?d;vzTvEUkF3 zf}eLnF#Bz)ZS76BF*q72P~`4>^St{927Rbnrcp8OA`Kics1&t7O|IVW%;ENs4h-#= z>X>CSgUaWqr453~NcSyLu|DOvLzVz=#47JTJ~@)ohCN-m%&H*Byrh;yozW*;z!Mu$+tvm23H@DD>ZY*KDhlaZl!J-thS zl+1;S)(%)8u6|vnKVKxL!Z$&NY#|?c%sQ3sI!gId;-V%%k4MOvl9d8VYDS~e%5LtL zSWbK2Ieb~?98;HkZ8Y^xoJe%gZaz2)O4(CG<6z#(wtoJR_MKSE5t8o&oDwNi zsY}`{j-R-jyd`moD&%vzaXsLX#xg5`Oku#poUuzs@q!f?FPe-Y_GRuN8hY-LLIWUm z5UfPBM^g4m{)8P5#S}&Cz>aLJ0k-~GgumIR#}mXvnj;k_y$99Z@Ekp=wlwh_Yh_wh zgl9ri$Cys+M~wMWBw&EpdB6^XM~oQ3Ei+PzFipn(^=XciYM2phv+A~{@Cw(se3xLYddM@k__u4SXZsR;EW{MO*dLs{8d*)L~f`SuR(9FDGJK< zBLwKCMjbro&%#K>_wndnWFswLP6K70ZcZPLme@WSr54rhqmUEt7g%yz0nW)*|H=Ge zLz&B*!RLCvIz#^MP2H$6Si#Z)G0l9J$~UFW+NDWv*Yj+L`jmly)pYAhqfdJeKlgRY z_@g<(^>V-55s!lsLf54-L&tp2Tp6P4*B=uS%HDLbV^p1x~>1)S-bv@hV%+19~GqO58R!bXZB=TD(I{H(G zp!m_&)jfMX6Y^f)@9lAIi@4*Y{KYA$r$A>Ab4fJ|t})<23wu-N0|%Oc-kH2V)KI6go>k}(ZyOR25&iXh0vkgYZ&beO!Zsm2 zmTJmQu<$d2k6pCY95A4Im$l1km!7YPdoch4;Qun6xqs`UZvFHNt`6&vH9hqY2TXZJ zGhqYnaL?F(R10bH7g`RY`m&r0BQ+k)md8g3zFByiAKPxd0U%EUwlY=X*(Ks8oA;A+ zbPDx4ywqrYzZs#QR7|KHwmKSNF{M*KgjKaEXVte#i;T>x+{8$PPDd z!Y10Z5XanT-U{PzKaOe+5Mra4w*HCr{`w5SRoT%qIwpG~%7KJ^F6R`=w=|Ca_k#w@ zgKT$JemmcJSL=gwE;Yys-hC?0_*Cw2rt*HDv7AuQ>T#TuECcA25UTM7#wLjl*=ttM zkN1t8y!QQxq_YjrTqb87q*e#h|NPe5t@mHJVCfgg`;4B}#DSbbxLk6-|LAFXMj$)zvI+}j><0lJ#UnB6e-wy^;; zr$lh|XBsT8Q*7xv5DK)HD{(A{(gYNOx51 z#b@k5ua}^b(-ndr#p^bFMuC4wbpIw37lmE1+UNHmc+rFY_(4Hu=}x&nN@2Ljf7nfe3)Ae- zv13eN>!%LGpCbDx?keZff{vTumy}gzt_ z{9EI+vO1d)yS+9ft~-Cw&9LtNh<>YvJlD6gDE7IWv{qKT3{*&!KH!HR4qbGo$V>*= zD{aF0G_39IW7(TI z3BH4V;5gOo1Dx-h*H9qoxr9B_`K6@3VBI&3h7bRkaK!#{KieTKqiF(7ihBK{ba~RD zac8sfg-dg*dVh7u=B#rDqgE$NRN`!uEfF;flBSa_*@J$GkONF+4uz_QZP33K+404kd;qD=-gqlkJ|1`*Lpn(nY^ z*E);_DK0cq4QF!`!zmVhhkh1U zJ4K95>Pfb%Y_;`%_s2Ot%ji8u157>rA@-X9!88@34`w6TR_i~W8>HA@Zpj`0IH)Sj z&9K-ggXu@g@*S5J{#|m*G9}jbotE>mt1c#|I%kaS-DR}uc6_cjF6**1rKG>* zeH(7X{)rK~hfVkvwo0b$63-gSAV7u40q)FmFv;HG0NBexbgk$*{{F(QUAv}0ke12Z zxnDnh&+8}Ped{r2ku85`LX=&G1#jCnbj|K%eCUslujKB!sn_U)=t%ZQrJd_D_>V#F zjBWZ=?Q=7HYkW4s`LMlemyVUa%)LKc-ZEroOUtu~8`Ip1*Ht^tSycS$p8`pSeQ*U-A3vXEQ+3A+WiZ;6ci`fEOYiQ>M)m2zucMYLZ^Va08Z;UPh#4B z{Mqj=C)rRb?q6iO`tm5lJ?v_|xo#$Qt!g?J$7c6yZd=R|abDa4x$83(QZJ9v(P=P> z{YOQ2^^-ru9~_-G-Q(GFdTl@PQTLjYj)kj@FYwXIqC$5JN z>pU98X+6O0(vV2LdjcHy!u;?@76Bss(GIvfvWiEhe$#;xn|eI+VGZ`LCWC5Eo>Stc6cZ}q1zt|i0>J3 zk8UhWmR9J^j!06@JsBi4vF`gEAUAu5>+?0z-Az%VBWptpF*imJmv!cr=$+8`n+F@b z`IE}gGOl6w7mu9E^HzjXOTPN1Teog&vCZ0&KClS%?|HwTJ$t@qHs^hac^ZE@OiKZ- ztV1t)^kV#fk?zygN)KUd^62DLLCp5aA}^lD!N5rox<3{X*#j7!>NOH)-i*sx;%TNS z;A{*HOvE*L&fOY$+0n=cQBTeI?b7Zmlqe!Sf>aa}fhhD>NMpCmDu;Iw+xyQ_bJCW`2hqG2@Q}1n~ z2zsN8M0RIIy|vX;hX=6Uxq!TOgjPtVQnQ;$#)(L⩔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 p=8L~sxEFu<>ivK6RouiB{>l%hg^P_u9 z=lWjP{r&y!$Nl*J?tkv{(RF=_^L(H0_v>|x=W#rr$LpcCrV>6b1uhDO!dFq2*F~W) z6i_Jii#S;DlkvUfF8Gg_r-HGkp4$!2JJud{C=F}RTP|*%E{-;|-gX|h9o<~{xp;ZG z_&8`CJUwsS7USl=`LB0yxp~-g2gj_zMNnsNDVy9zp)S8i{)gt;=Ou|kL!ngUW%PYA zSH9ixNt!*vTHpV;@I7jvkkdc;Oc$3LrU|`VK`{=M&c+W}X>F=tTEC&}kZ|DL#y**rRTAjG!btF7- zcTiMP(z3XVxANUlU17lfj+pgTkjL66{j9=c4gYGakM%L#JD~O({oQ)tSz3 zZ+jmL-$PAPlMF+q>6PQ4fP~)`5lzs6`^aOTMncKo(l_O*R%7Mm)gc#l z&0KyW;jc~+c<_5VE;97oMVt2z&bHD7Y$b^C@H|(Q>5h{+Di-ltB2jvHV!%2DTa^W)*f&1l~UtV0_ zypK&d=kMod>SdXwX;02HP-Uk^{jaMkkVk5l`>$4ym?tJBg#T zx0@P4#5BHI|etEs&n>xcL`{6sO_l``C8K z8_u9454Q$$tM1{TdeV61_&sKnIZ+%|jpHhL{rY}zBjK3m$VAYk%M8msFI-Xo!E)-c zr9^VXr3REjF4||LzrMcdMJRRf{B`}|6aO_)eALX$%#R<<_cu3p2F>0DHcYLIzT`vs zESJ9#Q9a!~wpOiJ{jv)4T6%N{6FA@Qp<{R9i!&~2mL*1AT+*9XZ(Osc-iMy!L#6TC z$3D)JKiS+4ii0iX>eA=WwNWT5E33}2S#JpmiSBP&BntJvK8b%q^%ZJReyeex?#fD( zJMk^s=p3Bk($D2)%Tv?Sv*1ndY61`UWX*ba4|gZnxh(5h(1UAl&zH__s(y_-ja+Eg z@(fDtR>!I@p#7NXOcJv4Z_AS(dwsLaf8|B{zaITroAv{zVenS=GfenKLHnz3Ji@)q zYi`NB_FUARO<_0cT4k>CUNsGER66^w-8bMR$uT9pa3OE?+uJudDDczzvcRRN7Y7G- zukNqBm%Eu#n*6Pns6o1}c=$E_mqv_tk#&r8zOXLw;M)``W_B(9(>Nnabm)?B$Q`7U#Ff40*`?GNw z1C@}FQ1{|`yGQbA^ftHz;o~iYgvBdbPt5U98jl6~O~#7UQn~7`x5v(MTht9LzL^Nr z`_R_bC9eY;^PmELH+7D?{;A`)y6V;fA9xERCC{~Bk!wvT_mwX%TKO*7-#g7C!)GB0 ziPYhorkLEZHmesG{%1MHJTbNO+0&<}BCYI4Xwur+@%h^8e5c9iF|2k%Vj?|Cx=1t2 z)7910Ta;Vsqi#x;WPm9dHTCM+g4l@_7H^39tX~VPv~2L)5xH2@`(ODkfqwY8bqiLj z8h67?Vs1<+xJ6mQjVPs;kS# zU6w!@A6sf~3l1$uI9;q{sKT~XvL&pK?J%ql>$tqs?PYr~+Wniqq?|lbUq(lVxKJy5 zt?KR{kAjV9iWtS`J5-5`bllvPo75WVS;tGmWqi)P43W9R+a@hF;mU)B8acWp&q~lf zefoq7Zfa^0+rRjc>~rHq{eYNBvE0Y)%MmO0Mu8ir_e5glXRx-&Evs={r z=m>eQ7?1GVf9gP8TjAo-g|txVSX2P;fUR zCbuO^FG)&D8ozQ#$m^#4YX!MZ*H~;Ybx0CYQ^UzPO_d=q1<1fMdz#+-I7j66?(j7~ z>cN8t%6f(BtXyv`Xd)kkhkq@Y<~p^;>n++JRIj&1_d`Ie>ihcjZCkeU{?00r5W3KQ zRF7Edy{$>x(IZtLe&aSlhLSGO9W8H;wlp_450y|pfBrn)R|TC1EQXTeN+b#ko?+e&*-59{B=Q+W0RmIj|vNPi6bgXLD9ysVsDfqizsXP0DUSk|Zg$sFFW zX4bn2^l*cQ!tG!~!UD4;5t>DE-rlb#oztILKf}t(%#5~f2}47@zWFr_@{veQZdiGa zrIP+zAL;~&buoH>xQEwMg7Q1sRuv3~xS~6T!d5pKUVAMY{+VnEpGEQb@3_3wp-0{N zHK)4?e9dzWY zBP}hRQuqa8ieb%XH(A*bB5+cIXWYwe$y61X>@nQ z;?Ck3bo7G3BCT#A*YV=$xVVc#H^=go)zof-sk+G+Ea4Cmz7dm8=$chi|cL$qMHWQ5jn~-AIvVG^$J$jW+a@`R5iy~#} zjbFV|?6K71SI|`zABu>Iro62zDq^nlUM=*N#L*mA_!Utc*UO+knR>QGfkwctFb>8rlcatFR!w{Od-t4IHrWB*8wr0@g_rK6*h zVR3t_S=17Oc`g&}DL}4)BrQqDVU~oxKJCSWKfkJCD23So%U<8`_nGUtG&)x8oPy$k zkYApfYUE{k=G58mw`%8yaah(HkLD_ziv#cz_p@>T(^Lf2wD$Bz;fYsH$vCKJO5utx z!^5PxEH=N_Ctbz8S9sDXUm~6pPbW7X85K1N4$P$X=y|sje!o+7jn^`n>$f`0a>L~X zkJDs>OCC8C4_hs!l88}m7{&eWVBp#9(hZh`N1SF$S2QynytorOI+VX{&UT+eqXj1+ z2h%Zga41%S1T_v&T;lTGb(uFGKFG`DXkXiyYE^(G;xT)jy28N9de++78t2?OIbGev zyzY~#ESK#Xi>}<>&MG^_H(^TI=YfZQp@9l%h`Hox>z6lle|UJI`s*7v?uf|90W@jw zyIa54t$h_0!%(O!F|PsYt3Eu(>Gn@vm`xj=X4bF=F@|L-SCXt@w=hwC^}e@XUMcK; z#K~R=#W5HOix1V|bsclQ`1t{(=vDGXNC$yAoHgJ{??)fBYC1g@cFpz?|#OI(xCjKed1H_x?ps$Wm`gSsicO zD1@g-fAi)}C|dXt84*$6LI}RuKMR~I#vmm%u~e9ETK>6zc1*rR-xN>yv4Eq^PFh)6 zSzhzR7zD73_fjd=%6!4(hm#_u;Gbs-BW_pi#%9doK^w8%KiKICx8CzEzDQU zpR#$H&_IJ&2W@IMF$IOwNf^{OV^om^n4_z!du#GNCT!0mO7Hyqd}$H|WRsYgi&XtR z7nHnfYHCF4#>M0k86!+g`d|~))$R*%;*SjtNDU4=)zDDT(LJ9Zjb}SIoAa$CiHM?q ziw2%r$YJaiE5w{rD=S9pcMvcXX5wUFx#d|aM2yi4OAH{%Mb$~($k>78R=#Y;W89mj zrYh^!RDStI=&h}W*+v-&Xm&|g1dV;ay+5_c;xLuO2hq`ZWsegJi;BeS%rks_eDufS z1w=&1@7=qHTyDTA=VJ#CO}`F>kHerY-6P4u?%v7-idQ9$|FQ-TOiWBeK_(4NO+|Oo zKr^e0tR%Q{db$C>bkuT`lCEfcNgj$|ROy?YO~Ifn$P?>2C-ET~uc^6g3ob~#cTgRZ zgOh+?RaN!;TJsrH6SQL{r>CdCgdpzJal^Fy_4^~LHU@Ez+qbDLDn1?@9CT$>!Djyc z^GD`Nk|?s4Mg8Mvg*h~?^FDr0Ujhbw5kv~LEwI6g~`TF`A*Zc6IuC+y9I(eccmcM>>b9gIdXh>g~;OJ&UIbc7w z)&3ls;mMVil?9JwJe8vRX;~IxrFofc`HoBslAd0+DVLjDF(aE2gXM^cu95g*Buie# zQMiU82@E94%GBnSXJccNh3pzi7k*S&!SJrUl1qk& zh=j!Uyn~OgFC7mL5tJl~%E}Se8L6okGZHEKNlDUT7nC(P&kA=%FZ2}_p_`WHbz|D{ zu^b%I5pv)QpHY`14ERAui1VQ5Tg-$r<*75~iYbsNX_Kh4yK?3p@f$4TEX=MoNZ@3BG<5p_KP- z=$UNY3m+f3D9f-PD2995SuAx2zZ4dJGZ2qtEml@mB8C2brmx`khF`K*N!YHWnln?KBAP8vE5i3`?OoXElEM?2Z1@7GS zPaku*u-~Dlurm1$doirl8wF|UU;_=6fPeteg60U#av?WWCu{j|@Tf$^=>-ool!uiS z%b5eUq7w4$d7dZl&l922-w3}O*(N`|WxgDUb5EYQ8uUTAJc%sH+?=7SobGfx#Nns< zY}6L6S#*n@MEv|3hL*(LLQI_alAfZXe~qlB?y(S)AVt4*T3fE0YS&M%yrvV!Do*I= z?taO(pscAG_L@r|mw28Q(9+S-CsNvt&_1$2n=P^Grr$_B3}e(fkg0kOrH)O%)r9&y`0 zdwYfy9vIlzEfW(oeCa*?v!5@uX{dBtIKB4N57$>J6z#09S3iTFRKl@GOubi_e_*L6 z2p!(4d+**!3Audv@i%)F--Eag3GO-Ext7))h%C3=-Aj0W@VKfj{bTouPrE;cCMVnr zSr&fU&13qINpP5Py_jD^B`Y&p(UY8;pVDEVNbWKb>e9Or{d1?$efZ4b5<7e=loK_6 z+iVac)i1lT3>w+;T?z>aQ9l#2DND%B%`H?aU^9@cKM_6TBOe}Gm`||!g$}1cLnTJh z%h=ROh=+(zODnLlL=zSSffTmr0oeor#R-|2E*=xVdm(#6bmw<-`>9Hqh=q;KKue4A zs2xX^A_ca|^kG4T08>hBtqFO*yFFvLwIG?4l@L>bUlbC4;FfrthFDNdP*g-eeM*aM zgnYwJe0H{#V@J-r38F{oUf$jnp6Mos4um^8{C1y{hlpHhk~B=J*>Anw-M3*mFJLs=?x`va8rL^8Aoi+nj(1ZrY%wC% zL(yxfq%a>Y)G(O*D&IbQyggh)*o5EJ%8S}^prtZcm%R&C;;4A2GQu2*uQ>z z;BKXr$hhF)(X}-X8&c3Hz$CadO85H@of#4BuWh+_w&cN-h|+ttyLdY2ekD zpr_=={iO?lz|tZ+v{7QY-e~f|Cn*|qUbB`6Rp;}T=$k{RTOCW*CsCLwDEw@*A&*aZ z;x(o26fuSGvGUu3ZrIy{{k_#@A#oVUKZ|5`9D(ScNB}9(r)OcofyVk&F#*G@Fp~!7 z1)rhj!u+E`>+qvSx~*$^y18PZ$D4m|s+#2DMFu9Og7?94K~ntugUC~wa|v%PvQT;? z9}bR;T&%+N6OQ;Bo;HOa1zs-HP|?#Uda;m^2q`opL1+GPBHI~swW1f#Sy^@DPV*by z7R^vhOU$O>HI={S>`j-{NkmTWfFsZqM%lJ|czDQ;Cq}`bQWh#Ck>|OmD&eTQw0d(a ze#i$mDlpwdh>6LGf;K0uDXMbB8S(Hm_tO1^-48o`Mu^&iox zocFA)y^$x1hp!S75-avKdB}$?sy~9jR~lve=@Ue;h$S2(nInZ=J57)af*k(h22D-P z0=n?P2jPZgUY?zZnZRb#(gw?fV~WMJy<#otWaB7kR$9FNH>mxDZL)Ow{GRV+WN@%* zM&fy56L{bbtS3*Nr14%4LyD_?&w#=&B<0Q2n z(<>0xURJ47CKeXRWF$Hb#Or4~!m^-tv~hM&rMO1rJ$_c?OLB6A)45|Kr06KMhTzV? z(8BS9=g2hZU5)_b_OD+MIK(>Vi?KlDgzErKXwEQaZ*1{ zwk>x1{(y@Coi#L+XmH@?)|#@${R;5r?M*)Fk2;@7$Y85-_aYES29Ol`{5cOw`tIGU z`fMrp+2PLpS-7|qJv{haCmLfOJiutlZrgQ(zpjY!kv5ZEzI^#gPFTVIUPe?@6eOpl zt+L99#l3zirpd50%dwXX4O`2v1R`e5`4y_oUq02R5M&fw9kHeg1WO|p^!%H~i~Hjl z&n_-5&W2I#wL>{UH+*kcptQTSwP4_>+;Ne35w9`hL;4TV(TUOwg7{-wuLdls7Ypc= zO08|f(^3qW4Xw~nwb|LTVfk$m;*iCE?cC7QrKkV9Y`IwU(EJ;ZZ~5I=$n~sP2OnY@ z$2bf3%KGN!6t?BLXv#!Gu~G*93&jj-7tU2w*eTvNc2K4uM}=n7AhuyZ{MTnoe?gUj zECAro7EVdY?=#Ee^{LE zCo5}-rG~u@3(1u{(XMk>LjX!IEiD0vqEbPUi6939J4FoOm}MIFLoo*QVs;G|JHC#? zLsdoBR`IhK=>!EyQQ*ZmwdQL-E=tP}{XSVH3Ik(f2Qu&E z!@A<)*I(5QBX`t2DI~5 zIK{=k&Nzd2N;+}|1s$ul#6ErcRFT>xxxmWiwQXq1fHuRhCu4Y{QFsNxwnu`m|HP$! zVj`4p@NrfNM_ZoQ!QIjLSictgl?84_@!{y(K8-Amm zcNNOPoSYnJlPMV+lYxltML=0-7U4^3(2Yl0KE>p;KElW zB&aP4TNYB&hYORVXP^RmBwm_Vp>9Q-nYpHJ#Otc+G@h5Y<<7+mH|~Xr+<5V@6O~jT z6{@d5?V`x9Hzx94j~P`udV7_0b2(VWsV^Qz*F>tPax?WNOh$d|X1&;Wq=mGgQgyg}e0-YP+l$6C zkTU#ha?G=b1=b}d)7KaTt8D>LkJvzwu7-JSC|*D49G*b*O#E_7TvKo&(+x^%9|d{& z7---@d>2$uQ0VydN$4EgGkjs#7OKPZOZZw^T5{5p%gfFNY1Z|G{$9!Pkx@BNrn-CZ zy()M!HA6ze%#^df!?`fPAD@z-!~EzmX=aw%d;EYn0=Wfu=Jmet%Pz{Qkmo|AHi(E5 z47SRiBwute{#9gJ?!DDrEF9f(&iOvxVF19tFp3liqjmN5P(EB5n;~(~OVr6s*7uMS zz0?Y&#NZ%xBW~1qZ>1+=dsb7+Z)e{{Wy18g;zgA}?HCXT+0fHFVL%ZsBXdthTF3i? zq5^}6h-#jbEnm7x{N(EDO(Cbe`o5OI!3zi~Wdn{v7U3zX~EF;Q)1vL$Yx&BslcsO>@ zbvLC%O>7HSt`E;RBGS|QAaM5d@ZGi8v=5_O?<*`o%Uy^!#S|7EOrW}VuVBz9+EwOW zyubfTBWwU<)KD}OKjt#82_7ypqHMH$ehtm^wadc<2Blok9}ql?UIG^AI+?aHE+^aA zCr4(3>9sXi_q~U2-U!#%*F%Xixw!ZcQXUk<>7f+Rr^6F;ipU?7>G{Nk&)0=PX~Gob z1l7wMJ9}}CnLh9Zmd673a#b#{WC+~5%0i^Dwif?qzvYdcng}MQ3^6%IEq3Z-==hgv z0rWADad+np{4;y;?%rb2Z)jO-ye>C>apo_XfJe@QFr!x$W2r_L46ivjK%=z$?>#b^ zm$msUsrWW4D#R_y#cd+EZAc#_4PmFKL_di!5_$nLJ)QT`vd}Fwg+64OM~II0&CVoa zP}<#(>rsyo6%E#$U)a>>XlTH{7oMv^lq{q16o>1q)K*+~z+LqazUk$Tm=OfbHb8e5 zck|b;7dEZ91*YX>sB2BZXk%;N-WvU3ehUq6{q&3sv&BkU1Gbd4hs;lkZ`JR&MqRX< z2-wY<>Plg+0zg7{^{TlrK>t;QBkmI67^#4!^8N73emFqzmU&J{9Uoi>I{L$m>|&KJt!_%+89|qd69k;If3AQIjPI_|cz#mPDpt_x}AH9Ndte*4F+M0K^IR zc}QLi0Gnn9#H?3?=VBtA{Nzsy>ti9Uo0S7IemKdH;Utx7M@mmd1d69-9w1Ns39(QfNzN)$7Ys zBJ}a|qZbyws1!@p45U;T5YJ~&6&7#9Bm?$-j93LJ93LH08w57w8I~m^C0+OK?&^Zr zd>iblyrf6}5ps)cE@P`eLSe>hadBLmi#00rfLi zNXf+ZnDo)R6Ae1(fcH3x;>5odddun(M>=!etW7Et(^Ga{d?IcYefa&fMDypgAS$1 zf{DZs!Cpe5K8$=_dt0T~lzGv!Z+6}+Glc#|YV4=J3GNF{P@~b3UKEEF$oR43$NXiz zADi0lSO8b@ZUEhFFE7tgs2PWXc}Pm65jHE1=I7pYoD>`Csl{9GIxWdR`3^wQx(-Xs zgBjvy=Yn~f0hwko`(XB~+dG2=^n|oD@>L-4%@#cD7b^W02MW4M^or^bI%sFJoBQoE z%ZA6($7LloHROa6Tk&*6Kwq%{M}~7}caw6l)0Pj9LI9`1f42>)i01+O-oSld+gj+a z0c}Ip9%mg(Bs4YTplhum9FW#Ltl=^ZMo(s`&(!yWuOgPgv6|@i?VN51IvgHq=&=KV zl(lzcV-lMfb~T?TWSGtSx#*_@a!kEAV`&+ma%A$|fuLntQZn;3Qp*#xYv-SV?m&nn zQM!n`%|=mnplLFg+hC>k^wbnyJ5Xo(zD`j<2i5!jGDkgyz*q;-@}iiyc*e@FsVHu; zZ1FpqjX;{ug21BaE>KVcz#do5OSy};0U2YnKnJdos)|m0eQj;+ON~3j-tNyh*x6~I zD}HZ}SRp*LQ5hKsaA0Slsgg0ixw)ynW=3fPsXZ5U;xjb4tzGIX zRz2o`8`)nR?Q%{gY(sK%v`;Yjomxngno8Q_DrS1oNY=jP_jrH5@3lWyF>8zF;h&a7 z<`$ywA*DntF@@2mKrg7N%B~xiSu?X3^V)!3uGkiX0&oF9JebdF&wvOmaiZQ=4;2iq zX{aYe)bIaiHdU}YmD^~^Q|y_=Tf~^FEhmuQ8vpifBm4LOTP_R_4b_t+ei7=hD>90ZFHQQa7u~VQ1>0L%T8Pxi zwWN5DOpl8Ky^^YO!X(bb>}}o2;{rslD|4@3AURQooT*tsvr{=Xlgi=cOYP)G1gzYI zzM-Lgi$ysC!H#BpgoG{1_~~>VeL*(`5?kJS`Y(>~adUTMW|nSeo>whI2?}j*9}AY6 z{fl2s1%B1(I*TqE5J!u^8h5&lRAhY%9D|B12~8T{w0_pG^=sE}r6|?Mbdoz?uFrll zaDcZU`jk4@#cm+Q3yW1*AaqVzuQoy@#=@P(TL_)KtgM^A>k5t9RMgb?&k8`9#DC%K zou63R1NvT|q!||L1$|MRVY%rf=qeBvv`{_WM$f`C+?*>Mx~%{f@rA*!0bh|{7XI$) zm=PWxuA!$1D8tb3(V?Y21M@@r{DZs7wYBjczi1(=b#)8=F-^@d+cRF=86Q~n^)+}B zraOQ1HMNu0$47d7i=);T`A_hV$+PJjBLMlXcHLcFBghGHHZk(!XHW_5f7ff9HFgvf zLviKbh;LObt;&R7d?Y1^TJUZ_!0O&|<(DtUXsGw^-+Q8N{n@me1nzYcid&fiU6tK$ zcd}pSwiVld%1l6$M#$Hc7=cS7x1R3&-k8?B$Ymb?F!xKbAHJ=fo%Ns1S#R+P$OWZ` zarc3ngTL|Io>PB2tTzE|{3Keih?c3ts<=g^rJA>I83Y89HDs1qN@~8aeCQCwxG47W zX^X%R$vlb)x;((_KjsM0b8P5RcYTLsoJk^-z89P;MonFt|SR=5m8 zf;od%5i`Lj3ie6+#2e$n@G>K zF~%<_KHBvr8y{DH9QOENaq(>Wo%Bkzedmt)dfJyZTU5hA)rZFvg6jV!=E=8e9sUk% zFkb+R$3h>Ss5W0DEkKN}jt(M7n%d^((TKzk`VST_{CC&R04NM~g(?CQ1r0Qn0<>`V zTe^Ok206+;J~hJ)nlm1?!$FQ)44kDN>RzM!2higokZ7#S%*y(xXp!8Ik`<`?{o}W9 z4VribWgnM|C6heG7B)9kjifrc&T`@5%iUh%*e|srW)f~+2;|a^OZCM>;k?f>(1!c} zwfSJY`)xjJaS=1_EMLdftCQ3ex>Y34j2wgTe9W|;r14vBco}R z)?x(yojW+Q4}3twf;0<$Tp~BNKb~d=L;-pz?R+xU6xC-YFWaVSJkr&}0P8FPMEDu9 zu;8!@aI5oZf8tn73!`mGk-(tffZj>T@RhQhG>%`UGR1UrGty2dc7n4N6?e{@`#C@h ztsJ_c^|0~om!5XX1H{x^TnBGuAPdS1M&>9}=jpS(uvS>>7_*T<_JfP?`H!27j)K-&y+rIUEgzj!hu{ z)_c{ZG^$kdyuz}46|7~%VnzCafermD!q773<7w&YI69H zQLbKPGoMRAvT3m-qz~-QS%d>o8>zIqI5jn;E}KwX%<>Jix3lxT*{Nm2P9jFnyP6+f zV4gui$w((!+HW7Qd+@7w8q5>})$>Gx63~wj$2sR15PH@h?JZxK&s*wu6zIEQD;pgX znw%PbH65>`n?3!p4T)1)>}M87#!_cgcRlW>Pla$(>`&9;KF0$;R`|4|p`Ny;*8OkX zBTm4?Q*hFO5cbr{>qbXBovE9fThu-&85zBh(B~;O{=>cP!3yISvgs))p#aR;E(iX$ zj=C)mqHsh>g^5BG&98kKW4?4GF!X~;85G#~?Qt~i2uyRIRxArifZGLf2n&9Qje6xW zdLAtE*<#`{VuuJ>IK&hEnTL??gZbdFPClR7t9mGdw=jL$ZB=?HhQO}HD2D zS>j0er#wRp>*Dgg{!*l0V^2~3U<${oDaDQPOF25LcFvz=r!*&Nf6e9TUlfz*7^W+e4IVf~Iew5Y7v2itCaGJ4A<@)3F%>m!S(z3y7 z&OnA484!(tWzE_Oeuu-Q!ysP^SNqFdX8a?jv< zjk}!BeAb;zxA7V}|Ct1(R}~c%1E9BKI{q1Sy!_0tEC=F~%V-t78D;GGK4d##E6FAN z1R#)Np%8lS#-~h^Nq0`O*B|CSW~@>~C?{f+6c-D8>EOClYwh?dtg$_;bBRSX9{1d; zVN^>^jr8O+er@U-m11il(>#ISEN2B(-xD;CrXLC-o*jXMk|TcG-P0p*cMOs!&;E=c z)Uc~ltx-{+WAjRde%M`5%-vWvz{12-0YJ#PG*X%EuqX;jf#;w)3jv*K-|F7>@_VR- zlbt_5)1mkY9h6v7c0-cykfg|ZWehHolESC~7gF!GLg^6+1y{=3pH27BHy}D({}4e8 znvxq|nX(A#4i3cg1UWc=In|4)rW@!j9r9I%Tr-{<=4tpK(y_i(RU?Lb^=iD1K88$* z{IzROKeFQi@&W8K-KL!%3BW%8dxL+JbmwM`-5YL`s{C7fTZ>%KaUaMOy*)H%kBACQ z5BlT?9Vh&MO3XvsK`C%?fGORyToHtRYxl4(_kp(slKAvdt?^|KQ0MyFMdlJ3Pies+?NSK+w9@e*vy>5Med~`9j!m9la80@hb z($8_6Q5N{M+a6Cq-8V3Ju5NiG;q?#dmrPb_jjj;j*hmz+>mOlVYTM=H{9`J1Q(N8v&66aardB_e><2}GeeQ0l&E6_DD zFmqZAA06An586w$qMtpMEbX6HX5(rLUS=9}JaXwj-%O?$I%EfRl<1(Qs-Y6^_%4?t z2^+sDwZbj0@@+?!*qYel$;p3{+(wQNjmibo#{P@^adC061OR1$g?1}nnfjVDd9INW zw`HjbA7R`8F-4n%0LpKhq z*mHfp*Se*Jy+4gG5v8Hog~gMJoshyk$?l}%9dGVpW1u~`ckkBbueXCnc`n7)S2bGq zKSi?e-4U(``%6{J&!9lT-@KW<#dyFhVKUu2nAetl@9#H8IE4f}Hi3Nf_chFastbXX zh74HQ-Uk^QbbsLw5P4#CQ~+Y&ReELzA@5ltYXbIPPfDJCmWGP;9x(F~z?N^I$%x>Yibl0yM=@w4fit#XMF$ zy|0UKU$*z5Y4ad7l#YX=hbZ>sadi$)^%AfU|EBEwKbSouiRB6|mOKY^*4?}H|Ftf# zrY=vlS)S^aP+4G|+>`p+Mb_2vz=4hse`ABSHxoMMbQ( zSn6}Ah`2cW)4@0WxLbQC{tZOU|9mozt5eMuz1BPygsrDaL;6xFL`(40aj+1?rjPzj z9=ml3xg&V}-$(d5uuM!$01;r_=t;Zx>=}nr>gi~W0j%-AWKLw zumOqh`!R`z?IBN)^WTX6&sG2N^Jg4L-$m;1PHwvtOEdd_H&^HqpO}cCaCs#q&;qpf z&z@Qd>GL8mKAhbVLFdov^Ru%c zmMX@Db^tFReWldDU|n8*xM`8!;JJP8W#1?=&Z}3|KttJCP&d!U#nlEt>E{q&HA3hf z^g_&jyALdmklXk1>qiHFG@YvJ-cP@Sp^}246liO(Rk~&&UkopNQl&^^nUg^&O-MZ-pUHZ&rQq&9RqTi|BT{Y9EvB1uwZmLY zemak}tUGj%yo6oH<3Yrs`e)QWThyZQu8ENQl>A7R_#Lti+c|VUDCePa9WG5-yH6-t z%#*bU(;mkC-ldH&b_cGGha#e(Ap~{okH`}<>oCPrU_(IQE`*nJA+*=#deR?5%g*Q< zl=pEUGIEM({8I1x(gwN~xG1FO9f=e&&nCPPVgDlzlU!Hi%Vtxpyhj`S-`s^;j0C2{ zsrw1@zeYwR4%S<4d_C2^7u&3`>ONdqae*#@D%=$3Z|L+5s}>9t{yGHU;(_Q}lP&!g z3vAT()w9H^zl@9+oe7o;CooyT&LvcM-_qhGM0&C}@p4i&*1n$+SE?cwczB>(>T7d3S9{en6 z|8qS|3E^*x<&L%fFJ@OR1?^N7)%|gztc7o22Z0m@3K6ZFydw&@b|zW9<(#8oYUR!?%*2soOLZom2zU5ag z%!IIMym%NBQ+}&_0=imsKpaz}Kqe0@17vC>TnrNf<7sY*-1ftbs-C~oPXCY3!3+Zs z5iF5uBJR1%h<`v${~HiLhvqll%d8-XdH^_@bi!-IqOPe)dJzh)ztaUELk-o)5Ml*Q zusK4sXs3vQJ(LsZoOWeeo#4R^tBK)XJn-X-OUQgXlnDar+1i zmjt!G_V#D#Td(CCFRVJCZ6^rUHrxh7cYfPTOpk7!5r^_*8}$1YK|s8<0vj>IZd9Q! z`KyPY%&3m;D;V+=K-0epGcZj3pyx17f(#%QL5Eumt?AwI;m;``Oq{yDjsV#SSaulk z2vfDPDIb6TX_!xCbs2kAf`Njd;RFZCo_4(mB|)ybbDd_w^sm6wU{ynM&}!gaXw94EV-trSUWy zkSL4??0&0*I)bF8u5N7g0CYiN22M*(FEjVJpsxe9I@T$q{6`{BUqz-wDAED*PIhru zHzP8@Gm&Xo80cUD&DHCrFDgk_YCRYFnh#&JUoPX3fu2Or3<~;>FamupO2h0lW>Bz) z(F@XoFTspznD+M_h{GLWgfwf9GBN-q;w-`~vOw5)<#%cP|IIMUu+`}RCP);tKYT)_ zl3>Nw9r|-a+aXW5yg`0a{rbdzc{BCA)YL9R)R>n%(Fsbjk0; zz$_GsmXlM%VX!bJoEGN^={JPs_(TjN9cGPped&*C$&xL2ZtP67yQh7l%=uBmUm2dMd<^lM* z!NBv~XBuBaUq6`(@-Z(j?^xB>cVJz)P~5ReWCK6Y+K!Bye+Q1^4@{h-WJv{C2Cj|? zf^I)<2-wXRprFW=ZFb{6L47*Dze{Q!WK*@By7$B{(`~tCBL1O z&SAULXDfxtfCPMfHd4lioMtCKTVepmorR@c^bE ze*RZ3uRb4%M=dtA3XL6E4as)k($2NVP!^SU&eO7iWR-|g*cAf>HuI%<_!P?-{5xW< zuG{NDNct~1a@hS(<};D#jEv~-TxCAlE@HvE)fC9|=CUbd?$KYF;0HTbssH)q`G0#9 z-STJ>V1d={Ddx#Qf5vl1a`%)(ybtuw=;8Q3i+pQXmBjM^|s z#W@>5$U*qW-#rNh#Yf0Q4m!|M9Hy^2K)Uqz?K*-(4;9NGQN5fhNVW7pR7Rex|6E^r z=%KbPkmj36G=?T0Zfr&9KfV$!MjS~rYy?E7c8NaxQC3&?^b|ea7)+$>Eppf0MR7gS1rU$$D_gbFaGz=(2yS#4Js zL2Ug@^$WraI~;Y<+fRS`Hht%n%aNcIp<`r((T?i(W^N4yFXqyqV7PZ`k;KHqg%pmj z#z5?}0TY$7ii(OFnNLoipyP(%`N(nqpA9xpU3&svEK>~HS{c=Y!yTBTw)Naj-xlHY z_*+pgs)@a_KG%0P#pu~%F?1B-z0elCsWJ3Vk!q_F^MGDHCiH(XPe@M*OaL7KguiR9 zgRWz2j3}RgnUzBZYUI2!VWPxA6>DLBW=5I_1H@6=33(MDKp?nt=MKzGG!G9~8$KC4 ze>ySbCGG7k1f#o9{tYuEdKNK(oWYJoc6h3IS-EUjw%m|`fdL)x9At@#lzGmw&vFS! z`yDclH6(t_YA~OjLeM)TCu5*MEEy)-oQddyN-K(sJ2;3Ydr58a53xe_t!nW+9K(t`=DmJFh?tgKc3!$3PcRpN|~sfZ_#t**1iPh~F>1 z6hWkO9hpg@qH_5y24h!rUMFHqdJmoeJ%do$X6XkJ6!^8~u4!xw#d8Dy*=RVmkN*e> zC)#JYyZ<~SCN%FHZD}v)IEiU!{$mJ~o)S_7bSnO%Ksvqf5X@l z|EAhjfdo8P+0luLUIM`e z0###Ak2*5^@b&B0{@HlyVL!2c2!s~MNO<*Oy}TgEmeGOQX@z4yPNqB%JMEAO+*@RZ zGg=6cI?I!F`#r?3{t63m#MRfoKup{CB>wZ-=VvA&0ei2|%lLP*fjo!vCR)HPs4Z|u zFNM4OjR!heZ=6TCa6*DXG}Pfbk9Y3eeOeq4(!KsFTCxWtno;0|G=N8N=ouKOEi~^B zH3s;@yTPf&>ypJb_}PY=K?zPzF$VAmEG ze^XNmX#T!3MCZ?k#K*_0?!rhG^d=z)KTs{;iw+s+?&y$)qhcll)Ya87 zP(XJJ?_Fjk$dVicbV!YY(#@TM@b7x3t7vG1susKlm_~pCqrwJ^0DrhF>TIiiLLCUF z+~8nfV&=dYD?}8eI$Q^eDQ!jA`aK5H$zx&y4CJ|hN>R1@OlUh?YT)p_UKCPX3>6Dn-L3m9;)N)&Q;;6^Zt%)dyfn*ejF$VV0jT4y%s z@Zf@>#o7PAt>gBn) zFu4}7dN3jnn5Ptn90jm2SVY)(xMOw| z1T3iF{ye2TsLhOPJ(vK5uaDpEZ5|!H1Y970=1)*@wZVvcjy4PQTkivj30hzqh&;%l zK49Lw{yQs9@0dXb)D8p&Huw~997J$21x-zY#^WRZyRgg@9Oj>~@F|c} zO%N&rxa0TW)-U|Foxppc7|x*9gX9WlOF+&f<-gG*?dr+}BVob7FHCQ=)3ieYT6!q9 zwe|B|Qql#O*ycs-5fYK{%NzKXzO$qdDkfVmT0Q$*yT84R4JN(g4Su9Ba8EAaKm;;} z7q{m%@n|HecE@jPr8sG7X(_=_xjP76K>bG*C$WPFx)GWbbbkcc1XNAHR3Y;pExb_l zHMI~*MT3=|6t@&FUc5l=;R&2dK_zmlX=Q&O9`8Cdpm1<-ntIat;RuXaaK1@U)3rjW z;{KxvZ5&zxpmPub=WhHnM`?TG#?)d_b{J53e!FWH1tY_>Dv8O{9bXZkgGGx^E%r(T zrk8T6*Kd5|go7b224hG^#>J&R%NcemfJQ!Y#ucD0Lux%5sI=4!{`+AU)5{AWR2%s7 zYrX)sH)CZG0rb5T+kcQFCNd@}q4B1Q$_LxYxGC;26TyfRLPA2qm?h%=-u5f3CR2ef zD|AMbp%;ARD=7CdnpQRdHkE)gCCcW+d#`-4u0p6Ym{CcZSzKf#B_+Ld`54$&Ce%O_ zccby5GbaU?kqY48eiRN6X5Xtf55Z%fd+Or|FAG4*b5*bxsB0fh`^)-6IwjjpS@3gak26lTDV2yX93_f z=TVMu4pvw@I9)!Rl%NWInJe>fSur@Lf-$KxAG$F~^Hz19B^c6=CH!AczJTWS*WglowM$`b1uKDo9Ensx2+>O6;F{J(AI4Jd4UyuaCuc4!0 z@1d@vlOOfz>lvj>WBELLF>|xjSOx3SqBk>4^DV>K<5yq(E1Ls;Hxgp&p>fys?H~}XN z0xAWNp{>yFF+YMom;*f)azSMcKf6A8nJFcgL$XUk6 zN92lnTCiRBqe&R#zR{~k3hXA3!lkXd%N1|eWZ{jswwDYTnVHR2^mM7+8vk55I{2l9 zM=6BoR=*L}`5G+0rL`3k+7}^3MMabD5ZvIfGa~2~w*!^=+-JiEfSw#Ad^io{9_ZB| ze7Jfd1)M+Q5nf#np4@ik@SgDo3wet}qq@+l8fg`ZmKnLC}5d`NZ zaUAhgz{;Yoy)aUM$5NMEpp>%w&O2;<5C{wfosbY2v_J&6)&WDmP-Cg`{dME+dNVf6 z@w6kpOc8m7iD?pyYvJ2lVaUxnhGiPDDV?@RT0)R8mE>LWJjIyz0IhViwUIn&hF1#{ zogr{^*)*Kb^ro~C4%Lw>8@2&a@D;uKEx_nJch=04+P6mLpk2xN%^V21HA5s~bRw`}=$7Jl|RxVg(oYa#vFgj(!RWI$XB8<>B!je$!*v z)Gs_CaqEY4ZCU?9L4H081!vyjp&S7H*X{%PZfucvBsOSuXGcWwN>UD(iir0Lx$Ah1 zHI5r{I9N25==pcYN8*9Uhj$Q}q?i~5NUVt<&tk&^mN`2-qoRm}xV=9~pWj z7L?S{(TU}1=5pHfS}=0tz09foC@_O1(lF~K^iGhifHNJ@P&IH&A|kaxggp>YT0kn` z;-twINe>F~IB;Gx6gV@2nvgdjQg(2OSg=UL{ex;wQ1(ODupPFL4(ALwP#7}U0DO)t z)J8mBOZss9V$a+>=w$xpN(h`B7Ih?`R^j=obSyytmIMC5el=S%xX+4)G*r&c2)#5y zaKzRt_Jt?4l7hk{!0zARXdB(!@2&yFLGr8j+W13=frRe>>eRCTUu^w%K+pZ({*OP4 zjFQBC(@g3^!Y2!}LGEH_P@h4w<*vZU;Mr8`1cnCnQ z2^Ut)*Vp$Q$hgklCD}D+J2+;YS@JcqkTQ5Qt$`RCwGtp`&^o;+39(i=JyzOx`QNS) z+poyv#CV6rSHrJeVPY675NaL9fmGnFZ~u7EalmsLulT!XF!sURtU0=y%ap-)%FONa zsvI{2B)h@`lERV7M@loJ7eG7F@ZivQXU?3-;)8W-H_jWCP;%M(_-E%9cZ8fWEPQ$H>3c?% zgM6d}TAcgH>1K#_+sz_!-Ai6f6h8-7@9%EYD2|E{Lwi;7d{;xX(+G*iQo7{f6Y8>~ z+uTpb+|%QRw)TxpcZcpu@6oyQ!;0ODU(KI@mm?qYP6$3n-8E%O;zIeS1#7~>5*)p! zohR)A<2B?O4yrU~5eU@%OS_7$e|TZ!pE2kDIec~M_gmY-XI~!ss>h$7`OTG!D8FXw z1kXqsy9)_coa;D?KU2ecPuF1v@w?Xis!?{U5vq_e$L>;HfHzU%_3sJ!umAi$<*Y9g z>f82ASp$*bTgorEPeXbLueQtz#D*|H!StLt+%27w9^6884GqnSr)E4*Wr`httEx;= zO*-jIxeAzuipV{^$IfnZYZE)Yds}Z5@3&`VT~7Y~B?+Q@URhR9^XG;Gj|TXh zy~+(9F=(lrNXe8hQj$xCX;}^5+ovh|&aUYT_%_K{onq7_u*a>B8zSMIEqJLG#g9i% zZ#hmn1+GF1dNGc#qF^M88d!5z@bz|=d-|4^ z&80rP8Pk~0#xo`8JnrPJu3!>tEh!oRpiASd`i&c}Y1C!9V(jcZ_%kh)IG>7()6VnP z%iawI$T13Y`d$)q%(L|ScCmXy`H)paEql(Q^v%lW_?8Xr0;h7UL%X?jyv`ZaPq{E` z4vi}PzPfkrY*+GR+}s{c`O7gx!Z~f#xXF`SpslYo#pStQ2=sMDMY@X4=#0<`T?v<4 zMK#ESouiZGP01BP_-&)2&zt2hUhC+Y*R#VS)4qrNn{akFCiwhx-Aao!?$ke867g-D zrW1lt!DKOfCV5x7YHT|-q-&+#;>F4R+ec@PcWn(p1mJC^5-RFeRgv2ugP5LoVa{?; z^afMviN+A}MP5c=)Tos5PrY2uF06??u)ltdQuu8dPNiRal^v|=!L6<^Nk#F~5stY_i_@LH(#J`5LCx5JWL=HW!6a5w zdeCj1Ci_pmvcde-()^}{nP(bEGY*k#I^(`RX&9`cDcGGEz?bl&slOSH9CLD3qh?EU z>xqvmQL8;CB7l*2%uK7`?;s$BWkMQa)HK!u z0<-i(LP{q@5mLhvkS|J^=rf)hVBI71Yy~8_8b{SYxml{Crxz!d!jHj&(iTng{+#m1 zr=D{2<;(G9$Urp=t1(yj6#lyP)WPnvvnGs^6P`X~%Ia zC~wbva%g*PKS2|ZQS@dn7_q&xRh7QdkWfT_EBtvuW+mQF91OcYP4lp0Tj}Kh2{$^% zC%xZRCMI-o@0ayUe`=G^GHyH*4B~g#yWzyaqMoI=?}*wXgG8|-8lWtaKSS@pEF4V%(SXa5Abi2`S|_SdH&L*&@_WYZp=1J4q~hTl!c@Anw_tcZVB$nG|lwU_fW0Oz$$8PVyV>T;8q*^FUVGgaP zUuCm4wdd}U(n!&63hYqR?*!vssXxd`bi{-p=R=bjd84EJQJ69P1>U#@TYmdH(rgk{ z32hriJ>(cf%Stg6#b0bo-oyVL!Wrq3e3zf%Sel7!CNTdY*ktD(Zc9&bF>f7c#)Fey zx2U+-JVSbfhk!)0zgFD1eS5QPHhhax&hli<1=T^ z>fLQkCkylO4mqq&SP#JD$INl!wAU!KCQhq;c%hyidpOC+rAJI~;{%O2(mbbR0rMb% z-xh<&<@Th_c1-EB?9B3?#~uFsMwt<^Ze7wiNBtoir@hF2{?9ymj12DU7Rz5>R(3Wu zeQ==8pcJ2qE8suJmxxdA7R^n^PoY)5IJ5YDdKyKvF0{^j|&D+vV+l`X{h!9?vZ{5=1yPdf<%$h1`l+V51C8t#`iWryv zaQgfvPi4H8f3mo`pLh`_%6OX7&(&+z#2?iK%>CE>)2xYIto@cfqqVFKL`A6>H#+X1 zJ4iJnm9DAQu*J^h|BqMhwWqd#QUh&Q;dJ}3^PY7MKw3;2i~HLAa()|y{~FvCD_z>v&bjrynH_E%0@#nY4>G6+EK$C8e6+N=sKP1U_r8(LFP9&p7v!x z6gmslk2(oAbo?5{c03#TBCND#h#v+;`3wXM3>b-U^h7u`?QZXA;~ZHzrl%=(rzg*J8#aE!`t^RKDAl9C{~s~b+Ju%BL4Ekf z1AMDRrg8Y|8$A9IB#jEJ8t9Ehsp4QIXl|rd{=nJ+b;B zZBUZ-$6nMEsXur?+XtTgpW!-fH3Ov+tz&yde8s0HxE_@n>? zskIo6Kn4>@XXtdq#kA&;?;|GSO4xRMlkv@W&`B8}e>?j4MEXWhCH1G_=6m|&Aicy7 zdg#5GP14K`Ouk0W5)w-Mj4!=@{rZx&`K~nR0?*@(v6CIr8LkC-V;Ym&|iDpZV~>!+97(4IzMOuMO+cC(Jy*Q|*yM!xBxj zV$(Xcf}Jd`<6jnW)uG41BUoq%RNZv^p#2Z)lBB%Ha4S-Zk_?C3vFpWL)Lz(pg2*fQuf$hm6`aq8wrIsHOWsMEZ+m;G;W+@5GD z`<=uJJOGgA7IA%i>#cjuKZVt=Ti0f0LEXA_^{?XCUY3+tOmot8X{M zKm#5O($Udz%r9GmKt=EUC>B9L`L(Up26)%iHLPug9?cRyoUvwF*8feJ6-UO}LCrNZ zch_QaZ?EqlEKdGM9St0|9VYf0j#h=!er;nw6`k}ri0)b`xHNPRGVyCnVH$^>gu*WWu#@z=knY5)7fN8T3pG- zxIzERRSiPf-nYA%)=efV{WjX<(5E&)zItG(_$E7yuja(7;EAZw*6ln_3Aktu7QaGl z**pa|&#Nde>3Hfcf4@=B8x5J40)sg#&flVib&5}){>Wo3aC-mR3a@q}5aftr99iU# zxmoRxZFu$(+Aa5=Us0e zN4~b6ST||xI-c9PcjW=eKc0I`A7R3I`C6EG&s~`$cO77-=a!dl#Q7pvvU5sb8MLC* z&=*e``4ic>$%V4v&J3ITLqX7BH61^6%lPwN4)W++tZc84(X=Fw?BBm%Q*$JF(&&8g zlX0@n;MPw{rv6kinb9M59t>T(_OW5BRyAnM7x$d^#;=KSqk5fj?pQPV8lCXtl;36X zMcnbry*?-BEQj*WXc=Y^S?K^#_MziX-#_IO-IB&?(ma5nYQt6xi~d*1<^K4+y|Fm% z=TYAlMA{f1C0$%5F+Ps3R~stD-}u!`m5rE0p6p+_-h*NC22^bgqJeHS$OenroV&NL z-^wd=L`RM$=YRe7EtR*bQR(O`|B4J99bw>f+O!aIf#abuwo9m+9^br{_^&u3)G_+t zJ!yiiwXC2upfYdI8are>M72RNx>L}fQw6!x#!fjk=jB2=-KVPp1BYU)YSOIhtZi5O zPGJD(y(qHRHtRjMal?kjHT@M($M2tCPyB_*H3SrY0(||1|1~mn$zSa~wBqjg__o+E z(;r2(8GTS|Z}<5Ip8=o}oFeSJa~XgW9uo}GlsDdVeC@y2z+uJTF`xSqX2uJ&WT@QU zVLt2|m_l6B@uO!WRQ$0mdrHDLdn0lZr})c%7Dn;z!$hEffc44H-H*TgYg%7voPVjb zCtidMw1|@+0;ZL1_m|;sJR`|4rMiQ$R^6(JE8Y*>;`x2iLK@l`NzaG=sX#!h1D~iU z6nIM^4F7RH8Z~mn-k+q_H54i>&z?R#1eD0bY=4u&;+0yy`v0m7k5AaGZUwUlWSTo= z@Y8>_`~x*7?tb5(iHIL?D^W<7G7^D4Own8_#~NR*{s zUT4?u+}XCYv~&m~=yq_9d7f+a%!ii0-8nbz+`@N}@rTPQBT4Z45Q1DbbI#WR<7FsQMG)?_P7|02 zp9dJ0BlX<0)kZ$0S=FU8XD7s!KNYY4^fyB$sZ>!fDa>fO*KFoS(c}k+XH6zzkSUaQJ+@Rjn8(y(A;=Z}VODPLDrAYy zg_n4!P#r>)AUaSk(wZSMlYiF2*!V{F85(00TcK~>Oi~QvH30?)S(guN4LXL13}GOl zv%gW7rfU-fLf(hEStl%I={F-$+Mt62@;x~8e82Danl@`zyI);J0pT!i*Y41l?i7=F zT8zmIr`BdDB7$U$@Fw;|HCwIu=#b#62wn??hUUnWeuSasjTy)ix}w z4n4xuV}a1?u;S7dVoku+Cl3t31{>JvjDxix;1IujhGS?Cw6_1m>$^=&Qmk%NUyp z`gTvm>WP4S(5Ayy}9P;y2u{!DzWN7SJ1hf1j>F@(>XSVd{SLsOEpwGHP@V zroD`yi0D!)k!gZS%`HO%V6k4$dO^lRkG-1zksmJP4}w0XyQlY!PW-g^B4mjGQR2n4 zq%c5tg5S`nxGz7rDm!<3fcK*uvYIi&7{Ie8bh0QlaW&a>H6P>)gX=DD2;pF-FQOjA@K{I zCNu{pWe{vFgJnqZHKj_;n486HcC#gKRJeGQba>`B7n(`D62Og`^jvtOupv|@6?PLe zZP%z$YA8ax2^=Y4_vs~H4Up2!_UcW&cLive;K;y&%)?^GtqWM%o4{8K{uEKN;|GaK z-4Gq!Zq^Y+9KE(8#jeo{StrJ9dK0>RCS?q`0kSlr@YVUoLMsCQOs%2nx3Of#{eAs} zn1Vw#rTjwZHW|G$yW5)3^TpY-5$`HTBaAmA4!|JmRryI@Xs@!UCoc9b$y`YYjUem< zPJQL|p?xw(ocZd8z5Z#3CeCm3W-S79HS`NTDNRsQ&HUk_+i}7bzWiFcgyb)mH3$q> zC`(oVDK_Iorc$2bf#*EuFj6hq_Rt5~P9#RL(R)-C(A&F5M-3ldi-%qdH$#ESp>-Sa zwFBh`ux8DV1H|K+5LI$x4ALc}J?rRP<>)l*J`eL2i=%9~lQY?U$rpXG6C>s~4E+h@ zFJXY@JCexFsF}1 z=QR?+C6-2xQv7+mHd!5a4H?;k2M4~jW_()wIcRV6?3L#! zl|9HFJr=y3L?sw_o%0?xa(BBl zS_)G@sX2usV0678I&@d>Wl!q*$SY*eQ!l9~IMT;v_$aRU4 zv!8Y4&REbCjyXDM#fXR-bmXFl;q~cJ)vQchaPxHnOmYnj-)Ojsv$46T`(g0iJX z$K)@nVz)uB@(Y`quDd?)h=eKtmf4_v08Udf);t_bab{R`{9ILf2*U= zW7F^GWnS=Z4FB3~X(s0kEsdgx_EsmXf?}{%^S3Jcty)#3H&IpAj2_U+`m&h*bk@5< zeZU?uyEE7A@%0ut!h9n5roL4b{NE)tvPTP+_|?ID(;r-#93oubY13MTcbquq_=x7p z@~>BQjo6vUThnHV({(PqXf+!(dm7Y?7jUdaT@|HYT^Xx0V7&bOb0FU36P*r?ncP*i zoD+N%eW4IQL+oy@szT}GlDwoIO@sVINh{n~8u8=Z*ImKYh9_YDu0?iUUI3hKEr0zt zXnebVzZMh9)6{R#LWN?kOHHP-{+dGMO^AOY>wp}OxYVTJIy5#Ztu9!=oyC^4hwX46 zX;mt7hO%=DdgylU{C(4|Hmq3KGN+TY161>rDc#C_M#7j<3FxGrDm(bZKj6d(GZlp; zR`F)D4<4edC{Xmx&C@TuoEaR~@33PI1&Rcs$L>jgw*k4|XXA_ew)b{;KQM74UAb6w zs$866Nd%}NzM#EdQDZAdpE(VTu+hiCa_DvYp!qpZfSe(g8cD7}KCE)F=tFEMF*F`5^ARY(7kc%6V32E*Qx zvFUls7hy`csoFt~YWJl@twiC4_#44vU#+X|yUQ7!q~OGt5mU5;jDIXGEr~1Dd7I`I zBDzvKX=HszE+W;#E{mlP@87$|`(6BUk*-`{E+N}IU)R;C=|8EfYV?J;q<3BT*s2OT z^JiKi1-)81JsGh*rfWfdkqTbo$<{+hO0)h0oWH|z*xv5TOFJEzaNa#VD6PnG%md$j zkHRcEy%lZx>K7rQu_o3Vk0RY3(0mOxZRd8KI>dy6TzX>@lgp4rb03zdDzq;fcJJ0y zw)}v}v{q3V^kbEW4yicr$N{^PZq`;TzO3u<8uA_X?jK64aWMPq< zIa@LYe?#=JsDF+REpCWE=Ve018o(7$}`D)6f*1hR3f15 zd$Y!VF4I(w3>a=>Qk~Si76433kjOM1xc8pdsclK;w|1;Va^m8bXYM{)KK`4-u=kCd zFMhFg#_ld|wRJkU)Yz9>rG&^WN`H{HnoQ#@-m+{=cSMh=D8r7iUsEw`sm0pKwE&UX z2mvq0ImCmDSw8z^0k6Q0SBko76Ocqml$~B)u+mWWIPBd2esHq_%S{ei*<;Rmg?_!@ z@bi!p+BB-lDSt_EsbqM^0W?ffQ6Pjr8S!U`vv&I7*VAw1-{mw-Mb~(G;k)`26H=u^ zb2p}hAB0AH9(}U`4&Kl66YiF~-Z3qkeQoko?)QAu0s_#NGOR25i(izx!li#@ec@!D z6#B;jizW=(ZTq=@CBzkZt-n!&S#xZj*F~nC{c`c^0QAAY;v$^eIqMIAX%lifsBf!gcd$B(UJCBA<2_i;bI9XxPgUQFf0t8Giq z?*+DGWMsPA)C~Gshp7$52jC)iXx`UstHeQ3lo?%LOl+P_`~Rd}T@^pTtrsqj=uo~; zV@noAAkb;1$yi7EK2kwKT$znqUEO%*q~_8o8=!WW(=?+opS;FdRk({%D# zj-P!Z;jgSrSR9$*x%n`~3tVF$kE&jS22~*6megT2U!%mn2k=B?xWKsN=Rcoct;5GG zG2K?K3IFy*re@y1vkV`!yTF4Vy2DxDv!ZO#np=iWvObTE5?^@E2Cy5m)Y+SPA>b^dajKMEU(Z&yuggS zn}s+9iM5vLi-~`{c!bk`WX-2H>zdHB0rY6=Jxf%%Y5dkLJ{O-RbT7KB(Q%vk1_TSn z(mMe@ARM{*<+M-w2Is`-Em#onQA%qriKTwBu2?nt+IEad5j?{AR^rOWY)=TibHeT|F<5 z=VVi7l}U_{(skZvY|G;7l?Ky&Kl9D2Dbph+^zExC&lcq!*m3RO0bBd%BxHHa5!8{I z5xSVsRV@k~iR5xyC!F_clZN0*h%LHnBpmL;?6n%6D)S~4Vpa5sn_{)V1`mAG~J?Ni5me4BW$ zPvY6Px)YYD@I6Artl9DSNumxxm*JCh%+pX>mv3+ zFDfd#O?9t}%N!W6@4f~3cN?-LP|8gTM*%zFn3gcjX>wT9;?ORsRMVjtwy^oUG4*%% zYPFjLgF#JZph?s_vKM?noejWUqUZ(*)DG6%t+u8Wjjg;e5fh2@5YVhT)rMFHE_oNV z^>+Vy6onF&mi)+YJ4Px=oty4pq75v$3YXiAgEj+Gb{)bBffU^S(<5+}O$k)3D`;6{ zH{})bxW^I;dT7Mz2Pd~$u?KKc`d$HTWpjJhHms|fmCT?_6t7^RT-YJebJjm-(UO=t=oh9OO z0eh<0)b|82ss^I43L4D8Jb}sf2CYY4;nT_C>E5fCq*iHrp<1?Af`aqNR``g|>asrI5%QFvNx9%z`7-_q62>FZpXw_?F zef(bb)5kLybf`&T)y%K>)_vC2|7%CT&p~t7`<2uUjox|_j%{#?ILWNoM^{sB+|MFh|q#D1+=^NZIP*?-rgEEGu8Z05tN z$O1dHvvNU$w1!LsCm~WapghO(PI((z)kFx;54=KDA#dKiQ7<|F&quu}Q>O}ENO-H^ zKWRywb+5l6KCO@1vKBB~AnF_h{9=7CbGD=tnWp^O!=&uDicCmjISHUT{rD(c|D~5F z5sDm>k6~Q0Jp4lb6M$85NhZWgEBoa7m5qofzyNeWzzO2r%zPnhboN^QD_LQ0Y1Hcm z`=-U&0R95qW)-^3Pb}c=^^H$=Ec?9o?+}c|GR@6zFt%zOV;|?0Ub#d*<^mpGUCa9# z)$qSs=a9Bq_z#O~+c<)@NyxA$EV3yFZquo2I+~G%0GSQtKyHySMPXu+EbaL9^sBaGD z-8&z&F}_c)V$u}GVKwJhd17u={PpXx)9xdd0xX4>arT6->B$M3&i@68_gx&A?!1x|eYgy}&IXtPeZZ zuh*A7s_*H2cA8HV%l}!_nR7tj&4y1Od)_PQ;memz_j>`%C6V_KZV741pB1|JJ^q z2cLL_pf<`8GbV@lbU}S3xEKr@_8s})>o)v`^*5)LlDpR+F8Ic|{zlLaQ{|V|RDfJ_ zT*izUGcM|1dfm2)y+_0i+Jenu->SUXI?<=iww{aJk6}S_vk&2x?;6lRAvzr^r9W*O zesdZ>{WQ<)D(r=}>O~@10Nx4q$sH=Sw%m4GHTU*f87aOgO%zg-ai!a%?p=_qM7#ZM z8d->%JJGfk6+u|TrcK+cUL^g_8t3+}Q&q5D(pU{EZb7>$ehj{DdC`in;_IOF1teQU z(XQhZ^^R4wH58Ptd7hX5JrVY^lXTkQ)R!h6c+avoo~5BZM1JM}D&iP>=^oh?FR8@fT46FOmX^q`AVSxS4U4MN+gpZ({5ADEoviynL z_oV*@q{JR7Z#acc1v`(t-;FxtB+Z(kBE{6zJnz$Mk?DvgQ%#VP@W#99riZhlOCT-g zB?h7c`JaWvF{Dy#bsmwRZL5ok-r{l~q0OA`P29nuJ zgSQwfz<@e_^7;1}4t!$s_(=m=e=^0P8M!w$%%O}FQgK*@H_c|u9Gu@XPHbcB_embS zpZ)uVhC2ni9j4Pjk*kSs={v$#|1W>rdTk{eYBa79M(ziL~L(g9S|9 z!Iz&nUL&u*g9)pFknJF=rfWX?zrWZ9120aOxSn8Z!-h3f;jleoDjRbQ8fOyBODP)pMjiK2sPUR(Ud1KZ zOk?Q#2whZ6(YK{bmdGlJDJ4-gVeL)O7Ql?+2ondXBWQnM+Mwt-|@11I3E~W zdfCMb+t^=!{Cvm;fbgT;8TK0bO}+(l8v2$?iw;C!+BNpZo0)Cv%srFJ{x=M*bL@+tQzSChX;+}H3Sr!rb4_+tfWsQ+=t)X;3BUoV?SxNp& zS3vTBA4fLC#4)QT^M8MxDIOOvflrImjB6}B;ps4pqVPpvPUy{t4;t1RS!yrY8^bbY zqSNE@(V7l`SD+oxfI(PO&KA}Eps|0K4NKKQM|&TUGwFVlcVc4VpS|bHYtN0F^-S^E zyXn9=t+8W=c3UYDyx@k$h z@wfQT{(<0=&FmgJ3j7evGPj~tHF1b**eqz!{(j(CSzlEvN#qcxSryt0nhV6&K;!at% zDen+*ia*a0OWLB|cWIYUB-ZYGcfmpirIo()ojrmKdsn2oDHp+w0p^`jc%2yv7QK#n z043VtM|3NX;+6zVLN2k)o{ut+4u}fHUjTkI#y``V_3*69o?}UHGQkJBy^nry1_P~e z_ObqGry6!(3C_EPSVVQS**{2=sJNf(5MvjmE((#9* zvyM}Ny8}Ce3QNC=S|(HwC7Squ{Tt??CmL`D{)zvxVc;DB8u=V&0)3Ld-R&hakK}F5 zlHGm0_4wP;{K(*i1x>|_$1(L};^DB>uT z-t+xk1udPFZ}Q*r{7tbqa7lSe%YbDAq7M)leiwVgsuRl1%D>v=In3#8H8hpDda$;p zrUT;wQ+OzGJe*~(tP zolp?kCroHcliJM(Kv5b5Mict87W~2j83F7LkmmKhKn0@v@!@e#ZGr?_9U*zE@vIW};4{)n(&U8;7qBeUK{O%; z>$v6BB#qN1+;Q-sd=yfU!9#iMOl;vyQQP;|*Xq2fklVMj7d?Cp58aUn|ehTI@OI zF@$x30XKoV*R9hOS{Mj3n{E>;j+L~19ObWHvIa<(3=|Q#bPQnqn&Q(jo^du2L+HjI zaM<3#sM;}lXp7%CC9-Wr<^chPVx2 z3wnE+CP~lSy{dw+pQ57YzaymF=<7%Ck>O=07MXaP_Z;YQ@%SLNvZtgy?bGyUCozDB zLaT?e_!1C9C+3Wl9((uewjW!SWCaga9kiocVFNwA5DM-$RE&c;Fq)TaAbsUw$_b^) z4$KFX*e|NnRw9p!MpU`TL)p=`u8L>>02BvV+*_lq(b`p2`1FT`A#2>YvGm@x zYu6U*4*s#*v#!3sm!ml#;^wJCYJJK)BbP^@CYrR|UQc<%MX1}qz+q)mD#8(rI|6AZ zLs-gbNJ!gJE1lyU4~Gc)^VC~(G7O|C`cCH;gVK;v_7juNsMgbWdv?=bO(SH{C&6Tx zCL5G6;k+k5_)S*U3fvR2LFEU~q}3`xh1(qG9tg&J+QG`j;Cd~EVz*dcT2~OP%s-qY zMFDo+^x>ygRTaSmXm}sU#;)|;heiizSRpa0s@RycwQsFkS)-V&-~e8_cFpK%UG=Fn zWYyO{TB)DF;mp^tQ%HZ>AVL1^Zi#Qb->#d^ShlVuSYX1aP;{R8%>s_pUs{N%Z!Er@ ztXq7InoF}m63dqW6tY0d#+;hdRMBU1@tfBB8p`@(&;Fw;Z1#W)rg{^sR23;Pjl#^i z)l)_siSzx9Kc1WCGKw`*h6M4s$$vh$ZdHN5bNd|hIVHP1aNYqiGzATFS#W$?m+fsg zOyGNQP)t<&0ur8SRjHfXpe=`hz6$@OtqEHL_Wbt$L(li~WZv$MSGM^~STE`p1p$bf zbNGDd$7dNC+kch7xF|QAw-_e)zptJ^d^@&bpYEMDPCf)fRLFvsIE zAp`pj1Db&iA580OAMC&Al-NKlVcH7?X!Ys~4yj7IcW@7hbFlsV%=X`u5T z);i;0pPjKS?gWh2|8OerWeS0tusbQD*qyFE;>9}A;E1IFNCc*L_fvd*&1T$JKrsXR zM;ITC9_0L}WB#~j)8gEdo)_k9|8~w~&QE`zQ&7?ATzcb{L~LGZ*6iWdD1 za9hsCbM^<+8-&PaB<5HDfk-%c@?=9MH?BQg^!Gds^EVD(;{PyB5@%>=C{yJAj`8L_ zHczl@6yu|6I~BoCn=jAG@)lt<>sC6kZdK@-P2VaN)oaidw#Ib*67t$~g4Bh;B7R zPXcZOwDJ{^TcxKC>%?R^VB&|7Pu|5g@3a5G3`rkWU;s_o#1pCbi8U-M67K!BscbkL zft7>`e%qH3pU61UC zn1RsLD-_9hpFYA2T7^C(6_LV%J9Q!_I?o^s2dXAe_2x7u)eYI@6@({!=GS13M;_T7$qBA*ZcaNKT6#?ZvJ6~( z%q1g9cIL#~W~i?UQjN>NuB$}4$JlYq_;X!__<{&zPDlnSjaq*wWbT&Fv=U|D_m3}h zku6dUT;;M!_-5p*lM~;|x0wv{3Kw&F{#1e30sIBvr+|g<>cm(9Oy-X9e-N6#*&o)Y zzexQkcg4+^a3?$XWU`+ew`2Kj45Lu-(TJ}=g#hY4;aBT~8j^&W#}*S0iCNrBR2x5= zd11vp=J+)0hym-@(zH~%H{psEBp^A*3B0ejgex*K2#b6!4xM>n11+spDD86K?gpWz zmY^VEAC3v#5!Se(+ORo1);zV32+{BfSSg^tU>@}Rlc!EyNdc8ZzGoeqHmzkOO zQ7%$oiI`S2p)$-3(!crcrXkvnX95hU@wTq%)gw;7MvUX(9Y<{U4=N&oBvMwk-|A2i zyl2(Ql~o3x8S8*&92+vaH!X@B}U=9gm4do4;9D^YW3HeU9WA8=jMm;ozb*tj|p7omwOI~~6 z-sE z^b?U3=|+Fvx;h7rjBZ<0OogDv!5KiTmf91-OX=~kU)`#~Q-0qqKHg6JVu1By%9a>5 zcMjM5Q?;L;UF<6T8Uf7)!}5o_21m~L@xD{vkujTh?OF>kv8h3XjuGN&Dhz+(?fWN( zJ)XFC;`&-4n$qhwXV#MB0Ghyl{8iz{kG2$B2EGY6zVrv=;5iNNGwR8p=tqqLwcfq3 zU8)XlKYX9dvkwyihFa8JyLRpHZ{NPP-BNRk$?f-D299cZ*wLfmu=nrY&Ga-{C^^u? zTMoh}Q$(ix<~5k6=y+HntJ-wldhPaQ^T*Qwz*BU1T@et_Cerxw>(`dAz5VrbcXmH< zG^fBR{U+yWTq)woFNo>hzW7Icsf>&wd8b#gyOuVR$PTj29y-m{~Ku*SlozaSp9`9A&;yANbf+Q-WQ8tIFOaZkD*MEe`T zWBD0#=d1yqU<5yD&^r6cE%fCCHvWn1kAT!Y| z>=AFP3lf1j|GZ(f-}>-y$Aybo~ z@iI#$Fx*3j7A8)4dLg`K^*YV_v`g3c&bNJNvFj3@#+J{?-$DAM^AWF;Nlym0y z+>fzsnXxN}Kp=l?=eTUnSS5q9j2{hDWMt;?pcTR5QJuSWf>)cy3dP$N%BiU5Ri?wu zpH!H4h}sG9ap6;^GMg*+?j_jg&-iyx?YK_f!_hccwMuN~xX1JdRw|pvc0Y>FOk3&R zv!~AU_^G(*WyqxA&~R5^3v0{rXJIxtP z!B%EnQT(TYcEHELp{bAVhkjpVT-ZD5(5tz(47J{oxB|*fNWNx=-NzSBA9C%#^f!h7 zT#u>eLd1cWAzZYgv_lG(t)>(DfUCon<=2yWp1c@EVI(2y)#xjXDi@~Dr!=1A;W2Jn z59ja=yLQbgb=GP-xV2$G{}rS)y*byfUmtgR(3WxkzaOxyPC1`(Z2Q53HZ3ihFi>6f zsnfpf#D%>;2-mwd!s-0}hgh`SoqJfmzVvO#uwkk6|2PaYOu1q5NE*Xw(&QeGG`pKZ zJd+*L+LgN--hMDtRjBh#f4w3mEc#R8`^W7O&XvObw}#z{28et%_u0`EZ8f%3iL$H7 z-$st!FE)2{rG{{z{e~|l9l~%O>G#_8Mr!K*8y5zcyFXs9V$5E>i6--(2Q~MZp^;l_ z%~F>Aj`(4*dhJ@XT^Q=GShD>w)@7?{E??#VqVO_s!zc;$=r*+MVs68}lV{fa1 z)nm>!WrEOF3!#q2`IGJ9+w>?6**SRV(2xi@lFMAV=OMD4r?&^y$bs9K23*JRKG;JaLq%t2|htk1*hyZFhNzEVXGQ%~7Id zjrZqtblvtwj~_Is8hKMq!THd#NfVPRs>8I$y3QJG|NFlWe*csVe(M?}AC1cHG^g9F zUf&P2dl~vYzkOlFi9@eE8inLewg`N*>rChGuHP3d-@NL=*5JK+C$2aTwn4<)A;Y!w zP*({%!$4in^k0iE$CY@0*9Qh7jZfEBv<&G?Pz-au&>-z2!VH<;ks@Npe~$fXmwQ+h ziNA4XSGOUznW|fLy?o1z9j{=B+RDJQYLD~iRA#9ko(iIoON5^Jtr|!N#Sh?zA;Eb^ zPX(hv+e_8|8%VUPWmP0KVT);Yb^Zvd|4LbHp7Hl1?d7!ebi`&SE z^8-VeL9kKv46Hi-jAuZK$KM}d&@$PkYXnVGXQl9Y(y%tSJXhplmJvW75pu0uYiqk( zffEd18qQMTZF!#A{Mx2#o~qsd{GFUqg{tnrLt0u%R3>_rWK+t`NrN2faEvdBF63dEeV+K%+c9 zGQNCu|HhSHaF^iVL{N0k)p58EKvD(jE!dlx`{NrA(_0#t=xTS?vK~I1LAB$I?b}4& z9HAisw`snptxj?%(#*6(T=uw{qStknvAB0nESQ&nw<$PLwSz_j z)I7ab%V@<(#pc{UvtL5E-&S`-FR+OIR$KNWPB^>JdimS!ZbiGxVY#LZ84|26v)56F z?V>_@1WD5wIkX+3F;Q00kI%zNc9W|N6i7!Xy<%lkRT)XW&5XEmlGI=;Uv8h2)({ zD>FUiVFz^M2l;V#D+$XFd3{SBADdeXY*H;n$VK@BJ2W)J`pzv!kFF14U znj8S>%7T!ih_5fd^liI%S5)JI0n^bni4@RaR1#>MlqL=PEMKx>Mer;1Y_iaq_fdlZ zr4G@0jifrMnA6p4d52(%T%@zl%8jVBkPC~ZN?b%Eb!0kH_Q|OKVilw7 zk2A&PT3sD3b00PHFh0B8r>ld7&c`-I1rJ)bz_*J5#15WYbbH%swYb%p8vV|zeCy@t zm7S@*>}WNH9OI2fQ9CZ+Sw|f@)K18w$X0o4W0qpIVa|-igSRjL5M_@(c()K3xD>tH zYJI;Ba6P0h@!f-E$EMh-tBo91jIFhX9-<1t{P**u8eVsTv*f55wU?{b*MEAtDI z;Vj;h;@M$fFD9Ufs*#Wt;c-4^9#tkPe|`PWTldlrnu^;RPer!Y>%>L43PpoUkVvw#_%U}t8S%DlgQiW_$+!bu3D4>heOZV_2L~5z_tLektY!2@RUw0?eeeJJ$rBw4 znKZGV{m2YN>`-zjNxjy170S%7G7a=_Vo ziqG$#ZaZlSw8rW>hkvDp4kngsEBgRie)htMc`(xKDcI`)RU;E z_5%utyE#T`S6pVBVPy;BCaf5}^w46{Mi>WJ!K6qpBbSHQ7Ib*__B+q}j#}+`-W^$s z4)<(V$LZ%5_qUvu3QWTc4R9)bV&5$$4wVSh1yl+Qp+Q80CCIdg7$gClL{dovdNLO;R#8Mr znbW1r%!^-)*p9PL4H%vfs?4J?nqjuXrTk!2R2D%$W^9rPkU6UEU`Bl|17}h(p1FJo z#h}ar;Rwi@je)PM4At>p)BvgAPF6#ZDcg;RtijVhOU_^D7!0;H0sHmvP*jqq`nZoR}8{brNs|(+L`swpbOC(mQ*PD7+ z^sYu>Zf&?#XjNMSao`=imbVy5CkamNa^X10jTMq`o>>67s|B-|^pFF9{)9e&@?)Yx zFKzI^fr88Of~zRP`cn1awb_?Zb47Lo^8@^{)|l$MEg8&@l3`VN9>LCef(<)OQivLr zs2xs0_bgMfpyg}~;?&~WxCYd{fAp>1T`t0dQKI-U*|F(fKdepoi%B(-6@A9$_;r;7 zhWYQ%&*DI}mgiwW5at~-*Ptaoo=FOEN#wJqf{)F7vfvL*;2WUhQJJF$SssgBw{G3d zk7ecGM*79oIhbamg-TS6gh@bB%E-VX7_h5n2d}ct9-}ru)TB|PbgHTcUT}OIz`}b* zS8wdW;O3ykhsq0Jza7cYoug^PNnQ5|wXJ)O1W2+uHD^5j7#cF9$t`^VN&WCpT!swz z2yE#PXMc%6D8EurK!HtxdynC?L`*Sz?SmtlC?00bo-HgG<(_PZ7s3756EjwDDh+rE zd`{uuMUx~6k>XQS#mcsHkG>mQiQh6Pd?|3K%n}H^LB53mIi2p{MzWGDak`Wg;XN=n1XfB~Vf!Tx9N2o zC+lTrX}uaSIMhsLxFMM>o$74)@?{=Av8oX51HiTxe2o6wCL2T)=XBX?(SP}J3w~1| zMRDq*M~MTAX+wGnwlBY)(_ysivrV4s1MAkChZ+Klbf2$Ti5s+$O>bEze@FhZ2HvGi zm;wqNm^0QutH-=Iij3xgS;&9SIQELkFQ^Di5w?g$4~yHJE;KV0QGz%=6LuP3UPnA1 zzO9oApD&fZT(&l$79#wO*u4vhfKU@PoWbi0Y$}BdIpejA z!zyDbFn>%~+%#W%uoZRvOBYLUttQyUgy$`fj}w+bJs{G4>yM9U{Gi@Fo0ZF=4j^NZ zD5=)VnXNGMn{F3BU@|LB{o!Ah)cn*Cf&-w| z{^cc3@s65*`SPgx>fL+w%5Ur+`MJ?t509N5QyeS0!VRK~8?bTDjDBg$frfW}?&IBX zn)|lZKA+YWm7aGu(~}R4^kD@p9IC*y-`W$w7;`cUobqw9q%2fT};itnB``wAUF z{hekrv&C^Uh_S)v{&=~tJs-2{9axm0R z+j-WLyc^D=URnNvidWs@ADJq8!;LXK;x_q_YK{Ism#37QJ&w@V&}nxO`d!vX(V&Z% zOd`4L1{QU^#4;u&IhI@IP7LU;64m)nVV#8YKlH%r*MbAVvqn5y^qlbt@l%p<8X}~; z**yivY_Gb0waKNnJL$~|i$WN_RqU2+_}hOpo)g}Ffs-P zZCx{`X3<+4_6+~}+iJu$XPu%Wcc+B>_P6|;W~lZuI{I_OrKPoRy~3umiXx+q=;hYA zyuh?9^iO%ppDOFST)F1ruP=&G#Sbh%C%zA=mwv=?aDORRi61g8IqIs(FngbNdLb0X zRxT%IY|s?5HEZ|BF&l8T;FQNvS*R#|$_n-$VtnMlsO_B%8!MDm5f(nbPG7Ck)PQ3n z6LVCC-IjSTmSb5Y&3)=eS}$F_T3Um`U2&zz%$o~QOitYtQlEtbr`caQb4<^r1lzo>*VUc(Sgj0zyzXast1}5-A74~`GW*lID&PRN zY6|er6CXf^9lYC{qG|*dbXN5vkY;?(eljrc2D;;jhzRbTcnn6x%V5zoL5QUv=zG^Q znhfe2nIrix@L~kjzRtK%=^ohP`|R}m{Wm|-G*wgJJ3GHV$%NOoT^_mAl;XF-JS(SX z*W7bGdfTV@qXbm8?eZ%|L=|Z0-gYBMJoY^`nJ_=?`{%;V&RE|liWn_Dt)N(mnQ;E> z&3y)pXhRBA6GN`>m{7WpE55(KH7DB0h6lAkJNq+ZaP?*Zc?|{Ar<+|r9SPsQeKq;n zM0vvpzNf8*qM&nQ1#^{*(SHMLU23N;nLZ48ZP>GC!l8;s?2e@V-*mn0T%KYJzg^ci zEPl0~R#~KlGBHrul>UJWG?82 z2^h2~acIH?x8OZYCDc-|*>G9?*V(Bq(W*ZbhO`7s_R4{#Yx3W}f9i(vgVE7br90Qq zsG@KquNw?-`!1Q8-`9weRaCAI65U3Dc{P@OAF!OlFr#UEk~>wVoP>L)aE$H!xl_)oLK+Q$`I{e5hI~^Le2Y4$;qb4*s;%rrlWmZ1a;@=WC z4`!wkJY#Qd5>sprd=*6o{wPbT>AN+2O2sO(Z5$d65r>0x%cvr3m5f6Y6~sAyLDSEI zET9e%Mwss=SR$Ds80CMmmfcVJcfsc%r7xp)(_!ft;w|=M(TW=4H>0h z<>xYQe%j!g3Vh23whefy8F69ji$E*Avv)@DTH#qaxHWJD<;wln2VmVJi+IMEzUAfj zCDtFMYgWx~xWToN=oR@JUtm5Ig&JRG7}u28$iR$1yd#2*oYO25bFy3&%b^ah-{v#spu3nm1nHZkTon43Q%re5qeyV z87B5xz>X5CEd+(N7|5AQ2`9f>wzB&CS|V@+bZnI4AF|R^$|jbHqgS%n`tEQpWVqY* zfNLu4Tu$G|NTei)Bf8dlA0*0cL8Y+rVnA}qog;{M1A7v68uYwTQ$_!v*; zJUiOrk=>$G)9GpnF)0lc?Az#4qi=X)v5~xu6ku80G52lA>eZ&n%Fc3^`Be){Xq+d_ojY;H?q(KC{7A)Bt5)51{&6CFq`kcsR!#0t zk5t(AejAeZ@T0MXMU%0IPu_THnd6agvJFeQn9=Lq-+cuysAG4~=1r>U8FPVFIXc#i(!k%{8D|)Qh z6cjZ0*w!UFRWEg_*WPyK(Sk3HSllSfXAavKi;i840ZyLz=i?5b_@DQDkAn@(E_l?9 z9T6`uow=f;&`Y$|Y}l!qiqJ|@9k%b75J*l_EI`w=ToEPQ82?i=wONphgE=hsG3 zt3+lK*WgqeQWORPw*eJ3UGn9P3IW^(H32iZlQ1088TZfD;Fx4*3oR8tPVF#4cZY>O z>-m2@kd5wb*0A9WuR(u{pU&fCe#*O)b;V(#yMy96xzHiFpiQy;DucIhEqZ_h*VuW8RSs6z@m~oVEyi*=*SB%;1>!php8E@0J9*l77EeKz1 zU{Z&0!broxsQ7=)C`Vn)jtFL!0xQ)f+}BzImNse9gwd3?@%ARdF_YGrEx#__I_HS0 z>2!w=kqJ(}>Hgm^otoR%9#&$W6W9aCBX%-E7mw;h#6Xhya+* zN-w;NISgA!c*mD?)Qv>9P{SWX9+nqQefOS>XsZ@wW2`7ZdK))?&goRoHma3%eJ+=u z@5Ji2c3KVCfKc3_X6~wHe4yJValeR18(sp+y!(0~?)Z`B@-%IicG}aWV}}l*mul2= z@Y>jlETBN-VHNk#+7%#4PSG_0bGkev}pMkvWBs}hk^5;BTp ztLJ^-8uxmB|ND9V|JU<)y?*!azOTzTzvp?J$MN~RKkt!!qd5&;fs7QkimD{t>6W46 zc})!?CWz4fY<+iQro{ zhS9x)cZJBk#FvgR<}q4$H!(gpb0Q=Oc)dbPaZ3)TY6pfrClJ+Y4zFG8%L5?D`4g6l zSHwkQbPk<+8KY+j4Y5I)qakmwi3Eel7%_mBsaZi@-Ve?2kpl<9E+=OJ|9|a)ANh(B z!_JFGng!dPZEurksWtg{Sfdgf*7#E}7MgPh;6j4u&jvklVBlI1dF~ImbZI$NQeCaq zczgGpJnVAKAU{~5f7oDT#5L#O!Gn-z=;yOfKT!Fhxl#Tab1U*5y2BJ}3*n^9tI&^@ zMyUft^m;n`%*STZkyTgY57I*?vB8KgmWx=n0#Kp|^$!ox;31;I$d_4qqBwJiDF8l=6U&6&2q#Hy)!5*t$iATI_b z>vEdI*h|ClSohz$xlJBsj;Q!5(W{>J!Qczvk~c~oy|6Kj>E-O_*VS!WuVIF>Pmlr- zb;AkRxPsFZ0P|o>2a~MYJ!X>C{Xlrciw|g>iGl@6nCFYVG(+Y;CuYsfnWPt9nzx9J zjg6x8uUEeZ4UUGw_VxVE41)r9m}7&Kh%aV*j1+_z*FCucX}g|(9pTC1#*9Wz14u6U zV!5Q?=8t~*;35hjq;`KxZbrr7&%l{Jp)zCmaV^AgYWA{|2D4zbjR>J$Z0uFweZ!$S zq7V@cRK;q2?cI$I6Sy(SQ?F+8$rW1^iQUln^-aLAaVYRAnuN@5%UtgsMJ z6|mQc6{dRY#>U1fArU9E7i`xd5J9XMnz#$K#VfE@gLL?4DbUc4z<7l6B7K3?tQ?fZ zS3!=6N-{1k>c`984TL9?1b==aWgFn(y>Emt(uXzP#e8hdUus{dX#0TfO4#5u6zH7T zL8h-SiT0i|KP?5>&~+%LzY!N;AMOw#~=m;njN|rB3D;}QleIu|3y?V zVG5J4q+R4R+XkbCK460qX9Z_ph)~H(`|hjE+--R?fj>jVD+EQEXJ2xz)Tgso;pc}V zb!)w#rUJQDXxCSroH?*G%N}c7NSceWK8Z3>)g#u0_==)@Yo~W_xa{j3;hzSi@uzez z#w^fWh}QiuNHIhO0H(oAOMv%Ea2}-9JlD{uqG&Ne;RM6v`wosXzc6X#pK98F{DrE6 zD*!7KN9TJOpzIpgikRvFjxxI6Qvu*s8J7(F(ky^(%D00QUKW zZY~5C)Ui0>fD?TtqbIm8b6w=xJmZ|j6?<+IvncL|EV%6Vr?3E+U2;*L_&z{I_mU8G zpKA$T`aM*c6@a#@8QThrzPN8!|8{ion=b zm)saVmM~MueGwV1TcLkl6e6na6uPs(LiilEL+A9p*)qd?2CxDcn}}nV-LuyGDS#Ec zhb0(-eSky8bcKVrK~pp{(GRn^r7RQxdoY$Q_lDX!I6TGaw-tl29OCKV4AJF16iyZ+ z!*wIfcv5G{WKs^m*6a`BuE?@_Xrzypmf-o@CJn>aw7g2nLjPq9?iBkhcS`x*V$I`8 z{C!DxzdqA=I=#Ek^#Wp`D`<7@C3;IU(83#6P{4natq?3#NtK1*@i9?^_oAL9^#W&l z2!(K*P1wb0ef!ZnYMcZUef5eJypW@61xUk6?%+9`0qK~}G(X{0B)5101wM9wp|*N37N;euXDeJeuS(Ed z@7M!6vj<$t>y`dHoIqgJY%@>7>QP{{OxBF^>BmEci;0MCzzX6A;RVGsrWJh zD~J95XVJ?AQaU)HU(Ve^x>+2NG!)3~MAWs>Fs>@?KRq#}Vt>oyru>5k5Sw8V#b8f6 z9|ai)g*Y4Lo*m;FpRvH`*Ckp8MMp>Xqgj!KQXNs+1DIyNCDe&3;+h573TNXlBsbAs zTVBQNDBNB?;Jh`p28qFDT{?O`(Rq?`u>W4u5!VWhA-E{Y6!eE<~(1lQ56 zaJKCR4L20ivWRdL!Q-Gd+yQMRAqo=iN7$j4QJ4^_#6;&lJpj`Mij4g%8ks0`elODb zX3m(vt%ubE%LDipsi~?8iR}VwraISH6W9-2^isuh)R+|zIHw}xI&_Q#leDAAE5k&o z>I*RKnv)og#gzMEEGjH5ZB*yGUg34U#8qO`Ovv- z!oukloeFDJz-hhVx+DO|u=04G{7`Cjmy4U*^JkqRFYpKGC_qF+mrxTtd#stMVNreo z`S(>V&oV@`bo~G);mCoaTyS%=Ow6M%$i{5CyLvebOK7SvZXDhMbYK3l7H!fHVu6t} zHRU+IhIe)s3^^a;M^d?!s7%cEbddISwu3P6s$RU9I~|77tHCWOs6sGiJFuLp&<)Z) zH@C3hLLTU6!OZ~t?yjKl_cQTk(RN+{#!9@ULawg8@#dY>><89gGFgc9kOu&^b*T~(rc^(l*%k2j(((W{(`~@eT0%lXFz48EoXgF{rJFj8 zR^h?;`*qJ?w#i=vH9uH?8Hc1n-T9bDyBN(GiN9p0njX33+F^?pce#2Gl(sU+9W4`g zcA_9$=^w&0fBt-8;7kPCXV$EoIgL|RKIG0oGTAE33%A&l8XKD&$@Ff=v@)CoK~5ja z2yPKX?#)7KG5eNlAkakGXkEoy+|tv@%`}$=&2^QI1;y$g7?=Up@5RWal3kHu zfA9@nUS4)r&m91Kjn%J#XK(_Ruy><2AH8CPedyP>-@inR)v;eKX$AeTfvl^_F%=?S z!omswn?VA8!Usuw>eKqEzPIGP*bpBE2z8>6JcyGCKBRp$?#44%EW{1WcZ zE`SSj6!Ev>Q($ma4G#>|gU}-(A%P?T4dNJ>jRN;Z`dW+NM)b6i`T%+I0K~q83&~+S zmoOaAM-jp!Z4H3&EevXc;M4?Tf5L2VAzWb;90@F2mndN>_L<-$9+-aXllKl#Hki?n z-6j&2{xJBpFNxnF<1GPGTqw>bZ`jdtuAV7=WBqpnkQGp1%{~d1-3H&=6t?f6xTuuB*u^J?WVe|e<+kXv}y+dpUi=`0rh1k>(J-_uob_&5|Hr-2jeT0 zoqL_M`GtpnZj;mB<{#w4>8ftoK6~D|vfGX_%ognWJbzjbnT7s`YWRV5D>r=+ysWgTcdN z0+~LLIbQ69|J-0)zfRoLOhHb9a3K4CY%uZo6{QJrrX66(;3t}w z8Af~zWOXAg<*K*k?8*G)(|`Qb|MqGFxiF9=64@tz=+d^Aqc18O8X6Lll$m`p9pxH# zDAdWz@R+T>Ve5_f$d! zYcQy{pKSa6>nH@{^sT=EL11l3uw!BPjHuC1^xzyknU4Bz6?M5!(;(~)+o@27F6WAIiA0NF#sct zOg1Hw21r{hBGpqH=+(8^Q z4wlCQa3iCqutQl~w_O*jhqJ2{>1V-o6b2~!BX+oC9jGOoPZml8WAs+k9mnlHT?2c@ zj4*j9g0%!cCgPk06dXs&`jh`aSb7D5o&#Od9&dq(!x5+59SDyJo&sN+XysrLq1k{( zDC?WCQdM1j4WNXU1|9Iq8rJO|V2i!rH=JSik zngBa!OXs1JF+%x2^e|EBp03jv{2{U_X13UOdG>qeQX(Q5?$W!_Hq%Xg-SU^PtnytJ zx>eA&H)cg_cn(>C_@vc|s1SdC@Q;b5%rtKrEQPF7mj!dHny)0e-lDCB1ot7dno7wEquJZU)cKxQTg$o?rV-;EopaW7!~Vt z2S>+^x*Vilpl?~TYLyTWa7kJ((38m&aYur!ULrn0TxN_Mi?Z&U;>a^EJcPR{b>2A1 zcU+q70LfTa-~p%LVwQLgHevG1t2S&Zvno99#82BvQ-Zp8geiJ$*dVWn=1CRMx;&a!*M+OE8Y^4`WXsd^eK|9 z?Vlri1+DGds|tu(p4t=VxuNWB=kcSH5`wSVU2p0+2gz}mzpjKZ1O{PCZD(``;3x9Kg?7f!-2%yd5Cb88(tb}T_lUn=e< z?VLG0#3kvWT?f5qqj?F$QGokNMw5wYDWl|U)vyp-(K3V%k}TC7(1wVCRfPM&OS;KV zfUH$0_%Z7TJ|9`}8UKseA~mT7U4WN?3kDZ#aGp_jedrEuH^ax8SNU%o6guH40rdIc zUv;H(yp;h3*YepmaSOI7gM*yFZnvzlp?iI(- z?x!$dB4m@iEB7FuB$B56;GhR5>llODb^$Yh?N}F!Frk|M`*6EswfuP#$1i6K=96eR zaKKEXyM09R93nl(KJ2W|}Z74(EMl<9=yVeESK6woFej834rC1-3$s|>uGCZ_3s zm?>~0Xz#UVyp|;Eu*k-Z8yiKDS|TWN@WI~26y{U1;QOp5f;9|-y5KAF@imxBzNi8L zAb%h(pN*JZJXM_+d>mG=L^wuFv;n%Q>do4j`kzI$5X;Ynv4B0eF6^ktU=4sX-q04u zoPvb~FGQJ}V0cuG&1JJ%n$#+DZq)5r72u zQArf*TKDf~6F_9QgAD`r$xxAc;<54C#qA7pSxwE5dkS!&8?YjYB&m{RI6t03=W+>U zSOc8vbzR{h&J!wVyk{m+l8eg~^${nL*`JvlJu-41{E4vceh!M9z8ND*%IMGF7PI>+ zUX0{5H)fW4Qdax_TzyKCHe&|k-t>PSKZ=5g=fHuth& zwj)0BAp!#$7;OMHw){WPd?3>l0<&X({bte$ktL5Y!Um(oGf&NxcD2lm$vYV*N#hg4 zlA1VHQsAsDj1ThEn%&;$raM_EKoDFDa=PV$nX$cJ(h+nCGv6~kkNPiKq<`NEhFnjl zG!?$gxK965=L9nly!wC9L%d3l&Td^5g7*^9i$nf>7~3i3^=oPZP%r2VGar-79#c5M zvsl37tfjmlMEm0@u`KwHv$g-?vrM;ppeN+!=Kf=!dK;w)41DhW`~K;Vi;$m&AVPOx zqeW|Y5!*ADfI#J6OJ%wf0~edi5nSzmiD~@pug~0d3mcoOD4YHX zEC6o5l%4&=zwcLj1!P0t6S?*;K(wbtHLM|+lv_jl*DaV_H$Y9W;cYMk^M{B!x|(!}zIbeqnYgQs+@ z^w)@Y9Q-1;;r*FM`1RFFPa=)h&Y@_>J*jxP1~F-4+Xb|@m(uYo(9mmM;HOp5ydcZD zi+L_T3oY}Jea?e6c@fc}v6Q#$1%67!qv^=xH{0*AJFm+!!>yN@Yn{L3$dubohVrSA zGoRnBp!0ju?y<&3?{i+;=*l`+eB7khoSX8j5-vGQ*REAvy5`&Z&mpyr?z_F#eQ7({ zv}a3mxwGfTw(>b~6Bi82ohLV6%T{~c_UrU2rqBrPp4)GfG%UHzEL1CA1l_JVGnuV; zX~Sg(4r&tUFS5|dxquR+h-my(T1$ys$%WR9EbPxM+ zMz6W}eAbz(8!JCP?`bs-GQV?FnWEV_uC{3A8!Xy0 z5mEd5%{c;oRb;JTv)#s!8_g@laXZwZPDP5!icd9d2sqD7Ln~h|p;S4>R>5&+(ru#c zQcmGWM{nYi6KifPUu*N+aoqJ#P8pn zrs(b*bGRE3y+|f~Kr~?O{Gk^6KFj;j14W9C=LTHXv}L@itWc_@=9W~7BENR@VR~NA zz&8cQkB(aml{!5PM@L+$Ck&q)+gVyR+-_H4Z@8azz}u*#^O1AyLC=*OuSQosn_x3G zT(!nNed@=Jv-RzChZk&mtw^JoAoF_Dz3S0ldzZItF(|r%7c10vqrFtjKDDE;=X*!M z7eUi3M#aEl#%mk&r^WePFHQMnLv=5w3?DOU%-LHT7(o(9Ec& zD2p*@gWPqRv<-5uhWms%LMnf%zufKW&Y*mqv(kC;cd@yd{(Su#`$d!!&+khtUVF_k z-}BoI8s$Wh-=k+d*h*`qx*Q~b%&*AzyqdQ2D)07HS&U0r=<(0Y?W<^{TH9TBq@Ta* z<{lj}A)`O?P0}yvQ`Y{Anc143C-)1iqo;MK7@H6us(wl1>>mB=Xlr}gvk__G(BcS_ zgTFk?1&%6R{qF31vnI)^rZ&&mbZ=zs>oeoq1J=sD_ptkRcs%0SQIT&0-8fWO=x}Dy zD>2hG`O!B~UTN```_KHv-{PQOzWgPNUixkM8jhPQe~VN~vhI=UDqS&EBvndx$#!X+ z+&-l{a#0gMR4okG2V|folR^~y*xNu zdbK4-*!pX;tCyZTaBA7ZEN5v!n>5fX~78cZGM z?xLYddKbOE>t59<(TOdujIC@t;uDSEE4!+i^WgcIM88$WryeGjTinVT z$E+!;zoGZ7PuE4@9_Hyz+~A9KT$(NiLfL&d@!+)sH!%zBLZ$~XW+SvtW^^3qe0_a| zK?Y%$ad&+W2%rSzR0#2Yz)9r*Tg7XbLwH;1>HXlFzZm^CoDMT!?#0@0 zB8PBCm#~S+wjDDl zVGtx^+jZ)51ydQ+fPLTIIBpX=JVd`F9crrB0}=j%LcfZ<13brv>NCvg;0r~6d?v|Jkw?xMgk(W?#*~ z-5?zh3>iq55BXFj$wTBkfSJ*ix?CG$UvQl-;kRUi(|-#NAnT0PuYzVR2+j)>wf6}u z;Bad6w9f|~)$2*%A(@;e^ld}~-JihId#yem@y85X_s!NcD<+by+5%z^RRX~ru>$r2 zy7C+@h~X4YauIOAD-tBif5VqS8=s4df*tUNS0KlD!4$j|j5slCYwJKL@{kzB-*zMw z!(0qUohS@w=SZTjB8qQ3Y>Q!#RDu>$nBeIEM~y7*6kCQ-)$XMzF3^_#ZBR1IESfb< zAi8hUc8H#yK1eOJyE!Kn4jG%rh|?NqQ`*o0y$W-iBn(EXjY|^prncv#^%cLZAb=*J&ryeA(aH`GJGj%8_<9KG-qiqs3JjbMs6MQmq``oq zH

F8(IdXvWma52jY|vuO$S9xxRh$-B*`rw>+x2$3qxQdI6V`6O-84sp`fbp++2 z2@ALkMKcW*<@sxjwsVm3f=z|FaiMelFmCN9^rRQ?Ay!C+A4ds0Y+YHUe)}8(YbGWFnux%Um^1ha!#}3N&3&r&%j!}I375iX z{TNt8MeBEXMVRdP@@tj^lNdSBp0xLB&v9KgtdE`lvOX?gef<3NJ-mF{Zu*qHy^z>a zl*`1#sm6Kd<|{T2jR5I$)y9Yf-@JLV=NG`v?4Iua{*(N{;B@Fp&vIbz(#Zvu4=p!? zKs=`ox*G1}=we`^IYCL=A`X~K@G1}!t zeLccfFquE67Ym*|O2KG4%rh>jWSVqe2<~GTi$B!o?7m#y zGT)%qgIakmg=uT2R1r3L1`5zEzZi4V+JvJR8ThpVT}t04#|MOX}4 zL^j#;nVFfgM^7zWv}p4V8!W)hmT3pu?Ve45mpH?60lj0I<>L9PaE9fA)@!Em4q1iU zhfoe73=C)}!q5}Ed-qHM=8TY{o9HeHAk2`VM=I04eFJp6cR^2K=gl=kZo!r{0RAJZ z#3UKjkc=MMU2s5o6CtHF0RH7i_!Ge&+Btk96By~_%ltgxSYUC);ba9xLUD+lnHr;d zp4&(|glC6%C=xi6xTbi5SHyT| z5rZD!p|qFAcdpLxU7%dr178Zzx#1RK^B21i1kyp+u^#h^XfOe{%EqQJK=|6`$0rDA zh*9p`-6f!jMrK%+U~m%x53{Ag)80MX0g>4Ng2dS=3+WN-w zFk;w@Snb8r?q6eIjdk2&8iv=FKZsN3QZlQ zZ9mBu0I*Y==H(FYyv@$v56{P#1dKf#SBhyG%u)*U(?>aI#Gir+U5KLZE6zz1u-dDj z*0cqtKlZGgc(uQUJh8G-m8gEsaZhcht7tcOw5Q_(fwO@?t?(wuS%{h{QSH|CZ=-mc zp;%nk^ffr(|4X=1Gn5w0oJKLp)4{yJK0QhRgM2V_6f7{PdU12X^%>}@Nd%wvB>^O)6;3e#BZZ*JZ?JdxJ zn6r`t*Jbc?FJ8hSsMG>WEoOlw`gL6n`OK<%pN=QiA3JOsZ1eR>;@8ov4e$#~()Jd% z+Ajzt`dm*>Prlg+w|d}&Qjl4g-)o2(L(P@cp~^*}L=*P=Uqpd8X|=56wAlq!wu==F z`@?Vcc>#ZAFy`W$L(JD{8I-dsFvT8_;qbiQ?796m!4B_2ZV0L-*=#`O5}zg2i6k0rr-!%6H{M!T$VcZ!7H|K?flSU z(hq_B+}8z5n0I(z%TX0=2VqfCEo#Txe$oKX7Ez8W7srUfqM7`mA%_+MERWG%yZ>08l6dLNOPI^=tL_F;!l zumB57-{pBNd=!Uq4&4{+Y{G$F3H=dk@#3UA+{(m9q<0$}jW;9_&c#XeT;Xs(7Rcnl zu`Eb3IgIPJ<%9Br(@W>eB9X$|8@*4w+TR6T@4bV0cIIclsS!^jJcG@IVUx8uLfTCi zQQbLllASjtY>y1%s2%#;I=?HKnVmf)Vw1@^4??*$M^4|yKA4%t;pX$^9(>h#ht~N1 zMHNu!HHXJ{nLz^l7D|FTDE|)@x%@~N1$*S}$(9FdV;Ii~z)i_27ioM&D}$9G8=|F% zkZC(SZ+f3pfxDyE#cJkiM@Eg%I=W1dR+1u4R>?`5`Vfzl-B4VA8Y&SOdK}V4lNApl z5Gi={JkTqN6YT^UF(P;=JY9>W`Qrt_vluoldiIUb?t_t4lXT5IufzkJygWr5h9*g2 zut+Tcg1oyQ=4g87!hLI%$GVr$#P>nEzZEMaaIK6x|0IYPRd|@qyxiOiKpu&mBBu@j zia`$Imme#$)Yjhq)keax0sGQGlldMzQaT8mcH+b1IVOX= zs{}erJTY4$VyAs7D$9uRGO<#&YHx4%hP0#^F%&YbwY4H`N24)sl(UH?QXQPh^poz) zOP7|2YgZDQIbr;GU=p}PTG|n>;DZDlsm-tpuK+(-!<}dcv5RPfnyxrN-t;LX^6+Mq z-tD#rpHU+m$F8p(@`Zwu_abqkg!%ZHBAbbe zsJZEING4)Wf*AB8Otv-PnW5E#`#(3VUjU^%C%o~Rl;i4eV@UH7KNb}@QtgiQoI+>{ zG$n*J3pC|KOw4K>t03;j)z#GqrDcPX3?)gmE7WCMuxx9fOD`G8LZP&sz#_z+&-&Og zem;*(EtC^Px*x0<_ZXAF90Ec#PLqZS6hRE>-@9~jR6_`IJww{D`(@cjd>1|73*0Zk zM;L+T2_LX&&F2A=YMRr8;nGXgMvH#2hY3dDQ$d?8kr*EzgR6N-%0zk5kz>hZe+8Q4 zxECU2Rgp&_QwaI^TyWb6?l9GaI+d!y4}n&=L7<3k@+dYA(_$=wcywp|sJgrH{A*bK z5vTaLpU}`~O3-$`gkeH-YCw+1zE5HDg3EC>KcFE1Uk~zTjt$<*#tvc7*A@0E+lF|k zI!g2QECb-tRWLO+G4bOTgP3xf?s^D%q0YOZ4x8^^U}ab=lKogZ4A|%dC~tr+05BBn z-M@C_(HCKXt#JKkql(7A3Kc2p*kPWH7W_L1#>D=m{rnP{y3dbRitM$s!`Yv;z>4Qaif8^=+ zm9%!U*I`96nC;GE2Vs@w&OIaxBzy0pd0S7uJFo^;Pxe$71K}zj+bQ9bw`XK z@*wCH4@ES*E!qHQu$OFd*Ejq6v!YpsZ@@c5r4=2DH?XRM2o&{Q(unpls<;w1XBad$ z(v}dIEJ7!8a2))62E7Lf`M%4#AHfpiEecAtB@7H(dY(Y4pC(AbJ>-!#p;@)O!QOan zF-5iyyw{^q`jtPwwHdpCU3ec$=*``zOW2^XkJA(f%O9)~_RhjUF;Sr)y zDk!)RTz1dkgI-+B(6JDIk?u^p6p%8x?*(A_9~#i*BJP(kY2@7o|Id?1u|Jz3H|~U- z^^=1h$~)bCOwt@N=GGnuap+YWB$l+k4&w;sYlgwSRgsgKb#$Fo8Z(ij=odkj#bZ{_in%&y_;y8u%Bsemy`_Gkr5Zy^$Q4KriiN0yGXISt(WEl zKpaEKJInS9h7u0|F}3O+8rOhg?B3bt2g{xF2Ze^QM;tXqPRw8e*P#$hIj2E!SOW~=RvUq5RAg-s;PPqc4ut{C}NMsPAdL!z}=)8o;t z6(OV%0~>{+K%V?!$=ng^c^Nk6r%$(Hd7ONOlVv{EdLr(-1o;0IXTY0^cpXK3HatcQ zW>hHj#Yc;DTecEa zBKxnpp$Vq$?((O!yKtK#w6uOjvPwaU4jC8gHmj!FH<4&y0T5Gv^)}Tn1fl&{U3A;# zZ9rZ@K@ZWB6)9>S8n-c}A^sA>fJE||D^Pib!c$B83Op{|;FRzcBH-(89Npa{&R{#* zN&roT&_m?$dS2&VNSX+$gqXND@_2-!rv&MQ$o|#hyOV$5ENp?;3Fd8IZnDaXU;_f2 z4-f<5gp@*6pEj6-5OPWxEO)dC*lUHh2qN3m^yMjw=o_%>8FH*t13F|jl0-&*#|>2>45CV7cSvPAfJDUq#tn>3O-)V5$&$xwg(9fP z0-L2cscoz=MLOg;?BYZT(SsN44fwZ?KO)vA;CwgtLa__z(dMa9T& zj~h-X7|sMM=8-W6-k%$wD<@a4@}mf~6u8`t~YDMaD8Tc>_WkHH%lW-_1HKE+9-nz5LnHu8z$#TJm1x!g>v9!(V# z#{_jT0xouktYh@Yw;)F%EbA^lqlWBfwaSHFx3$2=T`wod!NFm4Y}MxX@6ij>Hk$GY z!Yp>jdYTekD$96>=tcUMPw4XfncQ#!P4kcJ8!V)i zZbGD!FE*P}Z!1mHA3vWTS@AP}=wZMD3XN{?qJy+yf)7xg#Ngm&gcl|=?xYV)f{vo2 zPSqCPfliobcg67&_$VKGfU+ci{ zJk(XVm3Y%yp%5VlnwdW2bF@@NmSJy)e&sls554hR55MOcF5@_d{D|6U95F{vsj72J zOShgn=*c7-st0B}G3}c2zIzhq4I5@m8wX{@(L>OZ>~w)eJ;!fhDglNbKh15dpvhis zWgc$s+r(0zI9|bX1ztCSNKgHtHQ|BNdC~aDwjyEuM2yN*D5T+>f_Hc6{A1)*N}Vt0 zNbu^P0#5J4uUy6)3RNorU-ThaATN0L*(t=UUpFU&YuN2cA^bYCE>9Lj#W>2YNK`1g zSyLfFE16vFMyeO8J)NeqJtSA4B3vjLv}Bgy>Zqz`Wb_JFE(f35P3L+m2`;gNo+^6=->j3O-sMMZjwj7IlG?#uIw zU4TZ(u+Ui(cGG!kh!v@{L%*PV2=K&6`;FJ$(dZzHtPC8cynX_Tr*TFigUQ@w#X$s?jO@`~U;Ww+jikX?4ri7p(GKd*o)xK~ODw8A< zeYF~Wf(Zz2V?R2N>fg;8dd)8F>hJ(jofsOR80IM#BG9DqG7V_Qze5P&l}K+i0B^1LA6M~ZToSEY9A0%spz2xl8_{m zjD=F*dkWMj%A^Jq9Py+46+vXovlRr~d0wQ(_yR|&9JRfBU+8=R8TSb0n;b*-hSL&F z7ro8oUZ2~SxOwWQkmf=%NtlkbxO+FEI1mq`3NTbY3OqkjWqC!U>*CniYtxXzQqUKu z43p@42cf2=r?3Ku8VF1PwbMVp>UbATWMWc60(Tgu1h>eOJr1IYNa`>uczx6kHLPJ| zo#RbA{W{~E4oYn!vTX3n7m(PHvk|s$&`UkX@y}eWkgX*cTBV=b@Cmd0*BcHMO0@YF z5yavU;wa^cO|ij8Vvt6n0W{kjjgqyAB=*GA9VhKEP~FSlI42$5{XCu>4$*svzqtJ) z8|*hI(cbz3%{m()+(*bD6tM+IBy|Lf2QK1nE5;1GeEMmIM45LByB%9ms2)GFLe0nk zfCvOU=nEtZ|GIU% zPUBP`BHCrRxuI4kzPXT3C&{k$!(pk4Haq{tIa;QYd>K9<^r1++vPCR-8T865WYIwi zHv}6GJPE~3oJhc+S^%QHx_ZCI=00qj1#;e1H8p=ZyTzl26cT037R=)hBOfV!J=ZA2Wq z>dy96&|~Y?5AgRFe6wr~OuPes+WUo(a29fKf%$Io(2q^x)a?hT86(1qg7Q&4B>ncd zMZi0Z!h|!`457Z;fqAYoUMx*!1>z;rkJtVI{t8dJi1ZXtX(2dm?qfg~M?gg)DuWkg z%xi4uZ=WU9IWpKK_eKZ*efZ!hFgxe?FD0P=epd1~!=nFru=4-WH!q;-;ODz%_tJ>V zzX1SLrf;bEN#N4KDAlENJF_HM!Ccx+_{}JBb%aslb)j!ERlWlIX5#xg1V6kG-JGNl z_`&WSAe}002|QJ@NeFau<{%k*RO7V|ozAm!fw<^Ie9+q67yY`rOHLD`A3+@?RBPfX zrOQ9kgk`G(gZzf0=v3a4)S?v>s0?2Na(Q@oKB8!$r@#pdCnf z3tZ&iYUhEv6g;wj3?ivUn5!7+IaHqW#x7{=*0*=!yQiH{!L*zN1-K$^XCm0ix zh$nJgv8SgmJdf$A?<6vY^lCW{k1!VWe=n*+?#_&Hnoh4Rj5rsY(RcN*;kR3&fZ!og9CN~MivOFvo^CAv zC*0A@Rs>3#D3k(bIF$~YjFe4(T;{Ze3867UoY`^aBMy+0=47Hp9C3AAq6@Odp&Pcw zMrtvK!X$=nTwLcKgI1ukVEQdidSPy6My!Fjr~@Zl-r!93r|Pgs7+VE+Fx$|h zErA#`8-~F*IsLh?b&3LUbW6LwAFB8IS663fn0c6*32_VXUCMl2wHZ$J^~BhMq+Ky6 zsMN#oX$xGeV-hf!dkGvnp2mxqVSlNOwz2Ha$7{yoJ^Ne0DJVU9g)h0 zJFcx;w>Cb2Xq?QBcG!OjNYYJJ>;DE1K5ToyX~NzyQb0?eK>nNd5N31948JRCY80KF zAD$xnKN)#qx{y7r3NIEyfrC}_it29Ln1&O!K#iyExWJeW(rvMVAo|52B+Om^)d}BG z?o?D3Vid8b{et;|@MtA$99-#VmcCeGwEA3Xh_zZlGqoY}-aXrX@bgM>pF?n9IGw<` zY)VW>X=!OA5Q3dSd6DC}qo=3mHEwJaU`M1YwQrSsX{1?9S;t3@de0maBy(Az^x3!@dyt)J3AL`xW9H? zoV`1zacAi$B%#IN(k#5W2&V;3K}_qBQ+d+oipd|;&WM_!(Nu3>3RDf0=r2l{;u z!kt<-wup6Pc{BS#e1(TVc+-r$$rJZI4(_SYH?E^9e{MC^;a zx6bp9rBy-8)Tn!rXIE!_Yh>Sp4Ab*&GQZe*f4uFKiJs``&l$OzE^LlAW*7+Ye83{T zEtm>_z{E$z+B&VZ!!90*yu<>-^j)T=ao@lWWdu1z?JZi@+ixykzrI3E-i%m;r)WDE zH6r+mo9Ma~Ei4jX`D#3lXowF&0s_<4JJ#4wY-tq5aL*E*92}Ay9UbSOejMV!?93DY z?iU^y$yMvty={6=HjYf?9>vAUO~Jd<9V4th#M9~|WoPf#ho_75xVxo{e|k?dF7p)% zM{$DqjgU~jIsUN~;3*-UxX*M@G9xpyt_cP^#`1UYlgVjmIZJ+Sqo@Fumbyd=|KD)a z$`HJmsa#wz*)`Eo>n|9`N>0D%2NMK-97EmN;%^WUu;a5+f%TybJ!>ng`#BFD45Je& z<>DeifETaen#-d@ zIrvV$Gi!?5MBs4r^%jsq0j@#dcRq44*LvZ*K1Cdu;mxyoQ_#e3&xSW|=Ak1!L3|yp z@%xVJ=;*{idKp@oN_pA(sxVR1rj%w_#M;Xo5!(9e5(_g9pz^f^Pqy*#l`}IPKX&Z< zL1a`Z85rCI`BKH8`9nWbk3s#9y>}1Cq@)Nkb6{(SKrC$J46j?kdHLFn0~1q&cy7-D zn1n)6wg2-;H?^SwZ0kuN(hXt;Jr*msUPs3qQ%FvxK;lsUCE`Hs5736k({?>Q;=M64 zGSYNh@*8=WonOA(AZ|C-I~rlSxfRr^6$_W%)i49Fbeq%bZza76u z+m$^ua}7=|%V~LMb1bEzIMvg%#cc1Z5IlsZI#>6CUlL$l&6Kw)jNf0Z_vhoYyLR@qw>2f<`1$@;`lEMsgO9=I_vP!?X1Cy99T**LV^C{z)8few1^Fwh zC66r6Ygw@+!cAS*#3V-3*m&nUE-{QbINPG}+|TY=>C> zy^xUQfsxkc<`fKi_UmJ0ut|I=LDsE+*Jxkz_Ydg$$|}T+)5Z1Oi_16>&16rX&Lu&Z zfoGH7{NR@QjpM5djL4T5Ca)CUxRE1NB@n;Y9_&EnC8u&)`Uvkbpt1yE$v(0Z3>3BU z^o}*^hGlzR-+OqiG#2YBG?YhIr)(lgmAWMwy1L@W`ZCJf z>&XdA_Qb&3+?uy8m)+zG;@>pj`luP}CkN>Cg^|b`H;y8sbREjIyONRn_MpZc#pFc; z#F`vbvGZW=*>b;2d`+s_F<5+Z=C)YdOGRE8931=(+_@nkfp_E1ooysWQY61szR}SU zO@oE4?LF9U7QRSHNx5yZvI@WMSkJd_H;9ge^^vPHH$b8|V&%c4Y#2DW&f)ZFp*Mxh z6!nZgW&wE|M5h8>{sq%*NuyI|lJDMSU}0q~_vkJL#-L?n#1U}vq9}u06|7Y6$?d@b z4#x^!0xy-*txZW$QBzaql-{eDZX}iMTHI(Icac8#XxLBsh9IJ-z?ziwbA(dR~Es^lD*#`iNv? z4QHt@u~kpSSwtwNL?1k7$)`0GrDPtmftI4UW8&i#JP;I$!rZz2?Bc~!8R9YWcR905 zTm9!y)Ux$tBUd5;Ez3agHH>ckb=cyZPpQdqYwLz6=T8?OipNZhWpHL=gBcAIz%Sf1 z570&Y2K_AV`|vrvqqjmG6f#4U1N^RVhNME_?Xg{{_~C1T_Ev diff --git a/docs/source/_static/images/examples/tutorial_05_trade_off.png b/docs/source/_static/images/examples/tutorial_05_trade_off.png index 4e5461d95313ea755b321b44ac1aef3da480591a..469d2beef80161f50a223ed9812252861a9111b6 100644 GIT binary patch literal 113207 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%0Duao^-WS}-}%p@PDU^fEsE`*`Sx|JEC(M_Th2 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 TqJ51OgC9+G9ku%#cc1=WGxp|e literal 107933 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 diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index bae78b9..260fe50 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -1032,8 +1032,13 @@ capital recovery factor against BioSTEAM's; and the swept optimum against the closed-form total annualized cost of a two-stream problem. The facility's targets must equal the synthesis's bit for bit, with and without approach contributions; the network report's areas must equal independent -log-mean areas; and targeting must leave the flowsheet's streams, the -agents, later utility costs and a later synthesis unchanged. +log-mean areas integrated along the network's own streams where these are at +equilibrium (the report follows each stream as the targets' curves model it, +like for like with the area target, so it may differ slightly for a +point-load stream, held at its outlet temperature, or a non-equilibrium end +state, such as a vapour entering below its dew point); and targeting must +leave the flowsheet's streams, the agents, later utility costs and a later +synthesis unchanged. **Doctests.** The examples in the docstrings are executed as part of the test suite, so the numbers printed in the API reference are numbers the code diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 29b68b3..788edc4 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -2400,16 +2400,20 @@ def network_report(HXN, targets=None, **options): ----- The network's 'area' is sized as the area target is: each exchanger with the film coefficients of its two sides (1 kW/m²/K by default), - counter-current (a correction factor of 1), and integrated along the - exact temperature profiles of its streams (their curves in the targets) - against the process duty; a utility's temperature is linear in the duty - between its inlet and outlet, and a furnace takes no area (as fuel in - the targets). 'area_biosteam' is biosteam's own sizing: heuristic - overall coefficients (0.5 kW/m²/K, but 1 for condensing against - boiling, which no film coefficients reproduce), a correction factor for - two shells, the log-mean of the terminal temperature differences - (which overstates the mean difference along a glide) and the utility - duty (the process duty divided by the heat transfer efficiency). + counter-current (a correction factor of 1), and integrated against the + process duty along the temperature profiles of its streams as their + curves in the targets model them, like for like with the area target: a + point-load stream at its outlet temperature and a non-equilibrium end + state (such as a vapour entering below its dew point) as its jump, + where the stream itself may glide a little in the exchanger. A + utility's temperature is linear in the duty between its inlet and + outlet, and a furnace takes no area (as fuel in the targets). + 'area_biosteam' is biosteam's own sizing: heuristic overall + coefficients (0.5 kW/m²/K, but 1 for condensing against boiling, which + no film coefficients reproduce), a correction factor for two shells, + the log-mean of the terminal temperature differences (which overstates + the mean difference along a glide) and the utility duty (the process + duty divided by the heat transfer efficiency). The capital and utility costs are those of the whole network (`HXN.new_HXs` and `HXN.new_HX_utils`), not the facility's costs, @@ -2580,7 +2584,8 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, Utilities of the balanced curves. Defaults to `PinchTargets.utility_targets` with the default agents. ax : matplotlib.axes.Axes, optional - Axes to draw on; a new figure is created if not given. + Axes to draw on; a new figure (with a constrained layout, which + keeps the axis labels in it) is created if not given. file : str, optional If given, the figure is saved to this path. dpi : int, optional @@ -2636,7 +2641,7 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, raise ValueError("kind must be 'real', 'shifted' or 'balanced', not " f'{kind!r}') if ax is None: - fig, ax = plt.subplots(figsize=(7.5, 4.2)) + fig, ax = plt.subplots(figsize=(7.5, 4.2), layout='constrained') else: fig = ax.figure hot_color, cold_color = '#d62728', '#2e6db4' @@ -2724,12 +2729,17 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, H_hot_end = hot_H[-1] if hot_H.size else 0. H_cold_end = cold_H[-1] if cold_H.size else targets.Q_cold ax.margins(y=0.12) # headroom for the utility labels - ax.text(0.5 * (H_hot_end + H_cold_end), T_all.max() + 1., - f'hot utility {_format_duty(targets.Q_hot)} kJ/hr', ha='center', - va='bottom', gid='Hot utility', **text_kwargs) - ax.text(0.5 * targets.Q_cold, T_all.min() - 1., - f'cold utility {_format_duty(targets.Q_cold)} kJ/hr', ha='left', - va='top', gid='Cold utility', **text_kwargs) + # above the top of the curves, ending at the right one of their hot + # ends, and below their bottom from the middle of the cold utility: + # inside the axes, clear of the curves by a few points + ax.annotate(f'hot utility {_format_duty(targets.Q_hot)} kJ/hr', + xy=(max(H_hot_end, H_cold_end), T_all.max()), xytext=(-2, 4), + textcoords='offset points', ha='right', va='bottom', + gid='Hot utility', **text_kwargs) + ax.annotate(f'cold utility {_format_duty(targets.Q_cold)} kJ/hr', + xy=(0.5 * targets.Q_cold, T_all.min()), xytext=(0, -4), + textcoords='offset points', ha='left', va='top', + gid='Cold utility', **text_kwargs) pinch_T = targets.pinch_T tol = 1e-9 * pinch_T if shifted and all(T.size and T.min() - tol <= pinch_T <= T.max() + tol @@ -2763,7 +2773,8 @@ def plot_grand_composite_curve(targets, utilities=None, ax=None, file=None, Utilities to draw. Defaults to `PinchTargets.utility_targets` with the default agents; False draws the curve only. ax : matplotlib.axes.Axes, optional - Axes to draw on; a new figure is created if not given. + Axes to draw on; a new figure (with a constrained layout, which + keeps the axis labels in it) is created if not given. file : str, optional If given, the figure is saved to this path. dpi : int, optional @@ -2780,9 +2791,12 @@ def plot_grand_composite_curve(targets, utilities=None, ax=None, file=None, ascending level from the temperature axis, each a horizontal segment at its level as long as its duty, joined by dotted verticals; cooling utilities likewise by descending level. Fuel is drawn dashed at the top - of the grid and an unserved remainder in gray. Each label sits at the - end of its segment, below it for heating and above it for cooling, off - the curve where a level touches it. Artists carry stable gids: 'GCC', + of the grid and an unserved remainder in gray. Each label sits beside + the end of its segment, on the side with more room in the axes, off + the curve: right of the end, toward the pinch (below the level for + heating, above it for cooling), or left of the end, beyond the level + (above it for heating, below it for cooling), where the heat cascaded + is at least the end of the step. Artists carry stable gids: 'GCC', 'Pinch', 'Utility:' (the segments, 'Utility:unserved' for a remainder) and 'Label:'. @@ -2813,7 +2827,7 @@ def plot_grand_composite_curve(targets, utilities=None, ax=None, file=None, """ import matplotlib.pyplot as plt if ax is None: - fig, ax = plt.subplots(figsize=(5, 4.2)) + fig, ax = plt.subplots(figsize=(5, 4.2), layout='constrained') else: fig = ax.figure hot_color, cold_color = '#d62728', '#2e6db4' @@ -2835,12 +2849,9 @@ def plot_grand_composite_curve(targets, utilities=None, ax=None, file=None, key=lambda u: u.level) cooling = sorted((u for u in utilities if not u.heating), key=lambda u: -u.level) - # each label beside the end of its step, where a level touches the - # curve: below it for heating (the curve rises to the right above - # the pinch) and above it for cooling (it falls to the right below) - for steps, color, end, (dy, va) in ( - (heating, hot_color, Ts[0], (-2, 'top')), - (cooling, cold_color, Ts[-1], (2, 'bottom'))): + ends = [] # (ID, x, y, heating, color) at the end of each step + for steps, color, end in ((heating, hot_color, Ts[0]), + (cooling, cold_color, Ts[-1])): x = 0. previous = None for u in steps: @@ -2854,11 +2865,27 @@ def plot_grand_composite_curve(targets, utilities=None, ax=None, file=None, ls=':', lw=0.8) ax.plot([x, x + u.duty], [y, y], lw=1.5, gid='Utility:' + ID, **style) - ax.annotate(ID, xy=(x + u.duty, y), xytext=(3, dy), - textcoords='offset points', ha='left', va=va, - fontsize=7, color=style['color'], gid='Label:' + ID) x += u.duty previous = y + ends.append((ID, x, y, u.heating, style['color'])) + # each label beside the end of its step on the side with more room + # in the axes, so that it stays inside them, and off the curve. Left + # of the end it sits beyond the level (above it for heating, below + # it for cooling), where the curve is never left of the end: the + # heat cascaded there is at least the duty of the steps up to this + # one. Right of the end it sits toward the pinch, which the curve + # leaves rising to the right above it and falling to the right + # below it. + lo, hi = ax.get_xlim() + for ID, x, y, heating, color in ends: + sign = 1 if hi - x >= x - lo else -1 # right or left of the end + dy = -2 * sign if heating else 2 * sign + ax.annotate(ID, xy=(x, y), xytext=(3 * sign, dy), + textcoords='offset points', + ha='left' if sign > 0 else 'right', + va='top' if dy < 0 else 'bottom', fontsize=7, + color=color, gid='Label:' + ID) + ax.margins(y=0.08) # headroom for a label beyond the outermost level _sci_ticks(ax.xaxis) ax.set_xlabel('heat cascaded [kJ·h$^{-1}$]') ax.set_ylabel('shifted T [°C]') @@ -2876,7 +2903,8 @@ def plot_supertargets(sweep, ax=None, file=None, dpi=300): ---------- sweep : Supertargets ax : matplotlib.axes.Axes, optional - Axes to draw on; a new figure is created if not given. + Axes to draw on; a new figure (with a constrained layout, which + keeps the axis labels in it) is created if not given. file : str, optional If given, the figure is saved to this path. dpi : int, optional @@ -2923,7 +2951,7 @@ def plot_supertargets(sweep, ax=None, file=None, dpi=300): """ import matplotlib.pyplot as plt if ax is None: - fig, ax = plt.subplots(figsize=(6, 4)) + fig, ax = plt.subplots(figsize=(6, 4), layout='constrained') else: fig = ax.figure T = sweep['T_min_app'] diff --git a/tests/test_targeting.py b/tests/test_targeting.py index dbc69d8..550a552 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -2073,7 +2073,7 @@ def test_plot_gcc_gids(): finally: plt.close(fig) -def test_plot_readability(): +def test_plot_readability(quickstart): import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt @@ -2137,25 +2137,50 @@ def boxes(fig, artists): try: label, = _gid_artists(ax, 'Utility:unserved') assert label.get_text() == texts[LP_only] - hot, = _gid_artists(ax, 'Hot balanced composite') - cold, = _gid_artists(ax, 'Cold balanced composite') - (hot_H, hot_T), (cold_H, cold_T) = hot.get_data(), cold.get_data() + hot_curve, = _gid_artists(ax, 'Hot balanced composite') + cold_curve, = _gid_artists(ax, 'Cold balanced composite') + (hot_H, hot_T), (cold_H, cold_T) = hot_curve.get_data(), cold_curve.get_data() assert hot_H[0] == 0. and cold_H[0] == pytest.approx(12.372 * kW, rel=1e-9) assert min_vertical_gap(hot_T, hot_H, cold_T, cold_H) == pytest.approx( bst.HeatUtility.dT, abs=1e-6) finally: plt.close(fig) - # every utility label off both balanced composites: a sensible agent's - # beside the middle of its line (below right of the rising cold curve - # for cooling water and chilled water, above left of the hot curve for a - # hot oil), a latent one's by its flat and the furnace's above the end + def readable(fig, ax, labels, curves): + """Every one of the `labels` inside the axes and off the `curves`, + and the axis labels inside the figure: whether or not it is saved + with a tight bounding box (or shown in a window).""" + fig.canvas.draw() + renderer = fig.canvas.get_renderer() + within = lambda box, frame: (frame.x0 <= box.x0 and box.x1 <= frame.x1 + and frame.y0 <= box.y0 and box.y1 <= frame.y1) + frame = ax.get_window_extent(renderer) + points = [] + for line in curves: + xy = ax.transData.transform(np.column_stack(line.get_data())) + points += [np.linspace(a, b, 500) for a, b in zip(xy, xy[1:])] + points = np.concatenate(points) + assert labels + for label in labels: + box = label.get_window_extent(renderer) + assert within(box, frame), label.get_text() + inside = ((points[:, 0] >= box.x0) & (points[:, 0] <= box.x1) + & (points[:, 1] >= box.y0) & (points[:, 1] <= box.y1)) + assert not inside.any(), label.get_text() + for label in (ax.xaxis.label, ax.yaxis.label, ax.xaxis.get_offset_text()): + assert within(label.get_window_extent(renderer), fig.bbox), label.get_text() + # every utility label inside the axes and off both balanced composites: + # a sensible agent's beside the middle of its line (below right of the + # rising cold curve for cooling water and chilled water, above left of + # the hot curve for a hot oil), a latent one's by its flat and the + # furnace's above the end oil = bst.UtilityAgent('test_targeting_plot_hot_oil', Water=1, T=480., P=5e6, phase='l', thermo=bst.HeatUtility.thermo_water, T_limit=420., heat_transfer_price=1e-6) oiled = targets('test_targeting_plot_oil', [('C1', 390., 470., 1.), ('C2', 460., 470., 10.)], 10.) + liquid = liquids('test_targeting_plot_liquids')[1].targets() cases = [(threshold, None), (furnace, None), (oiled, oiled.utility_targets([oil], [])), - (liquids('test_targeting_plot_liquids')[1].targets(), None)] + (liquid, None)] for t, utilities in cases: fig, ax = plot_composite_curves(t, kind='balanced', utilities=utilities) try: @@ -2165,41 +2190,56 @@ def boxes(fig, artists): curves = [line for line in ax.get_lines() if 'balanced composite' in (line.get_gid() or '')] assert len(curves) == 2 - points = [] - for line in curves: - xy = ax.transData.transform(np.column_stack(line.get_data())) - points += [np.linspace(a, b, 500) for a, b in zip(xy, xy[1:])] - points = np.concatenate(points) - for box, label in zip(boxes(fig, labels), labels): - inside = ((points[:, 0] >= box.x0) & (points[:, 0] <= box.x1) - & (points[:, 1] >= box.y0) & (points[:, 1] <= box.y1)) - assert not inside.any(), label.get_text() + readable(fig, ax, labels, curves) finally: plt.close(fig) + # the utility labels of the real and shifted composites inside the axes + # and off the curves, also where the hot curve's hot end is the top of + # the plot, at the right of it (KEMP, hot streams only) + kemp = targets_of(cp_units('test_targeting_plot_kemp', KEMP, K), 10.) + for t in (kemp, hot, threshold, quickstart): + for kind in ('real', 'shifted'): + fig, ax = plot_composite_curves(t, kind=kind) + try: + labels = _gid_artists(ax, 'Hot utility') + _gid_artists(ax, 'Cold utility') + assert len(labels) == 2 + readable(fig, ax, labels, _gid_artists(ax, 'Hot composite') + + _gid_artists(ax, 'Cold composite')) + finally: + plt.close(fig) # the grand composite curve at its default size: heats of six digits - # [kJ/hr] in scientific notation, whose tick labels do not run together, - # and every label off the curve (which meets the end of a step) - t = targets('test_targeting_plot_readable_gcc', REF_A, 10.) - fig, ax = plot_grand_composite_curve(t) + # [kJ/hr] in scientific notation, whose tick labels do not run together + gcc_A = targets('test_targeting_plot_readable_gcc', REF_A, 10.) + fig, ax = plot_grand_composite_curve(gcc_A) try: lo, hi = ax.get_xlim() ticks = [x for x in ax.get_xticklabels() if lo <= x.get_position()[0] <= hi] assert ax.get_xticks()[-1] > 1e5 - labels = _gid_artists(ax, 'Label:') - assert len(labels) == 4 - boxed = boxes(fig, ticks + labels) - tick_boxes, label_boxes = boxed[:len(ticks)], boxed[len(ticks):] + tick_boxes = boxes(fig, ticks) assert ax.xaxis.get_offset_text().get_text() == '1e5' assert all(not a.overlaps(b) for a, b in zip(tick_boxes, tick_boxes[1:])) - gcc, = _gid_artists(ax, 'GCC') - xy = ax.transData.transform(np.column_stack(gcc.get_data())) - curve = np.concatenate([np.linspace(a, b, 500) for a, b in zip(xy, xy[1:])]) - for box, label in zip(label_boxes, labels): - inside = ((curve[:, 0] >= box.x0) & (curve[:, 0] <= box.x1) - & (curve[:, 1] >= box.y0) & (curve[:, 1] <= box.y1)) - assert not inside.any(), label.get_text() finally: plt.close(fig) + # a label for each step, inside the axes on the side of the step's end + # with more room (the last steps end at the largest heats: left of the + # end, above a heating level and below a cooling one) and off the curve, + # which meets the end of a step or lies beyond it (KEMP's cooling water + # ends short of the curve, which turns back below its level) + for t in (gcc_A, furnace, liquid, kemp, threshold, hot, quickstart): + fig, ax = plot_grand_composite_curve(t) + try: + heating = {u.ID: u.heating for u in t.utility_targets()} + labels = _gid_artists(ax, 'Label:') + assert len(labels) == len(heating) + readable(fig, ax, labels, _gid_artists(ax, 'GCC')) + lo, hi = ax.get_xlim() + for label in labels: + (x, _), (dx, dy) = label.xy, label.xyann + right = dx > 0 + assert right == (hi - x >= x - lo) + assert (dy > 0) == (heating[label.get_text()] != right) + finally: + plt.close(fig) def test_plot_supertargets_gids(tmp_path): import matplotlib @@ -2222,6 +2262,13 @@ def test_plot_supertargets_gids(tmp_path): assert (optimum.get_xdata()[0], optimum.get_ydata()[0]) == ( sweep.T_min_app, sweep.optimum['TAC']) assert not sweep.at_bound and optimum.get_label() == 'optimum 10 K' + # the axis labels inside the figure, without a tight bounding box + fig.canvas.draw() + frame = fig.bbox + for label in (ax.xaxis.label, ax.yaxis.label): + box = label.get_window_extent(fig.canvas.get_renderer()) + assert (frame.x0 <= box.x0 and box.x1 <= frame.x1 + and frame.y0 <= box.y0 and box.y1 <= frame.y1), label.get_text() finally: plt.close(fig) # an optimum at an end of the sweep is labeled so; one without a finite From 9573eb74f4b8b621fb54d497f1320e3181768ba0 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 07:50:08 +0000 Subject: [PATCH 22/52] address review 3 (docs): give the true rule for the flat-stretch optimum, drop fixture and private-helper references DOCS-R3-1 (fixed): ch05 no longer says the refinement "happens to report the last one it evaluated". The new text says the area, and with it the TAC, falls steadily across the threshold stretch as the grid moves. The least TAC is therefore always the warmest point evaluated below the threshold, here 1.848 K, 0.005 K short of the 1.853 K step. concepts.rst and the supertarget Notes no longer call the optimum "an arbitrary point". They say its position has no physical meaning, and that when the discretization trend is steady it lies at an end of the stretch (the warmest point evaluated, for the quickstart system). No numbers changed, so no captures or figures were regenerated. DOCS-R3-2 (fixed): concepts.rst now says "the four-stream problem of Linnhoff & Hindmarsh at 5.56 K" in place of the test fixture name KEMP. DOCS-R3-3 (fixed): the PinchTargets attributes 'grid' and 'cut' are now described inline (the grid keys Ts, shift, contributions, Hl/Hr, k_hi/k_lo, and the meaning of 'below'/'above'). They no longer point to the private _problem_table and _pinch_cut. Only prose and docstrings changed; the synthesis path is untouched, so the oracle was not rerun. Tests: pytest hensmith/targeting.py (doctests) 6 passed; tests/test_hxn.py -k "docstring_references or supertarget or pinch_targets" 1 passed; tests/test_targeting.py -k "supertarget or grid or cut" 1 passed. sphinx is not installed in this environment, so the -W build was not run here (the edits add no new markup). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/concepts.rst | 13 ++++++++----- docs/source/tutorial/05_targeting.rst | 13 ++++++++----- hensmith/targeting.py | 23 +++++++++++++++++------ 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 260fe50..e6b7346 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -498,9 +498,10 @@ divide it, and every unit added adds cost. BioSTEAM's correlations are not of that form: a double pipe exchanger below 150 ft² costs at most about 8,100 USD installed, a shell and tube at 150 ft² about 64,700 USD. An equal division can then put every shell below 150 ft², so that a utility of a few watts that -adds units would cut the capital target by most of it (KEMP at 5.56 K: seven -double pipes instead of four floating heads, a fifth of the cost), although a -network just adds a small exchanger and keeps its large ones. So when the +adds units would cut the capital target by most of it (the four-stream problem +of Linnhoff & Hindmarsh at 5.56 K: seven double pipes instead of four floating +heads, a fifth of the cost), although a network just adds a small exchanger +and keeps its large ones. So when the equal division puts the shells below 150 ft² and the area fills :math:`k = \lfloor A / 150\ \mathrm{ft^2} \rfloor \geq 1` shells of at least 150 ft², the target is the costlier of the :math:`N` double pipes and of @@ -546,8 +547,10 @@ equal shells place only roughly. Where the targets do not depend on ``T_min_app`` -- below the threshold of a threshold problem, the utilities and the balanced composite curves stay the same -- the costs differ only by the discretization of the curves, by a tiny relative amount (4e-7 -of the total cost for the quickstart system), so the optimum is an arbitrary -point of that flat stretch. A sweep with no finite +of the total cost for the quickstart system), so where the optimum lies in +that flat stretch means nothing physically; where the discretization trend is +steady, it lies at an end of the stretch (for the quickstart system, the +warmest point evaluated, just short of the threshold). A sweep with no finite total cost (an unserved utility everywhere, say) is not refined and warns that its optimum is meaningless. With approach contributions, only the unspecified streams follow ``T_min_app``; diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index d825688..e5318c9 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -246,11 +246,14 @@ change with ``T_min_app``, and neither does any target. The TACs at 1 and at 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; the refinement happens to report the last one it -evaluated, 1.848 K (inside the sweep, not at its bound). The real 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. +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.848 K (inside the +sweep, not at its bound), 0.005 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. .. figure:: /_static/images/examples/tutorial_05_supertargets.png :class: white-bg diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 788edc4..0e1d14e 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -652,7 +652,13 @@ class PinchTargets: table : ProblemTable The problem table. grid : dict - The table's grid (see `hensmith.hxn_synthesis._problem_table`). + The table's grid: 'Ts', its shifted temperatures [K] (descending, + n); 'shift', as `shift`; 'contributions', each stream's approach + temperature contribution [K] (N); 'Hl' and 'Hr', (N, n) each + stream's enthalpy [kJ/hr] at every grid temperature, the left (low + enthalpy) and right (high enthalpy) limit, which differ only at a + point load of the stream; 'k_hi' and 'k_lo', (N,) the grid indices + of each stream's own upper and lower temperature. shift : numpy.ndarray (N,) shift of each stream [K]: its real temperature at shifted temperature T* is ``T* + shift[j]``. @@ -663,8 +669,10 @@ class PinchTargets: Shifted pinch temperature [K]. cut : str 'below' or 'above': the side of the pinch to which the point loads - at the pinch temperature belong (see - `hensmith.hxn_synthesis._pinch_cut`). + at the pinch temperature belong. It is 'below' when the zero heat + flow of the cascade is the flow arriving at `pinch_T` (before those + point loads), 'above' when it is the flow leaving it (after them), + and always 'below' for a threshold problem with no hot utility. heat_utilities : list[HeatUtility] or None The heat utilities the streams were taken from, in stream order; None when built from streams. For `HeatExchangerNetwork.targets` @@ -2014,9 +2022,12 @@ def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, (below the threshold of a threshold problem, where neither the utilities nor the balanced composite curves change), the TACs differ only by the discretization of the streams' curves on the problem table's grid, by - a tiny relative amount, and the optimum is an arbitrary point of that - flat stretch: compare the TACs of its neighbors before reading anything - into its position. + a tiny relative amount, and where the optimum lies in that flat stretch + means nothing physically. Where the discretization trend is steady, it + lies at an end of the stretch (for the quickstart system of the + tutorial, the warmest point evaluated, just short of the threshold): + compare the TACs of its neighbors before reading anything into its + position. Unless `operating_hours` or `tea` is given, the operating hours are those of ``targets.system`` if it has them (see `PinchTargets.summary`). From 50d83dab2f9548db27e9df54ef16c4155f7e9fb4 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 08:06:03 +0000 Subject: [PATCH 23/52] address review 3 (e2e): accept keys of unbuilt auxiliaries and idle h keys, check utilities against the targets E2E-R3-1 (fixed): a contribution key that names an auxiliary its unit may build but does not hold in a simulation is now valid and has no effect, instead of raising from sys.simulate(). This covers an old auxiliary object whose top owner is at hand, and (owner.ID, name) or (owner.ID, 'name[k]') for a name of the auxiliary_unit_names of that owner or of one of its auxiliaries (new hxn_synthesis._names_unbuilt_auxiliary and _unknown_keys). Typos still raise (e.g. ('X1', 'heat_exchanger[2]'), ('X9', ...)). Updated the docs to match: the facility Notes, synthesize_network, concepts.rst and the API page. New test: an HXutilities outlet T changes 480 -> 440 -> 400 -> 480 K between simulations, with the tuple key and the old-object key. E2E-R3-2 (fixed): PinchTargets gains a key_units attribute (None by default, copied by at()). HXN.targets sets it to the facility's candidate units, and _film_mapping checks h keys against them and against unbuilt auxiliaries, as the facility checks contribution keys. So an h key of a unit that is idle or ignored in a sample has no effect in area_target, summary, network_report and supertarget, while unknown keys still raise. HXN.targets now reads the units option once in both branches. Documented this in the h parameter docs and the targets Returns. New test with a unit that falls idle, with and without ignored. E2E-R3-4 (fixed): new PinchTargets._check_utilities, used by _balanced_segments, _balanced_positions and _area (so by area/units/capital targets, the balanced curves and the balanced composite plot). It raises a ValueError unless the heating duties (unserved included) add up to Q_hot and the cooling ones to Q_cold, within 2*_tol + 1e-9*|Q|. plot_grand_composite_curve is left unchecked, since it may draw a subset. Documented the requirement in each utilities parameter and in tutorial ch05. New test: utilities of another T_min_app, an empty list, subsets and a duplicate all raise; the targets' own utilities pass, including partially unserved ones. E2E-R3-5 (nit, applied): the supertarget Notes, concepts.rst and tutorial ch05 now say that each point builds its own problem table, and suggest a coarser T_min_apps or a smaller refine for large systems. E2E-R3-6 (nit, applied): supertarget silences the per-point "no ... agent can serve" warnings and emits a single "N of the M minimum approach temperatures evaluated leave part of a utility target unserved" warning. Tested. No numbers in the docs change, so no captures or figures were regenerated. Tests: tests/test_targeting.py + tests/test_hxn_contributions.py 101 passed; doctests of hensmith/targeting.py, hxn_synthesis.py and _heat_exchanger_network.py 11 passed; the 4 new tests fail without the source changes. Oracle oracle_r3.json sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (matches). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/API/heat_exchanger_network.rst | 2 +- docs/source/concepts.rst | 15 ++- docs/source/tutorial/05_targeting.rst | 12 +- hensmith/_heat_exchanger_network.py | 24 +++- hensmith/hxn_synthesis.py | 52 ++++++++- hensmith/targeting.py | 123 +++++++++++++++++---- tests/test_hxn_contributions.py | 61 ++++++++++ tests/test_targeting.py | 84 +++++++++++++- 8 files changed, 335 insertions(+), 38 deletions(-) diff --git a/docs/source/API/heat_exchanger_network.rst b/docs/source/API/heat_exchanger_network.rst index ec6eb93..d7d2ae8 100644 --- a/docs/source/API/heat_exchanger_network.rst +++ b/docs/source/API/heat_exchanger_network.rst @@ -72,7 +72,7 @@ each of them changes. - Allow a process stream to be split into parallel branches that re-join. A side of the pinch that no unsplit network serves at MER is planned with splits and reaches the targets exactly on the planner's knots; sides that an unsplit network serves are never split, so a problem that needs no split gets the same network as with the default. Each split is a chain of ``Splitter`` units and a rigorous ``Mixer`` (adiabatic, no cost), listed in ``new_splitters`` and ``new_mixers``. With ``avoid_recycle``, a split that would repeat a stream pair is not used, and MER is then not guaranteed. Stored as an attribute of the same name, and part of the ``cache_network`` key. Defaults to False. * - ``approach_contributions`` - Mapping or callable - - Approach temperature contribution of each process stream, in K: a match between hot stream *h* and cold stream *c* must keep :math:`c_h + c_c` instead of ``T_min_app`` everywhere inside it, and a stream without a contribution contributes ``T_min_app / 2``; the utility exchangers keep BioSTEAM's own approach rule. A mapping's keys name units -- a unit, the ID of a unit without an owner (``'D1'``), or ``(owner.ID, unit.ID)`` for an auxiliary unit (``('D1', 'condenser')``; with the top owner, so it names every auxiliary of that ID under it) -- and a heat utility takes the value of the key nearest to its unit along the unit's chain of owners (two keys of the same level must agree); ID keys stay valid when a unit rebuilds its auxiliaries, and heat utility keys are rejected (``TypeError``). A callable ``f(hu)`` is called once per heat utility at every simulation and should be pure; a sequence raises a ``TypeError``. A value is None or nan (unspecified, an explicit default at its level) or a finite number > 0. Keys and values are checked against every available unit and its auxiliaries before any filtering, so an unknown key, a bad value or a conflict raises a ``ValueError`` even while its unit is idle, but a valid key whose unit holds no heat utility in a simulation has no effect. The resolved values are part of the ``cache_network`` key. May be changed between simulations. Defaults to None (every stream contributes ``T_min_app / 2``, so every pair requires ``T_min_app``). + - Approach temperature contribution of each process stream, in K: a match between hot stream *h* and cold stream *c* must keep :math:`c_h + c_c` instead of ``T_min_app`` everywhere inside it, and a stream without a contribution contributes ``T_min_app / 2``; the utility exchangers keep BioSTEAM's own approach rule. A mapping's keys name units -- a unit, the ID of a unit without an owner (``'D1'``), or ``(owner.ID, unit.ID)`` for an auxiliary unit (``('D1', 'condenser')``; with the top owner, so it names every auxiliary of that ID under it) -- and a heat utility takes the value of the key nearest to its unit along the unit's chain of owners (two keys of the same level must agree); ID keys stay valid when a unit rebuilds its auxiliaries, and heat utility keys are rejected (``TypeError``). A callable ``f(hu)`` is called once per heat utility at every simulation and should be pure; a sequence raises a ``TypeError``. A value is None or nan (unspecified, an explicit default at its level) or a finite number > 0. Keys and values are checked against every available unit and its auxiliaries before any filtering, so an unknown key, a bad value or a conflict raises a ``ValueError`` even while its unit is idle, but a valid key whose unit holds no heat utility in a simulation has no effect, as does a key of an auxiliary that its unit may build but does not build in that simulation (``('X1', 'heat_exchangers[2]')`` of a ``biosteam.HXutilities`` that needs fewer exchangers, say). The resolved values are part of the ``cache_network`` key. May be changed between simulations. Defaults to None (every stream contributes ``T_min_app / 2``, so every pair requires ``T_min_app``). Class attributes ---------------- diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index e6b7346..fc926b8 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -284,7 +284,7 @@ of the same level for a unit must agree. ID keys are the robust form: a unit that rebuilds its auxiliary units at every simulation (``biosteam.HXutilities``, for one) leaves a key holding an old auxiliary object, which then names the auxiliary that took its place: the one of the -same ID under owners of the same IDs. Heat utilities cannot be keys, +same ID under owners of the same IDs, if any. Heat utilities cannot be keys, because BioSTEAM replaces them at every simulation; a value per heat utility is given by a callable ``f(hu)`` instead, which is called once per heat utility at every simulation @@ -297,7 +297,15 @@ names none of them, a bad value or two conflicting keys raise a ``ValueError`` even while their unit is idle, but a valid key whose unit holds no heat utility in a simulation (no duty, ``ignored``, ``hxn_ok=False``) has no effect, so that a Monte Carlo sample in -which a unit falls idle does not fail. +which a unit falls idle does not fail. The same holds for an auxiliary that a +unit may build but does not hold in a sample: ``biosteam.HXutilities``, for +one, builds one exchanger per agent it needs, so a lower outlet temperature +drops ``('X1', 'heat_exchangers[2]')``. A key of the ID that BioSTEAM gives an +auxiliary, ``(owner.ID, name)`` or ``(owner.ID, 'name[k]')`` for a name of the +``auxiliary_unit_names`` of the owner or of one of its auxiliaries, is valid +whether or not that auxiliary is built, as is an old auxiliary object of a unit +at hand that none took the place of; while the auxiliary is missing, the key +has no effect. **The cache.** With ``cache_network=True``, a cached network is reused only while the explicit contributions resolve to the same value for every stream @@ -534,7 +542,8 @@ bare module cost of the exchangers only: no Lang factor and no working capital. **The sweep.** :func:`~hensmith.supertarget` evaluates the targets over a grid of ``T_min_app`` values, reusing the streams' curves (nothing is flashed -again), and then refines the grid around the least total annualized cost. The +again, though each point builds its own problem table, about the table of a +synthesis), and then refines the grid around the least total annualized cost. The cost is not smooth -- the units target jumps, the double pipe switches to a shell and tube, the pinch moves -- so the refinement only narrows a bracket, and ``at_bound`` flags an optimum at an end of the sweep. The capital target diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index e5318c9..c7f49e8 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -179,7 +179,11 @@ Area, units, capital and total cost ----------------------------------- On the balanced composite curves, :meth:`~hensmith.PinchTargets.summary` -targets the rest of the cost of a network. +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 a +mismatch raises a ``ValueError``. .. literalinclude:: /../_demo_src/examples/ch05_targeting.py :language: python @@ -225,7 +229,11 @@ Supertargeting: the least total annualized cost Chapter 4 swept ``T_min_app`` by synthesizing a network at each value. :func:`~hensmith.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 ``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. .. literalinclude:: /../_demo_src/examples/ch05_targeting.py :language: python diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index 0f1641c..0f8e790 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -294,15 +294,22 @@ class HeatExchangerNetwork(bst.Facility): agree. ID keys are the robust form: a unit that rebuilds its auxiliary units at every simulation (e.g. `biosteam.HXutilities`) leaves a key holding an old auxiliary, which then names the auxiliary that took its - place (the one of the same ID under owners of the same IDs; heat - utility keys are rejected, since BioSTEAM replaces them at every + place (the one of the same ID under owners of the same IDs), if any + (heat utility keys are rejected, since BioSTEAM replaces them at every simulation). The keys and values are checked against every unit available to the network and its nested auxiliary units before the heat utilities are taken: a key that names none of them, a bad value or two conflicting keys raise a ValueError, even while their unit is idle, but a valid key whose unit holds no heat utility in a simulation (no duty, `ignored`, or not available to a heat exchanger network) has - no effect, so that, e.g., Monte Carlo samples do not fail. A callable is + no effect, so that, e.g., Monte Carlo samples do not fail. A key of an + auxiliary that a unit may build but does not hold in a simulation is + valid too, and has no effect: an old auxiliary object of the unit that + none took the place of, or ``(owner.ID, name)`` or ``(owner.ID, + 'name[k]')``, the IDs biosteam gives auxiliaries, for a name of the + `auxiliary_unit_names` of the owner or of one of its auxiliaries (e.g. + ``('X1', 'heat_exchangers[2]')`` while a `biosteam.HXutilities` X1, + which builds one exchanger per agent it needs, needs fewer). A callable is called once per heat utility at every simulation (also to check a cached network, and again if a failed cache synthesizes anew), in the order of the facility's heat utilities: it should be pure. After a @@ -985,7 +992,11 @@ def targets(self, T_min_app=None): `force_ideal_thermo`, and its `system`, whose operating hours (if it has them, a TEA's for instance) `PinchTargets.summary` and `hensmith.targeting.supertarget` then use by default, as - `hensmith.targeting.network_report` does. + `hensmith.targeting.network_report` does. Their `key_units` are + the units that the facility's approach contribution keys may + name, so that a film coefficient key (``h=``) of a unit without + a stream in a simulation has no effect, as such an approach + contribution key has none. Raises ------ @@ -1074,6 +1085,7 @@ def targets(self, T_min_app=None): dH = hx.outs[0].H - hx.ins[0].H is_hot.append(dH < 0. if dH else i not in cold) x = self.approach_contributions + units = list(self._get_units()) if x is not None and not isinstance(x, Mapping) and callable(x): # Never called again: with replace_unit_heat_utilities, the # heat utilities it would read now hold the network's @@ -1083,10 +1095,11 @@ def targets(self, T_min_app=None): contributions = getattr(self, '_resolved_contributions', None) else: # by unit only, which the network never changes - contributions = self._network_contributions(hus) + contributions = self._network_contributions(hus, units) targets = PinchTargets._from_exchangers( hxs, is_hot, T_min_app, contributions, self.force_ideal_thermo, None, hus) + targets.key_units = self._candidate_units(units) targets.system = self.system return targets if self.system is None and not self.units: @@ -1098,6 +1111,7 @@ def targets(self, T_min_app=None): targets = PinchTargets.from_heat_utilities( hx_utils, T_min_app, self._network_contributions(hx_utils, units), self.force_ideal_thermo, self.sort_hus_by_T) + targets.key_units = self._candidate_units(units) targets.system = self.system return targets diff --git a/hensmith/hxn_synthesis.py b/hensmith/hxn_synthesis.py index d3a5704..d9390c6 100644 --- a/hensmith/hxn_synthesis.py +++ b/hensmith/hxn_synthesis.py @@ -844,12 +844,48 @@ def _owner_chains(units): unit = getattr(unit, '_owner', None) return list(found.values()) +def _names_unbuilt_auxiliary(key, universe, live): + """ + Whether the key `key` (of a valid type, see `_check_contribution_key`), + which names no unit of `universe` (see `_names_unit`; `live` holds the + ids of `universe`), names an auxiliary unit that a unit of `universe` + may build but does not hold now, as a `biosteam.HXutilities` builds one + exchanger per agent it needs: an old auxiliary object whose top owner is + in `universe` (rebuilt with fewer auxiliaries), or ``(owner.ID, ID)`` + for the ID that biosteam gives an auxiliary, ``name`` or ``name[k]``, + with `name` one of the `auxiliary_unit_names` of a unit of `universe` + under that top owner (e.g. ``('X1', 'heat_exchangers[2]')``). Such a key + is valid but names nothing. + """ + if isinstance(key, bst.Unit): + top = key.owner + return top is not key and id(top) in live + if not isinstance(key, tuple): return False + owner_ID, ID = key + match = re.fullmatch(r'(.+)\[\d+\]', ID) + names = {ID, match[1]} if match else {ID} + return any(unit.owner.ID == owner_ID + and not names.isdisjoint(getattr(unit, 'auxiliary_unit_names', + ())) + for unit in universe) + +def _unknown_keys(keys, universe): + """The keys of `keys` (units, str or pairs of str) that name neither a + unit of `universe` (see `_names_unit`) nor an auxiliary unit that one of + them may build (see `_names_unbuilt_auxiliary`).""" + live = {id(u) for u in universe} + return [key for key in keys + if not any(_names_unit(key, u, live) for u in universe) + and not _names_unbuilt_auxiliary(key, universe, live)] + def _mapping_contributions(hus, mapping, units): """ `_stream_contributions` of a mapping from keys naming units to contributions: checks the types of keys and values, then that every key names a unit of the universe (the owner chains of the units of `hus` - and of `units`, the units at hand of `_names_unit`), then that no two + and of `units`, the units at hand of `_names_unit`) or an auxiliary + unit that one of them may build but does not hold now (see + `_names_unbuilt_auxiliary`: such a key names nothing), then that no two keys of the same level for a unit of the universe give it different values (None and nan are equal); then each heat utility takes the value of the key of the lowest level on its unit's owner chain (None without @@ -861,8 +897,7 @@ def _mapping_contributions(hus, mapping, units): items.append((key, _contribution_value(value, f'key {key!r}'))) universe = _owner_chains([hu.unit for hu in hus] + list(units or ())) live = {id(u) for u in universe} - unknown = [key for key, _ in items - if not any(_names_unit(key, u, live) for u in universe)] + unknown = _unknown_keys([key for key, _ in items], universe) if unknown: raise ValueError('approach_contributions keys that name no unit: ' f"{', '.join(map(repr, unknown))} (a key is a unit, " @@ -2087,7 +2122,7 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, - a mapping ``{key: value}`` whose keys name units: a unit (that very object; a stale auxiliary that its owner rebuilt, no longer among the units of `hus` and their owners, names the one of the - same ID under owners of the same IDs instead), + same ID under owners of the same IDs instead, if any), ``(owner.ID, unit.ID)`` with the top owner (the unit's own ID twice for a unit without an owner; it names every auxiliary of that ID under that owner, at any depth, so the heaters of two @@ -2102,8 +2137,13 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, A None or nan value wins at its level like any other. Heat utility keys are rejected (BioSTEAM replaces them at every simulation). A key that names neither the unit of a heat utility - of `hus` nor one of its owners, or two keys of the same level for - a unit with different values, raise a ValueError; + of `hus` nor one of its owners, nor an auxiliary that one of them + may build but does not hold now (such a key has no effect: e.g. + ``('X1', 'heat_exchangers[2]')``, biosteam's ID of the third + exchanger of a `biosteam.HXutilities` X1, while X1 needs fewer, + or an old exchanger of X1 that none took the place of), or two + keys of the same level for a unit with different values, raise a + ValueError; - a sequence (or `numpy.ndarray`; not a str or bytes) with one value per heat utility, aligned with `hus` as given (before the heating utilities are placed first; zero-duty utilities diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 0e1d14e..15fda2d 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -23,14 +23,14 @@ from collections.abc import Mapping, Sequence from contextlib import contextmanager from numbers import Integral, Real -from warnings import warn +from warnings import catch_warnings, filterwarnings, warn import numpy as np import biosteam as bst from ._curves import _T_EQ, _copy from ._planner import _approach_contributions, _given_contributions from .hxn_synthesis import (_problem_table, _pinch_cut, _format_H, _arrange_heat_utilities, _process_streams, _explicit_contributions, - _contribution_key_level, _names_unit, _owner_chains, + _contribution_key_level, _owner_chains, _unknown_keys, _unit_message_name) __all__ = ('PinchTargets', 'supertarget', 'Supertargets', 'network_report', @@ -682,6 +682,12 @@ class PinchTargets: units : list[Unit] or None The unit of each stream (``hu.unit``), in stream order; None when built from streams. + key_units : list[Unit] or None + Further units that keys of `h` may name (with their owners) without + a stream of theirs, a key that names only such units then having no + effect: for `HeatExchangerNetwork.targets`, every unit available to + the network and its nested auxiliary units, which approach + contribution keys may name; else None (settable). force_ideal_thermo : bool Whether the stream copies use ideal thermodynamics (False when built from streams). @@ -1000,6 +1006,7 @@ def __init__(self, streams_inlet, streams_quenched, is_hot, T_min_app=5., self.cut = _pinch_cut(table) self.heat_utilities = None self.units = None + self.key_units = None self.force_ideal_thermo = False self.system = None #: Grid index of the pinch. @@ -1119,8 +1126,8 @@ def at(self, T_min_app, scale_contributions=False): Returns ------- PinchTargets - With the same streams, curves, labels, units, heat utilities and - system. + With the same streams, curves, labels, units, key units, heat + utilities and system. """ c = self.approach_contributions @@ -1132,6 +1139,7 @@ def at(self, T_min_app, scale_contributions=False): self.is_hot, T_min_app, c, self.labels, self.curves) new.heat_utilities = self.heat_utilities new.units = self.units + new.key_units = self.key_units new.force_ideal_thermo = self.force_ideal_thermo new.system = self.system return new @@ -1512,14 +1520,34 @@ def _utility(agent, heating, level, q, T_in, T_out, T_priced): # %% Balanced curves + def _check_utilities(self, utilities): + """Raise a ValueError unless the heating duties of `utilities` + (unserved ones included) add up to `Q_hot` and the cooling ones to + `Q_cold`, as those of `utility_targets` do (to the remainders of at + most `_tol` that it leaves out, and round-off).""" + for heating, Q, name in ((True, self.Q_hot, 'Q_hot'), + (False, self.Q_cold, 'Q_cold')): + total = math.fsum([u.duty for u in utilities + if bool(u.heating) == heating]) + if not abs(total - Q) <= 2. * self._tol + 1e-9 * abs(Q): + kind = 'heating' if heating else 'cooling' + raise ValueError( + f'the {kind} duties of utilities add up to {total:.6g} ' + f'kJ/hr, not {name} = {Q:.6g} kJ/hr: give the ' + 'utility_targets() of these targets (those of other ' + 'targets, e.g. of another T_min_app, or a part of them ' + 'do not balance their curves)') + def _balanced_segments(self, utilities, inv_h=None, inv_h_utilities=None): """ The hot and cold segments (`_composite`) of the balanced composite curves on real temperatures: the streams plus the served utilities, fuel as a last hot segment at infinite temperature (a weight of 0, no area). `inv_h` weighs the streams (N) and `inv_h_utilities` the - `utilities` (1 by default). + `utilities` (1 by default). The `utilities` are checked + (`_check_utilities`). """ + self._check_utilities(utilities) if inv_h is None: inv_h = np.ones(self.is_hot.size) if inv_h_utilities is None: inv_h_utilities = [1.] * len(utilities) sides = [] @@ -1549,7 +1577,12 @@ def balanced_composite_curves(self, utilities=None): Parameters ---------- utilities : list[UtilityTarget], optional - Defaults to `utility_targets` with the default agents. + `utility_targets` of these targets, with any agents: a + ValueError is raised unless the heating duties (unserved ones + included) add up to `Q_hot` and the cooling ones to `Q_cold` + (the utilities of other targets, e.g. of another `T_min_app`, + or a part of them do not balance the curves). Defaults to + `utility_targets` with the default agents. Returns ------- @@ -1574,7 +1607,9 @@ def balanced_composite_curves(self, utilities=None): def _balanced_positions(self, utilities): """The constraint positions with the levels of every allocated - finite level of `utilities`, and the balanced heat flow at each.""" + finite level of `utilities` (checked, see `_check_utilities`), and + the balanced heat flow at each.""" + self._check_utilities(utilities) Ts, arr, lea = self._gcc() positions = _positions(Ts, arr, lea, [u.level for u in utilities if math.isfinite(u.level)]) @@ -1591,7 +1626,12 @@ def balanced_grand_composite_curve(self, utilities=None): Parameters ---------- utilities : list[UtilityTarget], optional - Defaults to `utility_targets` with the default agents. + `utility_targets` of these targets, with any agents: a + ValueError is raised unless the heating duties (unserved ones + included) add up to `Q_hot` and the cooling ones to `Q_cold` + (the utilities of other targets, e.g. of another `T_min_app`, + or a part of them do not balance the curves). Defaults to + `utility_targets` with the default agents. Returns ------- @@ -1634,11 +1674,14 @@ def _film_mapping(self, h): `hensmith.hxn_synthesis._mapping_contributions`): the key of the lowest level on the owner chain of a stream's unit wins, and a stream index is below its unit. Keys of the same level for a unit must give - it the same value. + it the same value. Unit keys may also name `key_units` (and their + owners) and the auxiliaries that units may build but do not hold + now, as approach contribution keys of the facility may, and then + have no effect. """ N = self.is_hot.size units = self.units or () - universe = _owner_chains(units) + universe = _owner_chains([*units, *(self.key_units or ())]) live = {id(u) for u in universe} items = [] unknown = [] @@ -1657,7 +1700,7 @@ def _film_mapping(self, h): raise TypeError('h keys must be stream indices (int), units, ' 'unit IDs (str) or (owner.ID, unit.ID) tuples, ' f'not {type(key).__name__!r} ({key!r})') - elif not any(_names_unit(key, u, live) for u in universe): + elif _unknown_keys([key], universe): unknown.append(key) items.append((key, _film_value(value, f'h of key {key!r}'))) if unknown: @@ -1694,6 +1737,7 @@ def _film_mapping(self, h): def _area(self, utilities, h, h_utilities): """`area_target` with resolved film coefficients (`h`, an (N,) array; `h_utilities`, one per utility).""" + self._check_utilities(utilities) if any(u.agent is None for u in utilities): return math.nan hot, cold = self._balanced_segments( utilities, 1. / h, [1. / x for x in h_utilities]) @@ -1707,7 +1751,12 @@ def area_target(self, utilities=None, h=None, h_utilities=None): Parameters ---------- utilities : list[UtilityTarget], optional - Defaults to `utility_targets` with the default agents. + `utility_targets` of these targets, with any agents: a + ValueError is raised unless the heating duties (unserved ones + included) add up to `Q_hot` and the cooling ones to `Q_cold` + (the utilities of other targets, e.g. of another `T_min_app`, + or a part of them do not balance the curves). Defaults to + `utility_targets` with the default agents. h : float, Sequence[float] or Mapping, optional Film coefficient of the streams [kW/m²/K]: one for all, one per stream in stream order, or a mapping from stream indices (int) @@ -1716,7 +1765,13 @@ def area_target(self, utilities=None, h=None, h_utilities=None): an owner, or ``(owner.ID, unit.ID)``; an auxiliary unit takes the value of its owner's key unless a key names it). The nearest key wins, a stream index first; streams left out take 1. Labels are - not keys. Defaults to 1 for every stream. + not keys. A key that names no stream raises a ValueError, unless + it names one of `key_units` (for `HeatExchangerNetwork.targets`, + any unit available to the network, as for its approach + contribution keys) or an auxiliary that a unit may build but + does not hold now: it then has no effect, so that a key of a + unit idle in a Monte Carlo sample does not fail. Defaults to 1 + for every stream. h_utilities : float or Mapping, optional Film coefficient of the utilities [kW/m²/K]: one for all, or a mapping from agent IDs or agents (the others take 1). Defaults @@ -1743,7 +1798,12 @@ def units_target(self, utilities=None): Parameters ---------- utilities : list[UtilityTarget], optional - Defaults to `utility_targets` with the default agents. + `utility_targets` of these targets, with any agents: a + ValueError is raised unless the heating duties (unserved ones + included) add up to `Q_hot` and the cooling ones to `Q_cold` + (the utilities of other targets, e.g. of another `T_min_app`, + or a part of them do not balance the curves). Defaults to + `utility_targets` with the default agents. Returns ------- @@ -1827,7 +1887,12 @@ def capital_cost_target(self, utilities=None, h=None, h_utilities=None, Parameters ---------- utilities : list[UtilityTarget], optional - Defaults to `utility_targets` with the default agents. + `utility_targets` of these targets, with any agents: a + ValueError is raised unless the heating duties (unserved ones + included) add up to `Q_hot` and the cooling ones to `Q_cold` + (the utilities of other targets, e.g. of another `T_min_app`, + or a part of them do not balance the curves). Defaults to + `utility_targets` with the default agents. h, h_utilities : optional Film coefficients, as `area_target` takes them. P : float, optional @@ -1986,6 +2051,11 @@ def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, Warns ----- + RuntimeWarning + Once for the sweep, if some minimum approach temperatures leave part + of a utility target unserved (no agent given can serve it: their + TAC is nan), instead of the warning of + `PinchTargets.utility_targets` at each. RuntimeWarning If no minimum approach temperature of the sweep has a finite TAC (each has an unserved utility or an infinite area): the sweep is @@ -1999,7 +2069,12 @@ def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, temperature (``T_min_app / 2`` each), unless `scale_contributions` scales the specified ones by ``T_min_app / targets.T_min_app`` too, the contribution scaling of [Ahmad90]_; with every stream specified, the - sweep needs it. + sweep needs it. The streams' curves are reused, but each point builds + its own problem table (about that of a synthesis, which reads every + stream's enthalpy on its grid), so on a system of many streams with + rigorous thermodynamics the default sweep (40 temperatures and three + refinements) can take longer than a synthesis: a coarser `T_min_apps` + or a smaller `refine` keeps it short. The TAC is not smooth (the units target jumps, so do biosteam's correlations at 150 ft², and the pinch can move), so the sweep is a @@ -2112,7 +2187,10 @@ def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, 'scale_contributions=True to scale them with it') options = targets._check_options(options) def evaluate(T_min_app): - return targets.at(T_min_app, scale_contributions).summary(**options) + with catch_warnings(): # counted once for the sweep instead + filterwarnings('ignore', r'no (heating|cooling) agent can serve', + RuntimeWarning) + return targets.at(T_min_app, scale_contributions).summary(**options) records = [evaluate(float(x)) for x in np.unique(T)] for _ in range(refine): if not any(math.isfinite(r['TAC']) for r in records): @@ -2127,6 +2205,11 @@ def evaluate(T_min_app): if np.abs(evaluated - x).min() > 1e-12: records.append(evaluate(float(x))) sweep = Supertargets(targets, records, options, scale_contributions) + unserved = int(np.isnan(sweep['utility_cost']).sum()) + if unserved: + warn(f'{unserved} of the {len(records)} minimum approach temperatures ' + 'evaluated leave part of a utility target unserved (no agent ' + 'given can serve it), so their TAC is nan', RuntimeWarning) if not np.isfinite(sweep['TAC']).any(): warn('no minimum approach temperature of the sweep has a finite TAC ' '(each has an unserved utility or an infinite area), so it is ' @@ -2592,8 +2675,10 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, (shifted ones with the pinch marked), or 'balanced' for `PinchTargets.balanced_composite_curves` with each utility labeled. utilities : list[UtilityTarget], optional - Utilities of the balanced curves. Defaults to - `PinchTargets.utility_targets` with the default agents. + Utilities of the balanced curves, as + `PinchTargets.balanced_composite_curves` takes them (those of + `targets`). Defaults to `PinchTargets.utility_targets` with the + default agents. ax : matplotlib.axes.Axes, optional Axes to draw on; a new figure (with a constrained layout, which keeps the axis labels in it) is created if not given. diff --git a/tests/test_hxn_contributions.py b/tests/test_hxn_contributions.py index 750b047..4e33e95 100644 --- a/tests/test_hxn_contributions.py +++ b/tests/test_hxn_contributions.py @@ -1001,6 +1001,67 @@ def test_invalid_contributions_in_the_facility(): assert network_record(HXN) == reference assert HXN._resolved_contributions is None +def test_keys_of_auxiliaries_a_unit_does_not_build(): + # biosteam.HXutilities builds one exchanger per steam level it needs, so + # a lower outlet temperature drops 'heat_exchangers[2]' (and [1]): a key + # of it, the tuple or the old object, is valid but has no effect while + # it is missing, and names the exchanger again once it is built; keys + # of IDs the unit never gives an auxiliary still raise + bst.main_flowsheet.set_flowsheet('contributions_unbuilt') + bst.settings.set_thermo(['Water', 'Ethanol'], cache=True) + feed = bst.Stream('feed', Water=500., units='kmol/hr', T=300., P=20e5) + X1 = bst.HXutilities('X1', ins=feed, T=480., rigorous=False) + hot = bst.Stream('hot', Water=800., Ethanol=50., units='kmol/hr', T=460., + P=20e5) + X2 = bst.HXutility('X2', ins=hot, T=320., rigorous=False) + HXN = HeatExchangerNetwork('HXN', T_min_app=10.) + sys = bst.System.from_units('contributions_unbuilt', units=[X1, X2, HXN]) + + def run(T, x): + X1.T = T + HXN.approach_contributions = x + simulate_strictly(sys) + return network_record(HXN) + + tuple_key = {('X1', 'heat_exchangers[2]'): 3.} + run(480., tuple_key) + old = X1.heat_exchangers[2] + assert [hx.ID for hx in X1.heat_exchangers] == [ + 'heat_exchangers[0]', 'heat_exchangers[1]', 'heat_exchangers[2]'] + assert HXN.synthesis_info['approach_contributions'].tolist() == [ + 3. if hx is old else 5. for hx in HXN.original_heat_exchangers] + for T, N in ((440., 2), (400., 1)): + reference = run(T, None) + assert len(X1.heat_exchangers) == N + for x in (tuple_key, {old: 3.}, {('X1', 'heat_exchangers'): 3.}): + assert run(T, x) == reference + assert HXN._resolved_contributions is None + # film coefficient keys of the facility's targets alike + targets = HXN.targets() + area = targets.area_target() + assert targets.area_target(h={old: 2.}) == area + assert targets.area_target(h={('X1', 'heat_exchangers[2]'): 2.}) == area + # built again: the old object names the exchanger that took its place + run(480., {old: 3.}) + new = X1.heat_exchangers[2] + assert new is not old + assert HXN.synthesis_info['approach_contributions'].tolist() == [ + 3. if hx is new else 5. for hx in HXN.original_heat_exchangers] + run(400., None) + targets = HXN.targets() + for key in (('X1', 'heat_exchanger[2]'), ('X1', 'heat_exchangers[x]'), + ('X1', 'heat_exchangers[2'), ('X2', 'heat_exchangers[2]'), + ('X9', 'heat_exchangers[2]')): + with pytest.raises(ValueError, match='h keys that name no stream'): + targets.area_target(h={key: 2.}) + HXN.approach_contributions = {key: 3.} + with pytest.raises(ValueError, match='name no unit'): + sys.simulate() + # outside the facility, the universe is that of the heat utilities + hus = bst.process_tools.heat_exchanger_utilities_from_units([X1, X2]) + assert hxn_synthesis._stream_contributions(hus, tuple_key) == [None, None] + assert hxn_synthesis._stream_contributions(hus, {old: 3.}) == [None, None] + def test_contribution_keys_with_the_units_option(): # keys may name the units of the units option and their auxiliary units, # idle or not, but no other unit; a callable option is called once per diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 550a552..091f618 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -1018,6 +1018,38 @@ def test_bath_balanced_pair(quickstart): with pytest.raises(ValueError, match='name no stream'): quickstart.area_target(utilities, h={'reboiler': 2.}) +def test_utilities_must_be_of_the_targets(quickstart): + # utilities that do not add up to the targets' own utility targets (of + # another T_min_app, none, or a part) raise wherever they are balanced; + # the targets' own, with any agents, are accepted; the grand composite + # plot draws any + import matplotlib + matplotlib.use('Agg') + import matplotlib.pyplot as plt + t = quickstart + t20 = t.at(20.) + own = t20.utility_targets() + with warnings.catch_warnings(): + warnings.simplefilter('ignore', RuntimeWarning) # unserved cooling + partial = t20.utility_targets(cooling_agents=['cooling_water']) + assert any(u.agent is None for u in partial) + mismatched = t.utility_targets() + assert t.Q_hot != t20.Q_hot + calls = [t20.area_target, t20.units_target, t20.capital_cost_target, + t20.balanced_composite_curves, t20.balanced_grand_composite_curve, + lambda u: plot_composite_curves(t20, kind='balanced', utilities=u)] + for utilities in (mismatched, [], own[:1], own[1:], own + own[:1]): + for call in calls: + with pytest.raises(ValueError, match='duties of utilities add up to'): + call(utilities) + for utilities in (own, partial): + assert t20.units_target(utilities)['total'] > 0 + t20.balanced_composite_curves(utilities) + t20.balanced_grand_composite_curve(utilities) + assert t20.area_target(own) > 0. and math.isnan(t20.area_target(partial)) + fig, ax = plot_grand_composite_curve(t20, utilities=mismatched[:1]) + plt.close(fig) + def test_bath_independent_integration(quickstart): # reference A: the streams plus the four utilities t = targets('test_targeting_integration_A', REF_A, 10.) @@ -1558,11 +1590,23 @@ def flash(*args, **kwargs): raise AssertionError('the curves are rebuilt') supertarget(t, **kwargs) # no finite TAC anywhere (the cooling is unserved): the optimum is # meaningless, and the sweep says so - with pytest.warns(RuntimeWarning, match='no minimum approach temperature of the ' - 'sweep has a finite TAC'): + # (the unserved points are counted in one warning, not one per point) + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter('always') unserved = supertarget(t, [5., 10.], refine=1, cooling_agents=[]) + assert [str(w.message) for w in caught] == [ + '2 of the 2 minimum approach temperatures evaluated leave part of a ' + 'utility target unserved (no agent given can serve it), so their TAC ' + 'is nan', + 'no minimum approach temperature of the sweep has a finite TAC (each ' + 'has an unserved utility or an infinite area), so it is not refined ' + 'and its optimum, the smallest, is meaningless'] + assert all(w.category is RuntimeWarning for w in caught) assert np.all(np.isnan(unserved['TAC'])) and unserved.T_min_app == 5. assert len(unserved.records) == 2 # not refined around nan + # outside a sweep, each evaluation warns as before + with pytest.warns(RuntimeWarning, match='no cooling agent can serve'): + t.at(5.).summary(cooling_agents=[]) with warnings.catch_warnings(record=True) as caught: warnings.simplefilter('always') supertarget(t, [5., 10.], refine=0) @@ -1728,6 +1772,42 @@ def test_facility_targets_operating_hours(kemp_hxn): finally: sys.operating_hours = previous +def test_facility_targets_keys_of_idle_units(): + # film coefficient keys of a unit without a stream in a simulation (no + # duty, as in a Monte Carlo sample, or ignored) have no effect, as the + # facility's approach contribution keys have none; unknown keys still + # raise, and targets made from heat utilities know no other units + sys, HXN, (H1, H2, C1, H3) = liquids('test_targeting_idle_keys') + h, busy_h = {'H3': .5, 'H1': 2.}, {'H1': 2.} + HXN.approach_contributions = {'H3': 2.} + simulate_strictly(sys) + t = HXN.targets() + assert H3 in t.units and t.area_target(h=h) != t.area_target(h=busy_h) + H3.T = H3.ins[0].T # no duty + for ignored in (None, [H2]): + HXN.ignored = ignored + simulate_strictly(sys) + assert HXN._resolved_contributions is None # the key of H3 is idle + t = HXN.targets() + assert H3 not in t.units and H3 in t.key_units + assert t.at(20.).key_units is t.key_units + for keys in (h, h | {'H2': 3.}): + assert t.area_target(h=keys) == t.area_target(h=busy_h | { + k: v for k, v in keys.items() if k == 'H2' and not ignored}) + assert t.summary(h=h) == t.summary(h=busy_h) + assert network_report(HXN, h=h) == network_report(HXN, h=busy_h) + assert supertarget(t, [5., 10.], refine=0, h=h).records == ( + supertarget(t, [5., 10.], refine=0, h=busy_h).records) + for keys in ({'H9': 1.}, {('H3', 'heat_exchanger'): 1.}): + with pytest.raises(ValueError, match='h keys that name no stream'): + t.area_target(h=keys) + with pytest.raises(ValueError, match='different values at the same level'): + t.area_target(h={'H3': 2., H3: 3.}) + direct = PinchTargets.from_heat_utilities(t.heat_utilities) + assert direct.key_units is None + with pytest.raises(ValueError, match="h keys that name no stream: 'H3'"): + direct.area_target(h=h) + #: Keys of `network_report`. REPORT_KEYS = ['T_min_app', 'targets_T_min_app', 'status', 'applied', 'Q_hot', 'Q_cold', 'utilities', 'utility_cost', 'N_units', 'N_process_units', 'N_utility_units', From 1c9417aa16e821a17f2afd0b5f89b5898f5c9064 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 09:55:13 +0000 Subject: [PATCH 24/52] address review 4 (f1-facility): accept only biosteam's own auxiliary ids as keys of unbuilt auxiliaries F1F4-1 (fixed): _names_unbuilt_auxiliary accepted both 'name' and 'name[k]' for every name of auxiliary_unit_names, so ('X1', 'heat_exchangers'), ('D1', 'condenser[0]'), ('F1', 'heat_exchanger[3]'), ('D1', 'reboiler[7]') and ('D1', 'pump[0]') were silently valid no-ops (as approach_contributions keys and as PinchTargets h keys, which share _unknown_keys). It now follows biosteam's naming rule (Unit.auxiliary): 'name[k]' only while the unit's attribute `name` holds a list, the bare 'name' only while it does not, and either while it holds nothing (None or missing). Class Notes, concepts.rst and the helper docstring state which form applies to which attribute. Tests: ('X1', 'heat_exchangers') moved from the accepted keys to the raising keys (contributions and h), and indexed IDs of the quickstart column's and flash's single auxiliaries now must raise. F1F4-4 (nit, applied): test_contribution_keys_with_the_units_option no longer presents a one-shot iterator as a supported units option (it is exhausted on a second simulation, as at 5ef9a35); the case and its comment are dropped. Tests: tests/test_hxn_contributions.py + tests/test_targeting.py 101 passed; doctests of hxn_synthesis.py, _heat_exchanger_network.py and targeting.py 11 passed; oracle sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (unchanged); sphinx -W (offline) clean. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/concepts.rst | 13 ++++++++----- hensmith/_heat_exchanger_network.py | 12 +++++++----- hensmith/hxn_synthesis.py | 26 +++++++++++++++++--------- tests/test_hxn_contributions.py | 18 ++++++++++++------ 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index fc926b8..482e12c 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -301,11 +301,14 @@ which a unit falls idle does not fail. The same holds for an auxiliary that a unit may build but does not hold in a sample: ``biosteam.HXutilities``, for one, builds one exchanger per agent it needs, so a lower outlet temperature drops ``('X1', 'heat_exchangers[2]')``. A key of the ID that BioSTEAM gives an -auxiliary, ``(owner.ID, name)`` or ``(owner.ID, 'name[k]')`` for a name of the -``auxiliary_unit_names`` of the owner or of one of its auxiliaries, is valid -whether or not that auxiliary is built, as is an old auxiliary object of a unit -at hand that none took the place of; while the auxiliary is missing, the key -has no effect. +auxiliary under a name of the ``auxiliary_unit_names`` of the owner or of one +of its auxiliaries is valid whether or not that auxiliary is built, as is an +old auxiliary object of a unit at hand that none took the place of; while the +auxiliary is missing, the key has no effect. That ID is ``(owner.ID, +'name[k]')`` where the attribute ``name`` holds a list of auxiliaries (as +``heat_exchangers`` of ``HXutilities`` does) and ``(owner.ID, name)`` where it +holds a single one, so ``('X1', 'heat_exchangers')`` or ``('D1', +'condenser[0]')`` still raise. **The cache.** With ``cache_network=True``, a cached network is reused only while the explicit contributions resolve to the same value for every stream diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index 0f8e790..9df041b 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -305,11 +305,13 @@ class HeatExchangerNetwork(bst.Facility): no effect, so that, e.g., Monte Carlo samples do not fail. A key of an auxiliary that a unit may build but does not hold in a simulation is valid too, and has no effect: an old auxiliary object of the unit that - none took the place of, or ``(owner.ID, name)`` or ``(owner.ID, - 'name[k]')``, the IDs biosteam gives auxiliaries, for a name of the - `auxiliary_unit_names` of the owner or of one of its auxiliaries (e.g. - ``('X1', 'heat_exchangers[2]')`` while a `biosteam.HXutilities` X1, - which builds one exchanger per agent it needs, needs fewer). A callable is + none took the place of, or the ID biosteam gives an auxiliary under a + name of the `auxiliary_unit_names` of the owner or of one of its + auxiliaries: ``(owner.ID, 'name[k]')`` where the attribute `name` holds a + list of auxiliaries (e.g. ``('X1', 'heat_exchangers[2]')`` while a + `biosteam.HXutilities` X1, which builds one exchanger per agent it needs, + needs fewer), and ``(owner.ID, name)`` where it holds a single one (so + ``('X1', 'heat_exchangers')`` raises). A callable is called once per heat utility at every simulation (also to check a cached network, and again if a failed cache synthesizes anew), in the order of the facility's heat utilities: it should be pure. After a diff --git a/hensmith/hxn_synthesis.py b/hensmith/hxn_synthesis.py index d9390c6..dc46b04 100644 --- a/hensmith/hxn_synthesis.py +++ b/hensmith/hxn_synthesis.py @@ -852,10 +852,12 @@ def _names_unbuilt_auxiliary(key, universe, live): may build but does not hold now, as a `biosteam.HXutilities` builds one exchanger per agent it needs: an old auxiliary object whose top owner is in `universe` (rebuilt with fewer auxiliaries), or ``(owner.ID, ID)`` - for the ID that biosteam gives an auxiliary, ``name`` or ``name[k]``, - with `name` one of the `auxiliary_unit_names` of a unit of `universe` - under that top owner (e.g. ``('X1', 'heat_exchangers[2]')``). Such a key - is valid but names nothing. + for the ID that biosteam gives an auxiliary, with `name` one of the + `auxiliary_unit_names` of a unit of `universe` under that top owner: + ``name[k]`` if that unit's attribute `name` holds a list of auxiliaries + (e.g. ``('X1', 'heat_exchangers[2]')``), ``name`` if it holds a single + one, and either while it holds none (None or missing). Such a key is + valid but names nothing. """ if isinstance(key, bst.Unit): top = key.owner @@ -863,11 +865,17 @@ def _names_unbuilt_auxiliary(key, universe, live): if not isinstance(key, tuple): return False owner_ID, ID = key match = re.fullmatch(r'(.+)\[\d+\]', ID) - names = {ID, match[1]} if match else {ID} - return any(unit.owner.ID == owner_ID - and not names.isdisjoint(getattr(unit, 'auxiliary_unit_names', - ())) - for unit in universe) + name = match[1] if match else ID + for unit in universe: + if (unit.owner.ID != owner_ID + or name not in getattr(unit, 'auxiliary_unit_names', ())): + continue + # biosteam's ID of an auxiliary under `name`: 'name[k]' while the + # attribute holds a list, else 'name' (either while it holds nothing) + held = getattr(unit, name, None) + if held is None or isinstance(held, list) == bool(match): + return True + return False def _unknown_keys(keys, universe): """The keys of `keys` (units, str or pairs of str) that name neither a diff --git a/tests/test_hxn_contributions.py b/tests/test_hxn_contributions.py index 4e33e95..66b8283 100644 --- a/tests/test_hxn_contributions.py +++ b/tests/test_hxn_contributions.py @@ -732,6 +732,12 @@ def resolve(x): with pytest.raises(ValueError, match="name no unit: 'condenser', " r"\('D1', 'nope'\)"): resolve({'condenser': 1., ('D1', 'nope'): 1., 'D1': 2.}) + # biosteam gives a single auxiliary its bare name, so an indexed ID of + # one names nothing + for key in (('D1', 'condenser[0]'), ('D1', 'reboiler[7]'), + ('D1', 'pump[0]'), ('F1', 'heat_exchanger[3]')): + with pytest.raises(ValueError, match='name no unit'): + resolve({key: 1.}) # sequence (aligned with hus) and callable (once per heat utility, in # order) forms; heat utility and other keys are rejected assert hxn_synthesis._stream_contributions( @@ -1006,7 +1012,8 @@ def test_keys_of_auxiliaries_a_unit_does_not_build(): # a lower outlet temperature drops 'heat_exchangers[2]' (and [1]): a key # of it, the tuple or the old object, is valid but has no effect while # it is missing, and names the exchanger again once it is built; keys - # of IDs the unit never gives an auxiliary still raise + # of IDs the unit never gives an auxiliary still raise (the bare name of + # a list of auxiliaries among them) bst.main_flowsheet.set_flowsheet('contributions_unbuilt') bst.settings.set_thermo(['Water', 'Ethanol'], cache=True) feed = bst.Stream('feed', Water=500., units='kmol/hr', T=300., P=20e5) @@ -1033,7 +1040,7 @@ def run(T, x): for T, N in ((440., 2), (400., 1)): reference = run(T, None) assert len(X1.heat_exchangers) == N - for x in (tuple_key, {old: 3.}, {('X1', 'heat_exchangers'): 3.}): + for x in (tuple_key, {old: 3.}): assert run(T, x) == reference assert HXN._resolved_contributions is None # film coefficient keys of the facility's targets alike @@ -1051,7 +1058,7 @@ def run(T, x): targets = HXN.targets() for key in (('X1', 'heat_exchanger[2]'), ('X1', 'heat_exchangers[x]'), ('X1', 'heat_exchangers[2'), ('X2', 'heat_exchangers[2]'), - ('X9', 'heat_exchangers[2]')): + ('X9', 'heat_exchangers[2]'), ('X1', 'heat_exchangers')): with pytest.raises(ValueError, match='h keys that name no stream'): targets.area_target(h={key: 2.}) HXN.approach_contributions = {key: 3.} @@ -1065,7 +1072,7 @@ def run(T, x): def test_contribution_keys_with_the_units_option(): # keys may name the units of the units option and their auxiliary units, # idle or not, but no other unit; a callable option is called once per - # simulation, and a one-shot iterable is read once + # simulation D1, D1_H1, D1_H2, F1 = quickstart('contributions_units_option') calls = [] def listed(): @@ -1073,8 +1080,7 @@ def listed(): return [D1, D1_H1, F1] x = {'D1': 1., F1: 1., 'D1_H1': 4.} hand = {'condenser': 1., 'reboiler': 1., 'heat_exchanger': 1., 'D1_H1': 4.} - for k, option in enumerate([[D1, D1_H1, F1], listed, - iter([D1, D1_H1, F1])]): + for k, option in enumerate([[D1, D1_H1, F1], listed]): HXN = HeatExchangerNetwork(f'HXN_{k}', T_min_app=5., units=option, approach_contributions=x | {D1.pump: 2.}) sys = bst.System.from_units(f'contributions_units_option_{k}', From d5fc2ab9e6b8352b01da908107237243680eafd1 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 10:10:46 +0000 Subject: [PATCH 25/52] address review 4 (tg-core): cap agent levels only by streams their heat can reach, warn on replaced heat utilities TGC4-1 (fixed): utility_targets now sets each non-fuel agent's level inside the greedy allocation, after the agents before it are placed. A stream caps the level only if it is in reach: it has heat above the highest zero of the current balanced curve above the pinch temperature (below the lowest zero below it, for cooling). That zero is a utility pinch left by an earlier agent or another process pinch. A level at or past that zero gets no duty, and heat from a level beyond it never passes it. The upper bound of the level is still the highest (lowest) limit of all served streams, so levels with no such zero are unchanged. Without contributions every level is still R (R - T_min_app for cooling), so default results are unchanged. _levels was split into _level and _in_reach, and _allocate now takes the agents. The class Notes and concepts.rst state the rule. New test test_levels_of_streams_past_a_zero, which fails without the fix. Heating, Ck at c = 0.5: medium pressure steam now serves Ctop's 20 kW (447 -> 449 K, biosteam's own choice), not high pressure steam. Cooling, second process pinch at shifted 300 K: cooling water serves 16.28 kW of H2 down to 312.372 K, the same whether H1 ends at 308 or 313 K. TGC4-2 (fixed): PinchTargets.from_heat_utilities emits a RuntimeWarning naming the units whose heat-exchanger heat utilities have a unit_duty that differs from their stream's outs[0].H - ins[0].H by more than 1e-6 relative. These are the heat utilities that a HeatExchangerNetwork with replace_unit_heat_utilities=True has written over or emptied. On the quickstart, column and KEMP systems without replacement the largest mismatch is 1e-13; replaced ones are off by 8 % or more. HXN.targets() never takes this path after a simulation, so it is unaffected. The hus docstring (plus a Warns section), both ValueError messages and tutorial ch05 now say that after such a simulation the selection holds the network's utilities: use HXN.targets(), or build the targets before that simulation. New test test_from_heat_utilities_after_replaced_heat_utilities (KEMP at 10 K): the selection warns and gives only C1 and H4, every unit's own heat utility warns with all four named, and without replacement there is no warning and the targets match the facility's. Tests: pytest tests/test_targeting.py hensmith/targeting.py hensmith/_heat_exchanger_network.py (with doctests): 68 passed. tests/test_hxn_contributions.py -k nested_auxiliary_units: 1 passed. oracle sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (unchanged). docs/_demo_src/examples/ch05_targeting.py rerun alone, with hensmith.targeting RuntimeWarnings as errors: no generated capture or figure changed. sphinx is not installed in this environment, so the -W build was not run; the docs edits are prose and a numpydoc Warns section of the kind utility_targets already has. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/concepts.rst | 7 +- docs/source/tutorial/05_targeting.rst | 7 +- hensmith/targeting.py | 183 +++++++++++++++++--------- tests/test_targeting.py | 92 +++++++++++++ 4 files changed, 227 insertions(+), 62 deletions(-) diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 482e12c..ec7fe78 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -393,8 +393,11 @@ BioSTEAM's rule exactly. With approach contributions the streams have different shifts, and an agent's level is the highest single shifted temperature (the lowest, for cooling), up to the highest of the streams' own limits, at which it serves no stream beyond that stream's own real limit. A -level serves only what lies below it (above it, for cooling), so a stream -lying wholly beyond its limit bounds the level only at the stream's own end; +level serves only what lies below it (above it, for cooling), and its heat +goes no further than the next zero of the curve balanced with the agents +placed before it (see below): the pinch, a utility pinch or another process +pinch. So a stream lying wholly beyond its limit bounds the level only at the +stream's own end, and a stream lying wholly past that zero does not bound it; streams with slack are then served conservatively. **List order and fuel.** Non-fuel agents are placed in the order of diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index c7f49e8..a3ca900 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -68,7 +68,12 @@ facility, from the heat utilities of heat exchangers with ``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. +first inlet and outlet need not be the stream it heats or cools. 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 ---------------------------------------------- diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 15fda2d..a730e39 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -290,6 +290,16 @@ def _isolated_pricing(agents): if previous is missing: flowsheets.pop(ID, None) else: flowsheets[ID] = previous +def _duty_off_stream(hu): + """Whether the duty of the heat exchanger's heat utility `hu` + (``hu.unit_duty``) differs from that of its process stream (``outs[0].H + - ins[0].H`` of its unit) beyond rounding, as for the heat utilities + that a heat exchanger network simulated with + ``replace_unit_heat_utilities`` writes its utilities over or empties.""" + unit = hu.unit + dH = unit.outs[0].H - unit.ins[0].H + return abs(dH - hu.unit_duty) > 1e-6 * max(abs(dH), abs(hu.unit_duty)) + def _unit_label(unit): """``unit.ID``, or ``' - '`` for an auxiliary unit.""" return unit.ID if unit.owner is unit else f'{unit.owner.ID} - {unit.ID}' @@ -724,23 +734,26 @@ class PinchTargets: above its own limit (the lowest, down to the lowest limit, for cooling): ``R`` for heating and ``R - T_min_app`` for cooling without approach contributions, which is biosteam's rule. A level serves only - what lies below it (above it, for cooling), so a stream reaching beyond - its limit bounds the level at that limit, but one lying wholly beyond - it only at its own end (just short of it, if it has a point load - there). With contributions the level can be conservative for streams - with slack. Non-fuel agents are placed in list order (biosteam's first - feasible agent: for the default lists the lowest steam and the warmest - coolant first), each as much as the curve above its level (below it, for - cooling) allows given the agents already placed; what remains goes to - the first fuel agent, or is left unserved with a RuntimeWarning (nan - cost). A level equal to a grid temperature serves that temperature's - point loads (biosteam's own rule is strict; they differ only at exact - equality), except where a sensible agent's line would have no driving - force left: at a point load at ``agent.T - dT`` (``agent.T + dT`` for - cooling), a stream's limit when ``agent.dT`` is 0 (`UtilityAgent`'s - default), the agent would leave at its inlet temperature at an - unbounded flow, so its level stays just short of that point load, which - the next agent serves, as in biosteam. + what lies below it (above it, for cooling) and above the highest zero + of the curve balanced with the agents already placed (below the lowest + one): the pinch, a utility pinch or another process pinch, which its + heat never passes. So a stream reaching beyond its limit bounds the + level at that limit, one lying wholly beyond it only at its own end + (just short of it, if it has a point load there), and one with no heat + on the level's side of that zero not at all. With contributions the + level can be conservative for streams with slack. Non-fuel agents are + placed in list order (biosteam's first feasible agent: for the default + lists the lowest steam and the warmest coolant first), each as much as + the curve above its level (below it, for cooling) allows given the + agents already placed; what remains goes to the first fuel agent, or is + left unserved with a RuntimeWarning (nan cost). A level equal to a grid + temperature serves that temperature's point loads (biosteam's own rule + is strict; they differ only at exact equality), except where a sensible + agent's line would have no driving force left: at a point load at + ``agent.T - dT`` (``agent.T + dT`` for cooling), a stream's limit when + ``agent.dT`` is 0 (`UtilityAgent`'s default), the agent would leave at + its inlet temperature at an unbounded flow, so its level stays just + short of that point load, which the next agent serves, as in biosteam. Each agent is priced as one biosteam heat utility over the stretch it serves, from its level (or the end of the grid, if that is nearer) to @@ -1030,7 +1043,13 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, ``bst.process_tools.heat_exchanger_utilities_from_units(sys.units)`` (those a heat exchanger network may take, ``hu.hxn_ok``, as `HeatExchangerNetwork` selects them), not ``sys.heat_utilities`` - (sums by agent, of no unit). Zero-duty ones are dropped. + (sums by agent, of no unit). Zero-duty ones are dropped. After a + simulation of a `HeatExchangerNetwork` with + ``replace_unit_heat_utilities=True`` they hold the network's + utilities, not the process duties, and those of the streams it + serves completely are empty (left out of biosteam's selection): + take `HeatExchangerNetwork.targets` then, or build the targets + before that simulation. T_min_app : float, optional Minimum approach temperature [K], finite and >= 0 (> 0 with approach contributions). Defaults to 5. @@ -1060,16 +1079,28 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, ``Unit.add_heat_utility``: their unit's first inlet and outlet need not be the stream they heat or cool). + Warns + ----- + RuntimeWarning + If the duty of a heat exchanger's heat utility (``hu.unit_duty``) + differs from that of its process stream (``outs[0].H - + ins[0].H`` of its unit, which the targets use) beyond rounding, + as after a simulation with ``replace_unit_heat_utilities=True``. + """ hus = list(hus) _check_T_min_app(T_min_app) selection = ('take bst.process_tools.heat_exchanger_utilities_from_' 'units(sys.units), the heat utilities a heat exchanger ' - 'network may take') + 'network may take (before any simulation of a ' + 'HeatExchangerNetwork with replace_unit_heat_utilities=' + 'True, which writes its utilities over them; take its ' + 'targets() after one)') if any(hu.unit is None for hu in hus): raise ValueError('every heat utility must belong to a heat ' - f'exchanger (hu.unit): {selection}, not ' - 'System.heat_utilities (sums by agent, of no unit)') + 'exchanger (hu.unit), not be one of ' + 'System.heat_utilities (sums by agent, of no ' + f'unit): {selection}') others = [hu for hu in hus if hu.duty and not hu.hxn_ok] if others: names = ', '.join(dict.fromkeys(repr(_unit_label(hu.unit)) @@ -1078,6 +1109,17 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, f'(hu.hxn_ok), not those of {names}, whose first ' 'inlet and outlet need not be the stream they ' f'heat or cool: {selection}') + replaced = [hu for hu in hus if hu.hxn_ok and _duty_off_stream(hu)] + if replaced: + names = ', '.join(dict.fromkeys(repr(_unit_label(hu.unit)) + for hu in replaced)) + warn(f'the heat utilities of {names} have duties (hu.unit_duty) ' + 'other than their process streams\' (outs[0].H - ins[0].H), ' + 'which the targets use: after a simulation of a ' + 'HeatExchangerNetwork with replace_unit_heat_utilities=True ' + 'they hold its utilities, and the streams it serves ' + 'completely have none and are left out; take its targets() ' + 'instead', RuntimeWarning) values = _explicit_contributions(hus, approach_contributions, T_min_app) arranged, N_heating = _arrange_heat_utilities(hus, sort_hus_by_T) if values is None: @@ -1223,45 +1265,64 @@ def _side_heat(self): if self.Q_hot == 0.: above, below = 0. * above, below + above return above, below - def _levels(self, agents, heating): + def _level(self, agent, heating, j, reach): """ - ``[(agent, level)]``: each agent's shifted level, the highest - (lowest for cooling), up to the highest of the served streams' - shifted limits (down to the lowest), that serves no stream beyond + The shifted level of `agent` serving the streams `j` (`_served`) of + which those of the mask `reach` (`_in_reach`) are in its reach: the + highest (lowest for cooling), up to the highest of the shifted limits + of `j` (down to the lowest), that serves no stream in reach beyond its own real limit (see Notes of the class). """ dT = bst.HeatUtility.dT grid = self.grid Ts = grid['Ts'] - j = self._served(heating) s = self.shift[j] top, bottom = Ts[grid['k_hi'][j]], Ts[grid['k_lo'][j]] # the point load at the end a level beyond the stream would reach # first: its bottom (heating) or its top (cooling) k = grid['k_lo' if heating else 'k_hi'][j] point = np.abs(self.table.point_H[j, k]) > self._tol - levels = [] - for agent in agents: - # a stream reaching beyond its limit caps the level at the limit, - # or, lying wholly beyond it, at its own end (just short of it if - # a point load is there): a level serves only what lies below it - # (above it, for cooling) - if heating: - R = agent.T - agent.dT - dT - limit = R - s - caps = np.maximum(limit, bottom - _T_SHORT * point)[top > limit] - if j.size: L = caps.min(initial=limit.max()) - else: L = R - else: - R = agent.T + agent.dT + dT - limit = R - s - caps = np.minimum(limit, top + _T_SHORT * point)[bottom < limit] - if j.size: L = caps.max(initial=limit.min()) - else: L = R - self.T_min_app - if j.size and _sensible(agent): - L = self._short_of_stuck_loads(agent, heating, L, j, s) - levels.append((agent, float(L))) - return levels + # a stream in reach reaching beyond its limit caps the level at the + # limit, or, lying wholly beyond it, at its own end (just short of it + # if a point load is there): a level serves only what lies below it + # (above it, for cooling) + if heating: + R = agent.T - agent.dT - dT + limit = R - s + caps = np.maximum(limit, bottom - _T_SHORT * point)[reach & (top > limit)] + if j.size: L = caps.min(initial=limit.max()) + else: L = R + else: + R = agent.T + agent.dT + dT + limit = R - s + caps = np.minimum(limit, top + _T_SHORT * point)[reach & (bottom < limit)] + if j.size: L = caps.max(initial=limit.min()) + else: L = R - self.T_min_app + if reach.any() and _sensible(agent): + L = self._short_of_stuck_loads(agent, heating, L, j[reach], s[reach]) + return float(L) + + def _in_reach(self, j, positions, B, heating): + """ + Mask of the streams `j` (`_served`) that the heat of a next agent + can reach given the balanced heat flow `B` at the `positions` of the + agents already placed: those with heat above the highest zero of `B` + above the pinch temperature (below the lowest one below it, for + cooling), a utility pinch or another process pinch; all of them if + there is no such zero. A level at or beyond that zero gets no duty, + and the heat of a level short of it never passes it. + """ + T = positions[0] + pinch_T = self.pinch_T + if heating: z = np.flatnonzero((B <= self._tol) & (T > pinch_T)) + else: z = np.flatnonzero((B <= self._tol) & (T < pinch_T)) + if not z.size: return np.ones(j.size, dtype=bool) + # the heat between the virtual top and the zero (heating), or the + # zero and the virtual bottom (cooling): position p is column p + 1 + H = self._position_H(positions)[j] + if heating: heat = np.abs(H[:, z[0] + 1] - H[:, 0]) + else: heat = np.abs(H[:, -1] - H[:, z[-1] + 1]) + return heat > self._tol def _short_of_stuck_loads(self, agent, heating, L, j, s): """ @@ -1284,21 +1345,27 @@ def _short_of_stuck_loads(self, agent, heating, L, j, s): elif (load & (T <= agent.T + dT + _T_EQ)).any(): return L + _T_SHORT return L - def _allocate(self, levels, Q, heating): + def _allocate(self, agents, Q, heating): """ - Greedy list-order allocation of `Q` to the agents of `levels` on the - grand composite curve: ``([(level, q, agent)], remaining)``. + Greedy list-order allocation of `Q` to `agents` on the grand + composite curve: ``([(level, q, agent)], remaining)``. Each agent's + level (`_level`) is set by the streams in reach given the agents + placed before it (`_in_reach`). """ Ts, arr, lea = self._gcc() tol = self._tol + served = self._served(heating) allocated = [] remaining = Q - for agent, level in levels: + for agent in agents: if remaining <= tol: break - level = _snap(Ts, level) - positions = _positions(Ts, arr, lea, - [level] + [l for l, _, _ in allocated]) done = [(l, q) for l, q, _ in allocated] + positions = _positions(Ts, arr, lea, [l for l, _ in done]) + B = (_balance(positions, done, ()) if heating + else _balance(positions, (), done)) + reach = self._in_reach(served, positions, B, heating) + level = _snap(Ts, self._level(agent, heating, served, reach)) + positions = _positions(Ts, arr, lea, [level] + [l for l, _ in done]) if heating: B = _balance(positions, done, ()) bound = B[:_gap(positions, level)] @@ -1438,16 +1505,14 @@ def utility_targets(self, heating_agents=None, cooling_agents=None): if cooling_agents is None: cooling_agents = list(bst.HeatUtility.cooling_agents) tol = self._tol heat, remaining = self._allocate( - self._levels([a for a in heating_agents if not a.isfuel], True), - self.Q_hot, True) + [a for a in heating_agents if not a.isfuel], self.Q_hot, True) if remaining > tol: fuels = [a for a in heating_agents if a.isfuel] heat.append((math.inf, remaining, fuels[0] if fuels else None)) if not fuels: warn(f'no heating agent can serve {remaining:.6g} kJ/hr of the ' 'hot utility target; it is left unserved', RuntimeWarning) - cool, remaining = self._allocate( - self._levels(cooling_agents, False), self.Q_cold, False) + cool, remaining = self._allocate(cooling_agents, self.Q_cold, False) if remaining > tol: cool.append((-math.inf, remaining, None)) warn(f'no cooling agent can serve {remaining:.6g} kJ/hr of the ' diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 091f618..a8550c9 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -896,6 +896,58 @@ def test_levels_of_streams_beyond_reach(): pytest.approx(B.outs[0].T, abs=1e-9)) assert u.duty + MP.duty == pytest.approx(t.Q_hot, rel=1e-12) +def test_levels_of_streams_past_a_zero(): + # the heat of a level reaches only the next zero of the balanced curve + # given the agents placed before it: a stream wholly past that zero (a + # utility pinch, or a second process pinch) moves no level. Heating: low + # pressure steam takes 25.5 kW and leaves a utility pinch at shifted + # 447 K; Ck (shifted 420 -> 446.5 K) lies below it and is served by + # Hmid, so it does not cap medium pressure steam at its own limit (MP's + # less 4.5 K), and MP, not high pressure steam, serves Ctop's 20 kW. + rows = [('Ctop', 447., 449., 10.), ('Hmid', 457., 410., 3.), + ('Ck', 424.5, 451., 1.), ('Clow', 330., 400., 2.)] + t = targets('test_targeting_past_zero_heat', rows, 10., + approach_contributions={'Ck': .5}) + utilities = t.utility_targets() + LP, MP = agent('low_pressure_steam'), agent('medium_pressure_steam') + assert [u.ID for u in utilities] == [LP.ID, MP.ID] + assert_allclose([u.duty for u in utilities], [25.5 * kW, 20. * kW], rtol=1e-12) + u = utilities[1] + assert u.level == MP.T - MP.dT - bst.HeatUtility.dT + assert (u.T_in, u.T_out) == (pytest.approx(447., abs=1e-9), + pytest.approx(449., abs=1e-9)) + assert u.cost == pytest.approx(fresh_price(20. * kW, 447., 449., MP)[0], rel=1e-9) + # biosteam's own choice for Ctop + hu = bst.HeatUtility() + hu(20. * kW, 447., 449.) + assert hu.agent is MP + # cooling: the curve is zero at shifted 450 K (the pinch) and 300 K. H1 + # (shifted 450 -> 302 or 307 K) lies between the two and gives all its + # heat to C2, so wherever it ends it moves no level: cooling water + # serves H2 (shifted 300 -> 280 K) down to its limit, 312.372 K real. + CW, CHW = agent('cooling_water'), agent('chilled_water') + T_limit = CW.T + CW.dT + bst.HeatUtility.dT # real + results = [] + for end, CP in ((308., 1.), (313., 148. / 143.)): + rows = [('C1', 450., 500., 1.), ('H1', 456., end, CP), + ('C2', 300., 440., 148. / 140.), ('H2', 314., 294., 10.)] + t = targets(f'test_targeting_past_zero_cool_{end:g}', rows, 10., + approach_contributions={'H1': 1., 'H2': 9.}) + assert (t.Q_hot, t.Q_cold) == (pytest.approx(50. * kW, rel=1e-12), + pytest.approx(200. * kW, rel=1e-12)) + T, H = t.grand_composite_curve() + assert np.abs(H[(T == 450.) | (T == 300.)]).max() <= 1e-6 + cool = [u for u in t.utility_targets() if not u.heating] + assert [u.ID for u in cool] == [CW.ID, CHW.ID] + u = cool[0] + assert u.duty == pytest.approx(10. * kW * (314. - T_limit), rel=1e-9) + assert (u.T_in, u.T_out) == (pytest.approx(314., abs=1e-9), + pytest.approx(T_limit, abs=1e-9)) + assert u.cost == pytest.approx(fresh_price(-u.duty, 314., T_limit, CW)[0], + rel=1e-9) + results.append([(u.level, u.duty, u.cost) for u in cool]) + assert_allclose(results[0], results[1], rtol=1e-12) + def test_sensible_agent_without_driving_force(): # a sensible agent with agent.dT = 0 (UtilityAgent's default) has no # driving force left at a point load at its limit, agent.T - dT @@ -1422,6 +1474,46 @@ def test_from_heat_utilities_takes_exchangers(): [idle, *sorted(selected, key=lambda hu: hu.duty)], 10.) assert same.labels == t.labels and (same.Q_hot, same.Q_cold) == (t.Q_hot, t.Q_cold) +def test_from_heat_utilities_after_replaced_heat_utilities(kemp_hxn, kemp_hxn_replaced): + # biosteam's selection after a simulation with + # replace_unit_heat_utilities=True holds the network's utilities: C1 + # and H4 keep 20 and 60 kW of their 230 and 180 kW, and H2 and C3, + # served by the process exchangers alone, are empty and left out. A + # RuntimeWarning says so (the targets would be those of C1 and H4 + # alone); the facility's targets are the process's. + def select(sys): + hus = bst.process_tools.heat_exchanger_utilities_from_units(sys.units) + return sorted(hus, key=lambda hu: hu.unit.ID) + units, HXN = kemp_hxn_replaced + hus = select(HXN.system) + assert [hu.unit for hu in hus] == [units[0], units[3]] + assert_allclose([hu.unit_duty / kW for hu in hus], [20., -60.], rtol=1e-9) + message = (r"heat utilities of 'C1', 'H4' have duties \(hu.unit_duty\) other " + r"than their process streams' .*replace_unit_heat_utilities=True" + r".*take its targets\(\) instead") + with pytest.warns(RuntimeWarning, match=message): + t = PinchTargets.from_heat_utilities(hus, 10.) + assert t.labels == ['C1', 'H4'] + facility = HXN.targets() + assert facility.labels == ['C1', 'C3', 'H2', 'H4'] + assert (facility.Q_hot, facility.Q_cold) == (pytest.approx(20. * kW, rel=1e-9), + pytest.approx(60. * kW, rel=1e-9)) + # every unit's own heat utility: the empty ones are named too + with pytest.warns(RuntimeWarning, match="of 'C1', 'H2', 'C3', 'H4' have duties"): + PinchTargets.from_heat_utilities([u.heat_utilities[0] for u in units], 10.) + # without replace_unit_heat_utilities the selection is the process's, and + # no warning is raised + units, HXN = kemp_hxn + hus = select(HXN.system) + assert [hu.unit for hu in hus] == [units[0], units[2], units[1], units[3]] + with warnings.catch_warnings(): + warnings.simplefilter('error') + t = PinchTargets.from_heat_utilities(hus, 10.) + facility = HXN.targets() + assert t.labels == facility.labels + assert (t.Q_hot, t.Q_cold) == (pytest.approx(facility.Q_hot, rel=1e-12), + pytest.approx(facility.Q_cold, rel=1e-12)) + def test_is_hot_validated(): # is_hot must agree with each stream's direction (a stream without duty # may be either) From 8277a17bd1d989babe595a8ffac522322c81b6a3 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 10:19:29 +0000 Subject: [PATCH 26/52] address review 4 (tg-report): document and test the inf area of a crossed utility TGR4-1 (fixed, documented): network_report keeps biosteam's utility specification, so an exchanger whose utility line crosses its stream's profile (cooling water heated to its 324.82 K limit against steam that condenses just below it) has an inf 'area', and so has the network. That is the honest answer for the network as built: sizing it for more of the utility would be another network at another utility cost than the one the report prices, and leaving it out of the total would understate the area. Now: - the RuntimeWarning says why: " transfers heat at a driving force of -4.35 K: its cooling_water from 305.37 to 324.82 K, as biosteam specifies it, meets or crosses the profile of H1"; - the network_report Returns say 'area' is inf if an exchanger's is and that 'area_biosteam' is sized on terminal differences, and a Notes paragraph gives the cause, the agents' limits, that area_biosteam, costs, TAC and the area target stay finite, and why the report keeps biosteam's specification; - concepts.rst and ch05 say the same in their report paragraphs; - test_network_report_crossed_utility builds the vacuum condenser on a real network, checks independently (flashes of the outlet) that the water crosses the stream inside the exchanger with positive terminal differences, and checks the warning, the inf areas, the finite biosteam area, costs and targets, and the targets' water line below the dew point. TGR4-2 (nit, applied): concepts.rst and ch05 now say the report integrates along the streams' profiles as the targets' curves model them, not along the exact stream profiles. No number shown in the docs changes, so no capture was regenerated; the synthesis path is untouched (report code only). Tests: pytest tests/test_targeting.py 61 passed; pytest hensmith/targeting.py (doctests) 6 passed; sphinx-build -W clean. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/concepts.rst | 24 +++++++---- docs/source/tutorial/05_targeting.rst | 16 ++++++-- hensmith/targeting.py | 47 +++++++++++++++++----- tests/test_targeting.py | 58 +++++++++++++++++++++++++-- 4 files changed, 119 insertions(+), 26 deletions(-) diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index ec7fe78..6289a8a 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -576,13 +576,23 @@ contribution scaling of Ahmad, Linnhoff & Smith (1990). synthesized network with its targets: utility cost, units, area, capital and total annualized cost. Its ``'area'`` re-sizes the network's exchangers like the target sizes them (the same film coefficients, a correction factor of 1, -the integrated driving force along the exact stream profiles and the process -side duty), so it is comparable with the target; ``'area_biosteam'`` is -BioSTEAM's own sizing, which includes its correction factor, the terminal -log-mean temperature difference and the utility side duty. The report checks -that its targets and the network's streams are both of the units' current -states: after a stream or a unit changes, it raises a ``ValueError`` until the -system is simulated again. The comparison has limits: +the integrated driving force along the streams' profiles as the targets' +curves model them and the process side duty), so it is comparable with the +target; ``'area_biosteam'`` is BioSTEAM's own sizing, which includes its +correction factor, the terminal log-mean temperature difference and the +utility side duty. An exchanger whose profiles meet or cross has an infinite +``'area'``, and so has the network, with a ``RuntimeWarning`` naming it, while +its ``'area_biosteam'`` stays finite: BioSTEAM specifies a utility from the +ends of its exchanger only, taking a sensible agent to its limit (cooling +water to 324.82 K) whenever the process inlet allows it, so against a stream +that condenses a little below that limit (a vacuum condenser, say) the +utility's line crosses the condensing stretch inside the exchanger. The +report keeps BioSTEAM's specification of the utility, whose cost it reports; +the target, which keeps a sensible agent's line inside the grand composite +curve, stays finite. The report checks that its targets and the network's +streams are both of the units' current states: after a stream or a unit +changes, it raises a ``ValueError`` until the system is simulated again. The +comparison has limits: - the Bath target bounds the area only with equal film coefficients and the same utilities; diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index a3ca900..6786b04 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -344,16 +344,24 @@ 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 exact - stream profiles. On that basis the network needs more area than the 5946 m² - target, as it should with equal film coefficients and the same utilities. + 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. + 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 + :func:`~hensmith.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 diff --git a/hensmith/targeting.py b/hensmith/targeting.py index a730e39..113ca33 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -2356,17 +2356,17 @@ def __repr__(self): f'(TAC {self.optimum["TAC"]:.3e} USD/yr){bound}>') -def _profile_area(x, dT, inv_U, ID): +def _profile_area(x, dT, inv_U, ID, cause=''): """ Area [m²] of counter-current heat transfer over the duties `x` [kJ/hr] (ascending) at the driving forces `dT` [K] there, linear in between (each stretch at its log-mean temperature difference), with an overall 1/U of `inv_U` [m²·K/kW]; inf with a RuntimeWarning naming the exchanger `ID` - at a driving force of `_DT_ZERO` or less. + (followed by `cause`) at a driving force of `_DT_ZERO` or less. """ if dT.min() <= _DT_ZERO: - warn(f'{ID} transfers heat at a driving force of {dT.min():.6g} K', - RuntimeWarning) + warn(f'{ID} transfers heat at a driving force of {dT.min():.6g} K' + f'{cause}', RuntimeWarning) return math.inf LMTD = _lmtd(dT[:-1], dT[1:]) return float(np.sum(np.diff(x) / LMTD)) * inv_U / 3600. @@ -2489,8 +2489,12 @@ def _network_areas(HXN, targets, h, h_utilities, agents): T_a, T_b = hu.inlet_utility_stream.T, hu.outlet_utility_stream.T T_u = T_a + (T_b - T_a) * x / Q dT = T_u - T_p(x) if heating else T_p(x) - T_u + span = (f'at {T_a:.2f} K' if T_a == T_b + else f'from {T_a:.2f} to {T_b:.2f} K') + cause = (f': its {agent.ID} {span}, as biosteam specifies it, meets ' + f'or crosses the profile of {targets.labels[i]}') areas[hx.ID] = ( - _profile_area(x, dT, 1. / h[i] + 1. / h_agent, hx.ID), + _profile_area(x, dT, 1. / h[i] + 1. / h_agent, hx.ID, cause), hx.design_results.get('Area', 0.) / _FT2_PER_M2) return areas @@ -2535,12 +2539,14 @@ def network_report(HXN, targets=None, **options): 'utility_cost' [USD/hr]; 'N_units', the exchangers with duty (a furnace included), of which 'N_process_units' and 'N_utility_units'; 'area', the network's area like for like with - the area target (see Notes) [m²]; 'area_biosteam', the area biosteam - sized [m²]; 'exchanger_areas', ``{ID: (area, area_biosteam)}`` of - each exchanger with duty [m²]; 'purchase_cost' and 'installed_cost' - of all the network's exchangers [USD]; 'annualized_capital_cost', - 'operating_cost' and 'TAC' [USD/yr], as `PinchTargets.summary` - computes them; 'targets', `PinchTargets.summary` of the targets. + the area target [m²], inf if an exchanger's is (see Notes); + 'area_biosteam', the area biosteam sized on terminal temperature + differences [m²]; 'exchanger_areas', + ``{ID: (area, area_biosteam)}`` of each exchanger with duty [m²]; + 'purchase_cost' and 'installed_cost' of all the network's + exchangers [USD]; 'annualized_capital_cost', 'operating_cost' and + 'TAC' [USD/yr], as `PinchTargets.summary` computes them; 'targets', + `PinchTargets.summary` of the targets. Raises ------ @@ -2574,6 +2580,25 @@ def network_report(HXN, targets=None, **options): the mean difference along a glide) and the utility duty (the process duty divided by the heat transfer efficiency). + An exchanger's 'area' is inf, and so the network's 'area', with a + RuntimeWarning naming the exchanger, where its two temperature profiles + meet or cross. biosteam specifies a utility from the temperatures at + the ends of its exchanger only: it takes a sensible agent to its limit + whenever the process inlet allows it (by ``bst.HeatUtility.dT``), as + cooling water to 324.82 K and chilled water to 300.37 K, so against a + stream that condenses a little below that limit (a vacuum condenser of + steam condensing at 320 K, say) the utility's line crosses the + condensing stretch inside the exchanger: as specified, the exchanger + cannot be built. Its 'area_biosteam' stays finite, since biosteam sizes + on the terminal temperature differences, and so do the costs and the + TAC, which are biosteam's. The targets keep a sensible agent's line + inside the grand composite curve, so the area target of such streams + is finite. The report keeps biosteam's specification rather than size + the exchanger for more of the utility, leaving where its line stays + inside the stream's profile: that would be another network at another + utility cost. 'exchanger_areas' gives the areas of the other + exchangers. + The capital and utility costs are those of the whole network (`HXN.new_HXs` and `HXN.new_HX_utils`), not the facility's costs, which are the differences from the original exchangers (and zero for diff --git a/tests/test_targeting.py b/tests/test_targeting.py index a8550c9..2b60336 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -26,10 +26,11 @@ contributions and with replaced heat utilities), the report's areas against terminal log-mean temperature differences and against an independent integration on the network's own streams (branches of a - split, phase changes), the report at the units' current states only - (a cached network re-simulated, stale states detected, also with ideal - thermodynamics), and no side effects on the flowsheet, the agents' - stream dumps or a later synthesis. + split, phase changes), an infinite area where biosteam's utility + crosses its stream inside the exchanger, the report at the units' + current states only (a cached network re-simulated, stale states + detected, also with ideal thermodynamics), and no side effects on the + flowsheet, the agents' stream dumps or a later synthesis. E. Plots: stable gids whose data equal the curves, and readable labels and markers. @@ -2061,6 +2062,55 @@ def test_network_report_independent_areas(quickstart_hxn): assert any(ID.startswith('HX_') for ID in boiling) assert any(ID.startswith('Util_') for ID in boiling) +def test_network_report_crossed_utility(): + # A vacuum condenser: steam at 10.6 kPa (dew point 320.10 K) condensed + # to 317 K. biosteam heats its cooling water to the limit, 324.82 K, + # since the process inlet less dT is above it: the terminal + # differences are positive, but the water's line crosses the + # condensing stretch inside the exchanger. Its area is inf, and so the + # network's, with a warning giving the cause; biosteam's area, the + # costs and the targets stay finite. + bst.main_flowsheet.set_flowsheet('test_targeting_report_crossed') + bst.settings.set_thermo(['Water', 'Ethanol'], cache=True) + vapor = bst.Stream('V_in', Water=100., T=345., P=10600., phase='g') + liquid = bst.Stream('L_in', Water=5., T=300., P=101325., phase='l') + H1 = bst.HXutility('H1', ins=vapor, T=317., rigorous=True) + C1 = bst.HXutility('C1', ins=liquid, T=305., rigorous=False) + dew = vapor.dew_point_at_P().T + sys, HXN = simulate_HXN([H1, C1], 5.) + condenser, = [hx for hx in HXN.new_HX_utils if hx.total_heat_transfer > 1e-12] + hu = condenser.heat_utilities[0] + water = bst.HeatUtility.get_agent('cooling_water') + assert hu.agent.ID == 'cooling_water' + assert hu.outlet_utility_stream.T == water.T_limit + s_in, s_out = condenser.ins[0], condenser.outs[0] + assert s_in.T - hu.outlet_utility_stream.T > 0. and s_out.T - hu.inlet_utility_stream.T > 0. + assert s_out.T < dew < water.T_limit + # independently: flashes of the outlet put the stream below the water + # inside the exchanger + x = np.linspace(0., s_in.H - s_out.H, 41) + T_u = hu.inlet_utility_stream.T + (water.T_limit - hu.inlet_utility_stream.T) * x / x[-1] + dT = np.array([flashed_T(s_out, s_out.H + q) for q in x]) - T_u + assert dT.min() < -1. + with pytest.warns(RuntimeWarning, match=( + rf'{condenser.ID} transfers heat at a driving force of -[\d.]+ K: its ' + 'cooling_water from 305.37 to 324.82 K, as biosteam specifies it, ' + 'meets or crosses the profile of H1')): + report = network_report(HXN) + areas = report['exchanger_areas'] + assert sorted(areas) == sorted(hx.ID for hx in HXN.new_HXs + [condenser]) + assert areas[condenser.ID][0] == report['area'] == math.inf + assert math.isfinite(areas[condenser.ID][1]) and areas[condenser.ID][1] > 0. + assert all(math.isfinite(a) for ID, (a, _) in areas.items() if ID != condenser.ID) + assert report['area_biosteam'] == pytest.approx(sum(b for _, b in areas.values()), rel=1e-12) + assert math.isfinite(report['TAC']) and math.isfinite(report['installed_cost']) + targets = report['targets'] + assert math.isfinite(targets['area']) and math.isfinite(targets['TAC']) + # the targets keep the water's line inside the grand composite curve + (cooling,) = [u for u in HXN.targets().utility_targets() if not u.heating] + assert cooling.agent.ID == 'cooling_water' + assert cooling.T_utility_out < dew + def test_network_report_current_states(): # With cache_network, the network re-simulated at changed states is # reported at those states From 6c026b3e6e771779ef793ce3f2163db0def64d6c Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 12:39:16 +0000 Subject: [PATCH 27/52] address review 5 (f1-numerics): pass the scalar T_min_app as the default pair requirement F1N-R5-1 (fixed): synthesize_network passed the shift vector grid['shift'] as the pair requirement even with no contribution given, so _required_approach's float(shift[h] - shift[c]) promoted a numpy float32 T_min_app to float64 in the exactness limit, the repair and every HXprocess dT guard, changing default results relative to 5ef9a35. The default path (values is None) now passes T_min_app itself, as given (`shift = T_min_app if values is None else grid['shift']`); _exact_approach, _repair and _realize already accept a scalar, and the contribution path is unchanged. The comment there now says why. New regression test test_default_float32_T_min_app_keeps_its_own_arithmetic (r002, case_05_boiling_cold, smith2005_ex18_2_split) checks every exchanger's dT against float32 T_min_app - _APPROACH_TOL by float.hex (and that this differs from the float64 guard), and that contributions unspecified in any form ({}, all nan, a callable returning None, None) give the same network bit for bit; it fails on the previous code for all three cases. The reviewer's f32.py script now prints output identical to the 5ef9a35 tree. Tests: oracle sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (29 records, unchanged); tests/test_hxn_contributions.py plus the hensmith/hxn_synthesis.py doctests: 49 passed; tests/test_hxn_regression.py: 20 passed. No docs numbers change (float and int T_min_app were already bit-identical). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- hensmith/hxn_synthesis.py | 11 +++++++---- tests/test_hxn_contributions.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/hensmith/hxn_synthesis.py b/hensmith/hxn_synthesis.py index dc46b04..f4cf1de 100644 --- a/hensmith/hxn_synthesis.py +++ b/hensmith/hxn_synthesis.py @@ -2479,10 +2479,13 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, hx_utils_rearranged, streams_quenched, table, curves, grid = \ _pinch_analysis(hus, T_min_app, force_ideal_thermo, sort_hus_by_T, values) - # every pair's required approach is shift[h] - shift[c] (T_min_app by - # default, to the bit); the planner gets the contributions only if any - # is given, so the default call is unchanged - shift = grid['shift'] + # every pair's required approach is shift[h] - shift[c]; by default it + # is the scalar T_min_app itself, passed as given (not the shift vector, + # whose float() pair difference in `_required_approach` would promote a + # numpy float32 T_min_app to float64 and change the guards), so the + # default checks, repair and realization are unchanged to the bit; the + # planner gets the contributions only if any is given, likewise + shift = T_min_app if values is None else grid['shift'] contributions = None if values is None else grid['contributions'] N = len(hxs) is_hot = [False] * N diff --git a/tests/test_hxn_contributions.py b/tests/test_hxn_contributions.py index 66b8283..35e5137 100644 --- a/tests/test_hxn_contributions.py +++ b/tests/test_hxn_contributions.py @@ -15,7 +15,8 @@ mapping, a callable returning None, or every stream at ``T_min_app / 2``) give the default plans, problem tables and facility networks bit for bit, compared in-process with ``float.hex`` and - ``tobytes``. + ``tobytes``; a numpy float32 ``T_min_app`` keeps its own float32 + arithmetic in every exchanger's guard. B. Uniform contributions: every stream at ``x`` plans the network of ``T_min_app = 2x`` on a shifted scale offset by a constant, and on real-thermo cases all hot streams at ``a`` and all cold ones at ``b`` @@ -340,6 +341,32 @@ def test_default_networks_bit_for_bit(name): == float(info['min_approach'] - T).hex()) if splitting: assert HXN.new_splitters +@pytest.mark.parametrize('name', ['r002', 'case_05_boiling_cold', + 'smith2005_ex18_2_split']) +def test_default_float32_T_min_app_keeps_its_own_arithmetic(name): + # the default path uses a numpy float32 T_min_app as given (as before + # approach contributions existed), not promoted to float64 through the + # per-stream shifts: every exchanger's guard is HXprocess.dT = T_min_app + # - _APPROACH_TOL in float32, with contributions unspecified in any form + units, T, splitting = default_network_units(name) + T = np.float32(T) + guard = float(T - hxn_synthesis._APPROACH_TOL).hex() + # the float32 guard differs from the float64 one, so this discriminates + assert guard != (float(T) - hxn_synthesis._APPROACH_TOL).hex() + sys, HXN = simulate_HXN(units, T, f'contributions_f32_{name}', + stream_splitting=splitting) + assert HXN.new_HXs + assert all(float(u.dT).hex() == guard for u in HXN.new_HXs) + reference = network_record(HXN) + for contributions in ({}, {u: NAN for u in units}, lambda hu: None, + None): + HXN.approach_contributions = contributions + simulate_strictly(sys) + assert HXN._resolved_contributions is None + assert network_record(HXN) == reference + assert (float(HXN.synthesis_info['min_approach_margin']).hex() + == float(HXN.synthesis_info['min_approach'] - T).hex()) + # --------------------------------------------------------------------------- # B. Uniform contributions # --------------------------------------------------------------------------- From 8c259b50da1e49662bccfa47d37e92165626104a Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 12:52:09 +0000 Subject: [PATCH 28/52] address review 5 (tg-core): check the shift of utilities, test point loads at the pinch TGC5-1 (fixed): the duties check let utilities of another T_min_app through wherever the duties do not depend on it (streams of one kind, threshold problems below their threshold), and their cooling levels were then read on the wrong shifted temperatures (negative balanced GCC, wrong units). UtilityTarget gets a trailing field `shift` (default None, additive): the PinchTargets.shift of the targets that placed it, as a tuple. _check_utilities now also raises a ValueError when a utility's shift is not None and is not the targets' shift, which covers the balanced composite curves, the balanced GCC and the area, units and capital targets alike. Recording the shift rather than T_min_app and pinch_T compares what the levels are actually placed on: it catches other approach contributions, and it accepts targets with the same shifts (at() of the same T_min_app, or all-cold problems, whose shifts are 0 at every T_min_app and whose results are the same). A check of B.min() alone would not have caught utilities of a larger T_min_app (their levels are too low, so B stays non-negative but the units are 3 instead of 4), and it would not have covered the area. A utility built by hand (shift None) is still checked by its duties only. The UtilityTarget, method parameter and tutorial ch05 docs say so. New test: test_utilities_of_another_shift (one-sided and threshold problems with the utilities of at(0., 5., 20.) raise for every method; at(10.)'s and hand-built ones give the targets' own results). It fails if the shift check is bypassed. TGC5-2 (fixed): new test_point_loads_at_the_pinch_follow_the_cut. The p8 condenser at the pinch (cut 'below') gives no cooling water, chilled water serves all of Q_cold, and every cooling T_out is above agent.T + agent.dT + HeatUtility.dT. case_two_stream (cut 'above') gives a units target of {'above': 2, 'below': 2}. Fails under the reviewer's MUT=sidecut and MUT=pinchpos mutations. TGC5-3 (nit, fixed): new test_level_at_a_point_load_up_to_rounding, parametrized over steam limits 1e-11 K below, at and above a boiler's grid temperature: the steam serves all of Q_hot. Fails under MUT=snap at -1e-11 K. No docs numbers change, so no captures or figures were regenerated. The synthesis path is untouched. Oracle sha256 is still dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf. Tests: pytest -n 8 tests/test_targeting.py tests/test_hxn_targets.py: 108 passed. pytest hensmith/targeting.py (doctests): 6 passed. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/tutorial/05_targeting.rst | 5 +- hensmith/targeting.py | 66 +++++++++++------ tests/test_targeting.py | 100 ++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 23 deletions(-) diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index 6786b04..534725d 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -187,8 +187,9 @@ On the balanced composite curves, :meth:`~hensmith.PinchTargets.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 a -mismatch raises a ``ValueError``. +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``. .. literalinclude:: /../_demo_src/examples/ch05_targeting.py :language: python diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 113ca33..62ce73e 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -77,7 +77,8 @@ UtilityTarget = namedtuple('UtilityTarget', ( 'ID', 'agent', 'heating', 'level', 'duty', 'utility_duty', 'flow', 'cost', - 'T_in', 'T_out', 'T_utility_in', 'T_utility_out')) + 'T_in', 'T_out', 'T_utility_in', 'T_utility_out', 'shift'), + defaults=(None,)) UtilityTarget.__doc__ = """ One utility of `PinchTargets.utility_targets`: an agent placed on the grand @@ -116,6 +117,12 @@ T_utility_in, T_utility_out : float Inlet and outlet temperature of the agent [K], from the priced `HeatUtility`; nan when unserved. +shift : tuple[float] or None + `PinchTargets.shift` of the targets that placed it [K], in stream order: + `level` is on their shifted temperatures, and only targets with the same + shifts take it (their balanced curves and their area, units and capital + targets raise a ValueError for any other). None, the default, for a + utility built by hand, which is not checked. """ @@ -1567,21 +1574,21 @@ def utility_targets(self, heating_agents=None, cooling_agents=None): T_out, T_priced)) return utilities - @staticmethod - def _utility(agent, heating, level, q, T_in, T_out, T_priced): + def _utility(self, agent, heating, level, q, T_in, T_out, T_priced): """The `UtilityTarget` of `agent` serving `q` from `T_in` to `T_out` (process temperatures), priced as one biosteam heat utility from `T_priced` to `T_out` (inside `_isolated_pricing`).""" + shift = tuple(self.shift.tolist()) if agent is None: nan = math.nan return UtilityTarget(None, None, heating, level, q, nan, nan, nan, - T_in, T_out, nan, nan) + T_in, T_out, nan, nan, shift) hu = bst.HeatUtility() hu(q if heating else -q, T_priced, T_out, agent=agent) return UtilityTarget(agent.ID, agent, heating, level, q, abs(hu.duty), abs(hu.flow), hu.cost, T_in, T_out, hu.inlet_utility_stream.T, - hu.outlet_utility_stream.T) + hu.outlet_utility_stream.T, shift) # %% Balanced curves @@ -1589,7 +1596,11 @@ def _check_utilities(self, utilities): """Raise a ValueError unless the heating duties of `utilities` (unserved ones included) add up to `Q_hot` and the cooling ones to `Q_cold`, as those of `utility_targets` do (to the remainders of at - most `_tol` that it leaves out, and round-off).""" + most `_tol` that it leaves out, and round-off), and every utility's + `shift` is None or `shift`: the duties alone do not tell targets of + another shift apart when they do not depend on it (streams of one + kind only, or a threshold problem below its threshold), and their + levels would then be read on the wrong shifted temperatures.""" for heating, Q, name in ((True, self.Q_hot, 'Q_hot'), (False, self.Q_cold, 'Q_cold')): total = math.fsum([u.duty for u in utilities @@ -1602,6 +1613,14 @@ def _check_utilities(self, utilities): 'utility_targets() of these targets (those of other ' 'targets, e.g. of another T_min_app, or a part of them ' 'do not balance their curves)') + for u in utilities: + if u.shift is not None and not np.array_equal(u.shift, self.shift): + raise ValueError( + f'utility {u.ID!r} was placed on the shifted temperatures ' + 'of other targets (its shift is not that of these ' + 'targets): give the utility_targets() of these targets ' + '(those of other targets, e.g. of another T_min_app, do ' + 'not balance their curves)') def _balanced_segments(self, utilities, inv_h=None, inv_h_utilities=None): """ @@ -1644,9 +1663,10 @@ def balanced_composite_curves(self, utilities=None): utilities : list[UtilityTarget], optional `utility_targets` of these targets, with any agents: a ValueError is raised unless the heating duties (unserved ones - included) add up to `Q_hot` and the cooling ones to `Q_cold` - (the utilities of other targets, e.g. of another `T_min_app`, - or a part of them do not balance the curves). Defaults to + included) add up to `Q_hot` and the cooling ones to `Q_cold`, + and the `shift` of each is None or these targets' `shift` (the + utilities of other targets, e.g. of another `T_min_app`, or a + part of them do not balance the curves). Defaults to `utility_targets` with the default agents. Returns @@ -1693,9 +1713,10 @@ def balanced_grand_composite_curve(self, utilities=None): utilities : list[UtilityTarget], optional `utility_targets` of these targets, with any agents: a ValueError is raised unless the heating duties (unserved ones - included) add up to `Q_hot` and the cooling ones to `Q_cold` - (the utilities of other targets, e.g. of another `T_min_app`, - or a part of them do not balance the curves). Defaults to + included) add up to `Q_hot` and the cooling ones to `Q_cold`, + and the `shift` of each is None or these targets' `shift` (the + utilities of other targets, e.g. of another `T_min_app`, or a + part of them do not balance the curves). Defaults to `utility_targets` with the default agents. Returns @@ -1818,9 +1839,10 @@ def area_target(self, utilities=None, h=None, h_utilities=None): utilities : list[UtilityTarget], optional `utility_targets` of these targets, with any agents: a ValueError is raised unless the heating duties (unserved ones - included) add up to `Q_hot` and the cooling ones to `Q_cold` - (the utilities of other targets, e.g. of another `T_min_app`, - or a part of them do not balance the curves). Defaults to + included) add up to `Q_hot` and the cooling ones to `Q_cold`, + and the `shift` of each is None or these targets' `shift` (the + utilities of other targets, e.g. of another `T_min_app`, or a + part of them do not balance the curves). Defaults to `utility_targets` with the default agents. h : float, Sequence[float] or Mapping, optional Film coefficient of the streams [kW/m²/K]: one for all, one per @@ -1865,9 +1887,10 @@ def units_target(self, utilities=None): utilities : list[UtilityTarget], optional `utility_targets` of these targets, with any agents: a ValueError is raised unless the heating duties (unserved ones - included) add up to `Q_hot` and the cooling ones to `Q_cold` - (the utilities of other targets, e.g. of another `T_min_app`, - or a part of them do not balance the curves). Defaults to + included) add up to `Q_hot` and the cooling ones to `Q_cold`, + and the `shift` of each is None or these targets' `shift` (the + utilities of other targets, e.g. of another `T_min_app`, or a + part of them do not balance the curves). Defaults to `utility_targets` with the default agents. Returns @@ -1954,9 +1977,10 @@ def capital_cost_target(self, utilities=None, h=None, h_utilities=None, utilities : list[UtilityTarget], optional `utility_targets` of these targets, with any agents: a ValueError is raised unless the heating duties (unserved ones - included) add up to `Q_hot` and the cooling ones to `Q_cold` - (the utilities of other targets, e.g. of another `T_min_app`, - or a part of them do not balance the curves). Defaults to + included) add up to `Q_hot` and the cooling ones to `Q_cold`, + and the `shift` of each is None or these targets' `shift` (the + utilities of other targets, e.g. of another `T_min_app`, or a + part of them do not balance the curves). Defaults to `utility_targets` with the default agents. h, h_utilities : optional Film coefficients, as `area_target` takes them. diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 2b60336..6732096 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -862,6 +862,68 @@ def test_point_loads_at_stretch_ends(): with pytest.raises(RuntimeError, match='no stream has heat in the stretch'): t._ends(np.zeros((2, 3)), 0, 2, 300., 310., True) +def test_point_loads_at_the_pinch_follow_the_cut(): + # the point loads at the pinch temperature lie on the side of the cut: + # a condenser there with the cut 'below' is served below the pinch, so + # it caps the cooling levels. Cooling water, whose limit lies above the + # condensing temperature, then serves nothing and chilled water serves + # all, leaving above the coldest temperature at which biosteam takes it + # (on the other side, cooling water would leave below its limit) + test_hxn_targets.setup('test_targeting_cut_condenser', ['Water']) + P = bst.settings.chemicals.Water.Psat(311.) + vapour = bst.Stream(None, Water=20., T=311., P=P, phase='g') + liquid = vapour.copy() + liquid.vle(V=0., P=P) + hot_in = bst.Stream(None, Water=200., T=330., P=3e5) + hot_out = hot_in.copy() + hot_out.T = 300. + # the cold inlet at the condenser's shifted temperature + cold_in = bst.Stream(None, Water=400., T=liquid.T - 6., P=3e5) + cold_out = cold_in.copy() + cold_out.T = 340. + t = PinchTargets([cold_in, vapour, hot_in], [cold_out, liquid, hot_out], + [False, True, True], 10., [None, 1., 9.], + labels=['C', 'COND', 'H']) + assert t.cut == 'below' + assert t.pinch_T + t.shift[1] == pytest.approx(liquid.T, abs=1e-9) + dT = bst.HeatUtility.dT + cooling = [u for u in t.utility_targets() if not u.heating] + assert [u.ID for u in cooling] == ['chilled_water'] + assert cooling[0].duty == pytest.approx(t.Q_cold, rel=1e-12) + for u in cooling: # biosteam's get_suitable_cooling_agent + assert u.T_out > u.agent.T + u.agent.dT + dT + # a boiler at the pinch with the cut 'above' is served above it: above + # the pinch are C1's latent heat, H1 and low pressure steam (2 units), + # below it H1, C1's sensible heat and cooling water (2 units) + units, T_min_app = test_hxn_targets.case_two_stream() + t = targets_of(units, T_min_app) + assert t.cut == 'above' + assert t.units_target() == {'above': 2, 'below': 2, 'total': 4} + +@pytest.mark.parametrize('offset', [-1e-11, 0., 1e-11]) +def test_level_at_a_point_load_up_to_rounding(offset): + # a level equal to a grid temperature serves that temperature's point + # loads, also when it is off by rounding: a steam whose limit (its T + # less its dT and biosteam's) lies within rounding of where a boiler + # boils serves it all + test_hxn_targets.setup('test_targeting_snap', ['Water']) + boiler = water_boiler('B', 380., 10.) + t = targets_of([boiler], 10.) + k = np.flatnonzero(np.abs(t.table.point_H[0]) > 0.) + assert k.size == 1 + T_b = t.grid['Ts'][k[0]] + t.shift[0] + dT = bst.HeatUtility.dT + T = T_b + dT + offset + steam = bst.UtilityAgent('test_targeting_snap_steam', Water=1, T=T, + P=bst.settings.chemicals.Water.Psat(T), phase='g', + thermo=bst.HeatUtility.thermo_water, + regeneration_price=0.2378) + with warnings.catch_warnings(): + warnings.simplefilter('error', RuntimeWarning) # nothing unserved + utilities = t.utility_targets([steam], None) + assert [u.ID for u in utilities] == [steam.ID] + assert utilities[0].duty == pytest.approx(t.Q_hot, rel=1e-12) + def test_levels_of_streams_beyond_reach(): # a level serves only what lies below it (above it, for cooling): a # stream lying wholly beyond an agent's limit bounds the level only at @@ -1103,6 +1165,44 @@ def test_utilities_must_be_of_the_targets(quickstart): fig, ax = plot_grand_composite_curve(t20, utilities=mismatched[:1]) plt.close(fig) +def test_utilities_of_another_shift(): + # the utility targets of streams of one kind, or of a threshold problem + # below its threshold, do not depend on T_min_app, but the levels of + # cooling utilities do (they are on the shifted temperatures): the + # utilities of another shift raise wherever they are balanced even + # though their duties add up, so that the balanced grand composite + # curve stays non-negative and the units are those of these targets. + # Those of targets with the same shifts are the targets' own; one + # built by hand (shift None) is checked by its duties only + hot_only = [('H1', 400., 320., 1.), ('H2', 500., 290., 1.)] + threshold = [('H1', 450., 300., 2.), ('C1', 290., 380., 1.)] + for n, rows in enumerate((hot_only, threshold)): + t = targets(f'test_targeting_shift_{n}', rows, 10.) + own = t.utility_targets() + assert t.Q_hot == 0. and t.Q_cold > 0. + assert all(u.shift == tuple(t.shift.tolist()) for u in own) + calls = [t.area_target, t.units_target, t.capital_cost_target, + t.balanced_composite_curves, t.balanced_grand_composite_curve] + for T_min_app in (0., 5., 20.): + other = t.at(T_min_app) + assert other.Q_cold == pytest.approx(t.Q_cold, rel=1e-12) + utilities = other.utility_targets() + for call in calls: + with pytest.raises(ValueError, match='shifted temperatures of other targets'): + call(utilities) + for utilities in (t.at(10.).utility_targets(), + [u._replace(shift=None) for u in own]): + assert t.units_target(utilities) == t.units_target(own) + assert t.area_target(utilities) == t.area_target(own) + T, B = t.balanced_grand_composite_curve(utilities) + assert_allclose(B, t.balanced_grand_composite_curve(own)[1], rtol=0, atol=0) + assert B.min() >= 0. + # streams that are all cold have the same shifts (0) at every T_min_app + rows = [('C1', 300., 420., 1.), ('C2', 310., 380., 2.)] + t = targets('test_targeting_shift_cold', rows, 10.) + utilities = t.at(0.).utility_targets() + assert t.units_target(utilities) == t.units_target(t.utility_targets()) + def test_bath_independent_integration(quickstart): # reference A: the streams plus the four utilities t = targets('test_targeting_integration_A', REF_A, 10.) From 0e58023dfbf2bf55642202af9ab484c212ba139a Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 13:03:03 +0000 Subject: [PATCH 29/52] address review 5 (tg-super): refine on both sides of the least tac, never on it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TGS5-1 (fixed): supertarget evaluated np.linspace(lo, hi, n_refine + 2) inside the bracket of the least TAC and skipped the candidate that fell on the optimum itself, so on a symmetric bracket (any evenly spaced grid) an odd n_refine lost a point per round and n_refine=1 refined nothing. Each round now puts its n_refine new temperatures on both sides of the least TAC, never on it: with it they cut the bracket into n_refine + 2 gaps, shared by its two sides in proportion to their widths, each side evenly spaced (all on the one side at a bound of the sweep, as before). The docstring says so, with the spacing after r rounds now about (2 / (n_refine + 2))**r of the grid's, (1/5)**r for the defaults. New test test_sweep_refinement: on the 1..40 K grid each n_refine in (1, 2, 3, 8, 9) adds exactly n_refine points per round (refine=1 and 3), none on the optimum, evenly spaced per side; n_refine=1 adds one record; a lopsided bracket and an at-bound sweep place theirs as described. Regenerated outputs: the supertarget doctest (30 points, optimum 14.96 K), the plot_supertargets doctest (64 points, 7.816 K), docs/_demo_src ch05 run alone (ch05_sweep: optimum 1.849 K; ch05_trade_off: 21.59 K, capital 71606.7, operating 20031 USD/yr; their two figures) and the matching numbers of tutorial ch05. TGS5-2 (nit, applied): concepts.rst now gives the exception to "never falls when a unit is added": shells beyond about 35,000 ft² (3,300 m²) for the default floating head of carbon steel, from about 15,000 ft² by type and material, whose cost rises faster than their area. The class Notes of PinchTargets say the same in place of "about 20,000 ft²" (thresholds measured with _shell_split over every type and material: 14,091 to 45,940 ft², 33,031 to 45,940 for the default). TGS5-3 (nit, applied): capital_cost_target documents 'type' None with an infinite or nan area too. Tests: pytest tests/test_targeting.py 67 passed; pytest hensmith/targeting.py (doctests) 6 passed; docs/_demo_src/examples/ ch05_targeting.py ran alone with its asserts; oracle sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (unchanged, synthesis path untouched). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/_generated/ch05_sweep.txt | 8 +-- docs/source/_generated/ch05_trade_off.txt | 10 +-- .../examples/tutorial_05_supertargets.png | Bin 75019 -> 75016 bytes .../images/examples/tutorial_05_trade_off.png | Bin 113207 -> 113220 bytes docs/source/concepts.rst | 7 +- docs/source/tutorial/05_targeting.rst | 12 ++-- hensmith/targeting.py | 62 +++++++++++------- tests/test_targeting.py | 29 ++++++++ 8 files changed, 88 insertions(+), 40 deletions(-) diff --git a/docs/source/_generated/ch05_sweep.txt b/docs/source/_generated/ch05_sweep.txt index bb7f153..bce0eca 100644 --- a/docs/source/_generated/ch05_sweep.txt +++ b/docs/source/_generated/ch05_sweep.txt @@ -1,9 +1,9 @@ - + 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.848 2.808e+08 0 6836 5 2.482e+06 4.04e+05 1.454e+07 14943211 -1.853 2.808e+08 1981 6835 6 2.555e+06 4.158e+05 1.454e+07 14955185 +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 @@ -11,4 +11,4 @@ T_min_app Q_hot Q_cold area units installed capital operating 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.848 K, at the bound of the sweep: False +optimum 1.849 K, at the bound of the sweep: False diff --git a/docs/source/_generated/ch05_trade_off.txt b/docs/source/_generated/ch05_trade_off.txt index 187be33..107b63d 100644 --- a/docs/source/_generated/ch05_trade_off.txt +++ b/docs/source/_generated/ch05_trade_off.txt @@ -1,8 +1,8 @@ -Q_hot 4.17333e+06 -area 719.848 +Q_hot 4.17312e+06 +area 719.867 N_units 3 -installed_cost 439986 -annualized_capital_cost 71605.6 -operating_cost 20032 +installed_cost 439992 +annualized_capital_cost 71606.7 +operating_cost 20031 TAC 91637.6 diff --git a/docs/source/_static/images/examples/tutorial_05_supertargets.png b/docs/source/_static/images/examples/tutorial_05_supertargets.png index 9828b94dea718d7a19a40933b607727975cbb901..70100cb59361db9f2f1b0b3b4cb4c3d9c169552b 100644 GIT binary patch delta 15783 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 delta 80588 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 A(*OVf diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 6289a8a..6250ff6 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -522,8 +522,11 @@ equal division puts the shells below 150 ft² and the area fills :math:`k` equal shell and tubes with :math:`N - k` shells of no area (each costed as BioSTEAM costs a vanishing double pipe, about 1,300 USD installed). This is the least change to the equal division that never falls when a unit -is added; where it departs from it, it tends to be high (networks keep only -part of their area in shell and tubes) as the equal division tends to be low. +is added, except for shells beyond about 35,000 ft² (3,300 m²) with the +default floating head of carbon steel (from about 15,000 ft² by type and +material), whose cost rises faster than their area; where it departs from it, +it tends to be high (networks keep only part of their area in shell and tubes) +as the equal division tends to be low. The summary's ``'N_area_shells'``, ``'shell_area'`` and ``'shell_type'`` say which shells carry the area. BioSTEAM's other specifics carry over: the floating head correlation is not monotone below about 126 ft², which the diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index 534725d..199016e 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -256,14 +256,14 @@ 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.848 K differ by less than 7 USD/yr, 4e-7 of the TAC, which is no physical +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.848 K (inside the -sweep, not at its bound), 0.005 K short of the step. That step comes at the +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, @@ -276,7 +276,7 @@ the units target jumps to 9. 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.848 K. + 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. @@ -299,8 +299,8 @@ of the sweep. .. literalinclude:: /_generated/ch05_trade_off.txt :language: text -Here the annualized capital cost (71605.6 USD/yr at the optimum) falls as -``T_min_app`` grows and the operating cost (20032 USD/yr) rises with it, and +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. diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 62ce73e..acb0dd3 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -851,16 +851,17 @@ class PinchTargets: no area (each costed as biosteam costs a vanishing double pipe, about 1,305 USD installed). This is the least change to the equal division that never falls when a unit is added (or some area, except where - biosteam's correlations do: for shells beyond about 20,000 ft², whose - cost rises faster than their area, and for the 'Double pipe' - `heat_exchanger_type`, cheaper at 150 ft² than below). Where it departs - from the equal division it tends to be high, as networks keep only part - of their area in shell and tubes (the equal division tends to be low - there): an estimate, not a bound. The furnace is costed on the fuel's - utility duty. `summary` adds the total annualized cost, - the installed (bare module) cost times the capital recovery factor plus - the utility cost times the operating hours: no Lang factor and no - working capital. + biosteam's correlations do: for shells whose cost rises faster than + their area, beyond about 35,000 ft² (3,300 m²) for the default floating + head of carbon steel and from about 15,000 ft² by type and material, + and for the 'Double pipe' `heat_exchanger_type`, cheaper at 150 ft² than + below). Where it departs from the equal division it tends to be high, as + networks keep only part of their area in shell and tubes (the equal + division tends to be low there): an estimate, not a bound. The furnace + is costed on the fuel's utility duty. `summary` adds the total + annualized cost, the installed (bare module) cost times the capital + recovery factor plus the utility cost times the operating hours: no Lang + factor and no working capital. Examples -------- @@ -2001,8 +2002,8 @@ def capital_cost_target(self, utilities=None, h=None, h_utilities=None, how many of them share the area equally (the others have none); 'shell_area', the area of each of those [m²]; 'type', biosteam's type of each ('Double pipe' below 150 ft², None without shells - or with an infinite area); 'furnace_duty', the utility duty of - the fuel [kJ/hr]; + or with an infinite or nan area); 'furnace_duty', the utility + duty of the fuel [kJ/hr]; 'purchase_cost' and 'installed_cost' (bare module) [USD]. inf with an infinite area, nan with an unserved utility. @@ -2126,7 +2127,7 @@ def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, refine : int, optional Rounds of refinement around the least TAC. Defaults to 3. n_refine : int, optional - Evaluations per round of refinement. Defaults to 8. + New temperatures evaluated per round of refinement. Defaults to 8. scale_contributions : bool, optional Scale the specified approach contributions of `targets` with the minimum approach temperature (see `PinchTargets.at`). Defaults to @@ -2167,10 +2168,14 @@ def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, The TAC is not smooth (the units target jumps, so do biosteam's correlations at 150 ft², and the pinch can move), so the sweep is a - grid search: each round of refinement evaluates `n_refine` evenly spaced - temperatures between the neighbors of the least TAC so far, so after r - rounds the spacing is the grid's times (2/9)**r for the defaults. A - least TAC at the smallest or largest temperature evaluated is flagged + grid search: each round of refinement evaluates `n_refine` new + temperatures between the neighbors of the least TAC so far, never on + it: with it, they cut that bracket into ``n_refine + 2`` gaps, shared by + its two sides in proportion to their widths, each side evenly spaced + (all on its one side at a bound of the sweep). On an evenly spaced grid, + after r rounds the spacing near the optimum is then about the grid's + times ``(2 / (n_refine + 2))**r``, (1/5)**r for the defaults. A least + TAC at the smallest or largest temperature evaluated is flagged (`Supertargets.at_bound`). The capital target drops by about a shell and tube wherever the @@ -2235,11 +2240,11 @@ def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, >>> sweep = supertarget(targets, grid) >>> sweep - + >>> i = sweep.records.index(sweep.optimum) >>> [(r['shell_type'], round(r['area'], 2), round(r['TAC'])) ... for r in sweep.records[i - 1:i + 1]] - [('Floating head', 13.95, 12572), ('Double pipe', 13.93, 4930)] + [('Floating head', 13.95, 12573), ('Double pipe', 13.91, 4936)] References ---------- @@ -2289,10 +2294,21 @@ def evaluate(T_min_app): lo = records[max(i - 1, 0)]['T_min_app'] hi = records[min(i + 1, len(records) - 1)]['T_min_app'] if hi - lo <= 1e-6: break + # the new temperatures go on both sides of the least TAC, never on + # it (a bracket's midpoint can be it): with it they cut the bracket + # into n_refine + 2 gaps, shared by its sides in proportion to their + # widths (all on its one side at a bound of the sweep), so each side + # is spaced evenly and the two about alike + x = records[i]['T_min_app'] + k = math.floor((n_refine + 2) * (x - lo) / (hi - lo) + 0.5) - 1 + k = min(max(k, 0), n_refine) # how many go below it + candidates = np.concatenate(( + np.linspace(lo, x, k + 2)[1:-1], + np.linspace(x, hi, n_refine - k + 2)[1:-1])) evaluated = np.array([r['T_min_app'] for r in records]) - for x in np.linspace(lo, hi, n_refine + 2)[1:-1]: - if np.abs(evaluated - x).min() > 1e-12: - records.append(evaluate(float(x))) + for T_new in candidates: + if np.abs(evaluated - T_new).min() > 1e-12: + records.append(evaluate(float(T_new))) sweep = Supertargets(targets, records, options, scale_contributions) unserved = int(np.isnan(sweep['utility_cost']).sum()) if unserved: @@ -3151,7 +3167,7 @@ def plot_supertargets(sweep, ax=None, file=None, dpi=300): ... [H1.heat_utilities[0], C1.heat_utilities[0]], T_min_app=10.) >>> sweep = supertarget(targets) >>> sweep - + >>> fig, ax = plot_supertargets(sweep) >>> [line.get_gid() for line in ax.get_lines() if line.get_gid()] ['TAC', 'Capital', 'Operating', 'Optimum'] diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 6732096..6182981 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -1823,6 +1823,35 @@ def flash(*args, **kwargs): raise AssertionError('the curves are rebuilt') supertarget(partial, [0., 5.]) assert np.ptp(supertarget(partial, [5., 20.], refine=0)['Q_hot']) > 0. +def test_sweep_refinement(): + # each round evaluates n_refine new temperatures, on both sides of the + # least TAC and never on it: on an evenly spaced grid the bracket is + # symmetric, and an odd n_refine once put one on the optimum itself, so + # n_refine=1 refined nothing + t = targets('test_targeting_sweep_refinement', REF_A, 10.) + grid = np.arange(1., 41.) + coarse = supertarget(t, grid, refine=0) + x = coarse.T_min_app + assert x == 8. and not coarse.at_bound + for n in (1, 2, 3, 8, 9): + new = np.setdiff1d(supertarget(t, grid, refine=1, n_refine=n)['T_min_app'], grid) + assert new.size == n and x not in new + below, above = new[new < x], new[new > x] + # the n + 2 gaps of the bracket shared by its sides (the odd one below) + assert below.size == n - n // 2 and above.size == n // 2 + assert_allclose(np.diff([x - 1., *below, x]), 1. / (below.size + 1), rtol=1e-12) + assert_allclose(np.diff([x, *above, x + 1.]), 1. / (above.size + 1), rtol=1e-12) + assert len(supertarget(t, grid, refine=3, n_refine=n).records) == grid.size + 3 * n + one = supertarget(t, grid, refine=1, n_refine=1) + assert len(one.records) == grid.size + 1 and 7.5 in one['T_min_app'] + # a lopsided bracket: the gaps in proportion to the widths of its sides + assert_allclose(np.setdiff1d(supertarget(t, [5., 10., 20.], refine=1, n_refine=3)['T_min_app'], + [5., 10., 20.]), [7.5, 40. / 3., 50. / 3.], rtol=1e-12) + # at a bound of the sweep, all on its one side, as evenly spaced as before + assert supertarget(t, [10., 20., 30.], refine=0).at_bound + at_bound = supertarget(t, [10., 20., 30.], refine=1, n_refine=3) + assert list(at_bound['T_min_app']) == [10., 12.5, 15., 17.5, 20., 30.] + # %% Facility and report def terminal_area(hx): From d771f837353ff7714ec050172caad75bf5860e38 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 13:15:23 +0000 Subject: [PATCH 30/52] address review 5 (tg-report): floor the outlet check at a micro-kelvin, place the legend where it is clear TGR5-1 (fixed): _same_ends compared the network's outlet with the curve to 1e-6 of the duty only, which a rigorous utility exchanger's flash cannot reach on a large stream that changes temperature by micro-kelvins (duty still above Qmin), so network_report rejected a freshly simulated network and sent the user to re-simulate. The outlet now passes within the larger of 1e-6 of the duty and the heat of a micro-kelvin of the outlet stream (1e-6 * |C|, evaluated only when the relative check fails); a stale outlet still moves H by far more. New test_network_report_micro_kelvin_stream (20000 kmol/hr of water cooled by 1e-6 K, duty -1.5 kJ/hr): the report succeeds right after the simulation and again after a re-simulation, and a set point moved by 0.1 mK is still detected as a change of states (fails before the fix). TGR5-2 (fixed): plot_composite_curves places its legend at loc='best' instead of the fixed lower right corner, which covered 29-67 % of the cold composite of threshold problems that need only cooling. On every other case checked (REF_A, FURNACE, PAIR, KEMP, liquids, hot-only, cold-only, threshold) 'best' leaves the legend off the curves, the band and the labels; the docs' quickstart keeps the lower right, so docs/_demo_src/examples/ch05_targeting.py, re-run alone, regenerates its figures byte-identical. Documented in the Notes. test_plot_readability now checks that the legend lies inside the axes, off both composites and off the utility labels, also on the cooling-only threshold problem [('H1', 420, 330, 3), ('C1', 300, 340, 1)] (fails with the old corner). TGR5-3 (nit, applied): new parametrized test_network_report_cached_split_network on liquids with stream_splitting, cache_network and replace_unit_heat_utilities, with and without approach contributions: the report's targets equal the facility's and synthesis_info's at the synthesis, and after a 5 % feed change and re-simulation of the reused network each area agrees with independent_area to 1e-4. Tests: pytest tests/test_targeting.py hensmith/targeting.py -> 76 passed. Synthesis path untouched (oracle not needed). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- hensmith/targeting.py | 20 ++++--- tests/test_targeting.py | 112 ++++++++++++++++++++++++++++++++++------ 2 files changed, 109 insertions(+), 23 deletions(-) diff --git a/hensmith/targeting.py b/hensmith/targeting.py index acb0dd3..d20fceb 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -2429,14 +2429,18 @@ def _flows(stream): def _same_ends(curve, s_in, s_out): """Whether the streams `s_in` and `s_out` are at the end states of - `curve` (on the curve's thermodynamic basis): the same enthalpies (to - 1e-6 of its duty at the outlet, which flashes reach), and the same - pressure and flows at the inlet (an inlet at the enthalpy reference - has the same enthalpy at any flow).""" + `curve` (on the curve's thermodynamic basis): the same enthalpies, at + the outlet to the larger of 1e-6 of the duty and the heat of a + micro-kelvin of the outlet stream (a flash reaches the outlet only to + its tolerance, which is more than 1e-6 of the duty of a stream that + changes temperature by micro-kelvins); and the same pressure and flows + at the inlet (an inlet at the enthalpy reference has the same enthalpy + at any flow).""" span = abs(curve.H_out - curve.H_in) a, b = _flows(curve.stream_in), _flows(s_in) + dH_out = abs(curve.H_out - s_out.H) return (abs(curve.H_in - s_in.H) <= 1e-9 * (abs(curve.H_in) + span) - and abs(curve.H_out - s_out.H) <= 1e-6 * span + and (dH_out <= 1e-6 * span or dH_out <= 1e-6 * abs(s_out.C)) and abs(curve.P - s_in.P) <= 1e-9 * curve.P and a.shape == b.shape and np.abs(a - b).max(initial=0.) <= 1e-9 * np.abs(a).max(initial=0.)) @@ -2836,7 +2840,9 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, composite starts at the unserved cooling, and the hot one ends short of the cold one by the unserved heating. A label of a sensible agent sits off the middle of its line, above left for heating and below right for - cooling. + cooling. The legend goes where it covers the least of the curves, the + band and the labels (matplotlib's 'best' location: no fixed corner is + clear of the composites of every problem). Examples -------- @@ -2981,7 +2987,7 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, ax.set_ylabel('shifted T [°C]' if shifted else 'T [°C]') _sci_ticks(ax.xaxis) ax.set_xlabel('H [kJ·h$^{-1}$]') - ax.legend(loc='lower right', fontsize=8) + ax.legend(loc='best', fontsize=8) ax.grid(alpha=0.3) if file: fig.savefig(file, dpi=dpi, bbox_inches='tight') return fig, ax diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 6182981..9634ea8 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -28,11 +28,14 @@ independent integration on the network's own streams (branches of a split, phase changes), an infinite area where biosteam's utility crosses its stream inside the exchanger, the report at the units' - current states only (a cached network re-simulated, stale states - detected, also with ideal thermodynamics), and no side effects on the - flowsheet, the agents' stream dumps or a later synthesis. -E. Plots: stable gids whose data equal the curves, and readable labels - and markers. + current states only (a cached network re-simulated, also split, with + replaced heat utilities and approach contributions; stale states + detected, also with ideal thermodynamics, and a stream that changes + temperature by a micro-kelvin not taken for stale), and no side + effects on the flowsheet, the agents' stream dumps or a later + synthesis. +E. Plots: stable gids whose data equal the curves, and readable labels, + markers and legends. Reference A: H1 400 -> 300 K and C1 330 -> 440 K, both 1 kW/K, at T_min_app = 10 K: 50 kW hot and 40 kW cold utility, shifted pinch 390 K. @@ -2301,6 +2304,67 @@ def test_network_report_current_states(): with pytest.raises(ValueError, match='targets are not of the current states of H1'): network_report(HXN, targets) +@pytest.mark.parametrize('contributions', [None, {'H1': 3., 'C1': 2.}]) +def test_network_report_cached_split_network(contributions): + # A split network the facility reuses (cache_network) with its + # utilities written over the units' (replace_unit_heat_utilities), with + # and without approach contributions: the report's targets are the + # facility's, at the synthesis and at changed feeds after the network + # is re-simulated, and each area agrees with an independent integration + name = f'test_targeting_report_cached_split_{contributions is None}' + sys, HXN, units = liquids(name, stream_splitting=True, cache_network=True, + replace_unit_heat_utilities=True, + approach_contributions=contributions) + network = HXN.HXN_sys + report = network_report(HXN) + targets = HXN.targets() + expected = dict.fromkeys(['H1', 'H2', 'C1', 'H3'], None) | (contributions or {}) + assert dict(zip(targets.labels, targets.approach_contributions + or [None] * 4)) == expected + assert report['targets'] == targets.summary() + for key in ('Q_hot', 'Q_cold'): + assert report['targets'][key] == pytest.approx( + HXN.synthesis_info[key + '_target'], rel=1e-6), key + assert any(st.fraction != 1. for lc in HXN.stream_life_cycles + for st in lc.life_cycle) + units[2].ins[0].F_mass *= 1.05 + simulate_strictly(sys) + assert HXN.HXN_sys is network + changed = network_report(HXN) + assert changed['targets'] == HXN.targets().summary() + assert changed['targets']['Q_hot'] > report['targets']['Q_hot'] * (1 + 1e-3) + exchangers = {hx.ID: hx for hx in HXN.new_HXs + HXN.new_HX_utils} + for ID, (area, _) in changed['exchanger_areas'].items(): + assert area == pytest.approx(independent_area(exchangers[ID]), rel=1e-4), ID + +def test_network_report_micro_kelvin_stream(): + # A large stream that changes temperature by a micro-kelvin still has + # duty (above the facility's Qmin); the network's rigorous utility + # exchanger reaches its outlet only to the flash tolerance, which is + # more than 1e-6 of that duty: the network is of the current states + bst.main_flowsheet.set_flowsheet('test_targeting_report_micro_kelvin') + bst.settings.set_thermo(['Water', 'Ethanol'], cache=True) + def process_stream(ID, T_in, T_out, P, **kmol_hr): + inlet = bst.Stream(ID + '_in', T=T_in, P=P, phase='l', + units='kmol/hr', **kmol_hr) + return bst.HXutility(ID, ins=inlet, T=T_out, rigorous=False) + units = [process_stream('H1', 420., 320., 5e5, Water=60.), + process_stream('C1', 310., 380., 5e5, Water=50.), + process_stream('H2', 305.150001, 305.15, 101325., Water=20000.)] + sys, HXN = simulate_HXN(units, 10.) + H2 = units[2] + assert -2. < H2.heat_utilities[0].duty < -1. # (above Qmin) + report = network_report(HXN) + assert report['targets'] == HXN.targets().summary() + # a set point changed by 0.1 mK is a change of states + H2.T -= 1e-4 + H2.simulate() + with pytest.raises(ValueError, match='not of the current states of H2.*simulate the system'): + network_report(HXN) + H2.T += 1e-4 + simulate_strictly(sys) + assert network_report(HXN)['targets'] == report['targets'] + def test_no_side_effects(kemp_hxn): units, HXN = kemp_hxn # the furnace case of test_capital_target_formula: the network heats C1 @@ -2496,10 +2560,11 @@ def boxes(fig, artists): bst.HeatUtility.dT, abs=1e-6) finally: plt.close(fig) - def readable(fig, ax, labels, curves): - """Every one of the `labels` inside the axes and off the `curves`, - and the axis labels inside the figure: whether or not it is saved - with a tight bounding box (or shown in a window).""" + def readable(fig, ax, labels, curves, legend=True): + """Every one of the `labels` and the legend (if `legend`) inside the + axes and off the `curves` (and the legend off the labels), and the + axis labels inside the figure: whether or not it is saved with a + tight bounding box (or shown in a window).""" fig.canvas.draw() renderer = fig.canvas.get_renderer() within = lambda box, frame: (frame.x0 <= box.x0 and box.x1 <= frame.x1 @@ -2511,12 +2576,22 @@ def readable(fig, ax, labels, curves): points += [np.linspace(a, b, 500) for a, b in zip(xy, xy[1:])] points = np.concatenate(points) assert labels - for label in labels: - box = label.get_window_extent(renderer) - assert within(box, frame), label.get_text() + named = [(label.get_text(), label) for label in labels] + artists = list(named) + if legend: + legend = ax.get_legend() + assert legend is not None + artists.append(('legend', legend)) + for name, artist in artists: + box = artist.get_window_extent(renderer) + assert within(box, frame), name inside = ((points[:, 0] >= box.x0) & (points[:, 0] <= box.x1) & (points[:, 1] >= box.y0) & (points[:, 1] <= box.y1)) - assert not inside.any(), label.get_text() + assert not inside.any(), name + if legend: + box = legend.get_window_extent(renderer) + for name, label in named: + assert not label.get_window_extent(renderer).overlaps(box), name for label in (ax.xaxis.label, ax.yaxis.label, ax.xaxis.get_offset_text()): assert within(label.get_window_extent(renderer), fig.bbox), label.get_text() # every utility label inside the axes and off both balanced composites: @@ -2546,9 +2621,14 @@ def readable(fig, ax, labels, curves): plt.close(fig) # the utility labels of the real and shifted composites inside the axes # and off the curves, also where the hot curve's hot end is the top of - # the plot, at the right of it (KEMP, hot streams only) + # the plot, at the right of it (KEMP, hot streams only); the legend off + # them too, also where the cold composite of a threshold problem that + # needs only cooling runs into the lower right corner kemp = targets_of(cp_units('test_targeting_plot_kemp', KEMP, K), 10.) - for t in (kemp, hot, threshold, quickstart): + cooling = targets('test_targeting_plot_cooling', [('H1', 420., 330., 3.), + ('C1', 300., 340., 1.)], 10.) + assert cooling.Q_hot == 0. and cooling.Q_cold > 0. + for t in (kemp, hot, threshold, cooling, quickstart): for kind in ('real', 'shifted'): fig, ax = plot_composite_curves(t, kind=kind) try: @@ -2582,7 +2662,7 @@ def readable(fig, ax, labels, curves): heating = {u.ID: u.heating for u in t.utility_targets()} labels = _gid_artists(ax, 'Label:') assert len(labels) == len(heating) - readable(fig, ax, labels, _gid_artists(ax, 'GCC')) + readable(fig, ax, labels, _gid_artists(ax, 'GCC'), legend=False) lo, hi = ax.get_xlim() for label in labels: (x, _), (dx, dy) = label.xy, label.xyann From e11f3c6afdf09b849f878c47eb2de2fa2d1643d3 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 13:27:39 +0000 Subject: [PATCH 31/52] address review 5 (e2e): say the utility cost target is no bound, flag flat sweeps, shade non-finite stretches E2E-R5-1 (fixed): the utility cost target is the cost of biosteam's placement of its agents (list order, fuel last), not a minimum. With biosteam's default prices natural gas is cheaper per kJ than any steam for a furnace heating from below about 565 K, so a network at MER that heats a stream ending above the hottest steam with fuel over its whole stretch costs less than the target. The docs said only that the network can use costlier agents. Reworded the concepts.rst comparison limits bullet (both directions, with the cp case: 3.14 against 4.56 USD/hr), its "List order and fuel" paragraph and its "Targets are estimates" bullet, the Notes of network_report (only the energy targets bound the network) and of PinchTargets, the utility_targets summary, the tutorial ch05 remark after the report, and "any network" in the module docstring, README, API page and ch05 intro. New test test_network_report_cheaper_than_utility_target pins the case (status mer, equal Q_hot, natural gas over the whole stream, the per-kJ prices, 3.14 vs 4.56 USD/hr, TAC below its target, the 565 K crossover). E2E-R5-2 (nit, applied): plot_supertargets shades each stretch without a finite TAC (gid 'Nonfinite', halfway to its finite neighbours, one legend entry), so the axis keeps the whole sweep. Nothing is added when every TAC is finite, so the docs figures are unchanged. E2E-R5-3 (nit, applied): a sweep whose TACs are all finite and the same to a relative 1e-12 (at least two points) is not refined, and Supertargets.constant flags it; its repr says "TAC constant over the sweep" and the plot "(TAC constant)" in place of "at the bound" (at_bound itself is unchanged). New test test_sweep_constant. The synthesis path is untouched (no oracle run). Tests: pytest hensmith/targeting.py: 6 passed; pytest tests/test_targeting.py: 72 passed; sphinx -W (SPHINX_OFFLINE=1): clean. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- README.md | 2 +- docs/source/API/targeting.rst | 5 +- docs/source/concepts.rst | 41 ++++++++--- docs/source/tutorial/05_targeting.rst | 13 +++- hensmith/targeting.py | 93 +++++++++++++++++++---- tests/test_targeting.py | 102 ++++++++++++++++++++++++++ 6 files changed, 224 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 203fc40..efe5de8 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ see the documentation for the limits with real thermodynamics). Each stream can also carry its own approach temperature contribution (`HeatExchangerNetwork(..., approach_contributions={unit: dT})`): a match must then keep the sum of its two streams' contributions instead of -`T_min_app`. `hensmith.targeting` targets what any network of the streams +`T_min_app`. `hensmith.targeting` targets what a network of the streams can achieve before one is synthesized (several utilities priced by BioSTEAM, area, units, capital and total annualized cost: `HXN.targets()`), finds the `T_min_app` of the least targeted total annualized cost (`supertarget`), and diff --git a/docs/source/API/targeting.rst b/docs/source/API/targeting.rst index 1ab02e1..61bfa45 100644 --- a/docs/source/API/targeting.rst +++ b/docs/source/API/targeting.rst @@ -3,8 +3,9 @@ Targeting and supertargeting (hensmith.targeting) .. currentmodule:: hensmith -``hensmith.targeting`` says what any heat exchanger network of a set of -process streams can achieve, before (or instead of) synthesizing one. +``hensmith.targeting`` says what a heat exchanger network of a set of +process streams can achieve, before (or instead of) synthesizing one: the +energy targets bound every network, the others are estimates. :class:`PinchTargets` holds the problem table of the streams and derives from it the composite and grand composite curves, the targets of several utilities priced by biosteam (:class:`~hensmith.targeting.UtilityTarget`), the balanced diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 6250ff6..035a0ef 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -414,6 +414,13 @@ line would have no driving force left there: with ``agent.dT`` of 0 (``UtilityAgent``'s default), it could serve a point load at its limit only at its own inlet temperature, at an unbounded flow, so its level stays just short of that point load, which the next agent serves, as in BioSTEAM. +The order is BioSTEAM's preference, not price: the utility cost target is +what the agents cost placed this way, not the least utility cost, and an +agent later in the list can be cheaper per kJ (with BioSTEAM's default +prices, natural gas is cheaper than any steam for a furnace heating from +below about 565 K), so a network can cost less than the target as well as +more (see the comparison of a network with its targets in `Supertargeting: +area, units, capital and total cost`_ below). **Served intervals and pricing.** Each agent is priced as one BioSTEAM heat utility over the stretch it serves: from its level, or from the end of the @@ -568,9 +575,12 @@ only by the discretization of the curves, by a tiny relative amount (4e-7 of the total cost for the quickstart system), so where the optimum lies in that flat stretch means nothing physically; where the discretization trend is steady, it lies at an end of the stretch (for the quickstart system, the -warmest point evaluated, just short of the threshold). A sweep with no finite +warmest point evaluated, just short of the threshold). A sweep whose costs +are all finite and the same to round-off (streams that recover no heat, say) +is not refined, and ``constant`` flags it. A sweep with no finite total cost (an unserved utility everywhere, say) is not refined and warns that -its optimum is meaningless. With approach +its optimum is meaningless; :func:`~hensmith.plot_supertargets` shades the +stretches of a sweep without a finite cost. With approach contributions, only the unspecified streams follow ``T_min_app``; ``scale_contributions=True`` scales the specified ones with it, the contribution scaling of Ahmad, Linnhoff & Smith (1990). @@ -600,10 +610,21 @@ comparison has limits: - the Bath target bounds the area only with equal film coefficients and the same utilities; - the network gives each stream one agent at its outlet, BioSTEAM's list - rule, while the target allocates the agents on the grand composite curve, - so the network can use costlier agents than the target (on the four-stream - problem of Linnhoff & Hindmarsh at 10 K, the target is low pressure steam - and cooling water, the network medium pressure steam and chilled water); + rule, while the target allocates the agents on the grand composite curve + in list order, fuel last, so the network can use other agents than the + target, costlier or cheaper ones. On the four-stream problem of Linnhoff & + Hindmarsh at 10 K, the target is low pressure steam and cooling water, + the network medium pressure steam and chilled water, which cost more. But + a stream heated above the hottest steam takes fuel over its whole utility + stretch, and with BioSTEAM's default prices natural gas costs less per kJ + than the steam the target places below the fuel: a stream of 1 kW/K + heated from 330 to 520 K against one cooled from 400 to 300 K, at 10 K, + takes natural gas from 390 K at 5.2e-6 USD/kJ, where the target places + low, medium and high pressure steam (6.5e-6 to 1.2e-5 USD/kJ) and fuel + only above 504 K, so the network at MER costs 3.14 USD/hr against a + target of 4.56 USD/hr. The utility cost target is the cost of BioSTEAM's + placement of its agents, not a minimum, and neither it nor the network's + utility cost bounds the other; - the units target is an estimate in both directions, since Euler's S - 1 assumes one component and no loop per region: a balanced subset of streams, or one agent serving a stream whose heat the target splits @@ -1140,9 +1161,11 @@ What it is not: `Approach temperature contributions`_). The utility exchangers use BioSTEAM's approach rule whatever a stream's contribution. - **Targets are estimates.** Beyond the MER targets, the targets of - ``hensmith.targeting`` are estimates: the area target bounds a network's - area only with equal film coefficients and the same utilities, the units - target can be wrong in both directions, the capital target assumes shells + ``hensmith.targeting`` are estimates: the utility cost target is the cost + of BioSTEAM's placement of its agents, which a network at MER can exceed + or undercut, the area target bounds a network's area only with equal film + coefficients and the same utilities, the units target can be wrong in + both directions, the capital target assumes shells of equal area at one pressure (keeping the shell and tubes the area fills when an equal division would drop below 150 ft²), and with approach contributions the utility levels are conservative and their pricing diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index 199016e..18f6857 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -5,7 +5,7 @@ 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 any network of these streams needs, what +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 (:class:`~hensmith.PinchTargets`), sweeps ``T_min_app`` for the least total @@ -339,9 +339,14 @@ synthesized with the targets of its streams, on the same options. :language: text 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. The comparison of -everything else needs care, because a target is an estimate of an ideal -network: +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 diff --git a/hensmith/targeting.py b/hensmith/targeting.py index d20fceb..86940da 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -7,8 +7,9 @@ # github.com/BioSTEAMDevelopmentGroup/hensmith/blob/master/LICENSE.txt # for license details. """ -Targeting: what any heat exchanger network of a set of process streams can -achieve, before (or instead of) synthesizing one. `PinchTargets` holds the +Targeting: what a heat exchanger network of a set of process streams can +achieve, before (or instead of) synthesizing one (the energy targets bound +every network, the others are estimates). `PinchTargets` holds the problem table of the streams and derives from it the composite and grand composite curves, the multiple-utility targets priced by biosteam (`PinchTargets.utility_targets`), the balanced composite curves, and the @@ -753,7 +754,14 @@ class PinchTargets: lists the lowest steam and the warmest coolant first), each as much as the curve above its level (below it, for cooling) allows given the agents already placed; what remains goes to the first fuel agent, or is - left unserved with a RuntimeWarning (nan cost). A level equal to a grid + left unserved with a RuntimeWarning (nan cost). The order is biosteam's + preference, not price: the utility cost target is the cost of the + agents placed this way, not the least utility cost, and an agent later + in the list can be cheaper per kJ (with biosteam's default prices, + natural gas is cheaper than any steam for a furnace heating from below + about 565 K), so a network, which serves each stream with one agent at + its outlet, can cost less than the target as well as more (see Notes of + `network_report`). A level equal to a grid temperature serves that temperature's point loads (biosteam's own rule is strict; they differ only at exact equality), except where a sensible agent's line would have no driving force left: at a point load at @@ -1472,7 +1480,9 @@ def _line_limit(self, agent, heating, q, T, B, served): def utility_targets(self, heating_agents=None, cooling_agents=None): """ Multiple-utility targets: the agents placed on the grand composite - curve and priced by biosteam (see Notes of the class). + curve and priced by biosteam (see Notes of the class). They are + placed in biosteam's order of preference, not by price, so their + total cost is not the least utility cost of a network. Parameters ---------- @@ -2110,6 +2120,13 @@ def _least_TAC(records): TAC = np.array([r['TAC'] for r in records], dtype=float) return int(np.argmin(np.where(np.isnan(TAC), math.inf, TAC))) +def _constant_TAC(records): + """Whether the records are at least two, each with a finite TAC, and + their TACs the same to within round-off (a relative 1e-12).""" + TAC = np.array([r['TAC'] for r in records], dtype=float) + return (TAC.size > 1 and bool(np.isfinite(TAC).all()) + and float(np.ptp(TAC)) <= 1e-12 * float(np.abs(TAC).max())) + def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, scale_contributions=False, **options): """ @@ -2176,7 +2193,11 @@ def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, after r rounds the spacing near the optimum is then about the grid's times ``(2 / (n_refine + 2))**r``, (1/5)**r for the defaults. A least TAC at the smallest or largest temperature evaluated is flagged - (`Supertargets.at_bound`). + (`Supertargets.at_bound`). A sweep whose TACs are all finite and the + same (to a relative 1e-12) is not refined: its targets do not depend on + the minimum approach temperature over the sweep (for streams that + recover no heat, only heaters, say), its optimum is merely the smallest + temperature, and `Supertargets.constant` flags it. The capital target drops by about a shell and tube wherever the shrinking area fills one fewer of 150 ft² (see Notes of @@ -2289,6 +2310,8 @@ def evaluate(T_min_app): for _ in range(refine): if not any(math.isfinite(r['TAC']) for r in records): break # nothing to refine around + if _constant_TAC(records): + break # a flat line: nothing to refine either records.sort(key=lambda r: r['T_min_app']) i = _least_TAC(records) lo = records[max(i - 1, 0)]['T_min_app'] @@ -2359,6 +2382,11 @@ class Supertargets: at_bound : bool Whether `optimum` is at the smallest or largest temperature evaluated: the least TAC may lie beyond the sweep. + constant : bool + Whether the TAC is the same at every temperature evaluated (at least + two, each finite, to a relative 1e-12): the targets do not depend + on the minimum approach temperature over the sweep, and `optimum` + is merely the first record. Notes ----- @@ -2378,6 +2406,7 @@ def __init__(self, targets, records, options=None, self.optimum = self.records[i] self.T_min_app = self.optimum['T_min_app'] self.at_bound = i in (0, len(self.records) - 1) + self.constant = _constant_TAC(self.records) def __getitem__(self, key): return np.array([r[key] for r in self.records]) @@ -2390,7 +2419,9 @@ def to_frame(self): def __repr__(self): n = len(self.records) lo, hi = self.records[0]['T_min_app'], self.records[-1]['T_min_app'] - bound = ', at the bound' if self.at_bound else '' + if self.constant: bound = ', TAC constant over the sweep' + elif self.at_bound: bound = ', at the bound' + else: bound = '' return (f'<{type(self).__name__}: {n} point{"s" if n != 1 else ""}, ' f'T_min_app {lo:g}-{hi:g} K, optimum {self.T_min_app:.4g} K ' f'(TAC {self.optimum["TAC"]:.3e} USD/yr){bound}>') @@ -2650,14 +2681,23 @@ def network_report(HXN, targets=None, **options): original exchangers, to which the utility exchangers of the network, reported here, are equivalent). - The targets are estimates, and the network may beat some of them: + Only the energy targets ('Q_hot' and 'Q_cold') bound the network; the + others are estimates, and the network may beat some of them: - the network serves each stream with one agent at its outlet (biosteam's rule: the first agent of the list that can), while the targets place - the agents on the grand composite curve, so the network may use other - agents than the targets (with Linnhoff & Hindmarsh's problem at - 10 K, medium pressure steam and chilled water where the targets need - only low pressure steam and cooling water); + the agents on the grand composite curve in list order (fuel last), so + the network may use other agents than the targets, costlier or + cheaper ones: with Linnhoff & Hindmarsh's problem at 10 K, medium + pressure steam and chilled water where the targets need only low + pressure steam and cooling water, but a stream heated above the + hottest steam takes fuel over its whole utility stretch, and with + biosteam's default prices natural gas costs less per kJ than the + steam the targets place below the fuel (a stream of 1 kW/K heated + from 330 to 520 K against one cooled from 400 to 300 K, at 10 K: 3.14 + USD/hr at MER against a target of 4.56 USD/hr). The utility cost + target is the cost of biosteam's placement of its agents, not a + minimum: neither it nor the network's utility cost bounds the other; - the area target is a minimum only for equal film coefficients and the same utilities: that problem's network takes 54.06 m² against a target of 59.68 m², with hotter steam and colder coolant; @@ -3150,9 +3190,14 @@ def plot_supertargets(sweep, ax=None, file=None, dpi=300): Notes ----- Artists carry stable gids: 'TAC', 'Capital' (the annualized capital - cost), 'Operating' and 'Optimum'. The optimum is labeled '(at the - bound)' if it is at an end of the sweep (`Supertargets.at_bound`), and - is not marked without a finite TAC. + cost), 'Operating', 'Optimum' and 'Nonfinite'. The optimum is labeled + '(TAC constant)' if the TAC is the same over the sweep + (`Supertargets.constant`), else '(at the bound)' if it is at an end of + the sweep (`Supertargets.at_bound`), and is not marked without a finite + TAC. The lines leave out the temperatures without a finite TAC (an + unserved utility or an infinite area): each stretch of them is shaded + ('Nonfinite', halfway to its finite neighbors), so the axis keeps the + whole sweep. Examples -------- @@ -3187,7 +3232,8 @@ def plot_supertargets(sweep, ax=None, file=None, dpi=300): else: fig = ax.figure T = sweep['T_min_app'] - ax.plot(T, sweep['TAC'], color='k', lw=2, label='total annualized cost', + TAC = sweep['TAC'] + ax.plot(T, TAC, color='k', lw=2, label='total annualized cost', gid='TAC') ax.plot(T, sweep['annualized_capital_cost'], color='#2e6db4', lw=1.5, label='annualized capital cost', gid='Capital') @@ -3195,12 +3241,27 @@ def plot_supertargets(sweep, ax=None, file=None, dpi=300): label='operating cost', gid='Operating') optimum = sweep.optimum if math.isfinite(optimum['TAC']): - bound = ' (at the bound)' if sweep.at_bound else '' + if sweep.constant: bound = ' (TAC constant)' + elif sweep.at_bound: bound = ' (at the bound)' + else: bound = '' ax.axvline(optimum['T_min_app'], color='k', ls='--', lw=0.8) ax.plot([optimum['T_min_app']], [optimum['TAC']], 'o', mfc='w', mec='k', ms=7, zorder=4, label=f"optimum {optimum['T_min_app']:.3g} K{bound}", gid='Optimum') + bad = ~np.isfinite(TAC) + if bad.any(): + # the stretches without a finite TAC, which the lines leave out: + # shaded behind them, halfway to their finite neighbors + n = T.size + starts = np.flatnonzero(bad & ~np.r_[False, bad[:-1]]) + ends = np.flatnonzero(bad & ~np.r_[bad[1:], False]) + for k, (a, b) in enumerate(zip(starts, ends)): + left = T[a] if a == 0 else (T[a - 1] + T[a]) / 2 + right = T[b] if b == n - 1 else (T[b] + T[b + 1]) / 2 + ax.axvspan(left, right, color='0.85', lw=0, zorder=0, + label='no finite TAC' if k == 0 else '_nolegend_', + gid='Nonfinite') ax.set_xlabel('$T_{min,app}$ [K]') ax.set_ylabel('cost [USD·yr$^{-1}$]') ax.legend(loc='best', fontsize=8) diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 9634ea8..2022a23 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -1746,6 +1746,7 @@ def flash(*args, **kwargs): raise AssertionError('the curves are rebuilt') assert sweep.at_bound == (sweep.T_min_app in (T[0], T[-1])) single = supertarget(t, [10.], refine=2) assert len(single.records) == 1 and single.at_bound and 'at the bound' in repr(single) + assert not single.constant and not sweep.constant assert single.optimum == t.summary() # the options reach every evaluation hours = supertarget(t, [5., 10.], refine=0, operating_hours=0.) @@ -1826,6 +1827,32 @@ def flash(*args, **kwargs): raise AssertionError('the curves are rebuilt') supertarget(partial, [0., 5.]) assert np.ptp(supertarget(partial, [5., 20.], refine=0)['Q_hot']) > 0. +def test_sweep_constant(): + # streams that recover no heat (only heaters): every target is the same + # at every T_min_app, so the sweep is not refined, and says so instead + # of flagging its optimum, the first record, at the bound + t = targets('test_targeting_sweep_constant', [('C1', 330., 380., 1.), + ('C2', 300., 350., 2.)], 10.) + sweep = supertarget(t) + assert len(sweep.records) == 40 and np.ptp(sweep['TAC']) == 0. + assert sweep.constant and sweep.at_bound and sweep.T_min_app == 1. + assert repr(sweep) == ('') + # a balanced pair below its threshold (10 K): no utility, and the same + # exchanger whatever the T_min_app + pair = targets('test_targeting_sweep_constant_pair', PAIR, 10.) + below = supertarget(pair, [2., 4., 6., 8.]) + assert len(below.records) == 4 and below.constant + assert np.ptp(below['TAC']) <= 1e-12 * below['TAC'].max() + # past the threshold the TAC changes: refined as before + across = supertarget(pair, [5., 10., 15., 20.], refine=1, n_refine=2) + assert len(across.records) == 6 and not across.constant + # a nan TAC is not constant (nor are the TACs of a single temperature) + with pytest.warns(RuntimeWarning, match='leave part of a utility target unserved'): + unserved = supertarget(pair, [12., 14.], refine=0, cooling_agents=[]) + assert np.isnan(unserved['TAC']).all() and not unserved.constant + def test_sweep_refinement(): # each round evaluates n_refine new temperatures, on both sides of the # least TAC and never on it: on an evenly spaced grid the bracket is @@ -2365,6 +2392,44 @@ def process_stream(ID, T_in, T_out, P, **kmol_hr): simulate_strictly(sys) assert network_report(HXN)['targets'] == report['targets'] +def test_network_report_cheaper_than_utility_target(): + # The utility cost target is the cost of biosteam's placement of its + # agents (list order, fuel last), not a minimum: C1 ends above high + # pressure steam, so the network heats it with natural gas from its + # match on, which biosteam's default prices make cheaper per kJ than + # the steam the targets place below the fuel (the case of the Notes of + # network_report and of concepts.rst) + _, HXN = simulate_HXN(cp_units('test_targeting_report_cheaper', FURNACE), 10.) + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter('always') + report = network_report(HXN) + assert not [w for w in caught if issubclass(w.category, RuntimeWarning)] + targets = report['targets'] + assert report['status'] == 'mer' + assert report['Q_hot'] == pytest.approx(targets['Q_hot'], rel=1e-9) + assert report['Q_cold'] == pytest.approx(targets['Q_cold'], rel=1e-9) + [furnace] = [hx for hx in HXN.new_HX_utils if hx.heat_utilities[0].duty > 0.] + assert furnace.heat_utilities[0].agent.ID == 'natural_gas' + assert furnace.ins[0].T == pytest.approx(390.) and furnace.outs[0].T == pytest.approx(520.) + gas = report['utilities']['natural_gas'] + assert gas['duty'] == pytest.approx(report['Q_hot'], rel=1e-9) + heating = [u for u in HXN.targets().utility_targets() if u.heating] + assert [u.ID for u in heating] == ['low_pressure_steam', 'medium_pressure_steam', + 'high_pressure_steam', 'natural_gas'] + assert heating[-1].T_in == pytest.approx(503.991) # fuel above high pressure steam + # per kJ of process heat [USD/kJ] + assert gas['cost'] / gas['duty'] == pytest.approx(5.165e-6, rel=1e-3) + assert [round(u.cost / u.duty, 8) for u in heating[:3]] == [6.47e-6, 8.46e-6, 1.16e-5] + assert gas['cost'] < sum(u.cost for u in heating) + assert (round(report['utility_cost'], 2), round(targets['utility_cost'], 2)) == (3.14, 4.56) + assert report['TAC'] < targets['TAC'] + # natural gas is cheaper per kJ than any steam for a furnace heating + # from below about 565 K + LP = heating[0].cost / heating[0].duty + gas_per_kJ = lambda T_in: fresh_price(10 * kW, T_in, T_in + 10., + agent('natural_gas'))[0] / (10 * kW) + assert gas_per_kJ(560.) < LP < gas_per_kJ(570.) + def test_no_side_effects(kemp_hxn): units, HXN = kemp_hxn # the furnace case of test_capital_target_formula: the network heats C1 @@ -2716,3 +2781,40 @@ def test_plot_supertargets_gids(tmp_path): assert len(ax.lines) == 3 + 2 * len(labels) # with the dashed line finally: plt.close(fig) + # the temperatures without a finite TAC are shaded, halfway to their + # finite neighbors, so the axis keeps the whole sweep: the infinite area + # at 0 K, the unserved cooling of a pair past its threshold (10 K), a + # sweep unserved everywhere; none where every TAC is finite + pair = targets('test_targeting_plot_sweep_pair', PAIR, 10.) + with pytest.warns(RuntimeWarning, match='leave part of a utility target unserved'): + partly = supertarget(pair, [5., 10., 15., 20., 30.], refine=0, cooling_agents=[]) + assert list(np.isfinite(partly['TAC'])) == [True, True, False, False, False] + # two stretches (one legend entry), on records edited to have them + records = [dict(r) for r in supertarget(t, [5., 10., 15., 20., 25.], refine=0).records] + records[0]['TAC'] = math.inf + records[3]['TAC'] = records[4]['TAC'] = math.nan + two = Supertargets(t, records) + for sweep, spans in ((supertarget(t, [0., 5., 10., 20.], refine=0), [(0., 2.5)]), + (partly, [(12.5, 30.)]), (unserved, [(5., 10.)]), + (two, [(5., 7.5), (17.5, 25.)]), (at_bound, [])): + fig, ax = plot_supertargets(sweep) + try: + shaded = _gid_artists(ax, 'Nonfinite') + assert [(a.get_x(), a.get_x() + a.get_width()) for a in shaded] == spans + lo, hi = ax.get_xlim() + assert lo <= sweep['T_min_app'][0] and sweep['T_min_app'][-1] <= hi + labels = ax.get_legend_handles_labels()[1] + assert labels.count('no finite TAC') == (1 if spans else 0) + assert ax.get_legend().get_texts()[-1].get_text() == ( + 'no finite TAC' if spans else labels[-1]) + finally: + plt.close(fig) + # a constant TAC is labeled so + flat = supertarget(targets('test_targeting_plot_sweep_flat', [('C1', 330., 380., 1.)], 10.), + [5., 10.]) + fig, ax = plot_supertargets(flat) + try: + assert [a.get_label() for a in _gid_artists(ax, 'Optimum')] == [ + 'optimum 5 K (TAC constant)'] + finally: + plt.close(fig) From f12aa7f13d7f28dca0777787809956de073704f3 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 15:28:52 +0000 Subject: [PATCH 32/52] address review 6 (f1-facility): check the id held at an auxiliary's place, take holders as owners F1F6-1 (fixed): a tuple key (owner.ID, name) or (owner.ID, 'name[k]') of the ID that biosteam.Unit.auxiliary gives an auxiliary is now valid only where the attribute holds no unit (None, missing, a non-unit object, or a list shorter than k + 1) or holds the unit of that very ID. Where it holds a unit of another ID (EnzymeTreatment's heater is ('T1', ''); the high-rate wastewater units use '_hx'), the key raises 'name no unit' / 'h keys that name no stream' instead of being silently ignored, and the message names the held unit's key ("T1.heat_exchanger is ('T1', '')"). Keys of held units with biosteam's own IDs outside the facility (('D1', 'pump'), ('D1', 'reflux_drum')) stay valid. The k of 'name[k]' must be a plain decimal (no leading zeros). New helpers in hxn_synthesis: _auxiliary_slots, _unknown_key_hints. Class Notes of HeatExchangerNetwork and concepts.rst no longer say that (owner.ID, name) is the ID BioSTEAM gives every auxiliary; they say where the key raises. F1F6-2 (fixed): key resolution now derives owners from the units at hand without mutating user objects (_held_owners, _parent, _top, _key_universe): an auxiliary without an _owner among the auxiliary_units of a unit at hand (the facility's candidate units; outside the facility, the units of the heat utilities and their owner chains) takes that unit as its owner in _unit_name, _id_path, _names_unit, _contribution_key_level, _owner_chains and the unbuilt-auxiliary check, for approach contributions and PinchTargets h keys alike. So for a MultistageCompressor K1, 'K1' covers its intercoolers and ('K1', 'K1_H1') names one in every sample, whether or not K1 was simulated before System.from_units and however often pr changes, and the bare 'K1_H1' always raises (now with the hint "'K1_H1' is ('K1', 'K1_H1')"; str keys of auxiliaries get that hint in general). Documented in the class Notes and concepts.rst, including that an old intercooler object that never had an owner names nothing and raises (ID keys are the robust form). Tests: new test_keys_of_auxiliaries_under_other_IDs (EnzymeTreatment and a ListedHeaters unit with '_hx' list entries; contributions, h keys, in and outside the facility) and test_keys_of_auxiliaries_held_without_an_owner (MultistageCompressor, both build orders, pr 2 -> 2.2 -> 2, h keys, owners left unchanged). - pytest hensmith/hxn_synthesis.py hensmith/_heat_exchanger_network.py hensmith/targeting.py tests/test_hxn_contributions.py: 59 passed - pytest tests/test_targeting.py: 72 passed - oracle (default-off): sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (unchanged) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/concepts.rst | 41 +++-- hensmith/_heat_exchanger_network.py | 32 ++-- hensmith/hxn_synthesis.py | 248 +++++++++++++++++++--------- hensmith/targeting.py | 26 +-- tests/test_hxn_contributions.py | 155 ++++++++++++++++- 5 files changed, 389 insertions(+), 113 deletions(-) diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 035a0ef..e51c6f1 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -280,11 +280,19 @@ the value of the key nearest to its unit along the unit's chain of owners, so ``'D1'`` covers a column's condenser and reboiler and ``('D1', 'condenser')`` overrides it for the condenser. A None or nan value is an explicit default that wins at its level like any value, and two keys -of the same level for a unit must agree. ID keys are the robust form: a -unit that rebuilds its auxiliary units at every simulation -(``biosteam.HXutilities``, for one) leaves a key holding an old auxiliary -object, which then names the auxiliary that took its place: the one of the -same ID under owners of the same IDs, if any. Heat utilities cannot be keys, +of the same level for a unit must agree. A unit's owner is its ``owner``, +or, for an auxiliary that a unit available to the network holds without +setting its owner, that unit (the user's objects are left as they are): +``biosteam.MultistageCompressor`` builds its compressors and intercoolers +anew, without an owner, whenever ``pr`` or ``n_stages`` changes, and +``'K1'`` still covers the intercoolers of compressor K1 and ``('K1', +'K1_H1')`` names one in every sample, while ``'K1_H1'`` names nothing. ID +keys are the robust form: a unit that rebuilds its auxiliary units at every +simulation (``biosteam.HXutilities``, for one) leaves a key holding an old +auxiliary object, which then names the auxiliary that took its place: the +one of the same ID under owners of the same IDs, if any (an old intercooler +of K1, which never had an owner, names none and raises). Heat utilities +cannot be keys, because BioSTEAM replaces them at every simulation; a value per heat utility is given by a callable ``f(hu)`` instead, which is called once per heat utility at every simulation @@ -300,15 +308,20 @@ valid key whose unit holds no heat utility in a simulation (no duty, which a unit falls idle does not fail. The same holds for an auxiliary that a unit may build but does not hold in a sample: ``biosteam.HXutilities``, for one, builds one exchanger per agent it needs, so a lower outlet temperature -drops ``('X1', 'heat_exchangers[2]')``. A key of the ID that BioSTEAM gives an -auxiliary under a name of the ``auxiliary_unit_names`` of the owner or of one -of its auxiliaries is valid whether or not that auxiliary is built, as is an -old auxiliary object of a unit at hand that none took the place of; while the -auxiliary is missing, the key has no effect. That ID is ``(owner.ID, -'name[k]')`` where the attribute ``name`` holds a list of auxiliaries (as -``heat_exchangers`` of ``HXutilities`` does) and ``(owner.ID, name)`` where it -holds a single one, so ``('X1', 'heat_exchangers')`` or ``('D1', -'condenser[0]')`` still raise. +drops ``('X1', 'heat_exchangers[2]')``. A key of the ID that BioSTEAM's +``Unit.auxiliary`` gives an auxiliary under a name of the +``auxiliary_unit_names`` of the owner or of one of its auxiliaries is valid +whether or not that auxiliary is built, as is an old auxiliary object of a +unit at hand that none took the place of; while the auxiliary is missing, +the key has no effect. That ID is ``(owner.ID, 'name[k]')`` where the +attribute ``name`` holds a list of auxiliaries (as ``heat_exchangers`` of +``HXutilities`` does) and ``(owner.ID, name)`` where it holds a single one, +so ``('X1', 'heat_exchangers')`` or ``('D1', 'condenser[0]')`` still raise. +Not every unit gives its auxiliaries that ID, and where the attribute holds +a unit of another ID, the key raises: the heater of a +``biosteam.EnzymeTreatment`` T1 has the ID ``''``, so ``('T1', +'heat_exchanger')`` raises, with a message that gives the heater's key, +``('T1', '')`` (``'T1'`` covers it too). **The cache.** With ``cache_network=True``, a cached network is reused only while the explicit contributions resolve to the same value for every stream diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index 9df041b..f0b2014 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -291,11 +291,19 @@ class HeatExchangerNetwork(bst.Facility): reboiler of column D1 and ``('D1', 'condenser')`` overrides it for the condenser; a None or nan value is an explicit default that wins at its level like any other, and two keys of the same level for a unit must - agree. ID keys are the robust form: a unit that rebuilds its auxiliary - units at every simulation (e.g. `biosteam.HXutilities`) leaves a key - holding an old auxiliary, which then names the auxiliary that took its - place (the one of the same ID under owners of the same IDs), if any - (heat utility keys are rejected, since BioSTEAM replaces them at every + agree. A unit's owner is its `owner` or, for an auxiliary that a unit + available to the network holds without setting its owner, that unit: + a `biosteam.MultistageCompressor` K1 builds its compressors and + intercoolers anew whenever `pr` or `n_stages` changes, without an + owner, and ``'K1'`` still covers the intercoolers and ``('K1', + 'K1_H1')`` names one in every simulation (the bare ``'K1_H1'`` names + nothing; user objects are not changed). ID keys are the robust form: a + unit that rebuilds its auxiliary units at every simulation (e.g. + `biosteam.HXutilities`) leaves a key holding an old auxiliary, which + then names the auxiliary that took its place (the one of the same ID + under owners of the same IDs), if any; an old auxiliary that never had + an owner (an old intercooler of K1) names none, and raises (heat + utility keys are rejected, since BioSTEAM replaces them at every simulation). The keys and values are checked against every unit available to the network and its nested auxiliary units before the heat utilities are taken: a key that names none of them, a bad value @@ -305,13 +313,17 @@ class HeatExchangerNetwork(bst.Facility): no effect, so that, e.g., Monte Carlo samples do not fail. A key of an auxiliary that a unit may build but does not hold in a simulation is valid too, and has no effect: an old auxiliary object of the unit that - none took the place of, or the ID biosteam gives an auxiliary under a - name of the `auxiliary_unit_names` of the owner or of one of its - auxiliaries: ``(owner.ID, 'name[k]')`` where the attribute `name` holds a - list of auxiliaries (e.g. ``('X1', 'heat_exchangers[2]')`` while a + none took the place of, or the ID that `biosteam.Unit.auxiliary` gives + an auxiliary under a name of the `auxiliary_unit_names` of the owner or + of one of its auxiliaries, unless that place holds a unit of another + ID: ``(owner.ID, 'name[k]')`` where the attribute `name` holds a list of + auxiliaries (e.g. ``('X1', 'heat_exchangers[2]')`` while a `biosteam.HXutilities` X1, which builds one exchanger per agent it needs, needs fewer), and ``(owner.ID, name)`` where it holds a single one (so - ``('X1', 'heat_exchangers')`` raises). A callable is + ``('X1', 'heat_exchangers')`` raises). Not every unit gives its + auxiliaries that ID: the heater of a `biosteam.EnzymeTreatment` T1 has + the ID '', so ``('T1', 'heat_exchanger')`` raises, with a message that + gives the heater's key, ``('T1', '')``. A callable is called once per heat utility at every simulation (also to check a cached network, and again if a failed cache synthesizes anew), in the order of the facility's heat utilities: it should be pure. After a diff --git a/hensmith/hxn_synthesis.py b/hensmith/hxn_synthesis.py index f4cf1de..81a0ae4 100644 --- a/hensmith/hxn_synthesis.py +++ b/hensmith/hxn_synthesis.py @@ -768,54 +768,99 @@ def _pinch_cut(table): arriving = table.residual - point_total return 'below' if arriving[k] <= table.residual[k] else 'above' -def _unit_name(unit): +def _parent(unit, owners=None): + """The unit one step up the owner chain of `unit`: ``unit._owner`` or, + without one, the unit that holds `unit` by `owners` (see + `_held_owners`), or None for a top unit.""" + owner = getattr(unit, '_owner', None) + if owner is None and owners: + held = owners.get(id(unit)) + if held is not None and held[0] is unit: owner = held[1] + return owner + +def _top(unit, owners=None): + """The top owner of `unit` along `_parent` (`unit` itself without an + owner).""" + while (owner := _parent(unit, owners)) is not None: unit = owner + return unit + +def _held_owners(units): + """ + The owners that the units at hand give the auxiliary units they hold + without setting their owner: a dict from the id of such an auxiliary to + ``(auxiliary, holder)``, for each `biosteam.Unit` without an ``_owner`` + among the `auxiliary_units` of a unit of `units` or of one on their + owner chains (the first holder wins; none that would close a loop). + `biosteam.System.from_units` sets the owners of the auxiliaries that + exist when it is called, but some units build theirs anew without one: + `biosteam.MultistageCompressor` rebuilds its compressors and + intercoolers whenever `pr` or `n_stages` changes, so the keys of its + ID cover them only by this relation (see `_parent`). Nothing is + mutated. + """ + owners = {} + for unit in _owner_chains(units): + for aux in getattr(unit, 'auxiliary_units', ()): + if (not isinstance(aux, bst.Unit) + or getattr(aux, '_owner', None) is not None + or id(aux) in owners): + continue + above = unit + while above is not None and above is not aux: + above = _parent(above, owners) + if above is None: owners[id(aux)] = (aux, unit) + return owners + +def _unit_name(unit, owners=None): """``(owner.ID, unit.ID)``, with the top owner (``(ID, ID)`` for a unit - without an owner): the unit's tuple key.""" - return unit.owner.ID, unit.ID + without an owner; owners along `_parent`): the unit's tuple key.""" + return _top(unit, owners).ID, unit.ID -def _id_path(unit): - """The IDs of `unit` and of its owners in turn, ``(unit.ID, - unit._owner.ID, ..., top.ID)``, which tell apart auxiliary units of the - same ID under different owners.""" +def _id_path(unit, owners=None): + """The IDs of `unit` and of its owners in turn (along `_parent`), + ``(unit.ID, owner.ID, ..., top.ID)``, which tell apart auxiliary units + of the same ID under different owners.""" path = [] while unit is not None: path.append(unit.ID) - unit = getattr(unit, '_owner', None) + unit = _parent(unit, owners) return tuple(path) -def _unit_message_name(unit): +def _unit_message_name(unit, owners=None): """How messages name a unit: its tuple key (`_unit_name`), followed by its owners below the top one when there are any (the tuple key of an auxiliary of an auxiliary is shared by its namesakes).""" - path = _id_path(unit) - label = repr(_unit_name(unit)) + path = _id_path(unit, owners) + label = repr(_unit_name(unit, owners)) if len(path) <= 2: return label return f"{label} (of {' of '.join(map(repr, path[1:-1]))})" -def _names_unit(key, unit, live=()): +def _names_unit(key, unit, live=(), owners=None): """ Whether the approach contribution key `key` (of a valid type, see - `_check_contribution_key`) names `unit`. A unit key names itself; a unit - key that is not a unit at hand (its id not in `live`, the ids of the - units at hand: a stale auxiliary that its owner rebuilt) names the unit - of the same `_id_path` instead. A tuple key names every unit of that + `_check_contribution_key`) names `unit`, with owners along `_parent` + (`owners`, see `_held_owners`). A unit key names itself; a unit key + that is not a unit at hand (its id not in `live`, the ids of the units + at hand: a stale auxiliary that its owner rebuilt) names the unit of + the same `_id_path` instead. A tuple key names every unit of that `_unit_name` (every auxiliary of that ID under that top owner, at any depth) and a str key the unit of that ID without an owner. """ if isinstance(key, bst.Unit): if key is unit: return True - return id(key) not in live and _id_path(key) == _id_path(unit) - if isinstance(key, tuple): return key == _unit_name(unit) - return key == unit.ID and unit.owner is unit - -def _contribution_key_level(key, unit, live=()): - """The number of steps along the owner chain of `unit` (`unit`, - ``unit._owner``, ...) to the first unit that `key` names (see - `_names_unit`, which takes `live`), or None.""" + return (id(key) not in live + and _id_path(key, owners) == _id_path(unit, owners)) + if isinstance(key, tuple): return key == _unit_name(unit, owners) + return key == unit.ID and _parent(unit, owners) is None + +def _contribution_key_level(key, unit, live=(), owners=None): + """The number of steps along the owner chain of `unit` (`unit`, its + owner, ..., along `_parent` with `owners`) to the first unit that `key` + names (see `_names_unit`, which takes `live`), or None.""" level = 0 while unit is not None: - if _names_unit(key, unit, live): return level - unit = getattr(unit, '_owner', None) + if _names_unit(key, unit, live, owners): return level + unit = _parent(unit, owners) level += 1 return None @@ -835,98 +880,153 @@ def _check_contribution_key(key): '(str) or (owner.ID, unit.ID) tuples, not ' f'{type(key).__name__!r} ({key!r})') -def _owner_chains(units): - """Every unit on the owner chains of `units`, once each.""" +def _owner_chains(units, owners=None): + """Every unit on the owner chains of `units` (along `_parent` with + `owners`), once each.""" found = {} for unit in units: while unit is not None and id(unit) not in found: found[id(unit)] = unit - unit = getattr(unit, '_owner', None) + unit = _parent(unit, owners) return list(found.values()) -def _names_unbuilt_auxiliary(key, universe, live): - """ - Whether the key `key` (of a valid type, see `_check_contribution_key`), - which names no unit of `universe` (see `_names_unit`; `live` holds the - ids of `universe`), names an auxiliary unit that a unit of `universe` - may build but does not hold now, as a `biosteam.HXutilities` builds one - exchanger per agent it needs: an old auxiliary object whose top owner is - in `universe` (rebuilt with fewer auxiliaries), or ``(owner.ID, ID)`` - for the ID that biosteam gives an auxiliary, with `name` one of the - `auxiliary_unit_names` of a unit of `universe` under that top owner: - ``name[k]`` if that unit's attribute `name` holds a list of auxiliaries - (e.g. ``('X1', 'heat_exchangers[2]')``), ``name`` if it holds a single - one, and either while it holds none (None or missing). Such a key is - valid but names nothing. +def _key_universe(units): + """The units that keys may name, given the units at hand `units`: + ``(universe, live, owners)``, the owner chains of `units` (see + `_owner_chains`) along the owners they give the auxiliaries they hold + (`owners`, see `_held_owners`), and the ids of the universe (`live`).""" + owners = _held_owners(units) + universe = _owner_chains(units, owners) + return universe, {id(u) for u in universe}, owners + +def _auxiliary_slots(key, universe, owners=None): + """ + Where the units of `universe` hold the auxiliary unit of the tuple key + `key` by the ID that `biosteam.Unit.auxiliary` gives an auxiliary: + ``(owner.ID, 'name[k]')`` for the k-th of a list under the attribute + `name`, ``(owner.ID, name)`` for a single one, with `name` one of the + `auxiliary_unit_names` of a unit under the top owner ``owner.ID`` + (along `_parent` with `owners`). Yields ``(unit, held)`` for each such + unit whose attribute takes that form or holds nothing (None or + missing), with `held` the `biosteam.Unit` there, or None if there is + none (nothing, an object that is not a unit, or a list too short). """ - if isinstance(key, bst.Unit): - top = key.owner - return top is not key and id(top) in live - if not isinstance(key, tuple): return False owner_ID, ID = key - match = re.fullmatch(r'(.+)\[\d+\]', ID) + match = re.fullmatch(r'(.+)\[(0|[1-9]\d*)\]', ID) name = match[1] if match else ID for unit in universe: - if (unit.owner.ID != owner_ID + if (_top(unit, owners).ID != owner_ID or name not in getattr(unit, 'auxiliary_unit_names', ())): continue - # biosteam's ID of an auxiliary under `name`: 'name[k]' while the - # attribute holds a list, else 'name' (either while it holds nothing) held = getattr(unit, name, None) - if held is None or isinstance(held, list) == bool(match): - return True - return False + if held is not None and isinstance(held, list) != bool(match): + continue + if match and held is not None: + k = int(match[2]) + held = held[k] if k < len(held) else None + yield unit, held if isinstance(held, bst.Unit) else None -def _unknown_keys(keys, universe): +def _names_unbuilt_auxiliary(key, universe, live, owners=None): + """ + Whether the key `key` (of a valid type, see `_check_contribution_key`), + which names no unit of `universe` (see `_names_unit`; `live` holds the + ids of `universe`, `owners` the owners of `_parent`), names an + auxiliary unit that a unit of `universe` may build but does not hold + now, as a `biosteam.HXutilities` builds one exchanger per agent it + needs: an old auxiliary object whose top owner is in `universe` + (rebuilt with fewer auxiliaries), or a tuple key of the ID that biosteam + gives an auxiliary (see `_auxiliary_slots`) where a unit of `universe` + holds no unit, or holds the unit of that ID: ``('X1', + 'heat_exchangers[2]')`` while the list ``X1.heat_exchangers`` is + shorter, say. Such a key is valid but names nothing. A slot that holds + a unit of another ID (``T1.heat_exchanger`` of a + `biosteam.EnzymeTreatment` T1 is ``('T1', '')``) does not make its key + valid (see `_unknown_key_hints`). + """ + if isinstance(key, bst.Unit): + top = _top(key, owners) + return top is not key and id(top) in live + if not isinstance(key, tuple): return False + return any(held is None or held.ID == key[1] + for _, held in _auxiliary_slots(key, universe, owners)) + +def _unknown_keys(keys, universe, owners=None): """The keys of `keys` (units, str or pairs of str) that name neither a - unit of `universe` (see `_names_unit`) nor an auxiliary unit that one of - them may build (see `_names_unbuilt_auxiliary`).""" + unit of `universe` (see `_names_unit`, with owners along `_parent`) + nor an auxiliary unit that one of them may build (see + `_names_unbuilt_auxiliary`).""" live = {id(u) for u in universe} return [key for key in keys - if not any(_names_unit(key, u, live) for u in universe) - and not _names_unbuilt_auxiliary(key, universe, live)] + if not any(_names_unit(key, u, live, owners) for u in universe) + and not _names_unbuilt_auxiliary(key, universe, live, owners)] + +def _unknown_key_hints(keys, universe, owners=None): + """ + Hints for the unknown keys `keys` (see `_unknown_keys`) that name a + unit of `universe` (owners along `_parent` with `owners`) by another + key: for a str key, each auxiliary unit of that ID (``"'condenser' is + ('D1', 'condenser')"``), and for a tuple key of the ID that biosteam + gives an auxiliary (see `_auxiliary_slots`), each unit held there under + another ID (``"T1.heat_exchanger is ('T1', '')"``). Returns them as + ``'; '`` each, joined ('' if none). + """ + hints = {} + for key in keys: + if isinstance(key, str): + for unit in universe: + if unit.ID == key and _parent(unit, owners) is not None: + hints[f'; {key!r} is {_unit_name(unit, owners)!r}'] = None + elif isinstance(key, tuple): + for unit, held in _auxiliary_slots(key, universe, owners): + if held is not None and held.ID != key[1]: + hints[f'; {unit.ID}.{key[1]} is ' + f'{_unit_name(held, owners)!r}'] = None + return ''.join(hints) def _mapping_contributions(hus, mapping, units): """ `_stream_contributions` of a mapping from keys naming units to contributions: checks the types of keys and values, then that every key names a unit of the universe (the owner chains of the units of `hus` - and of `units`, the units at hand of `_names_unit`) or an auxiliary - unit that one of them may build but does not hold now (see - `_names_unbuilt_auxiliary`: such a key names nothing), then that no two - keys of the same level for a unit of the universe give it different - values (None and nan are equal); then each heat utility takes the value - of the key of the lowest level on its unit's owner chain (None without - one). + and of `units`, the units at hand of `_names_unit`, along the owners + they give the auxiliaries they hold without one, see `_key_universe`) + or an auxiliary unit that one of them may build but does not hold now + (see `_names_unbuilt_auxiliary`: such a key names nothing), then that + no two keys of the same level for a unit of the universe give it + different values (None and nan are equal); then each heat utility takes + the value of the key of the lowest level on its unit's owner chain + (None without one). """ items = [] for key, value in mapping.items(): _check_contribution_key(key) items.append((key, _contribution_value(value, f'key {key!r}'))) - universe = _owner_chains([hu.unit for hu in hus] + list(units or ())) - live = {id(u) for u in universe} - unknown = _unknown_keys([key for key, _ in items], universe) + universe, live, owners = _key_universe([hu.unit for hu in hus] + + list(units or ())) + unknown = _unknown_keys([key for key, _ in items], universe, owners) if unknown: raise ValueError('approach_contributions keys that name no unit: ' f"{', '.join(map(repr, unknown))} (a key is a unit, " 'the ID of a unit without an owner, or (owner.ID, ' - 'unit.ID))') + 'unit.ID)' + f'{_unknown_key_hints(unknown, universe, owners)})') for unit in universe: first = {} # level -> (key, value) for key, value in items: - level = _contribution_key_level(key, unit, live) + level = _contribution_key_level(key, unit, live, owners) if level is None: continue other, other_value = first.setdefault(level, (key, value)) if other_value != value: raise ValueError( f'approach_contributions keys {other!r} and {key!r} ' - f'give unit {_unit_message_name(unit)} different values ' - f'at the same level: {other_value!r} and {value!r}') + f'give unit {_unit_message_name(unit, owners)} different ' + f'values at the same level: {other_value!r} and ' + f'{value!r}') values = [] for hu in hus: best = None # (level, value) of the nearest key for key, value in items: - level = _contribution_key_level(key, hu.unit, live) + level = _contribution_key_level(key, hu.unit, live, owners) if level is not None and (best is None or level < best[0]): best = (level, value) values.append(None if best is None else best[1]) diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 86940da..eea7b33 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -31,8 +31,8 @@ from ._planner import _approach_contributions, _given_contributions from .hxn_synthesis import (_problem_table, _pinch_cut, _format_H, _arrange_heat_utilities, _process_streams, _explicit_contributions, - _contribution_key_level, _owner_chains, _unknown_keys, - _unit_message_name) + _contribution_key_level, _key_universe, _unknown_keys, + _unknown_key_hints, _unit_message_name) __all__ = ('PinchTargets', 'supertarget', 'Supertargets', 'network_report', 'plot_composite_curves', 'plot_grand_composite_curve', @@ -1774,12 +1774,13 @@ def _film_mapping(self, h): it the same value. Unit keys may also name `key_units` (and their owners) and the auxiliaries that units may build but do not hold now, as approach contribution keys of the facility may, and then - have no effect. + have no effect. An auxiliary that a unit at hand holds without an + owner is that unit's (see `hensmith.hxn_synthesis._key_universe`). """ N = self.is_hot.size units = self.units or () - universe = _owner_chains([*units, *(self.key_units or ())]) - live = {id(u) for u in universe} + universe, live, owners = _key_universe( + [*units, *(self.key_units or ())]) items = [] unknown = [] for key, value in h.items(): @@ -1797,7 +1798,7 @@ def _film_mapping(self, h): raise TypeError('h keys must be stream indices (int), units, ' 'unit IDs (str) or (owner.ID, unit.ID) tuples, ' f'not {type(key).__name__!r} ({key!r})') - elif _unknown_keys([key], universe): + elif _unknown_keys([key], universe, owners): unknown.append(key) items.append((key, _film_value(value, f'h of key {key!r}'))) if unknown: @@ -1805,19 +1806,21 @@ def _film_mapping(self, h): f"{', '.join(map(repr, unknown))} (a key is a " 'stream index or, for targets of heat utilities, ' 'a unit, the ID of a unit without an owner, or ' - '(owner.ID, unit.ID))') + '(owner.ID, unit.ID)' + f'{_unknown_key_hints(unknown, universe, owners)}' + ')') unit_items = [(k, v) for k, v in items if not isinstance(k, Integral)] for unit in universe: first = {} # level -> (key, value) for key, value in unit_items: - level = _contribution_key_level(key, unit, live) + level = _contribution_key_level(key, unit, live, owners) if level is None: continue other, other_value = first.setdefault(level, (key, value)) if other_value != value: raise ValueError( f'h keys {other!r} and {key!r} give unit ' - f'{_unit_message_name(unit)} different values at the ' - f'same level: {other_value!r} and {value!r}') + f'{_unit_message_name(unit, owners)} different values ' + f'at the same level: {other_value!r} and {value!r}') values = np.full(N, _H_DEFAULT) for j in range(N): best = None # (level, value) of the nearest key @@ -1825,7 +1828,8 @@ def _film_mapping(self, h): if isinstance(key, Integral): level = -1 if key == j else None else: - level = _contribution_key_level(key, units[j], live) + level = _contribution_key_level(key, units[j], live, + owners) if level is not None and (best is None or level < best[0]): best = (level, value) if best is not None: values[j] = best[1] diff --git a/tests/test_hxn_contributions.py b/tests/test_hxn_contributions.py index 35e5137..d5f88c9 100644 --- a/tests/test_hxn_contributions.py +++ b/tests/test_hxn_contributions.py @@ -40,15 +40,17 @@ D. Keys and validation: the value domain (None or nan, else a finite number > 0); the mapping, sequence and callable forms; keys naming units, auxiliary units through their owners (the nearest key wins), - auxiliaries of one ID under different owners and stale auxiliaries, and - the units option; unknown keys, conflicting keys and bad values, - checked before any stream is flashed and, in the facility, even for - idle units. + auxiliaries of one ID under different owners, stale auxiliaries, + auxiliaries a unit holds under other IDs than biosteam's or without an + owner, and the units option; unknown keys, conflicting keys and bad + values, checked before any stream is flashed and, in the facility, even + for idle units. E. The ``cache_network`` rule: a cached network is reused while the explicit contributions resolve to the same values per stream. """ import math import random +import re import numpy as np import pytest import biosteam as bst @@ -1096,6 +1098,151 @@ def run(T, x): assert hxn_synthesis._stream_contributions(hus, tuple_key) == [None, None] assert hxn_synthesis._stream_contributions(hus, {old: 3.}) == [None, None] +class ListedHeaters(bst.Unit): + """Two heaters in the list ``hxs``, built as some biosteam units build + their auxiliaries (the high-rate wastewater reactors, say): under IDs of + their own (``'_hx'``), not the ``'hxs[k]'`` that + `biosteam.Unit.auxiliary` gives.""" + _N_ins = 1 + _N_outs = 1 + auxiliary_unit_names = ('hxs',) + + def _init(self, Ts=(330., 360.)): + self.hxs = [bst.HXutility(f'.{self.ID}_hx{k}', ins=bst.Stream(None), + outs=bst.Stream(None), T=T, rigorous=False) + for k, T in enumerate(Ts)] + + def _run(self): + feed = self.ins[0] + for hx in self.hxs: + hx.ins[0].copy_like(feed) + hx.simulate() + feed = hx.outs[0] + self.outs[0].copy_like(feed) + + def _design(self): pass + def _cost(self): pass + +def test_keys_of_auxiliaries_under_other_IDs(): + # where a unit holds an auxiliary under another ID than the one biosteam + # gives it ('' for the heater of an EnzymeTreatment, '_hx' for + # ListedHeaters'), a key of biosteam's ID names nothing and raises, with + # the held unit's key in the message, for approach contributions and + # film coefficients, in and outside the facility; the held unit's own + # key and its owner's apply, and a list place past the end stays valid + # (no effect) + bst.main_flowsheet.set_flowsheet('contributions_other_IDs') + bst.settings.set_thermo(['Water', 'Ethanol'], cache=True) + feed = bst.Stream('feed', Water=1000., units='kmol/hr', T=300.) + T1 = bst.units.EnzymeTreatment('T1', ins=feed, T=340.) + water = bst.Stream('water', Water=500., units='kmol/hr', T=300.) + U = ListedHeaters('U', ins=water) + hot = bst.Stream('hot', Water=1500., units='kmol/hr', T=420., P=5e5) + H1 = bst.HXutility('H1', ins=hot, T=320., rigorous=False) + HXN = HeatExchangerNetwork('HXN', T_min_app=10.) + sys = bst.System.from_units('contributions_other_IDs', + units=[T1, U, H1, HXN]) + simulate_strictly(sys) + hx = T1.heat_exchanger + assert hx.ID == '' and [i.ID for i in U.hxs] == ['U_hx0', 'U_hx1'] + + def used(x): + HXN.approach_contributions = x + simulate_strictly(sys) + return dict(zip(HXN.original_heat_exchangers, + HXN.synthesis_info['approach_contributions'].tolist())) + + default = used(None) + assert set(default) == {hx, *U.hxs, H1} + for x, given in [({'T1': 2.}, {hx: 2.}), ({('T1', ''): 2.}, {hx: 2.}), + ({hx: 2.}, {hx: 2.}), + ({'U': 3.}, dict.fromkeys(U.hxs, 3.)), + ({('U', 'U_hx1'): 3.}, {U.hxs[1]: 3.}), + ({('U', 'hxs[2]'): 3.}, {})]: + assert used(x) == default | given + bad = [(('T1', 'heat_exchanger'), r"T1\.heat_exchanger is \('T1', ''\)"), + (('U', 'hxs[1]'), r"U\.hxs\[1\] is \('U', 'U_hx1'\)")] + for key, hint in bad: + HXN.approach_contributions = {key: 2.} + with pytest.raises(ValueError, match='name no unit: ' + + re.escape(repr(key)) + '.*; ' + hint): + sys.simulate() + HXN.approach_contributions = None + simulate_strictly(sys) + targets = HXN.targets() + area = targets.area_target() + assert targets.area_target(h={('T1', ''): .5}) != area + assert targets.area_target(h={('T1', ''): .5}) == targets.area_target( + h={'T1': .5}) + assert targets.area_target(h={('U', 'hxs[2]'): .5}) == area + for key, hint in bad: + with pytest.raises(ValueError, match='h keys that name no stream: ' + + re.escape(repr(key)) + '.*; ' + hint): + targets.area_target(h={key: .5}) + # outside the facility, the universe is that of the heat utilities + hus = bst.process_tools.heat_exchanger_utilities_from_units([T1, U, H1]) + assert [hu.unit for hu in hus] == [hx, *U.hxs, H1] + assert hxn_synthesis._stream_contributions(hus, {('T1', ''): 2.}) == [ + 2., None, None, None] + for key, hint in bad: + with pytest.raises(ValueError, match=hint): + hxn_synthesis._stream_contributions(hus, {key: 2.}) + +def test_keys_of_auxiliaries_held_without_an_owner(): + # biosteam.MultistageCompressor builds its compressors and intercoolers + # anew, without an owner, whenever pr changes (System.from_units sets + # the owners of those that exist when it is called): the facility takes + # the compressor as their owner, so 'K1' and ('K1', 'K1_H1') apply and + # the bare 'K1_H1' raises in every sample, whichever order the system + # was built in, and the user's objects are left as they are + for presimulated in (False, True): + name = f'contributions_no_owner_{presimulated}' + bst.main_flowsheet.set_flowsheet(name) + bst.settings.set_thermo(['Water', 'Methane'], cache=True) + gas = bst.Stream('gas', Methane=100., units='kmol/hr', T=300., + P=101325., phase='g') + K1 = bst.MultistageCompressor('K1', ins=gas, pr=2., n_stages=3, + eta=0.7) + cold = bst.Stream('cold', Water=200., units='kmol/hr', T=290., + P=5e5) + C1 = bst.HXutility('C1', ins=cold, T=350., rigorous=False) + if presimulated: K1.simulate() + HXN = HeatExchangerNetwork('HXN', T_min_app=5.) + sys = bst.System.from_units(name, units=[K1, C1, HXN]) + + def used(x): + HXN.approach_contributions = x + simulate_strictly(sys) + return dict(zip(HXN.original_heat_exchangers, HXN.synthesis_info[ + 'approach_contributions'].tolist())) + + for k, pr in enumerate((2., 2.2, 2.)): + K1.pr = pr + default = used(None) + hxs = K1.hxs + assert set(default) == {*hxs, C1} + assert [hx.ID for hx in hxs] == ['K1_H1', 'K1_H2', 'K1_H3'] + assert used({'K1': 2.}) == default | dict.fromkeys(hxs, 2.) + assert used({('K1', 'K1_H1'): 2.}) == default | {hxs[0]: 2.} + HXN.approach_contributions = {'K1_H1': 2.} + with pytest.raises(ValueError, + match=r"name no unit: 'K1_H1' .*; 'K1_H1' is " + r"\('K1', 'K1_H1'\)"): + sys.simulate() + HXN.approach_contributions = None + simulate_strictly(sys) + targets = HXN.targets() + area = targets.area_target() + assert targets.area_target(h={'K1': .5}) != area + assert targets.area_target(h={'K1': .5}) == targets.area_target( + h={('K1', f'K1_H{n}'): .5 for n in (1, 2, 3)}) + with pytest.raises(ValueError, match='h keys that name no stream'): + targets.area_target(h={'K1_H1': .5}) + # the owners as biosteam left them: set by System.from_units for + # the intercoolers it saw, none for those built since + owner = K1 if presimulated and k == 0 else None + assert all(getattr(hx, '_owner', None) is owner for hx in hxs) + def test_contribution_keys_with_the_units_option(): # keys may name the units of the units option and their auxiliary units, # idle or not, but no other unit; a callable option is called once per From 0a089bfa6e2674baec8afb562e19ae3a81f6be9a Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 15:39:27 +0000 Subject: [PATCH 33/52] address review 6 (tg-core): pin which served streams bound a sensible line, test the cooling short level TGC6-1 (fixed): new test_sensible_line_streams_of_other_shifts pins the hottest cold (coldest hot) stream rule of _line_limit with approach contributions: a hot oil serving C1 (shift 4 K) and C2 (shift -4 K), and its cooling mirror with a coolant serving H1 (shift 6 K) and H2 (shift 14 K). Each asserts the closed-form outlet (bound where the low-shift stream ends, x = 13 of 155 kW), the isolated price, and, from an independent cascade (new helper line_gaps), that the line keeps exactly dT from each served stream at its own shift. New test_sensible_line_point_load_alone pins the _T_EQ tolerance of the presence mask: a pure saturated condenser alone at the cold end of cooling water's stretch bounds the outlet at the condensing knot (CW.T + (T_c - dT - CW.T) q / Q_cond), and the balanced composites keep dT. Mutants (max/min swapped in either branch, strict mask) now fail. TGC6-2 (fixed): test_levels_of_streams_beyond_reach gains the cooling mirror of the boiler case: a condenser at 311 K (contribution 1 K) with H 340 -> 300 K (9 K), between CW.T + dT and cooling water's limit. Cooling water's level lies strictly within 1e-8 K above the condenser's shifted temperature, its T_out - dT > CW.T + CW.dT, it serves H above its outlet only, and chilled water serves the condenser and H below. Dropping the cooling `+ _T_SHORT * point` now fails the test. TGC6-3 (nit, applied): dropped the no-op threshold branch of _side_heat (a threshold problem already has k_pinch = 0 and the cut 'below'); its docstring says why all heat is below. Tests: tests/test_targeting.py, tests/test_hxn_targets.py and the hensmith/targeting.py doctests: 122 passed. Each of the five mutants (heating and cooling max/min swap, strict mask, cooling and heating short-level offsets) fails at least one targeted test. Oracle sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (unchanged). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- hensmith/targeting.py | 4 +- tests/test_targeting.py | 156 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 156 insertions(+), 4 deletions(-) diff --git a/hensmith/targeting.py b/hensmith/targeting.py index eea7b33..14471e6 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -1269,7 +1269,8 @@ def grand_composite_curve(self): def _side_heat(self): """Each stream's heat above and below the pinch [kJ/hr] (all below - for a threshold problem).""" + for a threshold problem, whose pinch is at the grid's top with the + cut 'below').""" table = self.table k = self._k_pinch interval_H = np.abs(table.interval_H) @@ -1278,7 +1279,6 @@ def _side_heat(self): below = interval_H[:, k:].sum(axis=1) + point_H[:, k+1:].sum(axis=1) if self.cut == 'above': above = above + point_H[:, k] else: below = below + point_H[:, k] - if self.Q_hot == 0.: above, below = 0. * above, below + above return above, below def _level(self, agent, heating, j, reach): diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 2022a23..ce1e975 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -15,8 +15,9 @@ B. Utility targets: the multiple-utility allocation on the grand composite curve with biosteam's agents (levels recomputed from the agents' attributes, list order, biosteam's dT, unserved remainders, served - stretches), priced against an independent isolated pricing, and the - balanced curves. + stretches), priced against an independent isolated pricing, a sensible + agent's line against an independent cascade of each served stream at its + own shift, and the balanced curves. C. Supertargets: the area target against closed forms and an independent integration, the units target on references with process and utility pinches, the capital costs against biosteam's own costing, the @@ -665,6 +666,40 @@ def test_sensible_line_condenser(T_sat): for T_min_app in (2., 20.): assert 0. < t.at(T_min_app).area_target() < math.inf +def test_sensible_line_point_load_alone(): + # a pure condenser, a point load alone at the cold end of cooling water's + # stretch, is present there: the line keeps dT below its condensing + # temperature (taken for absent, cooling water would leave at its limit + # and the balanced composites would cross by 4.5 K) + test_hxn_targets.setup('test_targeting_line_point_load', ['Water']) + P = bst.settings.chemicals.Water.Psat(318.) + vapour = bst.Stream(None, Water=40., T=318., P=P, phase='g') + vapour.vle(V=1., P=P) + liquid = vapour.copy() + liquid.vle(V=0., P=P) + hot_in = bst.Stream(None, Water=200., T=345., P=3e5) + hot_out = hot_in.copy() + hot_out.T = 330. + t = PinchTargets([vapour, hot_in], [liquid, hot_out], [True, True], 10., + labels=['COND', 'H']) + Ts = t.grid['Ts'] + assert t.grid['k_lo'][0] == t.grid['k_hi'][0] == Ts.size - 1 + T_c = Ts[-1] + t.shift[0] + dT, CW = bst.HeatUtility.dT, agent('cooling_water') + assert Ts[-1] > CW.T + CW.dT + dT - t.shift[0] # the stretch ends at the grid's end + (u,) = t.utility_targets() + assert u.ID == CW.ID and u.duty == pytest.approx(t.Q_cold, rel=1e-12) + assert (u.T_in, u.T_out) == (pytest.approx(345., abs=1e-9), T_c) + # the line has taken the condensation's heat when it reaches T_c - dT + T_out = CW.T + (T_c - dT - CW.T) * u.duty / (vapour.H - liquid.H) + assert u.T_utility_out == pytest.approx(T_out, rel=1e-12) + assert T_out < CW.T_limit - 10. + assert u.cost == pytest.approx(fresh_price(-u.duty, T_out + dT, u.T_out, CW)[0], + rel=1e-9) + curves = t.balanced_composite_curves([u]) + assert min_vertical_gap(*curves) == pytest.approx(dT, abs=1e-6) + assert min_driving_force(*curves) >= dT - 1e-6 + def test_sensible_line_constant_cp(): dT = bst.HeatUtility.dT # cooling water: 40 of its 64 kW are below 316 K @@ -761,6 +796,94 @@ def test_sensible_line_process_in_stretch(): assert u.cost == pytest.approx(fresh_price(u.duty, T_out - dT, u.T_out, oil)[0], rel=1e-12) +def line_gaps(t, rows, u): + """ + ``{ID: smallest gap [K]}`` between the line of the sensible utility `u` + (linear in its heat, from `T_utility_in` at its level to `T_utility_out`) + and each constant-CP stream of `rows` (as in `targets`), each at its own + shift, every stream on the level's served side being served by `u`: an + independent cascade over the shifted stretch below `u.level` (above it, + for cooling), positive where the line is hotter than a cold stream + (colder than a hot one). + """ + shift = dict(zip(t.labels, t.shift)) + ends = {} + for ID, T_a, T_b, CP in rows: + lo, hi = min(T_a, T_b) - shift[ID], max(T_a, T_b) - shift[ID] + if u.heating: hi = min(hi, u.level) + else: lo = max(lo, u.level) + if lo < hi: ends[ID] = (lo, hi, CP * kW) + knots = np.ravel([(lo, hi) for lo, hi, _ in ends.values()]) + P = np.unique(np.concatenate([knots, np.linspace(knots.min(), knots.max(), 2001)])) + def given(p): # the heat `u` has exchanged beyond the shifted positions p + if u.heating: + return sum(CP * np.clip(hi - np.maximum(lo, p), 0., None) + for lo, hi, CP in ends.values()) + return sum(CP * np.clip(np.minimum(hi, p) - lo, 0., None) + for lo, hi, CP in ends.values()) + assert u.duty == pytest.approx(given(-math.inf if u.heating else math.inf), + rel=1e-12) + gaps = {} + for ID, (lo, hi, _) in ends.items(): + p = P[(lo <= P) & (P <= hi)] + T_line = u.T_utility_in + (u.T_utility_out - u.T_utility_in) * given(p) / u.duty + gap = T_line - (p + shift[ID]) + gaps[ID] = float(np.min(gap if u.heating else -gap)) + return gaps + +def test_sensible_line_streams_of_other_shifts(): + # with approach contributions the streams at a position of a sensible + # agent's stretch are at different real temperatures: its line must keep + # dT from the hottest cold stream there (the coldest hot one, for + # cooling). A hot oil (480 K, dT 0) serves C1 (390 -> 470 K, shift 4 K) + # and C2 (400 -> 475 of 478 K, shift -4 K): where C1 ends (shifted 466 + # K) it has given only C2's 13 kW above it, and C1 is 8 K hotter than C2 + # there. Bounded by C2 there, it would leave at 395.1 K and pass 3 K + # below C1 at shifted 404 K. + dT, water = bst.HeatUtility.dT, bst.HeatUtility.thermo_water + rows = [('C1', 390., 470., 1.), ('C2', 400., 478., 1.)] + t = targets('test_targeting_line_shifts_heat', rows, 10., + approach_contributions={'C1': 1., 'C2': 9.}) + shift = dict(zip(t.labels, t.shift)) + assert (shift['C1'], shift['C2']) == (4., -4.) + oil = bst.UtilityAgent('test_targeting_hot_oil_shifts', Water=1, T=480., P=5e6, + phase='l', thermo=water, T_limit=300., + heat_transfer_price=1e-6) + NG = agent('natural_gas') + u, gas = t.utility_targets([oil, NG], []) + assert (u.ID, gas.ID) == (oil.ID, NG.ID) + assert (u.T_in, u.T_out) == (pytest.approx(390., abs=1e-9), 475.) + q = 155. * kW + assert u.duty == pytest.approx(q, rel=1e-12) + T_out = oil.T - (oil.T - dT - 470.) * q / (13. * kW) + assert u.T_utility_out == pytest.approx(T_out, rel=1e-12) + assert u.cost == pytest.approx(fresh_price(u.duty, T_out - dT, u.T_out, oil)[0], + rel=1e-12) + # both keep dT: C2 at the level (its limit), C1 where it ends + assert line_gaps(t, rows, u) == pytest.approx({'C1': dT, 'C2': dT}, abs=1e-9) + # its mirror: a coolant (300 K, dT 0) serves H1 (390 -> 310 K, shift 6 + # K) and H2 (380 -> 305 of 302 K, shift 14 K): where H1 ends (shifted + # 304 K) it has taken only H2's 13 kW below it, and H1 is 8 K colder + # than H2 there + rows = [('H1', 390., 310., 1.), ('H2', 380., 302., 1.)] + t = targets('test_targeting_line_shifts_cool', rows, 10., + approach_contributions={'H1': 1., 'H2': 9.}) + shift = dict(zip(t.labels, t.shift)) + assert (shift['H1'], shift['H2']) == (6., 14.) + coolant = bst.UtilityAgent('test_targeting_coolant_shifts', Water=1, T=300., + P=101325., phase='l', thermo=water, T_limit=370., + heat_transfer_price=1e-6) + CHW = agent('chilled_water') + u, chilled = t.utility_targets([], [coolant, CHW]) + assert (u.ID, chilled.ID) == (coolant.ID, CHW.ID) + assert (u.T_in, u.T_out) == (pytest.approx(390., abs=1e-9), 305.) + assert u.duty == pytest.approx(q, rel=1e-12) + T_out = coolant.T + (310. - dT - coolant.T) * q / (13. * kW) + assert u.T_utility_out == pytest.approx(T_out, rel=1e-12) + assert u.cost == pytest.approx(fresh_price(-u.duty, T_out + dT, u.T_out, coolant)[0], + rel=1e-12) + assert line_gaps(t, rows, u) == pytest.approx({'H1': dT, 'H2': dT}, abs=1e-9) + def test_pricing_temperatures(): dT = bst.HeatUtility.dT # the hot stream's top, 445 K shifted, is above every cold stream: medium @@ -961,6 +1084,35 @@ def test_levels_of_streams_beyond_reach(): assert (MP.T_in, MP.T_out) == (pytest.approx(level, abs=1e-8), pytest.approx(B.outs[0].T, abs=1e-9)) assert u.duty + MP.duty == pytest.approx(t.Q_hot, rel=1e-12) + # its mirror: a condenser wholly beyond cooling water's limit, above the + # limit of the stream it serves (and above CW.T + dT, where a sensible + # agent's line would have no driving force left, which also keeps a + # level short of a point load): the level stays just short of it + test_hxn_targets.setup('test_targeting_beyond_condenser', ['Water']) + P = bst.settings.chemicals.Water.Psat(311.) + vapour = bst.Stream(None, Water=10., T=311., P=P, phase='g') + vapour.vle(V=1., P=P) + liquid = vapour.copy() + liquid.vle(V=0., P=P) + hot_in = bst.Stream(None, Water=100., T=340., P=3e5) + hot_out = hot_in.copy() + hot_out.T = 300. + t = PinchTargets([vapour, hot_in], [liquid, hot_out], [True, True], 10., + [1., 9.], labels=['COND', 'H']) + level = liquid.T - t.shift[0] # where COND condenses, shifted: 305 K + CW, CHW = agent('cooling_water'), agent('chilled_water') + assert CW.T + dT < liquid.T < CW.T + CW.dT + dT + u, chilled = t.utility_targets() + assert (u.ID, chilled.ID) == (CW.ID, CHW.ID) + assert level < u.level < level + 1e-8 + assert u.T_out == u.level + t.shift[1] and u.T_out - dT > CW.T + CW.dT + # cooling water serves H above its outlet; chilled water serves the + # condenser and H below it + cooled = hot_in.copy() + cooled.T = u.T_out + assert u.duty == pytest.approx(hot_in.H - cooled.H, rel=1e-12) + assert chilled.duty == pytest.approx(vapour.H - liquid.H + cooled.H - hot_out.H, + rel=1e-12) def test_levels_of_streams_past_a_zero(): # the heat of a level reaches only the next zero of the balanced curve From 1175b80a0932ff2c8d8d8f596dbfaa3d0a61b007 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 15:56:01 +0000 Subject: [PATCH 34/52] address review 6 (tg-report): keep balanced curve labels inside the axes, label the furnace at the top of the cold curve TGR6-1 (fixed): the labels of the balanced composite curves are placed when drawn, at the figure's final layout, by a picklable textcoords (_Inside): a label that would run past the edge of the axes is moved just inside them and kept off the composite it labels (raised over the hot curve under its new span for heating, lowered under the cold curve for cooling); a label that would run into the label of a lower heating (higher cooling) agent is moved on past it. The fuel label now sits above the hot end of the cold balanced composite (at the top of both curves), where the furnace heat closes the curve, as the 'hot utility' label of the real/shifted plots does, stacked over any heating label it would overlap; headroom 0.16 when a heating label is near the top. Labels that fit are placed exactly as before (the ch05 figures regenerate byte-identical). test_plot_readability: the FURNACE fuel anchor and stacking, plus the 'pyro' (short LP flat near the left edge) and 'fuel_only' cases among the balanced readable() cases, with no two labels overlapping, and the shifted LP label 2 points inside the axes. TGR6-2 (fixed, nit): a sweep whose finite TACs (at least two) are the same, the others nan, is no longer refined, and its repr and plot say 'TAC constant where finite' instead of 'at the bound'. Supertargets.constant is unchanged (every TAC finite). Tests: pytest tests/test_targeting.py hensmith/targeting.py: 80 passed. The synthesis path is untouched (oracle not needed). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- hensmith/targeting.py | 173 +++++++++++++++++++++++++++++++--------- tests/test_targeting.py | 75 +++++++++++++---- 2 files changed, 195 insertions(+), 53 deletions(-) diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 14471e6..3b7b5be 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -2124,11 +2124,15 @@ def _least_TAC(records): TAC = np.array([r['TAC'] for r in records], dtype=float) return int(np.argmin(np.where(np.isnan(TAC), math.inf, TAC))) -def _constant_TAC(records): - """Whether the records are at least two, each with a finite TAC, and - their TACs the same to within round-off (a relative 1e-12).""" +def _constant_TAC(records, where_finite=False): + """Whether the records are at least two, each with a finite TAC (or, if + `where_finite`, have at least two finite TACs, the others left out), + and their TACs the same to within round-off (a relative 1e-12).""" TAC = np.array([r['TAC'] for r in records], dtype=float) - return (TAC.size > 1 and bool(np.isfinite(TAC).all()) + finite = np.isfinite(TAC) + if where_finite: TAC = TAC[finite] + elif not finite.all(): return False + return (TAC.size > 1 and float(np.ptp(TAC)) <= 1e-12 * float(np.abs(TAC).max())) def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, @@ -2201,7 +2205,11 @@ def supertarget(targets, T_min_apps=None, refine=3, n_refine=8, same (to a relative 1e-12) is not refined: its targets do not depend on the minimum approach temperature over the sweep (for streams that recover no heat, only heaters, say), its optimum is merely the smallest - temperature, and `Supertargets.constant` flags it. + temperature, and `Supertargets.constant` flags it. Nor is one whose + finite TACs (at least two) are the same, the others not (an unserved + utility or an infinite area): its optimum is merely the smallest + temperature of that flat stretch, so its repr says 'TAC constant where + finite' rather than 'at the bound'. The capital target drops by about a shell and tube wherever the shrinking area fills one fewer of 150 ft² (see Notes of @@ -2314,8 +2322,8 @@ def evaluate(T_min_app): for _ in range(refine): if not any(math.isfinite(r['TAC']) for r in records): break # nothing to refine around - if _constant_TAC(records): - break # a flat line: nothing to refine either + if _constant_TAC(records, where_finite=True): + break # a flat line (where finite): nothing to refine either records.sort(key=lambda r: r['T_min_app']) i = _least_TAC(records) lo = records[max(i - 1, 0)]['T_min_app'] @@ -2424,6 +2432,8 @@ def __repr__(self): n = len(self.records) lo, hi = self.records[0]['T_min_app'], self.records[-1]['T_min_app'] if self.constant: bound = ', TAC constant over the sweep' + elif _constant_TAC(self.records, where_finite=True): + bound = ', TAC constant where finite' elif self.at_bound: bound = ', at the bound' else: bound = '' return (f'<{type(self).__name__}: {n} point{"s" if n != 1 else ""}, ' @@ -2838,6 +2848,59 @@ def _sci_ticks(axis): formatter = axis.get_major_formatter() if type(formatter) is ScalarFormatter: formatter.set_powerlimits((-3, 4)) +class _Inside: + """ + A `textcoords` for the annotation `label` of `ax`: `dx`, `dy` points off + its anchor, above left of it (right- and bottom-aligned) if `dy` > 0, + else below right of it (left- and top-aligned). + + Where that runs past the left (right) edge of the axes, decided when + drawn (at the final layout of the figure, which places the axes), the + label is moved right (left) to keep 2 points inside them, and raised + (lowered) to stay `abs(dy)` points above (below) the `curves` ((H, T) + in data) under (over) its new span. It is then moved on, up (down), + past each of the labels `over` whose box it would overlap or come + within 2 points of. A class rather than a closure, so that the figure + can still be pickled. + """ + def __init__(self, ax, label, dx, dy, curves=(), over=()): + self.ax, self.label, self.dx, self.dy = ax, label, dx, dy + self.curves, self.over = list(curves), list(over) + + def __call__(self, renderer): + from matplotlib.transforms import Affine2D + ax, label, dx, dy = self.ax, self.label, self.dx, self.dy + sign = 1. if dy > 0 else -1. + pt = ax.figure.dpi / 72. # pixels per point + prop = label.get_fontproperties() + size = lambda text: renderer.get_text_width_height_descent( + text, prop, ismath=False) + w = size(label.get_text())[0] + h = max(size(label.get_text())[1], size('lp')[1]) + x, y = ax.transData.transform(label.xy) + (dx * pt, dy * pt) + # the label's right (left) end and its bottom (top) + span = lambda: (x - w, x) if sign > 0 else (x, x + w) + a, b = span() + if sign > 0: shift = ax.bbox.x0 + 2. * pt - a + else: shift = ax.bbox.x1 - 2. * pt - b + if shift * sign > 0.: + x += shift + a, b = span() + for H, T in self.curves: + X, Y = ax.transData.transform(np.column_stack((H, T))).T + if not X.size or b < X[0] or X[-1] < a: continue + Y = np.concatenate((Y[(X >= a) & (X <= b)], + np.interp([max(a, X[0]), min(b, X[-1])], X, Y))) + if sign > 0: y = max(y, Y.max() + dy * pt) + else: y = min(y, Y.min() + dy * pt) + for box in sorted((other.get_window_extent(renderer) for other in self.over), + key=lambda box: sign * box.y0): + near, far = (box.y0, box.y1) if sign > 0 else (box.y1, box.y0) + if (box.x0 < b and a < box.x1 and sign * (near - y) < h + and sign * (y - far) < 2. * pt): + y = far + sign * 2. * pt + return Affine2D().translate(x, y) + def plot_composite_curves(targets, kind='real', utilities=None, ax=None, file=None, dpi=300): """ @@ -2879,14 +2942,22 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, shifted composites touch: not for a threshold problem whose composites end apart, nor without streams on one side (whose empty composite stays out of the legend). Fuel, at infinite temperature on the balanced - curves, is labeled rather than drawn, and so is an unserved remainder - ('Utility:unserved', in the upper left corner): the cold balanced - composite starts at the unserved cooling, and the hot one ends short of - the cold one by the unserved heating. A label of a sensible agent sits - off the middle of its line, above left for heating and below right for - cooling. The legend goes where it covers the least of the curves, the - band and the labels (matplotlib's 'best' location: no fixed corner is - clear of the composites of every problem). + curves, is labeled rather than drawn, above the hot end of the cold + balanced composite (at the top of both), whose top its heat closes, + and so is an unserved remainder ('Utility:unserved', in the upper left + corner): the cold balanced composite starts at the unserved cooling, + and the hot one ends short of the cold one by the unserved heating. A + label of a latent agent sits by its flat, above left of the warm end of + a heating one and below right of the cold end of a cooling one, and a + sensible agent's off the middle of its line, above left for heating + and below right for cooling. When drawn (at the final layout of the + figure), a label that would run past the edge of the axes is moved just + inside them, and off the composite it labels; one that would run into + the label of a lower heating (higher cooling) agent is moved on past + it, up for heating and down for cooling, and the fuel's past those of + the heating agents. The legend goes where it covers the least of the + curves, the band and the labels (matplotlib's 'best' location: no fixed + corner is clear of the composites of every problem). Examples -------- @@ -2933,7 +3004,7 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, ax.plot(cold_H, cold_T - 273.15, color=cold_color, lw=2, label=_legend('cold balanced composite', cold_H), gid='Cold balanced composite') - above = [] # anchors of the labels above a heating flat + labels = [] # (annotation, offset, curve) of each agent's but fuel's fuels = [] unserved = [] for u in utilities: @@ -2958,7 +3029,6 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, i = np.searchsorted(T, T_u - 1e-6) offset, ha, va = (2, -2), 'left', 'top' x = H[min(max(i, 0), H.size - 1)] - if u.heating: above.append((x, T_u)) else: # at the middle of a sensible agent's line, off the rising # composite: above left of it for heating, below right of it @@ -2969,26 +3039,48 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, offset, ha, va = (-6, 4), 'right', 'bottom' else: offset, ha, va = (6, -4), 'left', 'top' - ax.annotate(label, xy=(x, T_u - 273.15), xytext=offset, - textcoords='offset points', ha=ha, va=va, - color=hot_color if u.heating else cold_color, - gid='Utility:' + u.ID, **text_kwargs) - stacked = False - for u in fuels: # at the end of the finite hot curve - x = hot_H[-1] if hot_H.size else 0. - y = hot_T[-1] if hot_T.size else targets.grid['Ts'][0] - # above the label of the top flat if that ends there too - stacked = any(abs(x - a) <= 1e-9 * (abs(x) + abs(a)) + 1e-12 - and abs(y - b) <= 1e-6 for a, b in above) - ax.annotate(f'{u.ID} (furnace) {_format_duty(u.duty)} kJ/hr', - xy=(x, y - 273.15), xytext=(-2, 14) if stacked else (0, 4), - textcoords='offset points', ha='right', va='bottom', - color=hot_color, gid='Fuel', **text_kwargs) + annotation = ax.annotate( + label, xy=(x, T_u - 273.15), xytext=(0, 0), ha=ha, va=va, + color=hot_color if u.heating else cold_color, + gid='Utility:' + u.ID, **text_kwargs) + labels.append((annotation, offset, (H, T - 273.15))) + # each moved inside the axes if it would run out of them (off the + # composite it labels), and on past the labels of the lower heating + # (higher cooling) agents it would run into: up for heating, down + # for cooling + placed = {True: [], False: []} # the heating and the cooling labels + for annotation, offset, curve in sorted( + labels, key=lambda item: item[0].xy[1] * np.sign(item[1][1])): + side = placed[offset[1] > 0] + annotation.set_anncoords(_Inside(ax, annotation, *offset, + curves=[curve], over=side)) + side.append(annotation) + heating = placed[True] + # the fuel's heat closes the cold curve: above its hot end (at the + # top of both curves), over the labels of the heating agents it + # would overlap there, and of any fuel before it + curves_T = np.concatenate((hot_T, cold_T)) + if curves_T.size: + T_top, span = curves_T.max(), np.ptp(curves_T) + x = max(H[-1] for H in (hot_H, cold_H) if H.size) + else: + T_top, span, x = targets.grid['Ts'][0], 0., 0. + # headroom for two labels where a heating agent's is near the top + stacked = bool(fuels) and any(T_top - 273.15 - a.xy[1] <= 0.15 * span + for a in heating) + for u in fuels: + annotation = ax.annotate( + f'{u.ID} (furnace) {_format_duty(u.duty)} kJ/hr', + xy=(x, T_top - 273.15), xytext=(0, 0), ha='right', va='bottom', + color=hot_color, gid='Fuel', **text_kwargs) + annotation.set_anncoords(_Inside(ax, annotation, -2, 4, + over=heating)) + heating.append(annotation) if unserved: # the curves end apart: say why ax.text(0.02, 0.97, '\n'.join(unserved), transform=ax.transAxes, ha='left', va='top', color='0.45', gid='Utility:unserved', **text_kwargs) - # headroom for a label (or two) above the top flat + # headroom for the labels above the top of the curves ax.margins(y=0.16 if stacked else 0.1) ax.set_ylabel('T [°C]') else: @@ -3196,12 +3288,13 @@ def plot_supertargets(sweep, ax=None, file=None, dpi=300): Artists carry stable gids: 'TAC', 'Capital' (the annualized capital cost), 'Operating', 'Optimum' and 'Nonfinite'. The optimum is labeled '(TAC constant)' if the TAC is the same over the sweep - (`Supertargets.constant`), else '(at the bound)' if it is at an end of - the sweep (`Supertargets.at_bound`), and is not marked without a finite - TAC. The lines leave out the temperatures without a finite TAC (an - unserved utility or an infinite area): each stretch of them is shaded - ('Nonfinite', halfway to its finite neighbors), so the axis keeps the - whole sweep. + (`Supertargets.constant`), '(TAC constant where finite)' if it is the + same at each temperature with a finite TAC (at least two), else '(at + the bound)' if it is at an end of the sweep (`Supertargets.at_bound`), + and is not marked without a finite TAC. The lines leave out the + temperatures without a finite TAC (an unserved utility or an infinite + area): each stretch of them is shaded ('Nonfinite', halfway to its + finite neighbors), so the axis keeps the whole sweep. Examples -------- @@ -3246,6 +3339,8 @@ def plot_supertargets(sweep, ax=None, file=None, dpi=300): optimum = sweep.optimum if math.isfinite(optimum['TAC']): if sweep.constant: bound = ' (TAC constant)' + elif _constant_TAC(sweep.records, where_finite=True): + bound = ' (TAC constant where finite)' elif sweep.at_bound: bound = ' (at the bound)' else: bound = '' ax.axvline(optimum['T_min_app'], color='k', ls='--', lw=0.8) diff --git a/tests/test_targeting.py b/tests/test_targeting.py index ce1e975..62ccdcb 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -2004,6 +2004,20 @@ def test_sweep_constant(): with pytest.warns(RuntimeWarning, match='leave part of a utility target unserved'): unserved = supertarget(pair, [12., 14.], refine=0, cooling_agents=[]) assert np.isnan(unserved['TAC']).all() and not unserved.constant + # the same finite TACs, the others nan (the cooling past the threshold + # unserved): not refined either, and its optimum, the smallest + # temperature of that flat stretch, is not said to be at the bound + with pytest.warns(RuntimeWarning, match='leave part of a utility target unserved'): + partly = supertarget(pair, [2., 4., 6., 8., 12., 14.], cooling_agents=[]) + assert len(partly.records) == 6 and not partly.constant and partly.at_bound + assert list(np.isfinite(partly['TAC'])) == [True] * 4 + [False] * 2 + assert repr(partly) == ('') + # but one finite TAC among nan ones is no flat stretch: at the bound + with pytest.warns(RuntimeWarning, match='leave part of a utility target unserved'): + one = supertarget(pair, [8., 12., 14.], refine=0, cooling_agents=[]) + assert repr(one).endswith(', at the bound>') def test_sweep_refinement(): # each round evaluates n_refine new temperatures, on both sides of the @@ -2714,14 +2728,18 @@ def boxes(fig, artists): renderer = fig.canvas.get_renderer() return [a.get_window_extent(renderer) for a in artists] # fuel above high pressure steam: the label of the furnace sits above - # that of the top flat, which ends where the finite hot curve does + # the hot end of the cold curve, whose top its heat closes, and over + # that of the top flat, which runs into it there furnace = targets('test_targeting_plot_readable_fuel', FURNACE, 10.) fig, ax = plot_composite_curves(furnace, kind='balanced') try: fuel, = _gid_artists(ax, 'Fuel') steam, = _gid_artists(ax, 'Utility:high_pressure_steam') - assert fuel.xy == pytest.approx(steam.xy, rel=1e-9) + hot_T, hot_H, cold_T, cold_H = furnace.balanced_composite_curves() + assert fuel.xy == pytest.approx((cold_H[-1], 520. - K), rel=1e-9) + assert steam.xy[0] < fuel.xy[0] and steam.xy[1] < fuel.xy[1] fuel, steam, frame = boxes(fig, [fuel, steam, ax]) + assert fuel.x0 < steam.x1 and steam.x0 < fuel.x1 # stacked assert not fuel.overlaps(steam) and fuel.y0 >= steam.y1 - 1. assert frame.y1 >= fuel.y1 finally: @@ -2822,20 +2840,46 @@ def readable(fig, ax, labels, curves, legend=True): oiled = targets('test_targeting_plot_oil', [('C1', 390., 470., 1.), ('C2', 460., 470., 10.)], 10.) liquid = liquids('test_targeting_plot_liquids')[1].targets() - cases = [(threshold, None), (furnace, None), (oiled, oiled.utility_targets([oil], [])), - (liquid, None)] - for t, utilities in cases: + # and where a label at its anchor would run past the left edge of the + # axes, moved inside them: the low pressure steam's above a short flat + # near that edge (pyro), and a furnace's that served only a cold + # stream far above the short hot curve (fuel_only), whose label at the + # top of the curves stays clear of that edge and of the cold + # utilities' labels + pyro = targets('test_targeting_plot_pyro', [('C1', 300., 773., 1.), + ('H1', 750., 320., 0.6)], 10.) + fuel_only = targets('test_targeting_plot_fuel_only', [('C1', 600., 700., 1.), + ('H1', 330., 300., 0.5)], 10.) + assert [u.ID for u in fuel_only.utility_targets() if u.heating] == ['natural_gas'] + cases = [(threshold, None, True), (furnace, None, True), + (oiled, oiled.utility_targets([oil], []), True), (liquid, None, True), + (pyro, None, False), (fuel_only, None, True)] + for t, utilities, sensible in cases: fig, ax = plot_composite_curves(t, kind='balanced', utilities=utilities) try: labels = _gid_artists(ax, 'Utility:') + _gid_artists(ax, 'Fuel') - assert any(not (u.agent.isfuel or u.agent.T_limit is None) - for u in utilities or t.utility_targets()) + assert sensible == any(not (u.agent.isfuel or u.agent.T_limit is None) + for u in utilities or t.utility_targets()) curves = [line for line in ax.get_lines() if 'balanced composite' in (line.get_gid() or '')] assert len(curves) == 2 readable(fig, ax, labels, curves) + label_boxes = boxes(fig, labels) + assert all(not a.overlaps(b) for i, a in enumerate(label_boxes) + for b in label_boxes[i + 1:]) finally: plt.close(fig) + # the low pressure steam's label of pyro, at its anchor, would start + # left of the axes: moved right just inside them, over the hot curve + fig, ax = plot_composite_curves(pyro, kind='balanced') + try: + steam, = _gid_artists(ax, 'Utility:low_pressure_steam') + box, frame = boxes(fig, [steam, ax]) + anchor = ax.transData.transform(steam.xy) + assert box.x1 > anchor[0] and box.y0 > anchor[1] + assert box.x0 - frame.x0 == pytest.approx(2. * fig.dpi / 72., abs=0.5) + finally: + plt.close(fig) # the utility labels of the real and shifted composites inside the axes # and off the curves, also where the hot curve's hot end is the top of # the plot, at the right of it (KEMP, hot streams only); the legend off @@ -2961,12 +3005,15 @@ def test_plot_supertargets_gids(tmp_path): 'no finite TAC' if spans else labels[-1]) finally: plt.close(fig) - # a constant TAC is labeled so + # a constant TAC is labeled so, and one constant where finite too flat = supertarget(targets('test_targeting_plot_sweep_flat', [('C1', 330., 380., 1.)], 10.), [5., 10.]) - fig, ax = plot_supertargets(flat) - try: - assert [a.get_label() for a in _gid_artists(ax, 'Optimum')] == [ - 'optimum 5 K (TAC constant)'] - finally: - plt.close(fig) + with pytest.warns(RuntimeWarning, match='leave part of a utility target unserved'): + flat_partly = supertarget(pair, [2., 4., 12.], cooling_agents=[]) + for sweep, label in ((flat, 'optimum 5 K (TAC constant)'), + (flat_partly, 'optimum 2 K (TAC constant where finite)')): + fig, ax = plot_supertargets(sweep) + try: + assert [a.get_label() for a in _gid_artists(ax, 'Optimum')] == [label] + finally: + plt.close(fig) From db2d70710b64279beab846209f5f431303b5f98c Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 18:14:22 +0000 Subject: [PATCH 35/52] address review 7 (f1-facility): accept stage keys of any number, hint at unowned stages outside the facility F1F7-1 (fixed): a key (top.ID, f'{K.ID}_{ticket}{n}') of the ID that a biosteam.MultistageCompressor K at hand gives its stage n (ticket K or H, or that of the stages it holds) is now valid for any n >= 1, and names nothing while K has fewer stages (new _names_unbuilt_stage, used by _names_unbuilt_auxiliary, so the facility's approach_contributions and the film coefficient keys of its targets both accept it). A sweep over n_stages 3 -> 2 -> 3 -> 4 with ('K1', 'K1_H3') no longer raises at 2 stages. concepts.rst, the class Notes and the API row say so. New test test_keys_of_stages_a_compressor_does_not_build (sweeps from 3 and from 2 stages, checks bad stage IDs still raise). F1F7-2 (fixed): _unknown_key_hints now hints that a tuple key (o, x) of a unit x without an owner is x, and that the stages a MultistageCompressor of a str key's ID builds have no owner there (their IDs are their keys). synthesize_network's mapping bullet and concepts.rst state that outside the facility such an intercooler has no owner, so 'K1_H1' is its key. F1F7-3 (fixed): the tutorial says a pair key names the auxiliary units of that ID under the top owner; the class Notes and concepts.rst call a unit's owner its direct owner (Unit.owner gives the top one). F1F7-4 (fixed): new test_held_owners_skip_loops_and_take_the_first_holder with two toy units holding each other and a third without owners, in both orders: the first holder owns, the loop is skipped, key resolution ends. Tests: tests/test_hxn_contributions.py 50 passed; tests/test_targeting.py -k "bath_balanced_pair or keys_of_idle_units or facility" 6 passed; doctests of hensmith/hxn_synthesis.py and _heat_exchanger_network.py 5 passed; oracle sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (matches); sphinx -W shows only the unreachable intersphinx inventories. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/API/heat_exchanger_network.rst | 2 +- docs/source/concepts.rst | 25 +++-- docs/source/tutorial/04_configuring.rst | 3 +- hensmith/_heat_exchanger_network.py | 21 ++-- hensmith/hxn_synthesis.py | 92 ++++++++++++++++-- tests/test_hxn_contributions.py | 107 +++++++++++++++++++++ 6 files changed, 224 insertions(+), 26 deletions(-) diff --git a/docs/source/API/heat_exchanger_network.rst b/docs/source/API/heat_exchanger_network.rst index d7d2ae8..21a4a51 100644 --- a/docs/source/API/heat_exchanger_network.rst +++ b/docs/source/API/heat_exchanger_network.rst @@ -72,7 +72,7 @@ each of them changes. - Allow a process stream to be split into parallel branches that re-join. A side of the pinch that no unsplit network serves at MER is planned with splits and reaches the targets exactly on the planner's knots; sides that an unsplit network serves are never split, so a problem that needs no split gets the same network as with the default. Each split is a chain of ``Splitter`` units and a rigorous ``Mixer`` (adiabatic, no cost), listed in ``new_splitters`` and ``new_mixers``. With ``avoid_recycle``, a split that would repeat a stream pair is not used, and MER is then not guaranteed. Stored as an attribute of the same name, and part of the ``cache_network`` key. Defaults to False. * - ``approach_contributions`` - Mapping or callable - - Approach temperature contribution of each process stream, in K: a match between hot stream *h* and cold stream *c* must keep :math:`c_h + c_c` instead of ``T_min_app`` everywhere inside it, and a stream without a contribution contributes ``T_min_app / 2``; the utility exchangers keep BioSTEAM's own approach rule. A mapping's keys name units -- a unit, the ID of a unit without an owner (``'D1'``), or ``(owner.ID, unit.ID)`` for an auxiliary unit (``('D1', 'condenser')``; with the top owner, so it names every auxiliary of that ID under it) -- and a heat utility takes the value of the key nearest to its unit along the unit's chain of owners (two keys of the same level must agree); ID keys stay valid when a unit rebuilds its auxiliaries, and heat utility keys are rejected (``TypeError``). A callable ``f(hu)`` is called once per heat utility at every simulation and should be pure; a sequence raises a ``TypeError``. A value is None or nan (unspecified, an explicit default at its level) or a finite number > 0. Keys and values are checked against every available unit and its auxiliaries before any filtering, so an unknown key, a bad value or a conflict raises a ``ValueError`` even while its unit is idle, but a valid key whose unit holds no heat utility in a simulation has no effect, as does a key of an auxiliary that its unit may build but does not build in that simulation (``('X1', 'heat_exchangers[2]')`` of a ``biosteam.HXutilities`` that needs fewer exchangers, say). The resolved values are part of the ``cache_network`` key. May be changed between simulations. Defaults to None (every stream contributes ``T_min_app / 2``, so every pair requires ``T_min_app``). + - Approach temperature contribution of each process stream, in K: a match between hot stream *h* and cold stream *c* must keep :math:`c_h + c_c` instead of ``T_min_app`` everywhere inside it, and a stream without a contribution contributes ``T_min_app / 2``; the utility exchangers keep BioSTEAM's own approach rule. A mapping's keys name units -- a unit, the ID of a unit without an owner (``'D1'``), or ``(owner.ID, unit.ID)`` for an auxiliary unit (``('D1', 'condenser')``; with the top owner, so it names every auxiliary of that ID under it) -- and a heat utility takes the value of the key nearest to its unit along the unit's chain of owners (two keys of the same level must agree); ID keys stay valid when a unit rebuilds its auxiliaries, and heat utility keys are rejected (``TypeError``). A callable ``f(hu)`` is called once per heat utility at every simulation and should be pure; a sequence raises a ``TypeError``. A value is None or nan (unspecified, an explicit default at its level) or a finite number > 0. Keys and values are checked against every available unit and its auxiliaries before any filtering, so an unknown key, a bad value or a conflict raises a ``ValueError`` even while its unit is idle, but a valid key whose unit holds no heat utility in a simulation has no effect, as does a key of an auxiliary that its unit may build but does not build in that simulation (``('X1', 'heat_exchangers[2]')`` of a ``biosteam.HXutilities`` that needs fewer exchangers, say, or ``('K1', 'K1_H3')`` of a ``biosteam.MultistageCompressor`` K1 with 2 stages). The resolved values are part of the ``cache_network`` key. May be changed between simulations. Defaults to None (every stream contributes ``T_min_app / 2``, so every pair requires ``T_min_app``). Class attributes ---------------- diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index e51c6f1..178e6d8 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -280,13 +280,18 @@ the value of the key nearest to its unit along the unit's chain of owners, so ``'D1'`` covers a column's condenser and reboiler and ``('D1', 'condenser')`` overrides it for the condenser. A None or nan value is an explicit default that wins at its level like any value, and two keys -of the same level for a unit must agree. A unit's owner is its ``owner``, -or, for an auxiliary that a unit available to the network holds without -setting its owner, that unit (the user's objects are left as they are): -``biosteam.MultistageCompressor`` builds its compressors and intercoolers -anew, without an owner, whenever ``pr`` or ``n_stages`` changes, and -``'K1'`` still covers the intercoolers of compressor K1 and ``('K1', -'K1_H1')`` names one in every sample, while ``'K1_H1'`` names nothing. ID +of the same level for a unit must agree. A unit's owner is its direct owner +(the unit it was made an auxiliary of; BioSTEAM's ``Unit.owner`` gives the +top one), or, for an auxiliary that a unit available to the network holds +without setting its owner, that unit (the user's objects are left as they +are): ``biosteam.MultistageCompressor`` builds its compressors and +intercoolers anew, without an owner, whenever ``pr`` or ``n_stages`` +changes, and ``'K1'`` still covers the intercoolers of compressor K1 and +``('K1', 'K1_H1')`` names one in every sample, while ``'K1_H1'`` names +nothing. (Outside the facility -- ``synthesize_network``, +``temperature_interval_pinch_analysis``, ``PinchTargets.from_heat_utilities`` +-- only the heat utilities' units and their owners are at hand, so such an +intercooler has no owner there and ``'K1_H1'`` is its key.) ID keys are the robust form: a unit that rebuilds its auxiliary units at every simulation (``biosteam.HXutilities``, for one) leaves a key holding an old auxiliary object, which then names the auxiliary that took its place: the @@ -308,7 +313,11 @@ valid key whose unit holds no heat utility in a simulation (no duty, which a unit falls idle does not fail. The same holds for an auxiliary that a unit may build but does not hold in a sample: ``biosteam.HXutilities``, for one, builds one exchanger per agent it needs, so a lower outlet temperature -drops ``('X1', 'heat_exchangers[2]')``. A key of the ID that BioSTEAM's +drops ``('X1', 'heat_exchangers[2]')``, and a ``biosteam.MultistageCompressor`` +K1 with two stages drops ``('K1', 'K1_H3')``: a key ``('K1', +'K1_')`` of the ID that K1 gives its stage n (ticket ``K`` for a +compressor, ``H`` for an intercooler) is valid for any stage number n >= 1, +and has no effect while K1 has fewer stages. A key of the ID that BioSTEAM's ``Unit.auxiliary`` gives an auxiliary under a name of the ``auxiliary_unit_names`` of the owner or of one of its auxiliaries is valid whether or not that auxiliary is built, as is an old auxiliary object of a diff --git a/docs/source/tutorial/04_configuring.rst b/docs/source/tutorial/04_configuring.rst index 645f688..9428f6a 100644 --- a/docs/source/tutorial/04_configuring.rst +++ b/docs/source/tutorial/04_configuring.rst @@ -223,7 +223,8 @@ allows 1e-6 K below it). Setting ``approach_contributions`` back to ``None`` restores the default on the next simulation. :doc:`../concepts` gives the shifted scale that makes the targets exact for any contributions, and the full key rules: -``(owner.ID, unit.ID)`` keys for a single auxiliary unit, the nearest key +``(owner.ID, unit.ID)`` keys for the auxiliary units of that ID under the +top owner, the nearest key along a unit's owners winning, and keys that are checked even while their unit is idle. diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index f0b2014..09f00d2 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -291,13 +291,16 @@ class HeatExchangerNetwork(bst.Facility): reboiler of column D1 and ``('D1', 'condenser')`` overrides it for the condenser; a None or nan value is an explicit default that wins at its level like any other, and two keys of the same level for a unit must - agree. A unit's owner is its `owner` or, for an auxiliary that a unit - available to the network holds without setting its owner, that unit: - a `biosteam.MultistageCompressor` K1 builds its compressors and - intercoolers anew whenever `pr` or `n_stages` changes, without an - owner, and ``'K1'`` still covers the intercoolers and ``('K1', - 'K1_H1')`` names one in every simulation (the bare ``'K1_H1'`` names - nothing; user objects are not changed). ID keys are the robust form: a + agree. A unit's owner is its direct owner (the unit it was made an + auxiliary of; `biosteam.Unit.owner` gives the top one) or, for an + auxiliary that a unit available to the network holds without setting + its owner, that unit: a `biosteam.MultistageCompressor` K1 builds its + compressors and intercoolers anew whenever `pr` or `n_stages` changes, + without an owner, and ``'K1'`` still covers the intercoolers and + ``('K1', 'K1_H1')`` names one in every simulation (the bare ``'K1_H1'`` + names nothing, though it is the key of that intercooler outside the + facility, see `synthesize_network`; user objects are not changed). ID + keys are the robust form: a unit that rebuilds its auxiliary units at every simulation (e.g. `biosteam.HXutilities`) leaves a key holding an old auxiliary, which then names the auxiliary that took its place (the one of the same ID @@ -320,7 +323,9 @@ class HeatExchangerNetwork(bst.Facility): auxiliaries (e.g. ``('X1', 'heat_exchangers[2]')`` while a `biosteam.HXutilities` X1, which builds one exchanger per agent it needs, needs fewer), and ``(owner.ID, name)`` where it holds a single one (so - ``('X1', 'heat_exchangers')`` raises). Not every unit gives its + ``('X1', 'heat_exchangers')`` raises); or the ID that a + `biosteam.MultistageCompressor` K1 gives its stage n, for any n >= 1 + (``('K1', 'K1_H3')`` while K1 has 2 stages). Not every unit gives its auxiliaries that ID: the heater of a `biosteam.EnzymeTreatment` T1 has the ID '', so ``('T1', 'heat_exchanger')`` raises, with a message that gives the heater's key, ``('T1', '')``. A callable is diff --git a/hensmith/hxn_synthesis.py b/hensmith/hxn_synthesis.py index 81a0ae4..6eb1885 100644 --- a/hensmith/hxn_synthesis.py +++ b/hensmith/hxn_synthesis.py @@ -926,6 +926,48 @@ def _auxiliary_slots(key, universe, owners=None): held = held[k] if k < len(held) else None yield unit, held if isinstance(held, bst.Unit) else None +def _stage_tickets(compressor=None): + """The ticket names that a `biosteam.MultistageCompressor` gives its + stages in their IDs (``f'{K.ID}_{ticket}{n}'``): those of the + `biosteam.IsentropicCompressor` and `biosteam.HXutility` stages it + builds from `pr` and `n_stages`, and those of the stages that + `compressor` holds now (given as `compressors` and `hxs`).""" + tickets = {getattr(getattr(bst, 'IsentropicCompressor', None), + 'ticket_name', None), bst.HXutility.ticket_name} + if compressor is not None: + for stages in (getattr(compressor, 'compressors', None), + getattr(compressor, 'hxs', None)): + tickets.update(getattr(stage, 'ticket_name', None) + for stage in stages or ()) + return {ticket for ticket in tickets if isinstance(ticket, str) and ticket} + +def _is_stage_ID(ID, compressor_ID, tickets): + """Whether `ID` is ``f'{compressor_ID}_{ticket}{n}'`` for a ticket name + of `tickets` and a stage number n >= 1 (see `_stage_tickets`).""" + prefix = f'{compressor_ID}_' + return ID.startswith(prefix) and any( + re.fullmatch(re.escape(ticket) + r'[1-9]\d*', ID[len(prefix):]) + for ticket in tickets) + +def _names_unbuilt_stage(key, universe, owners=None): + """ + Whether the tuple key `key` gives the ID of a stage that a + `biosteam.MultistageCompressor` K of `universe` under the top owner + ``key[0]`` (along `_parent` with `owners`) may build: + ``f'{K.ID}_{ticket}{n}'`` for any stage number n >= 1 (see + `_stage_tickets`). K sets its `auxiliary_unit_names` to the IDs of the + stages it builds whenever `pr` or `n_stages` changes, so ``('K1', + 'K1_H3')`` names an intercooler of K1 with 3 stages or more and no unit + with fewer. + """ + MultistageCompressor = getattr(bst, 'MultistageCompressor', None) + if MultistageCompressor is None: return False + owner_ID, ID = key + return any(isinstance(unit, MultistageCompressor) + and _is_stage_ID(ID, unit.ID, _stage_tickets(unit)) + and _top(unit, owners).ID == owner_ID + for unit in universe) + def _names_unbuilt_auxiliary(key, universe, live, owners=None): """ Whether the key `key` (of a valid type, see `_check_contribution_key`), @@ -938,8 +980,11 @@ def _names_unbuilt_auxiliary(key, universe, live, owners=None): gives an auxiliary (see `_auxiliary_slots`) where a unit of `universe` holds no unit, or holds the unit of that ID: ``('X1', 'heat_exchangers[2]')`` while the list ``X1.heat_exchangers`` is - shorter, say. Such a key is valid but names nothing. A slot that holds - a unit of another ID (``T1.heat_exchanger`` of a + shorter, say. Or a tuple key of the ID that a + `biosteam.MultistageCompressor` of `universe` gives a stage, of any + stage number (see `_names_unbuilt_stage`): ``('K1', 'K1_H3')`` while + K1 has 2 stages. Such a key is valid but names nothing. A slot that + holds a unit of another ID (``T1.heat_exchanger`` of a `biosteam.EnzymeTreatment` T1 is ``('T1', '')``) does not make its key valid (see `_unknown_key_hints`). """ @@ -947,8 +992,9 @@ def _names_unbuilt_auxiliary(key, universe, live, owners=None): top = _top(key, owners) return top is not key and id(top) in live if not isinstance(key, tuple): return False - return any(held is None or held.ID == key[1] - for _, held in _auxiliary_slots(key, universe, owners)) + return (any(held is None or held.ID == key[1] + for _, held in _auxiliary_slots(key, universe, owners)) + or _names_unbuilt_stage(key, universe, owners)) def _unknown_keys(keys, universe, owners=None): """The keys of `keys` (units, str or pairs of str) that name neither a @@ -967,20 +1013,45 @@ def _unknown_key_hints(keys, universe, owners=None): key: for a str key, each auxiliary unit of that ID (``"'condenser' is ('D1', 'condenser')"``), and for a tuple key of the ID that biosteam gives an auxiliary (see `_auxiliary_slots`), each unit held there under - another ID (``"T1.heat_exchanger is ('T1', '')"``). Returns them as - ``'; '`` each, joined ('' if none). + another ID (``"T1.heat_exchanger is ('T1', '')"``). A tuple key + ``(owner_ID, ID)`` of a unit of `universe` without an owner gets that + unit's key (``"('K1', 'K1_H1') is 'K1_H1' (...)"``), and a str key, + the stages without an owner that a `biosteam.MultistageCompressor` of + that ID gives its IDs (see `_is_stage_ID`), such as the intercoolers + of the heat utilities of a compressor K1 outside a HeatExchangerNetwork + (which alone takes K1 as their owner, see `_held_owners`). Returns them + as ``'; '`` each, joined ('' if none). """ hints = {} + tickets = _stage_tickets() for key in keys: if isinstance(key, str): + stages = [] for unit in universe: if unit.ID == key and _parent(unit, owners) is not None: hints[f'; {key!r} is {_unit_name(unit, owners)!r}'] = None + elif (_parent(unit, owners) is None + and _is_stage_ID(unit.ID, key, tickets)): + stages.append(unit.ID) + if stages: + one = len(stages) == 1 + keyed = ('its ID is its key' if one + else 'their IDs are their keys') + hints[f"; {', '.join(map(repr, stages))} " + f"{'has' if one else 'have'} no owner here, so {keyed}" + f' (a MultistageCompressor {key!r} owns the stages it ' + 'builds only among the units available to a ' + 'HeatExchangerNetwork)'] = None elif isinstance(key, tuple): for unit, held in _auxiliary_slots(key, universe, owners): if held is not None and held.ID != key[1]: hints[f'; {unit.ID}.{key[1]} is ' f'{_unit_name(held, owners)!r}'] = None + if key[0] != key[1] and any( + unit.ID == key[1] and _parent(unit, owners) is None + for unit in universe): + hints[f'; {key!r} is {key[1]!r} (a unit without an owner ' + 'here)'] = None return ''.join(hints) def _mapping_contributions(hus, mapping, units): @@ -2236,8 +2307,13 @@ def synthesize_network(hus, T_min_app=5., Qmin=1e-3, force_ideal_thermo=False, that ID under that owner, at any depth, so the heaters of two auxiliary flash drums are told apart by keying the drums or the heater objects) or the ID of a unit without an owner (a bare - auxiliary ID such as ``'condenser'`` names nothing). A heat - utility takes the value of + auxiliary ID such as ``'condenser'`` names nothing). Only the + units of `hus` and their owners are at hand, so an auxiliary that + its unit holds without setting its owner is a unit without an + owner here: an intercooler that a `biosteam.MultistageCompressor` + K1 built anew is ``'K1_H1'``, and ``'K1'`` raises (a + `HeatExchangerNetwork` that K1 is available to takes K1 as its + owner instead). A heat utility takes the value of the key nearest to its unit along the unit's chain of owners: a unit's own key beats its owner's, which beats the owner's owner's (e.g. ``'D1'`` covers the condenser and the reboiler of column diff --git a/tests/test_hxn_contributions.py b/tests/test_hxn_contributions.py index d5f88c9..bd9368e 100644 --- a/tests/test_hxn_contributions.py +++ b/tests/test_hxn_contributions.py @@ -1243,6 +1243,113 @@ def used(x): owner = K1 if presimulated and k == 0 else None assert all(getattr(hx, '_owner', None) is owner for hx in hxs) +def test_keys_of_stages_a_compressor_does_not_build(): + # biosteam.MultistageCompressor sets its auxiliary_unit_names to the IDs + # of the stages it builds, so a sweep over n_stages drops ('K1', 'K1_H3') + # at 2 stages: the key of a stage of any number is valid but has no + # effect while it is missing, and names the stage again once it is + # built, whichever n_stages the system started at; keys of IDs that K1 + # never gives a stage still raise + for start in (3, 2): + name = f'contributions_unbuilt_stage_{start}' + bst.main_flowsheet.set_flowsheet(name) + bst.settings.set_thermo(['Water', 'Methane'], cache=True) + gas = bst.Stream('gas', Methane=100., units='kmol/hr', T=300., + P=101325., phase='g') + K1 = bst.MultistageCompressor('K1', ins=gas, pr=2., n_stages=start, + eta=0.7) + cold = bst.Stream('cold', Water=200., units='kmol/hr', T=290., + P=5e5) + C1 = bst.HXutility('C1', ins=cold, T=350., rigorous=False) + HXN = HeatExchangerNetwork('HXN', T_min_app=5.) + sys = bst.System.from_units(name, units=[K1, C1, HXN]) + x = {'K1': 2., ('K1', 'K1_H3'): 4., ('K1', 'K1_K3'): 1.} + + def used(n, x): + K1.n_stages = n + HXN.approach_contributions = x + simulate_strictly(sys) + return dict(zip([hx.ID for hx in HXN.original_heat_exchangers], + HXN.synthesis_info['approach_contributions'] + .tolist())) + + for n in (start, 2, 3, 4, 2): + stages = {f'K1_H{i}': 2. for i in range(1, n + 1)} + if n >= 3: stages['K1_H3'] = 4. + assert used(n, x) == {'C1': 2.5} | stages + assert K1.auxiliary_unit_names == tuple( + f'K1_{t}{i}' for i in range(1, n + 1) for t in 'KH') + targets = HXN.targets() + assert targets.area_target(h={('K1', 'K1_H3'): .5}) == ( + targets.area_target() if n < 3 + else targets.area_target(h={K1.hxs[2]: .5})) + for key in (('K1', 'K1_H0'), ('K1', 'K1_H03'), ('K1', 'K1_X3'), + ('K1', 'K1_H'), ('K1', 'K2_H3'), ('C1', 'K1_H3'), + ('K1', 'K1_H3x')): + with pytest.raises(ValueError, match='h keys that name no stream'): + targets.area_target(h={key: 2.}) + HXN.approach_contributions = {key: 3.} + with pytest.raises(ValueError, match='name no unit'): + sys.simulate() + # outside the facility, only the heat utilities' units are at hand: the + # intercoolers have no owner, and the messages say so + hus = bst.process_tools.heat_exchanger_utilities_from_units([K1, C1]) + assert [hu.unit.ID for hu in hus] == ['K1_H1', 'K1_H2', 'C1'] + assert hxn_synthesis._stream_contributions(hus, {'K1_H1': 2.}) == [ + 2., None, None] + for key, hint in ( + ('K1', r"; 'K1_H1', 'K1_H2' have no owner here, so their IDs are " + r"their keys \(a MultistageCompressor 'K1' owns"), + (('K1', 'K1_H1'), r"; \('K1', 'K1_H1'\) is 'K1_H1' \(a unit " + r"without an owner here\)\)$")): + with pytest.raises(ValueError, match=hint): + hxn_synthesis._stream_contributions(hus, {key: 2.}) + with pytest.raises(ValueError, match=hint): + PinchTargets.from_heat_utilities(hus, 5., {key: 2.}) + # a key of a stage beyond n_stages is no key of a unit not at hand + with pytest.raises(ValueError, match=r"name no unit: \('K1', 'K1_H3'\)"): + hxn_synthesis._stream_contributions(hus, {('K1', 'K1_H3'): 2.}) + +def test_held_owners_skip_loops_and_take_the_first_holder(): + # units that hold each other as auxiliaries without an owner: the first + # holder in the order given owns a unit, a holder that would close a + # loop does not, and key resolution ends + bst.main_flowsheet.set_flowsheet('contributions_held_owners') + bst.settings.set_thermo(['Water'], cache=True) + + class Holder(bst.Unit): + _N_ins = _N_outs = 0 + def _run(self): pass + + A, B, C = Holder('A'), Holder('B'), Holder('C') + A.auxiliary_unit_names = ('b', 'c') + A.b, A.c = B, C + B.auxiliary_unit_names = ('a', 'c') + B.a, B.c = A, C + for first, second in ((A, B), (B, A)): + owners = hxn_synthesis._held_owners([first, second]) + assert owners == {id(second): (second, first), id(C): (C, first)} + universe, live, owners = hxn_synthesis._key_universe( + [first, second, C]) + assert universe == [first, second, C] + assert hxn_synthesis._top(second, owners) is first + assert hxn_synthesis._top(first, owners) is first + assert hxn_synthesis._parent(C, owners) is first + assert hxn_synthesis._id_path(C, owners) == ('C', first.ID) + assert hxn_synthesis._unknown_keys( + [first.ID, (first.ID, second.ID), (first.ID, 'C'), C], + universe, owners) == [] + assert hxn_synthesis._unknown_keys( + [second.ID, 'C', (second.ID, 'C')], universe, owners) == [ + second.ID, 'C', (second.ID, 'C')] + # the first holder's key covers the other two, one step up + for unit in (second, C): + assert hxn_synthesis._contribution_key_level( + first.ID, unit, live, owners) == 1 + assert hxn_synthesis._contribution_key_level( + second.ID, C, live, owners) is None + for unit in (A, B, C): assert getattr(unit, '_owner', None) is None + def test_contribution_keys_with_the_units_option(): # keys may name the units of the units option and their auxiliary units, # idle or not, but no other unit; a callable option is called once per From 9c7e4bc6712d746041e764dad3e8eb9f1a4fa7f0 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 18:55:09 +0000 Subject: [PATCH 36/52] address review 7 (tg-report): place plot labels in one pass, in order, inside the axes and off the curves TGR7-1 (fixed): the labels of the balanced composite curves are placed by one _Stacked placer per axes, in one pass for all of them, kept for the renderer, the axes box and the view limits (with the texts). Each label's textcoords (_At) reads its spot from that pass instead of measuring the other labels' get_window_extent, which re-ran their own placement and made a draw exponential in the number of labels. Fifteen steam levels now take at most a few passes per draw (0.3 s; 182 s before). New test_plot_labels_placed_once bounds the passes per draw (<= 4) with 15 agents and checks order, overlap and the axes. TGR7-2 (fixed): labels are stacked in a column per side, and the stack is bounded by the axes. At plot time (when the canvas has a renderer) make_room runs the columns unbounded and extends the y data limits so that they fit. When drawn, a column that would still run past the top or bottom is set in rows instead, each label at its own level, moved sideways past the labels of the levels beyond its own and their leaders. If that fails, the labels are moved sideways past the label in their way, and as a last resort clamped inside the axes. The two crowded-refrigerant cases are added to test_plot_readability: labels inside the axes, off the curves and tick labels, apart and in order. TGR7-3 (fixed): on each side the labels keep the order of the levels (each heating label no lower than the one before it, each cooling label no higher). A label left more than 1.5 label heights from its anchor gets a thin leader (a _Leader patch, gid 'Leader:') from the anchor to the nearest point of its box. Balanced labels get a halo in the axes face colour, so that a leader passing behind one strikes none of its letters. The curves the labels are kept off are now both balanced curves. Tests: pyro on 4.5 and 5 in axes and in a 1x2 grid (readable, in order, leaders from anchor to box), and no leaders at the default size for pyro, furnace and liquid. TGR7-4 (fixed): grand composite curve labels are placed when drawn by _Beside. It tries the four places beside the step end in order: the side with more room first (right toward the pinch or left beyond the level, then the other vertical), then the other side. It takes the first that is inside the axes and crosses neither the curve's display path (Path.intersects_bbox), the steps, nor an earlier label. If none is clear, it takes the nearest clear place further along the level, away from the end. The Notes are corrected. The reviewer's pocket, shallow and twin cases and a 'nose' case (1 of the reviewer's 40 random GCCs, where no place beside the end is clear) are added to the GCC readability checks, which now measure the boxes, not xyann. Docs: the Notes of plot_composite_curves and plot_grand_composite_curve describe the placement and the 'Leader:' gid. tutorial_05_balanced_composites.png was regenerated by running docs/_demo_src/examples/ch05_targeting.py alone. Only the label halos changed in it, and no other asset or capture changed. The synthesis path is untouched, so the oracle was not needed. Tests: tests/test_targeting.py -k plot: 5 passed; pytest hensmith/targeting.py (doctests): 6 passed. Sphinx and docutils are not installed in the local venvs, so sphinx -W was not run here; the docstring changes are prose only. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- .../tutorial_05_balanced_composites.png | Bin 85383 -> 85691 bytes hensmith/targeting.py | 520 ++++++++++++++---- tests/test_targeting.py | 181 +++++- 3 files changed, 581 insertions(+), 120 deletions(-) diff --git a/docs/source/_static/images/examples/tutorial_05_balanced_composites.png b/docs/source/_static/images/examples/tutorial_05_balanced_composites.png index 2e2d3dc43556db5c1ab04a5dbc332f8a8da29267..d1393827f610c6f602efd58b1265062bfb019609 100644 GIT binary patch literal 85691 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

*tIRU8oSaHvc1j4X{jEs8!;AP^ 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≫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%0D<*gW<51 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�f}t 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 a)i-~fyV>`NvRDjqzlkh%ZFrT-0;+%(Dn literal 85383 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 a) & (left <= b) + if not keep.any(): continue + x0, y0, x1, y1, left, right = (v[keep] for v in (x0, y0, x1, y1, left, right)) + dx = x1 - x0 + steep = dx == 0. # vertical: both ends + slope = (y1 - y0) / np.where(steep, 1., dx) + ys += [np.where(steep, y0, y0 + slope * (np.clip(a, left, right) - x0)), + np.where(steep, y1, y0 + slope * (np.clip(b, left, right) - x0))] + if not ys: return None + ys = np.concatenate(ys) + return ys.max() if top else ys.min() + +class _Labels: """ - A `textcoords` for the annotation `label` of `ax`: `dx`, `dy` points off - its anchor, above left of it (right- and bottom-aligned) if `dy` > 0, - else below right of it (left- and top-aligned). - - Where that runs past the left (right) edge of the axes, decided when - drawn (at the final layout of the figure, which places the axes), the - label is moved right (left) to keep 2 points inside them, and raised - (lowered) to stay `abs(dy)` points above (below) the `curves` ((H, T) - in data) under (over) its new span. It is then moved on, up (down), - past each of the labels `over` whose box it would overlap or come - within 2 points of. A class rather than a closure, so that the figure - can still be pickled. + Where the annotations of `ax` added to it go, decided when drawn: at + the final layout of the figure, which places the axes. All of them are + placed in one pass (`_place`), kept for the renderer, the box and the + view limits of the axes it was made for, so that drawing or measuring + a label (as constrained layout, a legend's 'best' location and a tight + bounding box each do) places none of them again. Each label's + `textcoords` is an `_At` of it: a class rather than a closure, as is + the `_Leader` of one, so that the figure can still be pickled. + + The labels keep off the polylines `curves` ((x, y) in data, their + points that are not finite left out). """ - def __init__(self, ax, label, dx, dy, curves=(), over=()): - self.ax, self.label, self.dx, self.dy = ax, label, dx, dy - self.curves, self.over = list(curves), list(over) + def __init__(self, ax, curves=()): + self.ax, self.labels, self.options = ax, [], [] + self.curves = [] + for x, y in curves: + xy = np.column_stack((x, y)).astype(float).reshape(-1, 2) + self.curves.append(xy[np.isfinite(xy).all(axis=1)]) + self._key = self._spots = None + + def __getstate__(self): + return {**self.__dict__, '_key': None, '_spots': None} + + def add(self, label, **options): + """Place the annotation `label` (after those added before it).""" + label.set_anncoords(_At(self, len(self.labels))) + self.labels.append(label) + self.options.append(options) + + def spots(self, renderer): + """The display (x, y) of the text of each label, and the ends of + its leader (or None).""" + ax = self.ax + key = (id(renderer), tuple(ax.viewLim.bounds), tuple(ax.bbox.bounds), + tuple([(label.get_text(), label.get_fontsize()) + for label in self.labels])) + if key != self._key: + self._spots, self._key = self._place(renderer), key + return self._spots + + def _sizes(self, renderer): + """The display (width, height) of the text of each label (not + where it is, which is not placed yet).""" + from matplotlib.text import Text + boxes = [Text.get_window_extent(label, renderer) for label in self.labels] + return [(box.width, box.height) for box in boxes] + + def _curves(self): + """The `curves` in display.""" + return [self.ax.transData.transform(xy) for xy in self.curves] + +class _At: + """The `textcoords` of the label `index` of the `_Labels` `labels`.""" + def __init__(self, labels, index): + self.labels, self.index = labels, index def __call__(self, renderer): from matplotlib.transforms import Affine2D - ax, label, dx, dy = self.ax, self.label, self.dx, self.dy - sign = 1. if dy > 0 else -1. + return Affine2D().translate(*self.labels.spots(renderer)[self.index][0]) + +class _Leader(Patch): + """ + A thin line from the anchor of the label `index` of the `_Labels` + `labels` to its text, drawn where the label sits away from its anchor + (its path is that of the last draw, empty without a line). + """ + def __init__(self, labels, index, **kwargs): + from matplotlib.transforms import IdentityTransform + super().__init__(fill=False, transform=IdentityTransform(), **kwargs) + self.set_in_layout(False) + self.labels, self.index = labels, index + self._path = Path(np.zeros((0, 2))) + + def get_path(self): + return self._path + + def draw(self, renderer): + if not self.get_visible(): return + ends = self.labels.spots(renderer)[self.index][1] + self._path = Path(np.zeros((0, 2)) if ends is None else np.array(ends)) + if ends is not None: super().draw(renderer) + +class _Stacked(_Labels): + """ + Labels of the balanced composite curves (the `curves`), added by + `add(label, offset)` in turn: each heating agent's by ascending level, + then the fuel's, then each cooling agent's by descending level. + + A label sits `dx`, `dy` points (the `offset`) off its anchor: above + left of it (right- and bottom-aligned) if `dy` > 0, a heating one, else + below right of it (left- and top-aligned), a cooling one. One that + would run past the left or right edge of the axes is moved to keep 2 + points inside them, and each is kept `abs(dy)` points above (below) + the curves under its span. The labels of a side are then stacked in a + column: each at least as high (low) as the one before it, which keeps + the order of the levels, and moved on, up (down), past each label + placed before it whose box it would overlap or come within 2 points + of. Where that would run past the top (bottom) of the axes, they are + set in rows instead, from the other end: each where it sits, moved + sideways, left (right), past the labels of the levels beyond its own + (whose rows are beyond its own, so that its leader crosses none of + them), if that keeps the order and all of them inside the axes; else + in the column, moved sideways past the label they would run into at + the edge. One left more than 1.5 times its height from its anchor gets + a leader to it. + """ + def add(self, label, offset, leader=None): + """Place the annotation `label` `offset` points off its anchor, + with a leader (a `_Leader` added to the axes, with the gid + `leader`) if it is away from it.""" + super().add(label, offset=offset) + if leader is not None: + self.ax.add_patch(_Leader(self, len(self.labels) - 1, + color=label.get_color(), lw=0.6, + gid=leader)) + + def make_room(self, x=0.): + """Extend the data limits of the axes in y (at `x`), so that the + labels fit in their columns at the layout of the figure now + (where a renderer is at hand).""" + ax = self.ax + renderer = getattr(ax.figure.canvas, 'get_renderer', lambda: None)() + if renderer is None or not np.isfinite(ax.dataLim.intervaly).all(): return + for _ in range(4): # the scale changes with the limits + y0, y1 = ax.get_ylim() # autoscaled first + below, above = self._place(renderer, bound=False) + if below <= 0. and above <= 0.: break + scale = (y1 - y0) / ax.bbox.height # data per pixel + lo, hi = ax.dataLim.intervaly + ax.update_datalim([(x, lo - 1.05 * below * scale), + (x, hi + 1.05 * above * scale)], updatex=False) + ax.autoscale_view(scalex=False) + + def _place(self, renderer, bound=True): + """The `spots`, or how far (in pixels) the columns of labels run + past the bottom and the top of the axes if not `bound` by them.""" + ax = self.ax pt = ax.figure.dpi / 72. # pixels per point - prop = label.get_fontproperties() - size = lambda text: renderer.get_text_width_height_descent( - text, prop, ismath=False) - w = size(label.get_text())[0] - h = max(size(label.get_text())[1], size('lp')[1]) - x, y = ax.transData.transform(label.xy) + (dx * pt, dy * pt) - # the label's right (left) end and its bottom (top) - span = lambda: (x - w, x) if sign > 0 else (x, x + w) - a, b = span() - if sign > 0: shift = ax.bbox.x0 + 2. * pt - a - else: shift = ax.bbox.x1 - 2. * pt - b - if shift * sign > 0.: - x += shift - a, b = span() - for H, T in self.curves: - X, Y = ax.transData.transform(np.column_stack((H, T))).T - if not X.size or b < X[0] or X[-1] < a: continue - Y = np.concatenate((Y[(X >= a) & (X <= b)], - np.interp([max(a, X[0]), min(b, X[-1])], X, Y))) - if sign > 0: y = max(y, Y.max() + dy * pt) - else: y = min(y, Y.min() + dy * pt) - for box in sorted((other.get_window_extent(renderer) for other in self.over), - key=lambda box: sign * box.y0): - near, far = (box.y0, box.y1) if sign > 0 else (box.y1, box.y0) - if (box.x0 < b and a < box.x1 and sign * (near - y) < h - and sign * (y - far) < 2. * pt): - y = far + sign * 2. * pt - return Affine2D().translate(x, y) + gap = 2. * pt + frame = ax.bbox + lo, hi = frame.x0 + gap, frame.x1 - gap + bottom, top = frame.y0 + gap, frame.y1 - gap + if not bound: bottom, top = -math.inf, math.inf + curves = self._curves() + sizes = self._sizes(renderer) + anchors = [ax.transData.transform(label.xy) for label in self.labels] + offsets = [options['offset'] for options in self.options] + def clear(i, u, v): + """`v` kept abs(dy) points off the curves under the span of + label `i` from `u`.""" + (w, h), dy = sizes[i], offsets[i][1] + reach = _reach(curves, u, u + w, dy > 0) + if reach is None: return v + if dy > 0: return max(v, reach + dy * pt) + return min(v, reach + dy * pt - h) + def leader(i, u, v): + """The ends of a leader from the anchor of label `i` to the + nearest point of its box from `u`, `v`, if that is more than + 1.5 times its height away, else None.""" + (w, h), (x, y) = sizes[i], anchors[i] + near = (min(max(x, u), u + w), min(max(y, v), v + h)) + span = math.hypot(near[0] - x, near[1] - y) + if span <= 1.5 * h: return None + trim = 1.5 * pt / span + return [(x + trim * (near[0] - x), y + trim * (near[1] - y)), + (near[0] - trim * (near[0] - x), near[1] - trim * (near[1] - y))] + def stack(order, up, column, placed): + """The lower left corners of the labels `order` of a side (up + for heating), placed past those `placed` and each other (and, + in rows, the leaders before them): in a column, else in rows; + and whether that kept them inside the axes and in order, + without moving any the other way.""" + placed = list(placed) + corners = {} + last = None + clean = True + for i in order: + (w, h), (dx, dy) = sizes[i], offsets[i] + x, y = anchors[i] + (dx * pt, dy * pt) + u, v = (x - w, y) if up else (x, y - h) + u = max(min(u, hi - w), lo) # inside the axes, left to right + v = clear(i, u, v) + if column and last is not None: # in order + v = max(v, last[0]) if up else min(v, last[1] - h) + for _ in range(2 * len(placed) + 1): + hits = [box for box in placed + if box[0] < u + w + gap and u < box[2] + gap + and box[1] < v + h + gap and v < box[3] + gap] + if not hits: break + # past the nearest one, on (up or down) or sideways + # (left or right) + if up: + on = min(hits, key=lambda box: box[1])[3] + gap + side = max(hits, key=lambda box: box[0])[0] - gap - w + moves = [('v', on, on + h <= top), ('u', side, side >= lo)] + else: + on = max(hits, key=lambda box: box[3])[1] - gap - h + side = min(hits, key=lambda box: box[2])[2] + gap + moves = [('v', on, on >= bottom), ('u', side, side + w <= hi)] + if not column: moves.reverse() + for n, (move, value, fits) in enumerate(moves): + if fits: break + else: + clean = False + break + if n: clean = False + if move == 'v': v = value + else: u, v = value, clear(i, value, v) + if not bottom <= v <= top - h: + clean = False + v = max(min(v, top - h), bottom) # inside the axes + if not column and last is not None and ( + v > last[0] + 0.5 * h if up else v < last[0] - 0.5 * h): + clean = False # out of order + corners[i] = (u, v) + placed.append((u, v, u + w, v + h)) + ends = None if column else leader(i, u, v) + if ends is not None: + (x0, y0), (x1, y1) = ends + placed.append((min(x0, x1), min(y0, y1), max(x0, x1), max(y0, y1))) + last = (v, v + h) + return corners, clean + corners = {} + placed = [] + for up in (True, False): + order = [i for i, (dx, dy) in enumerate(offsets) if (dy > 0) == up] + if not order: continue + side, clean = stack(order, up, True, placed) + if not clean and bound: + rows, clean = stack(order[::-1], up, False, placed) + if clean: side = rows + corners.update(side) + placed += [(u, v, u + sizes[i][0], v + sizes[i][1]) + for i, (u, v) in side.items()] + if not bound: + return (max([frame.y0 + gap - v for u, v in corners.values()], default=0.), + max([v + sizes[i][1] - frame.y1 + gap + for i, (u, v) in corners.items()], default=0.)) + spots = [] + for i, (w, h) in enumerate(sizes): + u, v = corners[i] + spots.append(((u + w, v) if offsets[i][1] > 0 else (u, v + h), + leader(i, u, v))) + return spots + +class _Beside(_Labels): + """ + Labels of the steps of the grand composite curve, added by + `add(label, heating)` in turn, each anchored at the end of its step. + + A label sits beside the end, 3 points right or left of it and 2 points + above or below its level: on the side with more room in the axes, + first, toward the pinch (below a heating level, above a cooling one) + right of the end and beyond the level left of it, then the other way; + then likewise on the other side. It takes the first of these whose box + is inside the axes and crosses none of the `curves` (the grand + composite curve, the first, and the steps) nor a label placed before + it; else the nearest such box on along its level, away from the end + (by steps of its height); else the first of the four inside the axes + that keeps off the grand composite curve, else the first inside the + axes, else the first. + """ + def add(self, label, heating): + """Place the annotation `label` (left- and bottom-aligned) of a + `heating` step or not.""" + super().add(label, heating=heating) + + def _place(self, renderer): + from matplotlib.transforms import Bbox + ax = self.ax + pt = ax.figure.dpi / 72. + frame = ax.bbox + gcc, *steps = [Path(xy.reshape(-1, 2)) for xy in self._curves()] + placed = [] + spots = [] + for label, options, (w, h) in zip(self.labels, self.options, + self._sizes(renderer)): + x, y = ax.transData.transform(label.xy) + toward = -1. if options['heating'] else 1. # the pinch + right = frame.x1 - x >= x - frame.x0 + sides = [(side, vertical) for side in ((1., -1.) if right else (-1., 1.)) + for vertical in ((toward, -toward) if side > 0 else (-toward, toward))] + def score(side, vertical, slide=0.): + """Whether the box `slide` pixels on from its place beside + the end is outside the axes, on the curve, and on the + steps or a label placed before it; and the box.""" + u = x + 3. * pt + slide if side > 0 else x - 3. * pt - w - slide + v = y + 2. * pt if vertical > 0 else y - 2. * pt - h + box = Bbox([[u, v], [u + w, v + h]]) + inside = (frame.x0 <= u and u + w <= frame.x1 + and frame.y0 <= v and v + h <= frame.y1) + return ((not inside, gcc.intersects_bbox(box, filled=False), + any(p.intersects_bbox(box, filled=False) for p in steps) + or any(box.overlaps(other) for other in placed)), box) + candidates = [score(*side) for side in sides] + if all(any(flags) for flags, box in candidates): + # none clear beside the end: the nearest clear one on along + # the level, away from the end + slides = [score(*side, slide=k * h) for k in range(1, 41) + for side in sides] + clear = [item for item in slides if not any(item[0])] + candidates = clear[:1] + candidates + box = min(candidates, key=lambda item: item[0])[1] + placed.append(box) + spots.append(((box.x0, box.y0), None)) + return spots def plot_composite_curves(targets, kind='real', utilities=None, ax=None, file=None, dpi=300): @@ -2938,26 +3217,37 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, Temperatures are shown in degC. Artists carry stable gids: 'Hot composite' and 'Cold composite' (or 'Hot balanced composite' and 'Cold balanced composite'), 'Heat recovered', 'Hot utility', 'Cold utility', - 'Pinch', 'Fuel' and 'Utility:'. The pinch is marked where the - shifted composites touch: not for a threshold problem whose composites - end apart, nor without streams on one side (whose empty composite stays - out of the legend). Fuel, at infinite temperature on the balanced - curves, is labeled rather than drawn, above the hot end of the cold - balanced composite (at the top of both), whose top its heat closes, - and so is an unserved remainder ('Utility:unserved', in the upper left - corner): the cold balanced composite starts at the unserved cooling, - and the hot one ends short of the cold one by the unserved heating. A - label of a latent agent sits by its flat, above left of the warm end of - a heating one and below right of the cold end of a cooling one, and a - sensible agent's off the middle of its line, above left for heating - and below right for cooling. When drawn (at the final layout of the - figure), a label that would run past the edge of the axes is moved just - inside them, and off the composite it labels; one that would run into - the label of a lower heating (higher cooling) agent is moved on past - it, up for heating and down for cooling, and the fuel's past those of - the heating agents. The legend goes where it covers the least of the - curves, the band and the labels (matplotlib's 'best' location: no fixed - corner is clear of the composites of every problem). + 'Pinch', 'Fuel', 'Utility:' and 'Leader:'. The + pinch is marked where the shifted composites touch: not for a threshold + problem whose composites end apart, nor without streams on one side + (whose empty composite stays out of the legend). Fuel, at infinite + temperature on the balanced curves, is labeled rather than drawn, + above the hot end of the cold balanced composite (at the top of both), + whose top its heat closes, and so is an unserved remainder + ('Utility:unserved', in the upper left corner): the cold balanced + composite starts at the unserved cooling, and the hot one ends short of + the cold one by the unserved heating. A label of a latent agent sits by + its flat, above left of the warm end of a heating one and below right + of the cold end of a cooling one, and a sensible agent's off the middle + of its line, above left for heating and below right for cooling. When + drawn (at the final layout of the figure, all of them in one pass), a + label that would run past the left or right edge of the axes is moved + just inside them, and off the curves. The labels of each side keep the order of the levels: each one + that would run into the label of a lower heating (higher cooling) agent + is moved on past it, up for heating and down for cooling (the fuel's + past those of the heating agents), in a column for which the limits of + the y axis leave room (as the axes are laid out when drawn on). Where + the column would still run past the top (bottom) of the axes, the + labels go in rows instead, each beside its flat and moved sideways past + the labels of the levels beyond its own; or, if those would not keep + the order inside the axes, those that would run past the edge are moved + sideways past the label in their way. A label left more than 1.5 times + its height away from its anchor gets a thin leader from it + ('Leader:', an empty path otherwise), and each label a halo, + so that a leader passing behind it strikes none of its letters. The + legend goes where it covers the least of the curves, the band and the + labels (matplotlib's 'best' location: no fixed corner is clear of the + composites of every problem). Examples -------- @@ -2998,13 +3288,18 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, hot_T, hot_H, cold_T, cold_H = targets.balanced_composite_curves(utilities) finite = np.isfinite(hot_T) hot_T, hot_H = hot_T[finite], hot_H[finite] + # a halo, so that a leader passing behind a label strikes none of + # its letters + from matplotlib.patheffects import withStroke + label_kwargs = dict(text_kwargs, path_effects=[ + withStroke(linewidth=2.5, foreground=ax.get_facecolor())]) ax.plot(hot_H, hot_T - 273.15, color=hot_color, lw=2, label=_legend('hot balanced composite', hot_H), gid='Hot balanced composite') ax.plot(cold_H, cold_T - 273.15, color=cold_color, lw=2, label=_legend('cold balanced composite', cold_H), gid='Cold balanced composite') - labels = [] # (annotation, offset, curve) of each agent's but fuel's + labels = [] # (annotation, offset, ID) of each agent's but fuel's fuels = [] unserved = [] for u in utilities: @@ -3042,20 +3337,20 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, annotation = ax.annotate( label, xy=(x, T_u - 273.15), xytext=(0, 0), ha=ha, va=va, color=hot_color if u.heating else cold_color, - gid='Utility:' + u.ID, **text_kwargs) - labels.append((annotation, offset, (H, T - 273.15))) - # each moved inside the axes if it would run out of them (off the - # composite it labels), and on past the labels of the lower heating - # (higher cooling) agents it would run into: up for heating, down - # for cooling - placed = {True: [], False: []} # the heating and the cooling labels - for annotation, offset, curve in sorted( - labels, key=lambda item: item[0].xy[1] * np.sign(item[1][1])): - side = placed[offset[1] > 0] - annotation.set_anncoords(_Inside(ax, annotation, *offset, - curves=[curve], over=side)) - side.append(annotation) - heating = placed[True] + gid='Utility:' + u.ID, **label_kwargs) + labels.append((annotation, offset, u.ID)) + # each moved inside the axes if it would run out of them, off the + # curves, in the order of the levels, and on past the labels placed + # before it that it would run into (up for heating, down for + # cooling, or sideways at the edge of the axes); with a leader to + # its anchor if that leaves it away from it + placement = _Stacked(ax, [(hot_H, hot_T - 273.15), (cold_H, cold_T - 273.15)]) + heating = sorted([item for item in labels if item[1][1] > 0], + key=lambda item: item[0].xy[1]) + cooling = sorted([item for item in labels if item[1][1] < 0], + key=lambda item: -item[0].xy[1]) + for annotation, offset, ID in heating: + placement.add(annotation, offset, leader='Leader:' + ID) # the fuel's heat closes the cold curve: above its hot end (at the # top of both curves), over the labels of the heating agents it # would overlap there, and of any fuel before it @@ -3067,21 +3362,23 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, T_top, span, x = targets.grid['Ts'][0], 0., 0. # headroom for two labels where a heating agent's is near the top stacked = bool(fuels) and any(T_top - 273.15 - a.xy[1] <= 0.15 * span - for a in heating) + for a, *_ in heating) for u in fuels: annotation = ax.annotate( f'{u.ID} (furnace) {_format_duty(u.duty)} kJ/hr', xy=(x, T_top - 273.15), xytext=(0, 0), ha='right', va='bottom', - color=hot_color, gid='Fuel', **text_kwargs) - annotation.set_anncoords(_Inside(ax, annotation, -2, 4, - over=heating)) - heating.append(annotation) + color=hot_color, gid='Fuel', **label_kwargs) + placement.add(annotation, (-2, 4), leader='Leader:' + u.ID) + for annotation, offset, ID in cooling: + placement.add(annotation, offset, leader='Leader:' + ID) if unserved: # the curves end apart: say why ax.text(0.02, 0.97, '\n'.join(unserved), transform=ax.transAxes, ha='left', va='top', color='0.45', gid='Utility:unserved', **text_kwargs) - # headroom for the labels above the top of the curves + # headroom for the labels above the top of the curves, and more + # where they need it to stack in columns ax.margins(y=0.16 if stacked else 0.1) + placement.make_room(x) ax.set_ylabel('T [°C]') else: shifted = kind == 'shifted' @@ -3160,11 +3457,16 @@ def plot_grand_composite_curve(targets, utilities=None, ax=None, file=None, its level as long as its duty, joined by dotted verticals; cooling utilities likewise by descending level. Fuel is drawn dashed at the top of the grid and an unserved remainder in gray. Each label sits beside - the end of its segment, on the side with more room in the axes, off - the curve: right of the end, toward the pinch (below the level for - heating, above it for cooling), or left of the end, beyond the level - (above it for heating, below it for cooling), where the heat cascaded - is at least the end of the step. Artists carry stable gids: 'GCC', + the end of its segment, placed when drawn: on the side with more room + in the axes, right of the end toward the pinch (below the level for + heating, above it for cooling) or left of it beyond the level (above it + for heating, below it for cooling, where the heat cascaded is at least + the end of the step), if that keeps it inside the axes and off the + curve, the steps and the labels placed before it; else at the first + other side of the end and of the level that does (a pocket of the + curve, or a curve that leaves the end at a shallow angle, can run + toward the pinch); else on along its level, away from the end, where + it first does. Artists carry stable gids: 'GCC', 'Pinch', 'Utility:' (the segments, 'Utility:unserved' for a remainder) and 'Label:'. @@ -3218,6 +3520,7 @@ def plot_grand_composite_curve(targets, utilities=None, ax=None, file=None, cooling = sorted((u for u in utilities if not u.heating), key=lambda u: -u.level) ends = [] # (ID, x, y, heating, color) at the end of each step + segments = [] # (x, y) of the steps and the dotted lines between for steps, color, end in ((heating, hot_color, Ts[0]), (cooling, cold_color, Ts[-1])): x = 0. @@ -3231,28 +3534,27 @@ def plot_grand_composite_curve(targets, utilities=None, ax=None, file=None, if previous is not None: ax.plot([x, x], [previous, y], color=style['color'], ls=':', lw=0.8) + segments.append(([x, x], [previous, y])) ax.plot([x, x + u.duty], [y, y], lw=1.5, gid='Utility:' + ID, **style) + segments.append(([x, x + u.duty], [y, y])) x += u.duty previous = y ends.append((ID, x, y, u.heating, style['color'])) - # each label beside the end of its step on the side with more room - # in the axes, so that it stays inside them, and off the curve. Left - # of the end it sits beyond the level (above it for heating, below - # it for cooling), where the curve is never left of the end: the - # heat cascaded there is at least the duty of the steps up to this - # one. Right of the end it sits toward the pinch, which the curve - # leaves rising to the right above it and falling to the right - # below it. - lo, hi = ax.get_xlim() - for ID, x, y, heating, color in ends: - sign = 1 if hi - x >= x - lo else -1 # right or left of the end - dy = -2 * sign if heating else 2 * sign - ax.annotate(ID, xy=(x, y), xytext=(3 * sign, dy), - textcoords='offset points', - ha='left' if sign > 0 else 'right', - va='top' if dy < 0 else 'bottom', fontsize=7, - color=color, gid='Label:' + ID) + # each label beside the end of its step, placed when drawn: on the + # side with more room in the axes, toward the pinch right of the + # end, beyond the level left of it (where the curve never is: the + # heat cascaded there is at least the end of the step), or where + # else it keeps inside the axes and off the curve, the steps and + # the labels before it (a pocket of the curve, or a curve that + # leaves the end of a step at a shallow angle, can run toward the + # pinch) + placement = _Beside(ax, [(H, T - 273.15), *segments]) + for ID, x, y, is_heating, color in ends: + label = ax.annotate(ID, xy=(x, y), xytext=(0, 0), ha='left', + va='bottom', fontsize=7, color=color, + gid='Label:' + ID) + placement.add(label, is_heating) ax.margins(y=0.08) # headroom for a label beyond the outermost level _sci_ticks(ax.xaxis) ax.set_xlabel('heat cascaded [kJ·h$^{-1}$]') diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 62ccdcb..79b73cd 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -2880,6 +2880,88 @@ def readable(fig, ax, labels, curves, legend=True): assert box.x0 - frame.x0 == pytest.approx(2. * fig.dpi / 72., abs=0.5) finally: plt.close(fig) + def in_order(fig, ax): + """The utility labels of the balanced curves of each side in the + order of their anchors (a heating label no lower than that of a + lower level, a cooling one no higher than that of a higher one), + and each beside its anchor (within 1.5 times its height) or with a + leader from its anchor to the nearest point of its box; the number + of leaders.""" + fig.canvas.draw() + renderer = fig.canvas.get_renderer() + pt = fig.dpi / 72. + leaders = {a.get_gid(): a.get_path().vertices for a in _gid_artists(ax, 'Leader:')} + labels = sorted(_gid_artists(ax, 'Utility:'), key=lambda label: label.xy[1]) + labels = [label for label in labels if label.get_gid() != 'Utility:unserved'] + heating = [label for label in labels if label.get_ha() == 'right'] + cooling = [label for label in labels if label.get_ha() == 'left'] + for side, up in ((heating, True), (cooling, False)): + boxes = [label.get_window_extent(renderer) for label in side] + ends = [box.y0 if up else box.y1 for box in boxes] + assert all(a <= b + 2. * pt for a, b in zip(ends, ends[1:])), ends + drawn = 0 + for label in labels: + box = label.get_window_extent(renderer) + anchor = ax.transData.transform(label.xy) + near = np.clip(anchor, (box.x0, box.y0), (box.x1, box.y1)) + ends = leaders['Leader:' + label.get_gid().split(':', 1)[1]] + if math.dist(anchor, near) <= 1.5 * box.height: + assert not len(ends), label.get_text() + else: + assert len(ends) == 2, label.get_text() + assert math.dist(ends[0], anchor) <= 2. * pt + assert math.dist(ends[1], near) <= 2. * pt + drawn += 1 + return drawn + # labels crowding one end of the curves (refrigerants at the cold end): + # stacked in a column below right of their flats, in the room made for + # it, inside the axes and off the x tick labels + crowded = [targets('test_targeting_plot_crowded', [('C0', 367., 382., 2.47), + ('H1', 877., 796., 1.7), + ('C2', 482., 892., 2.29), + ('H3', 651., 237., 0.83)], 10.), + targets('test_targeting_plot_crowded_2', [('C0', 670., 760., 0.41), + ('H1', 828., 240., 2.72), + ('C2', 348., 848., 1.4), + ('H3', 578., 545., 1.2)], 10.)] + for t in crowded: + assert sum(not u.heating for u in t.utility_targets()) == 5 + fig, ax = plot_composite_curves(t, kind='balanced') + try: + labels = _gid_artists(ax, 'Utility:') + _gid_artists(ax, 'Fuel') + readable(fig, ax, labels, [line for line in ax.get_lines() + if 'balanced composite' in (line.get_gid() or '')]) + label_boxes = boxes(fig, labels) + assert all(not a.overlaps(b) for i, a in enumerate(label_boxes) + for b in label_boxes[i + 1:]) + ticks = boxes(fig, [x for x in ax.get_xticklabels() if x.get_text()]) + assert all(not a.overlaps(b) for a in label_boxes for b in ticks) + in_order(fig, ax) + finally: + plt.close(fig) + # on narrower axes (of a figure of the user's, alone or beside another), + # where the labels of pyro moved inside the left edge rise over the + # curve: in the order of the levels, each with a leader from its flat + for size, columns in (((4.5, 4.2), 1), ((5., 4.2), 1), ((10., 4.2), 2)): + fig, axes = plt.subplots(1, columns, figsize=size, layout='constrained', + squeeze=False) + try: + ax = axes[0, 0] + plot_composite_curves(pyro, kind='balanced', ax=ax) + if columns > 1: plot_composite_curves(pyro, kind='real', ax=axes[0, 1]) + labels = _gid_artists(ax, 'Utility:') + _gid_artists(ax, 'Fuel') + readable(fig, ax, labels, [line for line in ax.get_lines() + if 'balanced composite' in (line.get_gid() or '')]) + assert in_order(fig, ax) >= 2 + finally: + plt.close(fig) + # at the default size, beside their flats or a step above another + for t in (pyro, furnace, liquid): + fig, ax = plot_composite_curves(t, kind='balanced') + try: + assert in_order(fig, ax) == 0 + finally: + plt.close(fig) # the utility labels of the real and shifted composites inside the axes # and off the curves, also where the hot curve's hot end is the top of # the plot, at the right of it (KEMP, hot streams only); the legend off @@ -2912,27 +2994,104 @@ def readable(fig, ax, labels, curves, legend=True): assert all(not a.overlaps(b) for a, b in zip(tick_boxes, tick_boxes[1:])) finally: plt.close(fig) - # a label for each step, inside the axes on the side of the step's end - # with more room (the last steps end at the largest heats: left of the - # end, above a heating level and below a cooling one) and off the curve, - # which meets the end of a step or lies beyond it (KEMP's cooling water - # ends short of the curve, which turns back below its level) - for t in (gcc_A, furnace, liquid, kemp, threshold, hot, quickstart): + # a label for each step beside the end of its step (3 points right or + # left of it, 2 points above or below its level), inside the axes and + # off the curve: on the side of the end with more room, toward the + # pinch right of it and beyond the level left of it (the last steps end + # at the largest heats: left of the end, above a heating level and + # below a cooling one), as for the first seven (KEMP's cooling water + # ends short of the curve, which turns back below its level); else + # where else it is clear, where a pocket of the curve under a heating + # level (pocket, twin) or a curve that leaves the end of a cooling step + # rising at a shallow angle (shallow) runs toward the pinch right of + # the end; else on along its level, where the nose of the curve runs + # through each place beside the end on the side with room (nose: the + # low pressure steam's) + pocket = targets('test_targeting_plot_gcc_pocket', + [('C0', 401., 539., 0.49), ('H1', 430., 406., 2.87), + ('C2', 358., 508., 2.13), ('H3', 484., 460., 2.92)], 10.) + shallow = targets('test_targeting_plot_gcc_shallow', + [('C0', 300., 429., 1.56), ('H1', 538., 460., 1.69), + ('C2', 357., 424., 0.33), ('H3', 477., 342., 0.84)], 10.) + twin = targets('test_targeting_plot_gcc_twin', + [('C1', 300., 700., 1.), ('H1', 650., 320., 0.8), + ('C2', 340., 420., 3.), ('H2', 480., 290., 1.5)], 10.) + nose = targets('test_targeting_plot_gcc_nose', + [('C0', 463., 529., 1.84), ('H1', 407., 392., 0.95), + ('C2', 300., 527., 1.56), ('H3', 438., 377., 2.33)], 10.) + # (whether any label is not where the side with room puts it, whether + # any is on along its level) + cases = [(t, (False, False)) for t in (gcc_A, furnace, liquid, kemp, + threshold, hot, quickstart)] + cases += [(pocket, (True, False)), (shallow, (True, False)), + (twin, (True, False)), (nose, (True, True))] + for t, expected in cases: fig, ax = plot_grand_composite_curve(t) try: heating = {u.ID: u.heating for u in t.utility_targets()} labels = _gid_artists(ax, 'Label:') assert len(labels) == len(heating) readable(fig, ax, labels, _gid_artists(ax, 'GCC'), legend=False) - lo, hi = ax.get_xlim() + renderer = fig.canvas.get_renderer() + frame = ax.get_window_extent(renderer) + pt = fig.dpi / 72. + moved = slid = False for label in labels: - (x, _), (dx, dy) = label.xy, label.xyann - right = dx > 0 - assert right == (hi - x >= x - lo) - assert (dy > 0) == (heating[label.get_text()] != right) + box = label.get_window_extent(renderer) + x, y = ax.transData.transform(label.xy) + right, above = box.x0 > x, box.y0 > y + assert (box.y0 - y if above else y - box.y1) == pytest.approx(2. * pt, abs=0.5) + off = box.x0 - x if right else x - box.x1 + if off > 3. * pt + 0.5: slid = True + else: assert off == pytest.approx(3. * pt, abs=0.5) + room = frame.x1 - x >= x - frame.x0 + moved |= not (right == room and above == (heating[label.get_text()] != right)) + assert (moved, slid) == expected finally: plt.close(fig) +def test_plot_labels_placed_once(monkeypatch): + # the labels of the balanced curves are placed in one pass for each + # layout of the figure, not again for each label measured (which made + # a draw exponential in the number of labels): fifteen steam levels + # take a few passes, kept in the order of their levels, apart, inside + # the axes and off the curves + import matplotlib + matplotlib.use('Agg') + import matplotlib.pyplot as plt + from hensmith import targeting + t = targets('test_targeting_plot_many', [('C1', 300., 600., 1.), + ('H1', 590., 320., 0.3)], 10.) + thermo = agent('low_pressure_steam').thermo + steam = [bst.UtilityAgent(f'test_targeting_plot_steam_{i}', Water=1, + T=330. + 230. * i / 14, P=1e5, phase='g', thermo=thermo, + heat_transfer_price=1e-7 * (i + 1)) for i in range(15)] + with warnings.catch_warnings(): + warnings.simplefilter('ignore', RuntimeWarning) + utilities = t.utility_targets(steam) + assert sum(u.heating and u.agent is not None for u in utilities) == 15 + calls = [] + place = targeting._Stacked._place + monkeypatch.setattr(targeting._Stacked, '_place', lambda self, *args, **kwargs: + calls.append(None) or place(self, *args, **kwargs)) + fig, ax = plot_composite_curves(t, kind='balanced', utilities=utilities) + try: + for _ in range(2): + calls.clear() + fig.canvas.draw() + assert 1 <= len(calls) <= 4 + renderer = fig.canvas.get_renderer() + frame = ax.get_window_extent(renderer) + labels = sorted(_gid_artists(ax, 'Utility:test_targeting_plot_steam'), + key=lambda label: label.xy[1]) + assert len(labels) == 15 + boxes = [label.get_window_extent(renderer) for label in labels] + assert all(frame.y0 <= box.y0 and box.y1 <= frame.y1 for box in boxes) + assert all(a.y0 <= b.y0 for a, b in zip(boxes, boxes[1:])) + assert all(not a.overlaps(b) for i, a in enumerate(boxes) for b in boxes[i + 1:]) + finally: + plt.close(fig) + def test_plot_supertargets_gids(tmp_path): import matplotlib matplotlib.use('Agg') From e57abcaa80dc7f83cbae07cdb61ba00716ebe1d0 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 19:11:43 +0000 Subject: [PATCH 37/52] address review 7 (e2e): keep the holders of owner-less stages in targets and at synthesis, label by them, take key_units in from_heat_utilities E2E-R7-1 (fixed): the holder relation of auxiliaries held without an owner (MultistageCompressor stages) is now captured with the streams. _held_owners/_key_universe/_mapping_contributions/_stream_contributions/ _explicit_contributions take an optional snapshot `held` that comes before the live relation (default None: unchanged). The facility stores _held_owners(hu units + candidate units) at each fresh synthesis next to original_heat_exchangers (_holders); HXN.targets() resolves a mapping of approach contributions with it and hands the targets _held_owners(hxs + key_units, held). PinchTargets keep it in _holders (set by _from_exchangers/from_heat_utilities, copied by at()), and _film_mapping resolves h keys with it. Stored targets now keep summary(h={'K1': x}) after K1 rebuilds its stages (repro: 1185.1 m2 before and after, was 395.03 after), and HXN.targets() after K1.simulate() alone gives the synthesis's contributions (was None). Docs: concepts.rst, HXN.targets Notes, PinchTargets.key_units. E2E-R7-2 (fixed earlier): stage keys of any number were already accepted by fd7a7d4 (nst7.py: n_stages 3 -> 2 simulates); stored targets now also keep ('K1', 'K1_H3') on their old intercooler via the snapshot. E2E-R7-3 (fixed): _unit_label takes the top owner along _parent with the targets' holders, so intercoolers are 'K1 - K1_H1' in the facility's targets (and in from_heat_utilities given key_units), and an auxiliary of an empty ID is labeled by its owner's ID alone ('T1', not 'T1 - '). No generated docs capture shows such a label. E2E-R7-4 (fixed): PinchTargets.from_heat_utilities takes key_units=None (e.g. sys.units, expanded with nested auxiliaries as the facility's _candidate_units, now the shared helper _with_auxiliaries): approach contribution and h keys may name them, so {'K1': x}, the K1 object and ('K1', 'K1_H1') work there as in the facility. HXN.targets() before a simulation passes its units this way. The str-key hint also names key_units; tutorial ch05 mentions it. Tests: new test_targets_keep_the_holders_of_stages_rebuilt_since; labels asserted in test_keys_of_auxiliaries_under_other_IDs; key_units in test_facility_targets_keys_of_idle_units. test_hxn_targets.py + test_hxn_contributions.py: 93 passed; test_targeting.py: 75 passed; doctests of targeting.py, hxn_synthesis.py, _heat_exchanger_network.py: 11 passed. Oracle sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (unchanged). Sphinx is not installed in either venv, so the docs build was not run here. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/concepts.rst | 11 ++-- docs/source/tutorial/05_targeting.rst | 15 +++--- hensmith/_heat_exchanger_network.py | 45 ++++++++++------ hensmith/hxn_synthesis.py | 68 +++++++++++++++-------- hensmith/targeting.py | 75 ++++++++++++++++++++------ tests/test_hxn_contributions.py | 77 +++++++++++++++++++++++++++ tests/test_targeting.py | 7 +++ 7 files changed, 233 insertions(+), 65 deletions(-) diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 178e6d8..7c58086 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -288,9 +288,14 @@ are): ``biosteam.MultistageCompressor`` builds its compressors and intercoolers anew, without an owner, whenever ``pr`` or ``n_stages`` changes, and ``'K1'`` still covers the intercoolers of compressor K1 and ``('K1', 'K1_H1')`` names one in every sample, while ``'K1_H1'`` names -nothing. (Outside the facility -- ``synthesize_network``, -``temperature_interval_pinch_analysis``, ``PinchTargets.from_heat_utilities`` --- only the heat utilities' units and their owners are at hand, so such an +nothing. Targets keep this relation as it was when they were made (those of +``HXN.targets()`` as it was at the network's synthesis), for their +contributions, film coefficient keys and labels (``'K1 - K1_H1'``): ``'K1'`` +still covers the intercoolers of targets stored in one sample after K1 has +rebuilt them in the next, or has been simulated alone. (Outside the +facility -- ``synthesize_network``, ``temperature_interval_pinch_analysis``, +``PinchTargets.from_heat_utilities`` without ``key_units=sys.units`` -- only +the heat utilities' units and their owners are at hand, so such an intercooler has no owner there and ``'K1_H1'`` is its key.) ID keys are the robust form: a unit that rebuilds its auxiliary units at every simulation (``biosteam.HXutilities``, for one) leaves a key holding an old diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index 18f6857..0fb02d2 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -68,12 +68,15 @@ facility, from the heat utilities of heat exchangers with ``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. 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. +first inlet and outlet need not be the stream it heats or cools. 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 ---------------------------------------------- diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index 09f00d2..f383255 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -22,7 +22,7 @@ import numpy as np from .hxn_synthesis import ( synthesize_network, StreamLifeCycle, plot_pinch_diagram, _first_inlet, - _explicit_contributions, + _explicit_contributions, _held_owners, _with_auxiliaries, ) from warnings import warn @@ -534,21 +534,19 @@ def _candidate_units(self, units=None): `units` (defaults to `_get_units`) followed by its nested auxiliary units, whether or not it holds a heat utility the network takes.""" if units is None: units = self._get_units() - candidates = [] - for unit in units: - candidates.append(unit) - candidates.extend([i for i in unit.nested_auxiliary_units - if isinstance(i, bst.Unit)]) - return candidates + return _with_auxiliaries(units) - def _network_contributions(self, hx_utils, units=None): + def _network_contributions(self, hx_utils, units=None, held=None): """ The explicit approach contributions of `hx_utils` (aligned with it; None where unspecified), or None if none is given (see `hensmith.hxn_synthesis._explicit_contributions`). The keys are checked against every candidate unit (`_candidate_units` of `units`, the units `hx_utils` were taken from if given), so a bad key or - value raises even while its unit holds no heat utility. + value raises even while its unit holds no heat utility. `held`, the + holders of auxiliaries held without an owner as taken earlier (see + `hensmith.hxn_synthesis._held_owners`), comes before those of the + units now. """ x = self.approach_contributions if x is None: return None @@ -559,7 +557,8 @@ def _network_contributions(self, hx_utils, units=None): f'has nothing to align with), not ' f'{type(x).__name__!r}') return _explicit_contributions(hx_utils, x, self.T_min_app, - units=self._candidate_units(units)) + units=self._candidate_units(units), + held=held) def _get_original_heat_utilties(self, units=None): if units is None: units = self._get_units() @@ -725,6 +724,10 @@ def _cost(self): # Signed-duty order is the default matching priority of the # synthesis passes: smallest heating duty first among the cold # streams, largest cooling duty first among the hot streams. + # The holders of the auxiliaries held without an owner, as + # the contributions were resolved by (see `targets`). + holders = _held_owners([hu.unit for hu in hx_utils] + + self._candidate_units(units)) hx_utils.sort(key = lambda x: x.duty) self.HXN_flowsheet = HXN_F = flowsheet for i in HXN_F.registries: i.clear() @@ -750,6 +753,7 @@ def _cost(self): self.new_HXs_cold_side = HXs_cold_side self.cold_indices = cold_indices self.original_heat_exchangers = hxs + self._holders = holders self.new_HXs = new_HXs self.new_HX_utils = new_HX_utils self.streams_inlet = streams_inlet @@ -1038,7 +1042,12 @@ def targets(self, T_min_app=None): `replace_unit_heat_utilities` hold the network's utilities, not the streams' (see `hensmith.targeting.PinchTargets`). A mapping of approach contributions is resolved again by the units of - `original_heat_utils`, so a mapping changed since applies; a + `original_heat_utils`, so a mapping changed since applies (an + auxiliary that a unit held without an owner at the synthesis, such + as an intercooler of a `biosteam.MultistageCompressor` K1, is still + that unit's, as are the targets' film coefficient keys, even after + K1 has rebuilt its stages: K1 simulated alone, or a later sample of + stored targets; the targets keep that relation); a callable is not called again (with `replace_unit_heat_utilities`, the heat utilities it would read hold the network's utilities by then): its values are those the last simulation resolved, and a callable set @@ -1105,6 +1114,11 @@ def targets(self, T_min_app=None): is_hot.append(dH < 0. if dH else i not in cold) x = self.approach_contributions units = list(self._get_units()) + key_units = self._candidate_units(units) + # the holders of the auxiliaries held without an owner as at the + # synthesis, which a holder that has rebuilt them since (a + # MultistageCompressor simulated alone) no longer holds + held = getattr(self, '_holders', None) if x is not None and not isinstance(x, Mapping) and callable(x): # Never called again: with replace_unit_heat_utilities, the # heat utilities it would read now hold the network's @@ -1114,11 +1128,11 @@ def targets(self, T_min_app=None): contributions = getattr(self, '_resolved_contributions', None) else: # by unit only, which the network never changes - contributions = self._network_contributions(hus, units) + contributions = self._network_contributions(hus, units, held) targets = PinchTargets._from_exchangers( hxs, is_hot, T_min_app, contributions, self.force_ideal_thermo, - None, hus) - targets.key_units = self._candidate_units(units) + None, hus, _held_owners([*hxs, *key_units], held)) + targets.key_units = key_units targets.system = self.system return targets if self.system is None and not self.units: @@ -1129,8 +1143,7 @@ def targets(self, T_min_app=None): hx_utils.sort(key=lambda x: x.duty) # as `_cost` sorts them targets = PinchTargets.from_heat_utilities( hx_utils, T_min_app, self._network_contributions(hx_utils, units), - self.force_ideal_thermo, self.sort_hus_by_T) - targets.key_units = self._candidate_units(units) + self.force_ideal_thermo, self.sort_hus_by_T, key_units=units) targets.system = self.system return targets diff --git a/hensmith/hxn_synthesis.py b/hensmith/hxn_synthesis.py index 6eb1885..a3231ff 100644 --- a/hensmith/hxn_synthesis.py +++ b/hensmith/hxn_synthesis.py @@ -784,7 +784,7 @@ def _top(unit, owners=None): while (owner := _parent(unit, owners)) is not None: unit = owner return unit -def _held_owners(units): +def _held_owners(units, held=None): """ The owners that the units at hand give the auxiliary units they hold without setting their owner: a dict from the id of such an auxiliary to @@ -795,11 +795,14 @@ def _held_owners(units): exist when it is called, but some units build theirs anew without one: `biosteam.MultistageCompressor` rebuilds its compressors and intercoolers whenever `pr` or `n_stages` changes, so the keys of its - ID cover them only by this relation (see `_parent`). Nothing is - mutated. - """ - owners = {} - for unit in _owner_chains(units): + ID cover them only by this relation (see `_parent`). `held`, such a + dict taken earlier (a snapshot, which keeps its objects alive), comes + first: its auxiliaries keep their holders even once a holder has + rebuilt them (no longer holds them), and the units at hand add the + others, along its owners. Nothing is mutated. + """ + owners = dict(held) if held else {} + for unit in _owner_chains(units, owners): for aux in getattr(unit, 'auxiliary_units', ()): if (not isinstance(aux, bst.Unit) or getattr(aux, '_owner', None) is not None @@ -890,12 +893,25 @@ def _owner_chains(units, owners=None): unit = _parent(unit, owners) return list(found.values()) -def _key_universe(units): +def _with_auxiliaries(units): + """Each unit of `units` followed by its nested auxiliary units (those + that are `biosteam.Unit` objects): the units that keys may name, as + `HeatExchangerNetwork` takes those available to it, whether or not + they hold a heat utility.""" + candidates = [] + for unit in units: + candidates.append(unit) + candidates.extend([i for i in unit.nested_auxiliary_units + if isinstance(i, bst.Unit)]) + return candidates + +def _key_universe(units, held=None): """The units that keys may name, given the units at hand `units`: ``(universe, live, owners)``, the owner chains of `units` (see `_owner_chains`) along the owners they give the auxiliaries they hold - (`owners`, see `_held_owners`), and the ids of the universe (`live`).""" - owners = _held_owners(units) + (`owners`, see `_held_owners`, after those of the snapshot `held`), and + the ids of the universe (`live`).""" + owners = _held_owners(units, held) universe = _owner_chains(units, owners) return universe, {id(u) for u in universe}, owners @@ -1019,7 +1035,8 @@ def _unknown_key_hints(keys, universe, owners=None): the stages without an owner that a `biosteam.MultistageCompressor` of that ID gives its IDs (see `_is_stage_ID`), such as the intercoolers of the heat utilities of a compressor K1 outside a HeatExchangerNetwork - (which alone takes K1 as their owner, see `_held_owners`). Returns them + (which, with the `key_units` of `hensmith.PinchTargets.from_heat_utilities`, + alone takes K1 as their owner, see `_held_owners`). Returns them as ``'; '`` each, joined ('' if none). """ hints = {} @@ -1041,7 +1058,8 @@ def _unknown_key_hints(keys, universe, owners=None): f"{'has' if one else 'have'} no owner here, so {keyed}" f' (a MultistageCompressor {key!r} owns the stages it ' 'builds only among the units available to a ' - 'HeatExchangerNetwork)'] = None + 'HeatExchangerNetwork, or the key_units of ' + 'PinchTargets.from_heat_utilities)'] = None elif isinstance(key, tuple): for unit, held in _auxiliary_slots(key, universe, owners): if held is not None and held.ID != key[1]: @@ -1054,14 +1072,14 @@ def _unknown_key_hints(keys, universe, owners=None): 'here)'] = None return ''.join(hints) -def _mapping_contributions(hus, mapping, units): +def _mapping_contributions(hus, mapping, units, held=None): """ `_stream_contributions` of a mapping from keys naming units to contributions: checks the types of keys and values, then that every key names a unit of the universe (the owner chains of the units of `hus` and of `units`, the units at hand of `_names_unit`, along the owners - they give the auxiliaries they hold without one, see `_key_universe`) - or an auxiliary unit that one of them may build but does not hold now + they give the auxiliaries they hold without one, after those of the + snapshot `held`, see `_key_universe`) or an auxiliary unit that one of them may build but does not hold now (see `_names_unbuilt_auxiliary`: such a key names nothing), then that no two keys of the same level for a unit of the universe give it different values (None and nan are equal); then each heat utility takes @@ -1073,7 +1091,7 @@ def _mapping_contributions(hus, mapping, units): _check_contribution_key(key) items.append((key, _contribution_value(value, f'key {key!r}'))) universe, live, owners = _key_universe([hu.unit for hu in hus] - + list(units or ())) + + list(units or ()), held) unknown = _unknown_keys([key for key, _ in items], universe, owners) if unknown: raise ValueError('approach_contributions keys that name no unit: ' @@ -1103,7 +1121,8 @@ def _mapping_contributions(hus, mapping, units): values.append(None if best is None else best[1]) return values -def _stream_contributions(hus, approach_contributions, units=None): +def _stream_contributions(hus, approach_contributions, units=None, + held=None): """ Resolve `approach_contributions` to one approach temperature contribution per heat utility of `hus`, in the given order (zero-duty @@ -1115,12 +1134,14 @@ def _stream_contributions(hus, approach_contributions, units=None): utility (called once per heat utility, in order). `units` adds units (with their owner chains) to those that keys may name and that are checked for conflicting keys, whether or not they hold a heat utility - of `hus`. + of `hus`, and `held` gives the holders of auxiliaries taken earlier + (see `_held_owners`). """ N = len(hus) x = approach_contributions if x is None: return [None] * N - if isinstance(x, Mapping): return _mapping_contributions(hus, x, units) + if isinstance(x, Mapping): + return _mapping_contributions(hus, x, units, held) if isinstance(x, (Sequence, np.ndarray)) and not isinstance(x, (str, bytes)): values = list(x) if len(values) != N: @@ -1136,14 +1157,15 @@ def _stream_contributions(hus, approach_contributions, units=None): f'a callable, not {type(x).__name__!r}') def _explicit_contributions(hus, approach_contributions, T_min_app, - units=None): + units=None, held=None): """ - `_stream_contributions` of `hus`, or None if nothing is specified - (`approach_contributions` None, or every value None or nan: the default - path). With a value, `T_min_app` must be finite and > 0. + `_stream_contributions` of `hus` (with `units` and `held`), or None if + nothing is specified (`approach_contributions` None, or every value + None or nan: the default path). With a value, `T_min_app` must be + finite and > 0. """ if approach_contributions is None: return None - values = _stream_contributions(hus, approach_contributions, units) + values = _stream_contributions(hus, approach_contributions, units, held) if all(v is None for v in values): return None _approach_contributions(len(values), T_min_app, values) # checks T_min_app return values diff --git a/hensmith/targeting.py b/hensmith/targeting.py index d3e09b0..1a77edd 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -34,7 +34,8 @@ from .hxn_synthesis import (_problem_table, _pinch_cut, _format_H, _arrange_heat_utilities, _process_streams, _explicit_contributions, _contribution_key_level, _key_universe, _unknown_keys, - _unknown_key_hints, _unit_message_name) + _unknown_key_hints, _unit_message_name, _held_owners, _top, + _with_auxiliaries) __all__ = ('PinchTargets', 'supertarget', 'Supertargets', 'network_report', 'plot_composite_curves', 'plot_grand_composite_curve', @@ -310,9 +311,15 @@ def _duty_off_stream(hu): dH = unit.outs[0].H - unit.ins[0].H return abs(dH - hu.unit_duty) > 1e-6 * max(abs(dH), abs(hu.unit_duty)) -def _unit_label(unit): - """``unit.ID``, or ``' - '`` for an auxiliary unit.""" - return unit.ID if unit.owner is unit else f'{unit.owner.ID} - {unit.ID}' +def _unit_label(unit, owners=None): + """``unit.ID``, or ``' - '`` for an auxiliary unit, + with its top owner as keys take it (along + `hensmith.hxn_synthesis._parent` with `owners`, which may make a unit + the owner of an auxiliary it holds without one), and ``owner.ID`` alone + for an auxiliary whose ID is empty.""" + owner = _top(unit, owners) + if owner is unit: return unit.ID + return f'{owner.ID} - {unit.ID}' if unit.ID else owner.ID def _utility_member(u): """The composite member ``(T, Hl, Hr)`` of a non-fuel `UtilityTarget`: @@ -707,7 +714,13 @@ class PinchTargets: a stream of theirs, a key that names only such units then having no effect: for `HeatExchangerNetwork.targets`, every unit available to the network and its nested auxiliary units, which approach - contribution keys may name; else None (settable). + contribution keys may name; for `from_heat_utilities`, its + `key_units` and their nested auxiliary units; else None (settable). + An auxiliary that one of them, or a unit of `units`, holds without + an owner when the targets are made (for `HeatExchangerNetwork.targets`, + at the network's synthesis) stays that unit's for keys and labels, + as the facility takes it (see Notes of `HeatExchangerNetwork`), even + once the unit has rebuilt its auxiliaries. force_ideal_thermo : bool Whether the stream copies use ideal thermodynamics (False when built from streams). @@ -1040,6 +1053,9 @@ def __init__(self, streams_inlet, streams_quenched, is_hot, T_min_app=5., self.key_units = None self.force_ideal_thermo = False self.system = None + #: The holders of auxiliaries held without an owner when the + #: targets were made (see `hensmith.hxn_synthesis._held_owners`). + self._holders = None #: Grid index of the pinch. self._k_pinch = int(np.flatnonzero(table.Ts == table.pinch_T)[0]) #: Threshold of every heat comparison [kJ/hr], the table's own. @@ -1047,7 +1063,8 @@ def __init__(self, streams_inlet, streams_quenched, is_hot, T_min_app=5., @classmethod def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, - force_ideal_thermo=False, sort_hus_by_T=False): + force_ideal_thermo=False, sort_hus_by_T=False, + key_units=None): """ Targets of the process streams behind heat utilities, prepared exactly as `hensmith.hxn_synthesis.synthesize_network` prepares them @@ -1080,13 +1097,24 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, sort_hus_by_T : bool, optional Order the streams by inlet temperature, as the synthesis's option. Defaults to False. + key_units : Iterable[Unit], optional + Further units at hand, such as ``sys.units``: the keys of + `approach_contributions` and of `h` (see `area_target`) may name + them and their nested auxiliary units (the targets' + `key_units`), whether or not they hold a heat utility of `hus`, + as the keys of a `HeatExchangerNetwork` with these units may. A + `biosteam.MultistageCompressor` K1 among them owns the stages it + holds without an owner, so ``'K1'`` covers its intercoolers and + ``('K1', 'K1_H1')`` names one. Without them, only the units of + `hus` and their owners are at hand, and ``'K1_H1'`` is that + intercooler's key. Defaults to None. Returns ------- PinchTargets Streams in stream order (heating utilities, i.e. cold streams, first), labeled by unit (``' - '`` for an - auxiliary unit). + auxiliary unit, with its top owner as keys take it). Raises ------ @@ -1138,7 +1166,9 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, 'they hold its utilities, and the streams it serves ' 'completely have none and are left out; take its targets() ' 'instead', RuntimeWarning) - values = _explicit_contributions(hus, approach_contributions, T_min_app) + if key_units is not None: key_units = _with_auxiliaries(key_units) + values = _explicit_contributions(hus, approach_contributions, T_min_app, + key_units) arranged, N_heating = _arrange_heat_utilities(hus, sort_hus_by_T) if values is None: contributions = None @@ -1146,24 +1176,32 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, value_of = dict(zip(map(id, hus), values)) contributions = [value_of[id(hu)] for hu in arranged] is_hot = [False] * N_heating + [True] * (len(arranged) - N_heating) - return cls._from_exchangers([hu.unit for hu in arranged], is_hot, + holders = _held_owners([hu.unit for hu in hus] + (key_units or [])) + self = cls._from_exchangers([hu.unit for hu in arranged], is_hot, T_min_app, contributions, - force_ideal_thermo, None, arranged) + force_ideal_thermo, None, arranged, + holders) + self.key_units = key_units + return self @classmethod def _from_exchangers(cls, hxs, is_hot, T_min_app, approach_contributions, - force_ideal_thermo, labels=None, heat_utilities=None): + force_ideal_thermo, labels=None, heat_utilities=None, + holders=None): """Targets of the process streams of the exchangers `hxs` (stream order, contributions resolved in stream order), labeled by unit by - default.""" + default, with the holders `holders` of auxiliaries held without an + owner (see `hensmith.hxn_synthesis._held_owners`), as they were when + the contributions were resolved.""" _check_T_min_app(T_min_app) streams_inlet, streams_quenched = _process_streams(hxs, force_ideal_thermo) - if labels is None: labels = [_unit_label(hx) for hx in hxs] + if labels is None: labels = [_unit_label(hx, holders) for hx in hxs] self = cls(streams_inlet, streams_quenched, is_hot, T_min_app, approach_contributions, labels) self.units = list(hxs) self.heat_utilities = None if heat_utilities is None else list(heat_utilities) self.force_ideal_thermo = bool(force_ideal_thermo) + self._holders = holders return self def at(self, T_min_app, scale_contributions=False): @@ -1202,6 +1240,7 @@ def at(self, T_min_app, scale_contributions=False): new.key_units = self.key_units new.force_ideal_thermo = self.force_ideal_thermo new.system = self.system + new._holders = self._holders return new def __repr__(self): @@ -1776,13 +1815,14 @@ def _film_mapping(self, h): it the same value. Unit keys may also name `key_units` (and their owners) and the auxiliaries that units may build but do not hold now, as approach contribution keys of the facility may, and then - have no effect. An auxiliary that a unit at hand holds without an - owner is that unit's (see `hensmith.hxn_synthesis._key_universe`). + have no effect. An auxiliary that a unit at hand held without an + owner when the targets were made, or holds so now, is that unit's + (see `hensmith.hxn_synthesis._key_universe`). """ N = self.is_hot.size units = self.units or () universe, live, owners = _key_universe( - [*units, *(self.key_units or ())]) + [*units, *(self.key_units or ())], self._holders) items = [] unknown = [] for key, value in h.items(): @@ -2513,7 +2553,8 @@ def _check_targets(HXN, targets): s_out = _copy(s_out, s_out.thermo.ideal()) if not _same_ends(c, s_in, s_out): raise ValueError(f'targets are not of the current states of ' - f'{_unit_label(hx)} (simulated since?): make ' + f'{_unit_label(hx, targets._holders)} ' + '(simulated since?): make ' 'them again with HXN.targets()') def _check_network(HXN, targets): diff --git a/tests/test_hxn_contributions.py b/tests/test_hxn_contributions.py index bd9368e..d60a6fe 100644 --- a/tests/test_hxn_contributions.py +++ b/tests/test_hxn_contributions.py @@ -1170,6 +1170,9 @@ def used(x): HXN.approach_contributions = None simulate_strictly(sys) targets = HXN.targets() + # labeled by owner, the heater of an empty ID by its owner's alone + assert dict(zip(targets.units, targets.labels)) == { + hx: 'T1', U.hxs[0]: 'U - U_hx0', U.hxs[1]: 'U - U_hx1', H1: 'H1'} area = targets.area_target() assert targets.area_target(h={('T1', ''): .5}) != area assert targets.area_target(h={('T1', ''): .5}) == targets.area_target( @@ -1310,6 +1313,80 @@ def used(n, x): with pytest.raises(ValueError, match=r"name no unit: \('K1', 'K1_H3'\)"): hxn_synthesis._stream_contributions(hus, {('K1', 'K1_H3'): 2.}) +def test_targets_keep_the_holders_of_stages_rebuilt_since(): + # targets keep the holders of the stages that a MultistageCompressor + # holds without an owner as they were when they were made (those of + # HXN.targets() as at the synthesis): stored targets of one sample keep + # 'K1' and ('K1', 'K1_H1') on their intercoolers, and their labels, + # after K1 rebuilds its stages in the next sample or is simulated alone + bst.main_flowsheet.set_flowsheet('contributions_kept_holders') + bst.settings.set_thermo(['Water', 'Methane'], cache=True) + gas = bst.Stream('gas', Methane=100., units='kmol/hr', T=300., + P=101325., phase='g') + K1 = bst.MultistageCompressor('K1', ins=gas, pr=2., n_stages=3, eta=0.7) + cold = bst.Stream('cold', Water=200., units='kmol/hr', T=290., P=5e5) + C1 = bst.HXutility('C1', ins=cold, T=350., rigorous=False) + HXN = HeatExchangerNetwork('HXN', T_min_app=5., + approach_contributions={'K1': 2.}) + sys = bst.System.from_units('contributions_kept_holders', + units=[K1, C1, HXN]) + keys = ({'K1': .5}, {('K1', 'K1_H1'): .5}, {('K1', 'K1_H3'): .5}) + + def results(targets): + return (targets.labels, targets.approach_contributions, + [targets.area_target(h=h) for h in keys], + targets.at(10.).area_target(h=keys[0])) + + stored = [] + for pr in (2., 2.5, 3., 2.2): + K1.pr = pr + simulate_strictly(sys) + hxs = list(K1.hxs) + targets = HXN.targets() + assert dict(zip(targets.units, targets.labels)) == { + C1: 'C1', **{hx: f'K1 - {hx.ID}' for hx in hxs}} + first = results(targets) + area = targets.area_target() + assert all(a != area for a in first[2]) + stored.append((targets, first, hxs)) + for targets, first, hxs in stored: + assert all(getattr(hx, '_owner', None) is None for hx in hxs) + assert results(targets) == first + # K1 simulated alone rebuilds its stages: the facility's targets keep + # the contributions the network was synthesized with + info = HXN.synthesis_info['approach_contributions'].tolist() + old = list(K1.hxs) + K1.pr = 2.5 + K1.simulate() + assert not any(hx in old for hx in K1.hxs) + targets = HXN.targets() + assert [2.5 if c is None else c + for c in targets.approach_contributions] == info == [ + 2.5, 2., 2., 2.] + assert targets.area_target(h={'K1': .5}) != targets.area_target() + # outside the facility, key_units put the compressor at hand + simulate_strictly(sys) + hus = bst.process_tools.heat_exchanger_utilities_from_units(sys.units) + hus.sort(key=lambda hu: hu.duty) # in the facility's order + for x in ({'K1': 2.}, {K1: 2.}, {('K1', 'K1_H1'): 2., 'K1': 2.}): + targets = PinchTargets.from_heat_utilities(hus, 5., x, + key_units=sys.units) + assert targets.approach_contributions == (None, 2., 2., 2.) + assert targets.key_units == HXN._candidate_units(sys.units) + assert targets.labels == HXN.targets().labels + assert targets.area_target(h={'K1': .5}) == HXN.targets( + ).area_target(h={'K1': .5}) + with pytest.raises(ValueError, match="name no unit: Date: Sat, 17 Oct 2026 21:46:49 +0000 Subject: [PATCH 38/52] address review 8 (f1-facility): walk nested auxiliaries without biosteam's nested_auxiliary_units, label callable values by their facility keys F1F8-1 (fixed): the facility's candidate units (every fresh synthesis, including the default path, which takes the holders of owner-less stages for HXN.targets() labels), HXN.targets() and PinchTargets.from_heat_utilities(key_units=...) went through biosteam.Unit.nested_auxiliary_units, which fails on an auxiliary of an auxiliary that is not a unit (the VacuumSystem of a flash drum under vacuum held by a custom unit) and recurses without end on units that hold each other. hxn_synthesis._with_auxiliaries now walks the auxiliaries itself (_nested_auxiliaries): it reads the auxiliaries of biosteam.Unit objects only, lists each unit once and goes into each unit at most once, in the order of nested_auxiliary_units (checked identical, unit by unit, on BinaryDistillation and ShortcutColumn under vacuum, MultistageCompressor, MultiEffectEvaporator, vacuum Flash and the test units TwoFlash, NestedHeaters and ListedHeaters). The holders stay computed on the default path (the walk can no longer fail there): without them, HXN.targets() after a compressor rebuilt its stages would label them 'K1_H1' instead of 'K1 - K1_H1'. New test_auxiliaries_that_hold_auxiliaries_that_are_not_units: a VacuumTrain unit in the facility gives, by default, the network (network_record) and targets of the same streams with the drum as a unit of its own, takes contributions by unit, tuple, heater and callable, and gives targets before (units option) and after a synthesis and from from_heat_utilities(key_units=...); test_held_owners_skip_loops_and_ take_the_first_holder checks _with_auxiliaries ends on mutual holders. Both fail without the fix. F1F8-2 (fixed, nit): a bad value of a callable now names its unit with the owners a mapping's keys take (_held_owners of the heat utilities' units, the given units and held), so a facility intercooler is ('K1', 'K1_H2') as in the mapping path; tested in the facility and in _stream_contributions with and without units. Tests: tests/test_hxn_contributions.py with the doctests of hensmith/hxn_synthesis.py and hensmith/_heat_exchanger_network.py: 57 passed; tests/test_targeting.py with hensmith/targeting.py doctests: 81 passed. Oracle: 29 cases, sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (unchanged). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- hensmith/_heat_exchanger_network.py | 5 +- hensmith/hxn_synthesis.py | 57 +++++++++++++++++--- tests/test_hxn_contributions.py | 84 +++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 9 deletions(-) diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index f383255..413dccb 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -725,7 +725,10 @@ def _cost(self): # synthesis passes: smallest heating duty first among the cold # streams, largest cooling duty first among the hot streams. # The holders of the auxiliaries held without an owner, as - # the contributions were resolved by (see `targets`). + # the contributions were resolved by (see `targets`, which + # labels by them with or without contributions; the walk + # reads auxiliaries of units only, see + # `hensmith.hxn_synthesis._nested_auxiliaries`). holders = _held_owners([hu.unit for hu in hx_utils] + self._candidate_units(units)) hx_utils.sort(key = lambda x: x.duty) diff --git a/hensmith/hxn_synthesis.py b/hensmith/hxn_synthesis.py index a3231ff..638c1f8 100644 --- a/hensmith/hxn_synthesis.py +++ b/hensmith/hxn_synthesis.py @@ -15,7 +15,7 @@ cycles (`StreamLifeCycle`) and pinch diagrams (`plot_pinch_diagram`). """ from collections import namedtuple -from collections.abc import Mapping, Sequence +from collections.abc import Iterable, Mapping, Sequence import heapq import math import re @@ -893,16 +893,54 @@ def _owner_chains(units, owners=None): unit = _parent(unit, owners) return list(found.values()) +def _nested_auxiliaries(unit): + """ + The `biosteam.Unit` objects that `unit` holds as auxiliary units, at + any depth, in the order of `biosteam.Unit.nested_auxiliary_units`. + Unlike that property, this reads the auxiliaries of units only (an + object that is not one, such as the `biosteam.VacuumSystem` of a flash + drum under vacuum, has none, and the property fails on it one level + down), and it takes each unit once, never `unit` itself, and walks + into each unit at most once (so units that hold each other end it). + """ + nested = [] + found = {id(unit)} # the units listed (and `unit`) + walked = set() # the units `walk` went into + + def add(aux): + if isinstance(aux, bst.Unit) and id(aux) not in found: + found.add(id(aux)) + nested.append(aux) + + def walk(holder): + # as `nested_auxiliary_units`: each attribute of `holder` (a unit or + # a list of them) before the auxiliaries of its units, and the + # auxiliaries of those that are units each before its own + walked.add(id(holder)) + for name in getattr(holder, 'auxiliary_unit_names', ()): + held = getattr(holder, name, None) + if held is None: continue + group = list(held) if isinstance(held, Iterable) else [held] + for aux in group: add(aux) + for aux in group: + if not isinstance(aux, bst.Unit): continue + for sub in getattr(aux, 'auxiliary_units', ()): + add(sub) + if isinstance(sub, bst.Unit) and id(sub) not in walked: + walk(sub) + + walk(unit) + return nested + def _with_auxiliaries(units): """Each unit of `units` followed by its nested auxiliary units (those - that are `biosteam.Unit` objects): the units that keys may name, as - `HeatExchangerNetwork` takes those available to it, whether or not - they hold a heat utility.""" + that are `biosteam.Unit` objects, see `_nested_auxiliaries`): the units + that keys may name, as `HeatExchangerNetwork` takes those available to + it, whether or not they hold a heat utility.""" candidates = [] for unit in units: candidates.append(unit) - candidates.extend([i for i in unit.nested_auxiliary_units - if isinstance(i, bst.Unit)]) + candidates.extend(_nested_auxiliaries(unit)) return candidates def _key_universe(units, held=None): @@ -1150,8 +1188,11 @@ def _stream_contributions(hus, approach_contributions, units=None, return [_contribution_value(v, f'heat utility {k}') for k, v in enumerate(values)] if callable(x): - return [_contribution_value(x(hu), - f'unit {_unit_message_name(hu.unit)}') + # labeled with the owners the keys of a mapping would take + owners = _held_owners([hu.unit for hu in hus] + list(units or ()), + held) + return [_contribution_value( + x(hu), f'unit {_unit_message_name(hu.unit, owners)}') for hu in hus] raise TypeError('approach_contributions must be a mapping, a sequence or ' f'a callable, not {type(x).__name__!r}') diff --git a/tests/test_hxn_contributions.py b/tests/test_hxn_contributions.py index d60a6fe..d8a432f 100644 --- a/tests/test_hxn_contributions.py +++ b/tests/test_hxn_contributions.py @@ -1294,6 +1294,12 @@ def used(n, x): HXN.approach_contributions = {key: 3.} with pytest.raises(ValueError, match='name no unit'): sys.simulate() + # a bad value of a callable names a stage by its key in the facility, + # as a mapping does (K1 holds its stages without an owner) + HXN.approach_contributions = lambda hu: (-1. if hu.unit.ID == 'K1_H2' + else None) + with pytest.raises(ValueError, match=r"unit \('K1', 'K1_H2'\) must be"): + sys.simulate() # outside the facility, only the heat utilities' units are at hand: the # intercoolers have no owner, and the messages say so hus = bst.process_tools.heat_exchanger_utilities_from_units([K1, C1]) @@ -1312,6 +1318,12 @@ def used(n, x): # a key of a stage beyond n_stages is no key of a unit not at hand with pytest.raises(ValueError, match=r"name no unit: \('K1', 'K1_H3'\)"): hxn_synthesis._stream_contributions(hus, {('K1', 'K1_H3'): 2.}) + # a callable's bad value names a stage by its key with the units given + bad = lambda hu: -1. if hu.unit.ID == 'K1_H2' else None + for units, name in ((None, 'K1_H2'), ([K1], 'K1')): + with pytest.raises(ValueError, match=rf"unit \('{name}', 'K1_H2'\) " + r"must be"): + hxn_synthesis._stream_contributions(hus, bad, units) def test_targets_keep_the_holders_of_stages_rebuilt_since(): # targets keep the holders of the stages that a MultistageCompressor @@ -1425,8 +1437,80 @@ def _run(self): pass first.ID, unit, live, owners) == 1 assert hxn_synthesis._contribution_key_level( second.ID, C, live, owners) is None + # the candidate units end too (biosteam's nested_auxiliary_units + # recurses without end), each unit once under each holder + assert hxn_synthesis._with_auxiliaries([first, second]) == [ + first, second, C, second, first, C] for unit in (A, B, C): assert getattr(unit, '_owner', None) is None +class VacuumTrain(bst.Unit): + """An auxiliary flash drum under vacuum, which holds its + `biosteam.VacuumSystem` (an auxiliary that is not a unit) as an + auxiliary: biosteam's ``nested_auxiliary_units`` fails on the train.""" + _N_ins = 1 + _N_outs = 2 + auxiliary_unit_names = ('flash',) + + def _init(self): + self.auxiliary('flash', bst.Flash, ins=self.ins[0], outs=self.outs, + P=50000., V=0.3) + + def _run(self): self.flash.simulate() + def _design(self): pass + def _cost(self): pass + +def test_auxiliaries_that_hold_auxiliaries_that_are_not_units(): + # a unit whose auxiliary holds an auxiliary that is not a unit: the + # facility, by default (the network of the same streams with the drum + # as a unit of its own, as before contributions) or with contributions, + # and its targets, before and after a synthesis, take the drum's heater + def build(name, nested): + bst.main_flowsheet.set_flowsheet(name) + bst.settings.set_thermo(['Water', 'Ethanol'], cache=True) + feed = bst.Stream('feed', Water=900., Ethanol=100., units='kmol/hr', + T=300.) + hot = bst.Stream('hot', Water=500., units='kmol/hr', T=420., P=1e6) + U1 = (VacuumTrain('U1', ins=feed) if nested + else bst.Flash('U1', ins=feed, P=50000., V=0.3)) + H1 = bst.HXutility('H1', ins=hot, T=320., rigorous=False) + HXN = HeatExchangerNetwork('HXN', T_min_app=5.) + return U1, H1, HXN, bst.System.from_units(name, units=[U1, H1, HXN]) + + U0, *_, HXN0, sys0 = build('contributions_vacuum_top', False) + simulate_strictly(sys0) + U1, H1, HXN, sys = build('contributions_vacuum', True) + U1.simulate() + H1.simulate() + heater = U1.flash.heat_exchanger + assert isinstance(U1.flash.vacuum_system, bst.VacuumSystem) + with pytest.raises(AttributeError, match='nested_auxiliary_units'): + U1.nested_auxiliary_units + assert HXN._candidate_units([U1, H1]) == [U1, U1.flash, heater, H1] + reference = HXN0.targets() + before = HeatExchangerNetwork('HXN_units', T_min_app=5., + units=[U1, H1]).targets() + assert before.labels == ['U1 - heat_exchanger', 'H1'] == reference.labels + assert (before.Q_hot, before.Q_cold) == (reference.Q_hot, reference.Q_cold) + simulate_strictly(sys) + assert [hx.ID for hx in HXN.original_heat_exchangers] == [ + 'heat_exchanger', 'H1'] + assert network_record(HXN) == network_record(HXN0) + after = HXN.targets() + assert after.labels == reference.labels + assert (after.Q_hot, after.Q_cold) == (reference.Q_hot, reference.Q_cold) + for x in ({'U1': 2.}, {('U1', 'flash'): 2.}, {heater: 2.}, + lambda hu: 2. if hu.unit is heater else None): + HXN.approach_contributions = x + simulate_strictly(sys) + assert HXN.synthesis_info['approach_contributions'].tolist() == [ + 2., 2.5] + assert HXN.targets().approach_contributions == (2., None) + hus = bst.process_tools.heat_exchanger_utilities_from_units([U1, H1]) + targets = PinchTargets.from_heat_utilities(hus, 5., {('U1', 'flash'): 2.}, + key_units=[U1, H1]) + assert targets.approach_contributions == (2., None) + assert targets.key_units == [U1, U1.flash, heater, H1] + def test_contribution_keys_with_the_units_option(): # keys may name the units of the units option and their auxiliary units, # idle or not, but no other unit; a callable option is called once per From 98347225a6c57bd3ef7f0f22da131f1622219cb7 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 21:58:29 +0000 Subject: [PATCH 39/52] address review 8 (tg-core): cap a sensible agent's level at a stream ending at its zero-margin temperature TGC8-1 (fixed): with approach contributions, a stream could end exactly at agent.T - dT real (agent.T + dT for cooling) of a sensible agent with agent.dT = 0 while another stream set the level higher (lower). That end then lay inside the agent's stretch with heat already given above it, so _line_limit bound the outlet at agent.T and biosteam divided by a zero enthalpy change (FloatingPointError under the errstate biosteam sets; nan cost and infinite flow otherwise), taking down utility_targets, summary, the balanced curves, area/units/capital targets, supertarget, network_report and the plots. PinchTargets._level now treats such a stream in reach as capping the level at that end for a sensible agent, as a stream reaching beyond its limit already does (the same zero-margin test _short_of_stuck_loads applies to point loads): the contact is then at the level, where the agent has given nothing and keeps dT. The test is a no-op for agents with agent.dT > 0 (biosteam's defaults) and without contributions (every stream of a kind then shares the limit, which is already the level), so no documented number changes. Class Notes, the _level docstring and concepts.rst describe the rule. The suggested backstop in _line_limit/_utility is not added: after the fix a zero-margin contact can only lie at the level (x = 0), and choosing another level or agent at pricing time would duplicate the allocation; the fuzzing below found no case left. New test test_sensible_line_stream_ending_without_driving_force covers both mirrors (hot oil at 480 K, coolant at 300 K, dT 0): level at the stream's end, the rest to the next agent, finite flow and cost equal to an independent pricing, line gaps {9, dT}, finite summary utility cost. It raises FloatingPointError on the previous code. Tests: pytest hensmith/targeting.py tests/test_targeting.py: 82 passed. Synthesis untouched; the oracle still prints 29 dcd15864a700a58d...691bf. Constant-CP fuzzing with an oracle updated for the rule: 1,200 random cases (seeds 30-37, including the reviewer's seed 32 case 14) and 720 round-valued cases aimed at dT-0 agents' zero-margin temperatures: no exception, no non-finite cost or flow, no line gap below dT (the round set raised FloatingPointError in 71 of 240 cases before the fix). sphinx is not installed here; the doc changes are prose with literals only. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/concepts.rst | 5 +++ hensmith/targeting.py | 28 ++++++++++++++--- tests/test_targeting.py | 67 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 5 deletions(-) diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 7c58086..0657799 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -441,6 +441,11 @@ line would have no driving force left there: with ``agent.dT`` of 0 (``UtilityAgent``'s default), it could serve a point load at its limit only at its own inlet temperature, at an unbounded flow, so its level stays just short of that point load, which the next agent serves, as in BioSTEAM. +For the same reason, with approach contributions, a stream that ends at +such an agent's limit bounds its level at that end, as a stream reaching +beyond its limit does: were another stream to set the level beyond that +end, the agent, having given heat above it (taken it below, for cooling), +would have to leave at its inlet temperature there. The order is BioSTEAM's preference, not price: the utility cost target is what the agents cost placed this way, not the least utility cost, and an agent later in the list can be cheaper per kJ (with BioSTEAM's default diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 1a77edd..22f2e65 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -784,6 +784,12 @@ class PinchTargets: ``agent.dT`` is 0 (`UtilityAgent`'s default), the agent would leave at its inlet temperature at an unbounded flow, so its level stays just short of that point load, which the next agent serves, as in biosteam. + For the same reason a stream that ends at that temperature bounds the + level of such an agent at that end, as one reaching beyond its limit + does: were another stream (of another approach contribution) to set the + level beyond that end, the agent would have given heat above it (taken + it below, for cooling) and its line would reach its inlet temperature + there; at the level it has given none. Each agent is priced as one biosteam heat utility over the stretch it serves, from its level (or the end of the grid, if that is nearer) to @@ -1328,7 +1334,9 @@ def _level(self, agent, heating, j, reach): which those of the mask `reach` (`_in_reach`) are in its reach: the highest (lowest for cooling), up to the highest of the shifted limits of `j` (down to the lowest), that serves no stream in reach beyond - its own real limit (see Notes of the class). + its own real limit nor, if the agent is sensible, one at ``agent.T - + dT`` real (``agent.T + dT`` for cooling) anywhere but at the level + (see Notes of the class). """ dT = bst.HeatUtility.dT grid = self.grid @@ -1342,20 +1350,30 @@ def _level(self, agent, heating, j, reach): # a stream in reach reaching beyond its limit caps the level at the # limit, or, lying wholly beyond it, at its own end (just short of it # if a point load is there): a level serves only what lies below it - # (above it, for cooling) + # (above it, for cooling). So does one ending at its limit, for a + # sensible agent whose dT is 0 (the stream's end at agent.T - dT + # real; agent.T + dT for cooling): the agent, having given heat above + # that end (taken it below), would have to leave at its inlet + # temperature there, at an unbounded flow (see `_line_limit`); at the + # level, where it has given none, it keeps dT + sensible = _sensible(agent) if heating: R = agent.T - agent.dT - dT limit = R - s - caps = np.maximum(limit, bottom - _T_SHORT * point)[reach & (top > limit)] + beyond = top > limit + if sensible: beyond |= top + s >= agent.T - dT - _T_EQ + caps = np.maximum(limit, bottom - _T_SHORT * point)[reach & beyond] if j.size: L = caps.min(initial=limit.max()) else: L = R else: R = agent.T + agent.dT + dT limit = R - s - caps = np.minimum(limit, top + _T_SHORT * point)[reach & (bottom < limit)] + beyond = bottom < limit + if sensible: beyond |= bottom + s <= agent.T + dT + _T_EQ + caps = np.minimum(limit, top + _T_SHORT * point)[reach & beyond] if j.size: L = caps.max(initial=limit.min()) else: L = R - self.T_min_app - if reach.any() and _sensible(agent): + if reach.any() and sensible: L = self._short_of_stuck_loads(agent, heating, L, j[reach], s[reach]) return float(L) diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 20417ef..5f52874 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -884,6 +884,73 @@ def test_sensible_line_streams_of_other_shifts(): rel=1e-12) assert line_gaps(t, rows, u) == pytest.approx({'H1': dT, 'H2': dT}, abs=1e-9) +def test_sensible_line_stream_ending_without_driving_force(): + # with approach contributions a stream can end at a sensible agent's + # zero-margin temperature, agent.T - dT (its limit when agent.dT is 0), + # while another stream sets the level higher: the agent, having given + # heat above that end, would have to leave at its inlet temperature + # there (biosteam would divide by a zero enthalpy change). That end + # caps the level, as a stream reaching beyond its limit does. A hot oil + # (480 K, dT 0) serves C1 (390 -> 480 K, shift -4 K, limit 479 K + # shifted) and C2 (400 -> 475 K, shift 0, limit 475 K, its end): it is + # placed at 475 K and natural gas serves the 9 kW of C1 above it. + dT, water = bst.HeatUtility.dT, bst.HeatUtility.thermo_water + rows = [('C1', 390., 480., 1.), ('C2', 400., 475., 1.)] + t = targets('test_targeting_line_end_heat', rows, 10., + approach_contributions={'C1': 9.}) + shift = dict(zip(t.labels, t.shift)) + assert (shift['C1'], shift['C2']) == (-4., 0.) + oil = bst.UtilityAgent('test_targeting_hot_oil_end', Water=1, T=480., P=5e6, + phase='l', thermo=water, T_limit=300., + heat_transfer_price=1e-6) + assert oil.dT == 0. and 475. == oil.T - dT + NG = agent('natural_gas') + u, gas = t.utility_targets([oil, NG], []) + assert (u.ID, gas.ID) == (oil.ID, NG.ID) + assert u.level == 475. + assert (u.T_in, u.T_out) == (pytest.approx(390., abs=1e-9), 475.) + q = 156. * kW + assert u.duty == pytest.approx(q, rel=1e-12) + assert gas.duty == pytest.approx(9. * kW, rel=1e-12) + # bounded where C2 starts (shifted 400 K), having given 150 kW above it + T_out = oil.T - (oil.T - dT - 400.) * q / (150. * kW) + assert u.T_utility_out == pytest.approx(T_out, rel=1e-12) + assert math.isfinite(u.flow) and math.isfinite(u.cost) + assert u.cost == pytest.approx(fresh_price(u.duty, T_out - dT, u.T_out, oil)[0], + rel=1e-12) + assert line_gaps(t, rows, u) == pytest.approx({'C1': 9., 'C2': dT}, abs=1e-9) + assert math.isfinite(t.summary(heating_agents=[oil, NG], + cooling_agents=[])['utility_cost']) + # its mirror: a coolant (300 K, dT 0) serves H1 (390 -> 300 K, shift 14 + # K, limit 291 K shifted) and H2 (380 -> 305 K, shift 10 K, limit 295 + # K, its end): it is placed at 295 K and chilled water serves the 9 kW + # of H1 below it + rows = [('H1', 390., 300., 1.), ('H2', 380., 305., 1.)] + t = targets('test_targeting_line_end_cool', rows, 10., + approach_contributions={'H1': 9.}) + shift = dict(zip(t.labels, t.shift)) + assert (shift['H1'], shift['H2']) == (14., 10.) + coolant = bst.UtilityAgent('test_targeting_coolant_end', Water=1, T=300., + P=101325., phase='l', thermo=water, T_limit=385., + heat_transfer_price=1e-6) + assert coolant.dT == 0. and 305. == coolant.T + dT + CHW = agent('chilled_water') + u, chilled = t.utility_targets([], [coolant, CHW]) + assert (u.ID, chilled.ID) == (coolant.ID, CHW.ID) + assert u.level == 295. + assert (u.T_in, u.T_out) == (pytest.approx(390., abs=1e-9), 305.) + assert u.duty == pytest.approx(q, rel=1e-12) + assert chilled.duty == pytest.approx(9. * kW, rel=1e-12) + # bounded where H2 starts (shifted 370 K), having taken 150 kW below it + T_out = coolant.T + (380. - dT - coolant.T) * q / (150. * kW) + assert u.T_utility_out == pytest.approx(T_out, rel=1e-12) + assert math.isfinite(u.flow) and math.isfinite(u.cost) + assert u.cost == pytest.approx(fresh_price(-u.duty, T_out + dT, u.T_out, coolant)[0], + rel=1e-12) + assert line_gaps(t, rows, u) == pytest.approx({'H1': 9., 'H2': dT}, abs=1e-9) + assert math.isfinite(t.summary(heating_agents=[], + cooling_agents=[coolant, CHW])['utility_cost']) + def test_pricing_temperatures(): dT = bst.HeatUtility.dT # the hot stream's top, 445 K shifted, is above every cold stream: medium From b6c187b110d83380a0da87f61f77d7ae8084b460 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 22:14:48 +0000 Subject: [PATCH 40/52] address review 8 (tg-report): put the unserved note under the x axis, raise on stages rebuilt since the last simulation TGR8-1 (fixed): plot_composite_curves(kind='balanced') wrote the note on an unserved remainder ('Utility:unserved') at the upper left corner of the axes, where the label placer, unaware of it, could stack or lift labels (and where 'best' then pushed the legend elsewhere). The note is not a label of anything drawn, so it now leaves the label area: an annotation under the x axis, left-aligned with the axes, placed at draw time below the axes and the visible x axis decorations (tick labels, label, offset text) by a picklable `_Under` coordinate class. A constrained layout (the default figure) and bbox_inches='tight' keep it in the figure. Docstring of plot_composite_curves (Notes and `ax`) updated. test_plot_readability now asserts the note is inside the figure, under the axes and the x axis decorations, and clear of every label and of the legend: REF_A with low pressure steam and with cooling water only at the default size, REF_A with cooling water only at (4.5, 4.2), and the reviewer's three-stream case with LP/HP steam at the default size (it fails on the old code). The legend may still cover a label at (4.5, 4.2) or smaller in the REF_A case: there every one of matplotlib's 'best' candidates overlaps a label or crosses a curve, as before this change; that is the docstring's "covers the least", not the note. test_plot_labels_placed_once required at least one placement pass on a repeated draw, which held only through a 1-ulp change of the axes height between draws; it now requires 1-4 passes at the first draw and at most 4 at the next (none when the layout is the same to the bit, as it now is). The served case is untouched, so no docs figure changes (the ch05 balanced figure has no unserved utility). TGR8-2 (fixed): network_report checked the network only against HXN.original_heat_exchangers, whose detached former stages (a MultistageCompressor simulated alone after a change of pr rebuilds its intercoolers) keep consistent former states, so it reported stale streams without error. _check_network (both the default and the explicit targets paths) now raises the "changed since the facility's last simulation?: simulate the system" kind of ValueError, naming the stream by its label, when an original exchanger is no longer among the facility's units and their nested auxiliaries (HXN._candidate_units()). Checked free of false positives after a system simulation for Flash, BinaryDistillation, MultiEffectEvaporator, MultistageCompressor and plain units. Raises section of network_report and the Notes of HXN.targets (which keeps the former stages until the system is simulated, as test_targets_keep_the_holders_of_ stages_rebuilt_since requires) updated. New test test_network_report_rebuilt_stages (fails on the old code with "DID NOT RAISE"). Tests: tests/test_targeting.py (all 77) plus tests/test_hxn_contributions.py::test_targets_keep_the_holders_of_stages_rebuilt_since: 78 passed; doctests of hensmith/targeting.py and hensmith/_heat_exchanger_network.py: 8 passed; oracle sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (29 cases) unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- hensmith/_heat_exchanger_network.py | 7 ++- hensmith/targeting.py | 69 ++++++++++++++++----- tests/test_targeting.py | 94 ++++++++++++++++++++++++++++- 3 files changed, 150 insertions(+), 20 deletions(-) diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index 413dccb..7b9ad50 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -1059,7 +1059,12 @@ def targets(self, T_min_app=None): would take, prepared as the synthesis prepares them, and a callable is called once per heat utility. Nothing is stored: with `cache_network`, the targets follow the - units' current states, not those of the network's synthesis. + units' current states, not those of the network's synthesis. A + unit that rebuilds its auxiliaries when simulated alone (K1 above, + after a change of its `pr`) leaves the streams on its former + stages, which are no longer in the process and keep their former + states, until the system is simulated again + (`hensmith.targeting.network_report` raises meanwhile). Examples -------- diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 22f2e65..e8f7775 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -2576,11 +2576,24 @@ def _check_targets(HXN, targets): 'them again with HXN.targets()') def _check_network(HXN, targets): - """Raise a ValueError unless every stream of `HXN`'s network enters and - leaves it at the end states of its curve in `targets` (which are of the - units' current states): its copy of the real inlet where it enters the - network, and the outlet of its last stage (the network's copies share - the curves' thermodynamic basis).""" + """Raise a ValueError unless every stream of `HXN`'s network is that of + an original exchanger still among the facility's units and their + nested auxiliaries (not one that its unit has rebuilt since, as a + `biosteam.MultistageCompressor` simulated alone after a change of `pr` + rebuilds its intercoolers, whose former objects keep their former, + consistent states), and enters and leaves the network at the end states + of its curve in `targets` (which are of the units' current states): its + copy of the real inlet where it enters the network, and the outlet of + its last stage (the network's copies share the curves' thermodynamic + basis).""" + at_hand = {id(unit) for unit in HXN._candidate_units()} + for hx, label in zip(HXN.original_heat_exchangers, targets.labels): + if id(hx) not in at_hand: + raise ValueError(f'the network of {HXN.ID} is of {label}, which is ' + "no longer one of the facility's units nor an " + 'auxiliary of one (rebuilt since the ' + "facility's last simulation?): simulate the " + 'system') for life_cycle, c, label in zip(HXN.stream_life_cycles, targets.curves, targets.labels): entry = life_cycle.entry @@ -2706,8 +2719,11 @@ def network_report(HXN, targets=None, **options): If `targets` are not those of the facility's process streams in their current states (enthalpies at both ends, and pressure and flows at the inlet), if the network's streams do not enter and - leave it at those states (a unit or a stream changed since the - facility's last simulation), or for an invalid option. + leave it at those states or are of exchangers no longer among the + facility's units and their auxiliaries (a unit or a stream changed + since the facility's last simulation: a + `biosteam.MultistageCompressor` simulated alone after a change of + `pr`, say, rebuilds its intercoolers), or for an invalid option. TypeError For an unknown option. @@ -2992,6 +3008,22 @@ def __call__(self, renderer): from matplotlib.transforms import Affine2D return Affine2D().translate(*self.labels.spots(renderer)[self.index][0]) +class _Under: + """ + The y `xycoords` of a note under the axes `ax`: the display box of the + axes and of the visible decorations of its x axis (tick labels, label + and offset text) as drawn, so that the note keeps below them at any + size (a class, so that the figure can still be pickled). + """ + def __init__(self, ax): + self.ax = ax + + def __call__(self, renderer): + from matplotlib.transforms import Bbox + ax = self.ax + box = ax.xaxis.get_tightbbox(renderer) + return ax.bbox if box is None else Bbox.union([ax.bbox, box]) + class _Leader(Patch): """ A thin line from the anchor of the label `index` of the `_Labels` @@ -3260,7 +3292,8 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, default agents. ax : matplotlib.axes.Axes, optional Axes to draw on; a new figure (with a constrained layout, which - keeps the axis labels in it) is created if not given. + keeps the axis labels and a note under the x axis in it) is created + if not given. file : str, optional If given, the figure is saved to this path. dpi : int, optional @@ -3282,10 +3315,11 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, (whose empty composite stays out of the legend). Fuel, at infinite temperature on the balanced curves, is labeled rather than drawn, above the hot end of the cold balanced composite (at the top of both), - whose top its heat closes, and so is an unserved remainder - ('Utility:unserved', in the upper left corner): the cold balanced - composite starts at the unserved cooling, and the hot one ends short of - the cold one by the unserved heating. A label of a latent agent sits by + whose top its heat closes, and so is an unserved remainder, in a note + under the x axis ('Utility:unserved', clear of the curves, of their + labels and of the legend): the cold balanced composite starts at the + unserved cooling, and the hot one ends short of the cold one by the + unserved heating. A label of a latent agent sits by its flat, above left of the warm end of a heating one and below right of the cold end of a cooling one, and a sensible agent's off the middle of its line, above left for heating and below right for cooling. When @@ -3430,10 +3464,13 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, placement.add(annotation, (-2, 4), leader='Leader:' + u.ID) for annotation, offset, ID in cooling: placement.add(annotation, offset, leader='Leader:' + ID) - if unserved: # the curves end apart: say why - ax.text(0.02, 0.97, '\n'.join(unserved), transform=ax.transAxes, - ha='left', va='top', color='0.45', gid='Utility:unserved', - **text_kwargs) + if unserved: + # the curves end apart: say why, in a note under the x axis, + # clear of the curves, of their labels and of the legend + ax.annotate('\n'.join(unserved), xy=(0., 0.), + xycoords=('axes fraction', _Under(ax)), xytext=(0, -4), + textcoords='offset points', ha='left', va='top', + color='0.45', gid='Utility:unserved', **text_kwargs) # headroom for the labels above the top of the curves, and more # where they need it to stack in columns ax.margins(y=0.16 if stacked else 0.1) diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 5f52874..2dbeba7 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -2571,6 +2571,46 @@ def test_network_report_current_states(): with pytest.raises(ValueError, match='targets are not of the current states of H1'): network_report(HXN, targets) +def test_network_report_rebuilt_stages(): + # A MultistageCompressor simulated alone after a change of pr rebuilds + # its intercoolers: the network is of the former ones, no longer in the + # process, whose states are consistent but stale; the report raises (by + # default and with explicit targets) until the system is simulated + bst.main_flowsheet.set_flowsheet('test_targeting_report_rebuilt') + bst.settings.set_thermo(['Water', 'Methane'], cache=True) + gas = bst.Stream('gas', Methane=100., units='kmol/hr', T=300., + P=101325., phase='g') + K1 = bst.MultistageCompressor('K1', ins=gas, pr=2., n_stages=3, eta=0.7) + cold = bst.Stream('cold', Water=200., units='kmol/hr', T=290., P=5e5) + C1 = bst.HXutility('C1', ins=cold, T=350., rigorous=False) + HXN = HeatExchangerNetwork('HXN', T_min_app=5.) + sys = bst.System.from_units('test_targeting_report_rebuilt', + units=[K1, C1, HXN]) + simulate_strictly(sys) + report = network_report(HXN) + former = list(K1.hxs) + assert set(HXN.original_heat_exchangers) == {C1, *former} + assert report['targets']['Q_hot'] > 0. + K1.pr = 3. + K1.simulate() + assert not set(K1.hxs) & set(former) + targets = HXN.targets() # still of the former stages, labeled by K1 + assert set(targets.units) == {C1, *former} + message = (r"the network of HXN is of K1 - K1_H\d, which is no longer one " + r"of the facility's units nor an auxiliary of one \(rebuilt " + r".*\): simulate the system") + with pytest.raises(ValueError, match=message): + network_report(HXN) + with pytest.raises(ValueError, match=message): + network_report(HXN, targets) + # simulated again: the network and the report are of the new stages + # (hotter, whose heat now covers the cold stream's) + simulate_strictly(sys) + assert set(HXN.original_heat_exchangers) == {C1, *K1.hxs} + report = network_report(HXN) + assert report['targets'] == HXN.targets().summary() + assert report['targets']['Q_hot'] == 0. and report['targets']['Q_cold'] > 0. + @pytest.mark.parametrize('contributions', [None, {'H1': 3., 'C1': 2.}]) def test_network_report_cached_split_network(contributions): # A split network the facility reuses (cache_network) with its @@ -2853,14 +2893,41 @@ def boxes(fig, artists): texts = {True: ('unserved heating 1.18E5 kJ/hr (not drawn)\n' 'unserved cooling 4.45E4 kJ/hr (not drawn)'), False: 'unserved cooling 4.45E4 kJ/hr (not drawn)'} - for LP_only in (True, False): + def noted(fig, ax): + """The note on the unserved remainders inside the figure, under the + axes and the decorations of the x axis, and clear of the labels and + of the legend.""" + fig.canvas.draw() + renderer = fig.canvas.get_renderer() + note, = _gid_artists(ax, 'Utility:unserved') + box = note.get_window_extent(renderer) + page = fig.bbox + assert (page.x0 <= box.x0 and box.x1 <= page.x1 + and page.y0 <= box.y0 and box.y1 <= page.y1) + above = [ax, ax.xaxis.label, ax.xaxis.get_offset_text(), + *[x for x in ax.get_xticklabels() if x.get_text()]] + assert all(box.y1 < a.get_window_extent(renderer).y0 for a in above) + others = [a for a in _gid_artists(ax, 'Utility:') + _gid_artists(ax, 'Fuel') + if a is not note] + [ax.get_legend()] + assert len(others) >= 3 + for a in others: + assert not box.overlaps(a.get_window_extent(renderer)), a + for LP_only, size in ((True, None), (False, None), (False, (4.5, 4.2))): heating = [agent('low_pressure_steam')] if LP_only else None with pytest.warns(RuntimeWarning, match='left unserved'): utilities = t.utility_targets(heating, [agent('cooling_water')]) - fig, ax = plot_composite_curves(t, kind='balanced', utilities=utilities) + if size is None: + fig, ax = plot_composite_curves(t, kind='balanced', utilities=utilities) + else: + # of a figure of the user's, where a label of the steam at the + # top of the curves, moved inside the left edge, reaches the + # upper left corner + fig, ax = plt.subplots(figsize=size, layout='constrained') + plot_composite_curves(t, kind='balanced', utilities=utilities, ax=ax) try: label, = _gid_artists(ax, 'Utility:unserved') assert label.get_text() == texts[LP_only] + noted(fig, ax) hot_curve, = _gid_artists(ax, 'Hot balanced composite') cold_curve, = _gid_artists(ax, 'Cold balanced composite') (hot_H, hot_T), (cold_H, cold_T) = hot_curve.get_data(), cold_curve.get_data() @@ -2869,6 +2936,23 @@ def boxes(fig, artists): bst.HeatUtility.dT, abs=1e-6) finally: plt.close(fig) + # at the default size, where the labels of both steams, kept off the + # tall riser of the hot curve under their span, rise to the upper left + # corner of the axes + short = targets('test_targeting_plot_unserved_steam', + [('H0', 634.16, 621.99, 1.101), ('C1', 401.54, 412.56, 2.498), + ('C2', 552.11, 649.10, 1.515)], 10.) + with pytest.warns(RuntimeWarning, match='left unserved'): + utilities = short.utility_targets( + [agent('low_pressure_steam'), agent('high_pressure_steam')], + [agent('chilled_brine'), agent('propane')]) + fig, ax = plot_composite_curves(short, kind='balanced', utilities=utilities) + try: + label, = _gid_artists(ax, 'Utility:unserved') + assert label.get_text() == 'unserved heating 4.81E5 kJ/hr (not drawn)' + noted(fig, ax) + finally: + plt.close(fig) def readable(fig, ax, labels, curves, legend=True): """Every one of the `labels` and the legend (if `legend`) inside the axes and off the `curves` (and the legend off the labels), and the @@ -3150,10 +3234,14 @@ def test_plot_labels_placed_once(monkeypatch): calls.append(None) or place(self, *args, **kwargs)) fig, ax = plot_composite_curves(t, kind='balanced', utilities=utilities) try: + # a few passes at the first draw, one per layout; at most as many at + # the next (none where the layout is the same to the bit) + counts = [] for _ in range(2): calls.clear() fig.canvas.draw() - assert 1 <= len(calls) <= 4 + counts.append(len(calls)) + assert 1 <= counts[0] <= 4 and counts[1] <= 4 renderer = fig.canvas.get_renderer() frame = ax.get_window_extent(renderer) labels = sorted(_gid_artists(ax, 'Utility:test_targeting_plot_steam'), From 0cf7fa4dee215bb7b70f0ded08a7c976f8dfec45 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sat, 17 Oct 2026 22:24:07 +0000 Subject: [PATCH 41/52] address review 8 (e2e): warn when HXN.targets() is of stages rebuilt since, validate the utilities of plot_composite_curves E2E-R8-1 (fixed): the network_report half was fixed by TGR8-2 in the previous commit (it now raises when an original exchanger is no longer among the facility's units and their nested auxiliaries). HXN.targets() still returned the targets of the detached former stages silently (only its Notes said so). It now warns (RuntimeWarning, at the caller's line) naming the first such stream by its label, "the targets of HXN are of the former state of K1 - K1_H2, which is no longer one of the facility's units nor an auxiliary of one (rebuilt since the facility's last simulation?): simulate the system for those of the process now", and still returns those targets (test_targets_keep_the_holders_of_stages_rebuilt_since keeps its contributions). The check is shared with network_report's (targeting._rebuilt_label, over the same candidate units). network_report takes its default targets through the new private HeatExchangerNetwork._pinch_targets, so it raises its ValueError without also emitting the warning. Docstring of HXN.targets: new Warns section, Notes updated. Tests: test_network_report_rebuilt_stages now expects the warning from HXN.targets() after K1.simulate() alone, checks that those targets equal the report's before the change, that network_report (by default and with explicit targets) raises without the warning, and that neither warns after the system is simulated again; test_targets_keep_the_holders_of_stages_rebuilt_since expects the warning. No false positive in tests/test_targeting.py or in the contribution tests that call HXN.targets() with the warning made an error. E2E-R8-3 (nit, fixed): plot_composite_curves raised "TypeError: 'bool' object is not iterable" for kind='balanced', utilities=False, and silently ignored utilities with kind 'real' or 'shifted'. It now raises a clear ValueError in both cases before making a figure; parameter doc and a new Raises section updated; covered in test_plot_composite_curves_gids. E2E-R8-2 (nit, documented): the Notes of plot_composite_curves now say that labels can still overlap on axes too small for them all (many agents on small axes without a layout engine) and that a larger figure or a constrained layout gives them room. Shrinking fonts or dropping duties was not done: it would change the documented label contents for a rare case. No docs figure or capture changes (the default path and the demo scripts are untouched; ch05 calls HXN.targets() only after a system simulation). Tests: tests/test_targeting.py (77) plus tests/test_hxn_contributions.py::test_targets_keep_the_holders_of_stages_rebuilt_since plus doctests of hensmith/targeting.py and hensmith/_heat_exchanger_network.py: 86 passed; tests/test_targeting.py with -W "error:the targets of:RuntimeWarning": 77 passed; the 6 tests of tests/test_hxn_contributions.py calling HXN.targets() with the same filter: 6 passed; oracle sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (29 cases) unchanged. sphinx is not installed in this environment, so the docs build was not run (the new Warns and Raises sections follow those already in targeting.py). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- hensmith/_heat_exchanger_network.py | 34 +++++++++++++++--- hensmith/targeting.py | 54 ++++++++++++++++++++++------- tests/test_hxn_contributions.py | 6 ++-- tests/test_targeting.py | 38 ++++++++++++++++---- 4 files changed, 105 insertions(+), 27 deletions(-) diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index 7b9ad50..0660ff9 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -1030,6 +1030,14 @@ def targets(self, T_min_app=None): If the facility has neither `units` nor a system (it learns its system when the system is first simulated). + Warns + ----- + RuntimeWarning + After a simulation, if a stream of the network is that of an + exchanger no longer among the facility's units and their nested + auxiliaries (rebuilt since the facility's last simulation, see + Notes): the targets are of its former state. + Notes ----- After a simulation, the streams are those of @@ -1063,8 +1071,10 @@ def targets(self, T_min_app=None): unit that rebuilds its auxiliaries when simulated alone (K1 above, after a change of its `pr`) leaves the streams on its former stages, which are no longer in the process and keep their former - states, until the system is simulated again - (`hensmith.targeting.network_report` raises meanwhile). + states, until the system is simulated again: the targets are then + of those former states, not of the process now, with a + RuntimeWarning (`hensmith.targeting.network_report` raises + meanwhile). Examples -------- @@ -1105,7 +1115,21 @@ def targets(self, T_min_app=None): """ - from .targeting import PinchTargets + targets, rebuilt = self._pinch_targets(T_min_app) + if rebuilt is not None: + warn(f'the targets of {self.ID} are of the former state of ' + f"{rebuilt}, which is no longer one of the facility's units " + "nor an auxiliary of one (rebuilt since the facility's last " + 'simulation?): simulate the system for those of the process ' + 'now', RuntimeWarning, stacklevel=2) + return targets + + def _pinch_targets(self, T_min_app=None): + """`targets` (see there) and the label of the first of their + streams whose exchanger is no longer among the facility's units and + their nested auxiliaries (rebuilt since the last simulation), or + None (always before a simulation).""" + from .targeting import PinchTargets, _rebuilt_label if T_min_app is None: T_min_app = self.T_min_app if (hasattr(self, 'original_heat_exchangers') and hasattr(self, 'original_heat_utils')): @@ -1142,7 +1166,7 @@ def targets(self, T_min_app=None): None, hus, _held_owners([*hxs, *key_units], held)) targets.key_units = key_units targets.system = self.system - return targets + return targets, _rebuilt_label(hxs, targets.labels, key_units) if self.system is None and not self.units: raise RuntimeError('simulate the system (or give the facility its ' 'units) before targeting') @@ -1153,7 +1177,7 @@ def targets(self, T_min_app=None): hx_utils, T_min_app, self._network_contributions(hx_utils, units), self.force_ideal_thermo, self.sort_hus_by_T, key_units=units) targets.system = self.system - return targets + return targets, None def get_original_hxs_associated_with_streams(self): # pragma: no cover original_units = self.system.units diff --git a/hensmith/targeting.py b/hensmith/targeting.py index e8f7775..02e8c69 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -2575,6 +2575,15 @@ def _check_targets(HXN, targets): '(simulated since?): make ' 'them again with HXN.targets()') +def _rebuilt_label(hxs, labels, units): + """The label (of `labels`, aligned with `hxs`) of the first of the + exchangers `hxs` that is not one of `units` (a facility's units and + their nested auxiliaries: a unit has rebuilt it since), or None.""" + at_hand = {id(unit) for unit in units} + for hx, label in zip(hxs, labels): + if id(hx) not in at_hand: return label + return None + def _check_network(HXN, targets): """Raise a ValueError unless every stream of `HXN`'s network is that of an original exchanger still among the facility's units and their @@ -2586,14 +2595,14 @@ def _check_network(HXN, targets): copy of the real inlet where it enters the network, and the outlet of its last stage (the network's copies share the curves' thermodynamic basis).""" - at_hand = {id(unit) for unit in HXN._candidate_units()} - for hx, label in zip(HXN.original_heat_exchangers, targets.labels): - if id(hx) not in at_hand: - raise ValueError(f'the network of {HXN.ID} is of {label}, which is ' - "no longer one of the facility's units nor an " - 'auxiliary of one (rebuilt since the ' - "facility's last simulation?): simulate the " - 'system') + label = _rebuilt_label(HXN.original_heat_exchangers, targets.labels, + HXN._candidate_units()) + if label is not None: + raise ValueError(f'the network of {HXN.ID} is of {label}, which is ' + "no longer one of the facility's units nor an " + 'auxiliary of one (rebuilt since the ' + "facility's last simulation?): simulate the " + 'system') for life_cycle, c, label in zip(HXN.stream_life_cycles, targets.curves, targets.labels): entry = life_cycle.entry @@ -2861,7 +2870,8 @@ def network_report(HXN, targets=None, **options): if not hasattr(HXN, 'new_HXs_hot_side'): raise RuntimeError('simulate the heat exchanger network before ' 'reporting on it') - if targets is None: targets = HXN.targets() + # (without the warning of stages rebuilt since: _check_network raises) + if targets is None: targets = HXN._pinch_targets()[0] else: _check_targets(HXN, targets) _check_network(HXN, targets) options = dict(options) @@ -3288,8 +3298,8 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, utilities : list[UtilityTarget], optional Utilities of the balanced curves, as `PinchTargets.balanced_composite_curves` takes them (those of - `targets`). Defaults to `PinchTargets.utility_targets` with the - default agents. + `targets`), with kind 'balanced' only. Defaults to + `PinchTargets.utility_targets` with the default agents. ax : matplotlib.axes.Axes, optional Axes to draw on; a new figure (with a constrained layout, which keeps the axis labels and a note under the x axis in it) is created @@ -3304,6 +3314,14 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, fig : matplotlib.figure.Figure ax : matplotlib.axes.Axes + Raises + ------ + ValueError + For another `kind`, for `utilities` with kind 'real' or 'shifted' + (whose curves take none), or for ``utilities=False`` (the balanced + curves are those of their utilities: kind 'real' draws the + composites without them). + Notes ----- Temperatures are shown in degC. Artists carry stable gids: 'Hot @@ -3334,8 +3352,11 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, labels go in rows instead, each beside its flat and moved sideways past the labels of the levels beyond its own; or, if those would not keep the order inside the axes, those that would run past the edge are moved - sideways past the label in their way. A label left more than 1.5 times - its height away from its anchor gets a thin leader from it + sideways past the label in their way. Labels can then still overlap, + on axes too small for them all (many agents on small axes without a + layout engine, say): a larger figure, or one with + ``layout='constrained'``, gives them room. A label left more than 1.5 + times its height away from its anchor gets a thin leader from it ('Leader:', an empty path otherwise), and each label a halo, so that a leader passing behind it strikes none of its letters. The legend goes where it covers the least of the curves, the band and the @@ -3370,6 +3391,13 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, if kind not in ('real', 'shifted', 'balanced'): raise ValueError("kind must be 'real', 'shifted' or 'balanced', not " f'{kind!r}') + if utilities is not None and kind != 'balanced': + raise ValueError(f'the {kind} composite curves take no utilities ' + "(those of kind='balanced')") + if utilities is False: + raise ValueError('the balanced composite curves are those of their ' + "utilities, not False (kind='real' draws the " + 'composites without them)') if ax is None: fig, ax = plt.subplots(figsize=(7.5, 4.2), layout='constrained') else: diff --git a/tests/test_hxn_contributions.py b/tests/test_hxn_contributions.py index d8a432f..3720465 100644 --- a/tests/test_hxn_contributions.py +++ b/tests/test_hxn_contributions.py @@ -1365,13 +1365,15 @@ def results(targets): assert all(getattr(hx, '_owner', None) is None for hx in hxs) assert results(targets) == first # K1 simulated alone rebuilds its stages: the facility's targets keep - # the contributions the network was synthesized with + # the contributions the network was synthesized with (and warn that + # their streams are of the former stages) info = HXN.synthesis_info['approach_contributions'].tolist() old = list(K1.hxs) K1.pr = 2.5 K1.simulate() assert not any(hx in old for hx in K1.hxs) - targets = HXN.targets() + with pytest.warns(RuntimeWarning, match='former state of K1 - K1_H'): + targets = HXN.targets() assert [2.5 if c is None else c for c in targets.approach_contributions] == info == [ 2.5, 2., 2., 2.] diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 2dbeba7..18e263f 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -2594,20 +2594,33 @@ def test_network_report_rebuilt_stages(): K1.pr = 3. K1.simulate() assert not set(K1.hxs) & set(former) - targets = HXN.targets() # still of the former stages, labeled by K1 + # the facility's targets are still of the former stages (labeled by + # K1), at their former states, and say so + rebuilt = (r"the targets of HXN are of the former state of K1 - K1_H\d, " + r"which is no longer one of the facility's units nor an " + r"auxiliary of one \(rebuilt .*\): simulate the system") + with pytest.warns(RuntimeWarning, match=rebuilt): + targets = HXN.targets() assert set(targets.units) == {C1, *former} + assert targets.summary() == report['targets'] message = (r"the network of HXN is of K1 - K1_H\d, which is no longer one " r"of the facility's units nor an auxiliary of one \(rebuilt " r".*\): simulate the system") - with pytest.raises(ValueError, match=message): - network_report(HXN) - with pytest.raises(ValueError, match=message): - network_report(HXN, targets) + for args in ((HXN,), (HXN, targets)): # it raises, without the warning + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter('always') + with pytest.raises(ValueError, match=message): + network_report(*args) + assert not [w for w in caught if 'former state' in str(w.message)] # simulated again: the network and the report are of the new stages - # (hotter, whose heat now covers the cold stream's) + # (hotter, whose heat now covers the cold stream's), and the targets + # warn no more simulate_strictly(sys) assert set(HXN.original_heat_exchangers) == {C1, *K1.hxs} - report = network_report(HXN) + with warnings.catch_warnings(): + warnings.simplefilter('error', RuntimeWarning) + report = network_report(HXN) + HXN.targets() assert report['targets'] == HXN.targets().summary() assert report['targets']['Q_hot'] == 0. and report['targets']['Q_cold'] > 0. @@ -2790,6 +2803,17 @@ def test_plot_composite_curves_gids(tmp_path): plt.close(fig) with pytest.raises(ValueError, match="kind must be"): plot_composite_curves(t, kind='hot') + # utilities are those of the balanced curves only, which need them (no + # figure is made before the error) + figures = plt.get_fignums() + for kind in ('real', 'shifted'): + with pytest.raises(ValueError, match=f'the {kind} composite curves ' + r"take no utilities \(those of kind='balanced'\)"): + plot_composite_curves(t, kind=kind, utilities=t.utility_targets()) + with pytest.raises(ValueError, match='the balanced composite curves are ' + 'those of their utilities, not False'): + plot_composite_curves(t, kind='balanced', utilities=False) + assert plt.get_fignums() == figures # fuel is labeled, not drawn furnace = targets('test_targeting_plots_fuel', [('C1', 330., 520., 1.), ('H1', 400., 300., 1.)], 10.) From fb49239b55c253ff7a21a7f92bf8fbe35cb10b30 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sun, 18 Oct 2026 00:37:34 +0000 Subject: [PATCH 42/52] address review 9 (f1-facility): take exchangers held through heat utilities as at hand, hint the other form of auxiliary list keys F1F9-1 (fixed): a unit may keep an exchanger outside its auxiliary_unit_names and take its heat utilities in _design (self.heat_utilities = self.hx.heat_utilities). The facility takes those heat utilities, so the exchanger becomes an original heat exchanger, but _with_auxiliaries (and so HXN._candidate_units and the key_units of from_heat_utilities) never listed it. As a result HXN.targets() warned "rebuilt since" and network_report raised right after a simulation. - hxn_synthesis._heat_utility_units is new: it gives the units of the heat utilities of a group of units (hu.unit), other than the group's own. - _with_auxiliaries now lists, after each unit and its nested auxiliaries, the exchangers they hold through their heat utilities only. This makes the "at hand" set of _rebuilt_label/_check_network include them. A unit that rebuilds such an exchanger and is simulated alone is still detected, as is a rebuilt MultistageCompressor stage. - _held_owners gives such an owner-less exchanger, held as no unit's auxiliary, the lowest unit on the owner chains whose heat_utilities hold its heat utility as its holder (an owner lists its auxiliaries' heat utilities too). This runs after the auxiliary holders and never closes a loop. So 'U' covers the exchanger, ('U', 'U_hx') names it, the bare 'U_hx' raises with the pair as a hint, and labels read 'U - U_hx' (the owner's ID alone for an unnamed exchanger, where the message read "of , which is" before). Outside the facility without key_units, the exchanger has no owner and 'U_hx' is its key, as for K1's stages. - Docs: the HXN Notes, the targeting docstrings (key_units, targets attribute, _rebuilt_label, _check_network) and concepts.rst now say this. - Tests: test_keys_of_exchangers_held_through_heat_utilities and test_held_owners_through_heat_utilities (lowest holder, an existing owner kept, mutual holders without a loop) in tests/test_hxn_contributions.py, and test_network_report_of_exchangers_held_through_heat_utilities[replace False/True] in tests/test_targeting.py (clean right after a simulation, warn/raise after a rebuilding unit is simulated alone, clean again after the system is simulated). F1F9-2 (nit, applied): _unknown_key_hints now also uses the new _other_form_hints. A tuple key giving a list attribute's bare name gets "; E1.evaporators is a list: ('E1', 'evaporators[0]'), ..." (at most three keys, then ...). An indexed key into a single auxiliary gets "; D1.condenser is ('D1', 'condenser')". Assertions were added to test_contribution_keys_on_auxiliary_units and test_keys_of_auxiliaries_a_unit_does_not_build (facility and h keys). Default-off results are unchanged: the oracle gives sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf. The review's Monte Carlo probe (mc.py) gives byte-identical records to HEAD in default mode, and in contrib mode against a HEAD worktree. Tests (NUMBA_DISABLE_JIT=1): tests/test_hxn_contributions.py, tests/test_targeting.py, tests/test_hxn_targets.py and the doctests of hensmith/hxn_synthesis.py, hensmith/_heat_exchanger_network.py and hensmith/targeting.py: 186 passed. Sphinx is not installed in either venv, so the docs build was not run (the edits are plain RST literals and docstring prose with no new cross-references). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/concepts.rst | 19 ++-- hensmith/_heat_exchanger_network.py | 23 +++-- hensmith/hxn_synthesis.py | 115 +++++++++++++++++++--- hensmith/targeting.py | 32 ++++--- tests/test_hxn_contributions.py | 142 +++++++++++++++++++++++++++- tests/test_targeting.py | 49 +++++++++- 6 files changed, 336 insertions(+), 44 deletions(-) diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 0657799..dccd6d2 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -283,14 +283,17 @@ is an explicit default that wins at its level like any value, and two keys of the same level for a unit must agree. A unit's owner is its direct owner (the unit it was made an auxiliary of; BioSTEAM's ``Unit.owner`` gives the top one), or, for an auxiliary that a unit available to the network holds -without setting its owner, that unit (the user's objects are left as they -are): ``biosteam.MultistageCompressor`` builds its compressors and -intercoolers anew, without an owner, whenever ``pr`` or ``n_stages`` -changes, and ``'K1'`` still covers the intercoolers of compressor K1 and -``('K1', 'K1_H1')`` names one in every sample, while ``'K1_H1'`` names -nothing. Targets keep this relation as it was when they were made (those of -``HXN.targets()`` as it was at the network's synthesis), for their -contributions, film coefficient keys and labels (``'K1 - K1_H1'``): ``'K1'`` +without setting its owner, or an exchanger without one whose heat utilities +a unit takes without holding it as an auxiliary (a custom unit's +``self.heat_utilities = self.hx.heat_utilities``), that unit (the user's +objects are left as they are): ``biosteam.MultistageCompressor`` builds its +compressors and intercoolers anew, without an owner, whenever ``pr`` or +``n_stages`` changes, and ``'K1'`` still covers the intercoolers of +compressor K1 and ``('K1', 'K1_H1')`` names one in every sample, while +``'K1_H1'`` names nothing. Targets keep this relation as it was when they +were made (those of ``HXN.targets()`` as it was at the network's synthesis), +for their contributions, film coefficient keys and labels +(``'K1 - K1_H1'``): ``'K1'`` still covers the intercoolers of targets stored in one sample after K1 has rebuilt them in the next, or has been simulated alone. (Outside the facility -- ``synthesize_network``, ``temperature_interval_pinch_analysis``, diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index 0660ff9..7745b62 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -294,9 +294,12 @@ class HeatExchangerNetwork(bst.Facility): agree. A unit's owner is its direct owner (the unit it was made an auxiliary of; `biosteam.Unit.owner` gives the top one) or, for an auxiliary that a unit available to the network holds without setting - its owner, that unit: a `biosteam.MultistageCompressor` K1 builds its - compressors and intercoolers anew whenever `pr` or `n_stages` changes, - without an owner, and ``'K1'`` still covers the intercoolers and + its owner, or an exchanger without one whose heat utilities a unit takes + without holding it as an auxiliary (``self.heat_utilities = + self.hx.heat_utilities``), that unit: a `biosteam.MultistageCompressor` + K1 builds its compressors and intercoolers anew whenever `pr` or + `n_stages` changes, without an owner, and ``'K1'`` still covers the + intercoolers and ``('K1', 'K1_H1')`` names one in every simulation (the bare ``'K1_H1'`` names nothing, though it is the key of that intercooler outside the facility, see `synthesize_network`; user objects are not changed). ID @@ -308,7 +311,8 @@ class HeatExchangerNetwork(bst.Facility): an owner (an old intercooler of K1) names none, and raises (heat utility keys are rejected, since BioSTEAM replaces them at every simulation). The keys and values are checked against every unit - available to the network and its nested auxiliary units before the + available to the network, its nested auxiliary units and the exchangers + they hold through their heat utilities only, before the heat utilities are taken: a key that names none of them, a bad value or two conflicting keys raise a ValueError, even while their unit is idle, but a valid key whose unit holds no heat utility in a simulation @@ -532,7 +536,9 @@ def _get_units(self): def _candidate_units(self, units=None): """The units that approach contribution keys may name: each unit of `units` (defaults to `_get_units`) followed by its nested auxiliary - units, whether or not it holds a heat utility the network takes.""" + units and the exchangers they hold through their heat utilities + only, whether or not it holds a heat utility the network takes (see + `hensmith.hxn_synthesis._with_auxiliaries`).""" if units is None: units = self._get_units() return _with_auxiliaries(units) @@ -1126,9 +1132,10 @@ def targets(self, T_min_app=None): def _pinch_targets(self, T_min_app=None): """`targets` (see there) and the label of the first of their - streams whose exchanger is no longer among the facility's units and - their nested auxiliaries (rebuilt since the last simulation), or - None (always before a simulation).""" + streams whose exchanger is no longer among the facility's units, + their nested auxiliaries and the exchangers they hold through their + heat utilities only (rebuilt since the last simulation), or None + (always before a simulation).""" from .targeting import PinchTargets, _rebuilt_label if T_min_app is None: T_min_app = self.T_min_app if (hasattr(self, 'original_heat_exchangers') diff --git a/hensmith/hxn_synthesis.py b/hensmith/hxn_synthesis.py index 638c1f8..a1dbf5d 100644 --- a/hensmith/hxn_synthesis.py +++ b/hensmith/hxn_synthesis.py @@ -799,19 +799,42 @@ def _held_owners(units, held=None): dict taken earlier (a snapshot, which keeps its objects alive), comes first: its auxiliaries keep their holders even once a holder has rebuilt them (no longer holds them), and the units at hand add the - others, along its owners. Nothing is mutated. + others, along its owners. Then a unit without an ``_owner`` that none + holds as an auxiliary, but whose heat utility is among the + `heat_utilities` of units on those chains (an exchanger kept outside + the `auxiliary_unit_names`, see `_heat_utility_units`), is held by the + lowest of them (an owner lists the heat utilities of its auxiliaries + too), none that would close a loop. Nothing is mutated. """ owners = dict(held) if held else {} - for unit in _owner_chains(units, owners): + chains = _owner_chains(units, owners) + + def closes_loop(aux, unit): + above = unit + while above is not None and above is not aux: + above = _parent(above, owners) + return above is not None + + for unit in chains: for aux in getattr(unit, 'auxiliary_units', ()): if (not isinstance(aux, bst.Unit) or getattr(aux, '_owner', None) is not None or id(aux) in owners): continue - above = unit - while above is not None and above is not aux: - above = _parent(above, owners) - if above is None: owners[id(aux)] = (aux, unit) + if not closes_loop(aux, unit): owners[id(aux)] = (aux, unit) + by_heat_utilities = {} + for unit in chains: + for hx in _heat_utility_units([unit]): + if getattr(hx, '_owner', None) is None and id(hx) not in owners: + by_heat_utilities.setdefault(id(hx), (hx, []))[1].append(unit) + for hx, holders in by_heat_utilities.values(): + holders = [unit for unit in holders if not closes_loop(hx, unit)] + # the lowest holder: one on the chain of no other holder + lowest = [unit for unit in holders + if not any(above is unit for other in holders + if other is not unit + for above in _owner_chains([other], owners)[1:])] + if lowest: owners[id(hx)] = (hx, lowest[0]) return owners def _unit_name(unit, owners=None): @@ -932,15 +955,45 @@ def walk(holder): walk(unit) return nested +def _heat_utility_units(units): + """ + The `biosteam.Unit` objects that the heat utilities of `units` belong + to (``hu.unit``), each once, other than the units of `units`: the + exchangers that a unit holds through its `heat_utilities` only, outside + its `auxiliary_unit_names`, as a unit may keep an exchanger as a plain + attribute and take its heat utilities in its `_design` + (``self.heat_utilities = self.hx.heat_utilities``; biosteam rejects + only the heat utilities of auxiliaries added again). A + `HeatExchangerNetwork` takes those heat utilities as any other + (`biosteam.process_tools.heat_exchanger_utilities_from_units` reads + `unit.heat_utilities`), so such exchangers are among its original + heat exchangers. An owner lists the heat utilities of its auxiliaries + too, whose units are its nested auxiliaries. + """ + found = {id(unit) for unit in units} + held = [] + for unit in units: + for hu in getattr(unit, 'heat_utilities', None) or (): + hx = getattr(hu, 'unit', None) + if isinstance(hx, bst.Unit) and id(hx) not in found: + found.add(id(hx)) + held.append(hx) + return held + def _with_auxiliaries(units): """Each unit of `units` followed by its nested auxiliary units (those - that are `biosteam.Unit` objects, see `_nested_auxiliaries`): the units - that keys may name, as `HeatExchangerNetwork` takes those available to - it, whether or not they hold a heat utility.""" + that are `biosteam.Unit` objects, see `_nested_auxiliaries`) and by the + exchangers that it or they hold through their heat utilities only (see + `_heat_utility_units`): the units that keys may name, as + `HeatExchangerNetwork` takes those available to it, whether or not they + hold a heat utility, and among which its original exchangers are found + while no unit has rebuilt them (see + `hensmith.targeting._rebuilt_label`).""" candidates = [] for unit in units: - candidates.append(unit) - candidates.extend(_nested_auxiliaries(unit)) + group = [unit, *_nested_auxiliaries(unit)] + candidates.extend(group) + candidates.extend(_heat_utility_units(group)) return candidates def _key_universe(units, held=None): @@ -980,6 +1033,38 @@ def _auxiliary_slots(key, universe, owners=None): held = held[k] if k < len(held) else None yield unit, held if isinstance(held, bst.Unit) else None +def _other_form_hints(key, universe, owners=None): + """ + Hints for the tuple key `key` of the ID that `biosteam.Unit.auxiliary` + gives an auxiliary in the other form than the attribute it names takes + (see `_auxiliary_slots`, which skips such attributes): for ``(owner.ID, + name)`` where a unit under that top owner holds a list there, the keys + of its units (``"E1.evaporators is a list: ('E1', 'evaporators[0]'), + ..."``), and for ``(owner.ID, 'name[k]')`` where it holds one unit, + that unit's key (``"D1.condenser is ('D1', 'condenser')"``). + """ + owner_ID, ID = key + match = re.fullmatch(r'(.+)\[(0|[1-9]\d*)\]', ID) + name = match[1] if match else ID + hints = [] + for unit in universe: + if (_top(unit, owners).ID != owner_ID + or name not in getattr(unit, 'auxiliary_unit_names', ())): + continue + held = getattr(unit, name, None) + if held is None or isinstance(held, list) == bool(match): continue + if match: + if isinstance(held, bst.Unit): + hints.append(f'; {unit.ID}.{name} is ' + f'{_unit_name(held, owners)!r}') + continue + keys = [repr(_unit_name(aux, owners)) for aux in held + if isinstance(aux, bst.Unit)] + if keys: + shown = ', '.join(keys[:3]) + (', ...' if len(keys) > 3 else '') + hints.append(f'; {unit.ID}.{name} is a list: {shown}') + return hints + def _stage_tickets(compressor=None): """The ticket names that a `biosteam.MultistageCompressor` gives its stages in their IDs (``f'{K.ID}_{ticket}{n}'``): those of the @@ -1067,8 +1152,10 @@ def _unknown_key_hints(keys, universe, owners=None): key: for a str key, each auxiliary unit of that ID (``"'condenser' is ('D1', 'condenser')"``), and for a tuple key of the ID that biosteam gives an auxiliary (see `_auxiliary_slots`), each unit held there under - another ID (``"T1.heat_exchanger is ('T1', '')"``). A tuple key - ``(owner_ID, ID)`` of a unit of `universe` without an owner gets that + another ID (``"T1.heat_exchanger is ('T1', '')"``), or the keys of the + units held there when the key takes the other form (a list's name + alone, or an index into a single one, see `_other_form_hints`). A tuple + key ``(owner_ID, ID)`` of a unit of `universe` without an owner gets that unit's key (``"('K1', 'K1_H1') is 'K1_H1' (...)"``), and a str key, the stages without an owner that a `biosteam.MultistageCompressor` of that ID gives its IDs (see `_is_stage_ID`), such as the intercoolers @@ -1103,6 +1190,8 @@ def _unknown_key_hints(keys, universe, owners=None): if held is not None and held.ID != key[1]: hints[f'; {unit.ID}.{key[1]} is ' f'{_unit_name(held, owners)!r}'] = None + for hint in _other_form_hints(key, universe, owners): + hints[hint] = None if key[0] != key[1] and any( unit.ID == key[1] and _parent(unit, owners) is None for unit in universe): diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 02e8c69..5df5436 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -713,9 +713,10 @@ class PinchTargets: Further units that keys of `h` may name (with their owners) without a stream of theirs, a key that names only such units then having no effect: for `HeatExchangerNetwork.targets`, every unit available to - the network and its nested auxiliary units, which approach - contribution keys may name; for `from_heat_utilities`, its - `key_units` and their nested auxiliary units; else None (settable). + the network, its nested auxiliary units and the exchangers they hold + through their heat utilities only, which approach contribution keys + may name; for `from_heat_utilities`, its `key_units` and theirs; + else None (settable). An auxiliary that one of them, or a unit of `units`, holds without an owner when the targets are made (for `HeatExchangerNetwork.targets`, at the network's synthesis) stays that unit's for keys and labels, @@ -1106,14 +1107,16 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, key_units : Iterable[Unit], optional Further units at hand, such as ``sys.units``: the keys of `approach_contributions` and of `h` (see `area_target`) may name - them and their nested auxiliary units (the targets' - `key_units`), whether or not they hold a heat utility of `hus`, - as the keys of a `HeatExchangerNetwork` with these units may. A + them, their nested auxiliary units and the exchangers they hold + through their heat utilities only (the targets' `key_units`), + whether or not they hold a heat utility of `hus`, as the keys of + a `HeatExchangerNetwork` with these units may. A `biosteam.MultistageCompressor` K1 among them owns the stages it holds without an owner, so ``'K1'`` covers its intercoolers and - ``('K1', 'K1_H1')`` names one. Without them, only the units of - `hus` and their owners are at hand, and ``'K1_H1'`` is that - intercooler's key. Defaults to None. + ``('K1', 'K1_H1')`` names one (as a unit owns an exchanger it + holds through its heat utilities only). Without them, only the + units of `hus` and their owners are at hand, and ``'K1_H1'`` is + that intercooler's key. Defaults to None. Returns ------- @@ -2577,8 +2580,10 @@ def _check_targets(HXN, targets): def _rebuilt_label(hxs, labels, units): """The label (of `labels`, aligned with `hxs`) of the first of the - exchangers `hxs` that is not one of `units` (a facility's units and - their nested auxiliaries: a unit has rebuilt it since), or None.""" + exchangers `hxs` that is not one of `units` (a facility's units, their + nested auxiliaries and the exchangers they hold through their heat + utilities only, see `hensmith.hxn_synthesis._with_auxiliaries`: a unit + has rebuilt it since), or None.""" at_hand = {id(unit) for unit in units} for hx, label in zip(hxs, labels): if id(hx) not in at_hand: return label @@ -2586,8 +2591,9 @@ def _rebuilt_label(hxs, labels, units): def _check_network(HXN, targets): """Raise a ValueError unless every stream of `HXN`'s network is that of - an original exchanger still among the facility's units and their - nested auxiliaries (not one that its unit has rebuilt since, as a + an original exchanger still among the facility's units, their nested + auxiliaries and the exchangers they hold through their heat utilities + only (see `_rebuilt_label`; not one that its unit has rebuilt since, as a `biosteam.MultistageCompressor` simulated alone after a change of `pr` rebuilds its intercoolers, whose former objects keep their former, consistent states), and enters and leaves the network at the end states diff --git a/tests/test_hxn_contributions.py b/tests/test_hxn_contributions.py index 3720465..c867f4e 100644 --- a/tests/test_hxn_contributions.py +++ b/tests/test_hxn_contributions.py @@ -762,11 +762,17 @@ def resolve(x): r"\('D1', 'nope'\)"): resolve({'condenser': 1., ('D1', 'nope'): 1., 'D1': 2.}) # biosteam gives a single auxiliary its bare name, so an indexed ID of - # one names nothing + # one names nothing, and the message gives the unit's key for key in (('D1', 'condenser[0]'), ('D1', 'reboiler[7]'), ('D1', 'pump[0]'), ('F1', 'heat_exchanger[3]')): with pytest.raises(ValueError, match='name no unit'): resolve({key: 1.}) + for key, hint in ((('D1', 'condenser[0]'), "D1.condenser is ('D1', " + "'condenser')"), + (('F1', 'heat_exchanger[3]'), "F1.heat_exchanger is " + "('F1', 'heat_exchanger')")): + with pytest.raises(ValueError, match=re.escape(f'; {hint}')): + resolve({key: 1.}) # sequence (aligned with hus) and callable (once per heat utility, in # order) forms; heat utility and other keys are rejected assert hxn_synthesis._stream_contributions( @@ -1093,6 +1099,12 @@ def run(T, x): HXN.approach_contributions = {key: 3.} with pytest.raises(ValueError, match='name no unit'): sys.simulate() + # the bare name of the list gets the keys of its exchangers + hint = "; X1.heat_exchangers is a list: ('X1', 'heat_exchangers[0]')" + with pytest.raises(ValueError, match=re.escape(hint) + r'\)$'): + sys.simulate() + with pytest.raises(ValueError, match=re.escape(hint)): + targets.area_target(h={('X1', 'heat_exchangers'): 2.}) # outside the facility, the universe is that of the heat utilities hus = bst.process_tools.heat_exchanger_utilities_from_units([X1, X2]) assert hxn_synthesis._stream_contributions(hus, tuple_key) == [None, None] @@ -1445,6 +1457,134 @@ def _run(self): pass first, second, C, second, first, C] for unit in (A, B, C): assert getattr(unit, '_owner', None) is None +class KeptHeater(bst.Unit): + """A heater kept as a plain attribute, outside `auxiliary_unit_names`, + whose heat utilities the unit takes in its `_design`, as units written + before biosteam's auxiliary units do (biosteam rejects only the heat + utilities of auxiliaries added again). With `rebuild`, it builds its + heater anew at every simulation.""" + _N_ins = 1 + _N_outs = 1 + + def _init(self, T, rebuild=False): + self.T = T + self.rebuild = rebuild + self.hx = self._heater() + + def _heater(self): + return bst.HXutility(f'.{self.ID}_hx', None, None, T=self.T, + rigorous=False) + + def _run(self): + self.outs[0].copy_like(self.ins[0]) + self.outs[0].T = self.T + + def _design(self): + if self.rebuild: self.hx = self._heater() + self.hx.simulate_as_auxiliary_exchanger(self.ins, self.outs) + self.heat_utilities = self.hx.heat_utilities + +def test_keys_of_exchangers_held_through_heat_utilities(): + # a unit that keeps a heater outside its auxiliary_unit_names and takes + # its heat utilities holds it for the facility, as a unit holds the + # auxiliaries it holds without an owner: 'U' covers the heater, + # ('U', 'U_hx') names it and the bare 'U_hx' raises, with the pair in + # the message; outside the facility, without key_units, the heater has + # no owner and 'U_hx' is its key; the user's objects are left as they are + bst.main_flowsheet.set_flowsheet('contributions_kept_heater') + bst.settings.set_thermo(['Water'], cache=True) + feed = bst.Stream('feed', Water=300., units='kmol/hr', T=300.) + U = KeptHeater('U', ins=feed, T=360.) + hot = bst.Stream('hot', Water=200., units='kmol/hr', T=400.) + H1 = bst.HXutility('H1', ins=hot, T=310., rigorous=False) + HXN = HeatExchangerNetwork('HXN', T_min_app=5.) + sys = bst.System.from_units('contributions_kept_heater', + units=[U, H1, HXN]) + simulate_strictly(sys) + hx = U.hx + assert hx.ID == 'U_hx' and not U.auxiliary_units + assert set(HXN.original_heat_exchangers) == {hx, H1} + assert HXN._candidate_units() == [U, hx, H1, HXN] + + def used(x): + HXN.approach_contributions = x + simulate_strictly(sys) + return dict(zip(HXN.original_heat_exchangers, + HXN.synthesis_info['approach_contributions'].tolist())) + + default = used(None) + assert set(default.values()) == {2.5} + for x, given in [({'U': 2.}, {hx: 2.}), ({('U', 'U_hx'): 2.}, {hx: 2.}), + ({U: 2.}, {hx: 2.}), ({hx: 2.}, {hx: 2.}), + ({'U': 2., ('U', 'U_hx'): 1.}, {hx: 1.}), + ({'H1': 1.}, {H1: 1.})]: + assert used(x) == default | given + HXN.approach_contributions = {'U_hx': 2.} + with pytest.raises(ValueError, match=r"name no unit: 'U_hx' .*; 'U_hx' " + r"is \('U', 'U_hx'\)"): + sys.simulate() + HXN.approach_contributions = None + simulate_strictly(sys) + targets = HXN.targets() + assert dict(zip(targets.units, targets.labels)) == {hx: 'U - U_hx', + H1: 'H1'} + area = targets.area_target() + assert targets.area_target(h={'U': .5}) != area + assert targets.area_target(h={'U': .5}) == targets.area_target( + h={('U', 'U_hx'): .5}) + # outside the facility, only the heat utilities' units are at hand + hus = bst.process_tools.heat_exchanger_utilities_from_units(sys.units) + assert [hu.unit for hu in hus] == [hx, H1] + assert hxn_synthesis._stream_contributions(hus, {'U_hx': 2.}) == [ + 2., None] + with pytest.raises(ValueError, match='name no unit'): + hxn_synthesis._stream_contributions(hus, {'U': 2.}) + for x in ({'U': 2.}, {U: 2.}, {('U', 'U_hx'): 2.}): + targets = PinchTargets.from_heat_utilities(hus, 5., x, + key_units=sys.units) + assert dict(zip(targets.units, targets.approach_contributions)) == { + hx: 2., H1: None} + assert targets.labels == HXN.targets().labels + assert getattr(hx, '_owner', None) is None and not U.auxiliary_units + +def test_held_owners_through_heat_utilities(): + # an exchanger held through heat utilities only is held by the lowest + # holder of its heat utility (an owner lists those of its auxiliaries + # too), after the holders of the auxiliaries held without an owner, + # never by a unit that would close a loop, and never if it has an owner + bst.main_flowsheet.set_flowsheet('contributions_heat_utility_holders') + bst.settings.set_thermo(['Water'], cache=True) + + class Holder(bst.Unit): + _N_ins = _N_outs = 0 + def _run(self): pass + + T, A, X, Y = Holder('T'), Holder('A'), Holder('X'), Holder('Y') + T.auxiliary_unit_names = ('a',) + T.a = A + hu_X, hu_Y = bst.HeatUtility(None, X), bst.HeatUtility(None, Y) + A.heat_utilities = [hu_X] + T.heat_utilities = [hu_X, hu_Y] # as biosteam lists A's under T + for units in ([T, A, X, Y], [X, Y, A, T], [A, T]): + owners = hxn_synthesis._held_owners(units) + assert owners == {id(A): (A, T), id(X): (X, A), id(Y): (Y, T)} + assert hxn_synthesis._id_path(X, owners) == ('X', 'A', 'T') + assert hxn_synthesis._unit_name(X, owners) == ('T', 'X') + assert hxn_synthesis._heat_utility_units([T]) == [X, Y] + assert hxn_synthesis._with_auxiliaries([T]) == [T, A, X, Y] + # an exchanger with an owner keeps it + Y._owner = A + assert id(Y) not in hxn_synthesis._held_owners([T, A, Y]) + # units that hold each other's heat utilities: the first holder in the + # order given holds the other, which does not close the loop + P, Q = Holder('P'), Holder('Q') + P.heat_utilities = [bst.HeatUtility(None, Q)] + Q.heat_utilities = [bst.HeatUtility(None, P)] + for first, second in ((P, Q), (Q, P)): + assert hxn_synthesis._held_owners([first, second]) == { + id(second): (second, first)} + for unit in (T, A, X, P, Q): assert getattr(unit, '_owner', None) is None + class VacuumTrain(bst.Unit): """An auxiliary flash drum under vacuum, which holds its `biosteam.VacuumSystem` (an auxiliary that is not a unit) as an diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 18e263f..3f6500b 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -58,7 +58,7 @@ from hensmith.targeting import _exchanger_costs, _economics, _lmtd, _profile_area from test_hxn import (build_system, cp_units, KEMP, simulate_HXN, simulate_strictly, _gid_artists) -from test_hxn_contributions import classic_cascade +from test_hxn_contributions import classic_cascade, KeptHeater import test_hxn_targets K = 273.15 @@ -2624,6 +2624,53 @@ def test_network_report_rebuilt_stages(): assert report['targets'] == HXN.targets().summary() assert report['targets']['Q_hot'] == 0. and report['targets']['Q_cold'] > 0. +@pytest.mark.parametrize('replace', [False, True]) +def test_network_report_of_exchangers_held_through_heat_utilities(replace): + # a unit that keeps its heater outside its auxiliary_unit_names and + # takes its heat utilities: right after a simulation, the facility's + # targets and report are of that heater, still at hand (labeled by the + # unit), and neither warns nor raises; a unit that builds its heater + # anew at every simulation, simulated alone, leaves the network of the + # former heater, and they say so until the system is simulated again + bst.main_flowsheet.set_flowsheet('test_targeting_report_kept_heater') + bst.settings.set_thermo(['Water'], cache=True) + feed = bst.Stream('feed', Water=300., units='kmol/hr', T=300.) + U = KeptHeater('U', ins=feed, T=360., rebuild=True) + hot = bst.Stream('hot', Water=200., units='kmol/hr', T=400.) + H1 = bst.HXutility('H1', ins=hot, T=310., rigorous=False) + HXN = HeatExchangerNetwork('HXN', T_min_app=5., + replace_unit_heat_utilities=replace) + sys = bst.System.from_units('test_targeting_report_kept_heater', + units=[U, H1, HXN]) + + def check_clean(): + simulate_strictly(sys) + assert set(HXN.original_heat_exchangers) == {U.hx, H1} + with warnings.catch_warnings(): + warnings.simplefilter('error', RuntimeWarning) + targets = HXN.targets() + report = network_report(HXN) + assert dict(zip(targets.units, targets.labels)) == { + U.hx: 'U - U_hx', H1: 'H1'} + assert report['targets'] == targets.summary() + assert targets.Q_hot == HXN.synthesis_info['Q_hot_target'] + return report + + check_clean() + report = check_clean() # the same once simulated again + former = U.hx + U.simulate() + assert U.hx is not former + with pytest.warns(RuntimeWarning, + match=r'the targets of HXN are of the former state of ' + r'U - U_hx, which is no longer'): + targets = HXN.targets() + assert targets.summary() == report['targets'] + with pytest.raises(ValueError, match=r'the network of HXN is of U - U_hx, ' + r'which is no longer'): + network_report(HXN) + check_clean() + @pytest.mark.parametrize('contributions', [None, {'H1': 3., 'C1': 2.}]) def test_network_report_cached_split_network(contributions): # A split network the facility reuses (cache_network) with its From 8ed92ec432b2363c686c8af2228afd9cf028754a Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sun, 18 Oct 2026 00:48:06 +0000 Subject: [PATCH 43/52] address review 9 (tg-core): pin which streams may bound an agent's level and three tolerance branches TGC9-1 (fixed): new tests pin the two rules of the level that only the coarse mutants were tested on; no code change, the shipped code is the correct side in each case. - test_levels_of_streams_between_zeros: the zero that bounds a level's reach is the furthest from the pinch. Cooling: process zeros at 450 (pinch), 445, 400 and 360 K shifted with S between the last two; boiling water (latent, 370 K) serves HL's 20 kW at level 330 K (catches cooling taking z[0]). Heating: steam at 415 K serves 5 kW at 420 K and leaves zeros at 420 and 440 K with S between them; steam at 431 K serves CL's 20 kW at level 461 K (catches heating taking z[-1]). Levels, duties, pricing temperatures and the GCC zeros are asserted in closed form, and each cost against an independent fresh_price. - test_levels_of_streams_across_the_pinch: a stream with no heat on the agent's side of the pinch is not served and does not cap the level. Steam at 407 K serves 2 kW at 402 K and natural gas 8 kW (CB lies below the pinch); its mirror, boiling water at 403 K serves 2 kW at 398 K and cooling water 8 kW (HB lies above it). Costs checked with fresh_price. TGC9-2 (nit, applied): - test_utilities_must_be_of_the_targets: a heating or a cooling duty off by +-1e-6 relative raises in _check_utilities, one off by 1e-12 does not. - test_from_heat_utilities_after_replaced_heat_utilities: a unit_duty off its stream's by +-1e-4 warns, one off by 1e-9 does not. - test_pricing_is_isolated: a '_hensmith_targets' flowsheet already registered is the same object after pricing and gets none of its streams. Mutation check (a copy of the package, the selected tests): the four mutants of the finding (M1c, M1h, M24, M25), the coarse all-in-reach ones, and M23, M29 (and the same threshold lowered to 1e-12) and M4 of the nit are all killed now. Tests: pytest tests/test_targeting.py hensmith/targeting.py: 87 passed. The synthesis path is untouched (test-only change). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- tests/test_targeting.py | 177 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 3f6500b..3d6b498 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -614,6 +614,16 @@ def test_pricing_is_isolated(): for a, (dump, items) in zip(agents, dumps): assert a.utility_stream_dump is dump and a.utility_stream_dump == items assert fresh_price(10 * kW, 450., 500., gas)[0] == before + # a flowsheet of the temporary one's ID that is already registered is + # registered again on exit, and gets none of the pricing's streams + flowsheets = bst.Flowsheet.flowsheet.__dict__ + mine = bst.Flowsheet('_hensmith_targets') + try: + assert [u.cost for u in t.utility_targets()] == [u.cost for u in first] + assert flowsheets['_hensmith_targets'] is mine and not mine.stream.data + assert registered() == registry + finally: + flowsheets.pop('_hensmith_targets', None) def vacuum_condenser(name, T_sat): """A vacuum condenser and a liquid cooler of water: 40 kmol/hr of vapour @@ -1233,6 +1243,147 @@ def test_levels_of_streams_past_a_zero(): results.append([(u.level, u.duty, u.cost) for u in cool]) assert_allclose(results[0], results[1], rtol=1e-12) +def latent_water(ID, T, phase, **prices): + """A latent water agent at `T` [K] (no `T_limit`): condensing steam + (`phase` 'g', a heating agent) or boiling water ('l', a cooling one).""" + water = bst.HeatUtility.thermo_water + return bst.UtilityAgent(ID, Water=1, T=T, P=water.chemicals.Water.Psat(T), + phase=phase, thermo=water, **prices) + +def test_levels_of_streams_between_zeros(): + # of several zeros of the balanced curve beyond the pinch, the heat of a + # level reaches only the furthest (the highest above the pinch, the + # lowest below it): a stream between two zeros moves no level, though it + # reaches beyond the agent's limit. Cooling: the curve is zero at + # shifted 450 K (the pinch), 445, 400 and 360 K. S (shifted 395 -> 365 + # K) lies between the zeros at 400 and 360 K and gives all its heat to + # CS; in reach, it would cap the boiling water (latent at 370 K, R = 375 + # K real) at S's limit, shifted 369.5 K, short of the zero at 360 K, + # where the water gets nothing, and chilled water would serve HL. The + # water serves HL (shifted 355 -> 335 K, 400 -> 380 K real) whole, at + # HL's limit, shifted 330 K. + dT = bst.HeatUtility.dT + rows = [('C1', 450., 500., 1.), ('HA', 455., 415., 1.), ('CA', 400., 440., 1.), + ('S', 400.5, 370.5, 1.), ('CS', 360., 390., 1.), ('HL', 400., 380., 1.)] + t = targets('test_targeting_between_zeros_cool', rows, 10., + approach_contributions={'S': .5, 'HL': 40.}) + shift = dict(zip(t.labels, t.shift)) + assert (shift['S'], shift['HL'], t.pinch_T) == (5.5, 45., 450.) + assert (t.Q_hot, t.Q_cold) == (pytest.approx(50. * kW, rel=1e-12), + pytest.approx(20. * kW, rel=1e-12)) + T, H = t.grand_composite_curve() + assert np.abs(H[np.isin(T, [450., 445., 400., 360.])]).max() <= 1e-6 + assert_allclose(H[np.isin(T, [390., 365.])], 5. * kW, rtol=1e-9) + boiling = latent_water('test_targeting_between_zeros_boiling', 370., 'l', + heat_transfer_price=1e-7) + cool = [u for u in t.utility_targets(None, [boiling, agent('chilled_water')]) + if not u.heating] + assert [u.ID for u in cool] == [boiling.ID] + u, = cool + assert u.level == pytest.approx(boiling.T + boiling.dT + dT - shift['HL'], abs=1e-9) + assert u.level == pytest.approx(330., abs=1e-9) + assert u.duty == pytest.approx(20. * kW, rel=1e-12) + assert (u.T_in, u.T_out) == (pytest.approx(400., abs=1e-9), + pytest.approx(380., abs=1e-9)) + assert u.cost == pytest.approx(fresh_price(-u.duty, 400., 380., boiling)[0], + rel=1e-9) + # heating: the curve has no zero above the pinch (shifted 400 K). A1 + # (latent at 415 K, R = 410 K real) serves 5 kW at shifted 420 K, where + # S, reaching beyond A1's limit (shifted 405.5 K), starts, and leaves + # zeros at 420 and 440 K, with Hb's heat between them. S (shifted 420 -> + # 430 K) lies between the two; in reach, it would cap A2 (latent at 431 + # K, R = 426 K real) at S's limit, shifted 421.5 K, short of the zero at + # 440 K, where A2 gets nothing, and natural gas would serve CL. A2 + # serves CL (shifted 440 -> 460 K, 405 -> 425 K real) whole, at CL's + # limit, shifted 461 K. + rows = [('C0', 400., 410., 1.), ('Ha', 430., 420., .5), ('S', 424.5, 434.5, 1.), + ('Hb', 450., 440., 1.), ('CL', 405., 425., 1.), ('Hlow', 410., 380., 1.)] + t = targets('test_targeting_between_zeros_heat', rows, 10., + approach_contributions={'S': .5, 'CL': 40.}) + shift = dict(zip(t.labels, t.shift)) + assert (shift['C0'], shift['S'], shift['CL'], t.pinch_T) == (0., 4.5, -35., 400.) + assert (t.Q_hot, t.Q_cold) == (pytest.approx(25. * kW, rel=1e-12), + pytest.approx(30. * kW, rel=1e-12)) + T, H = t.grand_composite_curve() + assert np.abs(H[T > 400.]).min() == pytest.approx(5. * kW, rel=1e-9) + assert_allclose(H[np.isin(T, [440., 420.])], 5. * kW, rtol=1e-9) + A1 = latent_water('test_targeting_between_zeros_A1', 415., 'g', + regeneration_price=0.2) + A2 = latent_water('test_targeting_between_zeros_A2', 431., 'g', + regeneration_price=0.2) + heat = [u for u in t.utility_targets([A1, A2, agent('natural_gas')], None) + if u.heating] + assert [u.ID for u in heat] == [A1.ID, A2.ID] + assert_allclose([u.level for u in heat], [420., 461.], atol=1e-9) + assert heat[1].level == pytest.approx(A2.T - A2.dT - dT - shift['CL'], abs=1e-9) + assert_allclose([u.duty for u in heat], [5. * kW, 20. * kW], rtol=1e-12) + assert_allclose([(u.T_in, u.T_out) for u in heat], [(400., 410.), (405., 425.)], + atol=1e-9) + for u, a in zip(heat, (A1, A2)): + assert u.cost == pytest.approx(fresh_price(u.duty, u.T_in, u.T_out, a)[0], + rel=1e-9) + +def test_levels_of_streams_across_the_pinch(): + # an agent serves only the streams with heat on its side of the pinch + # (cold streams above it, hot ones below it): a stream wholly on the + # other side moves no level, though it reaches beyond the agent's limit. + # Heating: the pinch is at shifted 400 K. CB (shifted 380 -> 398 K) lies + # below it and gets all its heat from H1; served, it would cap the steam + # (latent at 407 K, R = 402 K real) at CB's limit, shifted 397.5 K, + # below the pinch, where the steam gets nothing, and natural gas would + # serve all of Cup. The steam serves Cup (400 -> 410 K) up to its limit, + # 402 K, and natural gas the rest. + dT = bst.HeatUtility.dT + rows = [('Cup', 400., 410., 1.), ('CB', 384.5, 402.5, 1.), ('H1', 410., 380., 2.)] + t = targets('test_targeting_across_pinch_heat', rows, 10., + approach_contributions={'CB': .5}) + shift = dict(zip(t.labels, t.shift)) + assert (shift['Cup'], shift['CB'], t.pinch_T) == (0., 4.5, 400.) + assert (t.Q_hot, t.Q_cold) == (pytest.approx(10. * kW, rel=1e-12), + pytest.approx(42. * kW, rel=1e-12)) + steam = latent_water('test_targeting_across_pinch_steam', 407., 'g', + regeneration_price=0.2) + gas = agent('natural_gas') + heat = [u for u in t.utility_targets([steam, gas], None) if u.heating] + assert [u.ID for u in heat] == [steam.ID, gas.ID] + u, fuel = heat + assert u.level == pytest.approx(steam.T - steam.dT - dT - shift['Cup'], abs=1e-9) + assert u.level == pytest.approx(402., abs=1e-9) + assert_allclose([u.duty for u in heat], [2. * kW, 8. * kW], rtol=1e-12) + assert_allclose([(u.T_in, u.T_out) for u in heat], [(400., 402.), (402., 410.)], + atol=1e-9) + for u, a in zip(heat, (steam, gas)): + assert u.cost == pytest.approx(fresh_price(u.duty, u.T_in, u.T_out, a)[0], + rel=1e-9) + # cooling, its mirror: HB (shifted 420 -> 402 K) lies above the pinch + # (shifted 400 K) and gives all its heat to C1; served, it would cap the + # boiling water (latent at 403 K, R = 408 K real) at HB's limit, shifted + # 402.5 K, above the pinch, and cooling water would serve all of Hdown. + # The water serves Hdown (410 -> 400 K) down to its limit, 408 K, and + # cooling water the rest. + rows = [('Hdown', 410., 400., 1.), ('HB', 425.5, 407.5, 1.), ('C1', 400., 430., 2.)] + t = targets('test_targeting_across_pinch_cool', rows, 10., + approach_contributions={'HB': .5}) + shift = dict(zip(t.labels, t.shift)) + assert (shift['Hdown'], shift['HB'], t.pinch_T) == (10., 5.5, 400.) + assert (t.Q_hot, t.Q_cold) == (pytest.approx(42. * kW, rel=1e-12), + pytest.approx(10. * kW, rel=1e-12)) + boiling = latent_water('test_targeting_across_pinch_boiling', 403., 'l', + heat_transfer_price=1e-6) + CW = agent('cooling_water') + cool = [u for u in t.utility_targets(None, [boiling, CW]) if not u.heating] + assert [u.ID for u in cool] == [boiling.ID, CW.ID] + u = cool[0] + assert u.level == pytest.approx(boiling.T + boiling.dT + dT - shift['Hdown'], + abs=1e-9) + assert u.level == pytest.approx(398., abs=1e-9) + assert_allclose([u.duty for u in cool], [2. * kW, 8. * kW], rtol=1e-12) + assert_allclose([(u.T_in, u.T_out) for u in cool], [(410., 408.), (408., 400.)], + atol=1e-9) + for u, a in zip(cool, (boiling, CW)): + assert u.cost == pytest.approx(fresh_price(-u.duty, u.T_in, u.T_out, a)[0], + rel=1e-9) + def test_sensible_agent_without_driving_force(): # a sensible agent with agent.dT = 0 (UtilityAgent's default) has no # driving force left at a point load at its limit, agent.T - dT @@ -1379,6 +1530,18 @@ def test_utilities_must_be_of_the_targets(quickstart): for call in calls: with pytest.raises(ValueError, match='duties of utilities add up to'): call(utilities) + # they must add up to rounding: a heating or a cooling duty off by 1e-6 + # (far above the remainders of at most _tol that utility_targets leaves + # out) raises, one off by 1e-12 does not + for i in (0, -1): + for f, raises in ((1e-6, True), (-1e-6, True), (1e-12, False)): + off = list(own) + off[i] = own[i]._replace(duty=own[i].duty * (1. + f)) + if raises: + with pytest.raises(ValueError, match='duties of utilities add up to'): + t20.balanced_grand_composite_curve(off) + else: + t20.balanced_grand_composite_curve(off) for utilities in (own, partial): assert t20.units_target(utilities)['total'] > 0 t20.balanced_composite_curves(utilities) @@ -1836,6 +1999,20 @@ def select(sys): assert t.labels == facility.labels assert (t.Q_hot, t.Q_cold) == (pytest.approx(facility.Q_hot, rel=1e-12), pytest.approx(facility.Q_cold, rel=1e-12)) + # a duty off by 1e-4 of its stream's is another than the stream's; one + # off by 1e-9 is the stream's up to rounding + units = cp_units('test_targeting_duty_off', REF_A) + hus = [u.heat_utilities[0] for u in units] + dH = units[0].outs[0].H - units[0].ins[0].H + for f, off in ((1e-4, True), (-1e-4, True), (1e-9, False)): + hus[0].unit_duty = dH * (1. + f) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + PinchTargets.from_heat_utilities(hus, 10.) + messages = [str(x.message) for x in w + if 'other than their process' in str(x.message)] + assert len(messages) == off + if off: assert messages[0].startswith("the heat utilities of 'H1' have duties") def test_is_hot_validated(): # is_hot must agree with each stream's direction (a stream without duty From 6d900f95f937b3a941e71b0795ba44ae0cf9833a Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sun, 18 Oct 2026 00:53:14 +0000 Subject: [PATCH 44/52] address review 9 (tg-super): let area and capital targets take the agents h_utilities may key TGS9-1 (fixed): area_target and capital_cost_target rejected an h_utilities key that is the ID of a custom agent whenever that agent served nothing at the targets' T_min_app (utility_targets left it out), although summary, supertarget and network_report accept the same mapping because they pass their heating_agents and cooling_agents as known agents. - PinchTargets.area_target and capital_cost_target take a new optional keyword `agents` (last, default None: no change otherwise): agents whose IDs h_utilities may key as well, or IDs of agents of biosteam's lists, i.e. the heating_agents and cooling_agents the utilities were priced with. A new _key_agents validates it (TypeError for a bare str/agent or a non-agent item, ValueError for an ID not in biosteam's lists, with the same hint as _agents). It changes nothing but the known keys. - The h_utilities documentation of area_target now states that an ID must name an agent of `utilities`, of `agents` or of biosteam's lists, and how to key another agent (by the UtilityAgent, or via `agents`). - The error now says where the key was looked for and hints at the object key: "h_utilities keys that name no agent of the utilities, of agents, or of biosteam's lists: 'x' (key another agent by the UtilityAgent itself)"; summary, supertarget and network_report name "heating_agents or cooling_agents" instead of "agents". - New test test_h_utilities_keys_of_agents_serving_nothing: a custom agent served at T_min_app = 20 K and idle at 5 K; with agents the ID key matches the object key and summary at both points, in area and capital (and agents alone change nothing); without agents the idle point raises the new message; summary's message; validation of agents. Tests: tests/test_targeting.py plus the doctests of hensmith/targeting.py: 88 passed. Default-off oracle unchanged (29 cases, sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- hensmith/targeting.py | 81 ++++++++++++++++++++++++++++++++--------- tests/test_targeting.py | 55 ++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 17 deletions(-) diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 5df5436..956ad23 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -79,6 +79,9 @@ 'P', 'heat_exchanger_type', 'material', 'interest', 'years', 'operating_hours', 'tea') +#: The options of `PinchTargets.summary` whose agents `h_utilities` may key. +_OPTION_AGENTS = 'heating_agents or cooling_agents' + UtilityTarget = namedtuple('UtilityTarget', ( 'ID', 'agent', 'heating', 'level', 'duty', 'utility_duty', 'flow', 'cost', 'T_in', 'T_out', 'T_utility_in', 'T_utility_out', 'shift'), @@ -358,17 +361,18 @@ def _film_value(x, label): if math.isfinite(value) and value > 0.: return value raise ValueError(f'{label} must be a finite number > 0 kW/m2/K, not {x!r}') -def _utility_films(utilities, h_utilities, agents=()): +def _utility_films(utilities, h_utilities, agents=(), where='agents'): """ The film coefficient [kW/m²/K] of each of `utilities` from `h_utilities`: None (`_H_DEFAULT`), a number, or a mapping from agent - IDs or agents to numbers (`_H_DEFAULT` for an agent it leaves out). A - key must name an agent of `utilities`, of `agents` or of biosteam's - default lists. + IDs or agents to numbers (`_H_DEFAULT` for an agent it leaves out). An + ID key must name an agent of `utilities`, of `agents` (the option + `where` names in the error) or of biosteam's default lists. """ - return _agent_films([u.agent for u in utilities], h_utilities, agents) + return _agent_films([u.agent for u in utilities], h_utilities, agents, + where) -def _agent_films(of, h_utilities, agents=()): +def _agent_films(of, h_utilities, agents=(), where='agents'): """`_utility_films` of the agents `of` (None for an unserved utility).""" if h_utilities is None: return [_H_DEFAULT] * len(of) if not isinstance(h_utilities, Mapping): @@ -394,8 +398,10 @@ def _agent_films(of, h_utilities, agents=()): f'{ID!r} different values: {other_value!r} and ' f'{value!r}') if unknown: - raise ValueError('h_utilities keys that name no agent: ' - f"{', '.join(map(repr, unknown))}") + raise ValueError('h_utilities keys that name no agent of the ' + f"utilities, of {where}, or of biosteam's lists: " + f"{', '.join(map(repr, unknown))} (key another agent " + 'by the UtilityAgent itself)') return [values[a.ID][1] if a is not None and a.ID in values else _H_DEFAULT for a in of] @@ -599,6 +605,31 @@ def _agents(agents, heating): resolved.append(agent) return resolved +def _key_agents(agents): + """The `agents` of `PinchTargets.area_target` as a list (None is + none), each ID resolved among biosteam's heating and cooling agents as + they are now; a ValueError for an unknown ID.""" + if agents is None: return [] + if isinstance(agents, (str, bst.UtilityAgent)): + raise TypeError('agents must be a sequence of agents (or their IDs), ' + f'not {type(agents).__name__!r}') + known = [*bst.HeatUtility.heating_agents, *bst.HeatUtility.cooling_agents] + resolved = [] + for agent in agents: + if isinstance(agent, str): + match = [a for a in known if a.ID == agent] + if not match: + raise ValueError( + f'no agent {agent!r} in bst.HeatUtility.heating_agents or ' + 'cooling_agents; pass the UtilityAgent itself for another ' + 'one') + agent = match[0] + elif not isinstance(agent, bst.UtilityAgent): + raise TypeError('agents must be UtilityAgent objects or their IDs ' + f'(str), not {type(agent).__name__!r} ({agent!r})') + resolved.append(agent) + return resolved + def _economics(interest=None, years=None, operating_hours=None, tea=None): """ ``(crf, hours)``: the capital recovery factor [1/yr] and the operating @@ -1907,7 +1938,8 @@ def _area(self, utilities, h, h_utilities): utilities, 1. / h, [1. / x for x in h_utilities]) return _bath(hot, cold) - def area_target(self, utilities=None, h=None, h_utilities=None): + def area_target(self, utilities=None, h=None, h_utilities=None, + agents=None): """ The area target [m²] of vertical heat transfer between the balanced composite curves: Bath's formula (see Notes of the class). @@ -1939,8 +1971,17 @@ def area_target(self, utilities=None, h=None, h_utilities=None): for every stream. h_utilities : float or Mapping, optional Film coefficient of the utilities [kW/m²/K]: one for all, or a - mapping from agent IDs or agents (the others take 1). Defaults - to 1. + mapping from agent IDs or agents (the others take 1). An ID must + name an agent of `utilities`, of `agents` or of biosteam's + lists, or a ValueError is raised: key another agent by the + UtilityAgent itself, or pass it in `agents`. Defaults to 1. + agents : Sequence[UtilityAgent or str], optional + Agents whose IDs `h_utilities` may key as well, or the IDs of + agents of biosteam's lists (which it may key anyway): the + `heating_agents` and `cooling_agents` that `utilities` were + priced with, so that one `h_utilities` serves at every + `T_min_app`, an agent that serves nothing at one included, as + for `summary`. They change nothing else. Defaults to none. Returns ------- @@ -1952,7 +1993,8 @@ def area_target(self, utilities=None, h=None, h_utilities=None): """ if utilities is None: utilities = self.utility_targets() return self._area(utilities, self._film_coefficients(h), - _utility_films(utilities, h_utilities)) + _utility_films(utilities, h_utilities, + _key_agents(agents))) def units_target(self, utilities=None): """ @@ -2042,7 +2084,7 @@ def _capital(self, utilities, h, h_utilities, P, heat_exchanger_type, def capital_cost_target(self, utilities=None, h=None, h_utilities=None, P=101325., heat_exchanger_type='Floating head', - material='Carbon steel/carbon steel'): + material='Carbon steel/carbon steel', agents=None): """ The capital cost target: the area target in as many shells as the units target (less a furnace), split equally over all of them or @@ -2071,6 +2113,9 @@ def capital_cost_target(self, utilities=None, h=None, h_utilities=None, material : str, optional biosteam's shell and tube material. Defaults to 'Carbon steel/carbon steel'. + agents : Sequence[UtilityAgent or str], optional + Agents whose IDs `h_utilities` may key as well, as `area_target` + takes them. Returns ------- @@ -2087,7 +2132,8 @@ def capital_cost_target(self, utilities=None, h=None, h_utilities=None, """ if utilities is None: utilities = self.utility_targets() return self._capital(utilities, self._film_coefficients(h), - _utility_films(utilities, h_utilities), P, + _utility_films(utilities, h_utilities, + _key_agents(agents)), P, heat_exchanger_type, material)[0] def _check_options(self, options): @@ -2115,7 +2161,7 @@ def _check_options(self, options): self._film_coefficients(get('h')) _utility_films((), get('h_utilities'), [*(get('heating_agents') or ()), - *(get('cooling_agents') or ())]) + *(get('cooling_agents') or ())], _OPTION_AGENTS) return options def summary(self, **options): @@ -2161,7 +2207,7 @@ def summary(self, **options): utilities = self.utility_targets(heating_agents, cooling_agents) h_utilities = _utility_films( utilities, get('h_utilities'), - [*(heating_agents or ()), *(cooling_agents or ())]) + [*(heating_agents or ()), *(cooling_agents or ())], _OPTION_AGENTS) capital, N = self._capital( utilities, self._film_coefficients(get('h')), h_utilities, get('P', 101325.), get('heat_exchanger_type', 'Floating head'), @@ -2651,7 +2697,8 @@ def _network_areas(HXN, targets, h, h_utilities, agents): _profile_area(x, T_h(x) - T_c(x), 1. / h[i] + 1. / h[j], hx.ID), hx.design_results.get('Area', 0.) / _FT2_PER_M2) for hx, agent, h_agent in zip(utility, of, - _agent_films(of, h_utilities, agents)): + _agent_films(of, h_utilities, agents, + _OPTION_AGENTS)): if agent.isfuel: areas[hx.ID] = (0., 0.) continue diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 3d6b498..3a57fb4 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -1588,6 +1588,61 @@ def test_utilities_of_another_shift(): utilities = t.at(0.).utility_targets() assert t.units_target(utilities) == t.units_target(t.utility_targets()) +def test_h_utilities_keys_of_agents_serving_nothing(): + # a custom agent serves 10 kW at T_min_app = 20 K and nothing at 5 K + # (the curves are 10 K apart): its ID keys h_utilities of area_target + # and capital_cost_target at both once the agents the utilities were + # priced with are passed in agents, as summary takes them from its + # heating_agents and cooling_agents; without them, only where it serves + t = targets('test_targeting_key_agents', [('H1', 450., 300., 1.), + ('C1', 290., 440., 1.)], 10.) + LP = agent('low_pressure_steam') + oil = bst.UtilityAgent('test_targeting_key_oil', Water=1, T=600., P=1e7, + phase='g', thermo=bst.HeatUtility.thermo_water, + regeneration_price=.5) + by_ID_ = {'test_targeting_key_oil': .3} + by_agent = {oil: .3} + served, idle = t.at(20.), t.at(5.) + for at in (served, idle): + utilities = at.utility_targets([LP, oil]) + area = at.area_target(utilities, h_utilities=by_agent) + capital = at.capital_cost_target(utilities, h_utilities=by_agent) + for agents in ([LP, oil], ['low_pressure_steam', oil], (oil,)): + assert at.area_target(utilities, h_utilities=by_ID_, + agents=agents) == area + assert at.capital_cost_target(utilities, h_utilities=by_ID_, + agents=agents) == capital + # they change nothing else + assert (at.area_target(utilities, agents=agents) + == at.area_target(utilities)) + summary = at.summary(heating_agents=[LP, oil], h_utilities=by_ID_) + assert (summary['area'], summary['installed_cost']) == ( + area, capital['installed_cost']) + utilities = served.utility_targets([LP, oil]) + assert 'test_targeting_key_oil' in [u.ID for u in utilities] + assert (served.area_target(utilities, h_utilities=by_ID_) + == served.area_target(utilities, h_utilities=by_agent) + > served.area_target(utilities)) + assert idle.utility_targets([LP, oil]) == [] + message = ("h_utilities keys that name no agent of the utilities, of " + "agents, or of biosteam's lists: 'test_targeting_key_oil' " + r'\(key another agent by the UtilityAgent itself\)') + for call in (idle.area_target, idle.capital_cost_target): + with pytest.raises(ValueError, match=message): + call([], h_utilities=by_ID_) + with pytest.raises(ValueError, match=message): + call([], h_utilities=by_ID_, agents=[LP]) + with pytest.raises(ValueError, match='of heating_agents or cooling_agents, or'): + idle.summary(heating_agents=[LP], h_utilities=by_ID_) + for agents, error, match in ( + ('low_pressure_steam', TypeError, 'agents must be a sequence'), + (oil, TypeError, 'agents must be a sequence'), + (['steam'], ValueError, "no agent 'steam' in bst.HeatUtility"), + ([1], TypeError, 'agents must be UtilityAgent objects or their IDs')): + for call in (idle.area_target, idle.capital_cost_target): + with pytest.raises(error, match=match): + call([], agents=agents) + def test_bath_independent_integration(quickstart): # reference A: the streams plus the four utilities t = targets('test_targeting_integration_A', REF_A, 10.) From f18ff50d6ec74a69e6abe050c09af3ca4a088dc5 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sun, 18 Oct 2026 01:47:08 +0000 Subject: [PATCH 45/52] address review 9 (tg-report): keep the legend of the composite curves off their labels TGR9-1 (fixed): plot_composite_curves left the legend to matplotlib's 'best' location, which counts an overlapped label like one curve vertex, so on smaller axes (KEMP, REF_A, FURNACE and the liquids network at 4x3 in., or the middle panel of a constrained 1x3 grid at 10x4 in.) it took a spot over one or two agent labels. The legend is now a _ClearLegend (a matplotlib Legend added as Axes.legend adds one) whose 'best' hook, run when drawn as the labels are placed, treats the texts of the axes as obstacles: of matplotlib's spots in the axes, in its order, it takes the one clear of every text with matplotlib's least count of curve points and crossings, leaders (where drawn now, via the new _Leader.path) and band, which is matplotlib's own spot wherever that is clear of the labels; where every spot covers a text, it goes above the axes (and any title), left-aligned, where constrained layout and bbox_inches='tight' make room. It is used for every kind (real and shifted labels were covered the same way on 3x3 in. axes). The Notes of plot_composite_curves say so, and the label-overlap caveat now names short axes that a legend above them leaves shorter. Sweeps (11 problems, kinds real/shifted/balanced, single axes and constrained grids): at 7.5x4.2 (default), 6x4, 5x3.5, 4.5x4.2, 4x3 and 1x2/1x3 grids, legend-over-label cases go from 14 to 0, every other legend box and ylim is unchanged, and no labels newly overlap each other. On 2.5 to 3.5 in. tall axes, the cases go from 143 to 0. In 9 configurations with 5 or 6 agents on 2.5 in. tall axes, no spot in the axes is clear, so the legend goes above them. The shorter axes then let some labels overlap each other, as the caveat says, where before the legend hid them. The docs/_demo_src ch05 script, run alone, reproduces every asset byte for byte. Tests: test_plot_readability adds clear_legend(), which checks the legend is off every text and either inside the axes or above them and the title. It covers these cases: FURNACE and liquids at 4x3 and as the middle panel of a constrained 1x3 at 10x4 (labels readable, none overlapping, legend inside); liquids real/shifted at 3x3; and a user's texts on all nine spots (legend above the axes, then above the title). At the default size it also checks the location equals plain ax.legend(loc='best'). The new checks fail on the old code (FURNACE 4x3: legend over high_pressure_steam). pytest hensmith/targeting.py tests/test_targeting.py: 88 passed; pytest hensmith/_heat_exchanger_network.py: 2 passed; sphinx-build -W clean. Synthesis path untouched (oracle not needed). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- hensmith/targeting.py | 108 +++++++++++++++++++++++++++++++++++----- tests/test_targeting.py | 70 ++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 12 deletions(-) diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 956ad23..de8954e 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -26,6 +26,7 @@ from numbers import Integral, Real from warnings import catch_warnings, filterwarnings, warn import numpy as np +from matplotlib.legend import Legend from matplotlib.patches import Patch from matplotlib.path import Path import biosteam as bst @@ -3103,11 +3104,16 @@ def __init__(self, labels, index, **kwargs): def get_path(self): return self._path + def path(self, renderer): + """The path of the leader where the labels are placed for the + `renderer` (in display; empty without a leader).""" + ends = self.labels.spots(renderer)[self.index][1] + return Path(np.zeros((0, 2)) if ends is None else np.array(ends)) + def draw(self, renderer): if not self.get_visible(): return - ends = self.labels.spots(renderer)[self.index][1] - self._path = Path(np.zeros((0, 2)) if ends is None else np.array(ends)) - if ends is not None: super().draw(renderer) + self._path = self.path(renderer) + if len(self._path.vertices): super().draw(renderer) class _Stacked(_Labels): """ @@ -3334,6 +3340,74 @@ def score(side, vertical, slide=0.): spots.append(((box.x0, box.y0), None)) return spots +class _ClearLegend(Legend): + """ + A legend (of loc 'best') at matplotlib's 'best' location in its axes + among the spots that cover none of their texts (the labels), decided + when drawn, as the labels are placed: of the spots matplotlib tries in + the axes, the first, in its order, of those clear of the texts that + covers the least of the rest of the axes, by matplotlib's count (of + the points of their lines, patches and collections in it, of their + crossings of it, and of the rectangles, such as a shaded band, that it + overlaps; the leaders where they are drawn), which is matplotlib's + location wherever that is clear of the texts. Else, where each of them + covers a text, it goes above the axes (and any title of them), + left-aligned with them, where a constrained layout or a tight bounding + box makes room for it. + """ + # the anchors of the locations 1 to 10 ('upper right' to 'center') + _anchors = ('NE', 'NW', 'SW', 'SE', 'E', 'W', 'E', 'S', 'N', 'C') + + def _find_best_position(self, width, height, renderer): + # matplotlib's hook of the 'best' location (which the legend would + # fall back to without it): the display (x, y) of the lower left + # corner of the legend of `width` and `height` + from matplotlib.collections import PolyCollection + from matplotlib.patches import Rectangle + from matplotlib.text import Text + from matplotlib.transforms import Bbox + ax = self.parent + texts = [text.get_window_extent(renderer) for text in ax.texts + if text.get_visible() and text.get_text()] + paths = [line.get_transform().transform_path(line.get_path()) + for line in ax.lines] + boxes, points = [], [] + for patch in ax.patches: + if isinstance(patch, _Leader): + paths.append(patch.path(renderer)) + elif isinstance(patch, Rectangle): + boxes.append(patch.get_bbox().transformed(patch.get_data_transform())) + else: + paths.append(patch.get_transform().transform_path(patch.get_path())) + for collection in ax.collections: + if isinstance(collection, PolyCollection): + transform = collection.get_transform() + paths += [transform.transform_path(path) + for path in collection.get_paths()] + elif len(collection.get_offsets()): + points.append(collection.get_offset_transform().transform( + collection.get_offsets())) + points = np.concatenate(points) if points else np.zeros((0, 2)) + pad = self.borderaxespad * renderer.points_to_pixels( + self.prop.get_size_in_points()) + frame = self.get_bbox_to_anchor() + size = Bbox.from_bounds(0., 0., width, height) + best = None + for anchor in self._anchors: + box = size.anchored(anchor, container=frame.padded(-pad)) + if any(box.overlaps(text) for text in texts): continue + badness = (sum(box.count_contains(path.vertices) + + path.intersects_bbox(box, filled=False) for path in paths) + + box.count_contains(points) + box.count_overlaps(boxes)) + if best is None or badness < best[0]: best = (badness, box.p0) + if not badness: break + if best is not None: return best[1] + # the titles are the texts of the axes besides `texts` + titles = [title.get_window_extent(renderer).y1 for title in ax.get_children() + if isinstance(title, Text) and title not in ax.texts + and title.get_visible() and title.get_text()] + return frame.x0, max([frame.y1, *titles]) + pad + def plot_composite_curves(targets, kind='real', utilities=None, ax=None, file=None, dpi=300): """ @@ -3407,14 +3481,20 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, the order inside the axes, those that would run past the edge are moved sideways past the label in their way. Labels can then still overlap, on axes too small for them all (many agents on small axes without a - layout engine, say): a larger figure, or one with - ``layout='constrained'``, gives them room. A label left more than 1.5 - times its height away from its anchor gets a thin leader from it - ('Leader:', an empty path otherwise), and each label a halo, - so that a leader passing behind it strikes none of its letters. The - legend goes where it covers the least of the curves, the band and the - labels (matplotlib's 'best' location: no fixed corner is clear of the - composites of every problem). + layout engine, say, or on short axes that a legend above them leaves + shorter): a larger figure, or one with ``layout='constrained'``, gives + them room. A label left more than 1.5 times its height away from its + anchor gets a thin leader from it ('Leader:', an empty path + otherwise), and each label a halo, so that a leader passing behind it + strikes none of its letters. The legend, of any kind, covers none of + the labels: when drawn, it goes to the spot of matplotlib's 'best' + location in the axes that covers the least of the curves, the leaders + and the band among those clear of the labels (matplotlib's own spot + wherever that is clear of them: no fixed corner is clear of the + composites of every problem); or, where each of those spots would cover + a label (on small axes, say), above the axes and any title of them, + left-aligned with them, where a constrained layout or a tight bounding + box makes room for it. Examples -------- @@ -3597,7 +3677,11 @@ def plot_composite_curves(targets, kind='real', utilities=None, ax=None, ax.set_ylabel('shifted T [°C]' if shifted else 'T [°C]') _sci_ticks(ax.xaxis) ax.set_xlabel('H [kJ·h$^{-1}$]') - ax.legend(loc='best', fontsize=8) + # at matplotlib's 'best' location among the spots clear of the labels, + # or above the axes if none is (added as `Axes.legend` adds one) + ax.legend_ = _ClearLegend(ax, *ax.get_legend_handles_labels(), loc='best', + fontsize=8) + ax.legend_._remove_method = ax._remove_legend ax.grid(alpha=0.3) if file: fig.savefig(file, dpi=dpi, bbox_inches='tight') return fig, ax diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 3a57fb4..cfc9835 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -3442,6 +3442,76 @@ def in_order(fig, ax): + _gid_artists(ax, 'Cold composite')) finally: plt.close(fig) + # the legend of the composite curves of any kind over none of the + # labels, decided when drawn: at matplotlib's 'best' location where + # that is clear of them (as at the default size); on smaller axes (of + # a figure of the user's, alone or the middle one of three in a row), + # where each spot clear of the curves holds a label, at the spot clear + # of the labels that covers the least of the curves; and above the axes + # (and their title) where each spot in the axes covers a text + def clear_legend(fig, ax): + """The legend inside the figure and off every text of `ax`: inside + the axes, or above them (and their title), left-aligned with them; + whether it is inside them.""" + fig.canvas.draw() + renderer = fig.canvas.get_renderer() + box = ax.get_legend().get_window_extent(renderer) + for text in ax.texts: + assert not box.overlaps(text.get_window_extent(renderer)), text.get_text() + frame, page = ax.get_window_extent(renderer), fig.bbox + assert (page.x0 <= box.x0 and box.x1 <= page.x1 + and page.y0 <= box.y0 and box.y1 <= page.y1) + if (frame.x0 <= box.x0 and box.x1 <= frame.x1 + and frame.y0 <= box.y0 and box.y1 <= frame.y1): + return True + top = ax.title.get_window_extent(renderer).y1 if ax.get_title() else frame.y1 + pad = 0.5 * 8. * fig.dpi / 72. # borderaxespad of the 8 point font + assert box.x0 == pytest.approx(frame.x0) and box.y0 == pytest.approx(top + pad) + return False + for t, kind in ((furnace, 'balanced'), (liquid, 'balanced'), (kemp, 'real'), + (liquid, 'shifted')): + fig, ax = plot_composite_curves(t, kind=kind) + try: + assert clear_legend(fig, ax) + ours, = boxes(fig, [ax.get_legend()]) + mpl_best, = boxes(fig, [ax.legend(loc='best', fontsize=8)]) + assert ours.bounds == pytest.approx(mpl_best.bounds, abs=1e-6) + finally: + plt.close(fig) + for t in (furnace, liquid): + for size, columns in (((4., 3.), 1), ((10., 4.), 3)): + fig, axes = plt.subplots(1, columns, figsize=size, layout='constrained') + try: + ax = axes[1] if columns > 1 else axes + plot_composite_curves(t, kind='balanced', ax=ax) + labels = _gid_artists(ax, 'Utility:') + _gid_artists(ax, 'Fuel') + readable(fig, ax, labels, [line for line in ax.get_lines() + if 'balanced composite' in (line.get_gid() or '')], + legend=False) + label_boxes = boxes(fig, labels) + assert all(not a.overlaps(b) for i, a in enumerate(label_boxes) + for b in label_boxes[i + 1:]) + assert clear_legend(fig, ax) + finally: + plt.close(fig) + for kind in ('real', 'shifted'): + fig, ax = plt.subplots(figsize=(3., 3.), layout='constrained') + try: + plot_composite_curves(liquid, kind=kind, ax=ax) + assert clear_legend(fig, ax) + finally: + plt.close(fig) + fig, ax = plt.subplots(figsize=(6., 4.), layout='constrained') + try: + plot_composite_curves(liquid, kind='real', ax=ax) + for x, ha in ((0.02, 'left'), (0.5, 'center'), (0.98, 'right')): + for y, va in ((0.03, 'bottom'), (0.5, 'center'), (0.97, 'top')): + ax.text(x, y, 'a note of the user', ha=ha, va=va, transform=ax.transAxes) + assert not clear_legend(fig, ax) + ax.set_title('composite curves') + assert not clear_legend(fig, ax) + finally: + plt.close(fig) # the grand composite curve at its default size: heats of six digits # [kJ/hr] in scientific notation, whose tick labels do not run together gcc_A = targets('test_targeting_plot_readable_gcc', REF_A, 10.) From 915e5cdd46a5649280c5db6a25505c5e4e450331 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sun, 18 Oct 2026 01:57:16 +0000 Subject: [PATCH 46/52] address review 9 (e2e): warn of written-over heat utilities only when a network wrote them, refuse repeated ones, name curves by label E2E-R9-1 (fixed): PinchTargets.from_heat_utilities warned whenever a heat utility's unit_duty differed from its stream's outs[0].H - ins[0].H by more than 1e-6, and blamed a HeatExchangerNetwork simulated with replace_unit_heat_utilities=True. biosteam's MESHDistillation total condenser (full_condenser=True) reports a duty 0.1 % off its own streams' with no network anywhere, so the selection and a facility's targets() before any simulation warned of a network that never ran, and told the caller of targets() to call targets(). The facility module now keeps a weak set of the facilities holding heat utilities written over by _replace_unit_heat_utilities (added there, discarded by _restore_unit_heat_utilities), and _replacing_networks(hus) maps each such heat utility to its facility. The warning fires only for an off-stream heat utility that a facility holds written over, names that facility, and advises '.targets()' (never the facility being asked, whose pre-simulation path implies it has replaced nothing). Any other off-stream duty warns of nothing: the targets are the streams'. Docstring Warns section, _duty_off_stream note. Tests: the replaced-KEMP test matches the new text (network 'HXN', HXN.targets()); its threshold part now runs on a written-over heat utility (1e-4 warns, 1e-9 does not, value restored) and checks that an unreplaced 1e-4 offset is silent; new test_from_heat_utilities_of_a_total_condenser (MESHDistillation with full_condenser=True: no warning from the selection or from HeatExchangerNetwork(units=sys.units).targets(), equal targets). E2E-R9-2 (fixed): PinchTargets.__init__ builds the curves it is not given with the targets' labels, so curve warnings raised by HXN.targets(), network_report and supertarget name 'C6', not a private copy's 's9' (numbers unchanged: labels only name curves in warnings and reprs). labels docstring; the replaced-KEMP test checks the curve labels. E2E-R9-3 (fixed): from_heat_utilities raises a ValueError naming the unit when two heat utilities with duty are of the same unit (the same one given twice, as by overlapping selections such as a unit and its auxiliary), whose stream would count once per heat utility. Raises docstring, tutorial ch05 sentence; test in test_from_heat_utilities_takes_exchangers. Tests: pytest hensmith/targeting.py hensmith/_heat_exchanger_network.py tests/test_targeting.py tests/test_hxn_targets.py: 133 passed; tests/test_hxn.py -k replace: 2 passed. Oracle (default-off synthesis): sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf, unchanged. No docs captures change (none show curve warnings); sphinx is not installed here, the rst change is plain prose. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/tutorial/05_targeting.rst | 4 +- hensmith/_heat_exchanger_network.py | 23 ++++++++ hensmith/targeting.py | 63 +++++++++++++++++---- tests/test_targeting.py | 80 ++++++++++++++++++++++----- 4 files changed, 143 insertions(+), 27 deletions(-) diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index 0fb02d2..f62b5b5 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -68,7 +68,9 @@ facility, from the heat utilities of heat exchangers with ``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. Given +first inlet and outlet need not be the stream it heats or cools, and so does +a heat utility given twice (as selections from overlapping unit lists give +it), whose stream would count twice. 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 diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index 7745b62..9f36535 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -17,6 +17,7 @@ @author: sarangbhagwat and yoelcp """ import heapq +import weakref from collections.abc import Mapping import biosteam as bst import numpy as np @@ -36,6 +37,26 @@ #: network does leave is 3e-7 of the stream's duty). _SERVED_RTOL = 1e-9 +#: The facilities whose `_replace_unit_heat_utilities` has written their +#: utilities over heat utilities of their units that +#: `_restore_unit_heat_utilities` has not given back yet (see +#: `_replacing_networks`). +_REPLACING = weakref.WeakSet() + +def _replacing_networks(hus): + """ + The facilities that hold heat utilities of `hus` written over by + `HeatExchangerNetwork._replace_unit_heat_utilities` and not given back + yet, by the ``id`` of each such heat utility (a unit simulated again + since holds new heat utilities, which are not among them). + """ + ids = set(map(id, hus)) + networks = {} + for network in list(_REPLACING): + for hu, _ in getattr(network, '_replaced_heat_utilities', None) or (): + if id(hu) in ids: networks.setdefault(id(hu), network) + return networks + def _pass_through_served_streams(stream_life_cycles, original_units): """ Let every stream that the process exchangers bring to its outlet (to @@ -609,6 +630,7 @@ def _replace_unit_heat_utilities(self, heat_utilities, stream_life_cycles): else: hu.empty() # a stream the process exchangers serve _load_utility_costs(hu.unit) self._replaced_heat_utilities = replaced + _REPLACING.add(self) def _restore_unit_heat_utilities(self): """ @@ -625,6 +647,7 @@ def _restore_unit_heat_utilities(self): left, would drop out of the next network. """ replaced = getattr(self, '_replaced_heat_utilities', None) + _REPLACING.discard(self) if not replaced: return self._replaced_heat_utilities = None for hu, original in replaced: diff --git a/hensmith/targeting.py b/hensmith/targeting.py index de8954e..b49a77a 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -30,7 +30,7 @@ from matplotlib.patches import Patch from matplotlib.path import Path import biosteam as bst -from ._curves import _T_EQ, _copy +from ._curves import StreamCurve, _T_EQ, _copy from ._planner import _approach_contributions, _given_contributions from .hxn_synthesis import (_problem_table, _pinch_cut, _format_H, _arrange_heat_utilities, _process_streams, _explicit_contributions, @@ -310,7 +310,9 @@ def _duty_off_stream(hu): (``hu.unit_duty``) differs from that of its process stream (``outs[0].H - ins[0].H`` of its unit) beyond rounding, as for the heat utilities that a heat exchanger network simulated with - ``replace_unit_heat_utilities`` writes its utilities over or empties.""" + ``replace_unit_heat_utilities`` writes its utilities over or empties + (and, by 0.1 %, that of biosteam's `MESHDistillation` total + condenser).""" unit = hu.unit dH = unit.outs[0].H - unit.ins[0].H return abs(dH - hu.unit_duty) > 1e-6 * max(abs(dH), abs(hu.unit_duty)) @@ -687,7 +689,9 @@ class PinchTargets: ``T_min_app / 2``), or a finite number > 0 (`T_min_app` must then be finite and > 0 too). All unspecified is the same as None. labels : list[str], optional - Display name of each stream. Defaults to the IDs of `streams_inlet`. + Display name of each stream, which the curves built here also take + (to name their streams in their warnings). Defaults to the IDs of + `streams_inlet`. curves : list[StreamCurve], optional Prebuilt temperature-enthalpy curves of the streams (built here if not given). @@ -1057,6 +1061,9 @@ def __init__(self, streams_inlet, streams_quenched, is_hot, T_min_app=5., labels = [s.ID for s in streams_inlet] elif len(labels) != N: raise ValueError(f'{len(labels)} labels given for {N} streams') + if curves is None: # named by the labels in their warnings + curves = [StreamCurve(si, so, hot, label=label) for si, so, hot, label + in zip(streams_inlet, streams_quenched, is_hot, labels)] if x is None: table, curves, grid = _problem_table( streams_inlet, streams_quenched, is_hot, T_min_app, curves) @@ -1164,15 +1171,26 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, heat exchanger's (``hu.hxn_ok`` False, as for the heat utilities that reactors, mixers or compressors add with ``Unit.add_heat_utility``: their unit's first inlet and outlet - need not be the stream they heat or cool). + need not be the stream they heat or cool), or if two with duty + are of the same unit (the same heat utility given twice, as by + selections from overlapping unit lists), whose stream would + count once per heat utility. Warns ----- RuntimeWarning - If the duty of a heat exchanger's heat utility (``hu.unit_duty``) - differs from that of its process stream (``outs[0].H - - ins[0].H`` of its unit, which the targets use) beyond rounding, - as after a simulation with ``replace_unit_heat_utilities=True``. + If a heat utility is one that a `HeatExchangerNetwork` + simulated with ``replace_unit_heat_utilities=True`` has written + its utilities over (and not given back yet, as its next + simulation does), with a duty (``hu.unit_duty``) other than its + process stream's (``outs[0].H - ins[0].H`` of its unit, which + the targets use) beyond rounding: the warning names that + network, whose `HeatExchangerNetwork.targets` are the process's. + A duty that differs from its stream's for another reason warns + of nothing, the targets being the streams' (the total condenser + of a `biosteam.MESHDistillation` with ``full_condenser=True`` + reports a duty about 0.1 % off the enthalpy change of its own + streams, for instance). """ hus = list(hus) @@ -1196,16 +1214,37 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, f'(hu.hxn_ok), not those of {names}, whose first ' 'inlet and outlet need not be the stream they ' f'heat or cool: {selection}') + seen = set() + repeated = [hu for hu in hus if hu.duty and (id(hu.unit) in seen + or seen.add(id(hu.unit)))] + if repeated: + names = ', '.join(dict.fromkeys(repr(_unit_label(hu.unit)) + for hu in repeated)) + raise ValueError(f'the stream of {names} would count once per ' + 'heat utility of its unit given (the same heat ' + 'utility given twice, as by selections from ' + 'overlapping unit lists, such as a unit and an ' + 'auxiliary unit of it): give each heat ' + "exchanger's heat utility once") + # only those that a network has written over: a duty may differ from + # its stream's for another reason (biosteam's MESHDistillation total + # condenser, 0.1 % off), which the targets, the streams', ignore replaced = [hu for hu in hus if hu.hxn_ok and _duty_off_stream(hu)] + if replaced: + from ._heat_exchanger_network import _replacing_networks + networks = _replacing_networks(replaced) + replaced = [hu for hu in replaced if id(hu) in networks] if replaced: names = ', '.join(dict.fromkeys(repr(_unit_label(hu.unit)) for hu in replaced)) + IDs = list(dict.fromkeys(networks[id(hu)].ID for hu in replaced)) warn(f'the heat utilities of {names} have duties (hu.unit_duty) ' 'other than their process streams\' (outs[0].H - ins[0].H), ' - 'which the targets use: after a simulation of a ' - 'HeatExchangerNetwork with replace_unit_heat_utilities=True ' - 'they hold its utilities, and the streams it serves ' - 'completely have none and are left out; take its targets() ' + 'which the targets use: they hold the utilities of ' + f"HeatExchangerNetwork {', '.join(map(repr, IDs))}, " + 'simulated with replace_unit_heat_utilities=True, and the ' + 'streams it serves completely have none and are left out; ' + f"take {' or '.join(f'{ID}.targets()' for ID in IDs)} " 'instead', RuntimeWarning) if key_units is not None: key_units = _with_auxiliaries(key_units) values = _explicit_contributions(hus, approach_contributions, T_min_app, diff --git a/tests/test_targeting.py b/tests/test_targeting.py index cfc9835..7fc173c 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -55,7 +55,8 @@ plot_supertargets) from hensmith._curves import _copy from hensmith.hxn_synthesis import _problem_table, temperature_interval_pinch_analysis -from hensmith.targeting import _exchanger_costs, _economics, _lmtd, _profile_area +from hensmith.targeting import (_exchanger_costs, _economics, _lmtd, _profile_area, + _duty_off_stream) from test_hxn import (build_system, cp_units, KEMP, simulate_HXN, simulate_strictly, _gid_artists) from test_hxn_contributions import classic_cascade, KeptHeater @@ -2004,6 +2005,11 @@ def test_from_heat_utilities_takes_exchangers(): selected = bst.process_tools.heat_exchanger_utilities_from_units(units) assert [hu.unit for hu in selected] == [H, C] t = PinchTargets.from_heat_utilities(sorted(selected, key=lambda hu: hu.duty), 10.) + # a heat utility given twice would count its stream twice + for given in (selected + selected[:1], selected[:1] + selected): + with pytest.raises(ValueError, match=r"the stream of 'H' would count " + r"once per heat utility of its unit"): + PinchTargets.from_heat_utilities(given, 10.) HXN = HeatExchangerNetwork('HXN', T_min_app=10., units=units) facility = HXN.targets() assert facility.labels == t.labels == ['C', 'H'] @@ -2030,13 +2036,17 @@ def select(sys): assert [hu.unit for hu in hus] == [units[0], units[3]] assert_allclose([hu.unit_duty / kW for hu in hus], [20., -60.], rtol=1e-9) message = (r"heat utilities of 'C1', 'H4' have duties \(hu.unit_duty\) other " - r"than their process streams' .*replace_unit_heat_utilities=True" - r".*take its targets\(\) instead") + r"than their process streams' .*they hold the utilities of " + r"HeatExchangerNetwork 'HXN', simulated with " + r"replace_unit_heat_utilities=True.*take HXN.targets\(\) instead") with pytest.warns(RuntimeWarning, match=message): t = PinchTargets.from_heat_utilities(hus, 10.) assert t.labels == ['C1', 'H4'] facility = HXN.targets() assert facility.labels == ['C1', 'C3', 'H2', 'H4'] + # the curves are named by the labels (in their warnings), not by the + # IDs of the targets' private stream copies + assert [c.label for c in facility.curves] == facility.labels assert (facility.Q_hot, facility.Q_cold) == (pytest.approx(20. * kW, rel=1e-9), pytest.approx(60. * kW, rel=1e-9)) # every unit's own heat utility: the empty ones are named too @@ -2054,20 +2064,62 @@ def select(sys): assert t.labels == facility.labels assert (t.Q_hot, t.Q_cold) == (pytest.approx(facility.Q_hot, rel=1e-12), pytest.approx(facility.Q_cold, rel=1e-12)) - # a duty off by 1e-4 of its stream's is another than the stream's; one - # off by 1e-9 is the stream's up to rounding + # a written-over duty off by 1e-4 of its stream's is another than the + # stream's; one off by 1e-9 is the stream's up to rounding + units, HXN = kemp_hxn_replaced + hu = units[0].heat_utilities[0] + dH = units[0].outs[0].H - units[0].ins[0].H + replaced = hu.unit_duty + try: + for f, off in ((1e-4, True), (-1e-4, True), (1e-9, False)): + hu.unit_duty = dH * (1. + f) + assert _duty_off_stream(hu) == off + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + PinchTargets.from_heat_utilities([hu], 10.) + messages = [str(x.message) for x in w + if 'other than their process' in str(x.message)] + assert len(messages) == off + if off: assert messages[0].startswith("the heat utilities of 'C1' have duties") + finally: + hu.unit_duty = replaced + # a duty off its stream's that no network has written over warns of + # nothing: the targets are the streams' units = cp_units('test_targeting_duty_off', REF_A) hus = [u.heat_utilities[0] for u in units] dH = units[0].outs[0].H - units[0].ins[0].H - for f, off in ((1e-4, True), (-1e-4, True), (1e-9, False)): - hus[0].unit_duty = dH * (1. + f) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - PinchTargets.from_heat_utilities(hus, 10.) - messages = [str(x.message) for x in w - if 'other than their process' in str(x.message)] - assert len(messages) == off - if off: assert messages[0].startswith("the heat utilities of 'H1' have duties") + hus[0].unit_duty = dH * (1. + 1e-4) + assert _duty_off_stream(hus[0]) + with warnings.catch_warnings(): + warnings.simplefilter('error') + t = PinchTargets.from_heat_utilities(hus, 10.) + assert t.labels == ['C1', 'H1'] + +def test_from_heat_utilities_of_a_total_condenser(): + # the heat utility of the total condenser of biosteam's MESHDistillation + # reports a duty 0.1 % off the enthalpy change of the condenser's own + # streams (no network about): neither the selection nor the facility's + # targets before a simulation warn of a network that wrote it over + bst.main_flowsheet.set_flowsheet('test_targeting_total_condenser') + bst.settings.set_thermo(['Water', 'Ethanol'], cache=True) + feed = bst.Stream('feed', Water=800., Ethanol=100., units='kmol/hr', T=350.) + D1 = bst.MESHDistillation('D1', ins=feed, outs=('dist', 'bott'), N_stages=12, + feed_stages=(6,), reflux=2., boilup=1.5, + LHK=('Ethanol', 'Water'), full_condenser=True) + C1 = bst.HXutility('C1', ins=D1-0, T=310.) + C2 = bst.HXutility('C2', ins=D1-1, T=320.) + sys = bst.System.from_units('sys', units=[D1, C1, C2]) + sys.simulate() + hu = D1.condenser.heat_utilities[0] + dH = D1.condenser.outs[0].H - D1.condenser.ins[0].H + assert _duty_off_stream(hu) and abs(hu.unit_duty / dH - 1.) > 1e-4 + with warnings.catch_warnings(): + warnings.simplefilter('error') + hus = bst.process_tools.heat_exchanger_utilities_from_units(sys.units) + t = PinchTargets.from_heat_utilities(sorted(hus, key=lambda hu: hu.duty)) + facility = HeatExchangerNetwork('HXN', units=sys.units).targets() + assert 'D1 - condenser' in t.labels and facility.labels == t.labels + assert (facility.Q_hot, facility.Q_cold) == (t.Q_hot, t.Q_cold) def test_is_hot_validated(): # is_hot must agree with each stream's direction (a stream without duty From 5aa2c7dbe2eb283811498637e0002d1cebe9267e Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sun, 18 Oct 2026 02:30:00 +0000 Subject: [PATCH 47/52] isolate the default facility path from the key machinery Round 8's blocker showed that a facility without approach contributions had come to depend on the key machinery: every fresh synthesis in HeatExchangerNetwork._cost recorded the holders of auxiliaries held without an owner (_held_owners over _candidate_units, i.e. the _with_auxiliaries / _nested_auxiliaries walk), for the labels of targets() only, so an unusual unit structure could make a plain simulation raise. The default path is now the base path plus nothing it needs from that code. What: - New HeatExchangerNetwork._synthesis_holders, called where _cost took the holders. With approach_contributions, they are taken as before (the keys were just resolved by the same walk). Without (the default path) they serve the labels of targets() and network_report only: they are taken only if an original exchanger's _owner chain ends outside the facility's units (plain attribute reads; e.g. a MultistageCompressor's intercoolers), and are None on any error, so a default simulation never raises from them and labels then go by the live relation. A system whose exchangers its units own (columns, flashes, plain exchangers) runs none of the key machinery at all; _network_contributions already returned None first. - New test_default_facility_needs_none_of_the_key_machinery (tests/test_hxn_contributions.py): every key-machinery helper, in every hensmith module that imports it, and HXN._candidate_units, is monkeypatched to raise. The quickstart system (ShortcutColumn with condenser and reboiler, product coolers, flash), plain, with cache_network (reused after a feed change) and with replace_unit_heat_utilities, gives the same process and utility exchanger duties (float.hex) and unit heat utility duties as unpatched, over two simulations, and calls none of the helpers. A MultistageCompressor system (owner-less intercoolers) does the same with the best-effort holders failing to None; afterwards targets() has the same labels ('K1 - K1_H1', ...) and summary as unpatched. It fails on the previous commit for all four cases. - Simplified key resolution, behaviour unchanged (messages identical): _check_key_levels and _nearest_key_value are now shared by _mapping_contributions and PinchTargets._film_mapping, which each had their own copy of the same-level conflict check and of the nearest-key loop (h's stream-index keys are taken first, as level -1 made them win before); _auxiliary_attributes is shared by _auxiliary_slots and _other_form_hints, which each parsed the 'name[k]' key and looked up the attribute themselves; targets()' callable test drops a redundant None check. - Plot label placement reviewed and left as is: each placer (_Stacked with its rows fallback and leaders, _Beside, _Under, _ClearLegend) answers a confirmed finding that its tests pin, and the remaining heuristics (the margins next to make_room) shape the generated figures, so trimming them would change the docs assets. Validation: the four new cases fail on 0bd3bf8 and pass here; test_hxn_contributions, test_targeting, test_hxn_targets and the modules' doctests pass (194); full suite 1106 passed; default-path oracle sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (unchanged). No docs changed, so no sphinx run. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- hensmith/_heat_exchanger_network.py | 43 ++++++++-- hensmith/hxn_synthesis.py | 95 +++++++++++---------- hensmith/targeting.py | 32 ++------ tests/test_hxn_contributions.py | 123 +++++++++++++++++++++++++++- 4 files changed, 215 insertions(+), 78 deletions(-) diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index 9f36535..ba5c917 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -587,6 +587,35 @@ def _network_contributions(self, hx_utils, units=None, held=None): units=self._candidate_units(units), held=held) + def _synthesis_holders(self, hx_utils, units): + """ + The holders of the auxiliaries held without an owner (see + `hensmith.hxn_synthesis._held_owners`) at a synthesis of the heat + utilities `hx_utils` of `units`, which `targets` keeps for the keys + and labels of exchangers that a unit has rebuilt since. With + approach contributions, they are those the keys were resolved by. + Without (the default path), they serve labels only, and the + synthesis never depends on them: they are taken only if an + exchanger's owner chain ends outside `units` (an exchanger held + without an owner, as the intercoolers of a + `biosteam.MultistageCompressor`), so that none of the key machinery + runs otherwise, and are None on any error (labels then go by the + units as they are, plain unit IDs at worst). + """ + hxs = [hu.unit for hu in hx_utils] + if self.approach_contributions is not None: + return _held_owners(hxs + self._candidate_units(units)) + try: + at_hand = {id(unit) for unit in units} + for hx in hxs: + while getattr(hx, '_owner', None) is not None: hx = hx._owner + if id(hx) not in at_hand: break + else: + return None + return _held_owners(hxs + self._candidate_units(units)) + except Exception: + return None + def _get_original_heat_utilties(self, units=None): if units is None: units = self._get_units() ignored = self.ignored @@ -669,7 +698,9 @@ def _cost(self): units = list(self._get_units()) hx_utils = self._get_original_heat_utilties(units) # the explicit approach contributions by heat utility (resolved - # once per simulation; None without any) + # once per simulation; None without any, and with + # approach_contributions None, the default path, none of the key + # machinery runs here or in `_synthesis_holders`) values = self._network_contributions(hx_utils, units) value_of = (None if values is None else {id(hu): v for hu, v in zip(hx_utils, values)}) @@ -753,13 +784,7 @@ def _cost(self): # Signed-duty order is the default matching priority of the # synthesis passes: smallest heating duty first among the cold # streams, largest cooling duty first among the hot streams. - # The holders of the auxiliaries held without an owner, as - # the contributions were resolved by (see `targets`, which - # labels by them with or without contributions; the walk - # reads auxiliaries of units only, see - # `hensmith.hxn_synthesis._nested_auxiliaries`). - holders = _held_owners([hu.unit for hu in hx_utils] - + self._candidate_units(units)) + holders = self._synthesis_holders(hx_utils, units) hx_utils.sort(key = lambda x: x.duty) self.HXN_flowsheet = HXN_F = flowsheet for i in HXN_F.registries: i.clear() @@ -1181,7 +1206,7 @@ def _pinch_targets(self, T_min_app=None): # synthesis, which a holder that has rebuilt them since (a # MultistageCompressor simulated alone) no longer holds held = getattr(self, '_holders', None) - if x is not None and not isinstance(x, Mapping) and callable(x): + if callable(x) and not isinstance(x, Mapping): # Never called again: with replace_unit_heat_utilities, the # heat utilities it would read now hold the network's # utilities (or nothing, for a stream the process exchangers diff --git a/hensmith/hxn_synthesis.py b/hensmith/hxn_synthesis.py index a1dbf5d..cb04e5e 100644 --- a/hensmith/hxn_synthesis.py +++ b/hensmith/hxn_synthesis.py @@ -1006,31 +1006,39 @@ def _key_universe(units, held=None): universe = _owner_chains(units, owners) return universe, {id(u) for u in universe}, owners -def _auxiliary_slots(key, universe, owners=None): +def _auxiliary_attributes(key, universe, owners=None): """ - Where the units of `universe` hold the auxiliary unit of the tuple key - `key` by the ID that `biosteam.Unit.auxiliary` gives an auxiliary: - ``(owner.ID, 'name[k]')`` for the k-th of a list under the attribute - `name`, ``(owner.ID, name)`` for a single one, with `name` one of the + The attributes that the tuple key `key`, of the ID that + `biosteam.Unit.auxiliary` gives an auxiliary, may name: ``(owner.ID, + 'name[k]')`` the k-th unit of a list under the attribute `name`, and + ``(owner.ID, name)`` a single unit there, with `name` one of the `auxiliary_unit_names` of a unit under the top owner ``owner.ID`` - (along `_parent` with `owners`). Yields ``(unit, held)`` for each such - unit whose attribute takes that form or holds nothing (None or - missing), with `held` the `biosteam.Unit` there, or None if there is - none (nothing, an object that is not a unit, or a list too short). + (along `_parent` with `owners`). Yields ``(unit, name, k, held)`` for + each such unit, with k None for the single form and `held` what the + unit holds there (None if missing). """ owner_ID, ID = key match = re.fullmatch(r'(.+)\[(0|[1-9]\d*)\]', ID) - name = match[1] if match else ID + name, k = (match[1], int(match[2])) if match else (ID, None) for unit in universe: - if (_top(unit, owners).ID != owner_ID - or name not in getattr(unit, 'auxiliary_unit_names', ())): - continue - held = getattr(unit, name, None) - if held is not None and isinstance(held, list) != bool(match): - continue - if match and held is not None: - k = int(match[2]) - held = held[k] if k < len(held) else None + if (_top(unit, owners).ID == owner_ID + and name in getattr(unit, 'auxiliary_unit_names', ())): + yield unit, name, k, getattr(unit, name, None) + +def _auxiliary_slots(key, universe, owners=None): + """ + Where the units of `universe` hold the auxiliary unit of the tuple key + `key` by the ID that `biosteam.Unit.auxiliary` gives an auxiliary (see + `_auxiliary_attributes`). Yields ``(unit, held)`` for each such unit + whose attribute takes that form (a list for ``'name[k]'``) or holds + nothing (None or missing), with `held` the `biosteam.Unit` there, or + None if there is none (nothing, an object that is not a unit, or a + list too short). + """ + for unit, name, k, held in _auxiliary_attributes(key, universe, owners): + if held is not None: + if isinstance(held, list) != (k is not None): continue + if k is not None: held = held[k] if k < len(held) else None yield unit, held if isinstance(held, bst.Unit) else None def _other_form_hints(key, universe, owners=None): @@ -1043,17 +1051,10 @@ def _other_form_hints(key, universe, owners=None): ..."``), and for ``(owner.ID, 'name[k]')`` where it holds one unit, that unit's key (``"D1.condenser is ('D1', 'condenser')"``). """ - owner_ID, ID = key - match = re.fullmatch(r'(.+)\[(0|[1-9]\d*)\]', ID) - name = match[1] if match else ID hints = [] - for unit in universe: - if (_top(unit, owners).ID != owner_ID - or name not in getattr(unit, 'auxiliary_unit_names', ())): - continue - held = getattr(unit, name, None) - if held is None or isinstance(held, list) == bool(match): continue - if match: + for unit, name, k, held in _auxiliary_attributes(key, universe, owners): + if held is None or isinstance(held, list) == (k is not None): continue + if k is not None: if isinstance(held, bst.Unit): hints.append(f'; {unit.ID}.{name} is ' f'{_unit_name(held, owners)!r}') @@ -1226,6 +1227,14 @@ def _mapping_contributions(hus, mapping, units, held=None): 'the ID of a unit without an owner, or (owner.ID, ' 'unit.ID)' f'{_unknown_key_hints(unknown, universe, owners)})') + _check_key_levels(items, universe, live, owners, 'approach_contributions') + return [_nearest_key_value(items, hu.unit, live, owners) for hu in hus] + +def _check_key_levels(items, universe, live, owners, what): + """Raise a ValueError if two keys of `items` (``(key, value)`` pairs + of unit keys) of the same level for a unit of `universe` (see + `_contribution_key_level`, which takes `live` and `owners`) give it + different values; `what` names the keys (``'h'``, say).""" for unit in universe: first = {} # level -> (key, value) for key, value in items: @@ -1234,19 +1243,21 @@ def _mapping_contributions(hus, mapping, units, held=None): other, other_value = first.setdefault(level, (key, value)) if other_value != value: raise ValueError( - f'approach_contributions keys {other!r} and {key!r} ' - f'give unit {_unit_message_name(unit, owners)} different ' - f'values at the same level: {other_value!r} and ' - f'{value!r}') - values = [] - for hu in hus: - best = None # (level, value) of the nearest key - for key, value in items: - level = _contribution_key_level(key, hu.unit, live, owners) - if level is not None and (best is None or level < best[0]): - best = (level, value) - values.append(None if best is None else best[1]) - return values + f'{what} keys {other!r} and {key!r} give unit ' + f'{_unit_message_name(unit, owners)} different values ' + f'at the same level: {other_value!r} and {value!r}') + +def _nearest_key_value(items, unit, live, owners, default=None): + """The value of the key of `items` (``(key, value)`` pairs of unit + keys) of the lowest level on the owner chain of `unit` (see + `_contribution_key_level`, which takes `live` and `owners`; the first + of that level), or `default` if none names it.""" + best = None # (level, value) of the nearest key + for key, value in items: + level = _contribution_key_level(key, unit, live, owners) + if level is not None and (best is None or level < best[0]): + best = (level, value) + return default if best is None else best[1] def _stream_contributions(hus, approach_contributions, units=None, held=None): diff --git a/hensmith/targeting.py b/hensmith/targeting.py index b49a77a..fdced1f 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -34,9 +34,8 @@ from ._planner import _approach_contributions, _given_contributions from .hxn_synthesis import (_problem_table, _pinch_cut, _format_H, _arrange_heat_utilities, _process_streams, _explicit_contributions, - _contribution_key_level, _key_universe, _unknown_keys, - _unknown_key_hints, _unit_message_name, _held_owners, _top, - _with_auxiliaries) + _check_key_levels, _nearest_key_value, _key_universe, _unknown_keys, + _unknown_key_hints, _held_owners, _top, _with_auxiliaries) __all__ = ('PinchTargets', 'supertarget', 'Supertargets', 'network_report', 'plot_composite_curves', 'plot_grand_composite_curve', @@ -1944,29 +1943,12 @@ def _film_mapping(self, h): f'{_unknown_key_hints(unknown, universe, owners)}' ')') unit_items = [(k, v) for k, v in items if not isinstance(k, Integral)] - for unit in universe: - first = {} # level -> (key, value) - for key, value in unit_items: - level = _contribution_key_level(key, unit, live, owners) - if level is None: continue - other, other_value = first.setdefault(level, (key, value)) - if other_value != value: - raise ValueError( - f'h keys {other!r} and {key!r} give unit ' - f'{_unit_message_name(unit, owners)} different values ' - f'at the same level: {other_value!r} and {value!r}') + _check_key_levels(unit_items, universe, live, owners, 'h') + by_index = {k: v for k, v in items if isinstance(k, Integral)} values = np.full(N, _H_DEFAULT) - for j in range(N): - best = None # (level, value) of the nearest key - for key, value in items: - if isinstance(key, Integral): - level = -1 if key == j else None - else: - level = _contribution_key_level(key, units[j], live, - owners) - if level is not None and (best is None or level < best[0]): - best = (level, value) - if best is not None: values[j] = best[1] + for j in range(N): # a stream index is below its unit + values[j] = (by_index[j] if j in by_index else _nearest_key_value( + unit_items, units[j], live, owners, _H_DEFAULT)) return values def _area(self, utilities, h, h_utilities): diff --git a/tests/test_hxn_contributions.py b/tests/test_hxn_contributions.py index c867f4e..8952cbd 100644 --- a/tests/test_hxn_contributions.py +++ b/tests/test_hxn_contributions.py @@ -16,7 +16,10 @@ ``T_min_app / 2``) give the default plans, problem tables and facility networks bit for bit, compared in-process with ``float.hex`` and ``tobytes``; a numpy float32 ``T_min_app`` keeps its own float32 - arithmetic in every exchanger's guard. + arithmetic in every exchanger's guard. Without contributions, the + facility's simulations (cached or writing over the heat utilities) do + not depend on the key machinery: they run the same with its helpers + raising. B. Uniform contributions: every stream at ``x`` plans the network of ``T_min_app = 2x`` on a shifted scale offset by a constant, and on real-thermo cases all hot streams at ``a`` and all cold ones at ``b`` @@ -55,7 +58,9 @@ import pytest import biosteam as bst from numpy.testing import assert_allclose -from hensmith import hxn_synthesis, HeatExchangerNetwork, PinchTargets +from hensmith import (hxn_synthesis, targeting, HeatExchangerNetwork, + PinchTargets) +from hensmith import _heat_exchanger_network as network_module from hensmith import _planner as P from hensmith.hxn_synthesis import problem_table, synthesize_network, _pinch_cut from test_hxn import (cp_units, KEMP, R002 as R002_UNITS, planned_split, @@ -369,6 +374,120 @@ def test_default_float32_T_min_app_keeps_its_own_arithmetic(name): assert (float(HXN.synthesis_info['min_approach_margin']).hex() == float(HXN.synthesis_info['min_approach'] - T).hex()) +#: The helpers of the key machinery: key resolution, the walks of +#: auxiliaries and the owners of auxiliaries held without one. +KEY_HELPERS = ('_parent', '_top', '_held_owners', '_unit_name', '_id_path', + '_unit_message_name', '_names_unit', + '_contribution_key_level', '_check_contribution_key', + '_owner_chains', '_nested_auxiliaries', '_heat_utility_units', + '_with_auxiliaries', '_key_universe', '_auxiliary_slots', + '_other_form_hints', '_auxiliary_attributes', '_stage_tickets', + '_is_stage_ID', '_check_key_levels', '_nearest_key_value', + '_names_unbuilt_stage', '_names_unbuilt_auxiliary', + '_unknown_keys', '_unknown_key_hints', + '_mapping_contributions', '_stream_contributions') + +def break_key_machinery(monkeypatch): + """Make every helper of the key machinery raise wherever hensmith + takes it from, `HeatExchangerNetwork._candidate_units` too; returns the + list that the names of those called are added to.""" + called = [] + + def broken(name): + def helper(*args, **kwargs): + called.append(name) + raise RuntimeError(f'{name} called') + return helper + + for name in KEY_HELPERS: + assert hasattr(hxn_synthesis, name), name # still the machinery's + for module in (hxn_synthesis, network_module, targeting): + if hasattr(module, name): + monkeypatch.setattr(module, name, broken(name)) + monkeypatch.setattr(HeatExchangerNetwork, '_candidate_units', + broken('_candidate_units')) + return called + +def compressor_system(name, **options): + """A MultistageCompressor K1, whose intercoolers have no owner, and a + cold stream's heater C1, with a facility HXN of `options`.""" + bst.main_flowsheet.set_flowsheet(name) + bst.settings.set_thermo(['Water', 'Methane'], cache=True) + gas = bst.Stream('gas', Methane=100., units='kmol/hr', T=300., + P=101325., phase='g') + K1 = bst.MultistageCompressor('K1', ins=gas, pr=3., n_stages=3, eta=0.7) + cold = bst.Stream('cold', Water=200., units='kmol/hr', T=290., P=5e5) + C1 = bst.HXutility('C1', ins=cold, T=350., rigorous=False) + HXN = HeatExchangerNetwork('HXN', T_min_app=5., **options) + return bst.System.from_units(name, units=[K1, C1, HXN]), HXN, K1.ins[0] + +def quickstart_system(name, **options): + """`quickstart`'s units with a facility HXN of `options`.""" + D1, D1_H1, D1_H2, F1 = quickstart(name) + HXN = HeatExchangerNetwork('HXN', T_min_app=5., **options) + return (bst.System.from_units(name, units=[D1, D1_H1, D1_H2, F1, HXN]), + HXN, D1.ins[0]) + +@pytest.mark.parametrize('system, options', [ + (quickstart_system, {}), + (quickstart_system, dict(cache_network=True)), + (quickstart_system, dict(replace_unit_heat_utilities=True)), + (compressor_system, {}), +], ids=['quickstart', 'quickstart_cached', 'quickstart_replacing', + 'compressor']) +def test_default_facility_needs_none_of_the_key_machinery(system, options, + monkeypatch): + # without approach contributions, a facility's simulations do not + # depend on the key machinery: with each of its helpers raising, the + # network of the quickstart system (a column with its condenser and + # reboiler, product coolers, a flash) is synthesized, reused from the + # cache after a feed change, or written over its units' heat utilities + # just the same, and none of them is called; the intercoolers of a + # compressor are held without an owner, so their holders are taken, + # for the labels of targets() only, and are None if that fails + exchanger_duties = lambda units: [(u.ID, float(u.Q).hex()) + for u in units] + + def run(name, broken): + sys, HXN, feed = system(name, **options) + called = break_key_machinery(monkeypatch) if broken else [] + records = [] + for scale in (1., 1.02): + feed.F_mass *= scale + # (biosteam warns that methane has no Dortmund groups) + if system is compressor_system: sys.simulate() + else: simulate_strictly(sys) + duties = [hu.duty for unit in sys.units if unit is not HXN + for hu in unit.heat_utilities] + records.append((exchanger_duties(HXN.new_HXs), + exchanger_duties(HXN.new_HX_utils), duties, + HXN.HXN_sys)) + monkeypatch.undo() + return records, called, HXN + + name = '_'.join([system.__name__, *options]) + reference, _, HXN0 = run(f'contributions_unbroken_{name}', False) + records, called, HXN = run(f'contributions_broken_{name}', True) + assert [r[:3] for r in records] == [r[:3] for r in reference] + assert records[0][0] # process exchangers + cached = options.get('cache_network', False) + assert (records[1][3] is records[0][3]) == cached + assert (reference[1][3] is reference[0][3]) == cached + if system is quickstart_system: + assert not called + assert HXN._holders is None is HXN0._holders + else: # the holders, best-effort, fail + assert called and HXN._holders is None + assert HXN0._holders is not None + # with the machinery back, the targets are those of the network + # taken without it, labeled by the units as they are + targets, targets0 = HXN.targets(), HXN0.targets() + assert targets.labels == targets0.labels + assert targets.summary() == targets0.summary() + if system is compressor_system: + assert targets.labels == ['C1', 'K1 - K1_H1', 'K1 - K1_H2', + 'K1 - K1_H3'] + # --------------------------------------------------------------------------- # B. Uniform contributions # --------------------------------------------------------------------------- From c34e71090cc21a5c6e1bd7e9a97c9f7ef9487d2e Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sun, 18 Oct 2026 04:04:57 +0000 Subject: [PATCH 48/52] address review 10 (tg-super): resolve unit keys of h only when some are given TGS10-1 (fixed): PinchTargets._film_mapping evaluated units[j] for every stream without an index key, so targets built with the PinchTargets constructor (units None) raised IndexError for any h mapping that left a stream out (an empty one included), in area_target, capital_cost_target, summary and supertarget (regression from e3e2c1f). The nearest unit key is now looked up only when there are unit keys; streams left out take 1 again, matching 0bd3bf8. The direct-targets block of test_bath_balanced_pair now checks h={0: .5} against [.5, 1.], h={} against the default and the heat-utility targets, and that summary, supertarget and capital_cost_target run with h={1: 2.} and give the results of the sequence form. TGS10-2 (nit, applied): the Notes of PinchTargets and the capital paragraph of concepts.rst now say the furnace is costed at biosteam's default furnace pressure of 500 psi (P sets the shells' pressure only). Tests: tests/test_targeting.py plus the doctests of hensmith/targeting.py, 89 passed; the reviewer's reproduction (r10/c10_hmap.py) now gives 15.0, 8.333 and 20.0 as on 0bd3bf8. The synthesis path is untouched (no oracle run needed). sphinx is not installed in this environment; the doc changes are prose only. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/concepts.rst | 3 ++- hensmith/targeting.py | 15 +++++++++------ tests/test_targeting.py | 10 ++++++++++ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index dccd6d2..76653d8 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -547,7 +547,8 @@ units, and each loop of matches one more. **Capital.** The area is put in as many shells as the units target, :math:`N`, less one furnace for all the fuel, and every shell is costed with -BioSTEAM's own correlations; the furnace is costed on the fuel's utility duty. +BioSTEAM's own correlations; the furnace is costed on the fuel's utility duty, +at BioSTEAM's default furnace pressure of 500 psi. Linnhoff & Ahmad (1990) divide the area equally, :math:`N \cdot C(A / N)`: with economies of scale (:math:`C = a + b A^c`) that is the costliest way to divide it, and every unit added adds cost. BioSTEAM's correlations are not of diff --git a/hensmith/targeting.py b/hensmith/targeting.py index fdced1f..87c6e0d 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -923,10 +923,11 @@ class PinchTargets: below). Where it departs from the equal division it tends to be high, as networks keep only part of their area in shell and tubes (the equal division tends to be low there): an estimate, not a bound. The furnace - is costed on the fuel's utility duty. `summary` adds the total - annualized cost, the installed (bare module) cost times the capital - recovery factor plus the utility cost times the operating hours: no Lang - factor and no working capital. + is costed on the fuel's utility duty at biosteam's default furnace + pressure of 500 psi (`P` sets the pressure of the shells only). + `summary` adds the total annualized cost, the installed (bare module) + cost times the capital recovery factor plus the utility cost times the + operating hours: no Lang factor and no working capital. Examples -------- @@ -1947,8 +1948,10 @@ def _film_mapping(self, h): by_index = {k: v for k, v in items if isinstance(k, Integral)} values = np.full(N, _H_DEFAULT) for j in range(N): # a stream index is below its unit - values[j] = (by_index[j] if j in by_index else _nearest_key_value( - unit_items, units[j], live, owners, _H_DEFAULT)) + if j in by_index: values[j] = by_index[j] + elif unit_items: # only targets of heat utilities have units + values[j] = _nearest_key_value(unit_items, units[j], live, + owners, _H_DEFAULT) return values def _area(self, utilities, h, h_utilities): diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 7fc173c..0b55c40 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -1478,6 +1478,16 @@ def test_bath_balanced_pair(quickstart): assert direct.area_target(h={0: .5, 1: 2.}) == pytest.approx(25., rel=1e-12) with pytest.raises(ValueError, match='name no stream'): direct.area_target(h={'H1': 2.}) + # streams left out of a stream index mapping take 1 + assert direct.area_target(h={0: .5}) == direct.area_target(h=[.5, 1.]) + assert direct.area_target(h={}) == direct.area_target() == t.area_target(h={}) + assert direct.area_target(h={1: 2.}) == t.area_target(h={1: 2.}) + area = direct.area_target(h=[1., 2.]) + assert direct.summary(h={1: 2.})['area'] == area + sweep = supertarget(direct, [5., 15.], refine=0, h={1: 2.}) + assert_allclose(sweep['area'], supertarget(direct, [5., 15.], refine=0, + h=[1., 2.])['area'], rtol=0) + assert direct.capital_cost_target(h={1: 2.}) == direct.capital_cost_target(h=[1., 2.]) # utilities: reference A, keyed by agent ID or agent a = targets('test_targeting_pair_utilities', REF_A, 10.) utilities = a.utility_targets() From 20c52d48e7535ba9162e4c8b707a5791b2e4453a Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sun, 18 Oct 2026 06:36:35 +0000 Subject: [PATCH 49/52] address review 11 (e2e): warn when targets mix a changed inlet with a former outlet E2E-R11-1 (fixed): HXN.targets() and PinchTargets.from_heat_utilities now warn (RuntimeWarning) when an exchanger's ins[0].mol and outs[0].mol differ by more than 1 % of the larger (targeting._stale_labels, _STALE_FLOW = 1e-2): its inlet has changed since its unit was simulated, and the targets would take the stream from the new inlet to the former outlet. The message names the streams and says to simulate the system (or its units) first. The tolerance stays well above the about 0.1 % of a MESHDistillation total condenser, and test_from_heat_utilities_of_a_total_condenser still passes under simplefilter('error'). No state is stored, so units simulated alone give correct targets without a warning. network_report still raises its own ValueError and does not warn first (_pinch_targets now also returns the stale labels, and only HXN.targets warns of them). Before the facility's first simulation, from_heat_utilities warns, once. It warns rather than raises: a unit whose exchanger legitimately changes its flows between those ends (a reaction on a condenser stage, a custom auxiliary) would otherwise be unable to target at all, while its synthesis works. Documented in the Warns/Notes of both methods, in tutorial ch05 and in concepts.rst. New test test_facility_targets_changed_inlet covers one stream and two streams, the pre-simulation path, the report, units simulated alone, a change of temperature only, and where the warning points. E2E-R11-4 (nit, applied): targeting's warnings go through _warn, which uses warnings.warn(skip_file_prefixes=) (Python >= 3.12, as setup.py requires). They now point at the caller's line, not at targeting.py: unserved heating/cooling, replaced heat utilities, the supertarget sweep warnings, and the area driving-force warnings. Two tests now assert the filename. E2E-R11-3 (nit, not applied): pre-existing (5ef9a35 crashes the same way) and out of scope. A fallback would turn a crash on the cache path into a re-synthesis. Tests: tests/test_targeting.py + doctests of hensmith/targeting.py and hensmith/_heat_exchanger_network.py: 92 passed (doctests rerun after the last docstring edit: 8 passed); tests/test_hxn_contributions.py: 58 passed. Synthesis path untouched; oracle sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (matches). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/concepts.rst | 6 +- docs/source/tutorial/05_targeting.rst | 6 +- hensmith/_heat_exchanger_network.py | 41 +++++++--- hensmith/targeting.py | 108 ++++++++++++++++++++------ tests/test_targeting.py | 80 ++++++++++++++++++- 5 files changed, 203 insertions(+), 38 deletions(-) diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 76653d8..96d90ea 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -640,8 +640,10 @@ report keeps BioSTEAM's specification of the utility, whose cost it reports; the target, which keeps a sensible agent's line inside the grand composite curve, stays finite. The report checks that its targets and the network's streams are both of the units' current states: after a stream or a unit -changes, it raises a ``ValueError`` until the system is simulated again. The -comparison has limits: +changes, it raises a ``ValueError`` until the system is simulated again +(``HXN.targets()`` warns meanwhile of a stream whose inlet has changed in flow +since its unit was simulated, whose targets would run from that new inlet to +its former outlet). The comparison has limits: - the Bath target bounds the area only with equal film coefficients and the same utilities; diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index f62b5b5..456121d 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -60,7 +60,11 @@ duty by its agent's heat transfer efficiency, 0.95 for steam, and 2.828e+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. The same object can be built without a +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 same object can be built without a facility, from the heat utilities of heat exchangers with :meth:`PinchTargets.from_heat_utilities ` (as the trade-off example below does) or from streams as diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index ba5c917..a88ce11 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -1091,6 +1091,12 @@ def targets(self, T_min_app=None): exchanger no longer among the facility's units and their nested auxiliaries (rebuilt since the facility's last simulation, see Notes): the targets are of its former state. + RuntimeWarning + If the first inlet and outlet of a stream's exchanger differ in + flow (``ins[0].mol`` and ``outs[0].mol``) by more than 1 % of + the larger: its inlet has changed since its unit was simulated + (see Notes), and the targets take the stream from that new + inlet to the outlet of the former state, a state of no process. Notes ----- @@ -1128,7 +1134,14 @@ def targets(self, T_min_app=None): states, until the system is simulated again: the targets are then of those former states, not of the process now, with a RuntimeWarning (`hensmith.targeting.network_report` raises - meanwhile). + meanwhile). Each stream runs from its exchanger's inlet now to its + outlet as its unit last left it: after a feed or another stream + changes, simulate the system before targeting (or the units alone, + which gives the targets of the process now). A stream whose inlet + has changed in flow meanwhile warns (see Warns); a change of + temperature alone keeps the flows equal and warns of nothing, its + targets being right only if its unit sets its outlet by + temperature (as a `biosteam.HXutility` with ``T`` does). Examples -------- @@ -1169,22 +1182,29 @@ def targets(self, T_min_app=None): """ - targets, rebuilt = self._pinch_targets(T_min_app) + from .targeting import _warn_stale + targets, rebuilt, stale = self._pinch_targets(T_min_app) if rebuilt is not None: warn(f'the targets of {self.ID} are of the former state of ' f"{rebuilt}, which is no longer one of the facility's units " "nor an auxiliary of one (rebuilt since the facility's last " 'simulation?): simulate the system for those of the process ' 'now', RuntimeWarning, stacklevel=2) + _warn_stale(stale, f' of {self.ID}') return targets def _pinch_targets(self, T_min_app=None): - """`targets` (see there) and the label of the first of their - streams whose exchanger is no longer among the facility's units, - their nested auxiliaries and the exchangers they hold through their - heat utilities only (rebuilt since the last simulation), or None - (always before a simulation).""" - from .targeting import PinchTargets, _rebuilt_label + """`targets` (see there), the label of the first of their streams + whose exchanger is no longer among the facility's units, their + nested auxiliaries and the exchangers they hold through their heat + utilities only (rebuilt since the last simulation), or None, and + the labels of the streams whose exchanger's inlet has changed in + flow since its unit was simulated (see + `hensmith.targeting._stale_labels`). Both are None and empty + before a simulation, whose targets, + `PinchTargets.from_heat_utilities`, warn of the latter + themselves.""" + from .targeting import PinchTargets, _rebuilt_label, _stale_labels if T_min_app is None: T_min_app = self.T_min_app if (hasattr(self, 'original_heat_exchangers') and hasattr(self, 'original_heat_utils')): @@ -1221,7 +1241,8 @@ def _pinch_targets(self, T_min_app=None): None, hus, _held_owners([*hxs, *key_units], held)) targets.key_units = key_units targets.system = self.system - return targets, _rebuilt_label(hxs, targets.labels, key_units) + return (targets, _rebuilt_label(hxs, targets.labels, key_units), + _stale_labels(hxs, targets.labels)) if self.system is None and not self.units: raise RuntimeError('simulate the system (or give the facility its ' 'units) before targeting') @@ -1232,7 +1253,7 @@ def _pinch_targets(self, T_min_app=None): hx_utils, T_min_app, self._network_contributions(hx_utils, units), self.force_ideal_thermo, self.sort_hus_by_T, key_units=units) targets.system = self.system - return targets, None + return targets, None, [] def get_original_hxs_associated_with_streams(self): # pragma: no cover original_units = self.system.units diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 87c6e0d..d4e24d7 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -20,6 +20,7 @@ `plot_grand_composite_curve` and `plot_supertargets` draw them. """ import math +import os from collections import namedtuple from collections.abc import Mapping, Sequence from contextlib import contextmanager @@ -82,6 +83,15 @@ #: The options of `PinchTargets.summary` whose agents `h_utilities` may key. _OPTION_AGENTS = 'heating_agents or cooling_agents' +#: The frames that a warning of `_warn` skips: those of hensmith itself. +_PACKAGE_FRAMES = (os.path.dirname(os.path.abspath(__file__)) + os.sep,) + +#: The difference between the flows of an exchanger's first inlet and +#: outlet, relative to the larger, beyond which its inlet has changed since +#: its unit was simulated (see `_stale_labels`): far above a legitimate one, +#: as of the total condenser of a `biosteam.MESHDistillation` (about 0.1 %). +_STALE_FLOW = 1e-2 + UtilityTarget = namedtuple('UtilityTarget', ( 'ID', 'agent', 'heating', 'level', 'duty', 'utility_duty', 'flow', 'cost', 'T_in', 'T_out', 'T_utility_in', 'T_utility_out', 'shift'), @@ -441,8 +451,8 @@ def _bath(hot, cold): zero = np.flatnonzero(np.minimum(d1, d2) <= _DT_ZERO) if zero.size: k = zero[0] - warn('vertical heat transfer at a non-positive driving force between ' - f'H = {x[k]:.6g} and {y[k]:.6g} kJ/hr', RuntimeWarning) + _warn('vertical heat transfer at a non-positive driving force between ' + f'H = {x[k]:.6g} and {y[k]:.6g} kJ/hr') return math.inf LMTD = _lmtd(d1, d2) return float(np.sum((y - x) * (hot[4][i] + cold[4][j]) / 3600. / LMTD)) @@ -1191,6 +1201,18 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, of a `biosteam.MESHDistillation` with ``full_condenser=True`` reports a duty about 0.1 % off the enthalpy change of its own streams, for instance). + RuntimeWarning + If the first inlet and outlet of a heat utility's unit differ in + flow (``ins[0].mol`` and ``outs[0].mol``) by more than 1 % of + the larger: its inlet has changed since its unit was simulated + (a feed changed, its unit not simulated since), and the targets + take its stream from that new inlet to the outlet of the former + state, a state of no process: simulate the system (or the + units) first. A change of temperature alone keeps the flows + equal and warns of nothing: the targets are then of the new + inlet and of the outlet as last simulated, which is the + outlet's state now if the unit sets it by temperature (as a + `biosteam.HXutility` with ``T`` does). """ hus = list(hus) @@ -1238,14 +1260,14 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, names = ', '.join(dict.fromkeys(repr(_unit_label(hu.unit)) for hu in replaced)) IDs = list(dict.fromkeys(networks[id(hu)].ID for hu in replaced)) - warn(f'the heat utilities of {names} have duties (hu.unit_duty) ' - 'other than their process streams\' (outs[0].H - ins[0].H), ' - 'which the targets use: they hold the utilities of ' - f"HeatExchangerNetwork {', '.join(map(repr, IDs))}, " - 'simulated with replace_unit_heat_utilities=True, and the ' - 'streams it serves completely have none and are left out; ' - f"take {' or '.join(f'{ID}.targets()' for ID in IDs)} " - 'instead', RuntimeWarning) + _warn(f'the heat utilities of {names} have duties (hu.unit_duty) ' + 'other than their process streams\' (outs[0].H - ins[0].H), ' + 'which the targets use: they hold the utilities of ' + f"HeatExchangerNetwork {', '.join(map(repr, IDs))}, " + 'simulated with replace_unit_heat_utilities=True, and the ' + 'streams it serves completely have none and are left out; ' + f"take {' or '.join(f'{ID}.targets()' for ID in IDs)} " + 'instead') if key_units is not None: key_units = _with_auxiliaries(key_units) values = _explicit_contributions(hus, approach_contributions, T_min_app, key_units) @@ -1262,6 +1284,7 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, force_ideal_thermo, None, arranged, holders) self.key_units = key_units + _warn_stale(_stale_labels(self.units, self.labels)) return self @classmethod @@ -1661,13 +1684,13 @@ def utility_targets(self, heating_agents=None, cooling_agents=None): fuels = [a for a in heating_agents if a.isfuel] heat.append((math.inf, remaining, fuels[0] if fuels else None)) if not fuels: - warn(f'no heating agent can serve {remaining:.6g} kJ/hr of the ' - 'hot utility target; it is left unserved', RuntimeWarning) + _warn(f'no heating agent can serve {remaining:.6g} kJ/hr of ' + 'the hot utility target; it is left unserved') cool, remaining = self._allocate(cooling_agents, self.Q_cold, False) if remaining > tol: cool.append((-math.inf, remaining, None)) - warn(f'no cooling agent can serve {remaining:.6g} kJ/hr of the ' - 'cold utility target; it is left unserved', RuntimeWarning) + _warn(f'no cooling agent can serve {remaining:.6g} kJ/hr of the ' + 'cold utility target; it is left unserved') # the stretch each agent serves: from its level, clipped to the grid, # to the next zero of the balanced curve, the pinch or a utility pinch Ts, arr, lea = self._gcc() @@ -2481,14 +2504,13 @@ def evaluate(T_min_app): sweep = Supertargets(targets, records, options, scale_contributions) unserved = int(np.isnan(sweep['utility_cost']).sum()) if unserved: - warn(f'{unserved} of the {len(records)} minimum approach temperatures ' - 'evaluated leave part of a utility target unserved (no agent ' - 'given can serve it), so their TAC is nan', RuntimeWarning) + _warn(f'{unserved} of the {len(records)} minimum approach ' + 'temperatures evaluated leave part of a utility target ' + 'unserved (no agent given can serve it), so their TAC is nan') if not np.isfinite(sweep['TAC']).any(): - warn('no minimum approach temperature of the sweep has a finite TAC ' - '(each has an unserved utility or an infinite area), so it is ' - 'not refined and its optimum, the smallest, is meaningless', - RuntimeWarning) + _warn('no minimum approach temperature of the sweep has a finite TAC ' + '(each has an unserved utility or an infinite area), so it is ' + 'not refined and its optimum, the smallest, is meaningless') return sweep @@ -2584,8 +2606,8 @@ def _profile_area(x, dT, inv_U, ID, cause=''): (followed by `cause`) at a driving force of `_DT_ZERO` or less. """ if dT.min() <= _DT_ZERO: - warn(f'{ID} transfers heat at a driving force of {dT.min():.6g} K' - f'{cause}', RuntimeWarning) + _warn(f'{ID} transfers heat at a driving force of {dT.min():.6g} K' + f'{cause}') return math.inf LMTD = _lmtd(dT[:-1], dT[1:]) return float(np.sum(np.diff(x) / LMTD)) * inv_U / 3600. @@ -2649,6 +2671,43 @@ def _check_targets(HXN, targets): '(simulated since?): make ' 'them again with HXN.targets()') +def _warn(message): + """Warn `message` (a RuntimeWarning) at the line that called hensmith, + the first frame outside the package, not at a line of the package.""" + warn(message, RuntimeWarning, skip_file_prefixes=_PACKAGE_FRAMES) + +def _stale_labels(hxs, labels): + """The labels (of `labels`, aligned with `hxs`) of the exchangers `hxs` + whose first inlet and outlet differ in flow (``ins[0].mol`` and + ``outs[0].mol``) by more than `_STALE_FLOW` of the larger: an inlet + changed since its unit was simulated, whose stream the targets would + take from that new inlet to the outlet of the former state.""" + stale = [] + for hx, label in zip(hxs, labels): + a, b = hx.ins[0].mol, hx.outs[0].mol + if a.size == b.size and (np.abs(a - b).sum() + > _STALE_FLOW * max(a.sum(), b.sum())): + stale.append(label) + return stale + +def _warn_stale(labels, of=''): + """Warn (see `_warn`) of the streams of `labels` (see `_stale_labels`), + if any, in the targets `of` names (e.g. ``' of HXN'``).""" + if not labels: return + names = ', '.join(map(repr, labels)) + if len(labels) == 1: + _warn(f'the inlet and outlet of {names} differ in flow (its inlet ' + 'changed since its unit was simulated?): the targets' + f'{of} take its stream from its inlet now to its former ' + 'outlet, a state of no process; simulate the system (or its ' + 'units) first') + else: + _warn(f'the inlets and outlets of {names} differ in flow (their ' + 'inlets changed since their units were simulated?): the ' + f'targets{of} take their streams from their inlets now to ' + 'their former outlets, states of no process; simulate the ' + 'system (or its units) first') + def _rebuilt_label(hxs, labels, units): """The label (of `labels`, aligned with `hxs`) of the first of the exchangers `hxs` that is not one of `units` (a facility's units, their @@ -2948,7 +3007,8 @@ def network_report(HXN, targets=None, **options): if not hasattr(HXN, 'new_HXs_hot_side'): raise RuntimeError('simulate the heat exchanger network before ' 'reporting on it') - # (without the warning of stages rebuilt since: _check_network raises) + # (without the warnings of stages rebuilt since and of inlets changed + # in flow since: _check_network raises) if targets is None: targets = HXN._pinch_targets()[0] else: _check_targets(HXN, targets) _check_network(HXN, targets) diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 0b55c40..3306595 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -1418,6 +1418,8 @@ def test_sensible_agent_without_driving_force(): assert [str(x.message) for x in w] == [ f'no heating agent can serve {steam.duty:.6g} kJ/hr of the hot utility ' 'target; it is left unserved'] + # at the caller's line, not at hensmith's + assert [x.filename for x in w] == [__file__] # cooling: a condenser at a coolant's limit goes to the next coolant test_hxn_targets.setup('test_targeting_stuck_condenser', ['Water']) P = bst.settings.chemicals.Water.Psat(305.) @@ -2049,8 +2051,9 @@ def select(sys): r"than their process streams' .*they hold the utilities of " r"HeatExchangerNetwork 'HXN', simulated with " r"replace_unit_heat_utilities=True.*take HXN.targets\(\) instead") - with pytest.warns(RuntimeWarning, match=message): + with pytest.warns(RuntimeWarning, match=message) as record: t = PinchTargets.from_heat_utilities(hus, 10.) + assert [w.filename for w in record] == [__file__] # the caller's line assert t.labels == ['C1', 'H4'] facility = HXN.targets() assert facility.labels == ['C1', 'C3', 'H2', 'H4'] @@ -2490,6 +2493,81 @@ def test_facility_targets_reversed_stream(): assert t.Q_cold == pytest.approx(fresh.Q_cold, rel=1e-12) assert t.Q_cold > 1e6 > HXN.synthesis_info['Q_cold_target'] +def test_facility_targets_changed_inlet(): + # A feed changed in flow since its unit was simulated: each stream runs + # from its exchanger's inlet now to its outlet as last simulated, so the + # targets mix a new inlet with a former outlet (a state of no process), + # with a RuntimeWarning at the caller's line; the report raises instead + def stale_warnings(f, *args): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + result = f(*args) + return result, [x for x in w if 'differ in flow' in str(x.message)] + units = cp_units('test_targeting_changed_inlet', KEMP, K) + sys, HXN = simulate_HXN(units, 10.) + C1, C3 = units[0], units[2] + assert (C1.ID, C3.ID) == ('C1', 'C3') + hus = [u.heat_utilities[0] for u in units] + simulated, w = stale_warnings(HXN.targets) + assert not w + C1.ins[0].F_mass *= 2. + t, w = stale_warnings(HXN.targets) + assert [str(x.message) for x in w] == [ + "the inlet and outlet of 'C1' differ in flow (its inlet changed since " + 'its unit was simulated?): the targets of HXN take its stream from ' + 'its inlet now to its former outlet, a state of no process; simulate ' + 'the system (or its units) first'] + assert [x.filename for x in w] == [__file__] + # (the stale targets are of neither state) + assert t.Q_hot != simulated.Q_hot + selection, w = stale_warnings(PinchTargets.from_heat_utilities, hus, 10.) + assert [str(x.message) for x in w] == [ + "the inlet and outlet of 'C1' differ in flow (its inlet changed since " + 'its unit was simulated?): the targets take its stream from its inlet ' + 'now to its former outlet, a state of no process; simulate the system ' + '(or its units) first'] + assert [x.filename for x in w] == [__file__] + assert (selection.Q_hot, selection.Q_cold) == (t.Q_hot, t.Q_cold) + # the report raises, without the warning + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + with pytest.raises(ValueError, match='not of the current states of C1.*simulate the system'): + network_report(HXN) + assert not [x for x in w if 'differ in flow' in str(x.message)] + # two streams: both named + C3.ins[0].F_mass *= 0.5 + stale, w = stale_warnings(HXN.targets) + assert len(w) == 1 and str(w[0].message).startswith( + "the inlets and outlets of 'C1', 'C3' differ in flow (their inlets " + 'changed since their units were simulated?): the targets of HXN take ' + 'their streams') + # the units simulated alone: the targets of the process now, which the + # system's simulation synthesizes to + C1.simulate(); C3.simulate() + now, w = stale_warnings(HXN.targets) + assert not w and now.Q_hot != stale.Q_hot + simulate_strictly(sys) + info = HXN.synthesis_info + assert (now.Q_hot, now.Q_cold) == (pytest.approx(info['Q_hot_target'], rel=1e-12), + pytest.approx(info['Q_cold_target'], rel=1e-12)) + # a change of temperature alone keeps the flows equal: no warning, and + # the targets of a unit that sets its outlet by temperature are right + C1.ins[0].T -= 5. + t, w = stale_warnings(HXN.targets) + assert not w + C1.simulate() + assert HXN.targets().Q_hot == t.Q_hot + # before any simulation of the facility: the targets of the units' heat + # utilities warn (once) + units = cp_units('test_targeting_changed_inlet_alone', KEMP, K) + for u in units: u.simulate() + HXN = HeatExchangerNetwork('HXN', T_min_app=10., units=units) + units[3].ins[0].F_mass *= 1.5 + _, w = stale_warnings(HXN.targets) + assert len(w) == 1 and str(w[0].message).startswith( + "the inlet and outlet of 'H4' differ in flow (its inlet changed") + assert w[0].filename == __file__ + def test_facility_targets_replaced_callable(): # replace_unit_heat_utilities overwrites the heat utilities a callable # reads (the network's utility, or none for H2 and C3, which the process From 1432e0a8b9b6d1684538629437f4d99a81bc60e6 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sun, 18 Oct 2026 08:45:55 +0000 Subject: [PATCH 50/52] address review 12 (e2e): count the heat utilities of units in parallel once per unit, say a reversed stream in terms of its heat utility E2E-R12-1 (fixed): PinchTargets.from_heat_utilities refused any two heat utilities with duty of the same unit, but biosteam lists the heat utility of a unit in parallel once per unit in its owner's heat_utilities (Unit._load_costs; an AbstractStirredTankReactor, such as biosteam's fermentors, sets parallel['self'] = N for its recirculation heat exchanger). So the facility's selection, heat_exchanger_utilities_from_units(sys.units), and a facility's targets() before any simulation raised on such a process, and the advice to give each heat utility once dropped (N-1)/N of the reactor's cooling. A new _overcounted_units(hus, owners) refuses only the units whose heat utilities with duty are given more often than the unit or any owner on its chain (along _parent, with the held-owner relation) lists them, so a heat utility counts as a stream once per occurrence, as in the synthesis, and an overlapping selection (a unit and its auxiliary) still raises, now naming the counts ("'R1 - heat_exchanger' (6 given, 5 listed)") and advising heat_exchanger_utilities_from_units of units none of which owns another. key_units and the holders are now resolved before the check. Docstring (hus parameter, Raises) and tutorial ch05 wording fixed. Tests: new test_from_heat_utilities_of_units_in_parallel (a stirred-tank fermentor with parallel['self'] > 1: the selection, the preview facility's targets() before any simulation and the facility's targets() and synthesis targets after its simulation are equal, N reactor streams, the whole reactor cooling counted; the unit plus its auxiliary raises; the auxiliary alone is one stream); test_from_heat_utilities_takes_exchangers matches the new message. E2E-R12-2 (nit, fixed): from_heat_utilities now warns of stale streams (_stale_labels/_warn_stale) before it builds the targets, and turns the constructor's direction error (which named is_hot, a parameter the caller never gave) into one about the heat utility: "the heat utility of 'H1' heats (hu.duty = ...), but its stream, from its unit's inlet now to its outlet as last simulated, is cooled (...): its inlet has changed since its unit was simulated (a feed changed, or a recycle converged loosely?); simulate the system (or its units) first, converging any recycle more tightly". PinchTargets.__init__ still raises a plain ValueError with the same text, marked with the stream's index and change for this. Test: new test_from_heat_utilities_reversed_stream (feed made hotter than the heater's outlet: the new message, no is_hot, no stale warning; with a flow change too: the stale warning comes first). Tests: pytest hensmith/targeting.py tests/test_targeting.py tests/test_hxn_targets.py: 134 passed; hensmith/_heat_exchanger_network.py tests/test_hxn.py -k "replace or targets or doctest": 9 passed. The new tests fail on the previous code (the parallel refusal; the is_hot message). The synthesis path is untouched (targeting.py only), so the oracle was not rerun. No docs captures show these messages; sphinx is not installed here, the rst change is plain prose. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/tutorial/05_targeting.rst | 11 ++- hensmith/targeting.py | 131 ++++++++++++++++++++------ tests/test_targeting.py | 120 ++++++++++++++++++++++- 3 files changed, 225 insertions(+), 37 deletions(-) diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index 456121d..e00e702 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -72,9 +72,14 @@ facility, from the heat utilities of heat exchangers with ``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, and so does -a heat utility given twice (as selections from overlapping unit lists give -it), whose stream would count twice. Given +first inlet and outlet need not be the stream it heats or cools. 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 diff --git a/hensmith/targeting.py b/hensmith/targeting.py index d4e24d7..5b3664b 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -36,7 +36,7 @@ from .hxn_synthesis import (_problem_table, _pinch_cut, _format_H, _arrange_heat_utilities, _process_streams, _explicit_contributions, _check_key_levels, _nearest_key_value, _key_universe, _unknown_keys, - _unknown_key_hints, _held_owners, _top, _with_auxiliaries) + _unknown_key_hints, _held_owners, _parent, _top, _with_auxiliaries) __all__ = ('PinchTargets', 'supertarget', 'Supertargets', 'network_report', 'plot_composite_curves', 'plot_grand_composite_curve', @@ -314,6 +314,33 @@ def _isolated_pricing(agents): if previous is missing: flowsheets.pop(ID, None) else: flowsheets[ID] = previous +def _overcounted_units(hus, owners=None): + """ + The units given more heat utilities with duty in `hus` than they hold, + ``[(unit, given, listed), ...]`` in order of first occurrence: those + whose heat utilities `hus` holds more often than any unit on their + owner chain (`unit` and up along `hensmith.hxn_synthesis._parent` with + `owners`) lists them in its `heat_utilities`. biosteam lists the heat + utilities of a unit in parallel (``Unit.parallel``, as a + `biosteam.AbstractStirredTankReactor` sets for its recirculation heat + exchanger) once per unit in parallel in its owner's, each a stream of + the synthesis; a heat utility given more often than that is given + twice, as by selections from overlapping unit lists. + """ + given = {} + for hu in hus: + if hu.duty: given.setdefault(id(hu.unit), [hu.unit, 0])[1] += 1 + over = [] + for unit, n in given.values(): + if n == 1: continue + listed, above = 0, unit + while above is not None: + listed = max(listed, sum([hu.unit is unit and bool(hu.duty) + for hu in above.heat_utilities])) + above = _parent(above, owners) + if n > listed: over.append((unit, n, listed)) + return over + def _duty_off_stream(hu): """Whether the duty of the heat exchanger's heat utility `hu` (``hu.unit_duty``) differs from that of its process stream (``outs[0].H @@ -1084,12 +1111,16 @@ def __init__(self, streams_inlet, streams_quenched, is_hot, T_min_app=5., dH = np.array([c.H_out - c.H_in for c in curves]) tol = 1e-9 * float(np.abs(dH).sum()) for j in np.flatnonzero(np.where(is_hot, dH > tol, dH < -tol)): - raise ValueError(f'stream {labels[j]!r} is ' - f"{'heated' if dH[j] > 0. else 'cooled'} " - f'({dH[j]:+.6g} kJ/hr) but marked ' - f"{'hot' if is_hot[j] else 'cold'}: is_hot must " - 'be True for a cooled stream and False for a ' - 'heated one') + error = ValueError(f'stream {labels[j]!r} is ' + f"{'heated' if dH[j] > 0. else 'cooled'} " + f'({dH[j]:+.6g} kJ/hr) but marked ' + f"{'hot' if is_hot[j] else 'cold'}: is_hot " + 'must be True for a cooled stream and False ' + 'for a heated one') + # which stream, for `from_heat_utilities` to say it in terms of + # its heat utility, which gives its side + error._hensmith_reversed = (int(j), float(dH[j])) + raise error self.T_min_app = T_min_app self.approach_contributions = x self.is_hot = is_hot @@ -1134,7 +1165,13 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, ``bst.process_tools.heat_exchanger_utilities_from_units(sys.units)`` (those a heat exchanger network may take, ``hu.hxn_ok``, as `HeatExchangerNetwork` selects them), not ``sys.heat_utilities`` - (sums by agent, of no unit). Zero-duty ones are dropped. After a + (sums by agent, of no unit). Zero-duty ones are dropped. Each + counts once per occurrence, a stream each, as in the synthesis: + biosteam lists the heat utility of a unit in parallel + (``Unit.parallel``, as the recirculation heat exchanger of a + `biosteam.AbstractStirredTankReactor`, such as a fermentor, + with ``parallel['self']`` reactors) once per unit in its + owner's ``heat_utilities``, and so in this selection. After a simulation of a `HeatExchangerNetwork` with ``replace_unit_heat_utilities=True`` they hold the network's utilities, not the process duties, and those of the streams it @@ -1181,10 +1218,17 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, heat exchanger's (``hu.hxn_ok`` False, as for the heat utilities that reactors, mixers or compressors add with ``Unit.add_heat_utility``: their unit's first inlet and outlet - need not be the stream they heat or cool), or if two with duty - are of the same unit (the same heat utility given twice, as by - selections from overlapping unit lists), whose stream would - count once per heat utility. + need not be the stream they heat or cool). If the heat utilities + with duty of a unit are given more often than its + ``heat_utilities`` or those of any of its owners list them, as + when selections from overlapping unit lists (such as a unit and + an auxiliary unit of it) give the same heat utility twice: its + stream would count once more per extra one. If the stream of a + heat utility, from its unit's inlet now to its outlet as last + simulated, runs against the heat utility (cooled for a heating + one, or heated for a cooling one): its inlet has changed since + its unit was simulated (as the second warning below, given + before this error, may say too). Warns ----- @@ -1236,18 +1280,27 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, f'(hu.hxn_ok), not those of {names}, whose first ' 'inlet and outlet need not be the stream they ' f'heat or cool: {selection}') - seen = set() - repeated = [hu for hu in hus if hu.duty and (id(hu.unit) in seen - or seen.add(id(hu.unit)))] - if repeated: - names = ', '.join(dict.fromkeys(repr(_unit_label(hu.unit)) - for hu in repeated)) - raise ValueError(f'the stream of {names} would count once per ' - 'heat utility of its unit given (the same heat ' - 'utility given twice, as by selections from ' - 'overlapping unit lists, such as a unit and an ' - 'auxiliary unit of it): give each heat ' - "exchanger's heat utility once") + if key_units is not None: key_units = _with_auxiliaries(key_units) + holders = _held_owners([hu.unit for hu in hus] + (key_units or [])) + # a heat utility counts once per occurrence, as in the synthesis + # (biosteam repeats those of a unit in parallel): only more + # occurrences than its unit and owners list are given twice + over = _overcounted_units(hus, holders) + if over: + names = ', '.join(f'{_unit_label(unit, holders)!r} ({n} given, ' + f'{m} listed)' for unit, n, m in over) + raise ValueError(f'the heat utilities of {names} are given more ' + 'often than their units, or the owners of ' + 'these, list them in their heat_utilities (which ' + 'repeat those of a unit in parallel, by ' + 'Unit.parallel, once per unit, each a stream): ' + 'their streams would count once more per extra ' + 'one, as when selections from overlapping unit ' + 'lists (such as a unit and an auxiliary unit of ' + 'it) give the same heat utility twice; give the ' + 'heat utilities of units none of which owns ' + 'another, such as bst.process_tools.heat_' + 'exchanger_utilities_from_units(sys.units)') # only those that a network has written over: a duty may differ from # its stream's for another reason (biosteam's MESHDistillation total # condenser, 0.1 % off), which the targets, the streams', ignore @@ -1268,7 +1321,6 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, 'streams it serves completely have none and are left out; ' f"take {' or '.join(f'{ID}.targets()' for ID in IDs)} " 'instead') - if key_units is not None: key_units = _with_auxiliaries(key_units) values = _explicit_contributions(hus, approach_contributions, T_min_app, key_units) arranged, N_heating = _arrange_heat_utilities(hus, sort_hus_by_T) @@ -1278,13 +1330,30 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, value_of = dict(zip(map(id, hus), values)) contributions = [value_of[id(hu)] for hu in arranged] is_hot = [False] * N_heating + [True] * (len(arranged) - N_heating) - holders = _held_owners([hu.unit for hu in hus] + (key_units or [])) - self = cls._from_exchangers([hu.unit for hu in arranged], is_hot, - T_min_app, contributions, - force_ideal_thermo, None, arranged, - holders) + hxs = [hu.unit for hu in arranged] + labels = [_unit_label(hx, holders) for hx in hxs] + # before the targets, whose streams a changed inlet may reverse + _warn_stale(_stale_labels(hxs, labels)) + try: + self = cls._from_exchangers(hxs, is_hot, T_min_app, contributions, + force_ideal_thermo, labels, arranged, + holders) + except ValueError as error: + reversed_ = getattr(error, '_hensmith_reversed', None) + if reversed_ is None: raise + j, dH = reversed_ + hu = arranged[j] + raise ValueError( + f'the heat utility of {labels[j]!r} ' + f"{'heats' if hu.duty > 0. else 'cools'} (hu.duty = " + f'{hu.duty:+.6g} kJ/hr), but its stream, from its unit\'s ' + 'inlet now to its outlet as last simulated, is ' + f"{'heated' if dH > 0. else 'cooled'} ({dH:+.6g} kJ/hr): its " + 'inlet has changed since its unit was simulated (a feed ' + 'changed, or a recycle converged loosely?); simulate the ' + 'system (or its units) first, converging any recycle more ' + 'tightly') from None self.key_units = key_units - _warn_stale(_stale_labels(self.units, self.labels)) return self @classmethod diff --git a/tests/test_targeting.py b/tests/test_targeting.py index 3306595..d05402b 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -2017,10 +2017,12 @@ def test_from_heat_utilities_takes_exchangers(): selected = bst.process_tools.heat_exchanger_utilities_from_units(units) assert [hu.unit for hu in selected] == [H, C] t = PinchTargets.from_heat_utilities(sorted(selected, key=lambda hu: hu.duty), 10.) - # a heat utility given twice would count its stream twice + # a heat utility given twice (more often than its unit lists it) would + # count its stream twice for given in (selected + selected[:1], selected[:1] + selected): - with pytest.raises(ValueError, match=r"the stream of 'H' would count " - r"once per heat utility of its unit"): + with pytest.raises(ValueError, match=r"the heat utilities of 'H' \(2 given, 1 " + r"listed\) are given more often than " + r"their units.*heat_exchanger_utilities"): PinchTargets.from_heat_utilities(given, 10.) HXN = HeatExchangerNetwork('HXN', T_min_app=10., units=units) facility = HXN.targets() @@ -2033,6 +2035,118 @@ def test_from_heat_utilities_takes_exchangers(): [idle, *sorted(selected, key=lambda hu: hu.duty)], 10.) assert same.labels == t.labels and (same.Q_hot, same.Q_cold) == (t.Q_hot, t.Q_cold) +class Fermentation(bst.AbstractStirredTankReactor): + """biosteam's own example of a stirred-tank reactor (the + ContinuousFermentation of its docstring): in parallel, ``parallel['self']`` + reactors, each with a recirculation cooler (its auxiliary + ``heat_exchanger``, hxn_ok) at 1/N of the duty.""" + _N_ins = 1 + _N_outs = 2 + T_default = 32. + 273.15 + P_default = 101325. + tau_default = 8. + + def _run(self): + vent, effluent = self.outs + effluent.mix_from(self.ins, energy_balance=False) + self.reactions(effluent) + effluent.T = vent.T = self.T + effluent.P = vent.P = self.P + vent.phase = 'g' + vent.empty() + vent.receive_vent(effluent, energy_balance=False) + +def test_from_heat_utilities_of_units_in_parallel(): + # biosteam lists the heat utility of a unit in parallel once per unit in + # its owner's heat_utilities, and so in its selection: the synthesis + # takes it as a stream per reactor, and so do the targets of the + # selection and those of a facility before any simulation (which take + # the selection), equal to the facility's after its simulation + import thermosteam as tmo + glucose = tmo.Chemical('Glucose', phase='l', default=True) + test_hxn_targets.setup('test_targeting_parallel', ['Water', 'Ethanol', 'CO2', glucose]) + feed = bst.Stream('feed', Water=6000., Glucose=300., T=340.) + C0 = bst.HXutility('C0', ins=feed, T=305.15) + R1 = Fermentation('R1', ins=C0-0, outs=('vent', 'beer'), + reactions=bst.ReactionSystem( + bst.Reaction('Glucose -> 2Ethanol + 2CO2', 'Glucose', 0.9), + basis='mol')) + H1 = bst.HXutility('H1', ins=R1-1, T=360.) + units = [C0, R1, H1] + plain = bst.System.from_units('plain', units=units) + plain.simulate() + N = R1.parallel['self'] + hu = R1.heat_exchanger.heat_utilities[0] + assert N > 1 and hu.hxn_ok and hu.duty < 0. + assert [h is hu for h in R1.heat_utilities] == [True] * N + selected = bst.process_tools.heat_exchanger_utilities_from_units(units) + assert sum(h is hu for h in selected) == N + with warnings.catch_warnings(): + warnings.simplefilter('error') + t = PinchTargets.from_heat_utilities(sorted(selected, key=lambda h: h.duty), 10., + key_units=units) + preview = HeatExchangerNetwork('preview', T_min_app=10., units=units).targets() + assert t.labels.count('R1 - heat_exchanger') == N + assert preview.labels == t.labels + assert (preview.Q_hot, preview.Q_cold) == (t.Q_hot, t.Q_cold) + # each reactor's cooling counts: the reactor's whole duty, N times one + # recirculation cooler's + dH = R1.heat_exchanger.outs[0].H - R1.heat_exchanger.ins[0].H + alone = PinchTargets.from_heat_utilities([h for h in selected if h is not hu], 10.) + assert t.Q_cold - alone.Q_cold == pytest.approx(-N * dH, rel=1e-6) + # the heat utility given more often than the reactor lists it: a unit + # and its auxiliary unit both selected + overlapping = bst.process_tools.heat_exchanger_utilities_from_units( + [*units, R1.heat_exchanger]) + with pytest.raises(ValueError, match=r"the heat utilities of 'R1 - heat_exchanger' " + rf"\({N + 1} given, {N} listed\)"): + PinchTargets.from_heat_utilities(overlapping, 10., key_units=units) + # the auxiliary unit alone: its own single heat utility, one stream + single = PinchTargets.from_heat_utilities(R1.heat_exchanger.heat_utilities, 10.) + assert single.labels == ['R1 - heat_exchanger'] + # the facility's synthesis takes them alike + sys, HXN = simulate_HXN(units, 10., name='sys_parallel') + facility = HXN.targets() + assert facility.labels == preview.labels + assert (facility.Q_hot, facility.Q_cold) == (preview.Q_hot, preview.Q_cold) + info = HXN.synthesis_info + assert (info['Q_hot_target'], info['Q_cold_target']) == (t.Q_hot, t.Q_cold) + +def test_from_heat_utilities_reversed_stream(): + # a feed set hotter than its heater's outlet since the heater was + # simulated: the stream from the inlet now to the outlet as last + # simulated is cooled, against its heat utility, which heats. The error + # says so in terms of the heat utility (not of is_hot, which the caller + # never gave); the warning of a changed flow comes before it + test_hxn_targets.setup('test_targeting_reversed') + a = bst.Stream('a', Water=100., T=300.) + H1 = bst.HXutility('H1', ins=a, T=350.) + C1 = test_hxn_targets.utility_hx('C1', 370., 101325., 'l', 310., Water=100.) + H1.simulate() + hus = [H1.heat_utilities[0], C1.heat_utilities[0]] + a.T = 360. + message = (r"the heat utility of 'H1' heats \(hu.duty = \+\d+ kJ/hr\), but its " + r"stream, from its unit's inlet now to its outlet as last simulated, " + r"is cooled \(-\d+(\.\d+)? kJ/hr\): its inlet has changed since its " + r"unit was simulated") + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + with pytest.raises(ValueError, match=message) as error: + PinchTargets.from_heat_utilities(hus, 10.) + assert 'is_hot' not in str(error.value) + assert not [x for x in w if 'differ in flow' in str(x.message)] + a.imol['Water'] = 120. + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + with pytest.raises(ValueError, match=message): + PinchTargets.from_heat_utilities(hus, 10.) + assert [str(x.message)[:43] for x in w if 'differ in flow' in str(x.message)] == [ + "the inlet and outlet of 'H1' differ in flow"] + # simulated again: a cooler now + H1.simulate() + t = PinchTargets.from_heat_utilities([H1.heat_utilities[0], hus[1]], 10.) + assert t.labels == ['H1', 'C1'] and list(t.is_hot) == [True, True] + def test_from_heat_utilities_after_replaced_heat_utilities(kemp_hxn, kemp_hxn_replaced): # biosteam's selection after a simulation with # replace_unit_heat_utilities=True holds the network's utilities: C1 From 120dbadc735596413e0ad8aaf5702483f1dcfda9 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sun, 18 Oct 2026 10:42:29 +0000 Subject: [PATCH 51/52] address review 13 (f1-facility): count the streams of a unit in parallel in the cache rule and the targets F1F13-1 (fixed): - `_cost`: with cache_network, a network is reused only while the multiset of heat exchangers is the same (collections.Counter of the units behind the heat utilities against `original_heat_exchangers`), not the set: a unit in parallel whose Unit.parallel changed (a fermentor sized by its volume) changes the number of streams, and the network is synthesized again instead of being reused for the former number. Only the cache_network=True path changes, and only where the former set comparison reused a network of the wrong number of streams; cache_network=False (the default) never runs the comparison. - `HXN.targets()` warns (RuntimeWarning), and `network_report` raises a ValueError, when an exchanger of the network gives more or fewer streams than the facility's units list its heat utility now (a unit in parallel simulated alone whose number changed): new `targeting._miscounted` / `_miscounted_names`, returned by `_pinch_targets` and checked in `_check_network` before the end-state check. - Docs: the class Notes, the cache_network API row, concepts.rst ("Reusing a network"), tutorial ch04 describe the multiset; the targets() Warns/Notes, network_report Raises and tutorial ch05 qualify "the units alone give the targets of the process now". - Test: test_facility_units_in_parallel_whose_number_changes (cache kept at the same N, synthesized again at a new N with the loads and targets of a facility without the cache; units simulated alone warn and the report raises until the system is simulated). It fails with the former set comparison. Tests: tests/test_targeting.py tests/test_hxn_targets.py 129 passed; tests/test_hxn.py tests/test_hxn_contributions.py -k cache 12 passed; doctests of hensmith/_heat_exchanger_network.py hensmith/targeting.py 8 passed; oracle sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (29 cases, unchanged). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/API/heat_exchanger_network.rst | 2 +- docs/source/concepts.rst | 9 ++- docs/source/tutorial/04_configuring.rst | 3 +- docs/source/tutorial/05_targeting.rst | 8 ++- hensmith/_heat_exchanger_network.py | 77 +++++++++++++++------- hensmith/targeting.py | 63 ++++++++++++++++-- tests/test_targeting.py | 75 +++++++++++++++++++++ 7 files changed, 201 insertions(+), 36 deletions(-) diff --git a/docs/source/API/heat_exchanger_network.rst b/docs/source/API/heat_exchanger_network.rst index 21a4a51..1993a9b 100644 --- a/docs/source/API/heat_exchanger_network.rst +++ b/docs/source/API/heat_exchanger_network.rst @@ -54,7 +54,7 @@ each of them changes. - 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, ``stream_splitting`` and the explicit approach contributions (resolved per stream) are 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 (the splitters keep their branch fractions), 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. + - Reuse the network configuration of the previous simulation when the units contributing heat utilities (each as often as it gives a stream, as a unit in parallel does once per unit), ``stream_splitting`` and the explicit approach contributions (resolved per stream) are 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 (the splitters keep their branch fractions), 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. diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst index 96d90ea..f8fb788 100644 --- a/docs/source/concepts.rst +++ b/docs/source/concepts.rst @@ -992,9 +992,12 @@ own utilities. If no process match was made at all, the facility reports zero capital and no utilities. **Reusing a network.** With ``cache_network=True`` a later simulation checks -whether the set of units behind the heat utilities, and the explicit approach -contributions they resolve to, are unchanged; if they are, the -same network configuration is reused instead of being synthesized again. Each +whether the units behind the heat utilities, each as often as it gives a +stream (biosteam lists the heat utility of a unit in parallel once per unit, +so a reactor whose ``parallel['self']`` changes with its volume changes the +streams), and the explicit approach contributions they resolve to, are +unchanged; if they are, the same network configuration is reused instead of +being synthesized again. Each life cycle's first inlet is copied from the current stream, and each process exchanger keeps, as the enthalpy limit of its *must* stream (the hot stream above the pinch, the cold stream below it), the fraction of that stream's diff --git a/docs/source/tutorial/04_configuring.rst b/docs/source/tutorial/04_configuring.rst index 9428f6a..9556207 100644 --- a/docs/source/tutorial/04_configuring.rst +++ b/docs/source/tutorial/04_configuring.rst @@ -301,7 +301,8 @@ stream that finishes on process heat alone carries no utility symbol. simulations, which is worth doing when the same system is simulated many times with slightly different inputs, as in a Monte Carlo or a sensitivity analysis. When it is on and a network has already been synthesized, the units owning the -current heat utilities are compared with the exchangers behind the cached one, +current heat utilities are compared with the exchangers behind the cached one +(each as often as it gives a stream, as a unit in parallel does once per unit), and the explicit approach contributions, resolved to one value per stream, with those the cached network was synthesized with; if both match, the cached network is kept and only the stream states diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index e00e702..95d502b 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -64,8 +64,12 @@ 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 same object can be built without a -facility, from the heat utilities of heat exchangers with +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 :meth:`PinchTargets.from_heat_utilities ` (as the trade-off example below does) or from streams as :func:`~hensmith.problem_table` takes them. The facility's selection, diff --git a/hensmith/_heat_exchanger_network.py b/hensmith/_heat_exchanger_network.py index a88ce11..a3a0e59 100644 --- a/hensmith/_heat_exchanger_network.py +++ b/hensmith/_heat_exchanger_network.py @@ -18,6 +18,7 @@ """ import heapq import weakref +from collections import Counter from collections.abc import Mapping import biosteam as bst import numpy as np @@ -376,14 +377,16 @@ class HeatExchangerNetwork(bst.Facility): 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, `stream_splitting` and the explicit approach contributions - (resolved per stream) are 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. The splitters keep their + With `cache_network`, a network is reused while the multiset of heat + exchangers (each as often as it gives a stream: biosteam lists the heat + utility of a unit in parallel once per unit, by ``Unit.parallel``, which + a reactor may set by its volume), `stream_splitting` and the explicit + approach contributions (resolved per stream) are 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. The splitters keep their fractions, so a branch exchanger's limit is its branch's fraction of the whole stream's. If the cached network does not reproduce the outlets, or its energy balance is off, the network is synthesized again. @@ -713,11 +716,16 @@ def _cost(self): and getattr(self, '_synthesis_options', None) == (self.stream_splitting,)): # Units are a stable key to compare whether system has changed - # configuration; the explicit contributions, resolved per - # stream, must be those the network was synthesized with. + # configuration, each as often as it gives a stream: biosteam + # lists the heat utility of a unit in parallel once per unit + # (Unit.parallel, which a reactor may set by its volume), so a + # change of that number changes the streams; the explicit + # contributions, resolved per stream, must be those the network + # was synthesized with. hu_by_unit = {hu.unit: hu for hu in hx_utils} use_cached_network = ( - hu_by_unit.keys() == set(self.original_heat_exchangers) + Counter([hu.unit for hu in hx_utils]) + == Counter(self.original_heat_exchangers) and getattr(self, '_resolved_contributions', None) == ( None if value_of is None else tuple( value_of[id(hu_by_unit[hx])] @@ -1091,6 +1099,13 @@ def targets(self, T_min_app=None): exchanger no longer among the facility's units and their nested auxiliaries (rebuilt since the facility's last simulation, see Notes): the targets are of its former state. + RuntimeWarning + After a simulation, if an exchanger gives more or fewer streams + than the facility's units list its heat utility now: a unit in + parallel whose number (``Unit.parallel``, which a reactor may + set by its volume) has changed since, simulated alone (see + Notes). The targets keep a stream per unit of the facility's + last simulation. RuntimeWarning If the first inlet and outlet of a stream's exchanger differ in flow (``ins[0].mol`` and ``outs[0].mol``) by more than 1 % of @@ -1137,7 +1152,11 @@ def targets(self, T_min_app=None): meanwhile). Each stream runs from its exchanger's inlet now to its outlet as its unit last left it: after a feed or another stream changes, simulate the system before targeting (or the units alone, - which gives the targets of the process now). A stream whose inlet + which gives the targets of the process now, unless a unit in + parallel changes its number of units: biosteam lists its heat + utilities once per unit, and the streams stay one per unit at the + facility's last simulation, with a RuntimeWarning; + `hensmith.targeting.network_report` raises). A stream whose inlet has changed in flow meanwhile warns (see Warns); a change of temperature alone keeps the flows equal and warns of nothing, its targets being right only if its unit sets its outlet by @@ -1182,14 +1201,21 @@ def targets(self, T_min_app=None): """ - from .targeting import _warn_stale - targets, rebuilt, stale = self._pinch_targets(T_min_app) + from .targeting import _warn_stale, _miscounted_names + targets, rebuilt, miscounted, stale = self._pinch_targets(T_min_app) if rebuilt is not None: warn(f'the targets of {self.ID} are of the former state of ' f"{rebuilt}, which is no longer one of the facility's units " "nor an auxiliary of one (rebuilt since the facility's last " 'simulation?): simulate the system for those of the process ' 'now', RuntimeWarning, stacklevel=2) + if miscounted: + warn(f'the targets of {self.ID} take a stream per heat utility ' + "the facility's units listed at its last simulation, not " + 'as they list them now: ' + f'{_miscounted_names(miscounted)} (Unit.parallel changed ' + 'since, its unit simulated alone?): simulate the system for ' + 'those of the process now', RuntimeWarning, stacklevel=2) _warn_stale(stale, f' of {self.ID}') return targets @@ -1197,14 +1223,18 @@ def _pinch_targets(self, T_min_app=None): """`targets` (see there), the label of the first of their streams whose exchanger is no longer among the facility's units, their nested auxiliaries and the exchangers they hold through their heat - utilities only (rebuilt since the last simulation), or None, and - the labels of the streams whose exchanger's inlet has changed in - flow since its unit was simulated (see - `hensmith.targeting._stale_labels`). Both are None and empty - before a simulation, whose targets, - `PinchTargets.from_heat_utilities`, warn of the latter - themselves.""" - from .targeting import PinchTargets, _rebuilt_label, _stale_labels + utilities only (rebuilt since the last simulation), or None, the + exchangers of the others that give more or fewer streams than the + facility's units list their heat utility now (a unit in parallel + whose number has changed since, see + `hensmith.targeting._miscounted`), and the labels of the streams + whose exchanger's inlet has changed in flow since its unit was + simulated (see `hensmith.targeting._stale_labels`). They are None + and empty before a simulation, whose targets, + `PinchTargets.from_heat_utilities`, take the heat utilities as + listed now and warn of the last themselves.""" + from .targeting import (PinchTargets, _rebuilt_label, _stale_labels, + _miscounted) if T_min_app is None: T_min_app = self.T_min_app if (hasattr(self, 'original_heat_exchangers') and hasattr(self, 'original_heat_utils')): @@ -1242,6 +1272,7 @@ def _pinch_targets(self, T_min_app=None): targets.key_units = key_units targets.system = self.system return (targets, _rebuilt_label(hxs, targets.labels, key_units), + _miscounted(hxs, targets.labels, units, key_units), _stale_labels(hxs, targets.labels)) if self.system is None and not self.units: raise RuntimeError('simulate the system (or give the facility its ' @@ -1253,7 +1284,7 @@ def _pinch_targets(self, T_min_app=None): hx_utils, T_min_app, self._network_contributions(hx_utils, units), self.force_ideal_thermo, self.sort_hus_by_T, key_units=units) targets.system = self.system - return targets, None, [] + return targets, None, [], [] def get_original_hxs_associated_with_streams(self): # pragma: no cover original_units = self.system.units diff --git a/hensmith/targeting.py b/hensmith/targeting.py index 5b3664b..d17b69a 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -2788,6 +2788,40 @@ def _rebuilt_label(hxs, labels, units): if id(hx) not in at_hand: return label return None +def _miscounted(hxs, labels, units, at_hand): + """ + ``[(label, n, listed), ...]``, in order of first occurrence in `hxs` + (`labels` aligned with it): the exchangers of `hxs` that are among + `at_hand` (not rebuilt since, see `_rebuilt_label`) and give `n` of its + streams, one per time their heat utility was listed at the synthesis, + while the `heat_utilities` of `units` (a facility's units, from which + its synthesis takes the heat utilities) list a heat exchanger's heat + utility of theirs `listed` times now, with or without duty. biosteam + lists the heat utilities of a unit in parallel once per unit + (``Unit.parallel``, which a `biosteam.AbstractStirredTankReactor` sets + by its volume), so their number changes with the unit's size when the + unit is simulated alone; the streams stay one per unit at the facility's + last simulation. + """ + given = {} + for hx, label in zip(hxs, labels): + given.setdefault(id(hx), [label, 0])[1] += 1 + listed = {} + for unit in units: + for hu in unit.heat_utilities: + if hu.hxn_ok: listed[id(hu.unit)] = listed.get(id(hu.unit), 0) + 1 + at_hand = {id(unit) for unit in at_hand} + return [(label, n, listed.get(key, 0)) + for key, (label, n) in given.items() + if key in at_hand and listed.get(key, 0) != n] + +def _miscounted_names(miscounted): + """The streams of `miscounted` (see `_miscounted`) for a message, e.g. + ``"'R1 - heat_exchanger' 5 times, listed 2 times now"``.""" + times = lambda n: f'{n} time{"" if n == 1 else "s"}' + return '; '.join(f'{label!r} {times(n)}, listed {times(m)} now' + for label, n, m in miscounted) + def _check_network(HXN, targets): """Raise a ValueError unless every stream of `HXN`'s network is that of an original exchanger still among the facility's units, their nested @@ -2795,19 +2829,32 @@ def _check_network(HXN, targets): only (see `_rebuilt_label`; not one that its unit has rebuilt since, as a `biosteam.MultistageCompressor` simulated alone after a change of `pr` rebuilds its intercoolers, whose former objects keep their former, - consistent states), and enters and leaves the network at the end states + consistent states), every such exchanger gives as many streams as the + facility's units list its heat utility now (see `_miscounted`: not a + unit in parallel whose number has changed since), and every stream + enters and leaves the network at the end states of its curve in `targets` (which are of the units' current states): its copy of the real inlet where it enters the network, and the outlet of its last stage (the network's copies share the curves' thermodynamic basis).""" - label = _rebuilt_label(HXN.original_heat_exchangers, targets.labels, - HXN._candidate_units()) + hxs = HXN.original_heat_exchangers + units = list(HXN._get_units()) + at_hand = HXN._candidate_units(units) + label = _rebuilt_label(hxs, targets.labels, at_hand) if label is not None: raise ValueError(f'the network of {HXN.ID} is of {label}, which is ' "no longer one of the facility's units nor an " 'auxiliary of one (rebuilt since the ' "facility's last simulation?): simulate the " 'system') + miscounted = _miscounted(hxs, targets.labels, units, at_hand) + if miscounted: + raise ValueError(f'the network of {HXN.ID} has a stream per heat ' + 'utility its units listed at its last simulation, ' + 'not as they list them now: ' + f'{_miscounted_names(miscounted)} (Unit.parallel ' + 'changed since, its unit simulated alone?): ' + 'simulate the system') for life_cycle, c, label in zip(HXN.stream_life_cycles, targets.curves, targets.labels): entry = life_cycle.entry @@ -2934,11 +2981,15 @@ def network_report(HXN, targets=None, **options): If `targets` are not those of the facility's process streams in their current states (enthalpies at both ends, and pressure and flows at the inlet), if the network's streams do not enter and - leave it at those states or are of exchangers no longer among the - facility's units and their auxiliaries (a unit or a stream changed + leave it at those states, are of exchangers no longer among the + facility's units and their auxiliaries or are not as many as the + units list their heat utilities now (a unit or a stream changed since the facility's last simulation: a `biosteam.MultistageCompressor` simulated alone after a change of - `pr`, say, rebuilds its intercoolers), or for an invalid option. + `pr`, say, rebuilds its intercoolers, and a unit in parallel + simulated alone may change its number of units, ``Unit.parallel``, + whose heat utilities biosteam lists once per unit), or for an + invalid option. TypeError For an unknown option. diff --git a/tests/test_targeting.py b/tests/test_targeting.py index d05402b..f2841dc 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -2112,6 +2112,81 @@ def test_from_heat_utilities_of_units_in_parallel(): info = HXN.synthesis_info assert (info['Q_hot_target'], info['Q_cold_target']) == (t.Q_hot, t.Q_cold) +def test_facility_units_in_parallel_whose_number_changes(): + # a fermentor sets its number of reactors in parallel by its volume, so + # a feed change changes the number of streams of its recirculation + # cooler. With cache_network, the network is synthesized again for the + # new number (it was reused for the former one, its loads those of the + # former number of reactors, while only the set of exchangers was + # compared); with the units simulated alone, the facility's streams stay + # one per reactor of its last simulation, which its targets warn of and + # its report refuses until the system is simulated + import thermosteam as tmo + glucose = tmo.Chemical('Glucose', phase='l', default=True) + test_hxn_targets.setup('test_targeting_parallel_changed', + ['Water', 'Ethanol', 'CO2', glucose]) + feed = bst.Stream('feed', Water=6000., Glucose=300., T=340.) + C0 = bst.HXutility('C0', ins=feed, T=305.15) + R1 = Fermentation('R1', ins=C0-0, outs=('vent', 'beer'), + reactions=bst.ReactionSystem( + bst.Reaction('Glucose -> 2Ethanol + 2CO2', 'Glucose', 0.9), + basis='mol')) + H1 = bst.HXutility('H1', ins=R1-1, T=360.) + C2 = bst.HXutility('C2', ins=bst.Stream('cold', Water=3000., T=290.), T=320.) + units = [C0, R1, H1, C2] + sys, HXN = simulate_HXN(units, 5., name='sys_parallel_changed', + cache_network=True) + hx = R1.heat_exchanger + count = lambda: HXN.original_heat_exchangers.count(hx) + N = R1.parallel['self'] + assert N > 2 and count() == N + # the same number of reactors: the cached network is reused + network = HXN.HXN_sys + feed.F_mol *= 1.000001 + simulate_strictly(sys) + assert R1.parallel['self'] == N and HXN.HXN_sys is network + # fewer reactors: synthesized again, as a facility without the cache + feed.F_mol *= 0.4 + simulate_strictly(sys) + N = R1.parallel['self'] + assert HXN.HXN_sys is not network and count() == N + cached = (HXN.actual_heat_util_load, HXN.actual_cool_util_load, + HXN.installed_costs['Heat exchangers']) + with warnings.catch_warnings(): + warnings.simplefilter('error', RuntimeWarning) + targets = HXN.targets() + network_report(HXN) + assert targets.labels.count('R1 - heat_exchanger') == N + HXN.cache_network = False + simulate_strictly(sys) + assert (HXN.actual_heat_util_load, HXN.actual_cool_util_load, + HXN.installed_costs['Heat exchangers']) == cached + assert HXN.targets().summary() == targets.summary() + # more reactors, the units simulated alone: the streams stay one per + # reactor of the facility's last simulation, at the new duty of each + feed.F_mol *= 2.5 + for unit in units: unit.simulate() + assert R1.parallel['self'] > N + message = (rf"'R1 - heat_exchanger' {N} times, listed " + rf"{R1.parallel['self']} times now \(Unit.parallel changed " + r"since, its unit simulated alone\?\): simulate the system") + with pytest.warns(RuntimeWarning, match='the targets of HXN take a stream ' + 'per heat utility .*: ' + message): + targets = HXN.targets() + assert targets.labels.count('R1 - heat_exchanger') == N + for args in ((HXN,), (HXN, targets)): + with pytest.raises(ValueError, match='the network of HXN has a stream ' + 'per heat utility .*: ' + message): + network_report(*args) + # simulated again: one stream per reactor now, and no warning + N = R1.parallel['self'] + simulate_strictly(sys) + assert count() == N + with warnings.catch_warnings(): + warnings.simplefilter('error', RuntimeWarning) + assert HXN.targets().labels.count('R1 - heat_exchanger') == N + network_report(HXN) + def test_from_heat_utilities_reversed_stream(): # a feed set hotter than its heater's outlet since the heater was # simulated: the stream from the inlet now to the outlet as last From e1877b71329820c53928a2931bf63921bb6f4e25 Mon Sep 17 00:00:00 2001 From: Sarang Bhagwat Date: Sun, 18 Oct 2026 13:00:12 +0000 Subject: [PATCH 52/52] address review 14 (tg-core): refuse heat utilities their units no longer hold TG14-1 (fixed): biosteam gives a unit new heat utilities each time it is simulated, emptying the former ones (duty 0), so a selection kept across a simulation lost those streams silently in PinchTargets.from_heat_utilities (dropped as zero-duty ones), or, after a system simulation, failed with "needs at least one process stream". from_heat_utilities now raises a ValueError naming the units of any heat utility not (or no longer) among its unit's heat_utilities (an identity scan, as _restore_unit_heat_utilities already does), saying a selection is valid until its units are simulated again and to select them again. The changed-inlet warning (_warn_stale, new reselect flag, used by from_heat_utilities only; the facility's HXN.targets message is unchanged) and the reversed-stream error now add "then select the heat utilities again". The hus parameter, Raises and Warns docs and tutorial ch05 say the selection is valid until its units are simulated again. New test test_from_heat_utilities_of_a_former_selection (simulate one unit, then the system: ValueError naming the units; a fresh selection is right); test_from_heat_utilities_takes_exchangers now adds its idle heat utility to its unit before expecting it dropped (an unheld one raises); the changed-inlet and reversed-stream tests check the new advice. Synthesis and facility paths unchanged. Tests: tests/test_targeting.py, tests/test_hxn_targets.py, tests/test_hxn_contributions.py and the hensmith/targeting.py doctests: 194 passed. Oracle sha256 dcd15864a700a58d4edb6273f8ebaee729f7a821ba166e5de7ce5764aa1691bf (unchanged). sphinx is not installed here; the docs changes are plain prose. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HJhJoUiNNefNdnCJQEZ3q9 --- docs/source/tutorial/05_targeting.rst | 8 +++- hensmith/targeting.py | 56 ++++++++++++++++-------- tests/test_targeting.py | 61 ++++++++++++++++++++++++++- 3 files changed, 104 insertions(+), 21 deletions(-) diff --git a/docs/source/tutorial/05_targeting.rst b/docs/source/tutorial/05_targeting.rst index 95d502b..52fac40 100644 --- a/docs/source/tutorial/05_targeting.rst +++ b/docs/source/tutorial/05_targeting.rst @@ -76,8 +76,12 @@ built without a facility, from the heat utilities of heat exchangers with ``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 heat -utility counts as a stream each time it is given, as in the synthesis: +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 diff --git a/hensmith/targeting.py b/hensmith/targeting.py index d17b69a..be3b3a4 100644 --- a/hensmith/targeting.py +++ b/hensmith/targeting.py @@ -1165,7 +1165,10 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, ``bst.process_tools.heat_exchanger_utilities_from_units(sys.units)`` (those a heat exchanger network may take, ``hu.hxn_ok``, as `HeatExchangerNetwork` selects them), not ``sys.heat_utilities`` - (sums by agent, of no unit). Zero-duty ones are dropped. Each + (sums by agent, of no unit). 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. Zero-duty ones are dropped. Each counts once per occurrence, a stream each, as in the synthesis: biosteam lists the heat utility of a unit in parallel (``Unit.parallel``, as the recirculation heat exchanger of a @@ -1214,9 +1217,11 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, Raises ------ ValueError - If a heat utility belongs to no unit, or one with duty is not a - heat exchanger's (``hu.hxn_ok`` False, as for the heat utilities - that reactors, mixers or compressors add with + If a heat utility belongs to no unit, or is not (or no longer) + among its unit's ``heat_utilities`` (of a selection made before + its unit was simulated again), or one with duty is not a heat + exchanger's (``hu.hxn_ok`` False, as for the heat utilities that + reactors, mixers or compressors add with ``Unit.add_heat_utility``: their unit's first inlet and outlet need not be the stream they heat or cool). If the heat utilities with duty of a unit are given more often than its @@ -1252,11 +1257,11 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, (a feed changed, its unit not simulated since), and the targets take its stream from that new inlet to the outlet of the former state, a state of no process: simulate the system (or the - units) first. A change of temperature alone keeps the flows - equal and warns of nothing: the targets are then of the new - inlet and of the outlet as last simulated, which is the - outlet's state now if the unit sets it by temperature (as a - `biosteam.HXutility` with ``T`` does). + units) first, then select the heat utilities again. A change of + temperature alone keeps the flows equal and warns of nothing: + the targets are then of the new inlet and of the outlet as last + simulated, which is the outlet's state now if the unit sets it + by temperature (as a `biosteam.HXutility` with ``T`` does). """ hus = list(hus) @@ -1272,6 +1277,21 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, 'exchanger (hu.unit), not be one of ' 'System.heat_utilities (sums by agent, of no ' f'unit): {selection}') + # biosteam gives a unit new heat utilities each time it is simulated, + # emptying the former ones (no duty), which would drop out silently + former = [hu for hu in hus + if not any(i is hu for i in hu.unit.heat_utilities)] + if former: + names = ', '.join(dict.fromkeys(repr(_unit_label(hu.unit)) + for hu in former)) + raise ValueError(f'the heat utilities of {names} are not (or no ' + "longer) among their units' heat_utilities: " + 'biosteam gives a unit new heat utilities each ' + 'time it is simulated, emptying the former ones, ' + 'so a selection is valid until its units are ' + 'simulated again; select them again after any ' + 'simulation, e.g. bst.process_tools.heat_' + 'exchanger_utilities_from_units(sys.units)') others = [hu for hu in hus if hu.duty and not hu.hxn_ok] if others: names = ', '.join(dict.fromkeys(repr(_unit_label(hu.unit)) @@ -1333,7 +1353,7 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, hxs = [hu.unit for hu in arranged] labels = [_unit_label(hx, holders) for hx in hxs] # before the targets, whose streams a changed inlet may reverse - _warn_stale(_stale_labels(hxs, labels)) + _warn_stale(_stale_labels(hxs, labels), reselect=True) try: self = cls._from_exchangers(hxs, is_hot, T_min_app, contributions, force_ideal_thermo, labels, arranged, @@ -1352,7 +1372,7 @@ def from_heat_utilities(cls, hus, T_min_app=5., approach_contributions=None, 'inlet has changed since its unit was simulated (a feed ' 'changed, or a recycle converged loosely?); simulate the ' 'system (or its units) first, converging any recycle more ' - 'tightly') from None + 'tightly, then select the heat utilities again') from None self.key_units = key_units return self @@ -2759,23 +2779,25 @@ def _stale_labels(hxs, labels): stale.append(label) return stale -def _warn_stale(labels, of=''): +def _warn_stale(labels, of='', reselect=False): """Warn (see `_warn`) of the streams of `labels` (see `_stale_labels`), - if any, in the targets `of` names (e.g. ``' of HXN'``).""" + if any, in the targets `of` names (e.g. ``' of HXN'``); with `reselect` + (targets of a selection of heat utilities, which a simulation of their + units leaves behind), saying to select them again after simulating.""" if not labels: return names = ', '.join(map(repr, labels)) + advice = 'simulate the system (or its units) first' + if reselect: advice += ', then select the heat utilities again' if len(labels) == 1: _warn(f'the inlet and outlet of {names} differ in flow (its inlet ' 'changed since its unit was simulated?): the targets' f'{of} take its stream from its inlet now to its former ' - 'outlet, a state of no process; simulate the system (or its ' - 'units) first') + f'outlet, a state of no process; {advice}') else: _warn(f'the inlets and outlets of {names} differ in flow (their ' 'inlets changed since their units were simulated?): the ' f'targets{of} take their streams from their inlets now to ' - 'their former outlets, states of no process; simulate the ' - 'system (or its units) first') + f'their former outlets, states of no process; {advice}') def _rebuilt_label(hxs, labels, units): """The label (of `labels`, aligned with `hxs`) of the first of the diff --git a/tests/test_targeting.py b/tests/test_targeting.py index f2841dc..20a7b10 100644 --- a/tests/test_targeting.py +++ b/tests/test_targeting.py @@ -2028,9 +2028,15 @@ def test_from_heat_utilities_takes_exchangers(): facility = HXN.targets() assert facility.labels == t.labels == ['C', 'H'] assert (facility.Q_hot, facility.Q_cold) == (t.Q_hot, t.Q_cold) - # a non-exchanger heat utility without duty is dropped, as any is + # a non-exchanger heat utility without duty is dropped, as any is, once + # its unit holds it (one that it does not hold raises, see + # test_from_heat_utilities_of_a_former_selection) idle = bst.HeatUtility(unit=M) assert not idle.hxn_ok and idle.duty == 0. + with pytest.raises(ValueError, match=r"the heat utilities of 'M' are not \(or no " + r"longer\) among their units' heat_utilities"): + PinchTargets.from_heat_utilities([idle, *selected], 10.) + M.heat_utilities.append(idle) same = PinchTargets.from_heat_utilities( [idle, *sorted(selected, key=lambda hu: hu.duty)], 10.) assert same.labels == t.labels and (same.Q_hot, same.Q_cold) == (t.Q_hot, t.Q_cold) @@ -2209,6 +2215,9 @@ def test_from_heat_utilities_reversed_stream(): with pytest.raises(ValueError, match=message) as error: PinchTargets.from_heat_utilities(hus, 10.) assert 'is_hot' not in str(error.value) + assert str(error.value).endswith('simulate the system (or its units) first, ' + 'converging any recycle more tightly, then ' + 'select the heat utilities again') assert not [x for x in w if 'differ in flow' in str(x.message)] a.imol['Water'] = 120. with warnings.catch_warnings(record=True) as w: @@ -2222,6 +2231,54 @@ def test_from_heat_utilities_reversed_stream(): t = PinchTargets.from_heat_utilities([H1.heat_utilities[0], hus[1]], 10.) assert t.labels == ['H1', 'C1'] and list(t.is_hot) == [True, True] +def test_from_heat_utilities_of_a_former_selection(): + # biosteam gives a unit new heat utilities each time it is simulated, + # emptying the former ones (no duty): a selection kept from before is + # refused, naming the units simulated since, rather than leaving their + # streams out as zero-duty ones (the cold utility target 60 % off here) + # or, once the whole system is simulated, finding no stream at all. The + # warning of a changed inlet, which comes first, says to select them + # again after simulating; a selection made after a simulation is right + name = 'test_targeting_former_selection' + units = cp_units(name, [('H1', 400., 300., 1.), ('C1', 330., 440., 1.), + ('H3', 420., 340., .5)]) + H1, C1, H3 = units + sys = bst.System.from_units(name, units) + sys.simulate() + select = lambda: bst.process_tools.heat_exchanger_utilities_from_units(sys.units) + hus = select() + assert [hu.unit for hu in hus] == units + assert PinchTargets.from_heat_utilities(hus, 10.).labels == ['C1', 'H1', 'H3'] + H3.ins[0].F_mol *= 2. + with pytest.warns(RuntimeWarning, match=r"the inlet and outlet of 'H3' differ in " + r"flow .*simulate the system \(or its " + r"units\) first, then select the heat " + r"utilities again$"): + PinchTargets.from_heat_utilities(hus, 10.) + H3.simulate() # as the warning says + assert hus[2].duty == 0. and not any(hu is hus[2] for hu in H3.heat_utilities) + message = (r"the heat utilities of {} are not \(or no longer\) among their " + r"units' heat_utilities: biosteam gives a unit new heat utilities " + r"each time it is simulated, emptying the former ones, so a " + r"selection is valid until its units are simulated again; select " + r"them again after any simulation, e\.g\. bst\.process_tools\." + r"heat_exchanger_utilities_from_units\(sys\.units\)$") + with warnings.catch_warnings(): + warnings.simplefilter('error', RuntimeWarning) + with pytest.raises(ValueError, match=message.format("'H3'")): + PinchTargets.from_heat_utilities(hus, 10.) + now = PinchTargets.from_heat_utilities(select(), 10.) + assert now.labels == ['C1', 'H1', 'H3'] + assert (now.Q_hot, now.Q_cold) == (pytest.approx(1.08e5, rel=1e-9), + pytest.approx(3.6e5, rel=1e-9)) + # the whole system simulated: every heat utility of the former selection + # is empty, and each unit is named + sys.simulate() + with pytest.raises(ValueError, match=message.format("'H1', 'C1', 'H3'")): + PinchTargets.from_heat_utilities(hus, 10.) + again = PinchTargets.from_heat_utilities(select(), 10.) + assert (again.Q_hot, again.Q_cold) == (now.Q_hot, now.Q_cold) + def test_from_heat_utilities_after_replaced_heat_utilities(kemp_hxn, kemp_hxn_replaced): # biosteam's selection after a simulation with # replace_unit_heat_utilities=True holds the network's utilities: C1 @@ -2714,7 +2771,7 @@ def stale_warnings(f, *args): "the inlet and outlet of 'C1' differ in flow (its inlet changed since " 'its unit was simulated?): the targets take its stream from its inlet ' 'now to its former outlet, a state of no process; simulate the system ' - '(or its units) first'] + '(or its units) first, then select the heat utilities again'] assert [x.filename for x in w] == [__file__] assert (selection.Q_hot, selection.Q_cold) == (t.Q_hot, t.Q_cold) # the report raises, without the warning