pylinkage.linkage package

Submodules

pylinkage.linkage.analysis module

Analysis tools for linkages.

pylinkage.linkage.analysis.bounding_box(locus: Iterable[tuple[float, float]]) tuple[float, float, float, float]

Compute the bounding box of a locus.

Parameters:

locus – A list of points or any iterable with the same structure.

Returns:

Bounding box as (y_min, x_max, y_max, x_min).

pylinkage.linkage.analysis.kinematic_default_test(func: Callable[[...], float], error_penalty: float) Callable[[Linkage, Iterable[float], JointPositions | None], float]

Standard run for any linkage before a complete fitness evaluation.

This decorator makes a kinematic simulation, before passing the loci to the decorated function.

Parameters:
  • func – Fitness function to be decorated.

  • error_penalty – Penalty value for unbuildable linkage. Common values include float(‘inf’) and 0.

pylinkage.linkage.analysis.movement_bounding_box(loci: Iterable[Iterable[tuple[float, float]]]) tuple[float, float, float, float]

Bounding box for a group of loci.

Parameters:

loci – Iterable of loci (sequences of coordinates).

Returns:

Bounding box as (y_min, x_max, y_max, x_min).

pylinkage.linkage.linkage module

The linkage module defines useful classes for linkage definition.

Created on Fri Apr 16, 16:39:21 2021.

@author: HugoFara

class pylinkage.linkage.linkage.Linkage(joints: Iterable[Component], order: Iterable[Component] | None = None, name: str | None = None)

Bases: object

A linkage is a set of Joint objects.

It is defined as a kinematic linkage. Coordinates are given relative to its own base.

analyze_stroke(iterations: int | None = None) StrokeAnalysis

Analyze stroke/slide position over a full motion cycle.

For a linkage with a Prismatic joint, this tracks the slide position throughout the motion cycle.

Args:

iterations: Number of simulation steps. Defaults to one full rotation.

Returns:

StrokeAnalysis with min/max/range/mean statistics.

Raises:

ValueError: If no Prismatic joint found or no valid positions.

Example:
>>> analysis = linkage.analyze_stroke()
>>> print(f"Stroke range: {analysis.stroke_range:.2f}")
>>> print(f"Min: {analysis.min_position:.2f}")
>>> print(f"Max: {analysis.max_position:.2f}")
analyze_transmission(iterations: int | None = None, acceptable_range: tuple[float, float] = (40.0, 140.0)) TransmissionAngleAnalysis

Analyze transmission angle over a full motion cycle.

For a standard four-bar, joints are auto-detected.

Args:

iterations: Number of simulation steps. Defaults to one full rotation. acceptable_range: (min, max) acceptable angles in degrees.

Returns:

TransmissionAngleAnalysis with min/max/mean/is_acceptable.

Raises:

ValueError: If joints cannot be detected or no valid positions found.

Example:
>>> analysis = linkage.analyze_transmission()
>>> print(f"Min: {analysis.min_angle:.1f}°")
>>> print(f"Max: {analysis.max_angle:.1f}°")
>>> print(f"Acceptable: {analysis.is_acceptable}")
compile() None

Prepare numba-optimized solver for fast simulation.

This converts the linkage structure into numeric arrays for use by the numba-compiled simulation loop. The compilation is done automatically on first call to step_fast(), but can be called explicitly to control when the overhead occurs.

The compiled solver is cached and reused until invalidated by changes to constraints (set_num_constraints) or structure.

classmethod from_dict(data: dict[str, Any]) Linkage

Create a linkage from a dictionary representation.

Args:

data: Dictionary containing linkage data (as produced by to_dict()).

Returns:

A new Linkage instance.

Example:
>>> data = linkage.to_dict()
>>> new_linkage = Linkage.from_dict(data)
classmethod from_json(path: str | Path) Linkage

Load a linkage from a JSON file.

Args:

path: Path to the JSON file.

Returns:

A new Linkage instance.

Example:
>>> linkage.to_json("my_linkage.json")
>>> loaded = Linkage.from_json("my_linkage.json")
get_accelerations() list[tuple[float, float] | None]

Return accelerations for all joints.

Returns:

List of (ax, ay) tuples, one per joint. Returns None for joints whose acceleration has not been computed.

get_coords() list[tuple[float | None, float | None]]

Return the positions of each element in the system.

get_num_constraints(flat: bool = True) list[float | None] | list[tuple[float | None, ...]]

Numeric constraints of this linkage.

Parameters:

flat – Whether to force one-dimensional representation of constraints. The default is True.

Returns:

List of geometric constraints.

get_rotation_period() int

The number of iterations to finish in the previous state.

Formally, it is the common denominator of all crank periods.

Returns:

Number of iterations with dt=1.

get_velocities() list[tuple[float, float] | None]

Return velocities for all joints.

Returns:

List of (vx, vy) tuples, one per joint. Returns None for joints whose velocity has not been computed.

indeterminacy() int

Return the static indeterminacy degree of the linkage in 2D.

Uses a variant of the Gruebler-Kutzbach criterion for 2D planar mechanisms:

DOF = 3 * (n - 1) - kinematic_pairs + mobilities

Where:
  • n = number of bodies (including the ground/frame)

  • kinematic_pairs = sum of constraint DOFs removed by joints

  • mobilities = input degrees of freedom (e.g., from motors/cranks)

A positive return value indicates the mechanism is under-constrained, zero means it’s exactly constrained, and negative means it’s over-constrained.

Returns:

The indeterminacy degree (negative DOF when over-constrained).

Note:

This implementation is experimental and results should be verified for complex mechanisms. The algorithm counts bodies and constraints based on joint types (Static, Crank, Revolute, Fixed).

joints: tuple[Component, ...]
name: str
rebuild(pos: Sequence[tuple[float | None, float | None]] | None = None) None

Redefine linkage joints and given initial positions to joints.

Parameters:

pos – Initial positions for each joint in self.joints. Coordinates do not need to be precise, they will allow us the best fitting position between all possible positions satisfying constraints. (Default value = None).

sensitivity_analysis(output_joint: object | int | None = None, delta: float = 0.01, include_transmission: bool = True, iterations: int | None = None) SensitivityAnalysis

Analyze sensitivity of output path to each constraint dimension.

For each constraint, this function perturbs the value by delta and measures how much the output path changes.

Args:

output_joint: Joint to measure, index, or None (auto-detect last). delta: Relative perturbation magnitude (e.g., 0.01 for 1%). include_transmission: Whether to also measure transmission angle. iterations: Number of simulation steps. Defaults to one full rotation.

Returns:

SensitivityAnalysis with sensitivity coefficients and rankings.

Example:
>>> analysis = linkage.sensitivity_analysis(delta=0.01)
>>> print(f"Most sensitive: {analysis.most_sensitive}")
>>> for name, sens in analysis.sensitivity_ranking:
...     print(f"{name}: {sens:.4f}")
set_completely(dimensions: Iterable[float] | Iterable[tuple[float, ...]], positions: Sequence[tuple[float | None, float | None]], flat: bool = True) None

Set both dimension and initial positions.

Parameters:
  • dimensions – List of dimensions.

  • positions – Initial positions.

  • flat – If the dimensions are in “flat mode”. The default is True.

set_coords(coords: Sequence[tuple[float | None, float | None]]) None

Set coordinates for all joints of the linkage.

Parameters:

coords – Joint coordinates.

set_input_velocity(crank: Component, omega: float, alpha: float = 0.0) None

Set angular velocity and acceleration for a crank joint.

This is used for kinematics computation (velocity/acceleration analysis).

Args:

crank: The crank joint to set velocity for. omega: Angular velocity in rad/s. alpha: Angular acceleration in rad/s² (default 0).

Raises:

ValueError: If the crank is not part of this linkage.

Example:
>>> linkage.set_input_velocity(crank, omega=10.0)  # 10 rad/s
>>> positions, velocities = linkage.step_fast_with_kinematics(100)
set_num_constraints(constraints: Iterable[float] | Iterable[tuple[float, ...]], flat: bool = True) None

Set numeric constraints for this linkage.

Numeric constraints are distances or angles between joints.

Note:

This invalidates any cached solver data. The next call to step_fast() will recompile the solver automatically.

Parameters:
  • constraints – Sequence of constraints to pass to the joints.

  • flat – If True, constraints should be a one-dimensional sequence of floats. If False, constraints should be a sequence of tuples of digits. Each element will be passed to the set_constraints method of each corresponding Joint. (Default value = True).

simulation(iterations: int | None = None, dt: float = 1) Simulation

Create a simulation context manager for the linkage.

This provides a convenient way to run and iterate over linkage simulations with automatic resource management.

Example:
>>> with linkage.simulation(iterations=100) as sim:
...     for step, coords in sim:
...         process(coords)
Parameters:
  • iterations – Number of iterations to run. If None, uses get_rotation_period().

  • dt – Time step for crank rotation. Default is 1.

Returns:

A Simulation context manager.

step(iterations: int | None = None, dt: float = 1) Generator[tuple[tuple[float | None, float | None], ...], None, None]

Make a step of the linkage.

Parameters:
  • iterations – Number of iterations to run across. If None, the default is self.get_rotation_period(). (Default value = None).

  • dt – Amount of rotation to turn the cranks by. All cranks rotate by their self.angle * dt. The default is 1. (Default value = 1).

Returns:

Iterable of the joints’ coordinates.

step_fast(iterations: int | None = None, dt: float = 1) ndarray[tuple[Any, ...], dtype[float64]]

Run simulation using numba-optimized solver.

This is significantly faster than step() for large iteration counts, as it avoids Python method call overhead in the hot loop.

The first call may be slower due to numba compilation (JIT warm-up). Subsequent calls will be fast.

Args:
iterations: Number of iterations to run. If None, uses

get_rotation_period(). (Default: None)

dt: Amount of rotation per step. Cranks rotate by their

angle * dt. (Default: 1)

Returns:

Array of shape (iterations, n_joints, 2) containing all joint positions at each step. Access individual step positions via trajectory[step_idx, joint_idx] which gives (x, y).

Note:

If any configuration becomes unbuildable during simulation, the corresponding positions will be NaN. Check for this with np.isnan(trajectory).any() or use solver.has_nan_positions().

Example:
>>> trajectory = linkage.step_fast(iterations=1000)
>>> print(trajectory.shape)  # (1000, n_joints, 2)
>>> # Get position of joint 2 at step 100:
>>> pos = trajectory[100, 2]  # (x, y)
step_fast_with_kinematics(iterations: int | None = None, dt: float = 1) tuple[ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[float64]]]

Run simulation with velocity and acceleration using numba-optimized solver.

This extends step_fast() to also compute velocities and accelerations at each step. Requires that omega (and optionally alpha) is set on crank joints to specify angular velocities and accelerations.

Args:
iterations: Number of iterations to run. If None, uses

get_rotation_period(). (Default: None)

dt: Amount of rotation per step. Cranks rotate by their

angle * dt. (Default: 1)

Returns:

Tuple of (positions, velocities, accelerations) arrays, each with shape (iterations, n_joints, 2).

Example:
>>> linkage.set_input_velocity(crank, omega=10.0, alpha=0.0)
>>> pos, vel, acc = linkage.step_fast_with_kinematics(1000)
>>> print(vel[100, 2])  # velocity of joint 2 at step 100
>>> print(acc[100, 2])  # acceleration of joint 2 at step 100
stroke_position() float

Get the slide position of a prismatic joint at current position.

For a linkage with a Prismatic joint, this returns the slide position along the prismatic axis.

Returns:

Slide position along the axis.

Raises:

ValueError: If no Prismatic joint found.

Example:
>>> pos = linkage.stroke_position()
>>> print(f"Current slide position: {pos:.2f}")
to_dict() dict[str, Any]

Convert this linkage to a dictionary representation.

The dictionary can be used for serialization or reconstruction.

Returns:

Dictionary containing the linkage’s data including joints and solve order.

Example:
>>> data = linkage.to_dict()
>>> new_linkage = Linkage.from_dict(data)
to_json(path: str | Path) None

Save this linkage to a JSON file.

Args:

path: Path to the output JSON file.

Example:
>>> linkage.to_json("my_linkage.json")
>>> loaded = Linkage.from_json("my_linkage.json")
tolerance_analysis(tolerances: dict[str, float], output_joint: object | int | None = None, iterations: int | None = None, n_samples: int = 1000, seed: int | None = None) ToleranceAnalysis

Analyze manufacturing tolerance effects via Monte Carlo simulation.

Each sample randomly perturbs constraints within tolerance bounds and simulates the linkage to measure output variation.

Args:

tolerances: Constraint name -> tolerance value (e.g., {“crank_radius”: 0.1}). output_joint: Joint to measure, index, or None (auto-detect last). iterations: Number of simulation steps. Defaults to one full rotation. n_samples: Number of Monte Carlo samples. seed: Random seed for reproducibility.

Returns:

ToleranceAnalysis with statistics and output cloud for visualization.

Example:
>>> tolerances = {"Crank_radius": 0.1, "Revolute_dist1": 0.2}
>>> analysis = linkage.tolerance_analysis(tolerances, n_samples=500)
>>> print(f"Max deviation: {analysis.max_deviation:.4f}")
>>> analysis.plot_cloud()
transmission_angle() float

Get the transmission angle at the current linkage position.

For a four-bar linkage, this is the angle between the coupler and rocker links at their common joint.

Returns:

Transmission angle in degrees (0-180).

Raises:

ValueError: If joints cannot be auto-detected.

Example:
>>> angle = linkage.transmission_angle()
>>> print(f"Current transmission angle: {angle:.1f}°")
class pylinkage.linkage.linkage.Simulation(linkage: Linkage, iterations: int | None = None, dt: float = 1)

Bases: object

Context manager for linkage simulation.

Provides a clean interface for iterating over linkage positions with automatic setup and teardown.

Example:
>>> with linkage.simulation(iterations=100) as sim:
...     for step, coords in sim:
...         # coords is a tuple of (x, y) for each joint
...         print(f"Step {step}: {coords}")
property iterations: int

Number of iterations for this simulation.

property linkage: Linkage

The linkage being simulated.

pylinkage.linkage.sensitivity module

Sensitivity and tolerance analysis for linkage mechanisms.

This module provides analysis tools for:

  1. Sensitivity analysis: - Measures how each constraint dimension affects the output path - Identifies critical dimensions that most affect mechanism behavior

  2. Tolerance analysis: - Monte Carlo simulation of manufacturing tolerances - Statistical analysis of output variation due to dimensional tolerances

class pylinkage.linkage.sensitivity.SensitivityAnalysis(sensitivities: dict[str, float], baseline_path_metric: float, baseline_transmission: float | None, perturbed_path_metrics: ndarray[tuple[Any, ...], dtype[float64]], perturbed_transmission: ndarray[tuple[Any, ...], dtype[float64]] | None, constraint_names: tuple[str, ...], perturbation_delta: float)

Bases: object

Results of sensitivity analysis for a linkage.

Sensitivity measures how much the output path changes when each constraint dimension is perturbed by a small amount.

Attributes:
sensitivities: Mapping from constraint name to sensitivity coefficient.

Higher values indicate the output is more sensitive to that constraint.

baseline_path_metric: Mean path deviation at nominal constraints (should be 0). baseline_transmission: Mean transmission angle at nominal (degrees), or None. perturbed_path_metrics: Array of path deviation metrics, one per constraint. perturbed_transmission: Array of transmission angles per constraint, or None. constraint_names: Tuple of constraint names in order. perturbation_delta: Relative perturbation magnitude used (e.g., 0.01 for 1%).

baseline_path_metric: float
baseline_transmission: float | None
constraint_names: tuple[str, ...]
property most_sensitive: str

Return name of the most sensitive constraint.

perturbation_delta: float
perturbed_path_metrics: ndarray[tuple[Any, ...], dtype[float64]]
perturbed_transmission: ndarray[tuple[Any, ...], dtype[float64]] | None
sensitivities: dict[str, float]
property sensitivity_ranking: list[tuple[str, float]]

Return constraints ranked by sensitivity (highest first).

to_dataframe() object

Export results as pandas DataFrame.

Returns:

DataFrame with columns: constraint, sensitivity, perturbed_metric

Raises:

ImportError: If pandas is not installed.

class pylinkage.linkage.sensitivity.ToleranceAnalysis(nominal_path: ndarray[tuple[Any, ...], dtype[float64]], output_cloud: ndarray[tuple[Any, ...], dtype[float64]], tolerances: dict[str, float], mean_deviation: float, max_deviation: float, std_deviation: float, position_std: ndarray[tuple[Any, ...], dtype[float64]])

Bases: object

Results of Monte Carlo tolerance analysis.

Tolerance analysis simulates how manufacturing variations in link dimensions affect the output path. Each sample represents a possible manufactured linkage within tolerance bounds.

Attributes:

nominal_path: Output path at nominal dimensions, shape (n_steps, 2). output_cloud: Monte Carlo results, shape (n_samples, n_steps, 2). tolerances: Dictionary mapping constraint names to their tolerances. mean_deviation: Mean path deviation from nominal across all samples. max_deviation: Maximum path deviation from nominal (worst case). std_deviation: Standard deviation of path deviations. position_std: Per-position standard deviation, shape (n_steps,).

max_deviation: float
mean_deviation: float
nominal_path: ndarray[tuple[Any, ...], dtype[float64]]
output_cloud: ndarray[tuple[Any, ...], dtype[float64]]
plot_cloud(ax: Axes | None = None, show_nominal: bool = True, alpha: float = 0.1) Axes

Plot the tolerance cloud as a scatter plot.

Args:

ax: Matplotlib axes to plot on. Creates new figure if None. show_nominal: Whether to show the nominal path as a solid line. alpha: Transparency for sample points (0-1).

Returns:

Matplotlib axes with the plot.

position_std: ndarray[tuple[Any, ...], dtype[float64]]
std_deviation: float
to_dataframe() object

Export statistics as pandas DataFrame.

Returns:

DataFrame with tolerance statistics.

Raises:

ImportError: If pandas is not installed.

tolerances: dict[str, float]
pylinkage.linkage.sensitivity.analyze_sensitivity(linkage: Linkage, output_joint: object | int | None = None, delta: float = 0.01, include_transmission: bool = True, iterations: int | None = None) SensitivityAnalysis

Analyze sensitivity of output path to each constraint dimension.

For each constraint, this function: 1. Perturbs the constraint by delta (relative) 2. Simulates the linkage 3. Measures the path deviation from nominal 4. Computes sensitivity coefficient

Args:

linkage: The linkage to analyze. output_joint: Joint to measure, index, or None (auto-detect last joint). delta: Relative perturbation magnitude (e.g., 0.01 for 1%). include_transmission: Whether to also measure transmission angle sensitivity. iterations: Number of simulation steps. Defaults to one full rotation.

Returns:

SensitivityAnalysis with sensitivity coefficients and statistics.

Example:
>>> analysis = linkage.sensitivity_analysis(delta=0.01)
>>> print(analysis.most_sensitive)
>>> for name, sens in analysis.sensitivity_ranking:
...     print(f"{name}: {sens:.4f}")
pylinkage.linkage.sensitivity.analyze_tolerance(linkage: Linkage, tolerances: dict[str, float], output_joint: object | int | None = None, iterations: int | None = None, n_samples: int = 1000, seed: int | None = None) ToleranceAnalysis

Analyze manufacturing tolerance effects via Monte Carlo simulation.

For each sample: 1. Randomly perturb constraints within tolerance bounds 2. Simulate the linkage 3. Record the output path

Args:

linkage: The linkage to analyze. tolerances: Dictionary mapping constraint names to tolerance values.

Each constraint is perturbed by +/- tolerance uniformly.

output_joint: Joint to measure, index, or None (auto-detect last joint). iterations: Number of simulation steps. Defaults to one full rotation. n_samples: Number of Monte Carlo samples to run. seed: Random seed for reproducibility.

Returns:

ToleranceAnalysis with statistics and the sample cloud.

Example:
>>> tolerances = {
...     "Crank_radius": 0.1,    # +/- 0.1 mm
...     "Revolute_dist1": 0.2,  # +/- 0.2 mm
... }
>>> analysis = linkage.tolerance_analysis(tolerances, n_samples=500)
>>> print(f"Max deviation: {analysis.max_deviation:.4f}")
>>> analysis.plot_cloud()

pylinkage.linkage.transmission module

Kinematic analysis for linkage mechanisms.

This module provides analysis tools for:

  1. Transmission angle analysis (for Revolute/RRR joints): - The angle between coupler and output links at their connecting joint - Ideal range: 40° to 140°, optimal at 90°

  2. Stroke analysis (for Prismatic/RRP joints): - The slide position along the prismatic axis - Tracks min/max/range of travel over a motion cycle

class pylinkage.linkage.transmission.StrokeAnalysis(min_position: float, max_position: float, mean_position: float, stroke_range: float, positions: ndarray[tuple[Any, ...], dtype[float64]], min_position_step: int, max_position_step: int)

Bases: object

Results of stroke analysis for a prismatic joint over a motion cycle.

The stroke is the position of the slider along its axis, measured as the signed distance from the first line point (joint1) projected onto the slide axis.

Attributes:

min_position: Minimum slide position along the axis. max_position: Maximum slide position along the axis. mean_position: Mean slide position. stroke_range: Total travel distance (max - min). positions: Array of slide positions at each step. min_position_step: Step index where minimum position occurs. max_position_step: Step index where maximum position occurs.

property amplitude: float

Return half the stroke range (useful for oscillating mechanisms).

property center_position: float

Return the center of the stroke range.

max_position: float
max_position_step: int
mean_position: float
min_position: float
min_position_step: int
positions: ndarray[tuple[Any, ...], dtype[float64]]
stroke_range: float
class pylinkage.linkage.transmission.TransmissionAngleAnalysis(min_angle: float, max_angle: float, mean_angle: float, angles: ndarray[tuple[Any, ...], dtype[float64]], is_acceptable: bool, min_deviation: float, max_deviation: float, min_angle_step: int, max_angle_step: int)

Bases: object

Results of transmission angle analysis over a motion cycle.

Attributes:

min_angle: Minimum transmission angle in degrees. max_angle: Maximum transmission angle in degrees. mean_angle: Mean transmission angle in degrees. angles: Array of transmission angles at each step (degrees). is_acceptable: True if angle always in acceptable range. min_deviation: Minimum deviation from 90 degrees. max_deviation: Maximum deviation from 90 degrees. min_angle_step: Step index where minimum angle occurs. max_angle_step: Step index where maximum angle occurs.

property acceptable_range: tuple[float, float]

Return the standard acceptable range [40, 140] degrees.

angles: ndarray[tuple[Any, ...], dtype[float64]]
is_acceptable: bool
max_angle: float
max_angle_step: int
max_deviation: float
mean_angle: float
min_angle: float
min_angle_step: int
min_deviation: float
worst_angle() float

Return the angle with maximum deviation from 90 degrees.

pylinkage.linkage.transmission.analyze_stroke(linkage: Linkage, prismatic_joint: object | None = None, iterations: int | None = None) StrokeAnalysis

Analyze stroke/slide position over a full motion cycle.

For a linkage with a Prismatic joint, this tracks the slide position along the prismatic axis throughout the motion cycle.

Args:

linkage: The Linkage to analyze. prismatic_joint: The Prismatic joint to analyze. Auto-detected if None. iterations: Number of simulation steps. Defaults to one full rotation.

Returns:

StrokeAnalysis with statistics over the motion cycle.

Raises:

ValueError: If joint cannot be detected or no valid positions found.

pylinkage.linkage.transmission.analyze_transmission(linkage: Linkage, iterations: int | None = None, acceptable_range: tuple[float, float] = (40.0, 140.0)) TransmissionAngleAnalysis

Analyze transmission angle over a full motion cycle.

For a standard four-bar linkage, joints are auto-detected: - coupler_joint: The Crank joint (B) - output_joint: The Revolute joint (C) - rocker_pivot: The Static joint at Revolute.joint1 (D)

Args:

linkage: The Linkage to analyze. iterations: Number of simulation steps. Defaults to one full rotation. acceptable_range: (min, max) acceptable angles in degrees.

Returns:

TransmissionAngleAnalysis with statistics over the motion cycle.

Raises:

ValueError: If joints cannot be detected or no valid positions found.

pylinkage.linkage.transmission.compute_slide_position(slider_pos: tuple[float, float], line_point1: tuple[float, float], line_point2: tuple[float, float]) float

Compute the slide position along a prismatic axis.

The position is the signed distance from line_point1 to the projection of slider_pos onto the line, measured in the direction of line_point2.

Args:

slider_pos: Current position of the slider joint. line_point1: First point defining the slide axis (origin). line_point2: Second point defining the slide axis (direction).

Returns:

Signed distance along the axis from line_point1.

pylinkage.linkage.transmission.compute_transmission_angle(coupler_joint: tuple[float, float], output_joint: tuple[float, float], rocker_pivot: tuple[float, float]) float

Compute transmission angle in degrees.

The transmission angle is the angle between: - Coupler link: from coupler_joint (B) to output_joint (C) - Rocker link: from rocker_pivot (D) to output_joint (C)

Args:

coupler_joint: Position of the coupler input joint (B). output_joint: Position where angle is measured (C). rocker_pivot: Position of the rocker ground pivot (D).

Returns:

Transmission angle in degrees (0-180).

pylinkage.linkage.transmission.stroke_at_position(linkage: Linkage, prismatic_joint: object | None = None) float

Compute the slide position of a prismatic joint at current position.

Args:

linkage: The linkage to analyze. prismatic_joint: The Prismatic joint to analyze. Auto-detected if None.

Returns:

Slide position along the prismatic axis.

Raises:

ValueError: If joint cannot be determined or positions are invalid.

pylinkage.linkage.transmission.transmission_angle_at_position(linkage: Linkage, coupler_joint: object | None = None, output_joint: object | None = None, rocker_pivot: object | None = None) float

Compute transmission angle at the current linkage position.

Args:

linkage: The linkage to analyze. coupler_joint: Joint at coupler input (B). Auto-detected if None. output_joint: Joint where angle is measured (C). Auto-detected if None. rocker_pivot: Ground pivot of rocker (D). Auto-detected if None.

Returns:

Transmission angle in degrees.

Raises:

ValueError: If joints cannot be determined or positions are invalid.

Module contents

Definition and analysis of a linkage as a dynamic set of joints.

class pylinkage.linkage.Linkage(joints: Iterable[Component], order: Iterable[Component] | None = None, name: str | None = None)

Bases: object

A linkage is a set of Joint objects.

It is defined as a kinematic linkage. Coordinates are given relative to its own base.

analyze_stroke(iterations: int | None = None) StrokeAnalysis

Analyze stroke/slide position over a full motion cycle.

For a linkage with a Prismatic joint, this tracks the slide position throughout the motion cycle.

Args:

iterations: Number of simulation steps. Defaults to one full rotation.

Returns:

StrokeAnalysis with min/max/range/mean statistics.

Raises:

ValueError: If no Prismatic joint found or no valid positions.

Example:
>>> analysis = linkage.analyze_stroke()
>>> print(f"Stroke range: {analysis.stroke_range:.2f}")
>>> print(f"Min: {analysis.min_position:.2f}")
>>> print(f"Max: {analysis.max_position:.2f}")
analyze_transmission(iterations: int | None = None, acceptable_range: tuple[float, float] = (40.0, 140.0)) TransmissionAngleAnalysis

Analyze transmission angle over a full motion cycle.

For a standard four-bar, joints are auto-detected.

Args:

iterations: Number of simulation steps. Defaults to one full rotation. acceptable_range: (min, max) acceptable angles in degrees.

Returns:

TransmissionAngleAnalysis with min/max/mean/is_acceptable.

Raises:

ValueError: If joints cannot be detected or no valid positions found.

Example:
>>> analysis = linkage.analyze_transmission()
>>> print(f"Min: {analysis.min_angle:.1f}°")
>>> print(f"Max: {analysis.max_angle:.1f}°")
>>> print(f"Acceptable: {analysis.is_acceptable}")
compile() None

Prepare numba-optimized solver for fast simulation.

This converts the linkage structure into numeric arrays for use by the numba-compiled simulation loop. The compilation is done automatically on first call to step_fast(), but can be called explicitly to control when the overhead occurs.

The compiled solver is cached and reused until invalidated by changes to constraints (set_num_constraints) or structure.

classmethod from_dict(data: dict[str, Any]) Linkage

Create a linkage from a dictionary representation.

Args:

data: Dictionary containing linkage data (as produced by to_dict()).

Returns:

A new Linkage instance.

Example:
>>> data = linkage.to_dict()
>>> new_linkage = Linkage.from_dict(data)
classmethod from_json(path: str | Path) Linkage

Load a linkage from a JSON file.

Args:

path: Path to the JSON file.

Returns:

A new Linkage instance.

Example:
>>> linkage.to_json("my_linkage.json")
>>> loaded = Linkage.from_json("my_linkage.json")
get_accelerations() list[tuple[float, float] | None]

Return accelerations for all joints.

Returns:

List of (ax, ay) tuples, one per joint. Returns None for joints whose acceleration has not been computed.

get_coords() list[tuple[float | None, float | None]]

Return the positions of each element in the system.

get_num_constraints(flat: bool = True) list[float | None] | list[tuple[float | None, ...]]

Numeric constraints of this linkage.

Parameters:

flat – Whether to force one-dimensional representation of constraints. The default is True.

Returns:

List of geometric constraints.

get_rotation_period() int

The number of iterations to finish in the previous state.

Formally, it is the common denominator of all crank periods.

Returns:

Number of iterations with dt=1.

get_velocities() list[tuple[float, float] | None]

Return velocities for all joints.

Returns:

List of (vx, vy) tuples, one per joint. Returns None for joints whose velocity has not been computed.

indeterminacy() int

Return the static indeterminacy degree of the linkage in 2D.

Uses a variant of the Gruebler-Kutzbach criterion for 2D planar mechanisms:

DOF = 3 * (n - 1) - kinematic_pairs + mobilities

Where:
  • n = number of bodies (including the ground/frame)

  • kinematic_pairs = sum of constraint DOFs removed by joints

  • mobilities = input degrees of freedom (e.g., from motors/cranks)

A positive return value indicates the mechanism is under-constrained, zero means it’s exactly constrained, and negative means it’s over-constrained.

Returns:

The indeterminacy degree (negative DOF when over-constrained).

Note:

This implementation is experimental and results should be verified for complex mechanisms. The algorithm counts bodies and constraints based on joint types (Static, Crank, Revolute, Fixed).

joints: tuple[Component, ...]
name: str
rebuild(pos: Sequence[tuple[float | None, float | None]] | None = None) None

Redefine linkage joints and given initial positions to joints.

Parameters:

pos – Initial positions for each joint in self.joints. Coordinates do not need to be precise, they will allow us the best fitting position between all possible positions satisfying constraints. (Default value = None).

sensitivity_analysis(output_joint: object | int | None = None, delta: float = 0.01, include_transmission: bool = True, iterations: int | None = None) SensitivityAnalysis

Analyze sensitivity of output path to each constraint dimension.

For each constraint, this function perturbs the value by delta and measures how much the output path changes.

Args:

output_joint: Joint to measure, index, or None (auto-detect last). delta: Relative perturbation magnitude (e.g., 0.01 for 1%). include_transmission: Whether to also measure transmission angle. iterations: Number of simulation steps. Defaults to one full rotation.

Returns:

SensitivityAnalysis with sensitivity coefficients and rankings.

Example:
>>> analysis = linkage.sensitivity_analysis(delta=0.01)
>>> print(f"Most sensitive: {analysis.most_sensitive}")
>>> for name, sens in analysis.sensitivity_ranking:
...     print(f"{name}: {sens:.4f}")
set_completely(dimensions: Iterable[float] | Iterable[tuple[float, ...]], positions: Sequence[tuple[float | None, float | None]], flat: bool = True) None

Set both dimension and initial positions.

Parameters:
  • dimensions – List of dimensions.

  • positions – Initial positions.

  • flat – If the dimensions are in “flat mode”. The default is True.

set_coords(coords: Sequence[tuple[float | None, float | None]]) None

Set coordinates for all joints of the linkage.

Parameters:

coords – Joint coordinates.

set_input_velocity(crank: Component, omega: float, alpha: float = 0.0) None

Set angular velocity and acceleration for a crank joint.

This is used for kinematics computation (velocity/acceleration analysis).

Args:

crank: The crank joint to set velocity for. omega: Angular velocity in rad/s. alpha: Angular acceleration in rad/s² (default 0).

Raises:

ValueError: If the crank is not part of this linkage.

Example:
>>> linkage.set_input_velocity(crank, omega=10.0)  # 10 rad/s
>>> positions, velocities = linkage.step_fast_with_kinematics(100)
set_num_constraints(constraints: Iterable[float] | Iterable[tuple[float, ...]], flat: bool = True) None

Set numeric constraints for this linkage.

Numeric constraints are distances or angles between joints.

Note:

This invalidates any cached solver data. The next call to step_fast() will recompile the solver automatically.

Parameters:
  • constraints – Sequence of constraints to pass to the joints.

  • flat – If True, constraints should be a one-dimensional sequence of floats. If False, constraints should be a sequence of tuples of digits. Each element will be passed to the set_constraints method of each corresponding Joint. (Default value = True).

simulation(iterations: int | None = None, dt: float = 1) Simulation

Create a simulation context manager for the linkage.

This provides a convenient way to run and iterate over linkage simulations with automatic resource management.

Example:
>>> with linkage.simulation(iterations=100) as sim:
...     for step, coords in sim:
...         process(coords)
Parameters:
  • iterations – Number of iterations to run. If None, uses get_rotation_period().

  • dt – Time step for crank rotation. Default is 1.

Returns:

A Simulation context manager.

step(iterations: int | None = None, dt: float = 1) Generator[tuple[tuple[float | None, float | None], ...], None, None]

Make a step of the linkage.

Parameters:
  • iterations – Number of iterations to run across. If None, the default is self.get_rotation_period(). (Default value = None).

  • dt – Amount of rotation to turn the cranks by. All cranks rotate by their self.angle * dt. The default is 1. (Default value = 1).

Returns:

Iterable of the joints’ coordinates.

step_fast(iterations: int | None = None, dt: float = 1) ndarray[tuple[Any, ...], dtype[float64]]

Run simulation using numba-optimized solver.

This is significantly faster than step() for large iteration counts, as it avoids Python method call overhead in the hot loop.

The first call may be slower due to numba compilation (JIT warm-up). Subsequent calls will be fast.

Args:
iterations: Number of iterations to run. If None, uses

get_rotation_period(). (Default: None)

dt: Amount of rotation per step. Cranks rotate by their

angle * dt. (Default: 1)

Returns:

Array of shape (iterations, n_joints, 2) containing all joint positions at each step. Access individual step positions via trajectory[step_idx, joint_idx] which gives (x, y).

Note:

If any configuration becomes unbuildable during simulation, the corresponding positions will be NaN. Check for this with np.isnan(trajectory).any() or use solver.has_nan_positions().

Example:
>>> trajectory = linkage.step_fast(iterations=1000)
>>> print(trajectory.shape)  # (1000, n_joints, 2)
>>> # Get position of joint 2 at step 100:
>>> pos = trajectory[100, 2]  # (x, y)
step_fast_with_kinematics(iterations: int | None = None, dt: float = 1) tuple[ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[float64]]]

Run simulation with velocity and acceleration using numba-optimized solver.

This extends step_fast() to also compute velocities and accelerations at each step. Requires that omega (and optionally alpha) is set on crank joints to specify angular velocities and accelerations.

Args:
iterations: Number of iterations to run. If None, uses

get_rotation_period(). (Default: None)

dt: Amount of rotation per step. Cranks rotate by their

angle * dt. (Default: 1)

Returns:

Tuple of (positions, velocities, accelerations) arrays, each with shape (iterations, n_joints, 2).

Example:
>>> linkage.set_input_velocity(crank, omega=10.0, alpha=0.0)
>>> pos, vel, acc = linkage.step_fast_with_kinematics(1000)
>>> print(vel[100, 2])  # velocity of joint 2 at step 100
>>> print(acc[100, 2])  # acceleration of joint 2 at step 100
stroke_position() float

Get the slide position of a prismatic joint at current position.

For a linkage with a Prismatic joint, this returns the slide position along the prismatic axis.

Returns:

Slide position along the axis.

Raises:

ValueError: If no Prismatic joint found.

Example:
>>> pos = linkage.stroke_position()
>>> print(f"Current slide position: {pos:.2f}")
to_dict() dict[str, Any]

Convert this linkage to a dictionary representation.

The dictionary can be used for serialization or reconstruction.

Returns:

Dictionary containing the linkage’s data including joints and solve order.

Example:
>>> data = linkage.to_dict()
>>> new_linkage = Linkage.from_dict(data)
to_json(path: str | Path) None

Save this linkage to a JSON file.

Args:

path: Path to the output JSON file.

Example:
>>> linkage.to_json("my_linkage.json")
>>> loaded = Linkage.from_json("my_linkage.json")
tolerance_analysis(tolerances: dict[str, float], output_joint: object | int | None = None, iterations: int | None = None, n_samples: int = 1000, seed: int | None = None) ToleranceAnalysis

Analyze manufacturing tolerance effects via Monte Carlo simulation.

Each sample randomly perturbs constraints within tolerance bounds and simulates the linkage to measure output variation.

Args:

tolerances: Constraint name -> tolerance value (e.g., {“crank_radius”: 0.1}). output_joint: Joint to measure, index, or None (auto-detect last). iterations: Number of simulation steps. Defaults to one full rotation. n_samples: Number of Monte Carlo samples. seed: Random seed for reproducibility.

Returns:

ToleranceAnalysis with statistics and output cloud for visualization.

Example:
>>> tolerances = {"Crank_radius": 0.1, "Revolute_dist1": 0.2}
>>> analysis = linkage.tolerance_analysis(tolerances, n_samples=500)
>>> print(f"Max deviation: {analysis.max_deviation:.4f}")
>>> analysis.plot_cloud()
transmission_angle() float

Get the transmission angle at the current linkage position.

For a four-bar linkage, this is the angle between the coupler and rocker links at their common joint.

Returns:

Transmission angle in degrees (0-180).

Raises:

ValueError: If joints cannot be auto-detected.

Example:
>>> angle = linkage.transmission_angle()
>>> print(f"Current transmission angle: {angle:.1f}°")
class pylinkage.linkage.SensitivityAnalysis(sensitivities: dict[str, float], baseline_path_metric: float, baseline_transmission: float | None, perturbed_path_metrics: ndarray[tuple[Any, ...], dtype[float64]], perturbed_transmission: ndarray[tuple[Any, ...], dtype[float64]] | None, constraint_names: tuple[str, ...], perturbation_delta: float)

Bases: object

Results of sensitivity analysis for a linkage.

Sensitivity measures how much the output path changes when each constraint dimension is perturbed by a small amount.

Attributes:
sensitivities: Mapping from constraint name to sensitivity coefficient.

Higher values indicate the output is more sensitive to that constraint.

baseline_path_metric: Mean path deviation at nominal constraints (should be 0). baseline_transmission: Mean transmission angle at nominal (degrees), or None. perturbed_path_metrics: Array of path deviation metrics, one per constraint. perturbed_transmission: Array of transmission angles per constraint, or None. constraint_names: Tuple of constraint names in order. perturbation_delta: Relative perturbation magnitude used (e.g., 0.01 for 1%).

baseline_path_metric: float
baseline_transmission: float | None
constraint_names: tuple[str, ...]
property most_sensitive: str

Return name of the most sensitive constraint.

perturbation_delta: float
perturbed_path_metrics: ndarray[tuple[Any, ...], dtype[float64]]
perturbed_transmission: ndarray[tuple[Any, ...], dtype[float64]] | None
sensitivities: dict[str, float]
property sensitivity_ranking: list[tuple[str, float]]

Return constraints ranked by sensitivity (highest first).

to_dataframe() object

Export results as pandas DataFrame.

Returns:

DataFrame with columns: constraint, sensitivity, perturbed_metric

Raises:

ImportError: If pandas is not installed.

class pylinkage.linkage.Simulation(linkage: Linkage, iterations: int | None = None, dt: float = 1)

Bases: object

Context manager for linkage simulation.

Provides a clean interface for iterating over linkage positions with automatic setup and teardown.

Example:
>>> with linkage.simulation(iterations=100) as sim:
...     for step, coords in sim:
...         # coords is a tuple of (x, y) for each joint
...         print(f"Step {step}: {coords}")
property iterations: int

Number of iterations for this simulation.

property linkage: Linkage

The linkage being simulated.

class pylinkage.linkage.StrokeAnalysis(min_position: float, max_position: float, mean_position: float, stroke_range: float, positions: ndarray[tuple[Any, ...], dtype[float64]], min_position_step: int, max_position_step: int)

Bases: object

Results of stroke analysis for a prismatic joint over a motion cycle.

The stroke is the position of the slider along its axis, measured as the signed distance from the first line point (joint1) projected onto the slide axis.

Attributes:

min_position: Minimum slide position along the axis. max_position: Maximum slide position along the axis. mean_position: Mean slide position. stroke_range: Total travel distance (max - min). positions: Array of slide positions at each step. min_position_step: Step index where minimum position occurs. max_position_step: Step index where maximum position occurs.

property amplitude: float

Return half the stroke range (useful for oscillating mechanisms).

property center_position: float

Return the center of the stroke range.

max_position: float
max_position_step: int
mean_position: float
min_position: float
min_position_step: int
positions: ndarray[tuple[Any, ...], dtype[float64]]
stroke_range: float
class pylinkage.linkage.ToleranceAnalysis(nominal_path: ndarray[tuple[Any, ...], dtype[float64]], output_cloud: ndarray[tuple[Any, ...], dtype[float64]], tolerances: dict[str, float], mean_deviation: float, max_deviation: float, std_deviation: float, position_std: ndarray[tuple[Any, ...], dtype[float64]])

Bases: object

Results of Monte Carlo tolerance analysis.

Tolerance analysis simulates how manufacturing variations in link dimensions affect the output path. Each sample represents a possible manufactured linkage within tolerance bounds.

Attributes:

nominal_path: Output path at nominal dimensions, shape (n_steps, 2). output_cloud: Monte Carlo results, shape (n_samples, n_steps, 2). tolerances: Dictionary mapping constraint names to their tolerances. mean_deviation: Mean path deviation from nominal across all samples. max_deviation: Maximum path deviation from nominal (worst case). std_deviation: Standard deviation of path deviations. position_std: Per-position standard deviation, shape (n_steps,).

max_deviation: float
mean_deviation: float
nominal_path: ndarray[tuple[Any, ...], dtype[float64]]
output_cloud: ndarray[tuple[Any, ...], dtype[float64]]
plot_cloud(ax: Axes | None = None, show_nominal: bool = True, alpha: float = 0.1) Axes

Plot the tolerance cloud as a scatter plot.

Args:

ax: Matplotlib axes to plot on. Creates new figure if None. show_nominal: Whether to show the nominal path as a solid line. alpha: Transparency for sample points (0-1).

Returns:

Matplotlib axes with the plot.

position_std: ndarray[tuple[Any, ...], dtype[float64]]
std_deviation: float
to_dataframe() object

Export statistics as pandas DataFrame.

Returns:

DataFrame with tolerance statistics.

Raises:

ImportError: If pandas is not installed.

tolerances: dict[str, float]
class pylinkage.linkage.TransmissionAngleAnalysis(min_angle: float, max_angle: float, mean_angle: float, angles: ndarray[tuple[Any, ...], dtype[float64]], is_acceptable: bool, min_deviation: float, max_deviation: float, min_angle_step: int, max_angle_step: int)

Bases: object

Results of transmission angle analysis over a motion cycle.

Attributes:

min_angle: Minimum transmission angle in degrees. max_angle: Maximum transmission angle in degrees. mean_angle: Mean transmission angle in degrees. angles: Array of transmission angles at each step (degrees). is_acceptable: True if angle always in acceptable range. min_deviation: Minimum deviation from 90 degrees. max_deviation: Maximum deviation from 90 degrees. min_angle_step: Step index where minimum angle occurs. max_angle_step: Step index where maximum angle occurs.

property acceptable_range: tuple[float, float]

Return the standard acceptable range [40, 140] degrees.

angles: ndarray[tuple[Any, ...], dtype[float64]]
is_acceptable: bool
max_angle: float
max_angle_step: int
max_deviation: float
mean_angle: float
min_angle: float
min_angle_step: int
min_deviation: float
worst_angle() float

Return the angle with maximum deviation from 90 degrees.

pylinkage.linkage.analyze_sensitivity(linkage: Linkage, output_joint: object | int | None = None, delta: float = 0.01, include_transmission: bool = True, iterations: int | None = None) SensitivityAnalysis

Analyze sensitivity of output path to each constraint dimension.

For each constraint, this function: 1. Perturbs the constraint by delta (relative) 2. Simulates the linkage 3. Measures the path deviation from nominal 4. Computes sensitivity coefficient

Args:

linkage: The linkage to analyze. output_joint: Joint to measure, index, or None (auto-detect last joint). delta: Relative perturbation magnitude (e.g., 0.01 for 1%). include_transmission: Whether to also measure transmission angle sensitivity. iterations: Number of simulation steps. Defaults to one full rotation.

Returns:

SensitivityAnalysis with sensitivity coefficients and statistics.

Example:
>>> analysis = linkage.sensitivity_analysis(delta=0.01)
>>> print(analysis.most_sensitive)
>>> for name, sens in analysis.sensitivity_ranking:
...     print(f"{name}: {sens:.4f}")
pylinkage.linkage.analyze_stroke(linkage: Linkage, prismatic_joint: object | None = None, iterations: int | None = None) StrokeAnalysis

Analyze stroke/slide position over a full motion cycle.

For a linkage with a Prismatic joint, this tracks the slide position along the prismatic axis throughout the motion cycle.

Args:

linkage: The Linkage to analyze. prismatic_joint: The Prismatic joint to analyze. Auto-detected if None. iterations: Number of simulation steps. Defaults to one full rotation.

Returns:

StrokeAnalysis with statistics over the motion cycle.

Raises:

ValueError: If joint cannot be detected or no valid positions found.

pylinkage.linkage.analyze_tolerance(linkage: Linkage, tolerances: dict[str, float], output_joint: object | int | None = None, iterations: int | None = None, n_samples: int = 1000, seed: int | None = None) ToleranceAnalysis

Analyze manufacturing tolerance effects via Monte Carlo simulation.

For each sample: 1. Randomly perturb constraints within tolerance bounds 2. Simulate the linkage 3. Record the output path

Args:

linkage: The linkage to analyze. tolerances: Dictionary mapping constraint names to tolerance values.

Each constraint is perturbed by +/- tolerance uniformly.

output_joint: Joint to measure, index, or None (auto-detect last joint). iterations: Number of simulation steps. Defaults to one full rotation. n_samples: Number of Monte Carlo samples to run. seed: Random seed for reproducibility.

Returns:

ToleranceAnalysis with statistics and the sample cloud.

Example:
>>> tolerances = {
...     "Crank_radius": 0.1,    # +/- 0.1 mm
...     "Revolute_dist1": 0.2,  # +/- 0.2 mm
... }
>>> analysis = linkage.tolerance_analysis(tolerances, n_samples=500)
>>> print(f"Max deviation: {analysis.max_deviation:.4f}")
>>> analysis.plot_cloud()
pylinkage.linkage.analyze_transmission(linkage: Linkage, iterations: int | None = None, acceptable_range: tuple[float, float] = (40.0, 140.0)) TransmissionAngleAnalysis

Analyze transmission angle over a full motion cycle.

For a standard four-bar linkage, joints are auto-detected: - coupler_joint: The Crank joint (B) - output_joint: The Revolute joint (C) - rocker_pivot: The Static joint at Revolute.joint1 (D)

Args:

linkage: The Linkage to analyze. iterations: Number of simulation steps. Defaults to one full rotation. acceptable_range: (min, max) acceptable angles in degrees.

Returns:

TransmissionAngleAnalysis with statistics over the motion cycle.

Raises:

ValueError: If joints cannot be detected or no valid positions found.

pylinkage.linkage.bounding_box(locus: Iterable[tuple[float, float]]) tuple[float, float, float, float]

Compute the bounding box of a locus.

Parameters:

locus – A list of points or any iterable with the same structure.

Returns:

Bounding box as (y_min, x_max, y_max, x_min).

pylinkage.linkage.compute_slide_position(slider_pos: tuple[float, float], line_point1: tuple[float, float], line_point2: tuple[float, float]) float

Compute the slide position along a prismatic axis.

The position is the signed distance from line_point1 to the projection of slider_pos onto the line, measured in the direction of line_point2.

Args:

slider_pos: Current position of the slider joint. line_point1: First point defining the slide axis (origin). line_point2: Second point defining the slide axis (direction).

Returns:

Signed distance along the axis from line_point1.

pylinkage.linkage.compute_transmission_angle(coupler_joint: tuple[float, float], output_joint: tuple[float, float], rocker_pivot: tuple[float, float]) float

Compute transmission angle in degrees.

The transmission angle is the angle between: - Coupler link: from coupler_joint (B) to output_joint (C) - Rocker link: from rocker_pivot (D) to output_joint (C)

Args:

coupler_joint: Position of the coupler input joint (B). output_joint: Position where angle is measured (C). rocker_pivot: Position of the rocker ground pivot (D).

Returns:

Transmission angle in degrees (0-180).

pylinkage.linkage.kinematic_default_test(func: Callable[[...], float], error_penalty: float) Callable[[Linkage, Iterable[float], JointPositions | None], float]

Standard run for any linkage before a complete fitness evaluation.

This decorator makes a kinematic simulation, before passing the loci to the decorated function.

Parameters:
  • func – Fitness function to be decorated.

  • error_penalty – Penalty value for unbuildable linkage. Common values include float(‘inf’) and 0.