Source code for genesis.engine.sensors.joint_torque
from dataclasses import dataclass
from typing import TYPE_CHECKING
import torch
import genesis as gs
from genesis.options.sensors import JointTorque as JointTorqueOptions
from genesis.utils.misc import concat_with_tensor, make_tensor_field
from .base_sensor import SimpleSensor, SimpleSensorMetadata
if TYPE_CHECKING:
from .sensor_manager import SensorManager
@dataclass
class JointTorqueSensorMetadata(SimpleSensorMetadata):
"""Shared state for all JointTorqueSensor instances in a scene."""
solver: "gs.RigidSolver | None" = None
# Global DOF indices concatenated across all sensor instances in scene order.
dofs_idx: torch.Tensor = make_tensor_field((0,), dtype_factory=lambda: gs.tc_int)
[docs]class JointTorqueSensor(SimpleSensor[JointTorqueOptions, None, JointTorqueSensorMetadata]):
"""
Measures the generalized effort transmitted from each actuator to its joint output shaft (torque for revolute
DOFs, force for prismatic DOFs).
The reading is the commanded actuator effort minus the gearbox losses, derived from Newton's 3rd law at the
gearbox interface:
actuator_force = tau_control - armature * qacc + tau_frictionloss + tau_damping
where ``tau_damping = -damping * vel`` is the viscous passive effort. Gravity, Coriolis and contact loads are
captured implicitly through the constraint-solved acceleration ``qacc``.
"""
def __init__(
self,
options: JointTorqueOptions,
idx: int,
shared_context: None,
shared_metadata: JointTorqueSensorMetadata,
manager: "SensorManager",
):
# Resolve dofs_idx_local=None to the full DOF range before super().__init__(), because _get_return_format() is
# called inside the base constructor and needs the final DOF count.
if options.dofs_idx_local is None:
entity = manager._sim.entities[options.entity_idx]
options.dofs_idx_local = tuple(range(entity.n_dofs))
super().__init__(options, idx, shared_context, shared_metadata, manager)
def _get_return_format(self) -> tuple[int, ...]:
return (len(self._options.dofs_idx_local),)
@classmethod
def _get_cache_dtype(cls) -> torch.dtype:
return gs.tc_float
[docs] def build(self):
super().build()
if self._shared_metadata.solver is None:
self._shared_metadata.solver = self._manager._sim.rigid_solver
entity = self._manager._sim.entities[self._options.entity_idx]
dofs_idx = torch.tensor(
[entity.dof_start + i for i in self._options.dofs_idx_local],
dtype=gs.tc_int,
device=gs.device,
)
self._shared_metadata.dofs_idx = concat_with_tensor(self._shared_metadata.dofs_idx, dofs_idx, dim=0)
@classmethod
def _update_raw_data(
cls,
shared_context: None,
shared_metadata: JointTorqueSensorMetadata,
raw_data_T: torch.Tensor, # shape (n_sensor_dofs, B)
):
solver = shared_metadata.solver
actuator_force = solver.get_dofs_actuator_force(shared_metadata.dofs_idx)
if solver.n_envs == 0:
actuator_force = actuator_force[None]
raw_data_T.copy_(actuator_force.T)