Skip to main content

solver/numeric/finite_difference/
pde.rs

1//! FD-specific contract for grid-based HJB solvers.
2//!
3//! [`crate::models::control::ControlProblem`] is the solver-agnostic
4//! stochastic-control contract. It supplies a scalar driver and is consumed by
5//! both the BSDE regression solver and the explicit-Euler finite-difference
6//! step. A scalar driver is not sufficient to assemble the implicit operator
7//!
8//! ```text
9//! (I - dt L) V_new = V_old + dt s
10//! ```
11//!
12//! because `L` carries off-diagonal transport coefficients. This module
13//! defines the contract consumed only by time-dependent grid PDE solvers: the
14//! model supplies the per-dimension transport coefficients and a local source,
15//! and the solver builds the operator from those parts.
16//!
17//! The shared spatial primitives (`DimensionKind`, `Transport`) are defined in
18//! [`super::discretization`].
19
20use super::discretization::{DimensionKind, JumpKernel, Transport};
21use crate::models::control::StateDerivatives;
22
23/// Finite-difference view of a time-dependent stochastic optimal control
24/// problem.
25///
26/// A model that must be solved accurately on a diffusive grid implements this
27/// contract in addition to [`crate::models::control::ControlProblem`]. The
28/// two contracts are kept separate so the generic contract remains
29/// solver-agnostic and usable by the BSDE solver.
30pub trait PdeProblem<const N: usize>: crate::models::control::ControlProblem<N> {
31    /// Discretization kind for `dim`.
32    ///
33    /// # Panics
34    ///
35    /// Implementations should return a valid kind for every `dim < N`.
36    fn dimension_kind(&self, dim: usize) -> DimensionKind;
37
38    /// Transport and local source for the given control and derivatives.
39    ///
40    /// The solver calls this after [`crate::models::control::ControlProblem::optimize`]
41    /// at each backward step and assembles the implicit operator from the
42    /// returned coefficients.
43    fn transport(
44        &self,
45        t: f64,
46        state: &[f64; N],
47        control: &Self::Control,
48        derivs: &StateDerivatives<N>,
49    ) -> Transport<N>;
50
51    /// Jump kernel for arbitrary-amplitude jump dimensions.
52    ///
53    /// The default folds [`Transport::plus`]/[`Transport::minus`] into the unit
54    /// `+1`/`-1` transitions of the `DiscreteJump` convention, so models that
55    /// do not use general jumps are unchanged. Override this only for a
56    /// `DimensionKind::Jump` dimension whose amplitudes differ from one.
57    fn jump_kernel(
58        &self,
59        _t: f64,
60        _state: &[f64; N],
61        _control: &Self::Control,
62        _derivs: &StateDerivatives<N>,
63        transport: &Transport<N>,
64    ) -> JumpKernel<N> {
65        JumpKernel::from_transport(transport)
66    }
67}