Source code for leap_c.diff_mpc.data
from typing import TYPE_CHECKING, NamedTuple, Sequence
import numpy as np
from acados_template import AcadosOcp
from acados_template.acados_ocp_iterate import (
AcadosOcpFlattenedBatchIterate,
AcadosOcpFlattenedIterate,
)
if TYPE_CHECKING:
import torch
[docs]
class AcadosOcpSolverInput(NamedTuple):
"""Input for an AcadosOcpSolver representing a batch of problem instances to be solved.
Attributes:
x0: Initial state, shape (batch_size, nx)
u0: Initial control input, shape (batch_size, nu), optional.
If provided, the initial control input will be constrained to this.
p_global: Global parameters, shape (batch_size, np_global), optional.
If not provided, the default values set in the acados ocp object will be used.
p_stagewise: Stage-wise parameters, shape (batch_size, N_horizon + 1, np_stagewise),
or (batch_size, len(p_stagewise_sparse_idx), np_stagewise), if p_stagewise_sparse_idx is
provided, optional.
If not provided, the default values set in the acados ocp object will be used.
Has to be provided if p_stagewise_sparse_idx is provided.
p_stagewise_sparse_idx: If provided, the indices determine which elements of the
total stagewise parameter in the solver should be overwritten
by the provided p_stagewise values,
shape (batch_size, N_horizon + 1, nindices), optional.
"""
x0: np.ndarray
u0: np.ndarray | None = None
p_global: np.ndarray | None = None
p_stagewise: np.ndarray | None = None
p_stagewise_sparse_idx: np.ndarray | None = None
@property
def batch_size(self) -> int:
"""Get the batch size."""
return self.x0.shape[0]
[docs]
def get_sample(self, idx: int) -> "AcadosOcpSolverInput":
"""Get the sample at index `idx` from the batch."""
return AcadosOcpSolverInput(
None if self.x0 is None else self.x0[idx],
None if self.u0 is None else self.u0[idx],
None if self.p_global is None else self.p_global[idx],
None if self.p_stagewise is None else self.p_stagewise[idx],
None if self.p_stagewise_sparse_idx is None else self.p_stagewise_sparse_idx[idx],
)
[docs]
def collate_acados_flattened_iterate_fn(
batch: Sequence[AcadosOcpFlattenedIterate], collate_fn_map: dict | None = None
) -> AcadosOcpFlattenedBatchIterate:
return AcadosOcpFlattenedBatchIterate(
x=np.stack([x.x for x in batch], axis=0),
u=np.stack([x.u for x in batch], axis=0),
z=np.stack([x.z for x in batch], axis=0),
sl=np.stack([x.sl for x in batch], axis=0),
su=np.stack([x.su for x in batch], axis=0),
pi=np.stack([x.pi for x in batch], axis=0),
lam=np.stack([x.lam for x in batch], axis=0),
N_batch=len(batch),
)
[docs]
def collate_acados_flattened_batch_iterate_fn(
batch: Sequence[AcadosOcpFlattenedBatchIterate],
collate_fn_map: dict | None = None,
) -> AcadosOcpFlattenedBatchIterate:
return AcadosOcpFlattenedBatchIterate(
x=np.concatenate([x.x for x in batch], axis=0),
u=np.concatenate([x.u for x in batch], axis=0),
z=np.concatenate([x.z for x in batch], axis=0),
sl=np.concatenate([x.sl for x in batch], axis=0),
su=np.concatenate([x.su for x in batch], axis=0),
pi=np.concatenate([x.pi for x in batch], axis=0),
lam=np.concatenate([x.lam for x in batch], axis=0),
N_batch=sum([x.N_batch for x in batch]),
)
def _stack_safe(attr, batch):
parts = [getattr(part, attr) for part in batch]
if all(part is None for part in parts):
return None
return np.stack(parts, axis=0)
[docs]
def collate_acados_ocp_solver_input(
batch: Sequence[AcadosOcpSolverInput],
collate_fn_map: dict | None = None,
) -> AcadosOcpSolverInput:
"""Collates a batch of AcadosOcpSolverInput objects into a single object."""
return AcadosOcpSolverInput(
x0=np.stack([input.x0 for input in batch], axis=0),
u0=_stack_safe("u0", batch),
p_global=_stack_safe("p_global", batch),
p_stagewise=_stack_safe("p_stagewise", batch),
p_stagewise_sparse_idx=_stack_safe("p_stagewise_sparse_idx", batch),
)
[docs]
def validate_forward_inputs(
ocp: AcadosOcp,
x0: "np.ndarray | torch.Tensor",
u0: "np.ndarray | torch.Tensor | None",
) -> None:
"""Validate the shapes of ``x0`` and ``u0`` before the forward solve.
Works on any array-like exposing ``ndim`` and ``shape`` (numpy, torch, jax), so it can be
shared by the torch and jax layers. Run before solver initialization so dimension mismatches
fail fast with a clear message rather than as a cryptic C-level error.
Raises:
ValueError: If ``x0`` is not 2-D, its last dimension is not ``ocp.dims.nx``, ``u0`` is
not 2-D, its batch does not match ``x0``, or its last dimension is not ``ocp.dims.nu``.
"""
nx = ocp.dims.nx
nu = ocp.dims.nu
if x0.ndim != 2:
raise ValueError(f"x0 must be 2-D (B, nx), got shape {tuple(x0.shape)}.")
if x0.shape[1] != nx:
raise ValueError(f"x0 has {x0.shape[1]} states, expected nx={nx}.")
if u0 is not None:
if u0.ndim != 2:
raise ValueError(f"u0 must be 2-D (B, nu), got shape {tuple(u0.shape)}.")
if u0.shape[0] != x0.shape[0]:
raise ValueError(
f"u0 has batch size {u0.shape[0]}, expected {x0.shape[0]} (matching x0)."
)
if u0.shape[1] != nu:
raise ValueError(f"u0 has {u0.shape[1]} controls, expected nu={nu}.")