Resources Numerical accuracy Calculation policy

Fermentation (Monod kinetics)

Biomass, substrate, product and volume over time in a batch or fed-batch vessel.

How the precision is kept

The four equations are integrated by the adaptive Runge-Kutta routine rk45 of numeric.js, restarted on each of n equal output intervals (200 by default) with relative tolerance 1e-9 and absolute tolerance 1e-12. Those tolerances set the accuracy of the curves, not the 64-bit arithmetic. Substrate is treated as zero in the rate once it goes negative.

Calculations under this policy

1 calculation in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

process/batch.js CadCraft 0.8.2 64-bit 1 calculation Reviewed

Tests tools/process-test/test-link.mjs, tools/process-test/test-draw4.mjs

Monod growth in a batch or fed-batch vessel

64-bit

Concentrations of biomass, substrate and product, the volume, and their masses at each output time.

μ = μmax × S ÷ (Ks + S); dX/dt = (μ − kd) X − D X; dS/dt = −(μ ÷ Yxs + m) X + D (Sf − S); dP/dt = (α μ + β) X − D P; dV/dt = F; D = F ÷ V

64-bit throughout; the integrator is asked for rtol 1e-9 and atol 1e-12 on each of the n output intervals. S is clamped at 0 inside the rate and uptake stops when S is not above 0. Masses are concentration × volume at each output time.

  • process/batch.js
  • lines 83 to 100
  • CadCraft 0.8.2
/**
 * Monod kinetics, with maintenance, death and product formation (Luedeking–Piret), in a batch (F = 0) or fed-batch vessel.
 * par = { mumax (1/h), Ks (g/L), Yxs (g/g), m? (g/g/h), kd? (1/h), alpha? , beta? (Luedeking–Piret), X0, S0, P0?, V0, F? (L/h), Sf? (g/L), Feed after tFeed? }
 * → { t[], X[], S[], P[], V[], mass: {X,S,P} } over [0, tEnd] (hours).
 */
export function fermentation(par, { tEnd, n = 200 } = {}) {
  const { mumax, Ks, Yxs, m = 0, kd = 0, alpha = 0, beta = 0, F = 0, Sf = 0, tFeed = 0 } = par;
  const f = (t, y) => {
    const [X, S, P, V] = y, Sx = Math.max(S, 0), mu = mumax * Sx / (Ks + Sx), Fi = t >= tFeed ? F : 0, D = Fi / V;
    const dX = (mu - kd) * X - D * X, dS = -(mu / Yxs + m) * X * (Sx > 0 ? 1 : 0) + D * (Sf - S), dP = (alpha * mu + beta) * X - D * P;
    return [dX, dS, dP, Fi];
  };
  const out = { t: [0], X: [par.X0], S: [par.S0], P: [par.P0 || 0], V: [par.V0] };
  let y = [par.X0, par.S0, par.P0 || 0, par.V0];
  for (let i = 1; i <= n; i++) { const r = rk45(f, y, (i - 1) * tEnd / n, i * tEnd / n, { rtol: 1e-9, atol: 1e-12 }); y = r.y; out.t.push(i * tEnd / n); out.X.push(y[0]); out.S.push(y[1]); out.P.push(y[2]); out.V.push(y[3]); }
  out.mass = { X: out.X.map((x, i) => x * out.V[i]), S: out.S.map((x, i) => x * out.V[i]), P: out.P.map((x, i) => x * out.V[i]) };
  return out;
}

In detail

Method
Monod growth with maintenance, death, Luedeking-Piret product formation and dilution by the feed, integrated with rk45.
Number types
64-bit numbers in plain arrays.
Tolerances
rtol 1e-9 and atol 1e-12 passed to rk45 for every output interval.
Limits
Substrate uptake is switched off when the substrate is not above zero, which makes the right-hand side discontinuous there. The dilution rate divides by the volume without a guard.

Approach: 64-bit, stated limit

The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.

Every policy that takes this approach