Findings log#

One entry per mismatch between the draft contract and an existing model, or between the two reference codebases. Append, never silently resolve. Contract changes must appear here.

Verdicts: contract flaw (change the contract), model flaw (fix the model), family-specific (legitimate difference; keep in the family namespace; test: does it matter for cross-family comparison? If not, it stays family-specific).

Format: F-NNN | area | what differs | verdict | action | status

Entries#

Seeded by the Stage A side-by-side diff (2026-10-11). Sources compared:

  • HB = the finned-tube code prototype (not public), at d391da0.

  • PB = packed bed, encapsulated_ltes in encapsulated-LTES at 023bf58 (clean). Models: FullModel (resolved capsule at every x) and ReducedModel (one x-averaged capsule).

ID

Area

What differs

Verdict

Action

Status

F-001

Coordinates

Flow coordinate is z in HB and x in PB (both on domain "pipe"). Storage: HB y on "PCM vertical" (cylindrical) and, in the mush model, x on "PCM horizontal"; PB r on "capsule" (spherical). PB’s x clashes with HB’s fin-gap x.

Flow coordinate: model flaw (PB). Storage domains: family-specific.

PB renames x to z (D12). Storage domain names are settled with the open naming item.

closed (F-039): the bed uses z on “flow path” (F-038) and r on “capsule”

F-002

Parameter names

PCM properties: HB "PCM solid phase conductivity [W.m-1.K-1]", "PCM latent heat [J.kg-1]", "PCM melting temperature [K]"; PB has the same names without "PCM ". Flow: HB "Inlet flow rate [m3.s-1]" (volumetric, per pipe); PB "Inlet velocity [m.s-1]" (interstitial). "Pipe length [m]" is the bed length in PB. HB scales to the device with "Number of pipes"; PB has no cross-section, so everything is per unit bed area.

Contract flaw (no shared namespace yet) and model flaw in both (neither uses mass flow rate, D8).

Contract defines the material namespace and the device-level control (mass flow rate). PB needs a bed cross-section parameter so totals are in J and W.

closed (F-039): shared “PCM …” names; mass flow rate control; “Bed cross-sectional area [m2]”

F-003

Material classes

Not shared, despite DESIGN §1. HB: PhaseChangeMaterial with PureMaterial and CompositeMaterial, corners smoothed by smooth_min/smooth_max over "PCM melting range [K]" (default 0.5 K), liquid_fraction(H), k linear in liquid fraction, T = T_m taken as solid, H scaled by rho_L. PB: H2T, T2H, k are methods on the parameter class, sharp pybamm.minimum/maximum, k piecewise with indicator functions, no liquid-fraction function. PB T2H gives H = 0 at exactly T = T_m (both indicators false), and its guard if self.T_m == T compares symbols, so it never fires. Same underlying physics: rho_L = rho_s L, liquid sensible heat rho_l c_p_l.

Model flaw (PB); DESIGN §1 inaccurate.

Use HB’s classes as the base (Stage B merge). Correct DESIGN §1 wording.

closed: one copy of the material classes (pytes.materials), used by both families

F-004

Energy conservation

HB: exact, using the ledger. Sharp front uses the integral of dH/dt. Mush uses the integral of dH/dt between PCM and fin, and the same h(T_c - T_f) expression on both sides at the wall. Test tolerance 1e-9 of released energy. PB: the fluid source is q = -k(H_surf) * BoundaryGradient(T_c), a separately discretised flux. The stored-energy ODE uses the extrapolated T_out (F-006) and the inflow is wrong (F-005). Measured with default "Raul2018", 1 h, PyBaMM 26.7.1: max error in energy conservation / final stored energy = 3.7 % (FullModel), 4.3 % (ReducedModel).

Model flaw (PB).

Stage B: ledger coupling across the capsule surface (D6).

closed (F-039): the bed uses the ledger; conservation exact (4e-15) where PB was out by up to 2.9 %

F-005

Inlet condition

HB works around the pybamm.upwind ghost node: T_bc = (T_in + T_1)/2, with constant extrapolation on "pipe". PB uses a plain Dirichlet T_in, so the inflow is 2 T_in - T_1.

Model flaw (PB).

Move the HB workaround into compat (tagged), and use it for both families via the shared fluid submodel.

closed: pytes.compat.upwind in the shared fluid, both families

F-006

Outlet temperature

HB: value in the last cell (constant extrapolation), which is the value upwinding advects out. PB: default FiniteVolume extrapolation (linear), which is not the advected value.

Model flaw (PB).

Outlet from the fluid submodel only. Ties to the open question of deriving T_out from h_out.

closed: outlet is the advected last-cell enthalpy, T(h_out) (F-032), both families

F-007

Power: definition, sign, units

HB "Power [W]" = N rho_f c_p_f Q_f (T_out - T_in), positive on discharge, whole device. PB has no power variable. "Stored energy per unit area [J.m-2]" integrates eps rho_f c_p_f u (T_in - T_out), which is positive on charge and per unit area. Both use c_p dT, not enthalpy.

Model flaw (both).

Stage A: power from mdot (h_out - h_in) via the fluid-property object (D8). PB gets this from the shared fluid in Stage B.

closed: power from enthalpy in the shared device energy, both families

F-008

Energy variables

HB "Thermal energy [J]" = PCM + fins + fluid, relative to 0 K; "Phase-change material thermal energy [J]" includes fins; "Released energy [J]" is a solver state. PB "Total enthalpy per unit area [J.m-2]" = capsules + fluid, relative to 0 K; "Variation in total enthalpy..." relative to T_0. Neither uses the T_low reference (D14). Note: checklist item 7 says the finned pipe has no fluid inventory. That is outdated: the HB working tree includes it.

Model flaw (both).

Stage A: storage and total thermal energy with the zero at uniform T_low; HB “Released energy” becomes “Discharged energy”. Update checklist item 7.

closed: storage and total thermal energy, zero at the reference temperature (F-030), both families

F-009

Heat transfer coefficient

Both use the constant "Heat transfer coefficient [W.m-2.K-1]". HB applies it at the pipe wall; the mush model puts a pitch’s worth of wall heat through the fin root (d_m/d_c factor). PB applies it at the capsule surface, with specific area 3(1 - eps)/R. Both define "Heat transfer fluid conductivity" and never use it.

Source: model flaw (both, D8). Where it applies: family-specific (coupling slot).

HTC comes from the fluid-property object (a constant for now). Keep k_f (maintainer, 2026-10-11): reserved for axial diffusion and the correlation hook.

closed: the coefficient is in the surface flux law (NewtonCooling, F-032), both families

F-010

SOC and liquid fraction

HB "State of charge" = "Average liquid fraction", a scalar. Sharp front: weighted by the composite annulus volume. Mush: weighted by the PCM gap volume, excluding fins. PB "State of charge" is a field over x: the capsule volume fraction with liquid fraction ≥ 1/2 (a phase indicator, not a liquid fraction). "X-averaged state of charge" thresholds the x-averaged H, which is not the average of the indicator. PB has no average liquid fraction.

Model flaw (both, D13).

Stage A: energy-based SOC on total energy (since removed, F-030). Contract fixes the definition of average liquid fraction: PCM-volume-weighted, fins and fluid excluded.

closed: SOC removed (F-030); average liquid fraction PCM-volume-weighted in both families

F-011

Domain structure

Same pattern: "pipe" primary for the fluid; storage domain primary, with "pipe" secondary (tertiary for the mush model’s PCM).

Family-specific (compatible).

None. Fits the D5 geometry switch.

closed

F-012

Fluid equation

Both: state T_f, first-order upwind, no axial conduction (PB has it commented out). HB: A_flow = pi R_f^2, wall area per fluid volume 2/R_f. PB: divides by eps, specific area 3(1 - eps)/R per bed volume, u interstitial. Both match D5 with A_flow = pipe area or eps × bed area.

Family-specific.

Shared fluid submodel takes A_flow and the wetted perimeter from the family.

closed: one AdvectedFluid with FlowPath (flow area, number of paths) from each family

F-013

Cut-off and operating mode

HB: operating_mode and "Cut-off temperature [K]". PB: no events.

Model flaw (PB).

No longer applies: models have no cut-off events (F-031); PB needs none.

closed

F-014

Storage temperature as a state

PB carries T_c as an algebraic variable (T_c - H2T(H) = 0), which makes the model a DAE with twice the storage unknowns. HB uses T = H2T(H) as an expression.

Model flaw (PB) under D7 (state list [H]).

Stage B: use the expression.

closed (F-039): temperature is the expression H2T(H); the bed model is an ODE system

F-015

Celsius variables

HB: shared helper set_temperature_variables. PB: written by hand for each variable.

Model flaw (PB).

Shared helper (D12).

closed: shared helper in both families

F-016

Initial state

Both: uniform "Initial temperature [K]" mapped through T2H.

Model flaw (both, D14: initialise by SOC).

Stage A for HB. Initial temperature kept as an alternative (F-029).

closed: initial state by “Initial temperature [K]” (F-030), both families

F-017

Defaults

Mesh: HB 40×40 (sharp front) and 20³ (mush); PB r 40, x 80. Solver: HB IDAKLUSolver(rtol=1e-5, atol=1e-8), tuned and documented; PB default IDAKLUSolver(). Fluid extrapolation: see F-006. Quick-plot variables differ. Temperature scale 200 K, reference T_m, in both. Enthalpy scale: HB rho_L about mid-band; PB T2H(T_0) with no reference.

Mesh: family-specific. Fluid spatial method: contract (owned by the fluid submodel). Plot variables: D15. Solver and scaling: model flaw (PB).

Fluid spatial method set by the fluid submodel. Shared default plot variables.

closed (F-039): fluid method from compat; shared plot variables; bed mesh and solver chosen in F-039

F-018

Experiment runner

step.py control residuals recompute power as C Q (T_out - T_in) instead of reading "Power [W]". The runner reads "Inlet flow rate [m3.s-1]" and "Inlet temperature [K]" directly, and removes the event by name.

Model flaw (HB runner).

Stage A: residuals use the model’s power variable and the declared control set. Approved by the maintainer (2026-10-11).

closed (Stage A)

F-019

Test coverage

HB: 72 tests (smoke, conservation, materials, experiments). PB: a version check and the template’s entry-point tests, with no physics tests. PB has things HB lacks: experimental data (Nallusamy 2007), a mesh-refinement study, and a lower-fidelity ReducedModel.

Family-specific (coverage gap in PB).

Conformance suite covers PB in Stage B. ReducedModel is part of the PB hierarchy (maintainer, 2026-10-11); renamed AverageCapsuleModel on porting.

closed (F-041): the bed in the conformance suite (F-039); ReducedModel is the bed with "storage": "averaged"

F-020

Naming and sign

PB "Flux into phase-change material [W.m-2]" is -k dT/dr at the surface, which is the flux out of the capsule.

Model flaw (PB).

Name by direction in the contract.

closed: the storage boundary flux is named by direction, into the storage (”

F-021

Packaging

PB imports scienceplots at package import, so plotting is a hard dependency of the models.

Model flaw (PB), minor.

Plots go in the toolkit, as an optional extra.

closed: the bed models import no plotting package

F-022

Control: flow rate

HB’s control was "Inlet flow rate [m3.s-1]", volumetric and per pipe. PyTES uses "Mass flow rate [kg.s-1]" through the whole device (D8: power is mdot (h_out - h_in); D4: device-level interface). Default 1000 * 20 / 6e4 kg/s is the same flow. Step arguments flow_rate become mass_flow_rate; termination flow_rate_above becomes mass_flow_rate_above.

Contract flaw (HB contract).

Done in Stage A. The value no longer follows a change of fluid density, as a volumetric flow did.

closed

F-023

Variable renames

"Released energy [J]" → "Discharged energy [J]". "Thermal energy [J]" (from 0 K) → "Total thermal energy [J]" (zero at uniform T_low); new "Storage thermal energy [J]". "Phase-change material thermal energy [J]" is now from T_low, and in the mush model it is PCM only (it included the fins); the fins have "Fin thermal energy [J]". Removed: "Inlet flow rate [m3.s-1]", "Heat transfer fluid heat capacity per unit flow rate [J.m-3.K-1]". New: inlet/outlet specific enthalpy, "Heat loss power [W]", "Thermal capacity [J]", "Initial uniform temperature [K]", "Initial uniform liquid fraction".

Contract flaw (HB names), per D12.

Done in Stage A; see contract.md §3.

closed

F-024

Initial state and window

"Initial temperature [K]" is removed. New device parameters: "Lower temperature limit [K]" (default 10 °C), "Upper temperature limit [K]" (70 °C), "Initial state of charge" (1). With these defaults the initial state is the old one (uniform 70 °C, liquid).

Contract flaw (HB), per D14.

Done in Stage A, then superseded by F-030: the window is gone and the initial state is by temperature only.

closed

F-025

SOC values

HB’s "State of charge" was the average liquid fraction; it is now energy-based on total energy (D13). Values differ from HB’s: the sensible heat counts, and SOC reaches 0 only at uniform T_low. "Average liquid fraction" is unchanged (reference match to 1e-7).

Contract flaw (HB), per D13.

Done in Stage A, then superseded by F-030 (no SOC).

closed

F-026

PyBaMM 26.10

From 26.10, pybamm.Solution has __slots__: HB’s runner fails setting step_description (.cycles and .steps still work). Found running the reference on 26.10.

Contract-neutral; upstream change.

Workaround in pytes.compat.solution, read with pytes.experiments.step_description.

closed

F-027

Reference agreement

PyTES reproduces HB d391da0 to the solver tolerance: at the default rtol=1e-5, outlet temperature within 1.1e-3 K and power within 2e-5 relative; at rtol=1e-9, within 3e-7 K (same discrete equations). The experiment matches to 1e-9 relative. HB itself moves by 1.7e-5 relative between PyBaMM 26.7 and 26.10.

None (agreement).

Pinned in tests/data/reference_prototype_d391da0.npz; its generator, which needs the non-public prototype, was removed on 2026-10-17.

closed

F-028

Submodel interface

Submodels were plain objects with hand-written requires/provides and PyTES’s own two-phase build (fundamental, then coupled variables). They are now small pybamm.BaseModels that read other submodels’ outputs as pybamm.CoupledVariable; requires/provides are read from the code, the model merges them with pybamm.BaseModel.update, and storage owns all its boundary conditions (couplings provide "<label> boundary heat flux [W.m-2]" or "<label> boundary temperature [K]"). PyBaMM resolves coupled variables only with Discretisation(resolve_coupled_variables=True), which pybamm.Simulation does not set, so models resolve them when built (compat.resolve_coupled_variables). Reference agreement unchanged.

Contract flaw (submodel interface), at the maintainer’s request (2026-10-12).

Done. The check “undeclared read” is replaced by a domain check and a cycle check.

closed

F-029

Initial state by temperature

Models take initial_state="state of charge" (default) or "temperature", alongside operating_mode. The temperature mode reads "Initial temperature [K]" (default 70 °C, the same state as SOC 1) and starts uniform at that temperature, solid at T_m (HB’s convention). Both modes provide "Initial uniform temperature [K]" and "Initial uniform liquid fraction", so the submodels and the experiment runner are unchanged. A model argument rather than a parameter-value switch because the SOC of a temperature depends on the model’s capacity, so neither parameter can be given as an expression of the other in a shared parameter set.

Contract addition (D14), at the maintainer’s request (2026-10-14).

Done. New conformance check check_initial_temperature.

superseded by F-030

F-030

State of charge and temperature window

Removed: "State of charge", "Thermal capacity [J]", the optional "Storage state of charge", the parameters "Lower temperature limit [K]", "Upper temperature limit [K]" and "Initial state of charge", the initial_state model argument (F-029), and the terminations state_of_charge_below/above. Added: "Reference temperature [K]" (energy zero; default 10 °C, the old lower limit, so energies are unchanged) and pytes.energy_between(model, T_a, T_b). The initial state is uniform at "Initial temperature [K]" (default 70 °C, the old SOC 1). The conformance check check_soc_end_points becomes check_initial_state. Reference agreement (F-027) unchanged; energy_between(10 °C, 70 °C) equals the old capacity.

Contract flaw (DESIGN D13, D14, maintainer, 2026-10-14): a SOC between 0 and 1 needs a “full” energy the device does not have, and setting an initial SOC is ill-posed.

Done.

closed

F-031

Cut-off in the model

Removed: the operating_mode model argument, its event and the parameter "Cut-off temperature [K]". The models have no events; the runner no longer filters one out.

Contract flaw (DESIGN D11, maintainer, 2026-10-14): the model is the same for charge and discharge, and two places to stop a simulation can disagree.

Done. Cut-offs are experiment terminations.

closed

F-032

Fluid state, property interface and heat transfer coefficient

The fluid state is now "Heat transfer fluid specific enthalpy [J.kg-1]" (was the temperature), scaled like the temperature so that the scaled unknowns are unchanged for a constant-c_p liquid; the temperature is T(h) and the outlet temperature T(h_out). Fluid-property functions take the state (h, p) (were (T, p)), plus h(T, p). heat_transfer_coefficient is removed from the fluid-property object: couplings take a SurfaceFluxLaw (pytes.submodels.flux_laws), default NewtonCooling with the same "Heat transfer coefficient [W.m-2.K-1]". New required variable: the fluid specific enthalpy. Results: sharp front unchanged to 1e-9; mush within solver tolerance (3e-4 K at rtol=1e-5, 2e-8 K at rtol=1e-10, same equations). Reference agreement (F-027) unchanged. The mush end-of-discharge power changed sign within solver error (outlet within 1e-5 K of the inlet), so the discharge test now solves at rtol=1e-8, atol=1e-10 and keeps its strict check power > 0.

Contract change (DESIGN D5, D6, D8, maintainer, 2026-10-14).

Done. A test checks that a custom (cubic) flux law conserves energy exactly.

closed

F-033

Fins and the fin-root coupling

SensibleConduction (fin temperature as the state) is removed: the fins are EnthalpyStorage of the sensible SensibleMaterial, with "Fin enthalpy [J.m-3]" as the state, scaled like the old fin temperature. EnthalpyStorage gains an optional volumetric heat source and provides "<label> conduction heating rate [W.m-3]"; it provides the liquid-fraction variables only for phase-change materials. FinRootCoupling is now a ledger (a LedgerWallCoupling with flux factor d_m / d_c): the fluid’s heat is minus the fins’ integrated conduction heating rate, which stays exact for a non-constant conductivity. All ledger couplings use the conduction heating rate, which equals dH/dt without a source. SensibleMaterial’s conductivity parameter is now k_param, and k(H, states) a method, as for the other materials. Results: mush within 2.6e-8 K of the previous version at rtol=1e-10 (same equations, as k is constant); reference agreement (F-027) unchanged.

Contract change (DESIGN D5, D6, maintainer, 2026-10-14).

Done.

closed

F-034

Composition API

New: pytes.ComposedModel(submodels, param, name=..., defaults_from=..., geometry=..., submesh_types=..., var_pts=..., spatial_methods=..., solver=..., parameter_values=..., quick_plot_variables=...) builds a model from submodels with the built-in wiring checks, and raises if the geometry, submesh types or spatial methods miss a state domain; BaseModel.print_submodel_info(). Reusing submodels does not modify the model they came from (tested). The options dict is deferred to the first real variant.

Contract addition (DESIGN D3, maintainer, 2026-10-14).

Done; example notebook examples/composing_models.ipynb.

closed

F-035

Melting range and smoothing

"PCM melting range [K]" was the width over which the corners of H(T) were smoothed; it is now the physical melting range (solidus T_m - range / 2 to liquidus T_m + range / 2, H linear in T between them with the latent heat plus the mean solid-liquid heat capacity), and the smoothing has its own parameter, "PCM smoothing width [K]". Defaults: range 0, smoothing 0.5 K, which reproduce the previous relations exactly (term by term), so results and the reference agreement (F-027) are unchanged. Both may be 0 (the 1e-9 K floor on the smoothing keeps derivatives finite; tested). Removed, as a melting range now fixes the liquid fraction of a uniform state: equilibrium_enthalpy(T, phi) (use T2H), "Initial uniform liquid fraction", and the phi argument of uniform_energy. validate_parameter_values takes a list of non-negative names. Users who set "PCM melting range [K]" for smoothing must now set "PCM smoothing width [K]".

Contract change (DESIGN D7, D14, maintainer, 2026-10-14).

Done. Tested: mushy-zone relations, finite derivatives with no smoothing, energy conservation and initial states with a 2 K range.

closed

F-036

Power steps

ConstantPowerInletTemperature(power, mass_flow_rate) and ConstantPowerFlowRate(power, inlet_temperature) are replaced by one ConstantPower(power, inlet_temperature=... | mass_flow_rate=...), which adjusts the control left free (step.adjusted) and raises unless exactly one is given. The old names read as what is held but named what is adjusted. Results unchanged (reference experiment F-027 passes). No deprecation aliases (pre-v0.1).

Contract change (DESIGN D10, maintainer, 2026-10-14).

Done; example notebook examples/experiments.ipynb (the runner had none).

closed

F-037

Temperature differences

Names containing “temperature difference” are K-only: pytes.variables.is_temperature_difference, add_temperature_difference_variables (no twin), and add_temperature_variables now refuses such names. check_required_variables exempts them from needing a [degC] twin and fails if one has a twin. No model provides one yet.

Contract change (DESIGN D12, maintainer, 2026-10-14).

Done.

closed; the helpers are replaced by add_unit_twins (F-043)

F-038

Flow domain and “tube”

The shared flow domain is "flow path" (was "pipe", which does not describe a packed bed), coordinate z, in every family. The finned family uses “tube”, the usual heat-exchanger word: subpackage pytes.models.finned_tube (was finned_pipe), FinnedTubeParameters, BaseFinnedTubeModel, parameters "Tube radius [m]", "Tube length [m]", "Number of tubes" (were "Pipe ...", "Number of pipes"), notebook examples/finned_tube_models.ipynb. Model classes unchanged. Earlier findings keep the old names.

Contract change (DESIGN D12, maintainer, 2026-10-15), before the packed-bed port.

Done.

closed

F-039

Packed-bed port

New family pytes.models.packed_bed: PackedBedModel (fluid through the voids, flow area eps A; EnthalpyStorage of the PCM on a spherical “capsule” domain at each z, exchanging heat at r = R; LedgerWallCoupling with Newton’s law; DeviceEnergy; PerAreaTotals), parameters "Bed length [m]", "Bed cross-sectional area [m2]", "Bed porosity", "Capsule radius [m]" (outer radius) and the shared "PCM ..." names; sets "Nallusamy2007" (default; tank 360 mm x 460 mm from the article) and "Raul2018" (1 m2 placeholder area). The conformance suite passes unchanged. Comparison with PB FullModel at 023bf58 (Nallusamy, sharp melting, both on 40 x 80, rtol=1e-8): energy conserved to 4e-15 (PB: up to 2.9 %); outlet agrees to 3 mK and stored energy to 0.03 % at 4 h; up to 4.6 K apart while the front leaves the bed (about 22 min). PB’s outlet extrapolation explains at most 0.5 K and its inlet condition about 0 (diagnostic runs); the rest is radial mesh error in both codes, which converge to the same answer from opposite sides (at r = 40 each is about 2.5 K from PyTES at r = 160) and slowly, because of the melting front inside the capsule. Default mesh 40 x 80 (3.5 s; about 1.8 K at most and 0.24 K on average in the outlet, 1 % in stored energy). Heat transfer coefficient: PB’s parameter set has 100 W.m-2.K-1 but its validation notebook uses 1000; a sensitivity check against the Nallusamy data (not a fit: every other parameter is PB’s set unchanged) gives an RMS error in the fluid temperature of 1.4 K (100), 1.1 K (300) and 1.1 K (1000), and about 4.6 K in the PCM at 0.8 R for all. The default stays at PB’s parameter-set value, 100, which is not the best of the three; whether PB’s set was itself tuned to these data is for the maintainer to confirm. Data copied to examples/data/ with provenance; notebook examples/packed_bed_models.ipynb.

Port (Stage B). The h discrepancy is a model flaw in PB (set and validation disagree).

Done; the averaged variant (F-019) and the benchmarks follow.

closed

F-040

Analytical benchmarks (packed bed)

New pytes.validation with Schumann’s solution (schumann(xi, eta), checked against its own equations) and the quasi-steady melting of a sphere (sphere_melting_time). Benchmarks in tests/validation/, both composed from the packed bed’s submodels with ComposedModel: (1) Schumann: capsules of a sensible material (Biot 0.008), NTU 5.5; the outlet temperature is within 2.4 % (200 cells) and 1.3 % (400 cells) of the temperature step, halving with the cell size (first-order upwinding; tested: < 2 % at 400 cells and a ratio of 2 ± 20 %). (2) Spherical Stefan: one capsule at its melting temperature, surface 1 K above (Stefan number 0.011); with 160 cells, the time to melt 50 %, 75 %, 90 % and 99 % is within 0.9 % of the quasi-steady solution (tested at 2 %). The first 10-25 % of melting is not compared: with a sharp melting point the front crosses the large outer cells one at a time and the melted fraction rises in steps (up to 50 % off on coarse meshes).

Validation (DESIGN D1, Stage B criteria).

Done; notebook examples/validation.ipynb.

closed

F-041

Options and the averaged storage

First option (DESIGN D3): "storage": "resolved" | "averaged" in all three models. BaseModel gains options, allowed_options (first value the default), process_options, check_options and set_spatial_variables (spatial variables are made after the options, as an averaged storage’s exclude the flow path), and pytes.OptionError. StorageGeometry(flow_path=...) marks a storage averaged along a flow path; LedgerWallCoupling (and so FinRootCoupling) then uses the fluid state averaged along the flow path and spreads the heat uniformly along it, still exact. In the mush model the fin-PCM unit is averaged, fins and PCM separate (maintainer). Conformance runs over every option combination (tests/conftest.py::MODELS, 6 variants); all pass unchanged. Averaged agrees with resolved when the fluid is uniform (100 x flow: < 0.01 % in stored energy) and differs as a front forms (default flow: 8 %, 8 % and 2 % for sharp front, mush and bed), 3-4 times faster. Known artefact, inherited from PB: with the uniform spreading of the averaged coupling, the fluid near the outlet loses heat it does not have early in a charge, and the default bed’s outlet dips to 27 °C (initial 32 °C, inlet 70 °C); energy stays exact, so conformance does not see it. Kept, as the maintainer’s published reduced model (decision 2026-10-15, DESIGN D4); the local-driving-force alternative is recorded there.

Contract addition (DESIGN D3, D4, maintainer, 2026-10-15); closes F-019 (PB ReducedModel = bed with "storage": "averaged").

Done.

closed

F-042

Storage domain names

"PCM vertical" → "storage depth" (y, into the storage from the heat-exchange wall) and "PCM horizontal" → "fin gap" (x, between two resolved fins). The old names described a figure, not the geometry; the new ones also fit a future Cartesian channel (“annulus” was rejected for that reason). Coordinates unchanged. Earlier findings keep the old names.

Contract change (DESIGN D12, maintainer, 2026-10-15).

Done.

closed

F-043

SI units and unit twins; variable tiers

Submodels define every variable in SI units; pytes.variables.add_unit_twins, run in BaseModel.build_model, generates [degC], [kW] and [kWh] twins of every [K], [W] and [J] variable (not temperature differences) and rejects twins written by hand. Replaces add_temperature_variables and add_temperature_difference_variables (F-037) and the hand-written Power [kW] and Discharged energy [kWh]; new twins in every model, e.g. Total thermal energy [kWh], Heat loss power [kW]. check_celsius_twins becomes check_unit_twins. Extra variables cost nothing measurable (build and solve times unchanged; PyBaMM evaluates variables on request). Tiers: Heat transfer fluid thermal energy [J] is required; Mean storage temperature [K] dropped (undefined, unused); Pressure drop [Pa] reserved (RESERVED_VARIABLES).

Contract change (DESIGN D12, maintainer, 2026-10-16).

Done.

closed

F-044

Parameter names; time twins

Rule (DESIGN D1): material and fluid properties start with their layer prefix, geometry with the geometric object, never a material prefix; no family prefix. Renamed: "PCM outer radius [m]" → "Storage depth [m]" (geometry, not a PCM property; now the extent of the storage from the tube wall, so the outer radius is R_f + depth, which also fits a future planar geometry; default 6.75 mm, the same geometry) and "Fin spacing [m]" → "Fin pitch [m]" (it is the centre-to-centre distance). Time [min] and Time [h] are now generated twins of Time [s] (UNIT_TWINS allows several twins per unit; twin_name becomes unit_twins).

Contract change (DESIGN D1, D12, maintainer, 2026-10-16).

Done.

closed

F-045

Per-submodel parameters

BaseSubModel.parameters: the parameter names a submodel uses, read from its equations, conditions, variables, events and state scales (parameter_names helper). BaseModel.geometry_parameters: those in the default geometry (domain lengths, which enter through the mesh, not the equations). Both shown by print_submodel_info. A test checks that every parameter in each model’s default set is attributed, except the fluid conductivity and viscosity (hooks). Closes the open metadata item: requires, provides and parameters read from the code; assumptions declared.

Contract addition (DESIGN D3, maintainer, 2026-10-16).

Done.

closed

F-046

Surface flux law interface

SurfaceFluxLaw.heat_flux(fluid, h_f, T_f, T_s) becomes heat_flux(fluid, fluid_state, T_s), with pytes.submodels.FluidState (h, T, mass_flow_rate of the whole device, p reserved, default None): new fields never break existing laws. Geometry a law needs goes to its constructor, from the family. Couplings now also read "Mass flow rate [kg.s-1]". Results unchanged (Newton’s law ignores the flow). Closes the last open item of §4 for Stage B.

Contract change (DESIGN D6, maintainer, 2026-10-16).

Done.

closed

F-047

Stage C: plots, design study, packaging, CI

pytes.plotting (plot extra; not imported by import pytes): plot_summary, plot_power_against_energy, plot_fluid_profiles; power and energy axes include zero without an offset (a held power shows as a line at its value). Notebook examples/design_study.ipynb: a finned-tube unit and a packed bed with the same PCM and storable energy (sized with energy_between) on the same 5 kW test. Packaging: requires-python = ">=3.10,<3.16" (PyBaMM’s range), SciPy declared (used by pytes.validation), classifiers. CI (.github/workflows/tests.yml, not yet run: the repository has no remote): pre-commit; tests on Python 3.10 to 3.15 against the latest PyBaMM release (3.15 non-blocking: PyYAML has no 3.15 wheel and its source build crashes); slow tests; PyBaMM main non-blocking. Locally: 263 tests pass on 3.10 and 3.14. Pre-commit’s ruff pinned to the dev ruff (0.17.0; 0.6.9 formatted differently); its first run with the new pin could not be done here (GitHub blocked). PyBOP example dropped (maintainer).

Stage C (DESIGN §5, D15, D16).

Done. CI runs on every push since the repository was hosted (2026-10-16); 0.1.0a1 and 0.1.0a2 are released.

closed

F-048

Default parameter sets

The families’ default parameter values become the named compositions of pytes.parameter_layers, with values checked against the literature by the maintainer (2026-10-21). Against the earlier flat sets: water at 20 °C (CRC) has density 998.21 kg.m-3 and specific heat capacity 4184.1 J.kg-1.K-1, for 1000 and 4186; aluminium 897 J.kg-1.K-1, for 900; the PCM smoothing width is 0.01 K, for 0.5 K; “Nallusamy2007” flows at 1/30 kg.s-1 (2 l/min), for 0.0331 kg.s-1 from an interstitial velocity; “Raul2018” has its bed cross-section, pi 0.085² m², for a 1 m² placeholder, and 8.2 l/min of Hytherm 600 (0.0985 kg.s-1), for an interstitial velocity of 1 cm.s-1 over that placeholder; the finned-tube default becomes an illustrative one-tube battery (0.02 kg.s-1 of water, PCM 60). With all these changes, the Nallusamy (2007) fluid temperatures fit the measurements as before: 0.9 K to 2.0 K RMS, for 0.9 K to 1.9 K.

None (data, not contract: names, units and signs are unchanged).

The layer values are used. The reference results are regenerated from PyTES by tests/data/make_reference.py into tests/data/reference_regression.npz, a regression test; the prototype comparison of F-027, which needed the earlier finned-tube set, is retired with reference_prototype_d391da0.npz. The experiment tests’ powers are scaled to the new battery.

closed

F-049

Surface flux law

Flow-dependent heat transfer coefficients (issue #6). New option "heat transfer coefficient": "constant" (default, unchanged results) or a Nusselt correlation, "Gnielinski" in the finned tube and "Wakao-Kaguei" in the packed bed (pytes.submodels.NusseltCorrelation, Gnielinski, WakaoKaguei). The correlation constants are part of each law, so there are no new parameter names; the geometry (tube radius and number of tubes; capsule radius and bed area) comes from the family. SurfaceFluxLaw.variables is a new hook, and the coupling now provides "Heat transfer coefficient [W.m-2.K-1]" in every model, plus "Surface Reynolds number", "Surface Prandtl number" and "Surface Nusselt number" with a correlation. "Heat transfer fluid viscosity [Pa.s]" becomes a required name of a heat transfer fluid layer (it was optional, unused). NewtonCooling gains heat_transfer_coefficient(fluid, fluid_state). In the default sets the correlations give: finned tube Re 4244 (transitional), h 2278 W.m-2.K-1 against the constant 1000; Nallusamy2007 Re 18, h 152 against 100, with the fluid temperatures fitting the measurements slightly better over 4 h (0.7 K to 1.8 K RMS, for 0.9 K to 2.0 K); Raul2018 Re 6.9, h 113 against 100, below the fitted range of Wakao and Kaguei (Re > 15).

Contract flaw (additions only: an option value per family, variables, a required fluid name).

Implemented; conformance runs over every option combination (12 variants). The constant coefficients of the default sets are kept, as the default option. Correlations are registered by flow geometry (maintainer, 2026-10-22): pytes.submodels.register_correlation(name, nusselt, flow) adds one, from PyTES or a user, as an option value of the models of that flow (TubeCorrelation, PackedBedCorrelation supply the geometry), and pytes.conformance.check_model checks a model using it (DESIGN D6).

closed

F-050

Options

The option "storage": "resolved" | "averaged" (F-041) is renamed "storage along flow path", with the same values (maintainer, 2026-10-22). "storage" named a slot of model.submodels, not what the option varies; the lumped capsule (issue #5) adds a second option for the variation inside a storage element, "storage element", and the two names now say which direction each one is about. A clean break, as PyTES is in alpha: the old name raises pytes.OptionError naming the new one (BaseModel.renamed_options). Results are unchanged.

Contract flaw (an option name).

Renamed in the models, tests, notebooks, README, getting-started page, contract and DESIGN D3, D4. Breaking changelog fragment.

closed

F-051

Storage, surface flux law

Lumped capsule (issue #5). New packed-bed option "storage element": "resolved" | "lumped" | "lumped with Jeffreson" (the correction was first a separate option, "internal resistance", which needed a lumped capsule; folded into one value so that no combination is invalid, maintainer, 2026-10-23). New LumpedStorage and LumpedGeometry (one enthalpy per capsule, dH/dt = (3 / R) q), providing the same variables as EnthalpyStorage; the ledger coupling is unchanged and energy is conserved exactly. Every storage now provides "<label> thermal conductivity [W.m-1.K-1]". Surface flux laws gain an optional coupled_variables (the variables they read), which the coupling requires; the new Jeffreson law (h_eff = h / (1 + Bi / 5), Bi = h R / k; Jeffreson, AIChE Journal 18, 1972, doi 10.1002/aic.690180225, checked by the maintainer) wraps any Newton law and adds "Effective heat transfer coefficient [W.m-2.K-1]" and "Surface Biot number". Results: on Schumann’s benchmark the lumped sensible bed is as close as the resolved one (1.3 % of the step on 400 cells, all of it upwinding error). Against the resolved capsule in the Nallusamy (2007) charge, the largest error in stored energy, over its change, is 48 % (Jeffreson 20 %) with the real PCM (Bi 7 to 18), 1 % (0.3 %) with k = 10 W.m-1.K-1 and 0.1 % (0.04 %) with k = 100: lumping the default capsules is not accurate.

Contract flaw (additions only: an option, a storage variable, a flux law hook).

Implemented; tests against Schumann, against the resolved capsule as Bi falls, and of Jeffreson’s coefficient; notebook examples/lumped_capsule.ipynb.

closed

F-052

Fluid numerics

A second-order upwind scheme for the fluid (issue #7), the default (maintainer, 2026-10-23): pytes.compat.LimitedFiniteVolume, MUSCL with the van Albada limiter applied to gradients, so it works on non-uniform meshes; the inflow and outflow faces stay first order, and for the gradients the inflow value sits on the inflow face and the last cell’s value on the outflow face, so a steady linear profile is exact. compat.fluid_spatial_method() returns it, and fluid_spatial_method("first order") PyBaMM’s upwinding. Energy is conserved exactly (the flux still telescopes), on uniform and stretched meshes, and no new extremum appears. Measured: on Schumann’s benchmark the largest error in the scaled outlet temperature is 1.9 %, 0.8 %, 0.46 % on 25, 50, 100 cells against 12 %, 7.5 %, 4.3 % with first order (1.3 % on 400), at 1 to 4 times the solve time per cell; on the models the cost per cell is about the same. In the default finned-tube discharge the outlet error falls about 4 times per halving of the cells (0.15 K on 20 cells against 0.82 K). In the default packed-bed charge, on the earlier default mesh (40 by 80), 3.7 K at most and 0.22 K on average in the outlet temperature against 8.0 K and 0.34 K; the packed bed’s default_var_pts docstring had claimed 1.8 K at most, against a first-order reference that was itself far from converged along the bed. Default meshes rebalanced so that each direction contributes about the same error: sharp front 20 by 20 (from 40 by 40; y 0.054 K mean and 0.11 % energy, z 0.066 K and 0.12 %, about 1 s); packed bed 40 by 40 (from 40 by 80; r 0.27 K and 1.8 %, z 0.063 K and 0.33 %, a 5.4 K spike as the front leaves the bed, about 1.5 s); mush unchanged (z 0.061 K and 0.11 %). The Nallusamy (2007) fluid temperatures fit the measurements to 0.9 K to 2.1 K RMS, for 0.9 K to 2.0 K.

Model flaw (numerics: first-order diffusion), and a documentation error.

Implemented as the default; regression reference regenerated; tests (steady linear profile, a bounded and sharper step, energy on a stretched mesh in every model, Schumann with either scheme); notebook examples/advection_scheme.ipynb; DESIGN D16. Breaking changelog fragment (results change).

closed

F-053

Renames

PyTES had no way to deprecate a name: renames were clean breaks (F-036, F-050), and the only record of an old name was BaseModel.renamed_options (F-050), for options. Issue #17 adds pytes.deprecation: one registry of renames (RENAMES), PyTESDeprecationWarning, a module __getattr__ for renamed classes and functions, and the option hook in process_options. BaseModel.renamed_options (unreleased) is replaced by the registry entry for F-050, whose error message is unchanged apart from the model name prefix. Option values are deferred (none renamed yet; the issue asked for them), and so are variable and parameter names: PyBaMM looks them up itself. No result changes.

Contract change (DESIGN D3, D17, maintainer, 2026-10-23).

Done; tests in tests/toolkit/test_deprecation.py; pytest turns the warning into an error; CONTRIBUTING.md “Renaming and deprecating”.

closed

F-054

Names: steps, couplings

Names that said less than, or something other than, what the code does (issue #18, maintainer, 2026-10-23). step.ConstantInputs becomes step.Inlet and step.ConstantPower becomes step.Power: their values need not be constant, and the names now follow pybamm.step (named after what is held). New step.Rest: no flow, with the inlet temperature given or, by default, held where the previous step left it (the initial temperature for a first step), through a new solver input the runner sets from the previous step; until now a rest was ConstantInputs(T_in, 0). LedgerWallCoupling becomes WallCoupling (“ledger” names how energy is accounted, not the physics) and FinPCMCoupling becomes FinStorageCoupling (the material between the fins need not change phase); its default submodel name becomes “Fin-storage coupling”, and its keyword arguments pcm_label and pcm_geometry are kept, as the registry cannot deprecate keyword arguments. The old names are aliases through the registry (F-053): they warn until 0.2.0. Not changed: no mass_flow_rate_below termination (backwards flow is not supported), Target, ConstantPropertyLiquid. No result changes (the reference experiment is unchanged). The rename test for user-facing files no longer reads the changelog’s copy in docs/, which keeps the names of its time.

Contract flaw (names).

Renamed everywhere, aliases in pytes.deprecation.RENAMES; DESIGN D10 and the contract updated; tests for Rest.

closed

F-055

Variables

Outputs for comparison with measurements (issue #14, maintainer’s choices 2026-10-24). Required in every model: Volume flow rate [m3.s-1] (mdot / rho(h_in), twin [l.min-1]), Pumped volume [m3] (its time integral, a solver state, twin [l]), Inlet-outlet temperature difference [K] (T_in - T_out, the example name of D12, which no model provided), Heat transfer fluid velocity [m.s-1] (mean velocity in the flow area; interstitial in a packed bed), Charged energy [J] (-Discharged energy), and Discharged throughput [J] and Charged throughput [J] (time integrals of max(P, 0) and max(-P, 0), solver states). Every storage provides its volume-weighted averages, <label> average temperature [K] and, along the flow path, <label> cross-section average temperature [K]; and Storage average temperature [K] and Storage cross-section average temperature [K] (required) combine all the storage, volume-weighted, so that the mush model (fins and PCM apart) compares with the sharp front (homogenised, local equilibrium). One name in every family (the maintainer weighed “capsule average” for the bed and kept the shared name; in a packed bed the cross-section average is the capsule’s volume average, as the docs say). This reverses F-043’s dropping of Mean storage temperature [K] as undefined: it is now defined. Surface Reynolds number and Surface Prandtl number are provided with the constant coefficient too: NewtonCooling takes an optional flow geometry for reporting only. Pressure drop [Pa] stays reserved. Side effects: the three new solver states move the reference experiment within the solver tolerance (at most 1.5e-5 relative), so tests/data/reference_regression.npz is regenerated; and check_unit_twins compares to relative 1e-10 instead of 1e-12, as the [l.min-1] twin differs from the converted value by a few round-offs (2.6e-12), depending on the order in which the solver evaluates it. No physical result changes.

Contract flaw (additions; a tolerance).

Implemented; tests/models/test_output_variables.py; contract §3 and DESIGN D12 updated.

closed

F-056

Storage, coupling

Packed-bed fluid temperatures were not monotonic during a charge: at every height the fluid rose, fell by 1.2 K to 2.1 K, then rose again (default Nallusamy charge, default mesh), seen in the tutorials’ plots. The storage’s wall condition turned the flux law’s heat flux into a gradient with the conductivity at the extrapolated wall enthalpy, k(H_surf), while the heat that enters the storage is k grad(T) on the wall face, where PyBaMM extrapolates the node values of k. The two agree when k varies smoothly; the default paraffin melts at a sharp point (melting range 0 K) and its conductivity drops from 0.4 to 0.15 W.m-1.K-1, so while the cells by the wall melted the storage took 19 % to 29 % of the flux law’s heat (0.15 of the bed height, 12 to 16 min). Energy stayed exactly conserved (the ledger, D6, telescopes to the actual wall flux), so the conservation tests could not see it, and the coupling’s docstring wrongly said the two “differ slightly”. Evidence that it was numerics, not physics: the dips grew as the capsule mesh was refined (0.5 K on 10 cells to 3 K on 160), vanished with equal solid and liquid conductivities, and vanished with a melting range of 2 K.

Model flaw (numerics).

The wall condition uses boundary_value(k), the conductivity k grad(T) uses on the wall face, so exactly the flux law’s flux enters (EnthalpyStorage). The charge is now monotonic, converging as the capsule mesh is refined (largest dip 0.17 K on 20 cells, 0.04 K on 40, none from 80); energy is conserved as before (3e-8 J of 8.3 MJ). Results move: the Nallusamy (2007) fluid temperatures fit the measurements better at every height (RMS 0.98, 0.68, 1.41 and 1.85 K at a quarter, half, three quarters and the full height, for 1.09, 0.92, 1.63 and 2.05 K); the finned tubes, whose PCM melts over 2 K with conductivities 0.6 and 0.5, move by at most 0.006 K. Regression reference regenerated. Tests: the fluid’s heat equals the flux law’s flux over the capsule surface, and the default charge heats the fluid monotonically at four heights.

closed

Pinning note. HB was pinned at d391da0 on 2026-10-11 (the working tree diffed above), and PB at 023bf58.

Checklist for the side-by-side diff#

Compare, and log every difference:

  1. Variable names, units, and sign conventions (power, energy).

  2. Parameter naming and structure.

  3. Material classes (are they truly identical?) and melting-range smoothing (form and default width).

  4. Conservation approach: is the heat across the fluid-solid coupling computed from the storage side’s discrete energy balance, or as h*A*dT on both sides?

  5. Fluid equation: state variable, discretisation, upwinding workaround, inlet condition, outlet temperature.

  6. Power definition: from enthalpy or mdot*cp*dT.

  7. Fluid inventory: is it in the energy variables? (Finned pipe: yes, at d391da0; see F-008. Both energies are now required.)

  8. Heat transfer coefficient: constant, correlation, or hard-coded.

  9. Liquid fraction: averaging weights over the capsule volume and bed length, versus the finned-pipe definition.

  10. Domain structure: macro/micro layout, secondary domains, naming.

  11. Cut-off events and operating mode.

  12. Default geometry, mesh, spatial methods, solver, and plot variables.

  13. Test coverage: which existing tests have no counterpart in the other repo.