# Getting Started
`leap-c` turns a model predictive controller into a differentiable PyTorch layer. It wraps
acados' state-of-the-art optimal-control solver `AcadosOcpSolver` into a layer whose
**forward** pass solves a (batched) optimal control problem and whose **backward** pass returns
*exact* gradients of the solution with respect to the problem's parameters. Because it is an
ordinary `torch.nn.Module`, you can drop it into any model, compose it with neural networks, and call
`loss.backward()` straight through the solver — the basis for learning MPC parameters with
reinforcement or imitation learning.
## The parametric optimal control problem
At each step leap-c solves a discrete-time **parametric optimal control problem (OCP)**: given an
initial state $\bar{x}_0$ and a parameter vector $p$, find the state and control trajectories that
minimize the accumulated cost subject to the system dynamics and constraints.
$$
\begin{aligned}
\min_{\substack{x_0, \dots, x_N \\ u_0, \dots, u_{N-1}}}\quad
& \sum_{k=0}^{N-1} \ell(x_k, u_k;\, p) \;+\; \ell_N(x_N;\, p) \\[2pt]
\text{s.t.}\quad
& x_0 = \bar{x}_0, \\
& x_{k+1} = f(x_k, u_k;\, p), && k = 0, \dots, N-1, \\
& h(x_k, u_k;\, p) \le 0, && k = 0, \dots, N-1, \\
& h_N(x_N;\, p) \le 0.
\end{aligned}
$$
- $x_k \in \mathbb{R}^{n_x}$ are the **states** and $u_k \in \mathbb{R}^{n_u}$ the **controls**,
over a horizon of $N$ stages.
- $\bar{x}_0$ is the **initial state** (`x0`), fixed for each solve.
- $p$ collects the **parameters** — dynamics constants, cost weights, references — the quantities
you set at runtime or *learn*.
- $\ell, \ell_N$ are the stage and terminal cost, $f$ the dynamics, and $h, h_N$ the path and
terminal constraints.
Solving this problem defines a **solution map** from $(\bar{x}_0,\, p)$ to the first optimal
control $u_0^\star(\bar{x}_0,\, p)$ and the optimal value $V(\bar{x}_0;\, p)$. leap-c makes this
map differentiable in $p$.
## The differentiable-MPC interface
{py:class}`~leap_c.torch.AcadosDiffMpcTorch` implements the solution map above as a
`torch.nn.Module`. You build one from an `AcadosOcp` (the problem — dynamics, cost, constraints)
and an {py:class}`~leap_c.parameters.AcadosParameterManager` (which numbers are
learnable), then call it like any module:
```{mermaid}
flowchart LR
OCP["AcadosOcp
(dynamics, cost, constraints)"] --> DMPC
PM["AcadosParameterManager
(differentiable vs. runtime params)"] --> DMPC
DMPC["AcadosDiffMpcTorch
(torch.nn.Module)"] --> PIPE["your PyTorch model
/ training loop"]
```
Its public interface is just a constructor and a `forward`:
```python
class AcadosDiffMpcTorch(torch.nn.Module):
def __init__(
self,
ocp: AcadosOcp, # the OCP: dynamics, cost, constraints
parameter_manager: AcadosParameterManager, # which parameters are learnable
# ... optional solver / code-generation options
): ...
```
Its public interface is just a constructor and a `forward`:
```python
class AcadosDiffMpcTorch(torch.nn.Module):
def __init__(
self,
ocp: AcadosOcp, # the OCP: dynamics, cost, constraints
parameter_manager: AcadosParameterManager, # which parameters are learnable
# ... optional solver / code-generation options
): ...
def forward(
self,
x0: torch.Tensor, # initial state, shape (B, n_x)
u0: torch.Tensor | None = None, # optional fixed first action
params: dict[str, torch.Tensor] | None = None, # parameter overrides p, by name
ctx: AcadosDiffMpcCtx | None = None, # solver context, for warm starts
) -> tuple[AcadosDiffMpcCtx, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
# returns (ctx, u0, x, u, value)
...
```
`forward` maps directly onto the OCP: it fixes $x_0 = \bar{x}_0$ from `x0`, applies the named
overrides in `params` as $p$, solves, and returns the first optimal control `u0` $= u_0^\star$,
the full state and control trajectories `x`, `u`, the optimal `value` $= V(\bar{x}_0;\, p)$, and a
`ctx` used to warm-start later solves. (Omitting `u0` evaluates the state value $V(\bar{x}_0)$;
passing it evaluates the state-action value $Q(\bar{x}_0, u_0)$.) Calling `value.sum().backward()`
then propagates **exact** gradients $\partial u_0^\star / \partial p$ and $\partial V / \partial p$
back to the learnable parameter tensors.
## Minimal example
A complete use of the layer — register the learnable parameters, build the module, solve, and
differentiate through the solve — reads like this:
```python
import numpy as np
import torch
from leap_c.torch import AcadosDiffMpcTorch
from leap_c.parameters import AcadosParameterManager
# 1. Register parameters. The manager returns CasADi symbols to use in the OCP.
# differentiable=True -> learnable, exact gradients (shared acados p_global)
# differentiable=False -> runtime model value, not learned (acados p)
manager = AcadosParameterManager(N_horizon=20)
Q = manager.register_parameter("Q", np.array([1.0, 1.0]), differentiable=True)
R = manager.register_parameter("R", np.array([0.1]), differentiable=True)
mass = manager.register_parameter("mass", np.array([1.0]), differentiable=False)
# 2. Build the AcadosOcp from those symbols (dynamics, cost, constraints).
# acados-specific; see "Define a differentiable MPC" for the full definition.
ocp = build_point_mass_ocp(manager, Q, R, mass)
# 3. Wrap the OCP and its parameters into a differentiable torch.nn.Module.
diff_mpc = AcadosDiffMpcTorch(ocp, manager)
# 4. Solve a batch of OCPs for an initial state, overriding a parameter by name.
x0 = torch.tensor([[1.0, 0.0]], dtype=torch.float64) # (batch, n_x)
Q_t = torch.tensor([[1.0, 1.0]], dtype=torch.float64, requires_grad=True)
ctx, u0, x, u, value = diff_mpc(x0, params={"Q": Q_t})
# u0 is the first optimal action; differentiate the solution end to end.
value.sum().backward() # exact gradients w.r.t. differentiable params
print(u0, Q_t.grad)
```
`build_point_mass_ocp` stands in for the acados OCP construction, which uses the symbols
returned above in the dynamics and cost. See
[Define a differentiable MPC](define_differentiable_mpc.md) for the full, runnable version
(the OCP body spelled out) and the accompanying explanation.
## Next steps
- [Define a differentiable MPC](define_differentiable_mpc.md) — a complete, runnable example that
builds the `AcadosOcp`, wraps it, and differentiates through a solve.
- [Parameter management](parameter_management.md) — declaring learnable vs. runtime parameters and
varying them across the horizon.
- [Core API](api/index.md) — the main user-facing classes.