SSIKSolver#
SSIKSolver derives geometric IK or selects a verified prepared algebraic
model from the configured robot’s actual serial chain. Its Warp backend runs
double precision computation on CPU and CUDA, then filters candidates by joint limits and
forward-kinematics error. Its optional reference backend uses the pinned
SSIK implementation
on CPU for cross-checking and offline-prepared algebraic models.
Supported geometry#
Backend |
Supported chains |
Execution |
|---|---|---|
|
6R with parallel joint axes 1, 2 and 3 (zero based); strict 7R with concurrent shoulder and wrist axes |
CPU or CUDA; geometric candidates only |
|
Certified small deviations from three-parallel 6R geometry, including ARX5 X5A |
CPU or CUDA; geometric initialization plus bounded original-model numerical polishing |
|
Verified prepared Rizon4 joint prefix, including Rizon4S with its actual terminal frame |
CPU or CUDA; Warp algebraic matrix-pencil roots |
|
Compatibility alias for the prepared RR path |
CUDA only; prefer |
|
SSIK-dispatched 6R/7R models, with an offline artifact required for generic algebraic paths |
CPU only; optional SSIK dependency |
The Warp paths cover Aloha Mini, Tianji Marvin, ARX5 and Flexiv Rizon4S on
CPU and CUDA. ARX5 and Rizon4S use the explicit near-parallel and RR modes
above; they do not meet the strict geometric conditions. UR5 and strict
SRS W1 revisions provide additional geometry checks. Classification uses joint
axes and transforms rather than robot names. Approximate SRS, Franka-specific
geometry, and unprepared RR/HP algebraic models remain unsupported.
Unsupported geometry raises ValueError during initialization.
Aloha Mini’s two arm defaults use strict Warp SSIK. Both Tianji Marvin
variants use Warp SSIK with srs_search="seed_local". CPU and CUDA use the
same configured geometry and acceptance thresholds, without the optional SSIK
reference dependency. ARX5 defaults to geometry_mode="near_parallel";
Rizon4S defaults to geometry_mode="rr", rr_search="seed_local".
Aloha’s torso retains its position-only Pytorch solver.
Explicit solver_cfg replacements remain authoritative. All finite searches
can miss solutions; inspect the validity mask before executing a result.
The benchmark reports record the measured models, control paths and hardware windows separately. CUDA resource measurements include driver allocations alongside Torch/Warp counters. A preset’s default does not establish a latency guarantee for arbitrary workloads or concurrent rendering.
Configuration#
This example builds a preset’s kinematic chain without starting a simulation or renderer. Preserve its root, end link, joint order and TCP when replacing the solver configuration.
import torch
from embodichain.lab.sim.motion.solvers import SSIKSolverCfg
from embodichain_robots import AlohaMiniCfg
device = torch.device("cpu") # Use torch.device("cuda") for CUDA execution.
robot_cfg = AlohaMiniCfg.from_dict({"uid": "aloha_mini"})
part = "left_arm"
old_cfg = robot_cfg.solver_cfg[part]
chain = robot_cfg.build_pk_serial_chain(device=device)[part]
cfg = SSIKSolverCfg(
urdf_path=robot_cfg.fpath,
root_link_name=old_cfg.root_link_name,
end_link_name=old_cfg.end_link_name,
joint_names=robot_cfg.control_parts[part],
tcp=old_cfg.tcp,
backend="warp",
)
solver = cfg.init_solver(device=device, pk_serial_chain=chain)
seed = solver.get_default_qpos_seed().unsqueeze(0)
target = solver.get_fk(seed)
success, qpos = solver.get_ik(target, qpos_seed=seed)
valid, candidates = solver.get_ik(
target, qpos_seed=seed, return_all_solutions=True
)
For Marvin, replace the robot preset with
TianjiMarvinCfg.from_dict({"uid": "marvin", "with_gripper": False}) and
preserve that preset’s TCP in the same way. Both arms and both gripper variants
use their selected source end link. Aloha Mini’s torso uses a prismatic joint
and is outside this solver’s supported chain types.
Setting |
Default |
Meaning |
|---|---|---|
|
|
Explicit execution backend; failures never switch backends |
|
|
|
|
|
Maximum normalized-axis deviation in near-parallel mode; does not relax FK acceptance |
|
|
|
|
|
For Warp |
|
|
Global SRS swivel samples, in addition to a defined seed swivel |
|
|
Maximum accepted position error, metres |
|
|
Maximum accepted orientation error, radians |
|
|
Optional hard per-joint distance from the seed or previous path frame, radians |
|
|
Maximum target count per candidate-generation chunk |
|
|
Path to an offline-generated reference Python artifact |
Tip
For streamed control, pass the previous successful joint solution as the next
seed. Set max_joint_step to the largest acceptable per-frame joint change.
When no candidate satisfies that constraint, inspect the returned failure mask
before commanding the robot.
Results and solution selection#
Direct get_ik inputs are root-relative TCP transforms shaped (N, 4, 4).
Seeds may have shape (N, dof) or (1, dof). Omitting the seed uses the
effective joint-limit midpoint. The ordinary result chooses the valid branch
nearest to the limit-projected seed using the configured ik_nearest_weight.
Call |
Validity shape |
Joint shape |
|---|---|---|
|
|
|
|
|
|
|
|
|
The strict Warp candidate axis has eight slots for 6R, or
8 * (num_samples + 1) slots for uniform SRS 7R, or
8 * (num_samples + 2) with seed-local search. Fixed prepared RR returns all
384 slots from its 16 locked-joint samples and 24 pencil-root slots per sample.
RR seed_local returns 600 slots from the same 16 global and nine local samples.
These are conditional candidates, not a complete continuous 7R manifold.
Near-parallel mode returns nine slots: eight geometric initializations and one
always-computed seed initialization for bounded original-model correction.
This enumerates that hybrid algorithm’s candidates; it does not promise all
algebraic roots of a general, nonparallel 6R model.
Slots remain masked if their branch
does not exist, violates limits, exceeds the step constraint, or fails the FK
gate. The batch axis is retained when N == 1; an empty batch is supported.
Outputs contain float32 joint positions, and the FK gate checks the returned
precision. Internal geometric calculations use float64.
Failed or invalid slots contain a finite, limit-projected seed and have a false mask. Treat the mask as authoritative. A failed Warp call does not invoke numerical IK, SSIK reference, or any other fallback. Finite 7R sampling does not enumerate its full continuous solution manifold: failure cannot prove global unreachability. A defined seed swivel is considered alongside the global grid; it is skipped if the seed swivel is geometrically undefined.
Each geometric branch selects the joint-period representative nearest to the
seed within the effective limits. Multiple winding numbers are not enumerated.
Candidates within 1e-6 radians of a limit may be projected onto that limit to
absorb input and model representation roundoff. The returned joint positions
must still lie strictly within the limits and pass the unchanged FK gates.
For ill-conditioned 6R branches, analytical reconstruction can recover a
representative at a nearby wrist or shoulder limit. It recomputes the coupled
joint angles geometrically and applies the same final limits and FK checks;
it does not enable an iterative IK fallback. Tangent subproblems account for
coordinate roundoff when a caller supplies float32 target matrices.
Singular wrists produce finite seed-dependent representatives of their
continuous family rather than an infinite list.
Nearest mode bounds candidate scratch by chunk_size. All-candidate mode
additionally allocates the complete (N, K, dof) output, so its output memory
still grows with the total batch and SRS sample count. Returned tensors retain
their values across later solver calls. prepare_buffers(max_batch) can
reserve bounded internal scratch before repeated calls.
Selecting the numerical solver#
Explicit solver configurations remain authoritative. Restore the original numerical arm settings while preserving source frames, TCP, limits and weights with the following replacement. This applies to Aloha Mini, either Marvin variant, ARX5 and Rizon4S:
from copy import deepcopy
from embodichain.lab.sim.motion.solvers import PytorchSolverCfg
parts = ("arm",) if "arm" in robot_cfg.solver_cfg else ("left_arm", "right_arm")
for part in parts:
current = robot_cfg.solver_cfg[part]
robot_cfg.solver_cfg[part] = PytorchSolverCfg(
urdf_path=robot_cfg.fpath,
joint_names=robot_cfg.control_parts[part],
root_link_name=current.root_link_name,
end_link_name=current.end_link_name,
tcp=deepcopy(current.tcp),
user_qpos_limits=deepcopy(current.user_qpos_limits),
ik_nearest_weight=deepcopy(current.ik_nearest_weight),
num_samples=30,
max_iterations=500,
pos_eps=5e-4,
rot_eps=5e-4,
)
For a serialized replacement, use the new PytorchSolverCfg.to_dict() values
above, including class_type: PytorchSolver, the root/end links and the TCP.
A dictionary containing class_type replaces the part independently; it does
not inherit those integration fields. A class-type-only override therefore
loses the preset frames and can reset Marvin’s TCP. Construct a fresh numerical
config rather than changing the class label on an SSIK dictionary: copying its
num_samples=128 would retain that value instead of restoring the original
numerical setting of 30. A partial dictionary without class_type only changes
attributes of the selected default solver.
Optional CPU reference and offline artifacts#
Install the optional reference separately from the repository root:
pip install -e .
pip install -r requirements/ssik-reference.txt
The reference requirements are fixed to SSIK commit
4109147f37a7ca3e2516c83853aca583612783f3. Importing the ordinary solver package
and running the Warp backend do not require SSIK. The reference backend checks
the installed source revision and rejects CUDA devices. The VCS pin is kept
out of published wheel dependency metadata, as required by the release policy.
For the geometry in the example above, construct the reference using the same CPU chain:
reference_cfg = cfg.copy()
reference_cfg.backend = "reference"
reference_solver = reference_cfg.init_solver(
device="cpu",
pk_serial_chain=robot_cfg.build_pk_serial_chain(device=torch.device("cpu"))[part],
)
reference_success, reference_qpos = reference_solver.get_ik(
target.cpu(), qpos_seed=seed.cpu()
)
Generic algebraic models must be built explicitly before initialization. This command writes a Python artifact and an adjacent JSON manifest:
python -m embodichain.lab.sim.motion.solvers._ssik_reference \
--urdf /absolute/path/robot.urdf \
--root base_link --end flange \
--output /absolute/path/my_arm_ik.py
Set reference_artifact="/absolute/path/my_arm_ik.py" on a reference config
using the identical chain. Loading checks the SSIK revision, kinematic
fingerprint, ordered joint names, root/end links and artifact source hash
before import. Do not edit a generated artifact independently of its manifest.
Symbolic generation never runs inside a runtime IK call.
Reference evaluation disables optional rescue and optional refinement. Some SSIK families still contain internal root polishing or near-geometry numerical correction; reference success for those families does not establish a pure closed-form Warp implementation. The reference backend’s branch counts are determined by upstream SSIK rather than the Warp fixed-slot layout.
Prepared ARX5 and Rizon4S models#
For device execution, preserve the preset’s original root, end link and TCP as
in the first example, then set geometry_mode="near_parallel" for ARX5, or
backend="warp", geometry_mode="rr" on CPU or CUDA for Rizon4S. Neither requires installing
SSIK. These are the validated presets’ defaults; Rizon4S also selects
rr_search="seed_local". Explicit Pytorch replacements remain available as
shown above. ARX5 keeps its authored 85 mm TCP and tool rotation.
ARX5’s device path generates eight surrogate geometric branches and one seed initialization, then performs at most twelve monotone Newton corrections on the actual 6R model, with bounded joint updates. This is an explicitly selected numerical correction, not a pure closed-form result. Failure is returned without trying another solver.
The Rizon4S device path evaluates the prepared polynomial tensors, solves their
24-by-24 shifted matrix pencils with Warp QR, and back-substitutes roots on
the selected device. CPU uses scalar Warp scheduling and CUDA uses cooperative
warps; they share the coefficient system, samples and projective recovery. Workspaces are chunked to at most 32 targets; every
root slot passes through the original-model FK gate after float32 conversion.
Production queries use neither SciPy/LAPACK nor the official SSIK package,
and never generate symbolic coefficients. The private QZ oracle and optional
SSIK reference are used only for explicit numerical validation.
The default rr_search="fixed" preserves the 16-lock coefficient bundle.
For continuous control, select rr_search="seed_local" explicitly. Its
parameterized bundle evaluates arbitrary lock angles without runtime symbolic
derivation. Nine extra samples include the preceding lock, symmetric local
offsets and two original-model Jacobian predictions. The predictions select
only a lock angle: all returned joint solutions still come from algebraic RR
roots. They are not iterative Cartesian IK or a numerical fallback. The same
global pool remains present, and hard limits, step bounds and FK acceptance
mask every slot.
The parameterized path checks that the input is a finite rigid pose, then
applies two fixed polar-conditioning steps to the internal rotation used for
coefficient evaluation. This prevents float32 frame-conversion roundoff from
violating the exact SO(3) polynomial identities. It does not repair malformed
targets, change the target used for final original-model FK validation, or
relax either acceptance tolerance.
Existing real roots receive three bounded determinant-Newton corrections on
the original quadratic matrix pencil. CPU and CUDA call the same Warp kernel;
the reciprocal chart keeps large roots numerically bounded. This corrects
shift-invert roundoff without changing the sampled locks, root masks or
candidate count. It is algebraic root refinement, and does not introduce a
joint-space or Cartesian IK fallback. Unsafe corrections retain the last safe
root; the original-model FK gate still decides whether a candidate is valid.
ARX5’s target-aware batch path hook regenerates its nine candidates and their
periodic representatives from each preceding result on CPU and CUDA. For
single-target streaming, pass the preceding result explicitly. Rizon4S’s fixed
grid batch selector uses the initial path seed. Its seed_local path hook
regenerates the full pool using each preceding selected posture. Finite lock
sampling can still miss a feasible interval. Successful static IK and low
single-call latency alone do not establish bounded continuous coverage.
The bundled coefficient manifest records the source revision, hashes and an
independent DH/POE consistency certificate for every locked-joint chart. The
zero-lock chart corrects an upstream DH link distance using the source axis
geometry before offline rederivation. Reproduce the bundle with
scripts/benchmark/robotics/kinematic_solver/prepare_ssik_rr_rizon4.py and the
pinned SSIK source. CPU reference artifacts below preserve upstream behavior;
their zero-lock output is not an oracle for this corrected chart.
The separate parameterized bundle uses POE joint-frame factors and affine
sin/cos lock coefficients. Its manifest includes exact reconstruction identities,
coefficient hashes and an independent original-PK pose certificate. Rebuild it
with the same maintained builder, adding --parameterized --urdf PATH and
--ssik-source PINNED_SOURCE. Both bundles are packaged independently; loading
validates the selected bundle and actual model before device execution.
The bundled robot package contains explicit offline reference artifacts for
ARX5 (the registered X5A source) and FlexivRizon4S. These artifacts are
optional validation tools. Install the pinned reference requirements above, then select the
prepared artifact explicitly:
from importlib.resources import files
import torch
from embodichain.lab.sim.motion.solvers import SSIKSolverCfg
from embodichain_robots import ARX5Cfg, FlexivRizon4SCfg
robot_cfg = ARX5Cfg.from_dict({})
artifact_name = "arx5.py"
# For the Rizon 4S source model instead:
# robot_cfg = FlexivRizon4SCfg.from_dict({})
# artifact_name = "flexiv_rizon4s.py"
previous = robot_cfg.solver_cfg["arm"]
artifact = files("embodichain_robots").joinpath("_ssik_artifacts", artifact_name)
robot_cfg.solver_cfg["arm"] = SSIKSolverCfg(
backend="reference",
reference_artifact=str(artifact),
urdf_path=robot_cfg.fpath,
root_link_name=previous.root_link_name,
end_link_name=previous.end_link_name,
tcp=previous.tcp,
)
solver = robot_cfg.solver_cfg["arm"].init_solver(
device="cpu",
pk_serial_chain=robot_cfg.build_pk_serial_chain(device=torch.device("cpu"))["arm"],
)
For explicit validation, the same configuration can be passed to a CPU-device Robot through the
normal solver_cfg override. CUDA-device robots require a CUDA-capable solver;
reference mode does not move targets to CPU implicitly. The artifacts are package resources, not an automatic
fallback. A modified source chain requires its own verified artifact.
ARX5’s rounded half-turn origins produce a 7.24e-6 parallel-axis residual.
Its reference artifact contains general 6R Raghavan–Roth coefficients built
from the actual source; it does not snap the axes to the three-parallel Warp
family. Rebuild it with the ordinary offline command above, using
--root base_link --end ee_link and the selected X5A.urdf.
The Rizon 4S has non-spherical shoulder/wrist offsets. Its artifact reuses the
pinned SSIK Rizon 4 joint-lock/6R algebraic implementation after an explicit
joint-prefix comparison within 5e-7 representation tolerance. Its 43 mm
terminal-offset difference is handled by a baked rigid end-frame transform.
Source joint names/order, normalized axes and pre-terminal link offsets must
match before generation. The prepared joint-lock reference uses the fixed
16-sample grid with baked RR coefficients. A seed reorders that grid without
adding an unprepared lock value. It does not launch the upstream HP secondary
sweep after an empty result, so failure does not prove global unreachability.
num_samples controls SRS sampling and does not change this artifact grid. Final acceptance still uses the complete original
Rizon 4S model and configured TCP, at the unchanged pose tolerances.
Recreate that adapter with an explicit prebuilt selection:
python -m embodichain.lab.sim.motion.solvers._ssik_reference \
--urdf /absolute/path/rizon4s.urdf \
--root base_link --end flange \
--prebuilt-model flexiv.rizon4_ik \
--output /absolute/path/flexiv_rizon4s.py
Loading verifies the installed pinned prebuilt source and its coefficient sidecars against the manifest before executing the adapter. A different shoulder, wrist, axis or joint order is rejected; a shared robot name is not sufficient evidence of compatibility. The ARX5 and Rizon4S RR paths evaluate numerical eigenvalues/roots and retain upstream mandatory polishing of already accepted roots, even with optional near-miss refinement and rescue disabled. These CPU reference paths do not establish pure geometric Warp or hard real-time performance.
The unified benchmark selects all eight arm/model cases by default and inherits
mode and search settings from each preset. Explicit --ssik-geometry-mode,
--ssik-srs-search and --ssik-rr-search override the corresponding algorithm.
Use --ssik-models to select a smaller workload. New RR experiments use
--ssik-geometry-mode rr --ssik-rr-search seed_local --ssik-devices cpu cuda;
--ssik-cuda-rr retains the historical CUDA-only comparison.
Official SSIK is never imported by a normal Warp benchmark. Enable numerical
alignment explicitly with --ssik-reference, and provide
--ssik-reference-artifacts MODEL=/absolute/artifact.py for prepared algebraic
models. Reference rows report correctness only; they do not contribute timing
or memory gates. Unsupported geometry and missing preparation remain explicitly
unmeasured, while incompatible artifacts fail initialization. Allocation
counters and fresh-process driver observations must both be considered for
CUDA scratch; Torch statistics alone omit driver allocations.
Add --ssik-robot-e2e to run an isolated, engine-backed Robot benchmark with
the native no-render renderer. This preserves native physics and state
retrieval; headless mode alone can still initialize Hybrid rendering. The
result certifies this control context, not simultaneous GUI/rendering load.
It measures 10,000 changing-target calls with previous-result seeds through
the real root-state query, arena/root conversion and Robot.compute_ik.
Setup, physical simulation stepping and external-device transport are excluded.
Outputs undergo independent original-PK FK, effective-limit, step and failure-hold
checks, and validated coverage is compared with the same standalone streaming
targets. An unavailable engine or missing coverage comparison is unmeasured;
neither can pass the gate.
When both arms of a preset are selected, the same entry point also measures
10,000 paired calls through one actual Robot and checks the 8 ms paired gate.
Formal timing also requires a quiet measurement window. Record other GPU
compute processes during the run and retain contended measurements separately;
a successful short diagnostic does not replace the 10,000-call tail-latency
certificate.
How it works#
Normalize the actual PK chain into home-frame joint axes, segment offsets and fixed end rotation; validate supported geometric structure once.
Remove the configured TCP and home rotation from each target.
Enumerate coupled shoulder/wrist and elbow branches for three-parallel 6R, or elbow and general three-axis rotation branches at sampled SRS swivels.
Select periodic representatives within limits and validate candidate FK against the target, using the configured position and orientation gates.
Return all candidate masks, or reduce to the seed-nearest valid result.