Skip to main content

NumericalJacobian

Struct NumericalJacobian 

Source
pub struct NumericalJacobian<S> { /* private fields */ }
Expand description

Wrap any OdeSystem so that crate::ode::Rosenbrock23 can integrate it without a hand-coded Jacobian.

§The problem this solves

OdeSystem::jacobian has a default body that is unimplemented!(), so a system that does not override it panics the moment a stiff solver asks for a Jacobian — inside Rosenbrock23::inner_step, and, if the integration is running in an ensemble, out through the rayon scope. Every system that only knows its own derivatives is locked out of the crate’s only stiff solver.

Wrapping it in NumericalJacobian supplies the missing method by finite differences and changes nothing else: n_eqns and derivatives are forwarded verbatim.

§Owning, not borrowing

The wrapper owns the system by value, so it needs no lifetime parameter and no Box — both forbidden by the workspace design rules. Construct it with Self::new, get the system back with Self::into_inner.

§An analytic Jacobian is still better

Finite differences cost n + 1 to 4n extra derivatives calls per Rosenbrock stage and are accurate to roughly sqrt(eps) to eps^(4/5) rather than to machine precision — see the module-level “Achievable accuracy” table. If the analytic Jacobian is available, write it. This wrapper is for the systems where it is not, and as a verification oracle for the ones where it is: differencing a system that also implements jacobian analytically and comparing is the cheapest real check that the hand-derived version has no sign or transposition error.

§Units

Inherited from the wrapped system; nothing here is dimensioned.

§Example

use outram_foam_basic_lib::math::differentiate::{DiffSettings, NumericalJacobian};
use outram_foam_basic_lib::ode::{OdeSystem, Rosenbrock23};

// A stiff scalar system with NO hand-coded Jacobian: dy/dx = -1000 y.
struct StiffDecay;
impl OdeSystem for StiffDecay {
    fn n_eqns(&self) -> usize { 1 }
    fn derivatives(&self, _x: f64, y: &[f64], dydx: &mut Vec<f64>) {
        dydx.clear();
        dydx.push(-1000.0 * y[0]);
    }
    // no `jacobian` override -- the default would panic
}

let system = NumericalJacobian::new(StiffDecay, DiffSettings::central());
let mut solver = Rosenbrock23::new(1, 1e-10, 1e-10);
let mut y = vec![1.0_f64];
let mut dx = 1e-5;
solver.integrate(&system, 0.0, 0.01, &mut y, &mut dx).expect("integrates");

// exp(-1000 * 0.01) = exp(-10) = 4.5399929762484854e-5
let exact = (-10.0_f64).exp();
assert!((y[0] - exact).abs() < 1e-8, "got {}, want {exact}", y[0]);
assert_eq!(system.non_finite_jacobians(), 0);

Implementations§

Source§

impl<S> NumericalJacobian<S>

Source

pub fn new(system: S, settings: DiffSettings) -> Self

Wrap system, differencing its derivatives with settings.

§Arguments
  • system — owned by value.
  • settings — scheme and step-size policy. DiffSettings::central is the usual choice for a Rosenbrock Jacobian: 2n evaluations for O(h^2) truncation, where DiffScheme::Central4th doubles the cost again for accuracy the step controller cannot exploit.
Source

pub fn inner(&self) -> &S

Borrow the wrapped system.

Source

pub fn into_inner(self) -> S

Unwrap, returning the system.

Source

pub fn settings(&self) -> DiffSettings

The step-size policy in force.

Source

pub fn non_finite_jacobians(&self) -> usize

How many OdeSystem::jacobian calls have failed since construction.

The trait method returns (), so it has nowhere to report a failure; this counter is that report. A non-zero value means at least one Jacobian was handed to the solver with NaN entries, which the solver will have turned into crate::ode::OdeError::StepSizeUnderflow rather than a wrong answer — but it is worth knowing why an integration failed, and “the Jacobian could not be differenced” is a different bug from “the system is too stiff”.

§Units

A count, dimensionless.

Trait Implementations§

Source§

impl<S: Debug> Debug for NumericalJacobian<S>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<S> OdeSystem for NumericalJacobian<S>
where S: OdeSystem,

Source§

fn n_eqns(&self) -> usize

Number of coupled equations (the length of the state vector y).
Source§

fn derivatives(&self, x: f64, y: &[f64], dydx: &mut Vec<f64>)

Fill dydx with the derivatives at (x, y).
Source§

fn jacobian( &self, x: f64, y: &[f64], dfdx: &mut Vec<f64>, dfdy: &mut SquareMatrix, )

Fill dfdx and dfdy with the Jacobian at (x, y). Read more

Auto Trait Implementations§

§

impl<S> !Freeze for NumericalJacobian<S>

§

impl<S> RefUnwindSafe for NumericalJacobian<S>
where S: RefUnwindSafe,

§

impl<S> Send for NumericalJacobian<S>
where S: Send,

§

impl<S> Sync for NumericalJacobian<S>
where S: Sync,

§

impl<S> Unpin for NumericalJacobian<S>
where S: Unpin,

§

impl<S> UnsafeUnpin for NumericalJacobian<S>
where S: UnsafeUnpin,

§

impl<S> UnwindSafe for NumericalJacobian<S>
where S: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.