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_lengthC (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:
SymJointA 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:
ABCAbstract 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.
- 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:
SymJointA 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:
SymJointA 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:
objectA 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).
- 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:
objectResult 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:
objectGradient-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:
objectResult 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:
SymJointA 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:
ABCAbstract 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:
SymJointA 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:
SymJointA 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:
objectA 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:
objectGradient-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_lengthC (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.