pylinkage.simulation package
Submodules
pylinkage.simulation.linkage module
Linkage container for simulating planar mechanisms.
The Linkage class orchestrates a collection of components (Ground, actuators, dyads) to simulate a planar mechanism.
- class pylinkage.simulation.linkage.Linkage(components: Iterable[Component], order: Iterable[Component] | None = None, name: str | None = None)
Bases:
objectA planar linkage mechanism built from components.
The Linkage class orchestrates a collection of components (Ground points, actuators, and dyads) to simulate a planar mechanism. It handles solve order computation, stepping, and constraint management.
Example
>>> from pylinkage.components import Ground >>> from pylinkage.actuators import Crank >>> from pylinkage.dyads import RRRDyad >>> from pylinkage.simulation import Linkage >>> >>> O1 = Ground(0.0, 0.0, name="O1") >>> O2 = Ground(2.0, 0.0, name="O2") >>> crank = Crank(anchor=O1, radius=1.0, angular_velocity=0.1) >>> rocker = RRRDyad(crank.output, O2, distance1=2.0, distance2=1.5) >>> linkage = Linkage([O1, O2, crank, rocker], name="Four-Bar") >>> for positions in linkage.step(): ... print(positions)
- analyze_sensitivity(output_joint: object | int | None = None, delta: float = 0.01, include_transmission: bool = True, iterations: int | None = None) SensitivityAnalysis
Compute sensitivity of an output path to constraint perturbations.
See
pylinkage.linkage.analyze_sensitivity().
- analyze_stroke(prismatic_joint: object | None = None, iterations: int | None = None) StrokeAnalysis
Analyze stroke/slide position over a full motion cycle.
See
pylinkage.linkage.analyze_stroke().
- analyze_tolerance(tolerances: dict[str, float], output_joint: object | int | None = None, iterations: int | None = None, n_samples: int = 1000, seed: int | None = None) ToleranceAnalysis
Monte-Carlo tolerance analysis over the output path.
See
pylinkage.linkage.analyze_tolerance().
- analyze_transmission(iterations: int | None = None, acceptable_range: tuple[float, float] = (40.0, 140.0)) TransmissionAngleAnalysis
Analyze transmission angle over a full motion cycle.
See
pylinkage.linkage.analyze_transmission()for details.
- compile() None
Pre-compile the numba solver state for
step_fast().Cached on
self._solver_dataand reused until invalidated by a call tocompile()again.
- components: tuple[Component, ...]
- property dyads: tuple[Component, ...]
Return components (backwards compatibility alias).
- get_accelerations() list[tuple[float, float] | None]
Return accelerations for all components.
- Returns:
List of (ax, ay) tuples, one per component. Returns None for components whose acceleration has not been computed.
- get_constraints() list[float]
Return all geometric constraints as a flat list.
- Returns:
Flat list of all constraint values, used by optimizers.
- get_coords() list[tuple[float | None, float | None]]
Return positions of all components.
- Returns:
List of (x, y) positions.
- get_rotation_period() int
Return number of steps for one full cycle.
Computes the LCM of all actuator periods (cranks, arc cranks, and linear actuators). For cranks, period is 2*pi / angular_velocity. For arc cranks, period is 2 * (arc_end - arc_start) / angular_velocity. For linear actuators, period is 2 * stroke / velocity.
- Returns:
Number of iterations with dt=1.
- get_velocities() list[tuple[float, float] | None]
Return velocities for all components.
- Returns:
List of (vx, vy) tuples, one per component. Returns None for components whose velocity has not been computed.
- indeterminacy() int
Mobility (DOF) of the linkage — planar Gruebler-Kutzbach.
DOF = 3·(n − 1) − 2·R − Pwhere each non-ground component contributes its share of bodies and kinematic pairs:Groundanchors are points on the frame (no new body, no new pair on their own);Crank/LinearActuatoradd 1 body + 1 R/P pair;binary dyads (
RRRDyad,FixedDyad) add 2 bodies + 3 R-pairs;RRPDyadadds 2 bodies + 2 R-pairs + 1 P-pair.
A standard Grashof four-bar (Crank + RRRDyad) returns
1.
- name: str
- rebuild(positions: list[tuple[float, float]] | None = None) None
Rebuild the linkage, optionally setting initial positions.
- Parameters:
positions – Initial positions for each component. If None, uses current positions.
- set_completely(constraints: list[float], positions: list[tuple[float, float]]) None
Apply both constraints and initial positions in one call.
- Parameters:
constraints – Flat list (as accepted by
set_constraints()).positions – Per-component
(x, y)positions (as accepted byset_coords()).
- set_constraints(values: list[float]) None
Set constraints from a flat list.
Used to apply optimization results. Invalidates any cached SolverData so the next
step_fast()recompiles.- Parameters:
values – Flat list of constraint values.
- set_coords(coords: list[tuple[float, float]]) None
Set positions for all components.
- Parameters:
coords – List of (x, y) positions.
- set_input_velocity(actuator: Crank, omega: float, alpha: float = 0.0) None
Set angular velocity and acceleration for a crank actuator.
This is used for kinematics computation (velocity/acceleration analysis). The omega value will be used to compute linear velocities at each joint.
- Parameters:
actuator – The crank actuator to set velocity for.
omega – Angular velocity in rad/s (physical units for analysis).
alpha – Angular acceleration in rad/s² (default 0).
- Raises:
ValueError – If the actuator is not part of this linkage.
Example
>>> linkage.set_input_velocity(crank, omega=10.0) # 10 rad/s >>> for pos, vel, acc in linkage.step_with_derivatives(): ... print(f"Position: {pos}, Velocity: {vel}")
- simulation(iterations: int | None = None, dt: float = 1.0) _SimulationContext
Return a context manager that simulates this linkage.
The context restores the initial joint positions on exit, so repeated invocations return to the same starting state.
- step(iterations: int | None = None, dt: float = 1) Generator[tuple[tuple[float | None, float | None], ...], None, None]
Simulate the linkage.
Yields positions for all components at each step.
- Parameters:
iterations – Number of steps. If None, uses get_rotation_period().
dt – Time step multiplier for actuators (cranks and linear actuators).
- Yields:
Tuple of (x, y) positions for each component.
- step_fast(iterations: int | None = None, dt: float = 1) ndarray[tuple[Any, ...], dtype[float64]]
Run the simulation through the numba-compiled solver.
Significantly faster than
step()for large iteration counts because it avoids per-step Python dispatch.- Parameters:
iterations – Number of steps. Defaults to
get_rotation_period().dt – Time step multiplier (default 1.0).
- Returns:
Trajectory array of shape
(iterations, n_components, 2). Unbuildable configurations appear as NaN — check withnp.isnan(trajectory).any().
- step_fast_with_kinematics(iterations: int | None = None, dt: float = 1.0) tuple[ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[float64]]]
Run the numba-compiled simulation, returning velocities and accelerations.
Per-crank
omega/alphainputs must be set viaset_input_velocity()(cranks without an explicit input default to zero).- Parameters:
iterations – Number of steps. Defaults to
get_rotation_period().dt – Time step multiplier (default 1.0).
- Returns:
(positions, velocities, accelerations)— each a numpy array of shape(iterations, n_components, 2).
- step_with_derivatives(iterations: int | None = None, dt: float = 1) Generator[tuple[tuple[tuple[float | None, float | None], ...], tuple[tuple[float, float] | None, ...], tuple[tuple[float, float] | None, ...]], None, None]
Simulate the linkage with velocity and acceleration computation.
Yields positions, velocities, and accelerations for all components at each step. Requires that omega (and optionally alpha) is set on crank actuators via set_input_velocity().
- Parameters:
iterations – Number of steps. If None, uses get_rotation_period().
dt – Time step multiplier for actuators (cranks and linear actuators).
- Yields:
Tuple of (positions, velocities, accelerations) where –
positions: Tuple of (x, y) for each component
velocities: Tuple of (vx, vy) or None for each component
accelerations: Tuple of (ax, ay) or None for each component
Example
>>> linkage.set_input_velocity(crank, omega=10.0) >>> for pos, vel, acc in linkage.step_with_derivatives(): ... print(f"Crank velocity: {vel[2]}")
- stroke_position() float
Slide position of a prismatic joint at the current pose.
See
pylinkage.linkage.stroke_at_position().
- to_hypergraph() tuple[HypergraphLinkage, Dimensions]
Return a hypergraph view of this linkage.
Delegates to
pylinkage.hypergraph.from_sim_linkage(). The return is a tuple(HypergraphLinkage, Dimensions).
- transmission_angle() float
Transmission angle at the current pose, in degrees.
See
pylinkage.linkage.transmission_angle_at_position().
Module contents
Simulation - containers for running mechanism simulations.
This module provides container classes for orchestrating mechanism simulations:
- Classes:
Linkage: Container that manages components and runs step-by-step simulation
Example
Build and simulate a four-bar linkage:
from pylinkage.components import Ground
from pylinkage.actuators import Crank
from pylinkage.dyads import RRRDyad
from pylinkage.simulation import Linkage
# Ground points
O1 = Ground(0.0, 0.0, name="O1")
O2 = Ground(2.0, 0.0, name="O2")
# Crank (driver)
crank = Crank(anchor=O1, radius=1.0, angular_velocity=0.1)
# Rocker (RRR dyad)
rocker = RRRDyad(
anchor1=crank.output,
anchor2=O2,
distance1=2.0,
distance2=1.5,
)
# Build and simulate
linkage = Linkage([O1, O2, crank, rocker], name="Four-Bar")
for positions in linkage.step():
print(positions)
- class pylinkage.simulation.Linkage(components: Iterable[Component], order: Iterable[Component] | None = None, name: str | None = None)
Bases:
objectA planar linkage mechanism built from components.
The Linkage class orchestrates a collection of components (Ground points, actuators, and dyads) to simulate a planar mechanism. It handles solve order computation, stepping, and constraint management.
Example
>>> from pylinkage.components import Ground >>> from pylinkage.actuators import Crank >>> from pylinkage.dyads import RRRDyad >>> from pylinkage.simulation import Linkage >>> >>> O1 = Ground(0.0, 0.0, name="O1") >>> O2 = Ground(2.0, 0.0, name="O2") >>> crank = Crank(anchor=O1, radius=1.0, angular_velocity=0.1) >>> rocker = RRRDyad(crank.output, O2, distance1=2.0, distance2=1.5) >>> linkage = Linkage([O1, O2, crank, rocker], name="Four-Bar") >>> for positions in linkage.step(): ... print(positions)
- analyze_sensitivity(output_joint: object | int | None = None, delta: float = 0.01, include_transmission: bool = True, iterations: int | None = None) SensitivityAnalysis
Compute sensitivity of an output path to constraint perturbations.
See
pylinkage.linkage.analyze_sensitivity().
- analyze_stroke(prismatic_joint: object | None = None, iterations: int | None = None) StrokeAnalysis
Analyze stroke/slide position over a full motion cycle.
See
pylinkage.linkage.analyze_stroke().
- analyze_tolerance(tolerances: dict[str, float], output_joint: object | int | None = None, iterations: int | None = None, n_samples: int = 1000, seed: int | None = None) ToleranceAnalysis
Monte-Carlo tolerance analysis over the output path.
See
pylinkage.linkage.analyze_tolerance().
- analyze_transmission(iterations: int | None = None, acceptable_range: tuple[float, float] = (40.0, 140.0)) TransmissionAngleAnalysis
Analyze transmission angle over a full motion cycle.
See
pylinkage.linkage.analyze_transmission()for details.
- compile() None
Pre-compile the numba solver state for
step_fast().Cached on
self._solver_dataand reused until invalidated by a call tocompile()again.
- components: tuple[Component, ...]
- property dyads: tuple[Component, ...]
Return components (backwards compatibility alias).
- get_accelerations() list[tuple[float, float] | None]
Return accelerations for all components.
- Returns:
List of (ax, ay) tuples, one per component. Returns None for components whose acceleration has not been computed.
- get_constraints() list[float]
Return all geometric constraints as a flat list.
- Returns:
Flat list of all constraint values, used by optimizers.
- get_coords() list[tuple[float | None, float | None]]
Return positions of all components.
- Returns:
List of (x, y) positions.
- get_rotation_period() int
Return number of steps for one full cycle.
Computes the LCM of all actuator periods (cranks, arc cranks, and linear actuators). For cranks, period is 2*pi / angular_velocity. For arc cranks, period is 2 * (arc_end - arc_start) / angular_velocity. For linear actuators, period is 2 * stroke / velocity.
- Returns:
Number of iterations with dt=1.
- get_velocities() list[tuple[float, float] | None]
Return velocities for all components.
- Returns:
List of (vx, vy) tuples, one per component. Returns None for components whose velocity has not been computed.
- indeterminacy() int
Mobility (DOF) of the linkage — planar Gruebler-Kutzbach.
DOF = 3·(n − 1) − 2·R − Pwhere each non-ground component contributes its share of bodies and kinematic pairs:Groundanchors are points on the frame (no new body, no new pair on their own);Crank/LinearActuatoradd 1 body + 1 R/P pair;binary dyads (
RRRDyad,FixedDyad) add 2 bodies + 3 R-pairs;RRPDyadadds 2 bodies + 2 R-pairs + 1 P-pair.
A standard Grashof four-bar (Crank + RRRDyad) returns
1.
- name: str
- rebuild(positions: list[tuple[float, float]] | None = None) None
Rebuild the linkage, optionally setting initial positions.
- Parameters:
positions – Initial positions for each component. If None, uses current positions.
- set_completely(constraints: list[float], positions: list[tuple[float, float]]) None
Apply both constraints and initial positions in one call.
- Parameters:
constraints – Flat list (as accepted by
set_constraints()).positions – Per-component
(x, y)positions (as accepted byset_coords()).
- set_constraints(values: list[float]) None
Set constraints from a flat list.
Used to apply optimization results. Invalidates any cached SolverData so the next
step_fast()recompiles.- Parameters:
values – Flat list of constraint values.
- set_coords(coords: list[tuple[float, float]]) None
Set positions for all components.
- Parameters:
coords – List of (x, y) positions.
- set_input_velocity(actuator: Crank, omega: float, alpha: float = 0.0) None
Set angular velocity and acceleration for a crank actuator.
This is used for kinematics computation (velocity/acceleration analysis). The omega value will be used to compute linear velocities at each joint.
- Parameters:
actuator – The crank actuator to set velocity for.
omega – Angular velocity in rad/s (physical units for analysis).
alpha – Angular acceleration in rad/s² (default 0).
- Raises:
ValueError – If the actuator is not part of this linkage.
Example
>>> linkage.set_input_velocity(crank, omega=10.0) # 10 rad/s >>> for pos, vel, acc in linkage.step_with_derivatives(): ... print(f"Position: {pos}, Velocity: {vel}")
- simulation(iterations: int | None = None, dt: float = 1.0) _SimulationContext
Return a context manager that simulates this linkage.
The context restores the initial joint positions on exit, so repeated invocations return to the same starting state.
- step(iterations: int | None = None, dt: float = 1) Generator[tuple[tuple[float | None, float | None], ...], None, None]
Simulate the linkage.
Yields positions for all components at each step.
- Parameters:
iterations – Number of steps. If None, uses get_rotation_period().
dt – Time step multiplier for actuators (cranks and linear actuators).
- Yields:
Tuple of (x, y) positions for each component.
- step_fast(iterations: int | None = None, dt: float = 1) ndarray[tuple[Any, ...], dtype[float64]]
Run the simulation through the numba-compiled solver.
Significantly faster than
step()for large iteration counts because it avoids per-step Python dispatch.- Parameters:
iterations – Number of steps. Defaults to
get_rotation_period().dt – Time step multiplier (default 1.0).
- Returns:
Trajectory array of shape
(iterations, n_components, 2). Unbuildable configurations appear as NaN — check withnp.isnan(trajectory).any().
- step_fast_with_kinematics(iterations: int | None = None, dt: float = 1.0) tuple[ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[float64]]]
Run the numba-compiled simulation, returning velocities and accelerations.
Per-crank
omega/alphainputs must be set viaset_input_velocity()(cranks without an explicit input default to zero).- Parameters:
iterations – Number of steps. Defaults to
get_rotation_period().dt – Time step multiplier (default 1.0).
- Returns:
(positions, velocities, accelerations)— each a numpy array of shape(iterations, n_components, 2).
- step_with_derivatives(iterations: int | None = None, dt: float = 1) Generator[tuple[tuple[tuple[float | None, float | None], ...], tuple[tuple[float, float] | None, ...], tuple[tuple[float, float] | None, ...]], None, None]
Simulate the linkage with velocity and acceleration computation.
Yields positions, velocities, and accelerations for all components at each step. Requires that omega (and optionally alpha) is set on crank actuators via set_input_velocity().
- Parameters:
iterations – Number of steps. If None, uses get_rotation_period().
dt – Time step multiplier for actuators (cranks and linear actuators).
- Yields:
Tuple of (positions, velocities, accelerations) where –
positions: Tuple of (x, y) for each component
velocities: Tuple of (vx, vy) or None for each component
accelerations: Tuple of (ax, ay) or None for each component
Example
>>> linkage.set_input_velocity(crank, omega=10.0) >>> for pos, vel, acc in linkage.step_with_derivatives(): ... print(f"Crank velocity: {vel[2]}")
- stroke_position() float
Slide position of a prismatic joint at the current pose.
See
pylinkage.linkage.stroke_at_position().
- to_hypergraph() tuple[HypergraphLinkage, Dimensions]
Return a hypergraph view of this linkage.
Delegates to
pylinkage.hypergraph.from_sim_linkage(). The return is a tuple(HypergraphLinkage, Dimensions).
- transmission_angle() float
Transmission angle at the current pose, in degrees.
See
pylinkage.linkage.transmission_angle_at_position().