Source code for omni.isaac.lab.actuators.actuator_base

# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

from __future__ import annotations

import torch
from abc import ABC, abstractmethod
from collections.abc import Sequence
from typing import TYPE_CHECKING

import omni.isaac.lab.utils.string as string_utils
from omni.isaac.lab.utils.types import ArticulationActions

if TYPE_CHECKING:
    from .actuator_cfg import ActuatorBaseCfg


[docs]class ActuatorBase(ABC): """Base class for actuator models over a collection of actuated joints in an articulation. Actuator models augment the simulated articulation joints with an external drive dynamics model. The model is used to convert the user-provided joint commands (positions, velocities and efforts) into the desired joint positions, velocities and efforts that are applied to the simulated articulation. The base class provides the interface for the actuator models. It is responsible for parsing the actuator parameters from the configuration and storing them as buffers. It also provides the interface for resetting the actuator state and computing the desired joint commands for the simulation. For each actuator model, a corresponding configuration class is provided. The configuration class is used to parse the actuator parameters from the configuration. It also specifies the joint names for which the actuator model is applied. These names can be specified as regular expressions, which are matched against the joint names in the articulation. To see how the class is used, check the :class:`omni.isaac.lab.assets.Articulation` class. """ computed_effort: torch.Tensor """The computed effort for the actuator group. Shape is (num_envs, num_joints).""" applied_effort: torch.Tensor """The applied effort for the actuator group. Shape is (num_envs, num_joints).""" effort_limit: torch.Tensor """The effort limit for the actuator group. Shape is (num_envs, num_joints).""" velocity_limit: torch.Tensor """The velocity limit for the actuator group. Shape is (num_envs, num_joints).""" stiffness: torch.Tensor """The stiffness (P gain) of the PD controller. Shape is (num_envs, num_joints).""" damping: torch.Tensor """The damping (D gain) of the PD controller. Shape is (num_envs, num_joints).""" armature: torch.Tensor """The armature of the actuator joints. Shape is (num_envs, num_joints).""" friction: torch.Tensor """The joint friction of the actuator joints. Shape is (num_envs, num_joints)."""
[docs] def __init__( self, cfg: ActuatorBaseCfg, joint_names: list[str], joint_ids: slice | Sequence[int], num_envs: int, device: str, stiffness: torch.Tensor | float = 0.0, damping: torch.Tensor | float = 0.0, armature: torch.Tensor | float = 0.0, friction: torch.Tensor | float = 0.0, effort_limit: torch.Tensor | float = torch.inf, velocity_limit: torch.Tensor | float = torch.inf, ): """Initialize the actuator. Note: The actuator parameters are parsed from the configuration and stored as buffers. If the parameters are not specified in the configuration, then the default values provided in the arguments are used. Args: cfg: The configuration of the actuator model. joint_names: The joint names in the articulation. joint_ids: The joint indices in the articulation. If :obj:`slice(None)`, then all the joints in the articulation are part of the group. num_envs: Number of articulations in the view. device: Device used for processing. stiffness: The default joint stiffness (P gain). Defaults to 0.0. If a tensor, then the shape is (num_envs, num_joints). damping: The default joint damping (D gain). Defaults to 0.0. If a tensor, then the shape is (num_envs, num_joints). armature: The default joint armature. Defaults to 0.0. If a tensor, then the shape is (num_envs, num_joints). friction: The default joint friction. Defaults to 0.0. If a tensor, then the shape is (num_envs, num_joints). effort_limit: The default effort limit. Defaults to infinity. If a tensor, then the shape is (num_envs, num_joints). velocity_limit: The default velocity limit. Defaults to infinity. If a tensor, then the shape is (num_envs, num_joints). """ # save parameters self.cfg = cfg self._num_envs = num_envs self._device = device self._joint_names = joint_names self._joint_indices = joint_ids # parse joint stiffness and damping self.stiffness = self._parse_joint_parameter(self.cfg.stiffness, stiffness) self.damping = self._parse_joint_parameter(self.cfg.damping, damping) # parse joint armature and friction self.armature = self._parse_joint_parameter(self.cfg.armature, armature) self.friction = self._parse_joint_parameter(self.cfg.friction, friction) # parse joint limits # note: for velocity limits, we don't have USD parameter, so default is infinity self.effort_limit = self._parse_joint_parameter(self.cfg.effort_limit, effort_limit) self.velocity_limit = self._parse_joint_parameter(self.cfg.velocity_limit, velocity_limit) # create commands buffers for allocation self.computed_effort = torch.zeros(self._num_envs, self.num_joints, device=self._device) self.applied_effort = torch.zeros_like(self.computed_effort)
def __str__(self) -> str: """Returns: A string representation of the actuator group.""" # resolve joint indices for printing joint_indices = self.joint_indices if joint_indices == slice(None): joint_indices = list(range(self.num_joints)) return ( f"<class {self.__class__.__name__}> object:\n" f"\tNumber of joints : {self.num_joints}\n" f"\tJoint names expression: {self.cfg.joint_names_expr}\n" f"\tJoint names : {self.joint_names}\n" f"\tJoint indices : {joint_indices}\n" ) """ Properties. """ @property def num_joints(self) -> int: """Number of actuators in the group.""" return len(self._joint_names) @property def joint_names(self) -> list[str]: """Articulation's joint names that are part of the group.""" return self._joint_names @property def joint_indices(self) -> slice | Sequence[int]: """Articulation's joint indices that are part of the group. Note: If :obj:`slice(None)` is returned, then the group contains all the joints in the articulation. We do this to avoid unnecessary indexing of the joints for performance reasons. """ return self._joint_indices """ Operations. """
[docs] @abstractmethod def reset(self, env_ids: Sequence[int]): """Reset the internals within the group. Args: env_ids: List of environment IDs to reset. """ raise NotImplementedError
[docs] @abstractmethod def compute( self, control_action: ArticulationActions, joint_pos: torch.Tensor, joint_vel: torch.Tensor ) -> ArticulationActions: """Process the actuator group actions and compute the articulation actions. It computes the articulation actions based on the actuator model type Args: control_action: The joint action instance comprising of the desired joint positions, joint velocities and (feed-forward) joint efforts. joint_pos: The current joint positions of the joints in the group. Shape is (num_envs, num_joints). joint_vel: The current joint velocities of the joints in the group. Shape is (num_envs, num_joints). Returns: The computed desired joint positions, joint velocities and joint efforts. """ raise NotImplementedError
""" Helper functions. """ def _parse_joint_parameter( self, cfg_value: float | dict[str, float] | None, default_value: float | torch.Tensor | None ) -> torch.Tensor: """Parse the joint parameter from the configuration. Args: cfg_value: The parameter value from the configuration. If None, then use the default value. default_value: The default value to use if the parameter is None. If it is also None, then an error is raised. Returns: The parsed parameter value. Raises: TypeError: If the parameter value is not of the expected type. TypeError: If the default value is not of the expected type. ValueError: If the parameter value is None and no default value is provided. ValueError: If the default value tensor is the wrong shape. """ # create parameter buffer param = torch.zeros(self._num_envs, self.num_joints, device=self._device) # parse the parameter if cfg_value is not None: if isinstance(cfg_value, (float, int)): # if float, then use the same value for all joints param[:] = float(cfg_value) elif isinstance(cfg_value, dict): # if dict, then parse the regular expression indices, _, values = string_utils.resolve_matching_names_values(cfg_value, self.joint_names) # note: need to specify type to be safe (e.g. values are ints, but we want floats) param[:, indices] = torch.tensor(values, dtype=torch.float, device=self._device) else: raise TypeError( f"Invalid type for parameter value: {type(cfg_value)} for " + f"actuator on joints {self.joint_names}. Expected float or dict." ) elif default_value is not None: if isinstance(default_value, (float, int)): # if float, then use the same value for all joints param[:] = float(default_value) elif isinstance(default_value, torch.Tensor): # if tensor, then use the same tensor for all joints if default_value.shape == (self._num_envs, self.num_joints): param = default_value.float() else: raise ValueError( "Invalid default value tensor shape.\n" f"Got: {default_value.shape}\n" f"Expected: {(self._num_envs, self.num_joints)}" ) else: raise TypeError( f"Invalid type for default value: {type(default_value)} for " + f"actuator on joints {self.joint_names}. Expected float or Tensor." ) else: raise ValueError("The parameter value is None and no default value is provided.") return param def _clip_effort(self, effort: torch.Tensor) -> torch.Tensor: """Clip the desired torques based on the motor limits. Args: desired_torques: The desired torques to clip. Returns: The clipped torques. """ return torch.clip(effort, min=-self.effort_limit, max=self.effort_limit)