Inverse Kinematics
The moro.inverse_kinematics module provides numerical methods for
position inverse kinematics and Cartesian position trajectories.
Numython R&D, (c) 2026 Moro is a Python library for kinematic and dynamic modeling of serial robots. This library has been designed, mainly, for academic and research purposes, using SymPy as base library.
- class moro.inverse_kinematics.IKSolution(q: list, converged: bool, iterations: int, error: float, method: str = 'lm', residual: list | None = None, message: str = '')[source]
Bases:
objectRepresents the result of a position inverse kinematics solve.
- Attributes:
- qlist
Joint variables for the final solver state.
- convergedbool
Whether the solver reached the requested tolerance.
- iterationsint
Number of completed global iterations.
- errorfloat
Final position error norm. It equals
norm(residual)whenresidualis finite; otherwise it isnp.inf.- methodstr
Solver method used (“newton”, “lm”, or “ccd”).
- residuallist, optional
Final position residual
target_position - current_position. It has three elements when available, orNonefor numerical failures where no finite residual can be computed.- messagestr
Human-readable solver outcome message.
- converged: bool
- error: float
- iterations: int
- message: str = ''
- method: str = 'lm'
- q: list
- residual: list | None = None
- class moro.inverse_kinematics.IKTrajectorySolution(solutions: list, converged: bool, failed_index: int | None = None, message: str = '')[source]
Bases:
objectRepresents the global result of solving a sequence of position IK targets.
- Attributes:
- solutionslist of IKSolution
Per-target IK results in processing order. The list includes the failing solution when convergence stops at an intermediate target. It always contains at least one result.
- convergedbool
True only when all processed targets converged; a converged trajectory requires all individual solutions to be converged.
- failed_indexint, optional
Index of the first non-converged target. It is required for a failed trajectory and is None when all targets converged. The referenced solution is non-converged, is the final processed solution, and all previous solutions are converged.
- messagestr
Short global outcome message.
- converged: bool
- property errors
Return per-target final error norms in processing order.
- failed_index: int | None = None
- property iterations
Return per-target iteration counts in processing order.
- message: str = ''
- property qs
Return per-target joint vectors in processing order.
- solutions: list
- moro.inverse_kinematics.solve_position_ik(robot, target_position, q0=None, joint_limits=None, tol=1e-06, max_iter=None, method='lm', damping=1.0, damping_scale=0.5, *, parameters=None, random_state=None, step_tol=1e-12, error_change_tol=1e-12, stagnation_iterations=5)[source]
Solve the position inverse kinematics problem using Newton-Raphson, Levenberg-Marquardt, or Cyclic Coordinate Descent (CCD).
- Parameters:
- robotRobot
A Robot instance with forward kinematics and Jacobian defined.
- target_positionlist, tuple or numpy.ndarray
Desired end-effector position \([x, y, z]\).
- q0list or numpy.ndarray, optional
Initial guess for joint variables. If None, a random guess within joint limits is generated.
- joint_limitslist of tuples, optional
Joint limits as
[(q1_min, q1_max), (q2_min, q2_max), ...]. If None,robot.joint_limitsis used.- tolfloat, optional
Convergence tolerance on the position error norm. Default is 1e-6.
- max_iterint, optional
Maximum number of iterations. Default is 100 for Newton and LM, 500 for CCD.
- methodstr, optional
Solver method:
"newton","lm"(Levenberg-Marquardt), or"ccd"(Cyclic Coordinate Descent). Default is"lm".- dampingfloat, optional
Initial damping parameter \(\lambda\) for Levenberg-Marquardt. Only used when
method="lm". Default is 1.0.- damping_scalefloat, optional
Scaling factor for the damping parameter in Levenberg-Marquardt. Only used when
method="lm". Default is 0.5.- parametersdict-like, optional
Symbol substitutions for geometric constants and other non-joint symbols used by IK expressions. This mapping is applied locally using SymPy
subsand does not modifyrobotor its cached expressions.- random_stateNone, int, or numpy.random.Generator, optional
Random state used only when
q0 is None. WhenNone, a local generator fromnp.random.default_rng()is used. Integer seeds provide reproducible random initial guesses.- step_tolfloat, optional
Stagnation threshold for effective joint movement. The effective step is measured after applying joint limits:
norm(q_trial - q_current)for Newton/LM andnorm(q_after_sweep - q_before_sweep)for CCD. Must satisfystep_tol >= 0.- error_change_tolfloat, optional
Stagnation threshold for error improvement. If
previous_error - current_error <= error_change_tolconsecutively, the solver can terminate due to stagnation. Must satisfyerror_change_tol >= 0.- stagnation_iterationsint, optional
Number of consecutive stalled iterations (or CCD sweeps) required before returning a stagnation result. Must be an integer >= 1.
- Returns:
- IKSolution
An object containing the final joint variables, convergence status, iteration count, final error norm, method, residual, and message.
- Raises:
- ValueError
If inputs are invalid, if unresolved non-joint symbols remain in IK expressions, or if finite numeric functions cannot be built.
Notes
Newton-Raphson (
method="newton"):\[\mathbf{q}_{k+1} = \mathbf{q}_k + \mathbf{J}_p^\dagger(\mathbf{q}_k) \, (\mathbf{p}_d - \mathbf{f}(\mathbf{q}_k))\]Levenberg-Marquardt (
method="lm", default):\[\mathbf{q}_{k+1} = \mathbf{q}_k + (\mathbf{J}_p^T \mathbf{J}_p + \lambda^2 \mathbf{I})^{-1} \mathbf{J}_p^T \, (\mathbf{p}_d - \mathbf{f}(\mathbf{q}_k))\]CCD (
method="ccd") adjusts one joint at a time from the end-effector toward the base. For each revolute joint it computes the angle that rotates the end-effector toward the target in the plane perpendicular to the joint axis. For prismatic joints, it slides along the axis to reduce the error. CCD does not use a Jacobian matrix and is robust near singularities, but converges linearly.tolis expressed in the same linear units as the robot DH parameters andtarget_position.Joint limits are validated and used to clip every joint update. If
q0is provided, it is validated and clipped to joint limits before iterations.residualis the 3D vectortarget_position - current_positionat termination whenever a finite value is available.erroris the norm of that residual when available; otherwise,errorisnp.infandresidualisNone(for example, after numerical failures).messageis a stable short description of the solver outcome (converged, maximum iterations, stagnation reason, or numerical failure).Stagnation can stop the solver early when the effective step becomes too small or the position error stops improving for
stagnation_iterationsconsecutive iterations/sweeps.If
q0 is None, random initialization is sampled with a local NumPy Generator, so integerrandom_statevalues make initialization reproducible without changing NumPy’s global random state.iterationscounts completed global algorithm steps: Newton/LM count one per attempted update; CCD counts one per full sweep from joint n to 1. Therefore, if the initial guess already satisfies the tolerance,iterationsis 0. If convergence is not reached,iterationsequals the number of completed attempts/sweeps at termination (ormax_iterwhen the iteration limit is reached).Examples
>>> import moro as mr >>> from moro.abc import l1, l2, q1, q2 >>> from moro.inverse_kinematics import solve_position_ik >>> >>> # 2R planar robot >>> rr = mr.Robot((l1, 0, 0, q1, "r"), (l2, 0, 0, q2, "r")) >>> >>> # Levenberg-Marquardt (default) >>> sol = solve_position_ik( ... rr, ... [1.5, 0.5, 0.0], ... q0=[0.1, 0.1], ... parameters={l1: 1.0, l2: 1.0}, ... ) >>> >>> # Reproducible random initialization (used only when q0 is None) >>> sol = solve_position_ik( ... rr, ... [1.5, 0.5, 0.0], ... parameters={l1: 1.0, l2: 1.0}, ... random_state=42, ... ) >>> >>> # Newton-Raphson >>> sol = solve_position_ik(rr, [1.5, 0.5, 0.0], q0=[0.1, 0.1], ... method="newton", parameters={l1: 1.0, l2: 1.0}) >>> >>> # CCD >>> sol = solve_position_ik(rr, [1.5, 0.5, 0.0], q0=[0.1, 0.1], ... method="ccd", parameters={l1: 1.0, l2: 1.0})
- moro.inverse_kinematics.solve_position_trajectory(robot, target_positions, q0, *, parameters=None, method='lm', joint_limits=None, tol=1e-06, max_iter=None, damping=1.0, damping_scale=0.5, random_state=None, step_tol=1e-12, error_change_tol=1e-12, stagnation_iterations=5)[source]
Solve position IK for a sequence of Cartesian position targets.
This function is a thin orchestration layer over
solve_position_ik. It solves each target sequentially and reuses each converged solution as the initial guess for the next target.- Parameters:
- robotRobot
Robot model with forward kinematics and Jacobian support.
- target_positionsiterable or numpy.ndarray
Sequence of position targets with shape
(m, 3). A single vector with shape(3,)is rejected to avoid ambiguity withsolve_position_ik.- q0list or numpy.ndarray
Initial joint seed for the first target. This argument is required.
- parametersdict-like, optional
Symbol substitutions passed directly to
solve_position_ik.- methodstr, optional
Solver method passed to
solve_position_ik.- joint_limitslist of tuples, optional
Joint limits passed to
solve_position_ik.- tolfloat, optional
Position error tolerance passed to
solve_position_ik.- max_iterint, optional
Maximum iterations passed to
solve_position_ik.- dampingfloat, optional
LM damping parameter passed to
solve_position_ik.- damping_scalefloat, optional
LM damping scale passed to
solve_position_ik.- random_stateNone, int, or numpy.random.Generator, optional
Forwarded for API consistency with
solve_position_ik. With mandatoryq0and sequential seeding, it typically has no effect.- step_tolfloat, optional
Stagnation step tolerance passed to
solve_position_ik.- error_change_tolfloat, optional
Stagnation error-change tolerance passed to
solve_position_ik.- stagnation_iterationsint, optional
Stagnation iteration count passed to
solve_position_ik.
- Returns:
- IKTrajectorySolution
Trajectory-level result. Processing stops at the first non-converged target, and the failing solution is included in
solutions.
Notes
This utility does not perform interpolation, timing, smoothing, full-pose orientation IK, branch optimization, or global trajectory planning. Reusing the previous solution can improve local continuity but does not guarantee global branch continuity.
The returned
trajectory.qscan be used directly as a sequence of joint configurations for animation pipelines.Examples
>>> import moro as mr >>> from moro.abc import l1, l2, q1, q2 >>> from moro.inverse_kinematics import solve_position_trajectory >>> >>> robot = mr.Robot((l1, 0, 0, q1, "r"), (l2, 0, 0, q2, "r")) >>> targets = [ ... [1.5, 0.2, 0.0], ... [1.4, 0.4, 0.0], ... [1.2, 0.6, 0.0], ... ] >>> trajectory = solve_position_trajectory( ... robot, ... targets, ... q0=[0.1, 0.1], ... parameters={l1: 1.0, l2: 1.0}, ... ) >>> trajectory.qs