Flowsheet & recycle¶
The recycle/tear solver and the Flowsheet container. Recycle loops are
converged to a fixed point and differentiated by the implicit function theorem,
so gradients flow through the converged flowsheet rather than the iteration.
flowsheet
¶
Sequential-modular flowsheet solving with a differentiable recycle/tear solver.
A flowsheet with a recycle is an implicit problem: the value of a torn recycle
stream must equal what the flowsheet computes for it once that guess is fed back
in. Writing the single forward pass as g(tear, theta) -> tear (mix the feed
with the recycle guess, run the units, return the recomputed recycle), the
converged flowsheet is the fixed point tear* = g(tear*, theta).
tear_solve finds that fixed point with a Wegstein-accelerated
iteration (the workhorse of sequential-modular simulators, far more robust than
plain direct substitution on tight recycles) and differentiates the converged
solution by the implicit function theorem (a hand-written custom_vjp). The
forward iteration count never appears in the backward pass, so a gradient of any
product spec with respect to an operating variable costs one adjoint solve, no
matter how many recycle iterations were needed. That is what makes whole-process,
recycle-closed gradient optimisation tractable.
The tear state can be any JAX pytree (a Stream, a
list of them, a dict, ...); it is flattened internally. Convergence is judged on
a relative norm, so mixed-scale states (flows, temperature, pressure) all
converge to the same relative tolerance without manual scaling.
Flowsheet is a thin declarative wrapper: register feeds and unit
functions, mark a tear, and call Flowsheet.solve.
Classes:
| Name | Description |
|---|---|
Flowsheet |
A small declarative flowsheet: named streams produced by connected units. |
Functions:
| Name | Description |
|---|---|
tear_solve |
Converge a recycle by solving the tear fixed point |
Flowsheet
dataclass
¶
Flowsheet(
feeds: dict[str, Stream] = dict(),
units: list[_Unit] = list(),
tears: dict[str, Stream] = dict(),
)
A small declarative flowsheet: named streams produced by connected units.
Build a flowsheet by registering feeds and units, then designate a recycle
tear and call solve. Each unit is a plain function of its input
streams (and the shared theta) returning one or more output streams; the
flowsheet evaluates the units in registration order, which the caller arranges
to be a valid sequential-modular order with the recycle torn.
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"))
fs.tear("recycle", recycle_guess)
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 |
solve |
Solve the flowsheet (closing any recycle) and return all named streams. |
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.
solve
¶
Solve the flowsheet (closing any recycle) and return all named streams.
theta is the differentiable parameter pytree passed to every unit; any
output stream is differentiable with respect to it. Extra keyword arguments
are forwarded to tear_solve.
tear_solve
¶
tear_solve(
g: Callable[[Any, Any], Any],
tear0: Any,
theta: Any = None,
*,
q_min: float = -5.0,
q_max: float = 0.0,
tol: float = 1e-10,
atol: float = 1e-12,
max_iter: int = 200,
) -> Any
Converge a recycle by solving the tear fixed point tear = g(tear, theta).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
g
|
Callable[[Any, Any], Any]
|
One sequential-modular pass of the flowsheet. Given a tear-stream guess
(any pytree) and the parameter pytree |
required |
tear0
|
Any
|
Initial guess for the torn stream(s). |
required |
theta
|
Any
|
Differentiable parameter pytree (operating conditions, specs, feed).
Pass the quantities you want to differentiate through here; gradients
flow to |
None
|
q_min
|
float
|
Lower bound on the Wegstein acceleration factor. |
-5.0
|
q_max
|
float
|
Upper bound on the Wegstein acceleration factor. The default
|
0.0
|
tol
|
float
|
Relative tolerance for the convergence norm. |
1e-10
|
atol
|
float
|
Absolute floor for the convergence norm. |
1e-12
|
max_iter
|
int
|
Iteration cap for both the forward and adjoint solves. |
200
|
Returns:
| Type | Description |
|---|---|
Any
|
The converged tear stream(s), in the structure of |
Any
|
with respect to |