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

"warp"

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

"warp", geometry_mode="near_parallel"

Certified small deviations from three-parallel 6R geometry, including ARX5 X5A

CPU or CUDA; geometric initialization plus bounded original-model numerical polishing

"warp", geometry_mode="rr"

Verified prepared Rizon4 joint prefix, including Rizon4S with its actual terminal frame

CPU or CUDA; Warp algebraic matrix-pencil roots

"cuda_rr"

Compatibility alias for the prepared RR path

CUDA only; prefer backend="warp", geometry_mode="rr" for new configurations

"reference"

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

backend

"warp"

Explicit execution backend; failures never switch backends

geometry_mode

"strict"

"near_parallel" permits bounded 6R deviations and polishing; "rr" explicitly selects a verified prepared algebraic Warp model

axis_alignment_tolerance

1e-5

Maximum normalized-axis deviation in near-parallel mode; does not relax FK acceptance

srs_search

"uniform"

"seed_local" adds scalar geometric redundancy optimization near the current seed

rr_search

"fixed"

For Warp geometry_mode="rr" or legacy cuda_rr, "seed_local" adds local and analytically predicted lock samples using the parameterized RR bundle

num_samples

128

Global SRS swivel samples, in addition to a defined seed swivel

position_tolerance

1e-5

Maximum accepted position error, metres

rotation_tolerance

1e-5

Maximum accepted orientation error, radians

max_joint_step

None

Optional hard per-joint distance from the seed or previous path frame, radians

chunk_size

512

Maximum target count per candidate-generation chunk

reference_artifact

None

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

get_ik(..., return_all_solutions=False)

(N,)

(N, dof)

get_ik(..., return_all_solutions=True)

(N, K)

(N, K, dof)

get_ik_batch(...)

(...)

(..., dof)

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.

Continuous paths and shared methods#

supports_continuous_batch_ik is true. Through robot.compute_batch_ik(..., continuous=True), candidates are selected sequentially using the previous frame’s selected joints and the same weights. The optional max_joint_step is enforced against that previous frame. A failed frame returns failure and retains the preceding pose; later frames continue from that retained pose. For single-target streaming, maintain the seed in the caller; the solver has no implicit cross-call joint state.

For SRS seed_local, the target-aware path hook regenerates the same candidate pool at each frame from the preceding result. Its scalar search optimizes analytical branches over swivel (or the free roll at a collapsed elbow circle), including Euler pole anchors. It does not solve a Cartesian Jacobian system. The global samples and unchanged final FK/step gates still apply. CPU SRS evaluates a complete path in a fused kernel. Near-parallel and seed-local RR regenerate their pools from the previous result on either device. CUDA queues frame-dependent calls while parallelizing paths, redundancy samples and branches within each frame; the previous result stays on the device. This avoids serial FP64 work in one GPU lane without introducing host result reads or synchronization.

get_fk, get_fk_batch, get_qpos_limits, set_qpos_limits, and update_with_robot_limit retain the shared BaseSolver behavior. set_tcp updates the end-link-to-TCP transform without rebuilding the chain geometry; set_ik_nearest_weight updates branch-selection weights. Robot-level IK converts arena-frame poses into the solver root frame. See shared FK/IK conventions.

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#

  1. Normalize the actual PK chain into home-frame joint axes, segment offsets and fixed end rotation; validate supported geometric structure once.

  2. Remove the configured TCP and home rotation from each target.

  3. Enumerate coupled shoulder/wrist and elbow branches for three-parallel 6R, or elbow and general three-axis rotation branches at sampled SRS swivels.

  4. Select periodic representatives within limits and validate candidate FK against the target, using the configured position and orientation gates.

  5. Return all candidate masks, or reduce to the seed-nearest valid result.

References#