Tutorials › Quantitative Systems Pharmacology (QSP) › Unit Tests for Equilibria and Limiting Cases

Writing and Debugging a Model · Tutorial 82 of 799

Unit Tests for Equilibria and Limiting Cases

Learn to test equilibrium and limiting-case behavior in a turnover model, including parameter sensitivities, with explicit tolerances and interpretable diagnostics.

Intermediate 10 min read

What You'll Learn

  • Define the turnover model and distinguish its equilibrium from a transient state.
  • Test the zero-input equilibrium directly using the model’s right-hand side.
  • Check what happens as the elimination rate approaches zero, including when no equilibrium exists.
  • Derive and test steady-state sensitivities to the input and elimination rates.
  • Set absolute and relative tolerances that respect units and numerical scale.
  • Recognize why a passing unit test does not establish that an integrated trajectory is accurate.

Why Test Equilibria and Limiting Cases?

An ordinary differential equation (ODE) function can run without errors and still encode the wrong biology or algebra. A sign error, a missing input term, or an incorrect state index may produce plausible trajectories under some parameter settings. Unit tests target simple situations in which the model must satisfy known properties, independently of a full simulation. Here the tests concern a one-state turnover model: its zero-input equilibrium, its behavior when a rate is zero or approaches zero, and derivatives of its steady state with respect to parameters.

The model is deliberately simple enough to derive its expected results exactly. The same testing logic can then be applied to components of larger mechanistic models. These tests check the right-hand side and steady-state calculations; they do not replace checks of initial conditions, conservation, or ODE integration.

Model, Equilibrium, and Test Tolerances

Let \(R(t)\) be a response amount or concentration, and let \(k_{\mathrm{in}}\) be a constant input or production rate. Let \(k_{\mathrm{out}}\) be a first-order elimination rate constant. The turnover model is

$$ \frac{dR}{dt}=f(R;k_{\mathrm{in}},k_{\mathrm{out}}) =k_{\mathrm{in}}-k_{\mathrm{out}}R. $$

For example, if \(R\) is measured in mg/L and time in hours, \(k_{\mathrm{in}}\) has units mg/L/h and \(k_{\mathrm{out}}\) has units h\(^{-1}\). The right-hand side \(f\) therefore has units mg/L/h. The usual turnover assumptions here are that both rates are constant in time, the loss is proportional to the current \(R\), and the model is considered for \(k_{\mathrm{in}}\geq 0\), \(k_{\mathrm{out}}\geq 0\), and \(R\geq 0\).

Definition: An equilibrium \(R^*\) is a state at which the right-hand side is zero: \(f(R^*;k_{\mathrm{in}},k_{\mathrm{out}})=0\). If \(k_{\mathrm{out}}>0\), the model has the unique equilibrium \(R^*=k_{\mathrm{in}}/k_{\mathrm{out}}\). When \(k_{\mathrm{in}}=0\), this equilibrium is \(R^*=0\).

A unit test compares a computed value with an expected value. Floating-point calculations should generally allow a small discrepancy rather than require exact equality. A useful rule is to pass a test when the absolute difference is no larger than an absolute tolerance plus a relative tolerance times the magnitude of the expected value:

$$ |y_{\mathrm{test}}-y_{\mathrm{expected}}| \leq \mathrm{atol}+\mathrm{rtol}\,|y_{\mathrm{expected}}|. $$

The absolute tolerance protects comparisons near zero; the relative tolerance scales the allowance for larger values. Choose tolerances for the quantity being tested and state its units. A tolerance appropriate for \(R\) need not be appropriate for \(dR/dt\) or for a parameter derivative, since their units differ. Tolerances should be tight enough to expose meaningful errors, but not so tight that harmless roundoff causes failures.

Test the Zero-Input Equilibrium

In this model, zero input means setting \(k_{\mathrm{in}}=0\). For \(k_{\mathrm{out}}>0\), the expected equilibrium is \(R^*=0\). A direct test evaluates the right-hand side at that state and checks that it is zero within a tolerance. It can also evaluate the right-hand side at a positive state: with zero input, a positive response must decline, not grow. That second check is useful because a faulty implementation might return zero at \(R=0\) while still using the wrong sign for the loss term.

Worked Example: Zero-Input Equilibrium and Loss Direction

Suppose \(R\) is measured in mg/L, \(k_{\mathrm{in}}=0\) mg/L/h, and \(k_{\mathrm{out}}=0.25\) h\(^{-1}\). The expected equilibrium is

$$ R^*=\frac{0}{0.25}=0\ \mathrm{mg/L}. $$

At the expected equilibrium, the right-hand side should be

$$ f(0;0,0.25)=0-(0.25)(0)=0\ \mathrm{mg/L/h}. $$

For a unit test, set \(\mathrm{atol}=10^{-12}\) mg/L/h and \(\mathrm{rtol}=10^{-10}\), then check that the calculated right-hand side at \(R=0\) satisfies the tolerance rule. Because the expected value is zero, the relative-tolerance contribution is also zero; the absolute tolerance determines the allowed discrepancy.

As a sign check, evaluate the function at \(R=4\) mg/L. The expected value is \(f=0-(0.25)(4)=-1\) mg/L/h, so the response declines. A result of \(+1\) mg/L/h indicates a likely sign error in the loss term. This test checks a direction as well as a single equilibrium value.

The distinction between an equilibrium test and a test at an arbitrary state matters. For \(k_{\mathrm{in}}>0\), substituting \(R=0\) gives \(f=k_{\mathrm{in}}>0\), not zero: the response rises from zero toward its positive equilibrium. Testing that \(R=0\) is an equilibrium for every parameter setting would therefore be incorrect.

Zero-Rate Limits and the Boundary at No Elimination

The equilibrium formula \(R^*=k_{\mathrm{in}}/k_{\mathrm{out}}\) requires \(k_{\mathrm{out}}>0\). It must not be evaluated by dividing by zero. At \(k_{\mathrm{out}}=0\), the model instead has \(dR/dt=k_{\mathrm{in}}\). If \(k_{\mathrm{in}}>0\), the response increases linearly and there is no equilibrium. If both rates are zero, the derivative is zero at every state, so every \(R\) is an equilibrium; there is no unique equilibrium selected by this model.

A limiting-case test asks whether the model behaves consistently as a rate approaches zero. For constant rates, the solution from an initial state \(R(0)=R_0\), when \(k_{\mathrm{out}}>0\), is

$$ R(t)=R_0e^{-k_{\mathrm{out}}t} +\frac{k_{\mathrm{in}}}{k_{\mathrm{out}}} \left(1-e^{-k_{\mathrm{out}}t}\right). $$

Although this expression contains division by \(k_{\mathrm{out}}\), its limit as \(k_{\mathrm{out}}\) tends to zero is finite. Since \(1-e^{-k_{\mathrm{out}}t}\) is approximately \(k_{\mathrm{out}}t\) for small \(k_{\mathrm{out}}t\), the second term tends to \(k_{\mathrm{in}}t\), and the first tends to \(R_0\). Thus the zero-elimination limiting trajectory is \(R_0+k_{\mathrm{in}}t\), in agreement with the ODE when \(k_{\mathrm{out}}=0\). This limiting result is a useful check on the model and on code that evaluates the solution formula; the subtraction in \(1-e^{-k_{\mathrm{out}}t}\) can also lose numerical precision when its argument is very small.

Worked Example: Checking the Zero-Elimination Limit

Take \(R_0=5\) mg/L, \(k_{\mathrm{in}}=0.6\) mg/L/h, and \(t=2\) h. At exactly \(k_{\mathrm{out}}=0\), the expected response is

$$ R(2)=5+(0.6)(2)=6.2\ \mathrm{mg/L}. $$

For comparison, with \(k_{\mathrm{out}}=0.2\) h\(^{-1}\), the positive-rate solution gives \(R^*=0.6/0.2=3\) mg/L and

$$ R(2)=5e^{-0.4}+3(1-e^{-0.4}) =3+2e^{-0.4} \approx 4.3406\ \mathrm{mg/L}. $$

The response at the positive rate is lower than the zero-elimination limit because some of the response is being removed. A test of the zero-rate branch should check the expected value 6.2 mg/L, for example with \(\mathrm{atol}=10^{-10}\) mg/L and \(\mathrm{rtol}=10^{-9}\). A separate check of the right-hand side at \(k_{\mathrm{out}}=0\), \(R=5\) mg/L should return \(0.6\) mg/L/h. The test should not demand a finite steady state when \(k_{\mathrm{in}}>0\) and \(k_{\mathrm{out}}=0\).

A complementary zero-input limit is \(k_{\mathrm{in}}\to 0\) with \(k_{\mathrm{out}}>0\). The equilibrium \(k_{\mathrm{in}}/k_{\mathrm{out}}\) then tends to zero. The trajectory tends toward \(R_0e^{-k_{\mathrm{out}}t}\), which decays to the zero-input equilibrium. Testing this limit helps expose code that mistakenly leaves a nonzero production term when the input parameter is set to zero.

Derive and Test Steady-State Derivatives

Steady-state derivatives describe how the equilibrium changes when a parameter changes. They are not derivatives of the transient state with respect to time. For \(k_{\mathrm{out}}>0\), start from the equilibrium condition \(k_{\mathrm{in}}-k_{\mathrm{out}}R^*=0\). Solving gives \(R^*=k_{\mathrm{in}}/k_{\mathrm{out}}\). Differentiating this expression with respect to each parameter gives the expected sensitivities:

$$ \frac{\partial R^*}{\partial k_{\mathrm{in}}} =\frac{1}{k_{\mathrm{out}}}, \qquad \frac{\partial R^*}{\partial k_{\mathrm{out}}} =-\frac{k_{\mathrm{in}}}{k_{\mathrm{out}}^2}. $$

The first derivative is positive when \(k_{\mathrm{out}}>0\): increasing the input raises the equilibrium. The second derivative is nonpositive for \(k_{\mathrm{in}}\geq 0\) and \(k_{\mathrm{out}}>0\); it is strictly negative when \(k_{\mathrm{in}}>0\), and equals zero when \(k_{\mathrm{in}}=0\). In particular, increasing the elimination rate does not change the zero equilibrium when there is no input. These sign and boundary checks should be part of the tests, not assumed from intuition alone.

The same derivatives can be obtained by differentiating the equilibrium condition. For example, differentiating \(k_{\mathrm{in}}-k_{\mathrm{out}}R^*=0\) with respect to \(k_{\mathrm{out}}\), while holding \(k_{\mathrm{in}}\) fixed, gives \(-R^*-k_{\mathrm{out}}(\partial R^*/\partial k_{\mathrm{out}})=0\). Therefore \(\partial R^*/\partial k_{\mathrm{out}}=-R^*/k_{\mathrm{out}}=-k_{\mathrm{in}}/k_{\mathrm{out}}^2\). This provides an independent algebraic check of the quotient-rule result.

Worked Example: Sensitivity Checks at a Positive Equilibrium

Let \(k_{\mathrm{in}}=0.8\) mg/L/h and \(k_{\mathrm{out}}=0.4\) h\(^{-1}\). The equilibrium is \(R^*=0.8/0.4=2\) mg/L. The expected derivatives are

$$ \frac{\partial R^*}{\partial k_{\mathrm{in}}} =\frac{1}{0.4}=2.000\ \mathrm{h}, \qquad \frac{\partial R^*}{\partial k_{\mathrm{out}}} =-\frac{0.8}{(0.4)^2} =-5.000\ \mathrm{mg/L}\cdot\mathrm{h}. $$

The units follow from dividing the equilibrium units by the units of the parameter being varied. For a numerical cross-check, use a centered difference for \(k_{\mathrm{out}}\) with step \(h=0.001\) h\(^{-1}\):

$$ \frac{R^*(0.8,0.401)-R^*(0.8,0.399)}{2h} =\frac{0.8/0.401-0.8/0.399}{0.002} \approx -5.00003\ \mathrm{mg/L}\cdot\mathrm{h}. $$

This agrees with \(-5.000\) to the displayed precision. A test comparing this finite difference with the analytic derivative might use \(\mathrm{atol}=10^{-6}\) mg/L·h and \(\mathrm{rtol}=10^{-5}\). The chosen step and tolerance must be recorded together: a step that is too small can amplify roundoff, while one that is too large can introduce appreciable approximation error. The analytic derivative remains the expected value; the finite difference is a diagnostic cross-check, not a replacement for it.

Designing Tests and Diagnosing Failures

A compact test suite should cover distinct properties rather than repeat the same parameter setting. The following sequence is appropriate for this model:

1
Check the right-hand side.
Use hand-calculated values at a zero-input equilibrium and at a positive state. Confirm both the zero derivative at equilibrium and the expected direction of change away from it.
2
Check parameter boundaries.
Set \(k_{\mathrm{in}}=0\) and \(k_{\mathrm{out}}=0\) separately. Confirm that zero input leads to decay toward zero when elimination is positive, while zero elimination with positive input gives constant accumulation rather than a finite equilibrium.
3
Check the steady-state calculation.
For positive \(k_{\mathrm{out}}\), compare the computed equilibrium with \(k_{\mathrm{in}}/k_{\mathrm{out}}\), using tolerances in the units of \(R\).
4
Check derivatives and signs.
Compare sensitivities with their analytic values at positive rates, then check boundary cases such as \(k_{\mathrm{in}}=0\), where the derivative with respect to \(k_{\mathrm{out}}\) is zero.

Failures can help localize the bug. A nonzero derivative at the zero-input equilibrium suggests a residual input, a wrong state value, or an indexing error. A positive derivative at a positive state with no input suggests the sign of the loss term may be reversed. A finite steady-state value returned at \(k_{\mathrm{out}}=0\) with positive \(k_{\mathrm{in}}\) suggests an invalid division or an inappropriate special case. A derivative with the wrong units or sign can indicate that a parameter was confused with a state or that the derivative formula was implemented incorrectly.

A unit test of the right-hand side is different from integrating the ODE and comparing a trajectory with an analytic solution. The earlier material on testing ODE functions against analytic solutions addresses trajectory comparisons; equilibrium and limiting-case tests check targeted properties, often with less dependence on solver settings. Neither establishes that every possible parameter combination is correct. Tests should use multiple parameter values, including boundary cases, and should be supplemented by inspection of units and model equations.

A further limitation is that these checks use a one-state, constant-rate model with a known equilibrium. In a coupled model, an equilibrium may require solving a system of equations, and a zero-rate boundary may produce multiple equilibria or no equilibrium. The principles remain useful, but expected results must be derived for the model actually being tested. Do not apply \(k_{\mathrm{in}}/k_{\mathrm{out}}\) to a model whose input or loss terms have a different structure.

Common pitfall: Treating \(R^*=k_{\mathrm{in}}/k_{\mathrm{out}}\) as valid at \(k_{\mathrm{out}}=0\), or saying that increasing \(k_{\mathrm{out}}\) always lowers the equilibrium. The formula requires \(k_{\mathrm{out}}>0\); at zero elimination and positive input there is no equilibrium, while at zero input and positive elimination the equilibrium is zero and its derivative with respect to \(k_{\mathrm{out}}\) is zero. Check the parameter conditions before interpreting a derivative or evaluating a formula.

Check Your Understanding

Work through each question, then compare with the solutions.

  1. For \(k_{\mathrm{in}}=0.3\) mg/L/h and \(k_{\mathrm{out}}=0.1\) h\(^{-1}\), find the equilibrium and the right-hand side at that equilibrium.
  2. With \(k_{\mathrm{in}}=0.5\) mg/L/h and \(k_{\mathrm{out}}=0\), is there a finite equilibrium? What is \(dR/dt\)?
  3. For \(k_{\mathrm{in}}=0\) and \(k_{\mathrm{out}}>0\), what is \(\partial R^*/\partial k_{\mathrm{out}}\), and why?
  4. Starting from \(R_0=2\) mg/L, find the zero-elimination limiting response after 3 h when \(k_{\mathrm{in}}=0.4\) mg/L/h.
  5. Why is an absolute tolerance needed when testing whether the right-hand side at an expected equilibrium is zero?

Solutions. (1) \(R^*=0.3/0.1=3\) mg/L, and \(f=0.3-(0.1)(3)=0\) mg/L/h. (2) There is no finite equilibrium because the derivative is the positive constant \(0.5\) mg/L/h, so \(R\) increases linearly. (3) The derivative is \(-k_{\mathrm{in}}/k_{\mathrm{out}}^2=0\); changing the elimination rate cannot change the zero equilibrium when there is no input. (4) \(R(3)=R_0+k_{\mathrm{in}}t=2+(0.4)(3)=3.2\) mg/L. (5) At an expected value of zero, the relative-tolerance term \(\mathrm{rtol}|y_{\mathrm{expected}}|\) is zero, so only an absolute tolerance allows a small roundoff discrepancy.

Clinical Biostats