Tutorial: Creating a Custom PACE Environment and Identifying Parameters¶
Overview¶
This tutorial explains how to set up a custom robot environment within the PACE (Precise Adaptation through Continuous Evolution) framework in order to estimate the dynamics of your system through evolutionary parameter identification.
We will walk through the full pipeline, from defining your environment and actuators, to designing excitation trajectories and running the CMA-ES-based optimization. For clarity and consistency, the examples below use ANYmal as the reference robot. When adapting this tutorial to your own platform, simply replace occurrences of anymal with your robot's name and adjust the corresponding parameters.
The tutorial covers:
- Creating and configuring a PACE environment
- Defining actuator models and optimization boundaries
- Registering the environment with IsaacLab
- Designing excitation trajectories
- Running evolutionary parameter fitting
- Inspecting and comparing optimization results
Creating the Environment¶
The core of the PACE framework is the definition and registration of a task-specific simulation environment. This environment specifies the robot configuration, its actuators, and the parameter ranges used during system identification.
The reference implementation for ANYmal can be found at:
pace-sim2real/source/pace_sim2real/pace_sim2real/tasks/manager_based/pace/anymal_pace_env_cfg.py
This file provides the complete configuration of the robotic system and its associated PACE parameters.
from isaaclab.utils import configclass
from isaaclab_assets.robots.anymal import ANYMAL_D_CFG
from isaaclab.assets import ArticulationCfg
from pace_sim2real.utils import PaceDCMotorCfg
from pace_sim2real import PaceSim2realEnvCfg, PaceSim2realSceneCfg, PaceCfg
import torch
ANYDRIVE_PACE_ACTUATOR_CFG = PaceDCMotorCfg(
joint_names_expr=[".*HAA", ".*HFE", ".*KFE"],
saturation_effort=140.0,
effort_limit=89.0,
velocity_limit=8.5,
stiffness={".*": 85.0}, # P gain in Nm/rad
damping={".*": 0.6}, # D gain in Nm s/rad
encoder_bias=[0.0] * 12, # encoder bias in radians
max_delay=10, # max delay in simulation steps
)
@configclass
class AnymalDPaceCfg(PaceCfg):
"""Pace configuration for Anymal-D robot."""
robot_name: str = "anymal_d_sim"
data_dir: str = "anymal_d_sim/chirp_data.pt" # located in pace_sim2real/data/anymal_d_sim/chirp_data.pt
bounds_params: torch.Tensor = torch.zeros((49, 2)) # 12 + 12 + 12 + 12 + 1 = 49 parameters to optimize
joint_order: list[str] = [
"LF_HAA",
"LF_HFE",
"LF_KFE",
"RF_HAA",
"RF_HFE",
"RF_KFE",
"LH_HAA",
"LH_HFE",
"LH_KFE",
"RH_HAA",
"RH_HFE",
"RH_KFE",
]
def __post_init__(self):
# set bounds for parameters
self.bounds_params[:12, 0] = 1e-5
self.bounds_params[:12, 1] = 1.0 # armature between 1e-5 - 1.0 [kgm2]
self.bounds_params[12:24, 1] = 7.0 # dof_damping between 0.0 - 7.0 [Nm s/rad]
self.bounds_params[24:36, 1] = 0.5 # friction between 0.0 - 0.5
self.bounds_params[36:48, 0] = -0.1
self.bounds_params[36:48, 1] = 0.1 # bias between -0.1 - 0.1 [rad]
self.bounds_params[48, 1] = 10.0 # delay between 0.0 - 10.0 [sim steps]
@configclass
class ANYmalDPaceSceneCfg(PaceSim2realSceneCfg):
"""Configuration for Anymal-D robot in Pace Sim2Real environment."""
robot: ArticulationCfg = ANYMAL_D_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot", init_state=ArticulationCfg.InitialStateCfg(pos=(0.0, 0.0, 1.0)),
actuators={"legs": ANYDRIVE_PACE_ACTUATOR_CFG})
@configclass
class AnymalDPaceEnvCfg(PaceSim2realEnvCfg):
scene: ANYmalDPaceSceneCfg = ANYmalDPaceSceneCfg()
sim2real: PaceCfg = AnymalDPaceCfg()
def __post_init__(self):
# post init of parent
super().__post_init__()
# robot sim and control settings
self.sim.dt = 0.0025 # 400Hz simulation
self.decimation = 1 # 400Hz control
The custom environment consists of:
- The robot model and its actuators
- The optimization parameter bounds for CMA-ES
- PACE-specific configuration linking simulation and real-world data
The following sections describe each component in detail.
Creating the PACE DC Motors¶
ANYDRIVE_PACE_ACTUATOR_CFG = PaceDCMotorCfg(
joint_names_expr=[".*HAA", ".*HFE", ".*KFE"],
saturation_effort=140.0,
effort_limit=89.0,
velocity_limit=8.5,
stiffness={".*": 85.0},
damping={".*": 0.6},
encoder_bias=[0.0] * 12,
max_delay=10,
)
ANYmal uses a single drive type, but your robot may employ multiple actuator variants. In such cases, create one PaceDCMotorCfg per actuator type and associate the corresponding joints via joint_names_expr.
Key parameters include:
saturation_effort: peak torque capabilityeffort_limit: hardware-imposed torque limitvelocity_limit: maximum joint velocitystiffnessanddamping: internal PD gains used by the real actuatorencoder_bias: initial bias estimate (set to zero initially)max_delay: maximum expected actuator delay in simulation steps
If your robot uses a specialized actuator model, you can extend PaceDCMotorCfg by inheriting from your custom class and augmenting it with delay and encoder bias functionality.
Defining PACE specific configs¶
@configclass
class AnymalDPaceCfg(PaceCfg):
...
The AnymalDPaceCfg block defines algorithmic parameters and data linkage:
robot_name: determines logging directorydata_dir: path to recorded trajectory datajoint_order: alignment with real databounds_params: optimization limits
Optional CMA-ES settings (iterations, sigma, epsilon, logging frequency) can also be configured here.
IsaacLab determines joint ordering via breadth-first traversal, which may differ from the ordering used in your real robot’s control stack or logged data. To ensure correct alignment between simulated and real trajectories, explicitly define the joint order used on your physical system here.
PACE normalizes optimization parameters to the range [-1, 1]. However, the physically meaningful parameter limits are defined via bounds_params, which constrains the sampling domain of the CMA-ES optimizer.
The parameters are laid out as:
- Armature:
[0 : n_joints] - Viscous friction:
[n_joints : 2*n_joints] - Static friction:
[2*n_joints : 3*n_joints] - Encoder bias:
[3*n_joints : 4*n_joints] - Global delay:
[-1]
def __post_init__(self):
# set bounds for parameters
...
The values should be chosen conservatively based on hardware knowledge or prior estimates, and can be refined iteratively as needed.
Creating the Scene¶
@configclass
class ANYmalDPaceSceneCfg(PaceSim2realSceneCfg):
...
Here, you inherit from PaceSim2realSceneCfg and specify your robot USD, initial pose, and associated actuators. Ensure the initial height prevents ground penetration or unwanted contacts. Actuator naming is flexible and purely user-defined.
Registering the Environment¶
To expose the environment to the optimization pipeline, it must be registered within IsaacLab’s task registry:
pace-sim2real/source/pace_sim2real/pace_sim2real/tasks/manager_based/__init__.py
Following the naming convention Isaac-Pace-<robot>-v0:
gym.register(
id="Isaac-Pace-Anymal-D-v0",
entry_point="isaaclab.envs:ManagerBasedRLEnv",
disable_env_checker=True,
kwargs={
"env_cfg_entry_point": f"{__name__}.pace.anymal_pace_env_cfg:AnymalDPaceEnvCfg"
},
)
Designing the Excitation Trajectory¶
Well-designed trajectories are critical for accurate parameter estimation. PACE provides a script to prototype and validate trajectories before deployment on real hardware:
pace-sim2real/scripts/pace/data_collection.py
The following section temporarily injects example parameters for testing and can be omitted.
armature = torch.tensor([0.1] * len(joint_ids), device=env.unwrapped.device).unsqueeze(0)
damping = torch.tensor([4.5] * len(joint_ids), device=env.unwrapped.device).unsqueeze(0)
friction = torch.tensor([0.05] * len(joint_ids), device=env.unwrapped.device).unsqueeze(0)
bias = torch.tensor([0.05] * 12, device=env.unwrapped.device).unsqueeze(0)
time_lag = torch.tensor([[5]], dtype=torch.int, device=env.unwrapped.device)
Trajectory design is controlled through:
trajectory_direction: symmetric joint motion to cancel base forcestrajectory_bias: center of oscillationtrajectory_scale: oscillation amplitude
duration = args_cli.duration # seconds
sample_rate = 1 / env.unwrapped.sim.get_physics_dt() # Hz
num_steps = int(duration * sample_rate)
t = torch.linspace(0, duration, steps=num_steps, device=env.unwrapped.device)
f0 = args_cli.min_frequency # Hz
f1 = args_cli.max_frequency # Hz
# Linear chirp: phase = 2*pi*(f0*t + (f1-f0)/(2*duration)*t^2)
phase = 2 * pi * (f0 * t + ((f1 - f0) / (2 * duration)) * t ** 2)
chirp_signal = torch.sin(phase)
trajectory = torch.zeros((num_steps, len(joint_ids)), device=env.unwrapped.device)
trajectory[:, :] = chirp_signal.unsqueeze(-1)
trajectory_directions = torch.tensor(
[1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0],
device=env.unwrapped.device
)
trajectory_bias = torch.tensor(
[0.0, 0.4, 0.8] * 4,
device=env.unwrapped.device
)
trajectory_scale = torch.tensor(
[0.25, 0.5, -2.0] * 4,
device=env.unwrapped.device
)
trajectory[:, joint_ids] = (trajectory[:, joint_ids] + trajectory_bias.unsqueeze(0)) * trajectory_directions.unsqueeze(0) * trajectory_scale.unsqueeze(0)
While chirp-based signals provide a practical baseline, more advanced trajectory optimization approaches may further improve system identification quality.
Evolutionary Parameter Fitting¶
Once data collection is complete and stored in the correct directory, launch the optimization using:
cd ~/pace-sim2real
python scripts/pace/fit.py --headless --num_envs=4096 --task=Isaac-Pace-Anymal-D-v0
This process utilizes the CMAESOptimizer located at:
pace-sim2real/source/pace_sim2real/pace_sim2real/optim/cma_es.py
Logging and Optimization Inspection¶
All optimization logs are stored under:
pace-sim2real/logs/pace/anymal_d_sim
Key files include:
config.pt: full configuration and dataset metadatabest_trajectory.pt: best simulated joint trajectorymean_xxx.pt: mean parameter estimates per iteration
If logging of the full optimization process is enabled:
save_optimization_process = True
Additional files include:
progress.pt: best scores and complete parameter history over the iterations
Comparing Trajectories¶
During or after optimization, visualize the current best trajectory using:
python scripts/pace/plot_trajectory.py --plot_trajectory --robot_name=anymal_d_sim
After completion, include --plot_score to visualize convergence behavior over iterations if desired.
Deploying on Hardware¶
To use the identified parameters in your learning environment, integrate the PACE actuator model into your project.
Copy the following files (or directly import them in your learning environment):
pace-sim2real/source/pace_sim2real/pace_sim2real/utils/pace_actuator.pypace-sim2real/source/pace_sim2real/pace_sim2real/utils/pace_actuator_cfg.py
Use the following procedure:
- Import
PaceDCMotorCfg(and optionallyPaceDCMotor) into your project.from pace_sim2real.utils import PaceDCMotorCfg, PaceDCMotor -
During initialization, set:
max_delayencoder_bias
using the values obtained from the parameter identification step.
-
Prior to training, configure your articulation object with the optimized parameters for:
joint_armaturejoint_viscous_frictionjoint_friction
This concludes the complete workflow for creating a custom PACE environment and performing evolutionary system identification. Adjust parameters iteratively based on observed performance and physical plausibility to achieve optimal sim-to-real alignment.