pub trait ControlProblem<const N: usize> {
type Control;
Show 14 methods
// Required methods
fn optimize(
&self,
t: f64,
state: &[f64; N],
derivs: &StateDerivatives<N>,
) -> Self::Control;
fn running_reward(
&self,
t: f64,
state: &[f64; N],
control: &Self::Control,
) -> f64;
fn generator(
&self,
t: f64,
state: &[f64; N],
control: &Self::Control,
derivs: &StateDerivatives<N>,
) -> f64;
fn terminal(&self, state: &[f64; N]) -> f64;
fn next_step(
&self,
t: f64,
state: &[f64; N],
dt: f64,
noise: &[f64; N],
) -> [f64; N];
// Provided methods
fn driver(
&self,
t: f64,
state: &[f64; N],
control: &Self::Control,
derivs: &StateDerivatives<N>,
) -> f64 { ... }
fn bsde_driver(
&self,
t: f64,
state: &[f64; N],
control: &Self::Control,
_derivs: &StateDerivatives<N>,
_dt: f64,
) -> f64 { ... }
fn apply_constraint(&self, _state: &[f64; N], value: f64) -> f64 { ... }
fn discount_rate(&self, _state: &[f64; N]) -> f64 { ... }
fn constant_discount_rate(&self) -> Option<f64> { ... }
fn next_step_controlled(
&self,
t: f64,
state: &[f64; N],
_control: &Self::Control,
dt: f64,
noise: &[f64; N],
) -> [f64; N] { ... }
fn is_reduced_value(&self) -> bool { ... }
fn is_diffusion_dimension(&self, _dim: usize) -> bool { ... }
fn gradient_step(&self, _dim: usize) -> f64 { ... }
}Expand description
A finite-horizon stochastic optimal control problem.
The generic formulation is
state x in R^n
control u in U
dynamics dx = b(t,x,u) dt + sigma(t,x) dW (plus optional jumps)
objective J = E[ integral_t^T f(s,x,u) ds + g(x_T) ]
value V(t,x) = sup_u JThe associated HJB equation is
0 = d_t V + sup_u { f(t,x,u) + L^u V }where L^u V is the infinitesimal generator.
§Contract
The running reward f and the generator L^u V are kept separate because
different solvers consume them differently:
ControlProblem::running_rewardreturns the running rewardf(t,x,u). This is the driver used by the regression-based BSDE solver: the generator is already accounted for by simulating the forward SDE under the control.ControlProblem::generatorreturns the infinitesimal generatorL^u V = b(t,x,u) dot grad V + 0.5 tr(sigma sigma' hess V)evaluated at a fixed control and derivative bundle.ControlProblem::driveris the sumf + L^u V, the full HJB driver consumed by finite-difference (grid) solvers. Its default implementation isrunning_reward + generatorand should not be overridden.ControlProblem::optimizereturns the control that maximizes the driver for the given state and derivatives.
Discounting is handled separately by ControlProblem::discount_rate and
must not be folded into any of these hooks.
§Examples
use solver::models::control::{ControlProblem, StateDerivatives};
/// Constant-control problem with zero reward and zero terminal value.
struct Zero;
impl ControlProblem<1> for Zero {
type Control = f64;
fn optimize(&self, _t: f64, _s: &[f64; 1], _d: &StateDerivatives<1>) -> f64 { 0.0 }
fn running_reward(&self, _t: f64, _s: &[f64; 1], _c: &f64) -> f64 { 0.0 }
fn generator(&self, _t: f64, _s: &[f64; 1], _c: &f64, _d: &StateDerivatives<1>) -> f64 { 0.0 }
fn terminal(&self, _s: &[f64; 1]) -> f64 { 0.0 }
fn next_step(&self, _t: f64, s: &[f64; 1], _dt: f64, _n: &[f64; 1]) -> [f64; 1] { *s }
}Required Associated Types§
Required Methods§
Sourcefn optimize(
&self,
t: f64,
state: &[f64; N],
derivs: &StateDerivatives<N>,
) -> Self::Control
fn optimize( &self, t: f64, state: &[f64; N], derivs: &StateDerivatives<N>, ) -> Self::Control
Returns the control that maximizes the driver at (t, state).
Sourcefn running_reward(
&self,
t: f64,
state: &[f64; N],
control: &Self::Control,
) -> f64
fn running_reward( &self, t: f64, state: &[f64; N], control: &Self::Control, ) -> f64
Returns the running reward f(t,x,u).
Provided Methods§
Sourcefn driver(
&self,
t: f64,
state: &[f64; N],
control: &Self::Control,
derivs: &StateDerivatives<N>,
) -> f64
fn driver( &self, t: f64, state: &[f64; N], control: &Self::Control, derivs: &StateDerivatives<N>, ) -> f64
Returns the full HJB driver f(t,x,u) + L^u V.
The default implementation is running_reward + generator. Grid
solvers consume this; the regression-based BSDE solver consumes
ControlProblem::bsde_driver instead.
Sourcefn bsde_driver(
&self,
t: f64,
state: &[f64; N],
control: &Self::Control,
_derivs: &StateDerivatives<N>,
_dt: f64,
) -> f64
fn bsde_driver( &self, t: f64, state: &[f64; N], control: &Self::Control, _derivs: &StateDerivatives<N>, _dt: f64, ) -> f64
Returns the backward driver consumed by the BSDE regression solver.
For a full (non-reduced) control problem this is the running reward
f alone; the infinitesimal generator is already accounted for by
simulating the forward SDE under the control. Reduced value problems
(for example the CARA market-making theta ansatz) override this to
include the local source terms that are not part of the forward
transport.
dt is the forward time step. It lets the driver bound the running
reward at 1 / dt per fill side, which is the largest rate the forward
Euler step can represent before its Bernoulli fill probability
lambda * dt saturates at 1. Without this bound the backward reward
can count fills the forward step cannot produce, creating a
forward/backward inconsistency that destabilizes the regression at
large base intensities.
Sourcefn apply_constraint(&self, _state: &[f64; N], value: f64) -> f64
fn apply_constraint(&self, _state: &[f64; N], value: f64) -> f64
Optional pointwise constraint on the value, e.g. the early-exercise
obstacle V >= payoff for an American option.
Defaults to the identity. Finite-difference solvers apply this after each backward step; the BSDE path does not yet enforce it.
Sourcefn discount_rate(&self, _state: &[f64; N]) -> f64
fn discount_rate(&self, _state: &[f64; N]) -> f64
Discount rate r(t,x). Defaults to zero.
Sourcefn constant_discount_rate(&self) -> Option<f64>
fn constant_discount_rate(&self) -> Option<f64>
Constant discount rate hint. Defaults to None (state dependent).
Sourcefn next_step_controlled(
&self,
t: f64,
state: &[f64; N],
_control: &Self::Control,
dt: f64,
noise: &[f64; N],
) -> [f64; N]
fn next_step_controlled( &self, t: f64, state: &[f64; N], _control: &Self::Control, dt: f64, noise: &[f64; N], ) -> [f64; N]
Advances the state one step under an explicitly supplied control.
The default ignores control and delegates to
ControlProblem::next_step. Problems whose forward dynamics depend
on the control (for example market-making models whose fill intensities
determine the inventory jump, or price-impact models) override this to
use control instead of a frozen proxy, so the coupled (Picard) BSDE
forward pass simulates the state under the current optimal control.
Sourcefn is_reduced_value(&self) -> bool
fn is_reduced_value(&self) -> bool
Whether the value this problem solves is a reduced value.
A reduced value problem models a value function that has been collapsed
onto a subset of the state dimensions (for example the CARA market-making
ansatz reduces the full (S, q, X) value to the inventory-only function
theta(t, q)). For such problems the forward pass must not simulate the
jumps of the collapsed dimensions: the jumps are already absorbed into the
local source carried by ControlProblem::bsde_driver, so simulating them
forward would regress the jump-convolved continuation instead of the reduced
value at the same collapsed state.
Defaults to false (a full, non-reduced value problem).
Sourcefn is_diffusion_dimension(&self, _dim: usize) -> bool
fn is_diffusion_dimension(&self, _dim: usize) -> bool
Whether a dimension is driven by Brownian diffusion.
Sourcefn gradient_step(&self, _dim: usize) -> f64
fn gradient_step(&self, _dim: usize) -> f64
Physical finite-difference step for a dimension, used by mesh-free gradient stencils.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".