[Bug]: "TypeError: not a sequence" Error with Multi-DoF Joints in Custom MJCF Files
Bug Description
Description The official reinforcement learning (RL) code is designed for the Go2 robot, which assumes a one-to-one relationship between joints and degrees of freedom (1 joint = 1 DoF). When using a custom MJCF file where a single joint has multiple degrees of freedom (1 joint > 1 DoF), the code raises the following error: "TypeError: not a sequence".
This issue arises because the code does not handle scenarios where different links contains different numbers of Dofs (e.g., entity has different type of joints which contains different number of dofs).
Steps to Reproduce
To ReproduceSteps to reproduce the issue:
Within go2_env.py,replace the default Go2 robot's file with a custom MJCF file where at least one joint defines multiple degrees of freedom (e.g., 3 rotational DoFs). Probably ./genesis/asset/xml/ant.xml is enough, because it contains a free joint.
Run go2_train.py. Revise the parameters in get_cfgs() according to the custom MJCF, especially: "num_actions", "default_joint_angles", "dof_names"(which I thinks should be named as "joint_names") in env_cfg, and "num_obs" in obs_cfg. By the way, it would be better if the code can change this value automaticlly.
Observe the following error:
Traceback (most recent call last):
File "/Genesis/examples/locomotion/go2_train.py", line 315, in <module>
main()
File "/Genesis/examples/locomotion/go2_train.py", line 300, in main
env = Go2Env(
File "/Genesis/examples/locomotion/go2_env.py", line 26, in __init__
self._initialize_environment(num_envs, show_viewer)
File "/Genesis/examples/locomotion/go2_env.py", line 63, in _initialize_environment
self.robot.set_dofs_kp([self.env_cfg["kp"]] * self.env_cfg["num_actions"], self.motor_dofs)
File "/anaconda3/envs/genesis/lib/python3.10/site-packages/genesis/utils/misc.py", line 48, in wrapper
return method(self, *args, **kwargs)
File "/anaconda3/envs/genesis/lib/python3.10/site-packages/genesis/engine/entities/rigid_entity/rigid_entity.py", line 1807, in set_dofs_kp
self._solver.set_dofs_kp(kp, self._get_dofs_idx(dofs_idx_local), envs_idx)
File "/anaconda3/envs/genesis/lib/python3.10/site-packages/genesis/engine/entities/rigid_entity/rigid_entity.py", line 1757, in _get_dofs_idx
return self._get_dofs_idx_local(dofs_idx_local) + self._dof_start
File "/anaconda3/envs/genesis/lib/python3.10/site-packages/genesis/engine/entities/rigid_entity/rigid_entity.py", line 1751, in _get_dofs_idx_local
dofs_idx_local = torch.as_tensor(dofs_idx_local, dtype=gs.tc_int, device=gs.device)
TypeError: not a sequence
Expected Behavior
Maybe a more general code for RL training would suite more users. The code should correctly handle MJCF/URDF files with joints that have multiple DoFs, either by:
Automatically flattening or mapping joint configurations to a consistent sequence.
Raising a clear error message with suggestions to fix the input data.
Screenshots/Videos
No response
Relevant log output
Environment
- OS: Ubuntu 22.04
- GPU/CPU:NVIDIA GeForce RTX 4090
- GPU-driver version: 550.90.07
- CUDA / CUDA-toolkit version: CUDA Version: 12.4
Release version or Commit ID
commit hash: 36b6670a4a0783d4ed58d6cd8edbd5662727677f
Additional Context
I've tried to fix this error by adding the following changes:
-
change go2_env.py line71 from:
self.motor_dofs = [self.robot.get_joint(name).dof_idx_local for name in self.env_cfg["dof_names"]]to:self.motor_dofs = list(itertools.chain.from_iterable( [self.robot.get_joint(name).dof_idx_local] if isinstance(self.robot.get_joint(name).dof_idx_local, int) else self.robot.get_joint(name).dof_idx_local for name in self.env_cfg["dof_names"] )) -
change go2_env.py line108 - line 113 from:
self.default_dof_pos = torch.tensor( [self.env_cfg["default_joint_angles"][name] for name in self.env_cfg["dof_names"]], device=self.device, dtype=gs.tc_float, )
to:
flat_dof_pos = list(itertools.chain.from_iterable( [value] if isinstance(value, (int, float)) else value for value in [self.env_cfg["default_joint_angles"][name] for name in self.env_cfg["dof_names"]] ))
self.default_dof_pos = torch.tensor(flat_dof_pos, device=self.device, dtype=gs.tc_float)
My understanding is that Genesis is a physics engine and not an RL environment. The RL example is just a simple showcase of how you could use Genesis for RL training.
@Kashu7100 Thank you for your clarification! I completely understand that Genesis is primarily a physics engine, and the existing RL example is more of a functional demonstration. My question stems from some compatibility issues I encountered while trying to adapt the code to a custom model, and I agree that it’s unreasonable to expect the demo code to be universally applicable.
Regarding the manual configuration of num_actions and num_obs, I’d like to ask: Does Genesis provide any API interfaces that can automatically retrieve the following information to avoid hardcoding? For example:
num_actions:
Is it possible to directly obtain the total degrees of freedom (DoFs) of the entity through something like self.robot.num_dofs, instead of manually counting joint configurations?
(For instance, if the MJCF contains multiple joints with multiple DoFs, num_actions should be the sum of all DoFs across joints.)
num_obs:
Can the observation space dimension be dynamically obtained through something like self.obs_buf.shape[-1]?
Or is there a predefined observation calculation tool (e.g., the dimension of the return value from get_observations())?
Desired automatic configuration (pseudo-code)
env_cfg = { "num_actions": self.robot.total_dofs, # Assuming a total_dofs attribute exists "dof_names": self.robot.active_joint_names, # Assuming a list of joint names exists "default_joint_angles": self.robot.neutral_positions # Assuming neutral positions exist }
If such interfaces exist, could you provide an example of how to use them? This would be very helpful for adapting different models.
Additionally, I attempted to fix the code by flattening the motor_dofs and default_dof_pos (see the modifications in the original Issue). Do you think this approach aligns with Genesis’s design logic? Is there a more recommended way to implement this?