pylinkage.dyads package
Submodules
pylinkage.dyads.factory module
Factory function for creating dyads from isomer signatures.
This module provides a unified interface for creating any of the 12 dyadic isomers using a signature string.
- Notation:
R = Revolute joint (pin) T = Translating element (slider block) _ = Guide (rail/slot that slider moves along)
The 12 isomers map to 3 geometry types:
RRR: circle-circle intersectionCircle-line isomers (
RR_T,RRT_,RT_R,R_T_T,RT_T_,R_TT_,RT__T): RRPDyadLine-line isomers (
T_R_T,T_RT_,_TRT_): PPDyad
- pylinkage.dyads.factory.create_dyad(signature: str, anchors: dict[str, Component | _AnchorProxy], constraints: dict[str, float] | None = None, x: float | None = None, y: float | None = None, name: str | None = None) RRRDyad | RRPDyad | PPDyad
Create a dyad from an isomer signature.
This factory function provides a unified interface for creating any of the 12 dyadic isomers. It determines the appropriate geometry type from the signature and creates the corresponding dyad class.
- Parameters:
signature – Isomer signature (e.g., “RRR”, “RT_R”, “T_R_T”).
anchors – Dict mapping anchor roles to components. Required keys depend on geometry type: - circle_circle (RRR): “anchor1”, “anchor2” - circle_line (RRP variants): “revolute”, “line1”, “line2” - line_line (PP variants): “line1_p1”, “line1_p2”, “line2_p1”, “line2_p2”
constraints – Dict of constraints. Required keys depend on geometry type: - circle_circle (RRR): “distance1”, “distance2” - circle_line (RRP variants): “distance” - line_line (PP variants): none required
x – Initial x position hint (optional).
y – Initial y position hint (optional).
name – Human-readable identifier.
- Returns:
The appropriate dyad instance (RRRDyad, RRPDyad, or PPDyad).
- Raises:
ValueError – If signature is unknown or required anchors/constraints are missing.
Examples
>>> # Circle-circle (RRR) >>> dyad = create_dyad( ... signature="RRR", ... anchors={"anchor1": crank.output, "anchor2": ground2}, ... constraints={"distance1": 2.0, "distance2": 1.5}, ... name="rocker", ... )
>>> # Circle-line (RT_R - slider crank) >>> dyad = create_dyad( ... signature="RT_R", ... anchors={"revolute": crank.output, "line1": L1, "line2": L2}, ... constraints={"distance": 1.5}, ... name="slider", ... )
>>> # Line-line (T_R_T - double slider) >>> dyad = create_dyad( ... signature="T_R_T", ... anchors={ ... "line1_p1": A, "line1_p2": B, ... "line2_p1": C, "line2_p2": D, ... }, ... name="double_slider", ... )
- pylinkage.dyads.factory.get_isomer_geometry(signature: str) str
Get the geometry type for an isomer signature.
- Parameters:
signature – The isomer signature.
- Returns:
“circle_circle”, “circle_line”, or “line_line”.
- Return type:
Geometry type
- Raises:
ValueError – If signature is unknown.
- pylinkage.dyads.factory.get_required_anchors(signature: str) list[str]
Get the required anchor names for an isomer signature.
- Parameters:
signature – The isomer signature.
- Returns:
List of required anchor keys.
- Raises:
ValueError – If signature is unknown.
- pylinkage.dyads.factory.get_required_constraints(signature: str) list[str]
Get the required constraint names for an isomer signature.
- Parameters:
signature – The isomer signature.
- Returns:
List of required constraint keys.
- Raises:
ValueError – If signature is unknown.
pylinkage.dyads.fixed module
FixedDyad - deterministic polar constraint.
A fixed dyad positions a joint at a fixed distance and angle relative to two anchor joints. Unlike RRR dyad, the position is deterministic (no ambiguity).
- class pylinkage.dyads.fixed.FixedDyad(anchor1: Component | _AnchorProxy, anchor2: Component | _AnchorProxy, distance: float, angle: float, name: str | None = None)
Bases:
BinaryDyadFixed Dyad - deterministic polar projection.
Positions a joint at a fixed distance and angle from anchor1, with the angle measured relative to the line from anchor1 to anchor2.
Unlike RRRDyad which has two possible solutions, FixedDyad always has exactly one deterministic solution.
- Variables:
anchor1 – First anchor (origin for polar coordinates).
anchor2 – Second anchor (defines reference direction).
distance (float) – Distance from anchor1 to this joint.
angle (float) – Angle offset from anchor1->anchor2 direction (radians).
Example
>>> O1 = Ground(0.0, 0.0, name="O1") >>> O2 = Ground(2.0, 0.0, name="O2") >>> crank = Crank(anchor=O1, radius=1.0) >>> # Create a point at 90 degrees from crank->O2 line >>> fixed = FixedDyad( ... anchor1=crank.output, ... anchor2=O2, ... distance=1.0, ... angle=math.pi/2, ... name="coupler_point" ... )
- angle: float
- distance: float
- get_constraints() tuple[float, float]
Return the distance and angle constraints.
- Returns:
Tuple of (distance, angle).
- reload(dt: float = 1) None
Recompute position using polar projection.
The position is always deterministic - no ambiguity.
- Parameters:
dt – Time step (unused for Fixed, but required for interface).
- set_constraints(distance: float | None = None, angle: float | None = None, *args: float | None) None
Set the distance and angle constraints.
- Parameters:
distance – New distance from anchor1.
angle – New angle offset (radians).
*args – Ignored (for interface compatibility).
pylinkage.dyads.oscillating_cam module
Oscillating cam follower - rocker arm driven by cam profile.
An oscillating cam follower pivots about a fixed point based on the cam’s rotation angle. The cam profile determines the follower’s angular displacement as a function of the cam angle.
- class pylinkage.dyads.oscillating_cam.OscillatingCamFollower(cam_driver: Crank, profile: CamProfile, pivot_anchor: Ground, arm_length: float, initial_angle: float = 0.0, roller_radius: float = 0.0, name: str | None = None)
Bases:
ConnectedComponentOscillating cam follower - rocker arm driven by cam profile.
The follower pivots about a fixed point (pivot_anchor). The cam profile maps cam angle to follower arm angle offset. The output is at a fixed distance from the pivot along the arm direction.
For oscillating followers, the profile’s “displacement” is interpreted as an angular displacement (in radians) added to the initial_angle.
Knife-edge vs roller is controlled by the roller_radius parameter.
- Variables:
cam_driver (Crank) – Crank actuator driving the cam rotation.
profile (CamProfile) – Cam profile defining output angle offset vs input angle.
pivot_anchor (Ground) – Fixed pivot point for the rocker arm.
arm_length (float) – Distance from pivot to follower output.
initial_angle (float) – Starting angle of the arm (radians from +x).
roller_radius (float) – Radius of roller (0 for knife-edge).
Example
>>> from pylinkage.components import Ground >>> from pylinkage.actuators import Crank >>> from pylinkage.cam import FunctionProfile, CycloidalMotionLaw >>> import math >>> >>> O_cam = Ground(0.0, 0.0, name="cam_center") >>> O_pivot = Ground(2.0, 0.0, name="pivot") >>> cam = Crank(anchor=O_cam, radius=0.1, angular_velocity=0.1) >>> # Profile: maps cam angle to arm angle offset (radians) >>> profile = FunctionProfile( ... motion_law=CycloidalMotionLaw(), ... base_radius=0.0, # No offset at base ... total_lift=math.pi/4, # 45 degree swing ... ) >>> follower = OscillatingCamFollower( ... cam_driver=cam, ... profile=profile, ... pivot_anchor=O_pivot, ... arm_length=1.5, ... initial_angle=math.pi/2, # Arm starts vertical ... roller_radius=0.1, ... )
- property anchors: tuple[Component, ...]
Return parent components (cam driver and pivot).
- property angular_displacement: float
Return the current angular displacement from initial angle.
- property arm_angle: float
Return the current arm angle in radians.
- arm_length: float
- property cam_angle: float
Return the current cam angle in radians.
- get_constraints() tuple[float, ...]
Return optimizable constraints.
Returns arm_length, initial_angle, roller_radius, and profile constraints.
- initial_angle: float
- property output: _AnchorProxy
Return the output proxy for connecting other components.
- Returns:
An anchor proxy representing the follower output.
- profile: CamProfile
- reload(dt: float = 1) None
Recompute follower position from cam angle.
- Parameters:
dt – Time step (unused, cam angle from driver position).
- Raises:
ValueError – If cam driver or pivot has undefined position.
- roller_radius: float
- set_constraints(arm_length: float | None = None, initial_angle: float | None = None, roller_radius: float | None = None, *profile_constraints: float | None) None
Set constraints from optimization.
- Parameters:
arm_length – New arm length.
initial_angle – New initial angle.
roller_radius – New roller radius.
*profile_constraints – Constraints passed to profile.
pylinkage.dyads.pp module
PPDyad - line-line intersection.
A PP dyad consists of two prismatic constraints, positioning a joint
at the intersection of two lines. This covers isomers like T_R_T,
T_RT_ and _TRT_.
- class pylinkage.dyads.pp.PPDyad(line1_anchor1: Component | _AnchorProxy, line1_anchor2: Component | _AnchorProxy, line2_anchor1: Component | _AnchorProxy, line2_anchor2: Component | _AnchorProxy, x: float | None = None, y: float | None = None, name: str | None = None)
Bases:
ConnectedComponentPP Dyad - line-line intersection.
Positions a joint at the intersection of two lines: - Line 1: defined by line1_anchor1 and line1_anchor2 - Line 2: defined by line2_anchor1 and line2_anchor2
This dyad has no distance constraints - its position is fully determined by the four line-defining anchor points.
- Variables:
line1_anchor1 (Component | _AnchorProxy) – First point defining line 1.
line1_anchor2 (Component | _AnchorProxy) – Second point defining line 1.
line2_anchor1 (Component | _AnchorProxy) – First point defining line 2.
line2_anchor2 (Component | _AnchorProxy) – Second point defining line 2.
Example
>>> A = Ground(0.0, 0.0, name="A") >>> B = Ground(2.0, 0.0, name="B") >>> C = Ground(0.0, 1.0, name="C") >>> D = Ground(2.0, 2.0, name="D") >>> joint = PPDyad( ... line1_anchor1=A, ... line1_anchor2=B, ... line2_anchor1=C, ... line2_anchor2=D, ... name="intersection" ... )
- property anchors: tuple[Component, Component, Component, Component]
Return the parent dyads (four line anchors).
- get_constraints() tuple[()]
Return the constraints (none for PP dyad).
PP dyads have no distance constraints - position is fully determined by the four anchor points.
- Returns:
Empty tuple (no constraints).
- line1_anchor1: Component | _AnchorProxy
- line1_anchor2: Component | _AnchorProxy
- line2_anchor1: Component | _AnchorProxy
- line2_anchor2: Component | _AnchorProxy
- reload(dt: float = 1) None
Recompute position using line-line intersection.
- Parameters:
dt – Time step (unused for PP, but required for interface).
- Raises:
UnbuildableError – If lines are parallel (no intersection).
- set_constraints(*args: float | None) None
Set constraints (no-op for PP dyad).
PP dyads have no constraints to set.
- Parameters:
*args – Ignored (for interface compatibility).
pylinkage.dyads.rrp module
RRPDyad - circle-line intersection (slider mechanism).
An RRP dyad consists of a revolute connection to an anchor plus a prismatic (sliding) connection along a line.
- class pylinkage.dyads.rrp.RRPDyad(revolute_anchor: Component | _AnchorProxy, line_anchor1: Component | _AnchorProxy, line_anchor2: Component | _AnchorProxy, distance: float, x: float | None = None, y: float | None = None, name: str | None = None)
Bases:
ConnectedComponentRRP Dyad - circle-line intersection (slider mechanism).
Positions a joint at the intersection of: - A circle centered at the revolute anchor - A line defined by two line anchor points
The joint slides along the line while maintaining a fixed distance from the revolute anchor.
When two solutions exist, the nearest to current position is chosen (hysteresis for continuity during simulation).
- Variables:
revolute_anchor (Component | _AnchorProxy) – Joint connected by revolute pair.
line_anchor1 (Component | _AnchorProxy) – First joint defining the sliding line.
line_anchor2 (Component | _AnchorProxy) – Second joint defining the sliding line.
distance (float) – Distance from revolute_anchor to this joint.
Example
>>> O1 = Ground(0.0, 0.0, name="O1") >>> L1 = Ground(0.0, 1.0, name="L1") >>> L2 = Ground(2.0, 1.0, name="L2") >>> crank = Crank(anchor=O1, radius=1.0) >>> slider = RRPDyad( ... revolute_anchor=crank.output, ... line_anchor1=L1, ... line_anchor2=L2, ... distance=1.5, ... name="slider" ... )
- property anchors: tuple[Component, Component, Component]
Return the parent dyads (revolute anchor, line anchors).
- distance: float
- get_constraints() tuple[float]
Return the distance constraint.
- Returns:
Tuple containing the distance to revolute anchor.
- line_anchor1: Component | _AnchorProxy
- line_anchor2: Component | _AnchorProxy
- reload(dt: float = 1) None
Recompute position using circle-line intersection.
- Parameters:
dt – Time step (unused for RRP, but required for interface).
- Raises:
UnbuildableError – If circle doesn’t intersect line.
- revolute_anchor: Component | _AnchorProxy
- set_constraints(distance: float | None = None, *args: float | None) None
Set the distance constraint.
- Parameters:
distance – New distance to revolute anchor.
*args – Ignored (for interface compatibility).
pylinkage.dyads.rrr module
RRRDyad - circle-circle intersection (two revolute joints meeting at one).
The most common Assur group, consisting of two links connected by revolute joints, meeting at a computed internal revolute joint.
- class pylinkage.dyads.rrr.RRRDyad(anchor1: Component | _AnchorProxy, anchor2: Component | _AnchorProxy, distance1: float, distance2: float, x: float | None = None, y: float | None = None, name: str | None = None)
Bases:
BinaryDyadRRR Dyad - circle-circle intersection.
Positions a joint at the intersection of two circles centered at the anchor points. This is the most common Assur group.
When two solutions exist, the nearest to current position is chosen (hysteresis for continuity during simulation).
- Variables:
anchor1 – First connection point.
anchor2 – Second connection point.
distance1 (float) – Distance from anchor1 to this joint.
distance2 (float) – Distance from anchor2 to this joint.
Example
>>> O1 = Ground(0.0, 0.0, name="O1") >>> O2 = Ground(2.0, 0.0, name="O2") >>> crank = Crank(anchor=O1, radius=1.0) >>> rocker = RRRDyad( ... anchor1=crank.output, ... anchor2=O2, ... distance1=2.0, ... distance2=1.5, ... name="rocker" ... )
- distance1: float
- distance2: float
- get_constraints() tuple[float, float]
Return the two distance constraints.
- Returns:
Tuple of (distance1, distance2).
- reload(dt: float = 1) None
Recompute position using circle-circle intersection.
- Parameters:
dt – Time step (unused for RRR, but required for interface).
- Raises:
UnbuildableError – If the circles don’t intersect.
- set_constraints(distance1: float | None = None, distance2: float | None = None, *args: float | None) None
Set the distance constraints.
- Parameters:
distance1 – New distance to anchor1.
distance2 – New distance to anchor2.
*args – Ignored (for interface compatibility).
pylinkage.dyads.translating_cam module
Translating cam follower - linear motion driven by cam profile.
A translating cam follower moves along a fixed axis (guide) based on the cam’s rotation angle. The cam profile determines the follower’s displacement as a function of the cam angle.
- class pylinkage.dyads.translating_cam.TranslatingCamFollower(cam_driver: Crank, profile: CamProfile, guide: Ground, guide_angle: float = 0.0, roller_radius: float = 0.0, name: str | None = None)
Bases:
ConnectedComponentTranslating cam follower - linear motion driven by cam profile.
The follower moves along a fixed axis (defined by guide_angle) based on the cam’s rotation angle. The output position is determined by the cam profile evaluation.
- Knife-edge vs roller is controlled by the roller_radius parameter:
roller_radius=0: knife-edge follower (point contact)
roller_radius>0: roller follower (uses pitch curve)
- Variables:
cam_driver (Crank) – Crank actuator driving the cam rotation.
profile (CamProfile) – Cam profile defining displacement vs angle.
guide (Ground) – Ground point defining the guide axis origin.
guide_angle (float) – Angle of the guide axis (radians from +x).
roller_radius (float) – Radius of roller (0 for knife-edge).
Example
>>> from pylinkage.components import Ground >>> from pylinkage.actuators import Crank >>> from pylinkage.cam import FunctionProfile, HarmonicMotionLaw >>> import math >>> >>> O = Ground(0.0, 0.0, name="cam_center") >>> cam = Crank(anchor=O, radius=0.1, angular_velocity=0.1) >>> profile = FunctionProfile( ... motion_law=HarmonicMotionLaw(), ... base_radius=1.0, ... total_lift=0.5, ... ) >>> guide = Ground(0.0, 0.0, name="guide") >>> follower = TranslatingCamFollower( ... cam_driver=cam, ... profile=profile, ... guide=guide, ... guide_angle=math.pi/2, # Vertical motion ... roller_radius=0.1, ... )
- property anchors: tuple[Component, ...]
Return parent components (cam driver and guide).
- property cam_angle: float
Return the current cam angle in radians.
- property displacement: float
Return the current follower displacement from base position.
- get_constraints() tuple[float, ...]
Return optimizable constraints.
Returns roller_radius and profile constraints.
- guide_angle: float
- property output: _AnchorProxy
Return the output proxy for connecting other components.
- Returns:
An anchor proxy representing the follower output.
- profile: CamProfile
- reload(dt: float = 1) None
Recompute follower position from cam angle.
- Parameters:
dt – Time step (used to get cam angle from driver).
- Raises:
ValueError – If cam driver or guide has undefined position.
- roller_radius: float
- set_constraints(roller_radius: float | None = None, *profile_constraints: float | None) None
Set constraints from optimization.
- Parameters:
roller_radius – New roller radius.
*profile_constraints – Constraints passed to profile.
Module contents
Dyads - Assur group building blocks for planar linkages.
This module provides true Assur group dyads (0 DOF structural units):
- Classes:
RRRDyad: Circle-circle intersection (two links meeting at one joint) RRPDyad: Circle-line intersection (slider mechanism) PPDyad: Line-line intersection (double slider) FixedDyad: Deterministic polar projection BinaryDyad: Base class for binary Assur groups TranslatingCamFollower: Translating follower driven by cam profile OscillatingCamFollower: Oscillating (rocker) follower driven by cam profile
- Functions:
create_dyad: Factory function to create dyads from isomer signatures
- For other kinematic elements, use the appropriate modules:
pylinkage.components: Ground, base classes (Component, ConnectedComponent)
pylinkage.actuators: Crank, LinearActuator
pylinkage.cam: CamProfile, FunctionProfile, motion laws
pylinkage.simulation: Linkage
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)
- Deprecated re-exports:
This module used to re-export
Ground,PointTracker,Crank,ArcCrank,LinearActuator,Linkage,ComponentandConnectedComponentfrom their home modules. Those names still resolve here but warn; import them frompylinkage.components,pylinkage.actuatorsandpylinkage.simulation. See the Deprecations page.
- class pylinkage.dyads.BinaryDyad(x: float | None, y: float | None, name: str | None = None)
Bases:
ConnectedComponentBase class for dyads connecting exactly two parents.
Binary dyads have two anchor points and typically compute their position as an intersection (circle-circle, circle-line, etc.). These are the true Assur groups that add 0 degrees of freedom.
- anchor1: Component | _AnchorProxy
- anchor2: Component | _AnchorProxy
- property anchors: tuple[Component, Component]
Return the two parent dyads.
- class pylinkage.dyads.FixedDyad(anchor1: Component | _AnchorProxy, anchor2: Component | _AnchorProxy, distance: float, angle: float, name: str | None = None)
Bases:
BinaryDyadFixed Dyad - deterministic polar projection.
Positions a joint at a fixed distance and angle from anchor1, with the angle measured relative to the line from anchor1 to anchor2.
Unlike RRRDyad which has two possible solutions, FixedDyad always has exactly one deterministic solution.
- Variables:
anchor1 – First anchor (origin for polar coordinates).
anchor2 – Second anchor (defines reference direction).
distance (float) – Distance from anchor1 to this joint.
angle (float) – Angle offset from anchor1->anchor2 direction (radians).
Example
>>> O1 = Ground(0.0, 0.0, name="O1") >>> O2 = Ground(2.0, 0.0, name="O2") >>> crank = Crank(anchor=O1, radius=1.0) >>> # Create a point at 90 degrees from crank->O2 line >>> fixed = FixedDyad( ... anchor1=crank.output, ... anchor2=O2, ... distance=1.0, ... angle=math.pi/2, ... name="coupler_point" ... )
- angle: float
- distance: float
- get_constraints() tuple[float, float]
Return the distance and angle constraints.
- Returns:
Tuple of (distance, angle).
- reload(dt: float = 1) None
Recompute position using polar projection.
The position is always deterministic - no ambiguity.
- Parameters:
dt – Time step (unused for Fixed, but required for interface).
- set_constraints(distance: float | None = None, angle: float | None = None, *args: float | None) None
Set the distance and angle constraints.
- Parameters:
distance – New distance from anchor1.
angle – New angle offset (radians).
*args – Ignored (for interface compatibility).
- class pylinkage.dyads.OscillatingCamFollower(cam_driver: Crank, profile: CamProfile, pivot_anchor: Ground, arm_length: float, initial_angle: float = 0.0, roller_radius: float = 0.0, name: str | None = None)
Bases:
ConnectedComponentOscillating cam follower - rocker arm driven by cam profile.
The follower pivots about a fixed point (pivot_anchor). The cam profile maps cam angle to follower arm angle offset. The output is at a fixed distance from the pivot along the arm direction.
For oscillating followers, the profile’s “displacement” is interpreted as an angular displacement (in radians) added to the initial_angle.
Knife-edge vs roller is controlled by the roller_radius parameter.
- Variables:
cam_driver (Crank) – Crank actuator driving the cam rotation.
profile (CamProfile) – Cam profile defining output angle offset vs input angle.
pivot_anchor (Ground) – Fixed pivot point for the rocker arm.
arm_length (float) – Distance from pivot to follower output.
initial_angle (float) – Starting angle of the arm (radians from +x).
roller_radius (float) – Radius of roller (0 for knife-edge).
Example
>>> from pylinkage.components import Ground >>> from pylinkage.actuators import Crank >>> from pylinkage.cam import FunctionProfile, CycloidalMotionLaw >>> import math >>> >>> O_cam = Ground(0.0, 0.0, name="cam_center") >>> O_pivot = Ground(2.0, 0.0, name="pivot") >>> cam = Crank(anchor=O_cam, radius=0.1, angular_velocity=0.1) >>> # Profile: maps cam angle to arm angle offset (radians) >>> profile = FunctionProfile( ... motion_law=CycloidalMotionLaw(), ... base_radius=0.0, # No offset at base ... total_lift=math.pi/4, # 45 degree swing ... ) >>> follower = OscillatingCamFollower( ... cam_driver=cam, ... profile=profile, ... pivot_anchor=O_pivot, ... arm_length=1.5, ... initial_angle=math.pi/2, # Arm starts vertical ... roller_radius=0.1, ... )
- property anchors: tuple[Component, ...]
Return parent components (cam driver and pivot).
- property angular_displacement: float
Return the current angular displacement from initial angle.
- property arm_angle: float
Return the current arm angle in radians.
- arm_length: float
- property cam_angle: float
Return the current cam angle in radians.
- cam_driver: Crank
- get_constraints() tuple[float, ...]
Return optimizable constraints.
Returns arm_length, initial_angle, roller_radius, and profile constraints.
- initial_angle: float
- property output: _AnchorProxy
Return the output proxy for connecting other components.
- Returns:
An anchor proxy representing the follower output.
- pivot_anchor: Ground
- profile: CamProfile
- reload(dt: float = 1) None
Recompute follower position from cam angle.
- Parameters:
dt – Time step (unused, cam angle from driver position).
- Raises:
ValueError – If cam driver or pivot has undefined position.
- roller_radius: float
- set_constraints(arm_length: float | None = None, initial_angle: float | None = None, roller_radius: float | None = None, *profile_constraints: float | None) None
Set constraints from optimization.
- Parameters:
arm_length – New arm length.
initial_angle – New initial angle.
roller_radius – New roller radius.
*profile_constraints – Constraints passed to profile.
- class pylinkage.dyads.PPDyad(line1_anchor1: Component | _AnchorProxy, line1_anchor2: Component | _AnchorProxy, line2_anchor1: Component | _AnchorProxy, line2_anchor2: Component | _AnchorProxy, x: float | None = None, y: float | None = None, name: str | None = None)
Bases:
ConnectedComponentPP Dyad - line-line intersection.
Positions a joint at the intersection of two lines: - Line 1: defined by line1_anchor1 and line1_anchor2 - Line 2: defined by line2_anchor1 and line2_anchor2
This dyad has no distance constraints - its position is fully determined by the four line-defining anchor points.
- Variables:
line1_anchor1 (Component | _AnchorProxy) – First point defining line 1.
line1_anchor2 (Component | _AnchorProxy) – Second point defining line 1.
line2_anchor1 (Component | _AnchorProxy) – First point defining line 2.
line2_anchor2 (Component | _AnchorProxy) – Second point defining line 2.
Example
>>> A = Ground(0.0, 0.0, name="A") >>> B = Ground(2.0, 0.0, name="B") >>> C = Ground(0.0, 1.0, name="C") >>> D = Ground(2.0, 2.0, name="D") >>> joint = PPDyad( ... line1_anchor1=A, ... line1_anchor2=B, ... line2_anchor1=C, ... line2_anchor2=D, ... name="intersection" ... )
- property anchors: tuple[Component, Component, Component, Component]
Return the parent dyads (four line anchors).
- get_constraints() tuple[()]
Return the constraints (none for PP dyad).
PP dyads have no distance constraints - position is fully determined by the four anchor points.
- Returns:
Empty tuple (no constraints).
- line1_anchor1: Component | _AnchorProxy
- line1_anchor2: Component | _AnchorProxy
- line2_anchor1: Component | _AnchorProxy
- line2_anchor2: Component | _AnchorProxy
- reload(dt: float = 1) None
Recompute position using line-line intersection.
- Parameters:
dt – Time step (unused for PP, but required for interface).
- Raises:
UnbuildableError – If lines are parallel (no intersection).
- set_constraints(*args: float | None) None
Set constraints (no-op for PP dyad).
PP dyads have no constraints to set.
- Parameters:
*args – Ignored (for interface compatibility).
- class pylinkage.dyads.RRPDyad(revolute_anchor: Component | _AnchorProxy, line_anchor1: Component | _AnchorProxy, line_anchor2: Component | _AnchorProxy, distance: float, x: float | None = None, y: float | None = None, name: str | None = None)
Bases:
ConnectedComponentRRP Dyad - circle-line intersection (slider mechanism).
Positions a joint at the intersection of: - A circle centered at the revolute anchor - A line defined by two line anchor points
The joint slides along the line while maintaining a fixed distance from the revolute anchor.
When two solutions exist, the nearest to current position is chosen (hysteresis for continuity during simulation).
- Variables:
revolute_anchor (Component | _AnchorProxy) – Joint connected by revolute pair.
line_anchor1 (Component | _AnchorProxy) – First joint defining the sliding line.
line_anchor2 (Component | _AnchorProxy) – Second joint defining the sliding line.
distance (float) – Distance from revolute_anchor to this joint.
Example
>>> O1 = Ground(0.0, 0.0, name="O1") >>> L1 = Ground(0.0, 1.0, name="L1") >>> L2 = Ground(2.0, 1.0, name="L2") >>> crank = Crank(anchor=O1, radius=1.0) >>> slider = RRPDyad( ... revolute_anchor=crank.output, ... line_anchor1=L1, ... line_anchor2=L2, ... distance=1.5, ... name="slider" ... )
- property anchors: tuple[Component, Component, Component]
Return the parent dyads (revolute anchor, line anchors).
- distance: float
- get_constraints() tuple[float]
Return the distance constraint.
- Returns:
Tuple containing the distance to revolute anchor.
- line_anchor1: Component | _AnchorProxy
- line_anchor2: Component | _AnchorProxy
- reload(dt: float = 1) None
Recompute position using circle-line intersection.
- Parameters:
dt – Time step (unused for RRP, but required for interface).
- Raises:
UnbuildableError – If circle doesn’t intersect line.
- revolute_anchor: Component | _AnchorProxy
- set_constraints(distance: float | None = None, *args: float | None) None
Set the distance constraint.
- Parameters:
distance – New distance to revolute anchor.
*args – Ignored (for interface compatibility).
- class pylinkage.dyads.RRRDyad(anchor1: Component | _AnchorProxy, anchor2: Component | _AnchorProxy, distance1: float, distance2: float, x: float | None = None, y: float | None = None, name: str | None = None)
Bases:
BinaryDyadRRR Dyad - circle-circle intersection.
Positions a joint at the intersection of two circles centered at the anchor points. This is the most common Assur group.
When two solutions exist, the nearest to current position is chosen (hysteresis for continuity during simulation).
- Variables:
anchor1 – First connection point.
anchor2 – Second connection point.
distance1 (float) – Distance from anchor1 to this joint.
distance2 (float) – Distance from anchor2 to this joint.
Example
>>> O1 = Ground(0.0, 0.0, name="O1") >>> O2 = Ground(2.0, 0.0, name="O2") >>> crank = Crank(anchor=O1, radius=1.0) >>> rocker = RRRDyad( ... anchor1=crank.output, ... anchor2=O2, ... distance1=2.0, ... distance2=1.5, ... name="rocker" ... )
- distance1: float
- distance2: float
- get_constraints() tuple[float, float]
Return the two distance constraints.
- Returns:
Tuple of (distance1, distance2).
- reload(dt: float = 1) None
Recompute position using circle-circle intersection.
- Parameters:
dt – Time step (unused for RRR, but required for interface).
- Raises:
UnbuildableError – If the circles don’t intersect.
- set_constraints(distance1: float | None = None, distance2: float | None = None, *args: float | None) None
Set the distance constraints.
- Parameters:
distance1 – New distance to anchor1.
distance2 – New distance to anchor2.
*args – Ignored (for interface compatibility).
- class pylinkage.dyads.TranslatingCamFollower(cam_driver: Crank, profile: CamProfile, guide: Ground, guide_angle: float = 0.0, roller_radius: float = 0.0, name: str | None = None)
Bases:
ConnectedComponentTranslating cam follower - linear motion driven by cam profile.
The follower moves along a fixed axis (defined by guide_angle) based on the cam’s rotation angle. The output position is determined by the cam profile evaluation.
- Knife-edge vs roller is controlled by the roller_radius parameter:
roller_radius=0: knife-edge follower (point contact)
roller_radius>0: roller follower (uses pitch curve)
- Variables:
cam_driver (Crank) – Crank actuator driving the cam rotation.
profile (CamProfile) – Cam profile defining displacement vs angle.
guide (Ground) – Ground point defining the guide axis origin.
guide_angle (float) – Angle of the guide axis (radians from +x).
roller_radius (float) – Radius of roller (0 for knife-edge).
Example
>>> from pylinkage.components import Ground >>> from pylinkage.actuators import Crank >>> from pylinkage.cam import FunctionProfile, HarmonicMotionLaw >>> import math >>> >>> O = Ground(0.0, 0.0, name="cam_center") >>> cam = Crank(anchor=O, radius=0.1, angular_velocity=0.1) >>> profile = FunctionProfile( ... motion_law=HarmonicMotionLaw(), ... base_radius=1.0, ... total_lift=0.5, ... ) >>> guide = Ground(0.0, 0.0, name="guide") >>> follower = TranslatingCamFollower( ... cam_driver=cam, ... profile=profile, ... guide=guide, ... guide_angle=math.pi/2, # Vertical motion ... roller_radius=0.1, ... )
- property anchors: tuple[Component, ...]
Return parent components (cam driver and guide).
- property cam_angle: float
Return the current cam angle in radians.
- cam_driver: Crank
- property displacement: float
Return the current follower displacement from base position.
- get_constraints() tuple[float, ...]
Return optimizable constraints.
Returns roller_radius and profile constraints.
- guide: Ground
- guide_angle: float
- property output: _AnchorProxy
Return the output proxy for connecting other components.
- Returns:
An anchor proxy representing the follower output.
- profile: CamProfile
- reload(dt: float = 1) None
Recompute follower position from cam angle.
- Parameters:
dt – Time step (used to get cam angle from driver).
- Raises:
ValueError – If cam driver or guide has undefined position.
- roller_radius: float
- set_constraints(roller_radius: float | None = None, *profile_constraints: float | None) None
Set constraints from optimization.
- Parameters:
roller_radius – New roller radius.
*profile_constraints – Constraints passed to profile.
- pylinkage.dyads.create_dyad(signature: str, anchors: dict[str, Component | _AnchorProxy], constraints: dict[str, float] | None = None, x: float | None = None, y: float | None = None, name: str | None = None) RRRDyad | RRPDyad | PPDyad
Create a dyad from an isomer signature.
This factory function provides a unified interface for creating any of the 12 dyadic isomers. It determines the appropriate geometry type from the signature and creates the corresponding dyad class.
- Parameters:
signature – Isomer signature (e.g., “RRR”, “RT_R”, “T_R_T”).
anchors – Dict mapping anchor roles to components. Required keys depend on geometry type: - circle_circle (RRR): “anchor1”, “anchor2” - circle_line (RRP variants): “revolute”, “line1”, “line2” - line_line (PP variants): “line1_p1”, “line1_p2”, “line2_p1”, “line2_p2”
constraints – Dict of constraints. Required keys depend on geometry type: - circle_circle (RRR): “distance1”, “distance2” - circle_line (RRP variants): “distance” - line_line (PP variants): none required
x – Initial x position hint (optional).
y – Initial y position hint (optional).
name – Human-readable identifier.
- Returns:
The appropriate dyad instance (RRRDyad, RRPDyad, or PPDyad).
- Raises:
ValueError – If signature is unknown or required anchors/constraints are missing.
Examples
>>> # Circle-circle (RRR) >>> dyad = create_dyad( ... signature="RRR", ... anchors={"anchor1": crank.output, "anchor2": ground2}, ... constraints={"distance1": 2.0, "distance2": 1.5}, ... name="rocker", ... )
>>> # Circle-line (RT_R - slider crank) >>> dyad = create_dyad( ... signature="RT_R", ... anchors={"revolute": crank.output, "line1": L1, "line2": L2}, ... constraints={"distance": 1.5}, ... name="slider", ... )
>>> # Line-line (T_R_T - double slider) >>> dyad = create_dyad( ... signature="T_R_T", ... anchors={ ... "line1_p1": A, "line1_p2": B, ... "line2_p1": C, "line2_p2": D, ... }, ... name="double_slider", ... )
- pylinkage.dyads.get_isomer_geometry(signature: str) str
Get the geometry type for an isomer signature.
- Parameters:
signature – The isomer signature.
- Returns:
“circle_circle”, “circle_line”, or “line_line”.
- Return type:
Geometry type
- Raises:
ValueError – If signature is unknown.
- pylinkage.dyads.get_required_anchors(signature: str) list[str]
Get the required anchor names for an isomer signature.
- Parameters:
signature – The isomer signature.
- Returns:
List of required anchor keys.
- Raises:
ValueError – If signature is unknown.
- pylinkage.dyads.get_required_constraints(signature: str) list[str]
Get the required constraint names for an isomer signature.
- Parameters:
signature – The isomer signature.
- Returns:
List of required constraint keys.
- Raises:
ValueError – If signature is unknown.
- pylinkage.dyads.to_mechanism(linkage: Linkage) Mechanism
Convert a dyads Linkage to a mechanism Mechanism.
This creates a low-level Mechanism object from a dyads Linkage, allowing access to the full mechanism API (Joint/Link classes, step_fast(), etc.).
- Parameters:
linkage – A dyads Linkage to convert.
- Returns:
A mechanism.Mechanism object.
Note
This is a one-way conversion. Changes to the returned Mechanism will not be reflected in the original Linkage.