solprop.stepper.tableau#
Explicit Runge-Kutta tableaux with embedded error estimates.
Two order-5(4) pairs are provided: DOPRI5 [1] (the default scheme)
and TSIT5 [2]. Both are first-same-as-last (FSAL), so a step costs six
evaluations of the nonlinear term and the embedded companion supplies the local error
estimate for free (docs/dev/stepper.md sections 2 and 3).
A tableau is data, not behaviour: it carries the nodes, the Butcher matrix, the
propagated weights and the error weights, and it validates its own structure.
check_order_conditions() measures the order the coefficients actually attain,
from the rooted-tree conditions [3]. Applying a tableau to a state is a backend kernel
and does not live here.
Every rational coefficient is written as the exact rational of its reference and evaluated once, at import, so each constant is a single correctly-rounded division and no division happens per step.
References#
J. R. Dormand and P. J. Prince, “A family of embedded Runge-Kutta formulae”, J. Comput. Appl. Math. 6, 19 (1980). doi:10.1016/0771-050X(80)90013-3
Ch. Tsitouras, “Runge-Kutta pairs of order 5(4) satisfying only the first column simplifying assumption”, Comput. Math. Appl. 62, 770 (2011). doi:10.1016/j.camwa.2011.06.002
J. C. Butcher, Numerical Methods for Ordinary Differential Equations, 3rd ed., Wiley (2016), chapter 3.
Module Attributes
Highest order for which |
|
Dormand-Prince RK5(4)7M, the default scheme. |
|
DOPRI5's dense-output weights |
|
Tsitouras RK5(4), the alternative scheme. |
Functions
|
Measure the order the tableau's coefficients attain. |
|
Return a tableau's dense-output weights, or |
|
Contract an accepted DOPRI5 step into its continuous extension's coefficients. |
|
Evaluate a continuous extension at a fraction of its step. |
Classes
|
The order a tableau's coefficients attain, measured tree by tree. |
|
An explicit Runge-Kutta tableau with an embedded companion. |
- solprop.stepper.tableau.DOPRI5 = Tableau(name='DOPRI5', nodes=array([0. , 0.2 , 0.3 , 0.8 , 0.88888889, 1. , 1. ]), matrix=array([[ 0. , 0. , 0. , 0. , 0. , 0. , 0. ], [ 0.2 , 0. , 0. , 0. , 0. , 0. , 0. ], [ 0.075 , 0.225 , 0. , 0. , 0. , 0. , 0. ], [ 0.97777778, -3.73333333, 3.55555556, 0. , 0. , 0. , 0. ], [ 2.95259869, -11.59579332, 9.82289285, -0.29080933, 0. , 0. , 0. ], [ 2.84627525, -10.75757576, 8.90642272, 0.27840909, -0.2735313 , 0. , 0. ], [ 0.09114583, 0. , 0.4492363 , 0.65104167, -0.32237618, 0.13095238, 0. ]]), solution_weights=array([ 0.09114583, 0. , 0.4492363 , 0.65104167, -0.32237618, 0.13095238, 0. ]), error_weights=array([ 0.00123264, 0. , -0.00425277, 0.03697917, -0.0508638 , 0.04190476, -0.025 ]), order=5, embedded_order=4, first_same_as_last=True)#
Dormand-Prince RK5(4)7M, the default scheme.
- solprop.stepper.tableau.DOPRI5_DENSE = array([-1.12701757, 0. , 2.67542448, -5.68552696, 3.52193237, -1.76728126, 2.38246893])#
DOPRI5’s dense-output weights
d, Hairer, Noersett and Wanner’s (II.6.12).With
r1 = y0,r2 = y1 - y0,r3 = h k1 - r2,r4 = r2 - h k7 - r3andr5 = h sum_i d_i k_i, the fourth-order continuous extension atthetain[0, 1]isr1 + theta (r2 + (1 - theta) (r3 + theta (r4 + (1 - theta) r5)))(continuous_extension(); SEP 14 phase 3).
- solprop.stepper.tableau.MAX_CHECKED_ORDER = 5#
Highest order for which
check_order_conditions()knows the rooted trees.
- class solprop.stepper.tableau.OrderConditionReport(tolerance, solution_residuals, embedded_residuals, solution_order, embedded_order, max_residual, matches_declared_orders)[source]#
Bases:
objectThe order a tableau’s coefficients attain, measured tree by tree.
- Attributes:
- tolerancefloat
Residual below which a condition counts as satisfied, dimensionless.
- solution_residualscollections.abc.Mapping
Absolute residual
|sum_i b_i Phi_i(t) - 1/gamma(t)|of every rooted tree through orderMAX_CHECKED_ORDER, keyed by tree label, for the propagated weights. Dimensionless.- embedded_residualscollections.abc.Mapping
The same for the embedded companion
b_hat.- solution_orderint
Highest order, at most
MAX_CHECKED_ORDER, whose conditions the propagated weights all satisfy withintolerance.- embedded_orderint
The same for the embedded companion.
- max_residualfloat
Largest residual among the conditions the tableau declares it satisfies (up to
Tableau.orderfor the propagated weights andTableau.embedded_orderfor the companion). Independent oftolerance, so it is a measurement rather than a restatement of the threshold.- matches_declared_ordersbool
Whether the attained orders equal the declared ones.
- solprop.stepper.tableau.TSIT5 = Tableau(name='Tsit5', nodes=array([0. , 0.161 , 0.327 , 0.9 , 0.98002554, 1. , 1. ]), matrix=array([[ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00], [ 1.61000000e-01, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00], [-8.48065549e-03, 3.35480655e-01, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00], [ 2.89715306e+00, -6.35944849e+00, 4.36229543e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00], [ 5.32586483e+00, -1.17488836e+01, 7.49553934e+00, -9.24950664e-02, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00], [ 5.86145544e+00, -1.29209693e+01, 8.15936790e+00, -7.15849733e-02, -2.82690504e-02, 0.00000000e+00, 0.00000000e+00], [ 9.64607668e-02, 1.00000000e-02, 4.79889650e-01, 1.37900857e+00, -3.29006952e+00, 2.32471052e+00, 0.00000000e+00]]), solution_weights=array([ 0.09646077, 0.01 , 0.47988965, 1.37900857, -3.29006952, 2.32471052, 0. ]), error_weights=array([-0.00178001, -0.00081643, 0.00788088, -0.14471101, 0.58235717, -0.45808211, 0.01515152]), order=5, embedded_order=4, first_same_as_last=True)#
Tsitouras RK5(4), the alternative scheme.
- class solprop.stepper.tableau.Tableau(name, nodes, matrix, solution_weights, error_weights, order, embedded_order, first_same_as_last)[source]#
Bases:
objectAn explicit Runge-Kutta tableau with an embedded companion.
The stage derivatives are
k_i = f(z + c_i h, y + h sum_j a_ij k_j); the step isy_new = y + h sum_i b_i k_iand the local error estimate ise = h sum_i (b_i - b_hat_i) k_i.Equality is by identity (
eq=False): the fields are arrays, for which==is elementwise and would make a generated__eq__raise.- Parameters:
- namestr
Name of the scheme, used in error messages.
- nodesnumpy.ndarray
Stage abscissae
cof shape(n_stages,), as fractions of the step;c[0]is 0 for an explicit method.- matrixnumpy.ndarray
Butcher matrix
Aof shape(n_stages, n_stages), strictly lower triangular and dimensionless.- solution_weightsnumpy.ndarray
Weights
bof shape(n_stages,)of the propagated solution, which is the one of orderorder. Dimensionless, summing to 1.- error_weightsnumpy.ndarray
Weights
b - b_hatof shape(n_stages,)of the error estimate, dimensionless, summing to 0. The difference is stored rather thanb_hatbecause it is the only combination ever used, and forming it once avoids a cancellation between two nearly equal vectors at every step.- orderint
Order of the propagated solution.
- embedded_orderint
Order of the embedded companion
b_hat, which sets the controller exponent.- first_same_as_lastbool
Whether the last stage derivative equals the first derivative of the next step (FSAL), so it can be reused instead of recomputed.
- Raises:
- StepperError
If the shapes disagree, an entry is not finite,
Ais not strictly lower triangular, a row ofAdoes not sum to its node,bdoes not sum to 1,b - b_hatdoes not sum to 0 or is zero throughout, the orders are not0 < embedded < order, or the FSAL flag is set but the last row ofAis notb.
Notes
Two transcription errors are easy to make: swapping
bandb_hat, which propagates the lower-order solution, and storing an error vector in the slot labelled as the higher-order weights. The two sum rules checked here catch both: an error vector does not sum to 1 and a weight vector does not sum to 0. Seedocs/dev/stepper.mdsection 10.Examples
>>> from solprop.stepper import DOPRI5 >>> DOPRI5.n_stages, DOPRI5.order, DOPRI5.embedded_order (7, 5, 4) >>> DOPRI5.first_same_as_last True >>> bool(abs(DOPRI5.embedded_weights.sum() - 1.0) < 1e-15) True
- property embedded_weights: Float[ndarray, 'n_stages'][source]#
Return the weights of the embedded companion.
- Returns:
- numpy.ndarray
Weights
b_hat = b - (b - b_hat)of shape(n_stages,), dimensionless, summing to 1.
- error_weights: Float[ndarray, 'n_error']#
- matrix: Float[ndarray, 'n_rows n_cols']#
- property n_stages: int[source]#
Return the number of stages, counting the reused FSAL stage.
- Returns:
- int
Number of stages.
- nodes: Float[ndarray, 'n_stages']#
- solution_weights: Float[ndarray, 'n_solution']#
- solprop.stepper.tableau.check_order_conditions(tableau, tolerance=1e-15)[source]#
Measure the order the tableau’s coefficients attain.
A tableau of order
psatisfiessum_i b_i Phi_i(t) = 1/gamma(t)for every rooted treetof order up top. There are 17 such trees through order 5. The check is applied to the propagated weights and to the embedded companion, so the labelling of the pair is verified rather than assumed, which catches a swapped or mislabelled pair.This function reports; it does not raise. A caller that wants a hard failure asserts on
matches_declared_orders.- Parameters:
- tableauTableau
The tableau to measure.
- tolerancefloat, optional
Residual below which a condition counts as satisfied, dimensionless. The default 1e-15 is above the worst residual of either shipped tableau (8.0e-16, in Tsit5’s order-5 conditions, whose coefficients are published decimals rather than exact rationals) and eleven orders below the residual of an order that is genuinely not attained (8.7e-4 for both embedded companions at order 5).
- Returns:
- OrderConditionReport
The residuals, the attained orders and whether they match the declared ones.
Examples
>>> from solprop.stepper import DOPRI5, check_order_conditions >>> report = check_order_conditions(DOPRI5) >>> report.solution_order, report.embedded_order (5, 4) >>> report.matches_declared_orders True >>> bool(report.max_residual < 1e-15) True
- solprop.stepper.tableau.continuous_extension(tableau)[source]#
Return a tableau’s dense-output weights, or
Noneif it has none here.- Parameters:
- tableauTableau
The scheme.
- Returns:
- numpy.ndarray or None
DOPRI5_DENSEfor DOPRI5;Nonefor any other tableau.
Examples
>>> from solprop.stepper.tableau import DOPRI5, TSIT5, continuous_extension >>> continuous_extension(DOPRI5).shape, continuous_extension(TSIT5) ((7,), None)
- solprop.stepper.tableau.extension_coefficients(tableau, start, stages, dz)[source]#
Contract an accepted DOPRI5 step into its continuous extension’s coefficients.
- Parameters:
- tableauTableau
DOPRI5, the scheme with a continuous extension here.
- startnumpy.ndarray
The state at the step’s start,
y0.- stagessequence of numpy.ndarray
The step’s stage derivatives
k_i, one per stage.- dzfloat
The step
h.
- Returns:
- list of numpy.ndarray
[y0, y1 - y0, h k1 - (y1 - y0), (y1 - y0) - h k7 - (h k1 - (y1 - y0)), h sum_i d_i k_i], withy1 = y0 + h sum_i b_i k_i.
- Raises:
- ValueError
If the tableau has no continuous extension here.
Examples
A constant derivative extends linearly: half a step of
y' = 2from 1 is 2.>>> import numpy as np >>> from solprop.stepper.tableau import ( ... DOPRI5, extension_coefficients, extension_value) >>> stages = [np.array([2.0])] * 7 >>> coefficients = extension_coefficients(DOPRI5, np.array([1.0]), stages, 1.0) >>> extension_value(coefficients, 0.5).round(12).tolist() [2.0]
- solprop.stepper.tableau.extension_value(coefficients, theta)[source]#
Evaluate a continuous extension at a fraction of its step.
- Parameters:
- coefficientssequence of numpy.ndarray
From
extension_coefficients().- thetafloat
The fraction of the step, in
[0, 1].
- Returns:
- numpy.ndarray
r1 + theta (r2 + (1 - theta) (r3 + theta (r4 + (1 - theta) r5))).