0043. Dynamic electrolysis duties with an anolyte model interface, on a concentration basis¶
- Status: Proposed
- Date: 2026-11-04
Context¶
ADR 0041 gave a cell of
process/cells.yaml its measured voltage and efficiencies at one current density, and the
anolyte hydroxide deficit (1 - WDE) N I / F of a bipolar-membrane cell as a steady rate.
What a design needs next (core#138, tracker core#136) is the duty over time: a current or power
schedule, the reservoir the anolyte circulates through, its pH falling as hydroxide is lost and
carbon taken up, dosing and bleed, and how long the anolyte stays above a stated pH. Duties
should be compared fairly, and operation outside the measured conditions must be visible.
The reference model is that of Prats Vergel et al. (Nat. Chem. Eng. 2025), reproduced in
process-library#329: a finite-rate model of CO2 hydration and the carbonate equilibria with
moles in the tank as the state. The reproduction found (#329 D1) that its pH stop is taken on
the H+ amount (-log10 n_H), which is the pH only at 1 L: at 10 L it stops at pH 13, 10 %
early. The published figure is consistent with a concentration basis (10 L = 10 x 1 L).
Options considered: run the authors' script (scipy, and their volume-dependent stop); port the
finite-rate kinetics (rate constants with no stated source for this anolyte, a stiff system);
or an equilibrium speciation on conserved quantities. And for the code: one hard-wired KOH
balance in the duty, or an interface with a registry so a later model (activities, chloride,
finite-rate CO2 uptake) plugs in without changing the duty. The base dependencies have no
ODE solver; fixed_bed._dopri_step clamps the bed's own states.
Decision¶
- A new optional plant file
process/duties.yaml, schemacheme/electrolysis-duties/v1(kind: electrolysis_duties, catalogue keyprocess-duties), owned bydomains/process/electrolysis_duty.py: per duty the cell (an id ofprocess/cells.yaml), optional cells in series and stacks, the schedule (constant_current,piecewise,power,on_off), the reservoir (volume, initial composition, cation, anolyte model id, optional degassing withk_L aand the CO2 partial pressure), dosing (none,on_threshold,continuous), bleed, make-up water, the stated pH threshold (a design criterion; reports call it "time to a specified pH threshold", never a corrosion onset), the horizon, the minimum inventory and an optional tolerance and operating-map axes. - The cell's performance at each instant comes from
MeasuredPerformance.at(j)for a measured cell (voltage, the product's FE, WDE, the carbon share of the co-ion current) or from a specified cell's constants within its operating range. A power schedule is solved forjinside the measured range only. Nothing is extrapolated: the transient stops at the first exit from the measured range (current density or temperature), the scheduled operating time outside is summed, and such a duty is not feasible. - An anolyte model is chosen by id from a registry (
domains/chemistry/anolyte.py); the protocol states its id and version, state, accepted species,initial,rates,losses,speciate, its validity (ionic strength, pH, temperature), assumptions and limitations. An unknown id ischeme.duty-model-unknown; there is no default model. Time outside the model's validity is reported and also makes a duty not feasible. The duty reads a model's state by the names it declares (cationin mol andvolumein L required,carbonreported when present), never by position, and stops when any conserved inventory (mol) of it is exhausted. - The first model is
well-mixed-carbonatev1 (domains/chemistry/carbonate.py): moles of the cation and of total inorganic carbon and the volume as conserved quantities, instantaneous carbonate equilibrium from the charge balance (Kw 1e-14, pK1 6.37, pK2 10.32 at 25 °C, ideal solution), CO2 degassing above saturation only whenk_L ais stated. Its pH is on a concentration basis, so the time to a threshold at volume-independent fluxes scales exactly with the volume. We do not port the authors' finite-rate kinetics: at the pH of interest (12 and above) the carbon is almost all CO3-2 and their rates are fast against the fluxes, so equilibrium agrees with their concentration-basis result (322.30 h at 1 L, ours within 1e-5); a finite-rate model needs sourced rate constants and a stiff solver, and can be registered later as another model. - The KOH/carbonate anolyte stays out of the qualified NaCl electrolyte route
(
electrolytes.py,electrolytes.ymlunchanged); a test keeps it so. - Duties are integrated by a small generic adaptive Dormand-Prince 5(4) integrator with dense
output and event location in
foundation/ode.py(pure Python, no subject knowledge): events for the threshold, the minimum inventory, an exhausted inventory, dosing on and off, a product target; the schedule's breakpoints and range crossings split the integration. Its caps are reported, never silent: more than 100 000 on/off periods over the horizon is a schema error, more than 20 000 dosing switches stops the transient withcheme.duty-dosing-chatter. compare_dutiescompares duties at equal delivered product: each runs until its cumulative product equals the target, within a stated relative tolerance (default 1e-3), or is reported as not reaching it.cheme process duty [--json] [--plot DIR] [--compare ID,ID --co KG]is the thin command; plots are hand-written SVG, measured observations drawn as markers and simulated transients as lines. Findings arecheme.duty-*;cheme validatereports those that need no integration.
Consequences¶
- Positive: a design states its duty once, with provenance and a fingerprint of every input; the time to a stated pH, the dosing a schedule needs and the effect of the reservoir volume follow from the same measured series the steady cell uses, with the measured range enforced.
- Positive: comparisons at equal delivered product do not favour a duty that simply makes less; the constant-efficiency limit (equal charge, equal deficit) is a test.
- Negative: the pH is a concentration-basis pH of an ideal solution; at 1 mol/L ionic strength activity coefficients are far from 1, so it is not a measured pH. The constants are at 25 °C. The model has no chloride, formate or solids, and supersaturated CO2 stays in solution without degassing.
- Negative: the cell's performance is quasi-steady at each instant and does not depend on the reservoir's changing composition; the transient after a range exit is not computed.
- Negative: the pure-Python integrator is slower than a compiled one; operating maps with frequent dosing switches take seconds.
- Follow-ups: an activity-based or finite-rate anolyte model; temperature dependence of the constants; stack sizing from the duties (core#139).
Evidence¶
- core#138; tracker core#136; process-library#329 (D1, V1).
src/chemeascode/domains/process/electrolysis_duty.py,src/chemeascode/domains/process/electrolysis_plots.py,src/chemeascode/domains/chemistry/anolyte.py,src/chemeascode/domains/chemistry/carbonate.py,src/chemeascode/foundation/ode.py,src/chemeascode/interfaces/cli/process.py(duty).tests/test_electrolysis_duty.py,tests/test_carbonate.py,tests/test_ode.py,tests/data/electrochemical/co2-duty/.docs/framework/electrolysis-duties.md.