pylinkage.symbolic package

Submodules

pylinkage.symbolic.conversion module

Factory helpers and utilities for the symbolic linkage API.

Conversion helpers that bridged the legacy numeric Linkage (joint API) and the symbolic linkage were removed alongside the pylinkage.joints module. Build symbolic linkages directly using SymbolicLinkage or fourbar_symbolic().

pylinkage.symbolic.conversion.fourbar_symbolic(ground_length: float | Expr | str = 'L0', crank_length: float | Expr | str = 'L1', coupler_length: float | Expr | str = 'L2', rocker_length: float | Expr | str = 'L3', ground_x: float = 0.0, ground_y: float = 0.0) SymbolicLinkage

Create a symbolic four-bar linkage.

The four-bar consists of:

  • A (ground left): Static anchor at (ground_x, ground_y)

  • D (ground right): Static anchor at (ground_x + ground_length, ground_y)

  • B (crank end): Crank rotating around A with radius crank_length

  • C (coupler/rocker connection): Revolute connecting B and D

Parameters:
  • ground_length – Distance between ground anchors A and D.

  • crank_length – Length of the input crank (A to B).

  • coupler_length – Length of the coupler (B to C).

  • rocker_length – Length of the output rocker (D to C).

  • ground_x – X coordinate of ground anchor A. Default is 0.

  • ground_y – Y coordinate of ground anchor A. Default is 0.

Returns:

A SymbolicLinkage representing the four-bar.

pylinkage.symbolic.conversion.get_numeric_parameters(sym_linkage: SymbolicLinkage) dict[str, float]

Extract numeric parameter values stored in symbolic joints.

Returns a {parameter_name: value} dict populated from _numeric_* attributes previously stashed on each joint.

Parameters:

sym_linkage – The symbolic linkage.

Returns:

Dictionary mapping parameter names to numeric values.

pylinkage.symbolic.geometry module

Symbolic geometry functions using SymPy.

This module provides symbolic versions of the geometry functions from pylinkage.geometry, enabling closed-form expressions for linkage analysis.

pylinkage.symbolic.geometry.symbolic_circle_intersect(x1: Expr, y1: Expr, r1: Expr, x2: Expr, y2: Expr, r2: Expr, branch: int = 1) tuple[Expr, Expr]

Compute the symbolic intersection of two circles.

This function returns a closed-form expression for one of the two intersection points of two circles. The branch parameter selects which intersection point to return.

Algorithm (Paul Bourke, 1997):

  • d = distance between centers

  • a = (r1^2 - r2^2 + d^2) / (2*d) : distance from center1 to radical line

  • h = sqrt(r1^2 - a^2) : half-chord length

  • The intersection points are at distance h perpendicular to the line connecting the centers.

Parameters:
  • x1 – X coordinate of first circle center.

  • y1 – Y coordinate of first circle center.

  • r1 – Radius of first circle.

  • x2 – X coordinate of second circle center.

  • y2 – Y coordinate of second circle center.

  • r2 – Radius of second circle.

  • branch – +1 or -1 to select which intersection point. Default is +1.

Returns:

Tuple of (x, y) symbolic expressions for the intersection.

Note

The expressions may be complex (imaginary) for parameter values where the circles do not intersect. This is mathematically correct but will fail during numeric evaluation.

pylinkage.symbolic.geometry.symbolic_circle_line_intersect(cx: Expr, cy: Expr, r: Expr, p1_x: Expr, p1_y: Expr, p2_x: Expr, p2_y: Expr, branch: int = 1) tuple[Expr, Expr]

Compute the symbolic intersection of a circle and a line.

The line is defined by two points on it. The branch parameter selects which of the two intersection points to return.

Parameters:
  • cx – X coordinate of circle center.

  • cy – Y coordinate of circle center.

  • r – Radius of the circle.

  • p1_x – X coordinate of first point on line.

  • p1_y – Y coordinate of first point on line.

  • p2_x – X coordinate of second point on line.

  • p2_y – Y coordinate of second point on line.

  • branch – +1 or -1 to select which intersection point. Default is +1.

Returns:

Tuple of (x, y) symbolic expressions for the intersection.

Note

The expressions may be complex (imaginary) for parameter values where the circle and line do not intersect.

pylinkage.symbolic.geometry.symbolic_cyl_to_cart(radius: Expr, angle: Expr, ori_x: Expr | float = 0, ori_y: Expr | float = 0) tuple[Expr, Expr]

Convert polar coordinates to cartesian coordinates symbolically.

Parameters:
  • radius – Distance from origin.

  • angle – Angle in radians (counterclockwise from positive x-axis).

  • ori_x – X coordinate of origin. Default is 0.

  • ori_y – Y coordinate of origin. Default is 0.

Returns:

Tuple of (x, y) symbolic expressions.

pylinkage.symbolic.geometry.symbolic_dist(x1: Expr, y1: Expr, x2: Expr, y2: Expr) Expr

Compute the symbolic Euclidean distance between two points.

Parameters:
  • x1 – X coordinate of first point.

  • y1 – Y coordinate of first point.

  • x2 – X coordinate of second point.

  • y2 – Y coordinate of second point.

Returns:

Symbolic expression for the distance.

pylinkage.symbolic.geometry.symbolic_sqr_dist(x1: Expr, y1: Expr, x2: Expr, y2: Expr) Expr

Compute the symbolic squared distance between two points.

Useful for avoiding square roots in constraint equations.

Parameters:
  • x1 – X coordinate of first point.

  • y1 – Y coordinate of first point.

  • x2 – X coordinate of second point.

  • y2 – Y coordinate of second point.

Returns:

Symbolic expression for the squared distance.

pylinkage.symbolic.joints module

Symbolic joint classes for linkage analysis.

This module provides symbolic versions of the joint classes from pylinkage.joints, enabling closed-form expressions for linkage trajectories.

class pylinkage.symbolic.joints.SymCrank(parent: SymJoint | tuple[Expr, Expr], radius: float | Expr | str = 'r', theta: Symbol | None = None, name: str | None = None)

Bases: SymJoint

A symbolic crank (motor) joint.

The crank rotates around its parent at a constant radius. Its position is parameterized by the input angle theta.

constraint_equations() list[Equality]

Return the distance constraint from crank to parent.

Constraint: dist(self, parent) = r

property parameters: Set[sp.Symbol]

Return parameters, including radius if symbolic.

position_expr() tuple[Expr, Expr]

Return the crank position as a function of theta.

Position: (x0 + r*cos(theta), y0 + r*sin(theta))

r: Symbol | Expr
theta: Symbol
class pylinkage.symbolic.joints.SymJoint(x: float | Expr | None = None, y: float | Expr | None = None, parent0: SymJoint | None = None, parent1: SymJoint | None = None, name: str | None = None)

Bases: ABC

Abstract base class for symbolic joints.

A symbolic joint represents a kinematic joint with its position expressed as symbolic SymPy expressions. This enables: - Closed-form trajectory analysis - Symbolic differentiation for optimization - Algebraic manipulation of linkage equations

abstractmethod constraint_equations() list[Equality]

Return the constraint equations this joint satisfies.

Returns:

List of SymPy Eq objects representing constraints.

name: str
property parameters: Set[sp.Symbol]

Return all symbolic parameters in this joint’s position expressions.

Excludes the input angle theta.

Returns:

Set of SymPy symbols that are parameters.

parent0: SymJoint | None
parent1: SymJoint | None
abstractmethod position_expr() tuple[Expr, Expr]

Return the symbolic (x, y) position expressions.

Returns:

Tuple of (x_expr, y_expr) SymPy expressions.

class pylinkage.symbolic.joints.SymRevolute(parent0: SymJoint, parent1: SymJoint, distance0: float | Expr | str = 'r0', distance1: float | Expr | str = 'r1', branch: int = 1, name: str | None = None)

Bases: SymJoint

A symbolic revolute (pin) joint connecting two parent joints.

The revolute joint is constrained to be at fixed distances from two parent joints, which geometrically defines the intersection of two circles. The branch parameter selects which intersection.

branch: int
constraint_equations() list[Equality]

Return the distance constraints to both parents.

Constraints: - dist(self, parent0) = r0 - dist(self, parent1) = r1

property parameters: Set[sp.Symbol]

Return parameters, including distances if symbolic.

position_expr() tuple[Expr, Expr]

Return the revolute position from circle-circle intersection.

The position is at the intersection of: - Circle centered at parent0 with radius r0 - Circle centered at parent1 with radius r1

r0: Symbol | Expr
r1: Symbol | Expr
class pylinkage.symbolic.joints.SymStatic(x: float | Expr = 0, y: float | Expr = 0, name: str | None = None)

Bases: SymJoint

A symbolic static joint with fixed position.

This represents a ground point or fixed anchor in the linkage. The position can be numeric constants or symbolic parameters.

constraint_equations() list[Equality]

Static joints have no constraints.

position_expr() tuple[Expr, Expr]

Return the constant position expressions.

pylinkage.symbolic.linkage module

Symbolic linkage representation.

This module provides the SymbolicLinkage class for building and analyzing linkages with symbolic (SymPy) expressions for joint positions.

class pylinkage.symbolic.linkage.SymbolicLinkage(joints: Iterable[SymJoint], theta: Symbol | None = None, name: str | None = None)

Bases: object

A linkage with symbolic position expressions.

The SymbolicLinkage class holds a collection of symbolic joints and provides methods for: - Computing closed-form trajectory expressions - Extracting constraint equations - Computing Jacobians for optimization - Simplifying expressions

coupler_curve(joint_name: str) tuple[Expr, Expr]

Get the trajectory expression for a specific joint.

Parameters:

joint_name – Name of the joint.

Returns:

Tuple of (x(theta), y(theta)) expressions.

Raises:

ValueError – If no joint with the given name exists.

get_constraint_equations() list[Equality]

Return all constraint equations for the linkage.

Returns:

List of SymPy Eq objects representing all constraints.

get_joint(name: str) SymJoint

Get a joint by name.

Parameters:

name – Name of the joint.

Returns:

The joint with the given name.

Raises:

ValueError – If no joint with the given name exists.

get_trajectory_expressions() dict[str, tuple[Expr, Expr]]

Return symbolic trajectory expressions for all joints.

Returns:

Dictionary mapping joint name to (x(theta), y(theta)).

jacobian(joint_names: list[str] | None = None) MutableDenseMatrix

Compute the Jacobian matrix of positions with respect to parameters.

The Jacobian has rows for (x1, y1, x2, y2, …) and columns for each parameter. This is useful for sensitivity analysis and gradient-based optimization.

Parameters:

joint_names – List of joint names to include. If None, all joints are included.

Returns:

SymPy Matrix of shape (2*n_joints, n_params).

jacobian_theta(joint_names: list[str] | None = None) MutableDenseMatrix

Compute the Jacobian of positions with respect to theta.

This gives the velocity direction for each joint.

Parameters:

joint_names – List of joint names to include. If None, all joints are included.

Returns:

SymPy Matrix of shape (2*n_joints, 1).

joints: tuple[SymJoint, ...]
name: str
property parameters: dict[str, Symbol]

Return all symbolic parameters in the linkage.

Collects parameters from all joints and returns them as a dictionary mapping parameter names to symbols.

Returns:

Dictionary of parameter name to symbol.

simplify() SymbolicLinkage

Return a new linkage with simplified expressions.

Note: This creates new joint objects with simplified expressions, but the linkage structure is preserved.

Returns:

New SymbolicLinkage with simplified position expressions.

substitute(param_values: dict[str, float | Expr]) SymbolicLinkage

Create a new linkage with parameters substituted.

This is useful for partial evaluation or for substituting symbolic parameters with numeric values.

Parameters:

param_values – Dictionary mapping parameter names to values.

Returns:

New SymbolicLinkage with substituted parameters.

theta: Symbol

pylinkage.symbolic.optimization module

Symbolic optimization with gradients and Jacobians.

This module provides gradient-based optimization for linkage parameters using symbolic derivatives computed by SymPy.

class pylinkage.symbolic.optimization.OptimizationResult(success: bool, params: dict[str, float], objective_value: float, iterations: int, message: str = '')

Bases: object

Result of a symbolic optimization.

iterations: int

Number of iterations performed.

message: str = ''

Status message from the optimizer.

objective_value: float

Final objective function value.

params: dict[str, float]

Optimal parameter values.

success: bool

Whether the optimization converged successfully.

class pylinkage.symbolic.optimization.SymbolicOptimizer(linkage: SymbolicLinkage, objective_func: Callable[[dict[str, SymCoord]], sp.Expr], theta_samples: int = 360)

Bases: object

Gradient-based optimization using symbolic derivatives.

This class pre-computes symbolic gradients and uses lambdify for fast numeric evaluation during optimization. The optimizer uses scipy.optimize.minimize with analytical gradients.

Example

>>> def objective(trajectories):
...     x, y = trajectories["C"]
...     return (y - 2)**2  # Minimize deviation from y=2
...
>>> optimizer = SymbolicOptimizer(linkage, objective)
>>> result = optimizer.optimize(
...     initial_params={"r_AB": 1.0, "r_BC": 3.0, "r_CD": 3.0},
...     bounds={"r_AB": (0.5, 2), "r_BC": (1, 5), "r_CD": (1, 5)},
... )
evaluate(param_values: dict[str, float], theta_samples: Sequence[float] | ndarray[tuple[Any, ...], dtype[floating]] | None = None) float

Evaluate the objective function numerically.

The objective is averaged over all theta samples.

Parameters:
  • param_values – Dictionary mapping parameter names to values.

  • theta_samples – Theta values to evaluate at. If None, uses equally spaced samples over [0, 2*pi].

Returns:

Mean objective value.

gradient(param_values: dict[str, float], theta_samples: Sequence[float] | ndarray[tuple[Any, ...], dtype[floating]] | None = None) ndarray[tuple[Any, ...], dtype[float64]]

Evaluate the gradient numerically.

The gradient is averaged over all theta samples.

Parameters:
  • param_values – Dictionary mapping parameter names to values.

  • theta_samples – Theta values to evaluate at. If None, uses equally spaced samples over [0, 2*pi].

Returns:

Array of gradient values (one per parameter).

optimize(initial_params: dict[str, float], bounds: dict[str, tuple[float, float]] | None = None, theta_samples: Sequence[float] | ndarray[tuple[Any, ...], dtype[floating]] | None = None, method: str = 'L-BFGS-B', maxiter: int = 1000, tol: float = 1e-06) OptimizationResult

Run gradient-based optimization.

Uses scipy.optimize.minimize with analytical gradients.

Parameters:
  • initial_params – Dictionary of initial parameter values.

  • bounds – Dictionary mapping parameter names to (min, max) bounds. If None, parameters are unbounded.

  • theta_samples – Theta values for objective evaluation.

  • method – Scipy optimization method. Default is “L-BFGS-B”.

  • maxiter – Maximum iterations. Default is 1000.

  • tol – Convergence tolerance. Default is 1e-6.

Returns:

OptimizationResult with optimal parameters.

pylinkage.symbolic.optimization.generate_symbolic_bounds(linkage: SymbolicLinkage, center_values: dict[str, float], min_ratio: float = 5.0, max_factor: float = 5.0) dict[str, tuple[float, float]]

Generate optimization bounds based on center values.

Creates bounds as (center/min_ratio, center*max_factor) for each parameter. This is similar to the numeric generate_bounds function.

Parameters:
  • linkage – The symbolic linkage.

  • center_values – Dictionary of center values for each parameter.

  • min_ratio – Divisor for lower bound. Default is 5.

  • max_factor – Multiplier for upper bound. Default is 5.

Returns:

Dictionary mapping parameter names to (min, max) bounds.

pylinkage.symbolic.optimization.symbolic_gradient(objective: Expr, parameters: list[Symbol]) list[Expr]

Compute the symbolic gradient of an objective function.

Parameters:
  • objective – Symbolic objective expression.

  • parameters – List of parameter symbols to differentiate with respect to.

Returns:

List of partial derivative expressions.

pylinkage.symbolic.optimization.symbolic_hessian(objective: Expr, parameters: list[Symbol]) MutableDenseMatrix

Compute the symbolic Hessian matrix of an objective function.

Parameters:
  • objective – Symbolic objective expression.

  • parameters – List of parameter symbols.

Returns:

SymPy Matrix of second partial derivatives.

pylinkage.symbolic.solver module

Symbolic solving and trajectory computation.

This module provides functions for solving linkage equations symbolically and computing numeric trajectories from symbolic expressions.

pylinkage.symbolic.solver.check_buildability(linkage: SymbolicLinkage, param_values: dict[str, float], theta_value: float = 0.0) tuple[bool, str]

Check if a linkage is buildable with given parameters.

A linkage is unbuildable if the constraint equations cannot be satisfied (e.g., circles don’t intersect). This function evaluates the positions at a specific theta and checks for complex (imaginary) results.

Parameters:
  • linkage – The symbolic linkage.

  • param_values – Dictionary mapping parameter names to numeric values.

  • theta_value – Theta value to check at. Default is 0.

Returns:

Tuple of (is_buildable, error_message).

Example

>>> buildable, msg = check_buildability(linkage, {"r0": 1, "r1": 10})
>>> if not buildable:
...     print(f"Linkage is unbuildable: {msg}")
pylinkage.symbolic.solver.compute_trajectory_numeric(linkage: SymbolicLinkage, param_values: dict[str, float], theta_values: Sequence[float] | NDArray[np.floating], output_joints: list[str] | None = None) dict[str, NDArray[np.float64]]

Compute numeric trajectory by evaluating symbolic expressions.

Uses lambdify for efficient numeric evaluation of the symbolic trajectory expressions.

Parameters:
  • linkage – The symbolic linkage.

  • param_values – Dictionary mapping parameter names to numeric values.

  • theta_values – Sequence of theta values to evaluate at.

  • output_joints – List of joint names to include. If None, all joints.

Returns:

Dictionary mapping joint name to Nx2 array of (x, y) positions.

pylinkage.symbolic.solver.create_trajectory_functions(linkage: SymbolicLinkage, output_joints: list[str] | None = None) dict[str, tuple[object, object, list[sp.Symbol]]]

Create lambdified functions for trajectory evaluation.

This is useful when you need to evaluate trajectories many times with different parameter values, as it avoids repeated lambdify calls.

Parameters:
  • linkage – The symbolic linkage.

  • output_joints – List of joint names to include. If None, all joints.

Returns:

Dictionary mapping joint name to (x_func, y_func, param_symbols).

Example

>>> funcs = create_trajectory_functions(linkage)
>>> x_func, y_func, params = funcs["C"]
>>> x_vals = x_func(theta_array, *[param_values[str(p)] for p in params])
pylinkage.symbolic.solver.eliminate_theta(x_expr: Expr, y_expr: Expr, theta: Symbol | None = None) Expr | None

Eliminate theta to get an implicit curve equation f(x, y) = 0.

This converts parametric equations x(theta), y(theta) into an implicit algebraic equation in x and y only. This gives the algebraic equation of the coupler curve.

Uses the substitution cos(theta) = c, sin(theta) = s with the constraint c^2 + s^2 = 1, then eliminates c and s using Groebner basis.

Parameters:
  • x_expr – X coordinate as a function of theta.

  • y_expr – Y coordinate as a function of theta.

  • theta – The angle symbol to eliminate. Default uses global theta.

Returns:

Polynomial in x and y, or None if elimination fails.

Example

>>> theta = sp.Symbol('theta')
>>> x = sp.cos(theta)
>>> y = sp.sin(theta)
>>> eliminate_theta(x, y, theta)
x**2 + y**2 - 1
pylinkage.symbolic.solver.solve_linkage_symbolically(linkage: SymbolicLinkage, output_joints: list[str] | None = None, simplify: bool = True) dict[str, SymCoord]

Solve for joint positions as functions of theta.

Returns (optionally simplified) closed-form expressions for trajectory curves of all or specified joints.

Parameters:
  • linkage – The symbolic linkage to solve.

  • output_joints – List of joint names to include in output. If None, all joints are included.

  • simplify – Whether to simplify the expressions. Default is True.

Returns:

Dictionary mapping joint name to (x(theta), y(theta)).

Module contents

Symbolic computation module for pylinkage.

This module provides symbolic computation capabilities using SymPy, enabling:

  • Closed-form trajectory expressions: Get algebraic expressions for joint positions as functions of the input angle and link lengths.

  • Symbolic constraint equations: Express linkage constraints as symbolic equations for analysis and manipulation.

  • Gradient-based optimization: Compute analytical gradients for efficient gradient-based optimization of linkage parameters.

Example usage:

from pylinkage.symbolic import (
    SymStatic, SymCrank, SymRevolute, SymbolicLinkage,
    compute_trajectory_numeric, SymbolicOptimizer,
    fourbar_symbolic,
)
import numpy as np

# Create a four-bar linkage symbolically
linkage = fourbar_symbolic(
    ground_length=4,
    crank_length="L1",  # Symbolic parameter
    coupler_length="L2",
    rocker_length="L3",
)

# Get closed-form trajectory expression for coupler point C
x_expr, y_expr = linkage.coupler_curve("C")
print(f"C_x(theta) = {x_expr}")
print(f"C_y(theta) = {y_expr}")

# Compute numeric trajectory with specific parameter values
params = {"L1": 1.0, "L2": 3.0, "L3": 3.0}
theta_vals = np.linspace(0, 2 * np.pi, 360)
trajectories = compute_trajectory_numeric(linkage, params, theta_vals)

# Access (x, y) positions for joint C
C_positions = trajectories["C"]  # Shape: (360, 2)

For optimization:

import sympy as sp

# Define objective function symbolically
def objective(trajectories):
    x, y = trajectories["C"]
    # Minimize deviation from target y=2
    return (y - 2)**2

optimizer = SymbolicOptimizer(linkage, objective)
result = optimizer.optimize(
    initial_params={"L1": 1.0, "L2": 3.0, "L3": 3.0},
    bounds={"L1": (0.5, 2), "L2": (1, 5), "L3": (1, 5)},
)

if result.success:
    print(f"Optimal parameters: {result.params}")
    print(f"Objective value: {result.objective_value}")
class pylinkage.symbolic.OptimizationResult(success: bool, params: dict[str, float], objective_value: float, iterations: int, message: str = '')

Bases: object

Result of a symbolic optimization.

iterations: int

Number of iterations performed.

message: str = ''

Status message from the optimizer.

objective_value: float

Final objective function value.

params: dict[str, float]

Optimal parameter values.

success: bool

Whether the optimization converged successfully.

class pylinkage.symbolic.SymCrank(parent: SymJoint | tuple[Expr, Expr], radius: float | Expr | str = 'r', theta: Symbol | None = None, name: str | None = None)

Bases: SymJoint

A symbolic crank (motor) joint.

The crank rotates around its parent at a constant radius. Its position is parameterized by the input angle theta.

constraint_equations() list[Equality]

Return the distance constraint from crank to parent.

Constraint: dist(self, parent) = r

property parameters: Set[sp.Symbol]

Return parameters, including radius if symbolic.

position_expr() tuple[Expr, Expr]

Return the crank position as a function of theta.

Position: (x0 + r*cos(theta), y0 + r*sin(theta))

r: Symbol | Expr
theta: Symbol
class pylinkage.symbolic.SymJoint(x: float | Expr | None = None, y: float | Expr | None = None, parent0: SymJoint | None = None, parent1: SymJoint | None = None, name: str | None = None)

Bases: ABC

Abstract base class for symbolic joints.

A symbolic joint represents a kinematic joint with its position expressed as symbolic SymPy expressions. This enables: - Closed-form trajectory analysis - Symbolic differentiation for optimization - Algebraic manipulation of linkage equations

abstractmethod constraint_equations() list[Equality]

Return the constraint equations this joint satisfies.

Returns:

List of SymPy Eq objects representing constraints.

name: str
property parameters: Set[sp.Symbol]

Return all symbolic parameters in this joint’s position expressions.

Excludes the input angle theta.

Returns:

Set of SymPy symbols that are parameters.

parent0: SymJoint | None
parent1: SymJoint | None
abstractmethod position_expr() tuple[Expr, Expr]

Return the symbolic (x, y) position expressions.

Returns:

Tuple of (x_expr, y_expr) SymPy expressions.

class pylinkage.symbolic.SymRevolute(parent0: SymJoint, parent1: SymJoint, distance0: float | Expr | str = 'r0', distance1: float | Expr | str = 'r1', branch: int = 1, name: str | None = None)

Bases: SymJoint

A symbolic revolute (pin) joint connecting two parent joints.

The revolute joint is constrained to be at fixed distances from two parent joints, which geometrically defines the intersection of two circles. The branch parameter selects which intersection.

branch: int
constraint_equations() list[Equality]

Return the distance constraints to both parents.

Constraints: - dist(self, parent0) = r0 - dist(self, parent1) = r1

property parameters: Set[sp.Symbol]

Return parameters, including distances if symbolic.

position_expr() tuple[Expr, Expr]

Return the revolute position from circle-circle intersection.

The position is at the intersection of: - Circle centered at parent0 with radius r0 - Circle centered at parent1 with radius r1

r0: Symbol | Expr
r1: Symbol | Expr
class pylinkage.symbolic.SymStatic(x: float | Expr = 0, y: float | Expr = 0, name: str | None = None)

Bases: SymJoint

A symbolic static joint with fixed position.

This represents a ground point or fixed anchor in the linkage. The position can be numeric constants or symbolic parameters.

constraint_equations() list[Equality]

Static joints have no constraints.

position_expr() tuple[Expr, Expr]

Return the constant position expressions.

class pylinkage.symbolic.SymbolicLinkage(joints: Iterable[SymJoint], theta: Symbol | None = None, name: str | None = None)

Bases: object

A linkage with symbolic position expressions.

The SymbolicLinkage class holds a collection of symbolic joints and provides methods for: - Computing closed-form trajectory expressions - Extracting constraint equations - Computing Jacobians for optimization - Simplifying expressions

coupler_curve(joint_name: str) tuple[Expr, Expr]

Get the trajectory expression for a specific joint.

Parameters:

joint_name – Name of the joint.

Returns:

Tuple of (x(theta), y(theta)) expressions.

Raises:

ValueError – If no joint with the given name exists.

get_constraint_equations() list[Equality]

Return all constraint equations for the linkage.

Returns:

List of SymPy Eq objects representing all constraints.

get_joint(name: str) SymJoint

Get a joint by name.

Parameters:

name – Name of the joint.

Returns:

The joint with the given name.

Raises:

ValueError – If no joint with the given name exists.

get_trajectory_expressions() dict[str, tuple[Expr, Expr]]

Return symbolic trajectory expressions for all joints.

Returns:

Dictionary mapping joint name to (x(theta), y(theta)).

jacobian(joint_names: list[str] | None = None) MutableDenseMatrix

Compute the Jacobian matrix of positions with respect to parameters.

The Jacobian has rows for (x1, y1, x2, y2, …) and columns for each parameter. This is useful for sensitivity analysis and gradient-based optimization.

Parameters:

joint_names – List of joint names to include. If None, all joints are included.

Returns:

SymPy Matrix of shape (2*n_joints, n_params).

jacobian_theta(joint_names: list[str] | None = None) MutableDenseMatrix

Compute the Jacobian of positions with respect to theta.

This gives the velocity direction for each joint.

Parameters:

joint_names – List of joint names to include. If None, all joints are included.

Returns:

SymPy Matrix of shape (2*n_joints, 1).

joints: tuple[SymJoint, ...]
name: str
property parameters: dict[str, Symbol]

Return all symbolic parameters in the linkage.

Collects parameters from all joints and returns them as a dictionary mapping parameter names to symbols.

Returns:

Dictionary of parameter name to symbol.

simplify() SymbolicLinkage

Return a new linkage with simplified expressions.

Note: This creates new joint objects with simplified expressions, but the linkage structure is preserved.

Returns:

New SymbolicLinkage with simplified position expressions.

substitute(param_values: dict[str, float | Expr]) SymbolicLinkage

Create a new linkage with parameters substituted.

This is useful for partial evaluation or for substituting symbolic parameters with numeric values.

Parameters:

param_values – Dictionary mapping parameter names to values.

Returns:

New SymbolicLinkage with substituted parameters.

theta: Symbol
class pylinkage.symbolic.SymbolicOptimizer(linkage: SymbolicLinkage, objective_func: Callable[[dict[str, SymCoord]], sp.Expr], theta_samples: int = 360)

Bases: object

Gradient-based optimization using symbolic derivatives.

This class pre-computes symbolic gradients and uses lambdify for fast numeric evaluation during optimization. The optimizer uses scipy.optimize.minimize with analytical gradients.

Example

>>> def objective(trajectories):
...     x, y = trajectories["C"]
...     return (y - 2)**2  # Minimize deviation from y=2
...
>>> optimizer = SymbolicOptimizer(linkage, objective)
>>> result = optimizer.optimize(
...     initial_params={"r_AB": 1.0, "r_BC": 3.0, "r_CD": 3.0},
...     bounds={"r_AB": (0.5, 2), "r_BC": (1, 5), "r_CD": (1, 5)},
... )
evaluate(param_values: dict[str, float], theta_samples: Sequence[float] | ndarray[tuple[Any, ...], dtype[floating]] | None = None) float

Evaluate the objective function numerically.

The objective is averaged over all theta samples.

Parameters:
  • param_values – Dictionary mapping parameter names to values.

  • theta_samples – Theta values to evaluate at. If None, uses equally spaced samples over [0, 2*pi].

Returns:

Mean objective value.

gradient(param_values: dict[str, float], theta_samples: Sequence[float] | ndarray[tuple[Any, ...], dtype[floating]] | None = None) ndarray[tuple[Any, ...], dtype[float64]]

Evaluate the gradient numerically.

The gradient is averaged over all theta samples.

Parameters:
  • param_values – Dictionary mapping parameter names to values.

  • theta_samples – Theta values to evaluate at. If None, uses equally spaced samples over [0, 2*pi].

Returns:

Array of gradient values (one per parameter).

optimize(initial_params: dict[str, float], bounds: dict[str, tuple[float, float]] | None = None, theta_samples: Sequence[float] | ndarray[tuple[Any, ...], dtype[floating]] | None = None, method: str = 'L-BFGS-B', maxiter: int = 1000, tol: float = 1e-06) OptimizationResult

Run gradient-based optimization.

Uses scipy.optimize.minimize with analytical gradients.

Parameters:
  • initial_params – Dictionary of initial parameter values.

  • bounds – Dictionary mapping parameter names to (min, max) bounds. If None, parameters are unbounded.

  • theta_samples – Theta values for objective evaluation.

  • method – Scipy optimization method. Default is “L-BFGS-B”.

  • maxiter – Maximum iterations. Default is 1000.

  • tol – Convergence tolerance. Default is 1e-6.

Returns:

OptimizationResult with optimal parameters.

pylinkage.symbolic.check_buildability(linkage: SymbolicLinkage, param_values: dict[str, float], theta_value: float = 0.0) tuple[bool, str]

Check if a linkage is buildable with given parameters.

A linkage is unbuildable if the constraint equations cannot be satisfied (e.g., circles don’t intersect). This function evaluates the positions at a specific theta and checks for complex (imaginary) results.

Parameters:
  • linkage – The symbolic linkage.

  • param_values – Dictionary mapping parameter names to numeric values.

  • theta_value – Theta value to check at. Default is 0.

Returns:

Tuple of (is_buildable, error_message).

Example

>>> buildable, msg = check_buildability(linkage, {"r0": 1, "r1": 10})
>>> if not buildable:
...     print(f"Linkage is unbuildable: {msg}")
pylinkage.symbolic.compute_trajectory_numeric(linkage: SymbolicLinkage, param_values: dict[str, float], theta_values: Sequence[float] | NDArray[np.floating], output_joints: list[str] | None = None) dict[str, NDArray[np.float64]]

Compute numeric trajectory by evaluating symbolic expressions.

Uses lambdify for efficient numeric evaluation of the symbolic trajectory expressions.

Parameters:
  • linkage – The symbolic linkage.

  • param_values – Dictionary mapping parameter names to numeric values.

  • theta_values – Sequence of theta values to evaluate at.

  • output_joints – List of joint names to include. If None, all joints.

Returns:

Dictionary mapping joint name to Nx2 array of (x, y) positions.

pylinkage.symbolic.create_trajectory_functions(linkage: SymbolicLinkage, output_joints: list[str] | None = None) dict[str, tuple[object, object, list[sp.Symbol]]]

Create lambdified functions for trajectory evaluation.

This is useful when you need to evaluate trajectories many times with different parameter values, as it avoids repeated lambdify calls.

Parameters:
  • linkage – The symbolic linkage.

  • output_joints – List of joint names to include. If None, all joints.

Returns:

Dictionary mapping joint name to (x_func, y_func, param_symbols).

Example

>>> funcs = create_trajectory_functions(linkage)
>>> x_func, y_func, params = funcs["C"]
>>> x_vals = x_func(theta_array, *[param_values[str(p)] for p in params])
pylinkage.symbolic.eliminate_theta(x_expr: Expr, y_expr: Expr, theta: Symbol | None = None) Expr | None

Eliminate theta to get an implicit curve equation f(x, y) = 0.

This converts parametric equations x(theta), y(theta) into an implicit algebraic equation in x and y only. This gives the algebraic equation of the coupler curve.

Uses the substitution cos(theta) = c, sin(theta) = s with the constraint c^2 + s^2 = 1, then eliminates c and s using Groebner basis.

Parameters:
  • x_expr – X coordinate as a function of theta.

  • y_expr – Y coordinate as a function of theta.

  • theta – The angle symbol to eliminate. Default uses global theta.

Returns:

Polynomial in x and y, or None if elimination fails.

Example

>>> theta = sp.Symbol('theta')
>>> x = sp.cos(theta)
>>> y = sp.sin(theta)
>>> eliminate_theta(x, y, theta)
x**2 + y**2 - 1
pylinkage.symbolic.fourbar_symbolic(ground_length: float | Expr | str = 'L0', crank_length: float | Expr | str = 'L1', coupler_length: float | Expr | str = 'L2', rocker_length: float | Expr | str = 'L3', ground_x: float = 0.0, ground_y: float = 0.0) SymbolicLinkage

Create a symbolic four-bar linkage.

The four-bar consists of:

  • A (ground left): Static anchor at (ground_x, ground_y)

  • D (ground right): Static anchor at (ground_x + ground_length, ground_y)

  • B (crank end): Crank rotating around A with radius crank_length

  • C (coupler/rocker connection): Revolute connecting B and D

Parameters:
  • ground_length – Distance between ground anchors A and D.

  • crank_length – Length of the input crank (A to B).

  • coupler_length – Length of the coupler (B to C).

  • rocker_length – Length of the output rocker (D to C).

  • ground_x – X coordinate of ground anchor A. Default is 0.

  • ground_y – Y coordinate of ground anchor A. Default is 0.

Returns:

A SymbolicLinkage representing the four-bar.

pylinkage.symbolic.generate_symbolic_bounds(linkage: SymbolicLinkage, center_values: dict[str, float], min_ratio: float = 5.0, max_factor: float = 5.0) dict[str, tuple[float, float]]

Generate optimization bounds based on center values.

Creates bounds as (center/min_ratio, center*max_factor) for each parameter. This is similar to the numeric generate_bounds function.

Parameters:
  • linkage – The symbolic linkage.

  • center_values – Dictionary of center values for each parameter.

  • min_ratio – Divisor for lower bound. Default is 5.

  • max_factor – Multiplier for upper bound. Default is 5.

Returns:

Dictionary mapping parameter names to (min, max) bounds.

pylinkage.symbolic.get_numeric_parameters(sym_linkage: SymbolicLinkage) dict[str, float]

Extract numeric parameter values stored in symbolic joints.

Returns a {parameter_name: value} dict populated from _numeric_* attributes previously stashed on each joint.

Parameters:

sym_linkage – The symbolic linkage.

Returns:

Dictionary mapping parameter names to numeric values.

pylinkage.symbolic.solve_linkage_symbolically(linkage: SymbolicLinkage, output_joints: list[str] | None = None, simplify: bool = True) dict[str, SymCoord]

Solve for joint positions as functions of theta.

Returns (optionally simplified) closed-form expressions for trajectory curves of all or specified joints.

Parameters:
  • linkage – The symbolic linkage to solve.

  • output_joints – List of joint names to include in output. If None, all joints are included.

  • simplify – Whether to simplify the expressions. Default is True.

Returns:

Dictionary mapping joint name to (x(theta), y(theta)).

pylinkage.symbolic.symbolic_circle_intersect(x1: Expr, y1: Expr, r1: Expr, x2: Expr, y2: Expr, r2: Expr, branch: int = 1) tuple[Expr, Expr]

Compute the symbolic intersection of two circles.

This function returns a closed-form expression for one of the two intersection points of two circles. The branch parameter selects which intersection point to return.

Algorithm (Paul Bourke, 1997):

  • d = distance between centers

  • a = (r1^2 - r2^2 + d^2) / (2*d) : distance from center1 to radical line

  • h = sqrt(r1^2 - a^2) : half-chord length

  • The intersection points are at distance h perpendicular to the line connecting the centers.

Parameters:
  • x1 – X coordinate of first circle center.

  • y1 – Y coordinate of first circle center.

  • r1 – Radius of first circle.

  • x2 – X coordinate of second circle center.

  • y2 – Y coordinate of second circle center.

  • r2 – Radius of second circle.

  • branch – +1 or -1 to select which intersection point. Default is +1.

Returns:

Tuple of (x, y) symbolic expressions for the intersection.

Note

The expressions may be complex (imaginary) for parameter values where the circles do not intersect. This is mathematically correct but will fail during numeric evaluation.

pylinkage.symbolic.symbolic_circle_line_intersect(cx: Expr, cy: Expr, r: Expr, p1_x: Expr, p1_y: Expr, p2_x: Expr, p2_y: Expr, branch: int = 1) tuple[Expr, Expr]

Compute the symbolic intersection of a circle and a line.

The line is defined by two points on it. The branch parameter selects which of the two intersection points to return.

Parameters:
  • cx – X coordinate of circle center.

  • cy – Y coordinate of circle center.

  • r – Radius of the circle.

  • p1_x – X coordinate of first point on line.

  • p1_y – Y coordinate of first point on line.

  • p2_x – X coordinate of second point on line.

  • p2_y – Y coordinate of second point on line.

  • branch – +1 or -1 to select which intersection point. Default is +1.

Returns:

Tuple of (x, y) symbolic expressions for the intersection.

Note

The expressions may be complex (imaginary) for parameter values where the circle and line do not intersect.

pylinkage.symbolic.symbolic_cyl_to_cart(radius: Expr, angle: Expr, ori_x: Expr | float = 0, ori_y: Expr | float = 0) tuple[Expr, Expr]

Convert polar coordinates to cartesian coordinates symbolically.

Parameters:
  • radius – Distance from origin.

  • angle – Angle in radians (counterclockwise from positive x-axis).

  • ori_x – X coordinate of origin. Default is 0.

  • ori_y – Y coordinate of origin. Default is 0.

Returns:

Tuple of (x, y) symbolic expressions.

pylinkage.symbolic.symbolic_dist(x1: Expr, y1: Expr, x2: Expr, y2: Expr) Expr

Compute the symbolic Euclidean distance between two points.

Parameters:
  • x1 – X coordinate of first point.

  • y1 – Y coordinate of first point.

  • x2 – X coordinate of second point.

  • y2 – Y coordinate of second point.

Returns:

Symbolic expression for the distance.

pylinkage.symbolic.symbolic_gradient(objective: Expr, parameters: list[Symbol]) list[Expr]

Compute the symbolic gradient of an objective function.

Parameters:
  • objective – Symbolic objective expression.

  • parameters – List of parameter symbols to differentiate with respect to.

Returns:

List of partial derivative expressions.

pylinkage.symbolic.symbolic_hessian(objective: Expr, parameters: list[Symbol]) MutableDenseMatrix

Compute the symbolic Hessian matrix of an objective function.

Parameters:
  • objective – Symbolic objective expression.

  • parameters – List of parameter symbols.

Returns:

SymPy Matrix of second partial derivatives.

pylinkage.symbolic.symbolic_sqr_dist(x1: Expr, y1: Expr, x2: Expr, y2: Expr) Expr

Compute the symbolic squared distance between two points.

Useful for avoiding square roots in constraint equations.

Parameters:
  • x1 – X coordinate of first point.

  • y1 – Y coordinate of first point.

  • x2 – X coordinate of second point.

  • y2 – Y coordinate of second point.

Returns:

Symbolic expression for the squared distance.