Contributing to hensmith#
Bug reports, regression cases, and pull requests are welcome. This page describes how the repository is laid out, how to set up an environment that matches continuous integration, how the tests work, and the conventions a change has to respect.
Where the code lives#
The library is two public modules under hensmith/, and two private ones
behind them:
hensmith/_heat_exchanger_network.pyThe
HeatExchangerNetworkfacility unit: its_run,_designand_costmethods, and its integration with a BioSTEAM system’s heat utilities.hensmith/hxn_synthesis.pyThe problem table and pinch analysis (
problem_table,ProblemTable), network synthesis (synthesize_network: realizing the planner’s network as BioSTEAM exchangers and verifying it on exact stream states), the per-stream bookkeeping ofStreamLifeCycle, andplot_pinch_diagram.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 the synthesis work.
hensmith/_planner.py(private)The pinch-outward planner of unsplit networks at minimum energy requirement, on numbers only (numpy; no BioSTEAM objects). Its module docstring documents the model, the lemmas the search relies on and its guarantees.
hensmith/__init__.py re-exports the __all__ of the two public modules
and holds the biosteam registration block described in The import
contract below. The public API is documented under API Reference.
Tests live in tests/:
tests/test_hxn.pyBehavior of the
HeatExchangerNetworkunit on small, hand-built systems.tests/test_hxn_targets.pyThe stream curves, the problem table built on them, the pinch cut and the exact internal-approach check, against an independent dense-grid calculator of the targets.
tests/test_hxn_planner.pyThe planner on numbers only, against helpers written in the test module: a constant heat capacity problem table, the pinch design rules, a network walk and direct feasibility checks.
tests/test_hxn_mer.py(data intests/hxn_mer_cases.py)78 problems synthesized through the public facility: 40 for which an unsplit MER network provably exists, where the network must reach the targets, and 38 that provably need stream splits, where it must never beat them; the targets are checked against independent references, and every balance and the exact internal approach of every exchanger are checked in both sets.
tests/test_hxn_regression.pyTen synthetic systems of increasing complexity. For each, the synthesized network must close its energy balance, must not beat the minimum-energy requirement targets of the problem table computed on the same streams (and must report
'mer'exactly when it reaches them), must keep the minimum approach temperature inside every exchanger, and must recover at least as much heat as the utility loads documented in the file.
Development environment#
hensmith requires Python 3.12 or newer; continuous integration runs 3.12 and 3.13. Its only runtime dependency is BioSTEAM.
Because hensmith tracks the development version of the BioSTEAM stack, install that stack from GitHub HEAD exactly as CI does, from a checkout of this repository:
pip install --upgrade pip
pip install wheel pytest
pip install --no-cache-dir git+https://github.com/CalebBell/fluids.git
pip install --no-cache-dir git+https://github.com/CalebBell/chemicals.git
pip install --no-cache-dir git+https://github.com/CalebBell/thermo.git
pip install --no-cache-dir git+https://github.com/BioSTEAMDevelopmentGroup/thermosteam.git
pip install --no-cache-dir git+https://github.com/BioSTEAMDevelopmentGroup/biosteam.git
pip uninstall hensmith -y
pip install --no-deps -e .
Two steps in that sequence are deliberate.
pip uninstall hensmith -yInstalling biosteam pulls in a released hensmith as a dependency. It is removed before the checkout is installed in editable mode, so that
import hensmithresolves to the working tree and not to a stale copy insite-packages.pip install --no-deps -e .setup.pypinsbiosteam>=2.54.0: 2.54.0 is the first biosteam release without the bundledbiosteam.facilities.hxncopy of this package. An older biosteam would coexist with hensmith as two distinctHeatExchangerNetworkclasses, with the bundled copy winningbst.HeatExchangerNetworkand silently breakingisinstancechecks downstream. The pin is circular by design – biosteam depends on hensmith and hensmith pins biosteam – so letting pip resolve it here would fail whenever this repository’s floor is ahead of the biosteam release on PyPI.--no-depsskips that resolution; the stack installed above already satisfies the requirement.
Running the tests#
Run the suite from the repository root:
pytest . --disable-numba=1
CI runs pytest . -v --disable-numba=1 -p no:cacheprovider. The suite is
expected to be fully green: exit status 0, no failures and no errors.
--disable-numba is defined in conftest.py and defaults to 1; it
sets NUMBA_DISABLE_JIT, so the tests run interpreted instead of paying
for (and caching) numba compilation. conftest.py also sets
DISABLE_PREFERENCES (so that a run does not depend on locally saved
BioSTEAM preferences), FILTER_WARNINGS and PY_IGNORE_IMPORTMISMATCH.
Doctests are tests. pytest.ini adds --doctest-modules, so every
Examples block in every module under hensmith/ is executed and its
output compared. The option flags are NORMALIZE_WHITESPACE,
IGNORE_EXCEPTION_DETAIL, NUMBER and ELLIPSIS; NUMBER compares
floating-point results only to the precision written in the expected output,
so a docstring that prints fewer digits tolerates more drift.
A change to a synthesis or design algorithm legitimately changes the numbers a doctest prints. Regenerate those expected outputs by running the example and pasting its actual output – never hand-tune digits until a comparison passes, which turns a real regression into a green test. Say in the commit body which behavior change moved the numbers.
The regression suite states the same rule in stronger form. The documented
utility loads in tests/test_hxn_regression.py were recorded by running
that file directly (it prints them), and an improvement to the synthesizer
simply leaves slack against them. A maintainer lowers a documented load
deliberately when a better network is intended; the module docstring is
explicit about the other direction:
Never raise them to make a failing test pass.
Raising a documented load converts a synthesizer regression into a passing test, which is exactly what the case list exists to prevent.
The import contract#
biosteam and hensmith refer to each other on purpose: bst.HeatExchangerNetwork
works out of the box, and hensmith is still a separately released package.
The cycle is import-safe in both import orders only because of the following
invariants. Breaking one makes the import fail in a way that depends on which
package the user imported first, so treat them as part of the code:
Never star-import biosteam (
from biosteam import *) at module scope, anywhere in this package or in any module imported while hensmith is initializing. biosteam listsHeatExchangerNetworkin its__all__as a re-export from hensmith, and the name is bound into biosteam only at the very end ofhensmith/__init__.py. A star-import running before that point resolves the name against a half-initialized module and fails. Plainimport biosteam as bstis safe, and is what the modules use.The registration block stays last. biosteam imports hensmith at the very end of its own
__init__, and hensmith’s__init__setsbst.HeatExchangerNetworkandbst.facilities.HeatExchangerNetworkas its final statement. Binding last is what makes either order work: when hensmith is imported first, biosteam’s__init__runs while hensmith is still initializing and the class does not exist yet, so the name can only be bound once hensmith’s own definitions are complete.The name stays out of
biosteam.facilities.__all__. biosteam star-imports itsfacilitiessubpackage before hensmith’s registration block can run; listing the name there would resolve it eagerly and re-create the cycle.
biosteam’s own test suite guards the star-import invariant, so breaking it also fails CI in that repository.
Conventions#
Branches. One branch per fix or feature, branched from master and
named in lowercase kebab-case (fix-<what>, <feature>-<noun>). Do not
commit to master directly, and start a new branch for an unrelated fix
rather than piling it onto the current one.
Commit messages. A lowercase, imperative, one-line summary, then a body explaining the underlying cause, the reasoning, and how the change was validated – not a restatement of the diff.
Copyright header. Every module starts with the hensmith header:
# -*- coding: utf-8 -*-
# hensmith: Heat Exchanger Network Synthesis, Modeling, Integration,
# Thermodynamics, and Heuristics
# Copyright (C) 2020-, Sarang Bhagwat <sarangbhagwat.developer@gmail.com>
#
# This module is under the UIUC open-source license. See
# github.com/BioSTEAMDevelopmentGroup/hensmith/blob/master/LICENSE.txt
# for license details.
New files use it with the current year and the contributing author. Do not reorder the authors already listed in a file; append a new one at the end.
Docstrings. NumPy style, with the usual Parameters, Returns,
Notes, Examples and References sections, and a runnable doctest
under Examples for anything public. Cite the pinch-analysis or network
synthesis methodology under References.
Exports. Public names are __all__-driven: add a new public class or
function to its module’s __all__, which hensmith/__init__.py unions.
Public API and defaults. The signature of HeatExchangerNetwork and
the public functions of hxn_synthesis are used by downstream biorefinery
models, so prefer additive, backward-compatible changes. Default design and
synthesis parameters – the minimum approach temperature, the cost and area
correlations, the constructor defaults – are part of that contract:
changing one shifts every downstream techno-economic analysis, so propose
such a change on its own rather than folding it into an unrelated fix.
Style. Match the surrounding module: four-space indentation, no trailing
whitespace, private helpers prefixed with _, and line lengths in keeping
with the file being edited. Because the oldest Python CI runs is 3.12, avoid syntax and
standard-library features newer than 3.12.
Fix causes, not symptoms. A clamp, a broad except, or a retry loop
that makes a traceback disappear is a fix only when the analysis shows it is
the correct behavior. Pinch analysis has enough structure – monotone
cascades, piecewise-linear composite curves, energy balances – that a
closed-form or bracketed-monotone solution often exists where an iterative
one is in place; it is worth looking for one before patching the iteration,
and worth writing the derivation down when it replaces numerics.
Documentation#
The documentation is Sphinx sources under docs/source. Build it from the
repository root:
pip install -r docs/requirements.txt
sphinx-build -W -b html docs/source docs/build/html
-W turns warnings into errors, matching the Read the Docs configuration:
a page that builds cleanly locally builds cleanly there.
Figures, animations, and captured program output are not written by hand.
They are produced by the authoring scripts in docs/_demo_src and
regenerated with a single command:
python docs/_demo_src/build_all.py
which runs each script in turn, one process at a time. It needs Graphviz’s
dot on the local PATH for the flowsheet diagrams, so it is a local
step only: Read the Docs never runs it, and the generated assets are
committed to the repository.
To add or change a tutorial figure or an example output, edit the
corresponding chapter script under docs/_demo_src/examples/, run
build_all.py, and commit the regenerated files together with the page
that uses them. The tutorial pages include code
from those scripts region by region instead of pasting it, so the code on the
page is code that was actually executed. Likewise, every number quoted in
prose or in a caption is copied from a capture file under
docs/source/_generated – never typed from memory or carried over from an
earlier run.
Reporting issues#
Report bugs and feature requests as GitHub issues on the repository: BioSTEAMDevelopmentGroup/hensmith
The most useful report contains a minimal system that reproduces the
problem – the smallest set of streams and units that still shows it –
together with the full traceback, or the incorrect numbers alongside the
expected ones, and the versions of hensmith, biosteam and thermosteam in
use. A reproducer of that size can usually be turned into a test in
tests/ directly, which is the fastest route from a report to a fix.