The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.

The problem in one sentence

Two ways of writing the same rate law can give the same value but a different derivative in floating point: the one with a state in the denominator of a denominator breaks down when that state gets tiny.

Michaelis-Menten

The Michaelis-Menten rate of an enzyme with substrate concentration S:

\[v = \frac{v_{max} \, S}{K_m + S}\]

Dividing numerator and denominator by S gives a form often found in model code:

\[v = \frac{v_{max}}{1 + K_m / S}\]

Mathematically both are the same function, and its derivative

\[\frac{dv}{dS} = \frac{v_{max} \, K_m}{(K_m + S)^2}\]

is finite for every S >= 0.

What goes wrong at tiny S

In ODE and DAE models concentrations can become very small, e.g. S = 1e-200, or exactly 0. For the second form:

At S = 0 exactly, Km / 0 = Inf, the value is still vmax / Inf = 0, and the derivative is again NaN.

Difference quotients do not see this, because they only use values. It shows up as soon as a Jacobian comes from automatic differentiation, e.g. a jac function for ida() / cvode() built with jacobian().

What ast2ast reports

f <- function(S) {
  argtypes(S |> type(vec(double)))
  rate <- function(s) {
    argtypes(s |> type(vec(double)))
    returntype(vec(double))
    vmax <- 2
    Km <- 0.5
    return(vmax / (1 + Km / s))
  }
  return(jacobian(rate, S))
}
fcpp <- ast2ast::translate(f)
fcpp(c(1e-200, 0.3))
Error: In 'return(vmax/(1 + Km/s))': jacobian: d f[1] / d x[1] is not finite,
although f[1] is. The first non-finite derivative appears in
'return(vmax/(1 + Km/s))'. Typical cause: a division by a (nearly) zero state,
e.g. vmax / (1 + K / c); the same value as vmax * c / (c + K) has a finite
derivative.

The check runs when a Jacobian contains a non-finite entry while the function value is finite: one extra sweep finds the first line where a derivative stops being finite. If the function value itself is NaN, the Jacobian is NaN too and no error is raised, as in R.

The fix: multiply through

Multiply numerator and denominator by the state that sits in the inner denominator:

return(vmax * s / (Km + s))

The value is the same, and S now only appears multiplied, so nothing becomes huge: at S = 1e-200 and at S = 0 the derivative is just vmax / Km.

     [,1]   [,2]
[1,]    4 0.0000
[2,]    0 1.5625

Rule of thumb

If a state sits in the denominator of a denominator, multiply numerator and denominator by that state.

With several substrates, multiply by all of them:

# vmax / (1 + K1 / a + K2 / b)
vmax * a * b / (a * b + K1 * b + K2 * a)

# vmax / (1 + K1 / a + K2 / b + K1 * K2 / (a * b))   (random-order bi-substrate)
vmax * a * b / (a * b + K1 * b + K2 * a + K1 * K2)

The multiplied-through form is usually the textbook form anyway; the form with the double fraction tends to come from code that was written for values only.

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.