Source code for prt_rl.model_based.models.dynamics.lagrangian

from dataclasses import dataclass
from typing import Dict, Optional, Tuple, Union
import torch
import torch.nn as nn


Tensor = torch.Tensor


[docs] @dataclass class LNNConfig: """Configuration for a Lagrangian Neural Network.""" q_dim: int u_dim: int = 0 # set >0 for action-conditioned / forced dynamics lagrangian_hidden: Tuple[int, ...] = (128, 128) force_hidden: Tuple[int, ...] = (128, 128) activation: str = "tanh" learn_forces: bool = True # if False, assumes Q = 0 (unforced) damping: bool = False # optional dissipative term gravity: bool = False # optional learned potential structure eps: float = 1e-6 # numerical stability device: Optional[torch.device] = None dtype: Optional[torch.dtype] = None
[docs] class LagrangianNN(nn.Module): """ Lagrangian Neural Network with optional action-conditioned generalized forces. - Unforced (conservative): u_dim=0 or learn_forces=False => Q ≡ 0 - Forced/action-conditioned: u_dim>0 and learn_forces=True => Q = Q(q, dq, u) Intended usage: L = model.lagrangian(q, dq) Q = model.generalized_forces(q, dq, u) # optional ddq = model.accelerations(q, dq, u) # used in loss """ def __init__(self, cfg: LNNConfig): super().__init__() ... # ---------------------------- # Core API # ----------------------------
[docs] def lagrangian(self, q: Tensor, dq: Tensor) -> Tensor: """ Compute scalar Lagrangian L(q, dq) per batch element. Args: q: [B, n] dq: [B, n] Returns: L: [B, 1] (or [B]) """ ...
[docs] def generalized_forces(self, q: Tensor, dq: Tensor, u: Optional[Tensor] = None) -> Tensor: """ Compute generalized external forces Q. Args: q: [B, n] dq: [B, n] u: [B, m] (optional; required if action-conditioned) Returns: Q: [B, n] """ ...
[docs] def accelerations(self, q: Tensor, dq: Tensor, u: Optional[Tensor] = None) -> Tensor: """ Compute accelerations ddq from Euler–Lagrange with forcing: d/dt(∂L/∂dq) - ∂L/∂q = Q Args: q: [B, n] dq: [B, n] u: [B, m] (optional) Returns: ddq: [B, n] """ ...
# ---------------------------- # Autodiff helpers (useful for debugging / custom losses) # ----------------------------
[docs] def dL_dq(self, q: Tensor, dq: Tensor) -> Tensor: """Return ∂L/∂q, shape [B, n].""" ...
[docs] def dL_ddq(self, q: Tensor, dq: Tensor) -> Tensor: """Return ∂L/∂dq, shape [B, n].""" ...
[docs] def mass_matrix(self, q: Tensor, dq: Tensor) -> Tensor: """ Return M(q, dq) = ∂²L/∂dq², shape [B, n, n]. Often used as a learned inertia / metric term. """ ...
[docs] def coriolis_and_gravity(self, q: Tensor, dq: Tensor) -> Tensor: """ Return a convenience term C(q,dq) typically representing: C(q,dq) = ∂/∂q(∂L/∂dq) dq - ∂L/∂q (Exact definition may vary by implementation.) Shape [B, n]. """ ...
[docs] def energy(self, q: Tensor, dq: Tensor) -> Tensor: """ Return total energy E(q,dq) = dqᵀ(∂L/∂dq) - L. Shape [B, 1] (or [B]). """ ...
# ---------------------------- # Validation / shape guards # ----------------------------
[docs] def is_action_conditioned(self) -> bool: """Return True if model expects actions (u_dim > 0 and learn_forces).""" ...