Skip to content

Add pinch diagram plot for HeatExchangerNetwork - #255

Open
sarangbhagwat wants to merge 6 commits into
fix-hxn-pinch-analysisfrom
hxn-pinch-diagram
Open

Add pinch diagram plot for HeatExchangerNetwork#255
sarangbhagwat wants to merge 6 commits into
fix-hxn-pinch-analysisfrom
hxn-pinch-diagram

Conversation

@sarangbhagwat

Copy link
Copy Markdown
Member

Summary

Adds a way to visualize a synthesized heat exchanger network as a pinch diagram (the standard figure of cold streams above hot streams, exchanger connectors, utilities, and the pinch line).

  • hxn_synthesis.plot_pinch_diagram(...) — pure plotting function, returns (fig, ax).
  • HeatExchangerNetwork.plot_pinch_diagram(file=None, **kwargs) — thin wrapper.

Stacked on #254 (which is stacked on #253); retarget to master once those merge. Only the three commits 51a38fba, bd508d76, 60fd3303 belong to this PR.

The diagram

  • Cold streams (blue, flowing left→right) above hot streams (red, flowing right→left), with inlet/outlet T [°C] and H [kJ/hr] at both ends and the stream index in bold at the inlet.
  • One vertical connector per HXprocess, with a boxed ΔH label; cold-side exchangers left of a dashed pinch line, hot-side exchangers right of it, with shaded "Cold side" / "Hot side" backgrounds.
  • Circles for utility exchangers whose duty exceeds Qmin (red = hot utility on cold streams, blue = cold utility on hot streams).
  • Stream labels <unit> - <auxiliary> (<stream ID>), e.g. D303 - condenser (vapor), toggled by show_units, show_auxiliary_units, show_stream_IDs (all default True).
  • A compact legend below the axes (show_legend=True).

Design notes

  • The hot and cold stream of each exchanger come from the StreamLifeCycles by identity, and the side of the pinch from two new additive attributes HXN.new_HXs_hot_side / new_HXs_cold_side stored in _cost — no parsing of HX_<i>_<j>_<side> IDs.
  • Main unit and auxiliary name come from Unit.owner and a recursive search of get_auxiliary_units_with_names() (works for nested auxiliaries), rather than the ID-scanning in get_original_hxs_associated_with_streams.
  • Exchanger columns on each side are ordered by a topological sort under the constraint that each stream meets its exchangers in its flow direction (hot streams reversed), falling back to synthesis order if the constraints are cyclic — deterministic and readable.
  • Artists carry gids (HX:<ID>, Util:<ID>, Label:<i>) so the tests check the figure structurally.

Validation

  • tests/test_hxn.py: column-ordering unit test (incl. hot-stream reversal and cyclic fallback), structural test on the class doctest system, label composition/toggle test, legend test. tests/test_hxn.py + hxn doctests: 19 passed. Docstring example added.
  • Rendered the doctest system and the full sugarcane network (HXN.units=None) and checked them against a reference pinch figure.
  • Canonical suite (pytest . --disable-numba=1 -m "not slow"): 74 failed / 477 passed / 62 skipped. All failures pre-exist on the parent commit bbea7059 with this change stashed (baseline items plus sibling biorefineries/thermosteam clone drift: test_oilcane_O6/O8/O9, test_tire_modeling).

🤖 Generated with Claude Code

sarangbhagwat and others added 6 commits August 22, 2026 19:41
HeatExchangerNetwork had no way to visualize the synthesized network. Add
hxn_synthesis.plot_pinch_diagram (pure function, returns fig/ax) and a
HeatExchangerNetwork.plot_pinch_diagram wrapper that draw the standard
diagram: cold streams (blue, left to right) above hot streams (red, right
to left) with inlet/outlet T [degC] and H [kJ/hr], one vertical connector
with its duty per HXprocess, a dashed pinch line separating cold-side from
hot-side exchangers, and circles marking utility exchangers whose duty
exceeds Qmin.

Design notes:
- The hot and cold stream of each exchanger are found from the stream
  life cycles by identity, not by parsing HX_<i>_<j>_<side> IDs.
- Which side of the pinch an exchanger belongs to comes from the new
  attributes new_HXs_hot_side / new_HXs_cold_side stored in _cost (only
  their concatenation new_HXs was kept before).
- Columns on each side are ordered by a topological sort of the
  exchangers under the constraint that every stream meets them in its
  flow direction (hot streams reversed); contradictory constraints fall
  back to synthesis order. This keeps the diagram readable and
  deterministic.
- Artists carry gids (HX:<ID>, Util:<ID>) so tests can check the drawing
  structurally.

Validation: tests/test_hxn.py gains an ordering unit test (including the
hot-stream reversal and the cyclic fallback) and a structural test on the
class doctest system; docstring example added. tests/test_hxn.py + hxn
doctests: 17 passed. Rendered the doctest system and the full sugarcane
network (HXN.units=None) and checked them against the reference figure.
Canonical suite: 74 failed / 476 passed / 62 skipped; the 3 failures
beyond the previous run (test_oilcane_O6/O8/O9, test_tire_modeling) also
fail on the parent commit bbea705 with this change stashed - they come
from the sibling biorefineries/thermosteam clone state, not from biosteam.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…am ID

Stream indices alone do not say which process stream a row of the pinch
diagram is. Add a label next to each index, '<unit> - <auxiliary> (<ID>)',
with one toggle per part on plot_pinch_diagram: show_units (the unit that
owns the stream's original heat exchanger, i.e. the main unit for auxiliary
exchangers), show_auxiliary_units (the exchanger's name within that unit,
e.g. 'condenser' or a dotted path for nested auxiliaries), and
show_stream_IDs (the original exchanger's inlet stream ID; unnamed inlets
add nothing). All default to True.

The owner comes from Unit.owner and the auxiliary name from a recursive
search of get_auxiliary_units_with_names(), so no unit or stream IDs are
parsed (unlike get_original_hxs_associated_with_streams, which scans for
'.' in IDs and special-cases unit classes). plot_pinch_diagram takes the
per-stream original exchangers as a new original_hxs argument (required
when any label part is on); HeatExchangerNetwork.plot_pinch_diagram passes
original_heat_exchangers. Labels carry gid 'Label:<index>'.

Validation: new test_pinch_diagram_stream_labels (helper composition,
figure labels, toggles off -> no labels); tests/test_hxn.py + hxn doctests
18 passed. Rendered the full sugarcane network to check nested auxiliary
names. Canonical suite 74 failed / 477 passed / 62 skipped, same
pre-existing failure set as the previous commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New show_legend toggle (default True) on plot_pinch_diagram draws a
three-column legend below the axes with proxy handles for the six symbols:
cold stream, hot stream, process heat exchange, hot utility, cold utility,
and the pinch line. Placed with bbox_to_anchor below the axes so it never
overlaps the stream rows; savefig already uses bbox_inches='tight'.

Validation: new test_pinch_diagram_legend (entries and order; toggle off
-> no legend); tests/test_hxn.py + hxn doctests 19 passed; sugarcane
network re-rendered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Code-review fixes for the pinch diagram:

- Utility markers were colored by the stream they sit on, so hot utilities
  (on cold streams) were blue and cold utilities red - the opposite of the
  reference figure and of the diagram's own legend. Color them by utility
  type. test_pinch_diagram_legend now checks every Util marker's edge color
  against the legend handle for its type, so the legend and drawing cannot
  drift apart again.
- Stream labels were drawn at the same zorder as the connectors and ran
  through the hot-side columns; draw them above with a white box.
- Remove the dead empty-life-cycle branch (H = nan), which _format_H could
  not format anyway: every stream always has its utility stage.
- HeatExchangerNetwork.plot_pinch_diagram raises a clear RuntimeError when
  called before simulation instead of an AttributeError from
  _get_stream_life_cycles; regression test added.
- Close the figure in the docstring example; NumPy-style Returns section;
  comment documenting the gid contract used by the tests.

tests/test_hxn.py + hxn doctests: 20 passed; sugarcane network re-rendered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er circles

The unit/stream label sat lower than the bold index (both were anchored
with va='bottom' at different font sizes) and its white box could cover
exchanger and utility circles on the stream line. Put index and label on
a shared baseline (va='baseline') raised to yi + 0.25 so both sit in line
and clear of the circles.

tests/test_hxn.py + hxn doctests: 20 passed; sugarcane network re-rendered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With a shared baseline the smaller label still reads slightly low next to
the larger bold index; raise its baseline by 0.08 so the two appear
vertically centered on each other.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sarangbhagwat

sarangbhagwat commented Aug 23, 2026

Copy link
Copy Markdown
Member Author

Here's what the output of HXN.plot_pinch_diagram() looks like:

sugarcane_full_pinch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant