Flowsheet & recycle¶
The Flowsheet container and the recycle/tear solver. Flowsheet.partition
groups units into strongly connected blocks (Tarjan), orders them, and selects
tear streams automatically. The standalone tear_solve converges a loop by
Broyden (the default), Wegstein, or Newton and differentiates its fixed point.
Flowsheet assembles local unit derivatives into the shared process graph's
implicit system. Flowsheet.solve_with_info keeps every block's
report, each unit's heat and work, and, with a model, a physical audit of
every stream.
See the flowsheeting guide for worked examples.
flowsheet
¶
Process construction, execution, and checked implicit sensitivities.
A Flowsheet builds a shared ProcessGraph from physical unit kernels and named streams. Sequential execution orders acyclic units and converges cyclic partitions by tearing. Simultaneous execution solves the connection equations with locally assembled sparse Newton steps. Both strategies differentiate the same converged graph using local unit Jacobians and a sparse implicit solve.
Concrete process iterations run on the host, preserving compiled unit kernels. An explicit enclosing JIT stages the orchestration. Unit reports, independent stream checks, and optional physical audits remain acceptance requirements. The standalone tear_solve functions also support arbitrary floating pytrees; their small fixed-point derivative uses a dense reference system.
Classes:
| Name | Description |
|---|---|
Partition |
One block of the flowsheet's sequential-modular calculation order. |
TearResult |
Recycle state and the independently checked fixed-point residual. |
UnitRecord |
Energy and solve evidence a unit returned alongside its outlets. |
FlowsheetResult |
Named streams with per-block convergence, unit evidence, and physical audits. |
Flowsheet |
A declarative flowsheet: named streams produced by connected units. |
Functions:
| Name | Description |
|---|---|
tear_solve_with_info |
Solve a recycle, reporting its actual fixed-point residual. |
tear_solve |
Return a converged recycle, with implicit forward and reverse derivatives. |
Attributes:
| Name | Type | Description |
|---|---|---|
TEAR_METHODS |
tuple[str, ...]
|
Recycle convergence methods accepted by |
TEAR_METHODS
module-attribute
¶
Recycle convergence methods accepted by tear_solve and Flowsheet.solve.
Partition
dataclass
¶
One block of the flowsheet's sequential-modular calculation order.
Attributes:
| Name | Type | Description |
|---|---|---|
units |
tuple[str, ...]
|
Unit names in evaluation order (a valid order once |
tears |
tuple[str, ...]
|
Stream names torn to break this block's recycle loops (empty for an acyclic block). |
TearResult
¶
UnitRecord
dataclass
¶
UnitRecord(heat: Array, work: Array, report: SolveReport)
Energy and solve evidence a unit returned alongside its outlets.
Attributes:
| Name | Type | Description |
|---|---|---|
heat |
Array
|
Heat into the fluid (W); zero if the unit reports none. |
work |
Array
|
Shaft work into the fluid (W); zero if the unit reports none. |
report |
SolveReport
|
The unit's own solve report (a converged placeholder for units that return plain streams). |
FlowsheetResult
dataclass
¶
FlowsheetResult(
streams: dict[str, Stream],
reports: dict[str, SolveReport],
units: dict[str, UnitRecord] = dict(),
audits: dict[str, Any] = dict(),
unit_results: dict[str, Any] = dict(),
)
Named streams with per-block convergence, unit evidence, and physical audits.
Attributes:
| Name | Type | Description |
|---|---|---|
streams |
dict[str, Stream]
|
All feeds and computed streams. |
reports |
dict[str, SolveReport]
|
Solve reports keyed by |
units |
dict[str, UnitRecord]
|
Heat, work, and report retained from each unit's result. |
audits |
dict[str, Any]
|
Physical acceptance of each stream (empty without a model). |
unit_results |
dict[str, Any]
|
Complete unit pytrees when |
Methods:
| Name | Description |
|---|---|
check |
Raise with the responsible block's name if any calculation failed. |
converged
property
¶
Whether every recycle, unit, and stream-structure check succeeded.
accepted
property
¶
Converged, and every non-empty audited stream passed physical acceptance.
Without audits (no flowsheet model), this equals converged: no
physical acceptance is claimed that wasn't checked.
report
property
¶
report: SolveReport
The first failed block report, or the largest converged residual.
check
¶
Raise with the responsible block's name if any calculation failed.
Raises:
| Type | Description |
|---|---|
ConvergenceError
|
For a failed recycle, unit, or stream structure. |
PhysicalAcceptanceError
|
For an audited stream that failed physical acceptance (for example, a vapor-liquid state that is actually liquid-liquid unstable). |
Flowsheet
dataclass
¶
Flowsheet(
feeds: dict[str, Stream] = dict(),
units: list[ProcessUnit] = list(),
tears: dict[str, Stream] = dict(),
model: Any = None,
_maps: dict[Partition, Any] = dict(),
_graphs: dict[Any, CompiledGraph] = dict(),
)
A declarative flowsheet: named streams produced by connected units.
Build a flowsheet by registering feeds and units, then call solve. Each
unit is a plain function of its input streams (and the shared theta)
returning one or more output streams. The flowsheet works out the
calculation order itself: partition groups the units into strongly
connected blocks, orders the blocks, and chooses tear streams inside every
recycle loop. tear optionally designates a tear by hand together with its
starting guess; automatically selected tears are seeded by one pass of the
loop with an empty recycle.
Example::
fs = Flowsheet()
fs.feed("fresh", fresh_stream)
fs.unit("mixer", lambda fresh, rec, th: mix([fresh, rec]),
inputs=("fresh", "recycle"), outputs=("mixed",))
fs.unit("drum", lambda mixed, th: flash_drum(mixed, th["T"], th["P"]),
inputs=("mixed",), outputs=("vapor", "liquid"))
fs.unit("split", lambda liq, th: splitter(liq, [th["r"], 1 - th["r"]]),
inputs=("liquid",), outputs=("recycle", "purge"))
streams = fs.solve({"T": 320.0, "P": 2e6, "r": 0.6})
product = streams["vapor"]
Methods:
| Name | Description |
|---|---|
feed |
Register a fresh feed stream by name. Returns |
unit |
Register a unit |
tear |
Designate stream |
partition |
Validate connectivity and choose recycle partitions in the shared graph. |
solve_with_info |
Solve every block and retain reports, unit evidence, and stream audits. |
compile |
Reuse structural layouts without retaining a numerical solution. |
solve |
Return named streams after checked recycle convergence and acceptance. |
solve_path |
Continue between operating conditions with accepted recycle warm starts. |
Attributes:
| Name | Type | Description |
|---|---|---|
graph |
ProcessGraph
|
The canonical graph used by every execution strategy. |
feed
¶
Register a fresh feed stream by name. Returns self for chaining.
unit
¶
Register a unit fn(*input_streams, theta) -> output stream(s).
fn receives the named input streams positionally followed by the shared
theta pytree, and returns either a single Stream (for one
output name) or a tuple/list of streams aligned with outputs.
tear
¶
Designate stream name as a recycle tear with an initial guess.
Hand-designated tears are honoured by partition (and supplemented if
they do not break every loop). Designating a tear is also the way to
provide a starting guess for a stream whose component list differs from
the feeds', which the automatic seed cannot infer.
partition
¶
Validate connectivity and choose recycle partitions in the shared graph.
solve_with_info
¶
solve_with_info(
theta: Any = None,
*,
guess: Mapping[str, Stream] | None = None,
audit: bool = True,
strategy: str = "sequential",
linear_solver: str = "sparse",
retain_results: bool = False,
**tear_solve_kwargs: Any,
) -> FlowsheetResult
Solve every block and retain reports, unit evidence, and stream audits.
Failed blocks retain their best iterates for diagnosis. Use result.check()
at an application boundary, or inspect result.converged and
result.accepted inside JIT. With a flowsheet model and audit,
every stream is audited for physical acceptance after convergence.
strategy selects sequential or simultaneous on the same graph.
linear_solver="dense" explicitly selects the small reference solver;
the default sparse CPU solver never falls back to a dense plant matrix.
compile
¶
compile(
streams: Mapping[str, Stream],
*,
linear_solver: str = "sparse",
) -> CompiledGraph
Reuse structural layouts without retaining a numerical solution.
solve
¶
Return named streams after checked recycle convergence and acceptance.
Eager failures identify the responsible block, unit, or stream. Compiled
failures produce nonfinite streams; :meth:solve_with_info retains the
reports and best iterates for inspection. Set check=False to return
best iterates.
Raises:
| Type | Description |
|---|---|
ConvergenceError
|
If an eager block, unit, or stream check fails. |
PhysicalAcceptanceError
|
If an audited stream fails acceptance. |
solve_path
¶
solve_path(
start: Any,
target: Any,
*,
solve_options: Mapping[str, Any] | None = None,
**options: Any,
) -> Any
Continue between operating conditions with accepted recycle warm starts.
options are forwarded to :func:continuation_solve. Configure the
recycle method and tolerances through solve_options.
tear_solve_with_info
¶
tear_solve_with_info(
g: Callable[[Any, Any], Any],
tear0: Any,
theta: Any = None,
*,
method: str = "broyden",
q_min: float = -5.0,
q_max: float = 0.0,
tol: float = 1e-10,
atol: float = 1e-12,
max_iter: int = 200,
) -> TearResult
Solve a recycle, reporting its actual fixed-point residual.
g(tear, theta) and tear0 must have identical pytree structures.
Both JVPs and VJPs solve the linearized fixed-point equations independently
of the forward acceleration. An unconverged iterate has nonfinite
sensitivities. q_max may be raised toward one to damp oscillatory maps.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
g
|
Callable[[Any, Any], Any]
|
One flowsheet pass. |
required |
tear0
|
Any
|
Starting recycle state, as any floating-point pytree. |
required |
theta
|
Any
|
Differentiable operating parameters and upstream streams. |
None
|
method
|
str
|
Broyden (default), Wegstein, or Newton. |
'broyden'
|
q_min
|
float
|
Minimum Wegstein acceleration factor. |
-5.0
|
q_max
|
float
|
Maximum Wegstein acceleration factor. |
0.0
|
tol
|
float
|
Scaled fixed-point residual tolerance. |
1e-10
|
atol
|
float
|
Absolute floor added to the per-variable scaling. |
1e-12
|
max_iter
|
int
|
Forward iteration cap. |
200
|
Returns:
| Type | Description |
|---|---|
TearResult
|
The best recycle state and its solve report. |
Raises:
| Type | Description |
|---|---|
ValueError
|
For an unknown method or invalid numerical options. |
tear_solve
¶
tear_solve(
g: Callable[[Any, Any], Any],
tear0: Any,
theta: Any = None,
*,
method: str = "broyden",
q_min: float = -5.0,
q_max: float = 0.0,
tol: float = 1e-10,
atol: float = 1e-12,
max_iter: int = 200,
check: bool = True,
) -> Any
Return a converged recycle, with implicit forward and reverse derivatives.
Eager calls raise on failure by default. Under JAX transforms the failed
value is nonfinite; use :func:tear_solve_with_info to retain diagnostics
and the best iterate. check=False also returns that best iterate.
Raises:
| Type | Description |
|---|---|
ConvergenceError
|
If an eager calculation does not converge. |
ValueError
|
If numerical options are invalid. |
Process graph¶
See the shared runtime guide.
graph
¶
Canonical process connectivity and independently linearized unit equations.
A graph contains unit kernels and their ports. Both sequential and simultaneous execution use this graph. Its implicit derivative assembles local unit blocks; it never differentiates a complete recycle iteration or a nested plant solve.
Classes:
| Name | Description |
|---|---|
ProcessUnit |
One physical kernel and its named material ports. |
Partition |
One block of the flowsheet's sequential-modular calculation order. |
ProcessGraph |
Immutable connectivity shared by process construction and execution. |
CompiledGraph |
Fixed process layout with local residual and derivative assembly. |
SpecifiedGraph |
Connection equations with a small border of bounded design variables. |
ResidualGraph |
Locally assembled equations for custom residual units and specifications. |
Functions:
| Name | Description |
|---|---|
connection_edges |
Validate unbound port declarations using the canonical graph contract. |
stream_vector |
Pack material, temperature, pressure, and resolved vapor inventory. |
vector_stream |
Reconstruct a stream from its material and phase coordinates. |
unit_outlets |
Read the shared unit-result contract, including plain stream functions. |
ProcessUnit
dataclass
¶
ProcessUnit(
name: str,
fn: Callable[..., Any],
inputs: tuple[str, ...],
outputs: tuple[str, ...],
)
One physical kernel and its named material ports.
Partition
dataclass
¶
One block of the flowsheet's sequential-modular calculation order.
Attributes:
| Name | Type | Description |
|---|---|---|
units |
tuple[str, ...]
|
Unit names in evaluation order (a valid order once |
tears |
tuple[str, ...]
|
Stream names torn to break this block's recycle loops (empty for an acyclic block). |
ProcessGraph
dataclass
¶
ProcessGraph(
feeds: tuple[str, ...],
units: tuple[ProcessUnit, ...],
preferred_tears: tuple[str, ...] = (),
)
Immutable connectivity shared by process construction and execution.
Kernels receive input streams followed by a dynamic parameter pytree. Numerical values and property-package coefficients aren't graph metadata.
Methods:
| Name | Description |
|---|---|
edges |
Validate ownership and return declared stream dependencies. |
partition |
Order strongly connected components and select their tear streams. |
compile |
Bind component layouts without capturing operating values. |
partition
¶
Order strongly connected components and select their tear streams.
CompiledGraph
¶
CompiledGraph(
graph: ProcessGraph,
streams: Mapping[str, Stream],
*,
linear_solver: str = "sparse",
)
Fixed process layout with local residual and derivative assembly.
Each unit linearizes once at the current point. Input-coordinate directions are applied sequentially so a column's stage tangent isn't batched by the number of plant variables. Sparse storage follows the declared connections. Instances contain structure only and never cache numerical operating points.
Methods:
| Name | Description |
|---|---|
indices |
Scalar coordinates of a sequence of named internal streams. |
pack |
Collect internal stream coordinates in deterministic unit order. |
unpack |
Restore all streams, retaining the supplied differentiable feeds. |
residual |
The connection equations, evaluated through independent unit kernels. |
local_linearization |
Assemble unit Jacobians and a parameter direction from shared local data. |
linearize |
Select sparse or explicit dense algebra over the same local assembly. |
attach |
Attach assembled implicit derivatives to a detached converged state. |
diagnose |
Describe actual storage, scalar labels, and graph incidence. |
indices
¶
Scalar coordinates of a sequence of named internal streams.
pack
¶
Collect internal stream coordinates in deterministic unit order.
unpack
¶
Restore all streams, retaining the supplied differentiable feeds.
residual
¶
residual(value: Array, parameters: Any) -> Array
The connection equations, evaluated through independent unit kernels.
local_linearization
¶
Assemble unit Jacobians and a parameter direction from shared local data.
linearize
¶
Select sparse or explicit dense algebra over the same local assembly.
SpecifiedGraph
¶
SpecifiedGraph(
graph: CompiledGraph,
count: int,
bind: Callable[..., Any],
equations: Callable[..., Array],
)
Connection equations with a small border of bounded design variables.
bind(aux, parameters) supplies the graph's dynamic parameters and feeds.
equations(streams, bound) evaluates specification residuals at that
state. It mustn't solve the process. Unit physics remains in the graph;
design metrics add rows and manipulated variables add columns.
Methods:
| Name | Description |
|---|---|
specification_residual |
Evaluate the border equations using the current connection state. |
residual |
Evaluate one square process and design-specification system. |
linearize |
Assemble local unit blocks and the small specification border. |
diagnose |
Describe the augmented state, including every specification border. |
attach |
Attach the full bounded specification system's implicit derivative. |
specification_residual
¶
specification_residual(
value: Array, parameters: Any
) -> Array
Evaluate the border equations using the current connection state.
residual
¶
residual(value: Array, parameters: Any) -> Array
Evaluate one square process and design-specification system.
linearize
¶
Assemble local unit blocks and the small specification border.
diagnose
¶
Describe the augmented state, including every specification border.
ResidualGraph
¶
ResidualGraph(
size: int,
blocks: Sequence[
tuple[tuple[int, ...], int, Callable[..., Array]]
],
*,
linear_solver: str = "sparse",
)
Locally assembled equations for custom residual units and specifications.
Each block receives only its declared scalar inputs and the dynamic process parameters. Incidence is a structural contract, never inferred from values. Compiled unit boundaries are retained in primal and derivative evaluation.
Methods:
| Name | Description |
|---|---|
residual |
Evaluate independently compiled residual blocks. |
linearize |
Assemble sparse local Jacobians and one parameter right-hand side. |
attach |
Attach implicit derivatives assembled from the same residual blocks. |
connection_edges
¶
connection_edges(
feeds: Sequence[str],
connections: Sequence[
tuple[str, tuple[str, ...], tuple[str, ...]]
],
preferred_tears: Sequence[str] = (),
) -> list[Edge]
Validate unbound port declarations using the canonical graph contract.
stream_vector
¶
stream_vector(stream: Stream) -> Array
Pack material, temperature, pressure, and resolved vapor inventory.
Keeping both inventories preserves pure-fluid saturation quality without embedding an extra PH flash in every process connection.
vector_stream
¶
Reconstruct a stream from its material and phase coordinates.
numerics
¶
Process-level sparse algebra and orchestration with bounded compilation.
Unit physics stays in compiled JAX kernels. Concrete process iterations run on
the host; an enclosing JIT stages the same iteration with lax.while_loop.
Sparse LU runs on the CPU and differentiates its matrix equation, including
transpose solves and higher derivatives. No process-sized dense fallback is
allocated when a sparse factorization fails.
Classes:
| Name | Description |
|---|---|
SparseJacobian |
A square coordinate matrix with static incidence and dynamic coefficients. |
Functions:
| Name | Description |
|---|---|
clear_factor_cache |
Release retained sparse LU factors without clearing compiled unit kernels. |
newton_iterations |
Converge scaled equations without compiling the complete process. |
SparseJacobian
dataclass
¶
A square coordinate matrix with static incidence and dynamic coefficients.
Duplicate entries are summed. Storage is proportional to declared nonzeros.
to_dense is an explicit diagnostic/reference operation only. The CPU
solve transfers coefficients when used on an accelerator; it never claims
an accelerator-native sparse factorization.
Methods:
| Name | Description |
|---|---|
matvec |
Apply the matrix to a vector or a batch of right-hand sides. |
transpose |
Return the transpose without moving or duplicating coefficient storage. |
scaled |
Apply equation and variable scaling without densifying. |
to_dense |
Materialize an explicitly requested small reference matrix. |
solve_with_info |
Solve and check each RHS against the original matrix equation. |
solve |
Return a checked implicit solve, with NaN on failure. |
matvec
¶
Apply the matrix to a vector or a batch of right-hand sides.
transpose
¶
transpose() -> SparseJacobian
Return the transpose without moving or duplicating coefficient storage.
scaled
¶
scaled(rows: Array, columns: Array) -> SparseJacobian
Apply equation and variable scaling without densifying.
solve_with_info
¶
solve_with_info(rhs: Array) -> LinearResult
Solve and check each RHS against the original matrix equation.
LU pivots carry no derivative. JAX differentiates A x = b and uses
the explicit transpose solve for adjoints. A singular or inaccurate
result is nonfinite and retains independent linear residual evidence.
clear_factor_cache
¶
Release retained sparse LU factors without clearing compiled unit kernels.
newton_iterations
¶
newton_iterations(
residual: Callable[[Array, Any], Array],
jacobian: Callable[[Array, Any], Any],
start: Array,
parameters: Any,
*,
scale: Array | None = None,
tolerance: float = 1e-09,
max_iterations: int = 100,
lower: Array | None = None,
upper: Array | None = None,
) -> SolveResult
Converge scaled equations without compiling the complete process.
jacobian supplies a structured matrix. Bounds restrict trial points;
residual-decreasing backtracking retains the best state on failure. This
primal routine attaches no derivative to its iteration history.
Continuation¶
Adaptive continuation reuses accepted states while moving between operating conditions. See the reliability guide for examples and the distinction between a converged endpoint and a partially completed path.
continuation
¶
Adaptive operating-condition continuation with accepted-state warm starts.
Continuation is a host orchestration algorithm. Each point can use a compiled, implicitly differentiable solver. Failed trials never replace the accepted seed.
Classes:
| Name | Description |
|---|---|
ContinuationStep |
One attempted point on the path, including rejected trials. |
ContinuationResult |
Last accepted solution and the complete attempted path. |
Functions:
| Name | Description |
|---|---|
continuation_solve |
Move between parameter pytrees, reducing the step after a failed solve. |
ContinuationStep
¶
ContinuationResult
dataclass
¶
ContinuationResult(
value: Any,
progress: float,
report: SolveReport,
steps: tuple[ContinuationStep, ...],
)
Last accepted solution and the complete attempted path.
progress == 1 and a converged report are both required for success.
A failed path's value belongs to its last accepted operating point.
Methods:
| Name | Description |
|---|---|
check |
Raise if the requested endpoint wasn't reached. |
continuation_solve
¶
continuation_solve(
solve: Callable[[Any, Any], tuple[Any, SolveReport]],
start: Any,
target: Any,
*,
guess: Any = None,
initial_step: float = 0.25,
min_step: float = 0.0001,
max_steps: int = 100,
check: bool = True,
) -> ContinuationResult
Move between parameter pytrees, reducing the step after a failed solve.
solve(parameters, previous_value) returns a value and its report. It
should return failed reports or raise ConvergenceError for numerical
failures. Other exceptions propagate, since programming errors aren't
convergence failures. The final solve supplies the endpoint's own implicit
derivative; no derivative is defined through the adaptive path selection.
Raises:
| Type | Description |
|---|---|
ValueError
|
If path controls or parameter tree structures are invalid. |
ConvergenceError
|
If |