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[Joint], order: Iterable[Joint] | None = None, name: str | None = None)
Bases:
objectA linkage is a set of Joint objects.
It is defined as a kinematic linkage. Coordinates are given relative to its own base.
- 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_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.
- 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).
- 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).
- 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_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)
- 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")
- class pylinkage.linkage.linkage.Simulation(linkage: Linkage, iterations: int | None = None, dt: float = 1)
Bases:
objectContext 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.
Module contents
Definition and analysis of a linkage as a dynamic set of joints.
- class pylinkage.linkage.Linkage(joints: Iterable[Joint], order: Iterable[Joint] | None = None, name: str | None = None)
Bases:
objectA linkage is a set of Joint objects.
It is defined as a kinematic linkage. Coordinates are given relative to its own base.
- 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_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.
- 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).
- 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).
- 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_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)
- 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")
- class pylinkage.linkage.Simulation(linkage: Linkage, iterations: int | None = None, dt: float = 1)
Bases:
objectContext 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.
- 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.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.