Metadata-Version: 2.4
Name: deep-fbsde-nn
Version: 0.1.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>

<!-- 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 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) \, dt + \sigma(t, X_t, Y_t) \, dW_t \\[6pt]
\text{Backward SDE:} \quad & dY_t = -f(t, X_t, Y_t, Z_t) \, dt + Z_t^\top \sigma(t, X_t, Y_t) \, dW_t \\[6pt]
\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 both 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
- **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).

```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 of driver vs reference (sharp); end-to-end sanity band |
| `BlackScholesEquation` (basket) | Monte-Carlo benchmark method only | shape/contract tests |
| `AllenCahnEquation` | none yet | shape/contract tests |

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.

### Known limitations (honest edition)

- **HJB end-to-end accuracy:** with $Z$ derived by autograd from the same network as $u$, training under-weights strongly nonlinear (quadratic-in-$Z$) drivers; calibrated runs plateau ~12-15% above the reference at $d=3$. The PDE-residual test guards the math sharply; per-timestep $Z$ subnetworks (Han et al.'s original design) are the tracked fix ([TODOS.md](TODOS.md)).
- **XVA and greeks are experimental**: quarantined under `experiments/experimental/` with runtime warnings; they need debugging and are not part of the tested surface.
- **Basket and Allen-Cahn have no reference solution yet** — they ship contract-tested, not validated.

## Project Structure

```
deep-fbsde-nn/
├── deep_fbsde_nn/           # The installed package (torch + numpy only)
│   ├── equations/           # BaseEquation + BS, BSB, Allen-Cahn, HJB, vanilla call
│   ├── networks/            # NAIS-Net, FeedForward, activations, BS wrapper
│   ├── solvers/             # StandardSolver (fixed X0), GlobalSolver (distributed X0)
│   └── utils/               # device, metrics, checkpointing
├── tests/                   # pytest suite (fast + slow-marked convergence)
├── examples/quickstart.py   # CI-tested quickstart
├── experiments/             # research scripts (need the [experiments] extra)
│   └── experimental/        # XVA + greeks — known-broken, emits warnings
├── 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.

### 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 initial condition $X_0$ (original Deep BSDE) |
| `GlobalSolver` | Distributed $X_0$ (learns the solution surface) |

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

`SolverConfig` notes: `batch_size` defaults to 1, following the reference implementation this library was validated against; Han et al. (2018) used 64-256, and the default is being re-evaluated with benchmarks for 0.2 (see TODOS.md). The convergence tests use 16-64.

## 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},
  url = {https://github.com/ionutnodis/deep-fbsde-nn}
}
```
