Source code for embodichain_benchmarks.motion_generation.planners.ik_interpolate
# ----------------------------------------------------------------------------
# Copyright (c) 2021-2026 DexForce Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ----------------------------------------------------------------------------
"""Sequential IK plus joint interpolation diagnostic adapter."""
from __future__ import annotations
import math
import torch
from embodichain.lab.sim.motion.planners import PlanResult
from embodichain.compute.trajectory import interpolate_with_distance
from ..models import BenchmarkCase
from ..registry import register_planner_adapter
from .base import PlannerAdapter
__all__ = ["IkInterpolateAdapter"]
[docs]
class IkInterpolateAdapter(PlannerAdapter):
"""Use the robot IK solver followed by fixed-count joint interpolation."""
capabilities = frozenset(
{"eef_waypoint", "joint_waypoint", "batched", "empty_world"}
)
# Timing stays nominal (the base default): every interpolated step is
# assigned the configured `interpolation_dt`, which no limit informs.
[docs]
def build(self) -> None:
"""Freeze shared limits; no planner object is required."""
self.resolve_motion_limits()
[docs]
def plan(self, case: BenchmarkCase) -> PlanResult:
"""Solve each waypoint sequentially while retaining per-env failures."""
robot = self.context.robot
interpolation_dt = self.spec.config.get("interpolation_dt")
if isinstance(interpolation_dt, bool) or not isinstance(
interpolation_dt, (int, float)
):
raise ValueError(
"ik_interpolate requires an explicit numeric interpolation_dt."
)
interpolation_dt = float(interpolation_dt)
if not math.isfinite(interpolation_dt) or interpolation_dt <= 0.0:
raise ValueError("interpolation_dt must be finite and greater than zero.")
seed = case.start_qpos
alive = torch.ones(case.batch_size, dtype=torch.bool, device=robot.device)
targets = [seed]
validity = case.case_parameters.get(
"motion_validity", "ordered_cartesian_waypoints"
)
if validity == "ordered_joint_waypoints":
if case.reference_qpos.shape != (
case.batch_size,
case.num_waypoints,
seed.shape[-1],
):
raise ValueError(
"Joint cases require one frozen reference configuration per waypoint."
)
alive &= torch.isfinite(case.reference_qpos).all(dim=(1, 2))
targets.extend(case.reference_qpos.unbind(dim=1))
elif validity == "ordered_cartesian_waypoints":
for waypoint_index in range(case.num_waypoints):
success, solved = robot.compute_ik(
pose=case.target_waypoints[:, waypoint_index],
name=self.context.control_part,
joint_seed=seed,
)
success_tensor = torch.as_tensor(
success, dtype=torch.bool, device=robot.device
).flatten()
if success_tensor.numel() == 1 and case.batch_size > 1:
success_tensor = success_tensor.expand(case.batch_size)
alive &= success_tensor
solved = torch.as_tensor(solved, device=robot.device, dtype=seed.dtype)
seed = torch.where(success_tensor[:, None], solved, seed)
targets.append(seed)
else:
raise ValueError(f"Unsupported motion_validity mode {validity!r}.")
sparse_path = torch.stack(targets, dim=1)
positions = interpolate_with_distance(
trajectory=sparse_path,
interp_num=self.context.sample_interval,
device=robot.device,
)
dt = torch.zeros(positions.shape[:2], dtype=torch.float32, device=robot.device)
if positions.shape[1] > 1:
dt[:, 1:] = interpolation_dt
return PlanResult(
success=alive,
positions=positions,
dt=dt,
)
register_planner_adapter("ik_interpolate", IkInterpolateAdapter)