Skip to content

Process: BFD, PFD and streams

The block flow diagram (BFD) and process flow diagram (PFD) are generated, never drawn by hand. Their source is two YAML files, schema cheme/process/v1:

  • process/materials.yaml (kind process-materials): components (id, name, formula, CAS, molar mass, optional InChIKey and SMILES, checked by cheme validate), templates (ordered component lists) and the property method;
  • process/flowsheet.yaml (kind process-flowsheet): units (every value without its own unit is in these; pressure absolute, gauge only as an explicit unit, Nm3/h only with normal_conditions), cases, the balance tolerance, feeds and products (DEXPI Source and Sink), blocks and steps with explicit ports (in/out, material/energy/information), streams from port to port with a per-case state (pressure, temperature, flow, duty, composition, phase), links to the lists and sheets, and layout.bfd / layout.pfd pins. Each value carries provenance: given | assumed | calculated and, when given, a source. A value or composition may give an uncertainty (the LCA's shape, e.g. {gsd2: 1.2} or {distribution: triangular, low: 0.9, high: 1.1}); cheme assess lca samples a feed's or product's flow with it, and no calculated value's fingerprint includes it. An optional origin says what kind of figure it is: given measured, vendor or literature; assumed design-rule, estimate or literature; calculated simulated (cheme process simulate --fill writes it) or correlation (cheme process props --fill, cheme datasheets fill). Another pairing is cheme.provenance-origin (warning). A DEXPI sheet maps measured to OBSERVED, and lca.yaml's uncertainty.defaults may be keyed by an origin (measured: {gsd2: 1.02}), which wins over the provenance's.

plant.yaml declares the sheets by kind: {id: BFD, kind: bfd, file: process/BFD.xml} and {id: PFD, kind: pfd, file: process/PFD.xml}. A kind: validate gate with generated: [pfd] (the template's process-basic) fails until the PFD is declared and built.

plant.yaml's requires: cheme-as-code>=X.Y (written by cheme init) names the oldest cheme-as-code the repository works with; an older one stops every command, the pre-commit hook included, with cheme.version-required and an upgrade hint before any schema error.

  • cheme process build [SHEET...] [--force] writes DEXPI 2.0.1 ProcessModels with a drawing. The BFD shows blocks, feeds and products and every stream that crosses a block boundary; the PFD nests the steps in their blocks, with block ports delegating to step ports (superReference), and adds the internal streams. Values become QualifiedValues with their provenance and case. The layout is layered left to right with recycles routed back, labels placed clear of lines and nodes, and the layout.<view> pins applied. A rebuild is byte-identical; a title rename moves nothing.
  • The PFD drawing (ADR 0046). The sheet is the smallest that holds the drawing and the stream table: ISO 216 A3 to A0 or an ISO 5457 elongated one (A3x3 to A0x3), landscape, with the frame, zone markers and a title block (title, project, drawing number, case, sheet). A layout wider than an A0 wraps into rows, read like text. Steps are drawn with the ISO symbol of their class (a step's optional symbol: overrides it: a symbol name, junction or box; an unknown name is cheme.process-symbol, warning); mixing and splitting steps are junction tees. A utility terminal (category: utility, a utility: given, or only energy ports) is not a box with a line to every user: each of its streams is a short stub on top of the step it serves, with the utility's id. Process lines are drawn heavier than equipment, with flow arrowheads; blocks holding steps are light dash-dot process areas. Below the drawing the stream table lists each material stream's phase, temperature (°C), pressure (bar), mass flow and the flows of the largest components, and each energy stream's duty, for the default case (assumed values marked *, stale ones blank). The BFD gets the same frame and title block.
  • Calculated values and phase in the sheets. Each material stream's MaterialState (its MaterialStateType) carries the state.calculated values DEXPI 2.0.1 has a property for: density (kg/m3) and specificEnthalpy, one QualifiedValue per case with the YAML provenance (calculated: CALCULATED) and source. DEXPI binds specificEnthalpy to mass units, so kJ/kmol is divided by the case's molar mass (kJ/kg; the display text keeps the kJ/kmol value, the description names the molar mass and the enthalpy reference); without a molar mass the enthalpy is not a QualifiedValue. The phase is the material state's phases: one MaterialStateType per phase present (identifier vapor or liquid, two-phase gives both), its description <case>: <phase>, vapour fraction <x>; <provenance>; source: .... DEXPI has no property for the molar mass (or any other calculated value): those are lines calculated molar_mass (<case>) = 28.96 kg/kmol; source: ... in the state type's description; no class or attribute is invented. Stale values are not exported: a value or phase reported by cheme.process-calculated-stale is left out of the sheet, and the description says stale, not exported (<case>): density, phase, so a DEXPI consumer never reads a value its inputs no longer give. Because staleness follows from the YAML, the sheet is generated-outdated until rebuilt; recalculate (props --fill) and rebuild to export again.
  • BFD aggregation. bfd: {aggregate: true} in the flowsheet draws parallel boundary streams between the same two BFD nodes (same kind, same direction) as one connection; aggregate: [[A, B], ...] limits it to those pairs (cheme.process-aggregate if one is not on the BFD). The connection keeps its first stream's id and ports, its label and description list every stream (represents: S2 S3), and it carries no values; the PFD, stream table and balances are unchanged (examples/process-aggregate/).
  • Refinement keeps stream ids. When a block gets steps, a boundary stream keeps its id and moves its end from BLOCK.port to STEP.port, whose port delegates the block port. Balances count it once; the BFD and its balances are unchanged by a refinement (examples/process-blocks/ before, examples/process-recycle/ after).
  • Nested blocks. A block with parent: P is drawn inside P on the PFD (with its own steps and nested blocks) and is a sub process step of P; the BFD shows top-level blocks. Its ports delegate P's ports where a stream crosses P's boundary, just like step ports (examples/process-nested/). Inside a block, ports are ordered by where their lines go and the step order with the fewest crossings wins.
  • Generated-sheet provenance. Each sheet records a fingerprint of its YAML source and of its own XML. validate reports cheme.generated-outdated (warning: the YAML changed, rebuild), cheme.generated-edited (error: the XML was edited by hand; build refuses it unless --force) and cheme.generated-version (warning: cheme-as-code's generator version, generator code (generator_sha256, a hash of the chemeascode.domains.process sources) or schema version changed). A YAML edit never reports generated-edited.
  • Balances. Steady-state, non-reactive mass and component balances per block and per step, on its boundary streams (internal streams cancel), with |in - out| <= abs + rel * max(in, out), in the default case and every other case that streams give a state for. cheme.process-balance (error) names the block, the case and both sums; process-balance-not-evaluable (info, default case only: in another case a balance with missing values is not reported) names the missing values; process-component-balance-not-evaluable (info) a mass balance that closes while the component balance lacks compositions (accepted by the concept gate, not by process-basic).
  • Reactions live in process/reactions.yaml (schema cheme/reactions/v1, cheme schema process-reactions): stoichiometry over the component ids (reactants negative), an optional conversion of a key component, heat of reaction, catalyst and sources. validate checks each reaction's element balance from the components' formula (cheme.reaction-*). A step or block naming reactions: [ID, ...] (optionally conversion: {ID: fraction}), or a block with balance_scope: reactive, balances mass and elements instead of components, and the component changes must be explained by extents of its reactions (least squares; process streams prints the extents). A block holding a reactor step is reactive too. reactor: equilibrium takes the named reactions to equilibrium at the outlet's temperature (plus approach) and pressure, reactor: gibbs minimises the Gibbs energy over every component of the streams (both ideal gas, in process propagate), with ln K from each component's ideal_gas data (formation enthalpy, entropy, Cp/R polynomial) or a reaction's equilibrium_constant. With fugacity: peng_robinson the equilibrium is that of a real gas (Peng-Robinson fugacity coefficients from the components' critical: constants; domains/chemistry/eos.py). examples/process-ammonia is an ammonia synthesis loop with a real-gas equilibrium converter, purge and recycle.
  • Fixed beds (reactor: pfr with catalyst: and bed:): process propagate integrates the reactions' rate laws along the catalyst mass (1D plug flow, adiabatic, isothermal or cooled, Ergun pressure drop; domains/process/fixed_bed.py) and writes the outlet's composition, flow, temperature and pressure; process reactors adds the bed (catalyst mass and volume, pressure drop, hot spot, profile). datasheets fill sizes a reactor's datasheet from its bed (or the catalyst's space velocity) and assess equipment adds its first catalyst fill at the catalyst's price. examples/process-shift is an adiabatic high-temperature shift bed.
  • cheme process reactors [--case C] [--json] reports per reactor its model, the extents, each reaction's heat and ln K at the outlet, ln(Q/K) (0 at equilibrium) and the duty H(out) - H(in) on the elements basis, ideal gas.
  • Catalysts live in process/catalysts.yaml (schema cheme/catalysts/v1): active phase, promoters, support, particle and bed data, the temperature and pressure window, space velocity, poisons, deactivation and price, each with provenance. A reactor names its catalyst:; validate warns when its streams leave the window or a feed carries a poison. Reactions take a rate: (power law or LHHW, per kg of catalyst or per m3, optional (1 - Q/K)), which process reactors evaluates at the outlet. A reaction's catalyst:, its rate.catalyst and its mechanism.catalyst name a catalyst by id; validate warns on one the file does not declare (cheme.catalyst-unknown).
  • Reactive hazard screening: cheme process reactor-hazards [--json] gives each reactor's adiabatic rise, MTSR, MTT and, with a TD24 from calorimetry (hazard: on the reactor), Stoessel's criticality class; a cooled bed's hot-spot sensitivity (domains/safety/reaction_hazards.py, findings cheme.reactor-hazard-*).
  • Reactor choice: cheme process reactor-choice NODE --product P [--key K] [--tanks N] [--svg FILE] [--json | --csv] compares an ideal PFR, one CSTR and N CSTRs in series for a step's reactions: yield and selectivity of P against the key's conversion over a range of space times, the largest yield of each, and the PFR's instantaneous selectivity (domains/process/reactor_choice.py; the SVG in light and dark); --region-svg FILE adds the candidate attainable region (domains/process/attainable_region.py).
  • Reactor uncertainty: cheme process reactor-uncertainty NODE [--samples 200] [--seed 1] [--case C] [--json] samples the uncertainty of a step's reaction data (rate.uncertainty of A and Ea, and that of heat_of_reaction, equilibrium_constant and conversion) by Monte Carlo through the step's own reactor model (a fixed bed, a stirred tank or batch, a conversion reactor; equilibrium and Gibbs reactors are not sampled) and prints the conversion of the key (the first fed reactant), the outlet and the highest temperature as declared, with their mean and 5th, 50th and 95th percentile; draws that do not solve are counted and named (domains/process/reactor_uncertainty.py). Nothing is written. Exit 2 when the step cannot be sampled (not one inlet, no reaction with an uncertainty, not solvable as declared).
  • Selectivity as a design input: a conversion reactor step may give selectivity: {key, conversion, products: {id: S}} (kmol formed per kmol of the key converted); process propagate solves the extents of its parallel and series reactions (chemistry.reactions.selectivity_extents), refusing selectivities the reactions cannot give.
  • Stirred tanks and batches: reactor: cstr (a tank: of a volume or residence time, count tanks in series, isothermal or adiabatic) and reactor: batch (a batch: time or target conversion) solve the reactions' rate laws (domains/process/stirred_tank.py), gas or liquid; process propagate --fill writes the outlet and process reactors reports the tanks' conversions and duty, or the batch's profile and heat release.
  • Liquid-phase reactions: a reaction with phase: liquid takes its equilibrium_constant on basis: mole_fraction, activity (UNIFAC from the components' CAS numbers, extra props; activity_model: ideal in reactions.yaml for activity coefficients of 1) or concentration (with each component's liquid_molar_volume), and its rate law on driving: mole_fraction, activity or concentration (domains/chemistry/liquid.py). reactor: equilibrium solves a liquid equilibrium and process reactors reports ln(Q/K) and the rate on that basis; validate warns when a basis does not fit the phase (cheme.reaction-phase-basis).
  • Solids, crystallizers and filters: a component with phase: solid in process/materials.yaml is a solid (a crystal, or a hydrate with its water in the formula, Na2CO3(H2O)10), a separate component from the same substance dissolved; every stream then divides into dry solid and liquid (mother liquor or moisture) with their compositions (domains/process/solids.py; no particle size or population balance). A block or step with crystallizer: (one solute, one solvent, one material inlet) is a cooling and/or evaporative crystallizer with an optional specified filter or centrifuge (separation: solid recovery and cake liquid) and mother-liquor recycle:; basis: solubility predicts the crystals from sourced solubility data (the mother liquor leaves saturated; points interpolated in temperature, never extrapolated), basis: yield imposes them (specified, compared with the solubility when given). solid_separation: is a filter or centrifuge on its own. process propagate --fill writes the outlets, validate balances the node on mass and elements, the model basis is solubility-equilibrium (predictive, not simulated), imposed-yield or solid-split (specified). cheme process crystallizers [--case C] [--json] reports the crystals formed (and at equilibrium), recovery, loop iterations, mass, solute and element residuals, the lumped duty (with energy:), the outlets as dry solid, liquid and moisture, and refuses impurities, several solutes or a temperature outside the data (cheme.process-crystallizer-unsupported). Qualified against Felder & Rousseau Example 4.5-2 and Geankoplis Examples 12.11-1 and 12.11-2 (tests/test_crystallization.py; docs/adr/0025-solid-components-and-a-bounded-crystallizer-adapter.md).
  • Reaction evidence: process/reaction-evidence.yaml (schema cheme/reaction-evidence/v1, cheme schema reaction-evidence, skills reaction-evidence-search and route-selection) records the searches, sources, observations, candidate routes, conflicts and the model basis behind the reactions, before any flowsheet. cheme validate checks it (cheme.evidence-*: references, DOIs and patent numbers, a location for every reported value, no numbers from a source only seen as a snippet, units, the yield basis, duplicate evidence, kinetics validity, an explicit model basis whose reactions reactions.yaml declares). cheme chemistry evidence [--json] prints the route comparison. Worked example: examples/reaction-evidence-ethyl-acetate (three routes to ethyl acetate from papers and patents).
  • The reaction library: cheme chemistry library [ID] [--add] lists, shows and adds 23 industrial processes (ammonia, methanol, reforming, shift, methanation, ethylene oxide, sulfuric and nitric acid, Claus, Fischer-Tropsch, hydrotreating, styrene, acrylonitrile, propylene oxide, cracking, vinyl chloride, formaldehyde, maleic anhydride, cumene, ethylbenzene, MTBE, HCN): components with chemicals' ideal-gas data and critical constants, main and side reactions, typical catalysts and windows, with sources (domains/process/data/library/, built by scripts/build_reaction_library.py from scripts/reaction_library.yaml). --add appends to the plant's process files, keeping their comments. Each entry's model_basis says which reactor model its own data support, checked against the data: equilibrium (8 entries: every reaction reversible, gas phase, ideal-gas data over the window; an equilibrium limit, not a kinetic model) or stoichiometry_only (15 entries: no conversion, selectivity, equilibrium basis or rate law); conversion_selectivity and kinetic need sourced conversions or rate laws no entry has yet. The listing, --add and GET /api/library show it: an entry brings chemistry, the reactor model is chosen separately.
  • Microkinetics: a reaction's mechanism: lists its elementary surface steps (adsorbates X*, free sites *, per-site rate constants or sticking coefficients). cheme chemistry microkinetics ID -T K -p ID=BAR ... [--cantera] [--export FILE] solves the mean-field steady state (coverages, TOF, degree of rate control per step, apparent activation energy, orders; domains/chemistry/microkinetics.py), checks it against Cantera (extra cantera) and writes Cantera's YAML input.
  • Rate laws from data: experiments/kinetics/*.yaml (schema cheme/kinetic-data/v1) holds rates of a reaction; cheme experiments fit-rate FILE [--form lhhw] [--reverse] fits a power law (linear least squares on ln r) or an LHHW law (Levenberg-Marquardt), each parameter with its 95 % interval; cheme experiments sample-mechanism makes such data from a mechanism, to lump it into a rate law for a fixed bed. Mechanism steps may give DFT energies: (transition state theory) instead of rate constants.
  • Electrochemical cells and measurements: cheme process cell [--json] evaluates the cells of process/cells.yaml (schema cheme/electrochemical-cells/v1): the Faraday balance, power, heat duty and kWh per kg, from specified values or, with performance: measured, from a measured series looked up at the cell's current density (refused outside its range, never extrapolated), and an optional membrane block (water dissociation efficiency, co-ion fluxes, anolyte hydroxide deficit; docs/framework/electrochemical-cells.md). experiments/electrochemistry/*.yaml (schema cheme/electrochemical-measurements/v1) holds a measured series with replicates; cheme experiments electrochem FILE [--json] [--plot DIR] computes per replicate and point (mean, sd, n) the Faradaic efficiency of each product from the outlet flow and mole fractions (standard conditions required), partial current densities, the water dissociation efficiency from the anolyte titration, power, heat and the charge and carbon balances, and writes polarisation, efficiency, partial-current and water-dissociation plots only for the inputs that support them (docs/framework/electrochemical-measurements.md).
  • Electrolysis duties: process/duties.yaml (schema cheme/electrolysis-duties/v1) states dynamic duties of a cell of process/cells.yaml against its anolyte reservoir: a current, power or on/off schedule, the reservoir (volume, composition, anolyte model by id, optional CO2 degassing), dosing on a pH trigger or continuous, bleed, make-up water, the stated pH threshold (a design criterion) and the horizon. cheme process duty [IDS] [--json] [--plot DIR] [--compare ID,ID --co KG] integrates them (Dormand-Prince 5(4) with events): the voltage, Faradaic efficiency and water dissociation efficiency at each instant from the measured series (never extrapolated: the transient stops at the first exit from the measured range and the time outside is summed) or the specified cell; pH, inventory, product, charge, energy, dosing and bleed over time; the time to the specified pH threshold; --compare compares duties at equal delivered product; --plot writes aligned time plots and an operating map (WDE x reservoir volume) with the measured WDE points apart from the simulated values. The first anolyte model is well-mixed-carbonate v1 (equilibrium carbonate speciation on a concentration basis; docs/framework/electrolysis-duties.md). A duty a stack of process/cells.yaml names runs at the arrangement that stack is sized at (cheme.duty-arrangement), as in cheme process stack, and so does a duty that names the stack itself (stack: STK-1, a comparison duty on the skid's hardware; another cells_in_series or stacks on it is an error). The totals are split by route (the cation and carbon dosed, bled and through the membrane, the initial inventories), so both balances close on their own. reservoir.size: to_horizon (optional time_margin) reports the exact volume that reaches the horizon, V0 Q(target) / Q*, for a duty without dosing, bleed, make-up or degassing at one operating point, raised to keep a stated minimum inventory and the anolyte model's validity to the target (required_volume, limited_by; otherwise cheme.duty-size, error).
  • Electrolyser stacks: the optional stacks: section of process/cells.yaml (same schema, optional fields only) names per stack design a cell, the product duty, the electrical limits, the assumed envelope per cell, clearances, the replacement interval and the skid equipment a duty sizes. cheme process stack [IDS] [--json] [--write] sizes it at the cell's operating point (refused outside its measured or declared range, never extrapolated): A = n F Ndot / (nu FE j), ceil(A / A_cell) cells, at most floor(U_max / U) in series per stack, the stacks, the reconciled voltage, current, DC power, output and margin, kWh per kg, the envelope and footprint; and the reservoir, dosing and bleed tanks and the recirculation and dosing pumps' inputs from the named duty, run at that arrangement (a duty at another current density or of another cell, or not feasible, gives none: cheme.stack-duty); a dosing tank's refill_interval and a bleed tank's empty_interval size it for the most dosed or bled in any such interval, with the fills or emptyings per campaign; product.recovery (default 1) sizes the stacks for the product formed, rate / recovery, and reports formed and delivered output; a duty with reservoir.size: to_horizon gets its reservoir volume written into process/duties.yaml by --write. Without --write it writes nothing and reports what differs (cheme.stack-outdated); --write edits the equipment-list dimensions in place and the pumps' datasheet type_specific (the recirculation pump's with its end points suction_from and discharge_to), never over a value written by hand (cheme.stack-kept), from which cheme layout build and cheme datasheets draft/fill take them; a value it wrote that is no longer given is cleared, a tag two stacks name is written by neither, and --write --json adds written (file -> tags). The LCA does not read the stacks: their power and dosing go into the flowsheet streams by hand. Exit 1 on an error finding (a process/cells.yaml that does not follow its schema among them, its findings printed), 2 without the file or stacks or for an unknown id (docs/framework/electrolyser-stacks.md, ADR 0044, skill skid-design).
  • cheme chemistry kinetics -r ID=NAME ... [-p ID=NAME ...] [--rmg-endpoint URL] reduces the NIST (and RMG) records of an elementary gas-phase reaction to one Arrhenius record and prints it as a rate: entry (network). An entry that is a formula and also another compound's SMILES (CO, NO, O, C) is refused as ambiguous: give a name (carbon monoxide), an explicit SMILES ([C-]#[O+]) or smiles:CO for the SMILES reading.
  • cheme chemistry thermo NAME... [--fit LO,HI] (extra props) prints a component's ideal_gas entry from chemicals: ATcT formation enthalpy, entropy and Poling's Cp/R (to 1000 K), or Cp/R fitted to thermo's ideal-gas heat capacity over LO to HI K.
  • cheme process streams [--case C] [--md | --csv | --json] prints the stream table (value, unit and provenance per property, calculated values, stale ones marked) and the balances.
  • cheme process propagate --fill | --check derives missing compositions and flows through steps whose streams determine them: pass-through classes (compressing, pumping, cooling, heating, filtering, transporting, flow control; heat exchanging with one stream in and one out) keep composition and flow, mixers combine, splitters keep the composition and split by split: {outlet port: fraction} (else the one missing outlet flow is the mass balance remainder), and a conversion reactor (a step naming reactions:, each with a conversion) converts its fraction of each key component's inlet flow; a crystallizer or a filter (crystallizer:, solid_separation:, below) fills its outlets. Other separators, and reactors without conversions, are only checked, never solved. Values are calculated with source and input fingerprint, never overwrite given or assumed ones, and are reported stale when an upstream value changes. Run it before props --fill. --json prints the run as an adapter result (cheme/adapter-result/v1, cheme schema adapter-result): converged when every node is solved, else not-converged with the unsolved nodes in messages and units; iterations the sweeps through the flowsheet; per stream the derived values and those written (filled). It writes no file of its own.
  • cheme process props --fill | --check | --coverage (extra props) calculates phase, vapour fraction, molar mass, density and enthalpy, only inside the validated range of the process's property package (property_method.package in materials.yaml, else the package covering the most streams; --coverage lists it and, per stream, whether it is covered or why not, writing nothing). Package air-pr-chemsep: N2, O2 and Ar; 77 to 315 K; vapour 100 to 3200 kPa(a); liquid and two-phase 100 to 600 kPa(a). Peng-Robinson (thermo, ChemSep kij) for equilibrium and vapour density, COSTALD for liquid density, enthalpy on ideal gas at 298.15 K and 101.325 kPa. The references (NIST WebBook equations, tests/data/process_props_references.yaml) pass within 0.5 % (gas density at ambient pressure), 1 % (gas density up to 31 bar), 1.5 % (liquid density) and 0.3 K (saturation temperatures). Package syngas-pr-vapor: H2, N2, Ar, CH4, CO, CO2 and their mixtures, NH3 pure and in mixtures with H2, N2, Ar and CH4 up to mole fraction 0.25 (ammonia synthesis loop gas), vapour only, in per-component temperature-pressure boxes within 250 to 1000 K and 100 to 30000 kPa(a), benchmarked by tests/test_property_packages.py against CoolProp's reference equations of state, GERG-2008 mixtures and the Neumann et al. (2020) ammonia mixture models, and against measured densities (density 2 % pure, 3 % mixtures; enthalpy differences 300 kJ/kmol). A state outside the range, another component, a pressure that is not absolute and positive, solids, or a phase the flash contradicts is refused with a finding, and nothing is written. --fill never overwrites given or assumed values, records the method, its package versions and range in materials.yaml, and stores an input fingerprint with each calculated value; when an input changes, validate reports cheme.process-calculated-stale until it is recalculated (a refused recalculation keeps the old values, marked stale). --check recalculates and reports only (exit 1 on a warning); --fill exits 3 when it refused a stream and names the refused streams on its summary line. --json prints the run as an adapter result (solver thermo with its version and EOS; not-converged with the refused or uncalculable streams in messages; iterations null, one flash per stream).
  • cheme process simulate --fill | --check [--save x.dwxmz] solves the flowsheet in DWSIM. process/simulation.yaml gives the property package and what each step needs beyond its streams (every value with its provenance):
schema: cheme/simulation/v1
kind: simulation
property_package: Peng-Robinson (PR)
specs:
  K1: {outlet_pressure: {value: 800, provenance: given}, efficiency: {value: 0.78, provenance: assumed}}
  E1: {outlet_temperature: {value: 308.15, provenance: given}}

DWSIM runs in Docker: build the image once with docker build --platform linux/amd64 -t cheme-dwsim:9.0.5 -f docker/dwsim/Dockerfile . in this repository. Simulated streams get temperature, pressure, flow and composition (energy streams their duty) as calculated; given and assumed values are never overwritten, and a deviation from them is a warning. A duty is written only where its sign agrees with its energy stream's direction (heat added or power into the unit, heat removed or power produced out of it); otherwise, and for a component separator's energy imbalance, cheme.process-sim-energy-direction warns. Run props --fill afterwards for phase, density and enthalpy. Units: compressor, expander, pump, heater, cooler, valve, mixer, splitter, flash drum, heat exchanger, shortcut and rigorous column, and conversion, equilibrium and Gibbs reactors from the flowsheet's reactions (the specs each needs: cheme schema process-simulation); recycle loops are torn and solved iteratively. Rigorous columns take side draws, a partial or no condenser, no reboiler and a condenser-reboiler check (examples/process-double-column, an air separation double column). See examples/process-simulation and examples/process-separation.

simulator: idaes in simulation.yaml (default dwsim; ADR 0039) solves the flowsheet in the optional IDAES backend instead: the worker runs in the Python CHEME_IDAES_PYTHON names (with idaes-pse and IPOPT; cheme-as-code never imports IDAES or Pyomo). Its scope is heater, cooler, valve, pump, mixer, splitter, flash, conversion reactor and recycles (compressor and expander with Peng-Robinson), property packages IDAES ideal VLE and IDAES Peng-Robinson for benzene, toluene, hydrogen and methane; anything else is cheme.process-sim-backend-unsupported (error) at translation, and neither package is benchmarked (-sim-method-unbenchmarked, info). Values carry the source cheme process simulate: IDAES <version>, <property package>; the run record also names the solver (IPOPT). See docs/framework/idaes.md and examples/process-hda-idaes.

--fill also records the run in process/simulation-run.json (generated, committed with the flowsheet; schema cheme/simulation-run/v1, one entry per case): simulator and version, property package, when, the model fingerprint and the input files' commit and fingerprints, convergence and status, iterations (the flowsheet passes DWSIM made: one per solve without a recycle, else the iterations of the slowest recycle), messages, per unit its status (simulated, or excluded with the reason), convergence, results and warnings, per stream the simulated values and which of them it holds. A run that does not converge is recorded too (no stream value is written). --check and cheme validate report a record from other inputs (cheme.process-sim-run-stale, warning; translating needs no simulator), --check also a case without one (-sim-run-missing, info). GET /api/simulation serves it. Each entry is an adapter result (cheme/adapter-result/v1) in the file's keys: simulator (with method, the property package) is its solver.

The property package must be one the worker runs (cheme process methods; another is cheme.process-sim-property-package, error). method_selection: {facts: {...}} records the facts of the thermodynamic model selection; the translation re-evaluates them and flags a package they do not recommend (cheme.process-sim-method-not-recommended, warning) unless method_selection.substitution records why it is accepted (-sim-method-substituted, info); incomplete facts are -sim-method-selection. - cheme process methods [--json] prints the supported-method contract (domains/process/thermodynamics/methods.yml): for each model the selection tree can recommend, whether cheme process phase (thermo) and cheme process simulate (DWSIM) run it, else the nearest model they run as an explicit substitution with what it loses, or none. Coverage decisions: electrolytes, solid equilibrium and petroleum pseudocomponents are unsupported on both routes (ions, unidentified components and, with water, the salts, acids and bases electrolytes.yml lists, and any compound of an alkali or alkaline-earth metal, are refused by the phase route and by the simulation translation, cheme.process-sim-electrolyte; the phase route has no solid phase). Electrolytes are limited: the electrolyte route runs Pitzer's liquid model for aqueous NaCl only (cheme process electrolyte, below). The phase route claims no application range; the simulation route claims one only where a test benchmarks it (today Peng-Robinson (PR) for N2/O2/Ar and their mixtures, Soave-Redlich-Kwong (SRK) for each of them alone, NRTL for the ethanol + water bubble points at 101.3 kPa(a) against measured VLE data, on DWSIM 9.0.5, methods[].simulation.application_range, checked by cheme maturity). - cheme process phase COMPONENT... (--model EOS | --liquid M --vapor V) [--z x,y] [--temperature T --pressure P] [--accept-zero A:B] [--check] [--json] (extra props) reports per binary pair whether the model's interaction parameter is sourced (with its source: thermo's ChemSep PR, NRTL and Wilson tables, the fetched ChemSep SRK file, thermo's UNIFAC group parameters), missing (the calculation uses zero) or accepted_zero (--accept-zero); with a temperature and pressure it also runs the flash. --check exits 1 when a pair is missing. Python: thermodynamics.coverage.interaction_parameter_coverage. - cheme process electrolyte --temperature T --pressure P (--molality M | --mass-fraction W | --species Na+=M,Cl-=M) [--system NaCl-H2O] [--json] (extras cantera and props) runs a qualified aqueous electrolyte system of the versioned contract domains/process/thermodynamics/electrolytes.yml (ADR 0027): today NaCl-H2O, through Pitzer's equations in Cantera's HMW-electrolyte phase with the Silvester and Pitzer (1977) parameters, complete dissociation into Na+ and Cl-. It prints the mean ionic activity coefficient (molality scale), the osmotic coefficient, the water activity and the water vapour pressure over the solution (a_w times the IAPWS-95 saturation pressure), the relative apparent molar enthalpy phi_L of NaCl (heats of dilution), the species, element and charge closure, and the provenance (solver version, parameter source, tolerances, benchmark). The claim: 273.15-373.15 K, up to 1000 kPa(a) and 6 mol/kg, phi_L from 313.15 K, one liquid phase; tests/test_electrolytes.py checks it against Hamer and Wu (1972) and Pitzer, Peiper and Busey (1984) within 2 % in gamma+-, 0.01 in phi and 0.03 RT in phi_L. Anything else is refused with exit code 3 and a rule id (cheme.process-electrolyte-range, -unsupported for another ion, -charge-balance, -phase for a boiling solution, -system), never computed with a neutral-fluid model. A brine in a mass balance with specified splits (an inventory) needs no property model; its properties come from this route or stay unclaimed. Python: thermodynamics.electrolytes.electrolyte_state. A converged run is also checked for energy, independently of the duty balance DWSIM closes itself; the record keeps the audit under energy. Exchangers: the hot and cold composite curves from the solved temperatures and duties (counter-current, constant heat capacity flow per stream) give the least approach; a crossing is sim-temperature-cross, an approach below min_approach (in simulation.yaml, or per exchanger spec) sim-exchanger-approach; a stream that changes phase is sim-exchanger-zones (info: no zone temperatures, so the internal approach is not checked). Utility levels: a duty whose energy stream comes from (or goes to) a terminal with category: utility and utility: {kind, supply_temperature, return_temperature, min_approach} is checked against those temperatures (sim-utility-level). Enthalpy closure: per unit and for the simulated plant (boundary streams, heat, shaft work) from DWSIM's stream enthalpies, within an explicit tolerance (sim-energy-unbalanced). Reactors (conversion, equilibrium, Gibbs, kinetic_pfr) close on a heat-of-formation basis: DWSIM's enthalpy flows plus sum (n_out - n_in) dHf_298 of the reacting components, on DWSIM's own formation enthalpies (its database, which its reactor duty takes), so the closure tests the simulation's energy conservation. Verified live (DWSIM 9.0.5): the reference state (each component's ideal gas at 298.15 K = 0, no formation enthalpies) for Peng-Robinson, PR78 and SRK, not for NRTL; the duty reproduced within 1e-3 kW for vapour ammonia synthesis at 10 bar (conversion, equilibrium and Gibbs reactors under Peng-Robinson; the conversion reactor under PR78 and SRK). The project's formation enthalpies are compared with DWSIM's separately (sim-formation-data): a warning where values declared in materials make a reaction heat that differs by more than 1 % of it (predictive.formation-data then fails); a smaller nonzero difference, chemicals' fallback values and missing values are info and recorded limitations, with each component's difference in kJ/kmol (ammonia: -45558 kJ/kmol in ATcT, -45898 in DWSIM, 0.74 % of the synthesis heat). Another package and units with an unestablished duty sign (a column top without condenser) are sim-energy-unsupported, never a number. With simulator: idaes the record's energy.reference states IDAES's basis (formation enthalpies included, formation_enthalpy_included: true) and a reactor's closure is sim-energy-unsupported (idaes.md). A component separator (a specified split) conserves no energy: its imbalance is stated, not judged (status: stated, imbalance_kW = enthalpy out minus in from the solved streams, positive: heat the unit would have to take in; DWSIM's EnergyImb is recorded with its observed sign, simulator_convention, and not relied on), and the plant closes with it as a stated residual (stated_kW; sim-energy-stated, info, beyond the tolerance; not a utility; ADR 0038). Every duty is classified once as hot or cold utility, power, or heat recovered between process streams (process-to-process exchangers, condenser-reboilers), which is never also counted as utility. - cheme process extract [UNIT] [--systems] [--json] (extra props) solves each extractor of process/extraction.yaml (schema cheme/extraction/v1, cheme schema process-extraction): a feed and a solvent feed through stages countercurrent equilibrium stages at one temperature, NRTL from thermo with a bundled LLE system (system:, listed by --systems) or sourced parameters for every pair (model:). It prints the raffinate and extract, solute recovery, solvent loss, distribution coefficient and selectivity, the component closure and qualification (qualified only for a bundled system inside its benchmarked range; the bundled water + 1-propanol + n-butyl acetate system is benchmarked against Costa et al. 2009 at 323.15 K). Refused (exit 1, a finding with its line): no two-phase region, a missing pair (never taken as zero), a temperature outside the parameters' range. Isothermal, no energy balance; solvent regeneration is a separate unit; the flowsheet keeps a component separator citing the result. Writes nothing. Python: separations.extraction. - cheme process energy [--case C] [--json] prints the energy balances of blocks and steps (stream calculated.enthalpy times molar flow against the energy streams crossing them, cheme.process-energy-balance, checked by validate in every case with stream states; a calculated enthalpy must be in kJ/kmol and a molar mass in kg/kmol, another unit is cheme.process-unit and leaves the balance not evaluable, never converted; a block holding a step without an energy stream whose duty is unknown is not evaluable; a reacting node needs units.enthalpy_reference.state: elements and values on that basis, else it is unsupported), the energy audit of the recorded simulation run, and the boundary gaps that leave TEA and LCA inventories incomplete: steps that exchange heat or work without an energy stream (cheme.boundary-utility-unassigned), terminals without category (raw_material, product, by_product, utility, wastewater, off_gas, solid_waste, emission; cheme.boundary-unclassified) and utility terminals without levels. Exit 1 on a warning. - cheme process pinch --dt-min X [--dt-min-source S] [--case C] [--include-constrained] [--json|--md] [--write] takes whole-plant pinch targets (problem table, heat_integration/pinch.py) from the case's recorded simulation run (process/simulation-run.json; it never runs the simulator: a missing, stale or unconverged run is an error, cheme.process-pinch-run-missing, -run-stale, -run-not-converged). dTmin is required, its source recorded (default ASSUMED). heat_integration/streams.py extracts one stream per thermal duty and side, each in one class: utility (heaters, coolers, flashes, condensers, reboilers, the process side of an exchanger against a utility terminal's stream such as a waste-heat boiler, whose steam is the utility and no second stream), recovery (both sides of a process/process exchanger, counted once; every side of a multi-stream exchanger; a condenser heating another column's reboiler), constrained (reactors, with the reason; out of the targets unless --include-constrained) or excluded (with the reason). Quality: the simulators report end temperatures only, so a stream is linearised (one constant-CP or isothermal segment; a phase change is not split at dew or bubble points, cheme.process-pinch-stream-linearised) or insufficient (excluded, cheme.process-pinch-stream-excluded). The reconciliation lists every thermal duty of the run from the units' results and checks the streams give each back within 0.1 kW + 1 %; heat left over is cheme.process-pinch-reconciliation (warning). It prints the current utility demand (of the targeted streams; recovered heat is not utility), the theoretical minimum utility demand at dTmin and the theoretical energy-saving potential (their difference; it ignores pressure, distance, fouling, control, safety and cost and is not a saving), the hot-side, cold-side and shifted pinch or a threshold problem, utility-use pinch violations (a utility heater below, a cooler above the pinch) apart from cross-pinch process-to-process transfer of the existing exchangers whose sides are all known (with their least approach), the stream table and the reconciliation. --write records settings, core version, the simulation run (model and content fingerprints), the inputs and the results in process/pinch-run.json (schema cheme/pinch-run/v1, one entry per case, generated) and refuses with exit 3 while the reconciliation has a finding. Exit 1 on an error or a warning, 2 on invalid input (no or a negative --dt-min, an unknown case, --json with --md). Python: heat_integration.report; ADR 0040. - cheme process furnace [ID] [--json] gives the energy basis of a solid-reaction furnace declared in process/furnaces.yaml (schema cheme/furnaces/v1; today limestone calcination only): the element balance, the product and off-gas composition (calcination and fuel CO2 apart), the reaction enthalpy and the furnace energy closure (fuel LHV or electric power, sensible heat of feed, product and off-gas, moisture, losses) from the NASA condensed and gas data Cantera ships (extra cantera), never from a fluid flash. A measured fuel flow or power is checked (cheme.furnace-closure), an absent one solved; the degree of calcination is given, never predicted. Temperatures outside a species' data, other reactions (carbothermal reduction, dolomite) and missing losses are refused with a finding. method: specific-energy is a labelled conceptual fallback (a sourced GJ/t or the lime BREF's range by kiln type). Reference case, data and tolerances: docs/framework/furnace.md. Exit 1 on a warning or error. - Links. Blocks, steps, streams, feeds and products link to equipment, line, instrument or sheet (several per object, an optional sheet: scope). They resolve against the lists and plant.yaml; unresolved ones are cheme.process-link-unresolved (info), and the gate kind: process-links with level: warning or error reports or requires them. - cheme process layout [IDS] --view bfd|pfd [--pin] prints the generated positions, and --pin writes them into layout.<view> so later builds keep them.

validate, status, diff, show and render take BFD and PFD sheets like P&IDs; the loader's warning about port reference cycles is demoted to one info line per sheet. The template's skill process-design walks through it, and pid-draft starts the P&IDs from the PFD. The schemas of both files: process-materials and process-flowsheet; every cheme process subcommand with its options: CLI reference.