Source code for jaxsim.parsers.descriptions.link
from __future__ import annotations
import dataclasses
import jax.numpy as jnp
import jax_dataclasses
import numpy as np
from jax_dataclasses import Static
import jaxsim.typing as jtp
from jaxsim.math import Adjoint
from jaxsim.utils import JaxsimDataclass
[docs]
@jax_dataclasses.pytree_dataclass(eq=False, unsafe_hash=False)
class LinkDescription(JaxsimDataclass):
"""
In-memory description of a robot link.
Attributes:
name: The name of the link.
mass: The mass of the link.
inertia: The inertia tensor of the link.
index: An optional index for the link (it gets automatically assigned).
parent: The parent link of this link.
pose: The pose transformation matrix of the link.
children: The children links.
"""
name: Static[str]
mass: float = dataclasses.field(repr=False)
inertia: jtp.Matrix = dataclasses.field(repr=False)
index: int | None = None
parent: LinkDescription | None = dataclasses.field(default=None, repr=False)
pose: jtp.Matrix = dataclasses.field(default_factory=lambda: jnp.eye(4), repr=False)
children: Static[tuple[LinkDescription]] = dataclasses.field(
default_factory=list, repr=False
)
def __hash__(self) -> int:
from jaxsim.utils.wrappers import HashedNumpyArray
return hash(
(
hash(self.name),
hash(float(self.mass)),
HashedNumpyArray.hash_of_array(self.inertia),
hash(int(self.index)) if self.index is not None else 0,
HashedNumpyArray.hash_of_array(self.pose),
hash(tuple(self.children)),
# Here only using the name to prevent circular recursion:
hash(self.parent.name) if self.parent is not None else 0,
)
)
def __eq__(self, other: LinkDescription) -> bool:
if not isinstance(other, LinkDescription):
return False
if not (
self.name == other.name
and np.allclose(self.mass, other.mass)
and np.allclose(self.inertia, other.inertia)
and self.index == other.index
and np.allclose(self.pose, other.pose)
and self.children == other.children
and (
(self.parent is not None and self.parent.name == other.parent.name)
if self.parent is not None
else other.parent is None
),
):
return False
return True
@property
def name_and_index(self) -> str:
"""
Get a formatted string with the link's name and index.
Returns:
str: The formatted string.
"""
return f"#{self.index}_<{self.name}>"
[docs]
def lump_with(
self, link: LinkDescription, lumped_H_removed: jtp.Matrix
) -> LinkDescription:
"""
Combine the current link with another link, preserving mass and inertia.
Args:
link: The link to combine with.
lumped_H_removed: The transformation matrix between the two links.
Returns:
The combined link.
"""
# Get the 6D inertia of the link to remove.
I_removed = link.inertia
# Create the SE3 object. Note the inverse.
r_X_l = Adjoint.from_transform(transform=lumped_H_removed, inverse=True)
# Move the inertia
I_removed_in_lumped_frame = r_X_l.transpose() @ I_removed @ r_X_l
# Create the new combined link
lumped_link = self.replace(
mass=self.mass + link.mass,
inertia=self.inertia + I_removed_in_lumped_frame,
)
return lumped_link