Defects found in IMRv2 at dea31cd, all reproduced with MATLAB R2025a via
tools/gen_imrv2_cases.m and tools/probe_viscosity.m. The eight are listed below;
the original scoping notes are in git history, in a PLAN.md retired in #218.
-
Giesekus and linear PTT cannot be run.
f_call_params.mdispatchesstress6 and 7 and forces spectral collocation for both, but its own input gate rejectsstress > 5. PyIMR implements both. -
The non-Newtonian viscosity suite is non-functional.
nu_model3--7 leaveintf/dintf/ddintfunassigned and raise;nu_model = 2(Carreau-Yasuda) calls a four-argument helper with three arguments and raises. Onlynu_model = 1(Carreau) runs, and it fails its own Newtonian reduction by6.7e-01-- its stress integral is quadratic in the strain rate where the Newtonian term it must reduce to is linear. -
Collapse initialization is a stub for most materials.
f_call_params.mapplies a precursor only for the Zener family; it leaves the initial stress empty for memoryless materials and returns zeros under an explicit% TODO initial max stress for UCM and Oldroyd-B. The flag is accepted and silently ignored. PyIMR implements the precursor for Oldroyd-B and the distributed models, and refuses the flag outright for memoryless materials. -
The collapse precursor locates the maximum by discrete argmax.
f_init_stress.mtakesmax(abs(X(:,1)))over ode23tb output points rather than root-solving the wall velocity.Ris locally quadratic at the maximum and the stress locally linear, so this costs O(sqrt(tol)) in peak position and carries that straight into the initial stress. Upstream'sSzerois-0.1600469117against-0.1599451098here -- a 1.02e-04 offset equivalent to sampling 1.9e-03 before the peak, where the radius is only 2.06e-06 lower. That single number accounts for the whole 1.55e-03 deviation on the pinned collapse-Zener trajectory; injecting upstream's ownSzeroreproduces it at 2.08e-05. PyIMR root-findsv = 0instead, which is O(tol). -
The Mie-Gruneisen branch takes the wrong root of its own density quadratic.
a*mu^2 + b*mu + A = 0has roots tending to0and-1/nogasA -> 0;f_radial_eq.mtakes(-b + sqrt(d))/(2a), which is the-1/nogbranch -- a 32.5% density deficit at ambient pressure, a 48-60% enthalpy error, and a negativec^2. That negativec^2is whyradial = 6(Gilmore/Mie-Gruneisen) returns complex radii, reachingmax|imag(R/R0)| = 4.069without raising: it is the only branch that evaluates the sound speed from the EoS. The branch also omits the stress term fromPb, whichradial = 3and4both include.With the correct root, density and sound speed recover their ambient values (
rho/rho0 - 1 = 4.3e-05,c/c0 - 1 = 2.8e-04), the analytic enthalpy matches bothh ~ P - 1and a direct numerical integral of1/rho, andradial = 5agrees with the independent Tait formradial = 3to 5.2e-04 -- against 4.8e-01 as shipped. Upstream'sradial = 5collapses toR/R0 = 0.0536where its own Tait branch gives0.0821. -
The
radialconstraint is stated three mutually inconsistent ways. -
f_init_stress.muses an undefinedz1in theDe == 0 || De == Infbranch. Unreachable for the memory models that call it, so latent rather than active. -
calc_omega_Ntreats the gas pressure atRmaxas the equilibrium value. This one is in IMR-vanilla rather than IMRv2. It inflates the linearised stiffness byalpha**(-3*kappa)and overpredicts the natural frequency by 42x on the reference case, which is why PyIMR'sdata.natural_frequencyis a reimplementation rather than a port.
These are the reason several PyIMR models are validated by reduction limit rather than against a pinned upstream trajectory: for those models, no working upstream implementation exists to pin against.
Moved here from the package docstring, where four of its claims had gone stale
without anyone noticing — it still said radial = 6 was "NOT supported,
confirmed dead/broken upstream" long after #18 implemented it, and still pointed
at a tests/run_validation.py that #32 split up.
bubtherm = 1 implements IMRv2's elseif bubtherm branch of f_imr_fd.m:
gas-phase thermal PDE, dry gas (kv0 = 0, vapor = 0). With medtherm = 0 the
wall is an isothermal-equivalent clamp (thetadot[-1] = 0). Its Pdot uses bare
P (kappa*P), not (P - Pv) — that is IMRv2's actual equation for this
branch rather than a simplification, and the bubtherm = 0 polytropic branch's
Pdot does use (P - Pv). The two are deliberately not reconciled: they are
genuinely different equations in the source.
medtherm = 1 adds the liquid boundary layer — a stretched exterior grid
(Mt points, Lt controlling the stretching) and an advection + diffusion +
viscous-dissipation right-hand side for Tm. The wall temperature theta[-1]
is not a free state; it is an algebraic boundary value enforcing heat-flux
continuity across the interface, and is solved in closed form (#57): the
residual is a quadratic in sqrt((alpha + beta)^2 + 2*alpha*theta). Upstream
iterates a secant here. thetadot[-1] = 0 and Tmdot[0] = 0 always, because
both slots are algebraic rather than evolved. Forward sensitivities
differentiate the boundary solve.
masstrans = 1 (needs bubtherm = 1, vapor = 1) implements the
if bubtherm && masstrans branch: a wall vapour mass fraction field kv(y, t),
a kv-weighted mixture conductivity and diffusivity, extra mass-transfer terms
in Pdot/Uvel/thetadot, and a kvdot equation. kv[-1] is set
algebraically each RHS call from vapour-liquid equilibrium using a T[-1]
computed from the stale, pre-update kv[-1] — IMRv2's own one-step lag,
replicated exactly rather than reconciled.
With medtherm = 0 and mass transfer on, theta[-1] never evolves, so
T[-1] == 1 identically and no wall solve is needed. With both on, theta[-1]
comes from a coupled root-find (_wall_theta_bw_full) that enforces
vapour-mass-flux continuity alongside heat flux; no closed form exists there,
because the vapour fraction puts Tw inside pvsat. alpha_m in that solve
uses the stale kv[-1] too, same lag. Forward sensitivities cover it.
Zener and QuadraticZener use 4*LAM/Re8 for the acceleration coefficient
where IMRv2 uses 4/Re8. On compressible trajectories with differing
retardation and relaxation times the two differ by roughly 5e-02. IMRv2's own
stress carries -4*LAM/Re8*Rdot/R, so the coefficient it pairs with that stress
is internally inconsistent, and the reduction limit to LinearMaxwell converges
only with the LAM factor restored. Three Zener reference trajectories were
regenerated from PyIMR as a result, and pin regressions rather than
cross-checking upstream (#174, IMRv2#18).