Skip to main content

ControlProblem

Trait ControlProblem 

Source
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 J

The 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_reward returns the running reward f(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::generator returns the infinitesimal generator L^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::driver is the sum f + L^u V, the full HJB driver consumed by finite-difference (grid) solvers. Its default implementation is running_reward + generator and should not be overridden.
  • ControlProblem::optimize returns 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§

Source

type Control

The action type. For Merton this is a scalar portfolio fraction; for market making it is a pair of bid/ask intensities.

Required Methods§

Source

fn optimize( &self, t: f64, state: &[f64; N], derivs: &StateDerivatives<N>, ) -> Self::Control

Returns the control that maximizes the driver at (t, state).

Source

fn running_reward( &self, t: f64, state: &[f64; N], control: &Self::Control, ) -> f64

Returns the running reward f(t,x,u).

Source

fn generator( &self, t: f64, state: &[f64; N], control: &Self::Control, derivs: &StateDerivatives<N>, ) -> f64

Returns the infinitesimal generator L^u V for the given control and derivative bundle.

Source

fn terminal(&self, state: &[f64; N]) -> f64

Terminal value g(x) at the horizon.

Source

fn next_step( &self, t: f64, state: &[f64; N], dt: f64, noise: &[f64; N], ) -> [f64; N]

Advances the state one step under the optimal control at forward time t.

Provided Methods§

Source

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.

Source

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.

Source

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.

Source

fn discount_rate(&self, _state: &[f64; N]) -> f64

Discount rate r(t,x). Defaults to zero.

Source

fn constant_discount_rate(&self) -> Option<f64>

Constant discount rate hint. Defaults to None (state dependent).

Source

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.

Source

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).

Source

fn is_diffusion_dimension(&self, _dim: usize) -> bool

Whether a dimension is driven by Brownian diffusion.

Source

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".

Implementors§