Metadata-Version: 2.4
Name: deep-fbsde-nn
Version: 0.3.0
Summary: Neural network solvers for high-dimensional FBSDEs and PDEs in quantitative finance
Author: Ionut Nodis
License: MIT
Project-URL: Repository, https://github.com/ionutnodis/deep-fbsde-nn
Keywords: FBSDE,BSDE,PDE,neural-networks,deep-learning,option-pricing,quantitative-finance,pytorch
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Topic :: Office/Business :: Financial
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: torch>=2.0.0
Requires-Dist: numpy>=1.24.0
Provides-Extra: experiments
Requires-Dist: matplotlib>=3.7.0; extra == "experiments"
Requires-Dist: scipy>=1.10.0; extra == "experiments"
Provides-Extra: dev
Requires-Dist: pytest>=7.3.0; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

<h1 align="center">Deep FBSDE Neural Networks</h1>

<p align="center">
<a href="https://github.com/ionutnodis/deep-fbsde-nn/actions/workflows/ci.yml"><img src="https://github.com/ionutnodis/deep-fbsde-nn/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<a href="https://pypi.org/project/deep-fbsde-nn/"><img src="https://img.shields.io/pypi/v/deep-fbsde-nn" alt="PyPI"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="MIT License"></a>
<a href="https://doi.org/10.5281/zenodo.22311423"><img src="https://zenodo.org/badge/1132717784.svg" alt="DOI"></a>
<a href="https://colab.research.google.com/github/ionutnodis/deep-fbsde-nn/blob/main/examples/quickstart_bsb.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open in Colab"></a>
</p>

<!-- regenerated by `python experiments/make_hero_figure.py` before each release -->
<p align="center"><img src="https://raw.githubusercontent.com/ionutnodis/deep-fbsde-nn/main/docs/assets/hero_bsb_d100.png" width="720" alt="Deep BSDE prediction tracking the exact Black-Scholes-Barenblatt solution at d=100"></p>

A PyTorch library for solving high-dimensional Partial Differential Equations (PDEs) and Forward-Backward Stochastic Differential Equations (FBSDEs) using deep learning methods.

## Acknowledgements

This library was developed by **Ionut Nodis** as part of the **CQF (Certificate in Quantitative Finance) Final Project** (January 2026) — the full write-up is in the repo: [**CQF final report (PDF)**](docs/cqf-final-report.pdf). The implementation builds upon example code and guidance provided by **Professor Panos Parpas** from Imperial College London.

The theoretical foundations are based on the Deep BSDE method introduced by Han, Jentzen, and E (2018), with neural network stability enhancements from the NAIS-Net architecture (Ciccone et al., 2018; Güler, Laignelet, Parpas, 2019).

## Overview

The Deep BSDE method reformulates high-dimensional PDEs as Forward-Backward SDEs and uses neural networks to approximate the solution, sidestepping the grid-based curse of dimensionality. The library is designed for high-dimensional problems (the experiments run at $d = 100$); the CI-validated surface is described honestly in [Validation](#validation).

### The FBSDE System

The PDE solution $u(t, x)$ corresponds to the backward component $Y_t$ of a coupled Forward-Backward SDE system:

$$
\begin{aligned}
\text{Forward SDE:}\quad & dX_t = \mu(t, X_t, Y_t, Z_t)\thinspace dt + \sigma(t, X_t, Y_t)\thinspace dW_t \\
\text{Backward SDE:}\quad & dY_t = -f(t, X_t, Y_t, Z_t)\thinspace dt + Z_t^\top \sigma(t, X_t, Y_t)\thinspace dW_t \\
\text{Terminal condition:}\quad & Y_T = g(X_T)
\end{aligned}
$$

where:
- $X_t \in \mathbb{R}^d$ is the forward process (e.g., asset prices)
- $Y_t \in \mathbb{R}$ is the backward process (the PDE solution)
- $Z_t \in \mathbb{R}^d$ is the gradient process — **in this library, $Z_t = \nabla_x u(t, X_t)$** (the raw gradient; the diffusion $\sigma$ is applied explicitly in the martingale term)
- $W_t$ is a $d$-dimensional Brownian motion

### Connection to PDEs (Feynman-Kac)

The FBSDE solution satisfies $Y_t = u(t, X_t)$ where $u$ solves the semilinear parabolic PDE:

$$
\frac{\partial u}{\partial t} + \mu \cdot \nabla u + \frac{1}{2} \text{Tr}\left(\sigma \sigma^\top D^2 u\right) + f(t, x, u, \nabla u) = 0
$$

with terminal condition $u(T, x) = g(x)$. Every equation's `driver()` returns exactly the $f$ from this PDE form — one sign convention, shared by all solvers, enforced by the test suite.

## Features

- **High-dimensional PDEs**: experiments at $d = 10, 50, 100$; see [Validation](#validation) for what CI proves
- **NAIS-Net architecture**: spectral projection conditions every residual block (eigenvalues of the state matrix confined to $[\varepsilon, 1-\varepsilon]$)
- **Multiple equation types**: Black-Scholes (basket + vanilla), Black-Scholes-Barenblatt, Allen-Cahn, Hamilton-Jacobi-Bellman, XVA (Burgard-Kjaer)
- **MLMC training**: Multi-Level Monte Carlo progressive time-stepping for cheaper early iterations
- **Device agnostic**: automatic detection of CUDA, MPS (Apple Silicon), or CPU
- **Lean install**: the core package depends on `torch` and `numpy` only
- **Safe checkpoints**: models load with `torch.load(weights_only=True)` — no pickle code execution

## Installation

```bash
pip install deep-fbsde-nn
```

For development (includes pytest, ruff, build tooling):

```bash
git clone https://github.com/ionutnodis/deep-fbsde-nn.git
cd deep-fbsde-nn
pip install -e ".[dev]"
```

To run the scripts under `experiments/` from a source checkout you also need the plotting stack:

```bash
pip install -e ".[experiments]"   # adds matplotlib + scipy
```

### Dependencies

Core: **Python ≥ 3.12**, `torch ≥ 2.0`, `numpy ≥ 1.24` — nothing else.

## Quick Start

The same example lives as a runnable, CI-tested script at [`examples/quickstart.py`](examples/quickstart.py) (`python examples/quickstart.py --dim 4 --iterations 500` for a laptop-scale run) — or **try it in one click on Colab** (badge above): the notebook is generated from a CI-executed [jupytext source](examples/quickstart_notebook.py), so it can't rot.

```python
from deep_fbsde_nn.equations import BlackScholesBarenblattEquation
from deep_fbsde_nn.networks import NAISNet
from deep_fbsde_nn.solvers import StandardSolver, SolverConfig
from deep_fbsde_nn.utils import get_device

device = get_device()  # auto-detects CUDA/MPS/CPU
dimension = 100

# BSB has an analytical solution — ideal for validation
equation = BlackScholesBarenblattEquation(
    dimension=dimension, sigma_min=0.1, sigma_max=0.3,
    terminal_time=1.0, device=device,
)

network = NAISNet(
    input_dim=dimension + 1,  # (t, X)
    hidden_dim=256, output_dim=1, num_layers=4, activation="sine",
)

solver_config = SolverConfig(
    batch_size=16, num_timesteps=50,
    learning_rate=1e-3, num_iterations=5000, use_mlmc=True,
)

solver = StandardSolver(equation, network, solver_config, device=device)
solver.train()

results = solver.validate()
print(f"Relative error: {results['relative_error']:.2f}%")
```

## Validation

What the CI actually proves, per equation:

| Equation | Reference | CI coverage |
|----------|-----------|-------------|
| `BlackScholesBarenblattEquation` | analytical solution | convergence < 2% (low-d); D=100 smoke on release |
| `VanillaCallEquation` | Black-Scholes closed form (D=1, in-class, `torch.erf`) | convergence < 5% |
| `HJBEquation` | Cole-Hopf Monte-Carlo formula (in-class, seedable) | PDE-residual consistency (sharp); `StepwiseSolver` reproduces Han et al.'s published $d{=}100$ value 4.5901 to <2% in CI (0.03% calibrated); `StandardSolver` sanity band |
| `BlackScholesEquation` (basket) | seeded Monte-Carlo benchmark (same smoothed payoff) | `StepwiseSolver` within max(3%, 5·SE) of MC, D=5 |
| `AllenCahnEquation` | published branching-diffusion value 0.052802 (canonical Han et al. spec) | `StepwiseSolver` < 3% at $d{=}100$ (0.98% calibrated) |
| `XVAEquation` | closed form $e^{-c(T-t)}\cdot BS$ (D=1 long options; cross-validated vs a classical MC oracle) | anchored `StepwiseSolver` at S ∈ {75, 100, 130}: price < max(8%, 0.1 abs) and delta error < 0.02 (calibrated: 0.18% ATM price, worst delta 0.011 across S=75-130) |

Plus: NAIS-Net projection invariant, Brownian path statistics, checkpoint round-trips under `weights_only=True`, packaging E2E (build → clean-venv install → import → training smoke), and this README's quickstart at reduced scale.

## Benchmarks

Reproducible with one command — every row is a seeded run against an exact solution, a published benchmark value, or a seeded Monte-Carlo reference:

<!-- BENCHMARK:START -->
| Equation | d | Solver | Reference | Rel. error | Time |
|---|---|---|---|---|---|
| Black-Scholes-Barenblatt | 2 | Standard | exact solution | 1.89% | 5s |
| Black-Scholes-Barenblatt | 10 | Stepwise | exact solution | 0.11% | 14s |
| Black-Scholes-Barenblatt | 50 | Stepwise | exact solution | 0.12% | 22s |
| Black-Scholes-Barenblatt | 100 | Stepwise | exact solution | 0.06% | 32s |
| Vanilla call | 1 | Standard | Black-Scholes closed form | 1.01% | 16s |
| BS basket call | 5 | Stepwise | seeded MC (same payoff) | 0.62% | 15s |
| BS basket call | 25 | Stepwise | seeded MC (same payoff) | 0.35% | 22s |
| XVA call (Burgard-Kjaer) | 1 | Stepwise | closed form e^-cT * BS (MC cross-validated) | 0.18% | 16s |
| Hamilton-Jacobi-Bellman | 3 | Stepwise | Cole-Hopf MC (seeded) | 2.31% | 15s |
| Hamilton-Jacobi-Bellman | 20 | Stepwise | Cole-Hopf MC (seeded) | 0.28% | 28s |
| Hamilton-Jacobi-Bellman | 100 | Stepwise | Cole-Hopf MC (seeded) / published 4.5901 | 0.03% | 68s |
| Allen-Cahn | 100 | Stepwise | published 0.052802 (branching diffusion) | 0.98% | 65s |

<sub>Seeded runs, CPU (arm), torch 2.14.0. Regenerate: `python benchmarks/run.py` (~10 min).</sub>
<!-- BENCHMARK:END -->

### Known limitations (honest edition)

- **Strongly nonlinear drivers with `StandardSolver`/`GlobalSolver`:** deriving $Z$ by autograd from the same network as $u$ under-weights quadratic-in-$Z$ drivers — those solvers plateau ~12-15% above the HJB reference. **Resolved in practice by `StepwiseSolver`** (2% at $d{=}3$, 0.03% at $d{=}100$); the derivative-coupled solvers keep the honest caveat since they're what you need for solution surfaces and greeks.
- **Single-network price surfaces are approximate.** Learning $u(t,S)$ across a wide moneyness range in one network hits a multi-scale loss floor (out-of-the-money wings are worth ~1000x less than in-the-money regions in squared error, and the terminal-gradient target is discontinuous at the strike). The accurate recipe is **anchored pointwise evaluation**: fix a `StepwiseSolver` at each spot you care about and read `predict()` / `delta0()` — validated for `XVAEquation` to delta error ≤0.011 across the full moneyness range (the v0.2 caveat this replaces measured 0.09).
- **BS/BSB greeks plotting** (`experiments/experimental/greeks_viz.py`) stays experimental.

## Project Structure

```
deep-fbsde-nn/
├── deep_fbsde_nn/           # The installed package (torch + numpy only)
│   ├── equations/           # BaseEquation + BS, BSB, Allen-Cahn, HJB, vanilla call, XVA
│   ├── networks/            # NAIS-Net, FeedForward, activations, BS wrapper
│   ├── solvers/             # StandardSolver (fixed X0), GlobalSolver (distributed X0), StepwiseSolver (per-step Z-nets)
│   └── utils/               # device, metrics, checkpointing
├── tests/                   # pytest suite (fast + slow-marked convergence)
├── examples/quickstart.py   # CI-tested quickstart (+ Colab notebook)
├── benchmarks/              # seeded benchmark harness (python benchmarks/run.py)
├── experiments/             # research scripts (need the [experiments] extra)
│   └── experimental/        # greeks plotting — experimental, warns at import
├── docs/                    # design docs + release material
└── tables.py                # results-table generator for experiments
```

## API Reference

### Equations

All equations inherit from `BaseEquation` and must implement:

| Method | Description |
|--------|-------------|
| `drift(t, X, Y, Z)` | Forward SDE drift $\mu(t, X, Y, Z)$ |
| `diffusion(t, X, Y)` | Forward SDE diffusion $\sigma(t, X, Y)$ |
| `driver(t, X, Y, Z)` | The $f$ from the PDE form above ($dY = -f\,dt + Z^\top\sigma\,dW$) |
| `terminal(X)` | Terminal condition $g(X)$ |
| `exact_solution(t, X)` | Reference solution $u(t, X)$ (optional) |

#### Black-Scholes-Barenblatt

Option pricing under uncertain volatility $\sigma \in [\sigma_{\min}, \sigma_{\max}]$; for the convex payoff $g(x) = \|x\|^2$ the exact solution is $u(t, x) = \|x\|^2 \exp(\sigma_{\max}^2 (T - t))$.

#### Hamilton-Jacobi-Bellman

The LQG control benchmark $\partial_t u + \Delta u - \lambda \|\nabla u\|^2 = 0$ with the Cole-Hopf reference
$u(t,x) = -\tfrac{1}{\lambda}\ln \mathbb{E}\left[\exp(-\lambda\, g(x + \sqrt{2}\, W_{T-t}))\right]$, implemented in-class with seedable Monte Carlo.

#### XVA (Burgard-Kjaer)

XVA-adjusted option pricing: $f_{xva}(V) = \lambda_c(1-R_c)V^+ - \lambda_b(1-R_b)V^- + (r_f-r)V$. For long options $V \ge 0$, so the driver linearizes exactly and $V(t,S) = e^{-c(T-t)}BS(t,S)$ is the in-class closed form (cross-validated against a classical Monte-Carlo XVA engine). Price and greeks at any spot:

```python
from deep_fbsde_nn.equations import XVAEquation
from deep_fbsde_nn.solvers import StepwiseSolver, SolverConfig

eq = XVAEquation(dimension=1, S0=80.0, strike=100.0)   # anchor at the query spot
config = SolverConfig(batch_size=64, num_timesteps=20,
                      num_iterations=3000, use_mlmc=False)
solver = StepwiseSolver(eq, config)
solver.train()
price, delta = solver.predict().item(), solver.delta0().item()
```

### Networks

| Network | Description |
|---------|-------------|
| `NAISNet` | Conditioned residual architecture (recommended) |
| `FeedForwardNet` | Standard feedforward baseline |

**NAIS-Net conditioning.** Each block's state matrix is $A = R^\top R + \varepsilon I$ where $R^\top R = W^\top W$ is rescaled so $\|R^\top R\|_F \le \delta = 1 - 2\varepsilon$. Since the Frobenius norm bounds the spectral norm and $R^\top R$ is symmetric PSD, the eigenvalues of $A$ lie in $[\varepsilon, 1-\varepsilon]$ — $A$ is positive definite with spectral norm strictly below one (the conditioning used in the stability analysis of Güler, Laignelet & Parpas, 2019). The `is_stable` property verifies this invariant numerically rather than assuming it.

### Solvers

| Solver | Description |
|--------|-------------|
| `StandardSolver` | Fixed $X_0$, one network $u(t,x)$, $Z = \nabla u$ by autograd |
| `GlobalSolver` | Distributed $X_0$ (learns the solution surface) |
| `StepwiseSolver` | Fixed $X_0$, trainable $Y_0/Z_0$ + one $Z$-network per timestep — the original Han-Jentzen-E (2018) parameterization. Strongest on nonlinear drivers (reproduces the published $d{=}100$ HJB value to 0.03%), but learns $u$ only at $(0, X_0)$ |

All solvers integrate the BSDE as $\hat{Y}_{n+1} = Y_n - f\,\Delta t + Z_n^\top \sigma\, \Delta W_n$ — the same convention, tested. `StepwiseSolver` additionally warm-starts $Y_0$ at the driver-free value $\mathbb{E}[g(X_T)]$ and zero-initializes its $Z$-net outputs, which is what makes high-dimensional training stable without per-problem tuning.

`SolverConfig` notes: `batch_size` defaults to **16** (changed from 1 in v0.2 after an evidence sweep: strictly better accuracy at ~1.2x cost). Because `StandardSolver`'s loss sums over the batch, batch size scales the effective learning rate — with very large batches, lower `learning_rate` accordingly.

## Experiments

From a source checkout with the `[experiments]` extra:

```bash
python experiments/exp01_bsb_dimension.py --all        # BSB dimension scaling
python experiments/exp02_architecture_comparison.py --dim 100
python experiments/exp03_black_scholes.py --dim 50
python experiments/exp04_hjb.py --dim 100
```

> Note: results produced before v0.1.0 predate two driver-convention fixes
> (StandardSolver sign, HJB factor) and should be regenerated.

## Extending the Library

Subclass `BaseEquation` and implement the four methods; `driver()` must return the $f$ of the PDE form $\partial_t u + \mu\cdot\nabla u + \tfrac12\mathrm{Tr}(\sigma\sigma^\top D^2u) + f = 0$:

```python
import torch
from deep_fbsde_nn.equations import BaseEquation, EquationConfig

class MyEquation(BaseEquation):
    """Example: ∂u/∂t + (1/2)Δu + u - u³ = 0 (Allen-Cahn-type)."""

    def __init__(self, dimension: int, device=None):
        super().__init__(EquationConfig(name="MyEq", dimension=dimension), device)

    def drift(self, t, X, Y, Z):
        return torch.zeros_like(X)          # dX = σ dW

    def diffusion(self, t, X, Y):
        return torch.ones_like(X)           # σ = I

    def driver(self, t, X, Y, Z):
        return Y - Y ** 3                   # f from the PDE form

    def terminal(self, X):
        norm_sq = torch.sum(X ** 2, dim=1, keepdim=True)
        return 1.0 / torch.sqrt(1.0 + norm_sq)
```

## Support policy

Tested in CI: Python 3.12, latest stable `torch` on CPU Linux (fast suite per push, convergence suite weekly and on release). Maintained by one person: issues are read, PRs are reviewed best-effort, security reports (see [SECURITY.md](SECURITY.md)) get priority. If a weekly CI run breaks on a new torch release, expect a fix or a pin within days, not hours.

## References

1. **Han, J., Jentzen, A., & E, W.** (2018). Solving high-dimensional partial differential equations using deep learning. *PNAS*, 115(34), 8505-8510.
2. **Ciccone, M., Gallieri, M., Masci, J., Osendorfer, C., & Gomez, F.** (2018). NAIS-Net: Stable Deep Networks from Non-Autonomous Differential Equations. *NeurIPS*.
3. **Güler, R.A., Laignelet, A., & Parpas, P.** (2019). Towards Robust and Stable Deep Learning Algorithms for Forward Backward Stochastic Differential Equations. *arXiv:1910.11623*.
4. **E, W., Han, J., & Jentzen, A.** (2017). Deep learning-based numerical methods for high-dimensional parabolic partial differential equations and backward stochastic differential equations. *Communications in Mathematics and Statistics*, 5(4), 349-380.

## License

MIT License — see [LICENSE](LICENSE).

## Citation

GitHub renders a "Cite this repository" button from [CITATION.cff](CITATION.cff). BibTeX:

```bibtex
@software{nodis2026deepfbsde,
  author = {Nodis, Ionut},
  title = {Deep FBSDE Neural Networks},
  year = {2026},
  doi = {10.5281/zenodo.22311423},
  url = {https://github.com/ionutnodis/deep-fbsde-nn}
}
```
