pylinkage.cam package

Submodules

pylinkage.cam.motion_laws module

Standard cam motion laws.

Motion laws define the dimensionless displacement function s(u) where u in [0, 1] is the normalized angle and s in [0, 1] is the normalized displacement.

Available motion laws:

HarmonicMotionLaw: Simple harmonic (cosine) motion CycloidalMotionLaw: Cycloidal motion (zero velocity/acceleration at ends) ModifiedTrapezoidalMotionLaw: Modified trapezoidal (low peak acceleration) PolynomialMotionLaw: General polynomial (3-4-5 or custom)

class pylinkage.cam.motion_laws.CycloidalMotionLaw

Bases: MotionLaw

Cycloidal motion law.

s(u) = u - sin(2*pi*u) / (2*pi)

Properties:
  • Zero velocity at boundaries (smooth start/stop)

  • Zero acceleration at boundaries (no jerk discontinuity)

  • Higher peak acceleration than harmonic

  • Best for high-speed applications requiring smooth motion

Example

>>> law = CycloidalMotionLaw()
>>> law.displacement(0.0)  # Zero displacement at start
0.0
>>> law.velocity(0.0)  # Zero velocity at start
0.0
>>> law.velocity(0.5)  # Maximum velocity at midpoint
2.0
acceleration(u: float) float

Compute cycloidal acceleration.

displacement(u: float) float

Compute cycloidal displacement.

property profile_type: int

Return cycloidal profile type code.

velocity(u: float) float

Compute cycloidal velocity.

class pylinkage.cam.motion_laws.HarmonicMotionLaw

Bases: MotionLaw

Simple harmonic (cosine) motion law.

s(u) = (1 - cos(pi * u)) / 2

Properties:
  • Smooth displacement curve

  • Non-zero acceleration at boundaries (acceleration discontinuity)

  • Maximum velocity at u = 0.5

  • Simple and widely used

Example

>>> law = HarmonicMotionLaw()
>>> law.displacement(0.0)  # Start
0.0
>>> law.displacement(0.5)  # Midpoint
0.5
>>> law.displacement(1.0)  # End
1.0
acceleration(u: float) float

Compute harmonic acceleration.

displacement(u: float) float

Compute harmonic displacement.

property profile_type: int

Return harmonic profile type code.

velocity(u: float) float

Compute harmonic velocity.

class pylinkage.cam.motion_laws.ModifiedTrapezoidalMotionLaw

Bases: MotionLaw

Modified trapezoidal motion law.

Uses sinusoidal acceleration segments at start/end with constant acceleration in the middle.

Properties:
  • Lower peak acceleration than harmonic or cycloidal

  • Zero velocity at boundaries

  • Continuous acceleration (no jerk discontinuity)

  • Good for high-load applications

Example

>>> law = ModifiedTrapezoidalMotionLaw()
>>> law.displacement(0.0)
0.0
>>> law.displacement(1.0)
1.0
acceleration(u: float) float

Compute modified trapezoidal acceleration (numerical).

displacement(u: float) float

Compute modified trapezoidal displacement.

property profile_type: int

Return modified trapezoidal profile type code.

velocity(u: float) float

Compute modified trapezoidal velocity.

class pylinkage.cam.motion_laws.MotionLaw

Bases: ABC

Abstract base class for cam motion laws.

Motion laws define the dimensionless displacement function s(u) where u in [0, 1] is the normalized angle and s in [0, 1] is the normalized displacement.

Subclasses must implement displacement(), velocity(), and acceleration() methods that operate on normalized coordinates.

abstractmethod acceleration(u: float) float

Compute dimensionless acceleration d2s/du2.

Parameters:

u – Normalized angle in [0, 1].

Returns:

Normalized acceleration.

abstractmethod displacement(u: float) float

Compute dimensionless displacement s(u).

Parameters:

u – Normalized angle in [0, 1].

Returns:

Normalized displacement in [0, 1].

abstract property profile_type: int

Return the profile type code for numba dispatch.

to_numba_coefficients() NDArray[np.float64]

Return coefficients for numba evaluation.

Default implementation returns empty array. Override for polynomial/custom motion laws.

Returns:

Numpy array of coefficients.

abstractmethod velocity(u: float) float

Compute dimensionless velocity ds/du.

Parameters:

u – Normalized angle in [0, 1].

Returns:

Normalized velocity.

class pylinkage.cam.motion_laws.PolynomialMotionLaw(coefficients: list[float] | None = None)

Bases: MotionLaw

General polynomial motion law.

s(u) = sum(coefficients[i] * u^i)

The polynomial must satisfy boundary conditions:
  • s(0) = 0, s(1) = 1 (displacement)

  • ds/du(0) = 0, ds/du(1) = 0 (velocity, typically)

Common polynomials:
  • 3-4-5 polynomial: [0, 0, 0, 10, -15, 6]

  • 4-5-6-7 polynomial: [0, 0, 0, 0, 35, -84, 70, -20]

Example

>>> # 3-4-5 polynomial (zero velocity and acceleration at ends)
>>> law = PolynomialMotionLaw([0, 0, 0, 10, -15, 6])
>>> law.displacement(0.0)
0.0
>>> law.displacement(1.0)
1.0
>>> law.velocity(0.0)
0.0
acceleration(u: float) float

Compute polynomial acceleration.

property coefficients: NDArray[np.float64]

Return polynomial coefficients.

displacement(u: float) float

Compute polynomial displacement.

property profile_type: int

Return polynomial profile type code.

to_numba_coefficients() NDArray[np.float64]

Return polynomial coefficients for numba evaluation.

velocity(u: float) float

Compute polynomial velocity.

pylinkage.cam.motion_laws.polynomial_345() PolynomialMotionLaw

Create a 3-4-5 polynomial motion law.

s(u) = 10*u^3 - 15*u^4 + 6*u^5

This polynomial has:
  • Zero velocity at u=0 and u=1

  • Zero acceleration at u=0 and u=1

Returns:

PolynomialMotionLaw instance.

pylinkage.cam.motion_laws.polynomial_4567() PolynomialMotionLaw

Create a 4-5-6-7 polynomial motion law.

s(u) = 35*u^4 - 84*u^5 + 70*u^6 - 20*u^7

This polynomial has:
  • Zero velocity at u=0 and u=1

  • Zero acceleration at u=0 and u=1

  • Zero jerk at u=0 and u=1

Returns:

PolynomialMotionLaw instance.

pylinkage.cam.profiles module

Cam profile definitions.

A cam profile defines the displacement as a function of cam angle. Profiles support evaluation, derivatives, and integration with the optimization constraint system.

Available profiles:

FunctionProfile: Profile from motion law + timing parameters PointArrayProfile: Profile from discrete points with spline interpolation

class pylinkage.cam.profiles.CamProfile

Bases: ABC

Abstract base class for cam profiles.

A cam profile defines the follower displacement as a function of cam rotation angle. The profile determines the shape of the cam and the resulting follower motion.

Variables:
  • base_radius (float) – Minimum radius (base circle radius).

  • name (str) – Human-readable identifier.

base_radius: float
abstractmethod evaluate(angle: float) float

Evaluate cam radius at given angle.

Parameters:

angle – Cam rotation angle in radians.

Returns:

Cam radius at this angle.

abstractmethod evaluate_derivative(angle: float) float

Evaluate cam radius derivative (dr/dtheta) at given angle.

Required for pressure angle and pitch curve calculations.

Parameters:

angle – Cam rotation angle in radians.

Returns:

Derivative dr/dtheta at this angle.

abstractmethod get_constraints() tuple[float, ...]

Return optimizable constraint values.

Returns:

Tuple of constraint values that can be optimized.

name: str
pitch_radius(angle: float, roller_radius: float) float

Compute pitch curve radius for roller follower.

The pitch curve is the path traced by the roller center.

Parameters:
  • angle – Cam rotation angle in radians.

  • roller_radius – Radius of the roller follower.

Returns:

Distance from cam center to roller center.

pressure_angle(angle: float) float

Compute pressure angle at given cam angle.

The pressure angle is the angle between the normal to the cam profile and the direction of follower motion. High pressure angles (> 30 degrees) can cause binding.

Parameters:

angle – Cam rotation angle in radians.

Returns:

Pressure angle in radians.

abstractmethod set_constraints(*args: float | None) None

Set constraint values from optimization.

Parameters:

*args – Constraint values to set.

abstractmethod to_numba_data() tuple[int, NDArray[np.float64]]

Convert profile to numba-compatible representation.

Returns:

Tuple of (profile_type_code, data_array).

class pylinkage.cam.profiles.FunctionProfile(motion_law: MotionLaw | None = None, base_radius: float = 1.0, total_lift: float = 0.5, rise_start: float = 0.0, rise_end: float | None = None, dwell_high_end: float | None = None, fall_end: float | None = None, name: str | None = None)

Bases: CamProfile

Cam profile from motion law and timing parameters.

Defines a rise-dwell-fall-dwell motion using a standard motion law. The motion law determines the shape of the rise and fall segments.

Variables:
  • motion_law (pylinkage.cam.motion_laws.MotionLaw) – The motion law defining displacement curve.

  • base_radius (float) – Base circle radius.

  • total_lift (float) – Maximum follower displacement.

  • rise_start (float) – Angle where rise begins (radians).

  • rise_end (float) – Angle where rise ends / dwell-high begins.

  • dwell_high_end (float) – Angle where dwell-high ends / fall begins.

  • fall_end (float) – Angle where fall ends / dwell-low begins.

Example

>>> from pylinkage.cam import FunctionProfile, HarmonicMotionLaw
>>> import math
>>> profile = FunctionProfile(
...     motion_law=HarmonicMotionLaw(),
...     base_radius=1.0,
...     total_lift=0.5,
...     rise_start=0.0,
...     rise_end=math.pi/2,
...     dwell_high_end=math.pi,
...     fall_end=3*math.pi/2,
... )
>>> profile.evaluate(0.0)  # At base radius
1.0
>>> profile.evaluate(math.pi)  # At max lift
1.5
base_radius: float
dwell_high_end: float
evaluate(angle: float) float

Evaluate cam radius at given angle.

evaluate_derivative(angle: float) float

Evaluate cam radius derivative at given angle.

fall_end: float
get_constraints() tuple[float, ...]

Return optimizable constraints.

Returns base_radius and total_lift as optimizable parameters. Timing parameters are typically fixed.

motion_law: MotionLaw
name: str
rise_end: float
rise_start: float
set_constraints(base_radius: float | None = None, total_lift: float | None = None, *args: float | None) None

Set constraint values.

to_numba_data() tuple[int, NDArray[np.float64]]

Convert to numba-compatible representation.

total_lift: float
class pylinkage.cam.profiles.PointArrayProfile(angles: list[float] | NDArray[np.float64], radii: list[float] | NDArray[np.float64], periodic: bool = True, name: str | None = None)

Bases: CamProfile

Cam profile from discrete (angle, radius) points.

Uses cubic spline interpolation to create a smooth profile through the given points. Supports periodic boundary conditions for closed cam profiles.

The spline coefficients are precomputed at construction for efficient numba evaluation in the simulation loop.

Example

>>> import math
>>> angles = [0, math.pi/4, math.pi/2, math.pi, 3*math.pi/2, 2*math.pi]
>>> radii = [1.0, 1.2, 1.5, 1.5, 1.2, 1.0]
>>> profile = PointArrayProfile(angles=angles, radii=radii)
>>> profile.evaluate(math.pi/4)  # Approximately 1.2
1.2
property angles: NDArray[np.float64]

Return the angle values.

base_radius
evaluate(angle: float) float

Evaluate cam radius at given angle using spline interpolation.

evaluate_derivative(angle: float) float

Evaluate cam radius derivative at given angle.

get_constraints() tuple[float, ...]

Return optimizable constraints (the radius values).

name
property radii: NDArray[np.float64]

Return the radius values.

set_constraints(*radii: float | None) None

Set radius values and recompute spline.

Parameters:

*radii – New radius values (same length as original).

to_numba_data() tuple[int, NDArray[np.float64]]

Convert to numba-compatible representation.

Module contents

Cam profile definitions for cam-follower mechanisms.

This module provides classes for defining cam profiles that drive cam-follower mechanisms. Profiles can be defined analytically using motion laws or from discrete points with spline interpolation.

Profile Types:

FunctionProfile: Profile from motion law + timing parameters PointArrayProfile: Profile from discrete points with spline interpolation

Motion Laws:

HarmonicMotionLaw: Simple harmonic (cosine) motion CycloidalMotionLaw: Cycloidal motion (zero velocity/acceleration at ends) ModifiedTrapezoidalMotionLaw: Modified trapezoidal (low peak acceleration) PolynomialMotionLaw: General polynomial (3-4-5 or custom)

Factory Functions:

polynomial_345: Create a 3-4-5 polynomial motion law polynomial_4567: Create a 4-5-6-7 polynomial motion law

Example

Create a cam profile with harmonic motion:

from pylinkage.cam import FunctionProfile, HarmonicMotionLaw
import math

profile = FunctionProfile(
    motion_law=HarmonicMotionLaw(),
    base_radius=1.0,
    total_lift=0.5,
    rise_start=0.0,
    rise_end=math.pi/2,
    dwell_high_end=math.pi,
    fall_end=3*math.pi/2,
)

# Evaluate at various angles
r0 = profile.evaluate(0.0)       # Base radius
r_max = profile.evaluate(math.pi) # Max radius

Create a cam profile from discrete points:

from pylinkage.cam import PointArrayProfile
import math

angles = [0, math.pi/4, math.pi/2, math.pi, 3*math.pi/2, 2*math.pi]
radii = [1.0, 1.2, 1.5, 1.5, 1.2, 1.0]
profile = PointArrayProfile(angles=angles, radii=radii)
class pylinkage.cam.CamProfile

Bases: ABC

Abstract base class for cam profiles.

A cam profile defines the follower displacement as a function of cam rotation angle. The profile determines the shape of the cam and the resulting follower motion.

Variables:
  • base_radius (float) – Minimum radius (base circle radius).

  • name (str) – Human-readable identifier.

base_radius: float
abstractmethod evaluate(angle: float) float

Evaluate cam radius at given angle.

Parameters:

angle – Cam rotation angle in radians.

Returns:

Cam radius at this angle.

abstractmethod evaluate_derivative(angle: float) float

Evaluate cam radius derivative (dr/dtheta) at given angle.

Required for pressure angle and pitch curve calculations.

Parameters:

angle – Cam rotation angle in radians.

Returns:

Derivative dr/dtheta at this angle.

abstractmethod get_constraints() tuple[float, ...]

Return optimizable constraint values.

Returns:

Tuple of constraint values that can be optimized.

name: str
pitch_radius(angle: float, roller_radius: float) float

Compute pitch curve radius for roller follower.

The pitch curve is the path traced by the roller center.

Parameters:
  • angle – Cam rotation angle in radians.

  • roller_radius – Radius of the roller follower.

Returns:

Distance from cam center to roller center.

pressure_angle(angle: float) float

Compute pressure angle at given cam angle.

The pressure angle is the angle between the normal to the cam profile and the direction of follower motion. High pressure angles (> 30 degrees) can cause binding.

Parameters:

angle – Cam rotation angle in radians.

Returns:

Pressure angle in radians.

abstractmethod set_constraints(*args: float | None) None

Set constraint values from optimization.

Parameters:

*args – Constraint values to set.

abstractmethod to_numba_data() tuple[int, NDArray[np.float64]]

Convert profile to numba-compatible representation.

Returns:

Tuple of (profile_type_code, data_array).

class pylinkage.cam.CycloidalMotionLaw

Bases: MotionLaw

Cycloidal motion law.

s(u) = u - sin(2*pi*u) / (2*pi)

Properties:
  • Zero velocity at boundaries (smooth start/stop)

  • Zero acceleration at boundaries (no jerk discontinuity)

  • Higher peak acceleration than harmonic

  • Best for high-speed applications requiring smooth motion

Example

>>> law = CycloidalMotionLaw()
>>> law.displacement(0.0)  # Zero displacement at start
0.0
>>> law.velocity(0.0)  # Zero velocity at start
0.0
>>> law.velocity(0.5)  # Maximum velocity at midpoint
2.0
acceleration(u: float) float

Compute cycloidal acceleration.

displacement(u: float) float

Compute cycloidal displacement.

property profile_type: int

Return cycloidal profile type code.

velocity(u: float) float

Compute cycloidal velocity.

class pylinkage.cam.FunctionProfile(motion_law: MotionLaw | None = None, base_radius: float = 1.0, total_lift: float = 0.5, rise_start: float = 0.0, rise_end: float | None = None, dwell_high_end: float | None = None, fall_end: float | None = None, name: str | None = None)

Bases: CamProfile

Cam profile from motion law and timing parameters.

Defines a rise-dwell-fall-dwell motion using a standard motion law. The motion law determines the shape of the rise and fall segments.

Variables:
  • motion_law (pylinkage.cam.motion_laws.MotionLaw) – The motion law defining displacement curve.

  • base_radius (float) – Base circle radius.

  • total_lift (float) – Maximum follower displacement.

  • rise_start (float) – Angle where rise begins (radians).

  • rise_end (float) – Angle where rise ends / dwell-high begins.

  • dwell_high_end (float) – Angle where dwell-high ends / fall begins.

  • fall_end (float) – Angle where fall ends / dwell-low begins.

Example

>>> from pylinkage.cam import FunctionProfile, HarmonicMotionLaw
>>> import math
>>> profile = FunctionProfile(
...     motion_law=HarmonicMotionLaw(),
...     base_radius=1.0,
...     total_lift=0.5,
...     rise_start=0.0,
...     rise_end=math.pi/2,
...     dwell_high_end=math.pi,
...     fall_end=3*math.pi/2,
... )
>>> profile.evaluate(0.0)  # At base radius
1.0
>>> profile.evaluate(math.pi)  # At max lift
1.5
base_radius: float
dwell_high_end: float
evaluate(angle: float) float

Evaluate cam radius at given angle.

evaluate_derivative(angle: float) float

Evaluate cam radius derivative at given angle.

fall_end: float
get_constraints() tuple[float, ...]

Return optimizable constraints.

Returns base_radius and total_lift as optimizable parameters. Timing parameters are typically fixed.

motion_law: MotionLaw
name: str
rise_end: float
rise_start: float
set_constraints(base_radius: float | None = None, total_lift: float | None = None, *args: float | None) None

Set constraint values.

to_numba_data() tuple[int, NDArray[np.float64]]

Convert to numba-compatible representation.

total_lift: float
class pylinkage.cam.HarmonicMotionLaw

Bases: MotionLaw

Simple harmonic (cosine) motion law.

s(u) = (1 - cos(pi * u)) / 2

Properties:
  • Smooth displacement curve

  • Non-zero acceleration at boundaries (acceleration discontinuity)

  • Maximum velocity at u = 0.5

  • Simple and widely used

Example

>>> law = HarmonicMotionLaw()
>>> law.displacement(0.0)  # Start
0.0
>>> law.displacement(0.5)  # Midpoint
0.5
>>> law.displacement(1.0)  # End
1.0
acceleration(u: float) float

Compute harmonic acceleration.

displacement(u: float) float

Compute harmonic displacement.

property profile_type: int

Return harmonic profile type code.

velocity(u: float) float

Compute harmonic velocity.

class pylinkage.cam.ModifiedTrapezoidalMotionLaw

Bases: MotionLaw

Modified trapezoidal motion law.

Uses sinusoidal acceleration segments at start/end with constant acceleration in the middle.

Properties:
  • Lower peak acceleration than harmonic or cycloidal

  • Zero velocity at boundaries

  • Continuous acceleration (no jerk discontinuity)

  • Good for high-load applications

Example

>>> law = ModifiedTrapezoidalMotionLaw()
>>> law.displacement(0.0)
0.0
>>> law.displacement(1.0)
1.0
acceleration(u: float) float

Compute modified trapezoidal acceleration (numerical).

displacement(u: float) float

Compute modified trapezoidal displacement.

property profile_type: int

Return modified trapezoidal profile type code.

velocity(u: float) float

Compute modified trapezoidal velocity.

class pylinkage.cam.MotionLaw

Bases: ABC

Abstract base class for cam motion laws.

Motion laws define the dimensionless displacement function s(u) where u in [0, 1] is the normalized angle and s in [0, 1] is the normalized displacement.

Subclasses must implement displacement(), velocity(), and acceleration() methods that operate on normalized coordinates.

abstractmethod acceleration(u: float) float

Compute dimensionless acceleration d2s/du2.

Parameters:

u – Normalized angle in [0, 1].

Returns:

Normalized acceleration.

abstractmethod displacement(u: float) float

Compute dimensionless displacement s(u).

Parameters:

u – Normalized angle in [0, 1].

Returns:

Normalized displacement in [0, 1].

abstract property profile_type: int

Return the profile type code for numba dispatch.

to_numba_coefficients() NDArray[np.float64]

Return coefficients for numba evaluation.

Default implementation returns empty array. Override for polynomial/custom motion laws.

Returns:

Numpy array of coefficients.

abstractmethod velocity(u: float) float

Compute dimensionless velocity ds/du.

Parameters:

u – Normalized angle in [0, 1].

Returns:

Normalized velocity.

class pylinkage.cam.PointArrayProfile(angles: list[float] | NDArray[np.float64], radii: list[float] | NDArray[np.float64], periodic: bool = True, name: str | None = None)

Bases: CamProfile

Cam profile from discrete (angle, radius) points.

Uses cubic spline interpolation to create a smooth profile through the given points. Supports periodic boundary conditions for closed cam profiles.

The spline coefficients are precomputed at construction for efficient numba evaluation in the simulation loop.

Example

>>> import math
>>> angles = [0, math.pi/4, math.pi/2, math.pi, 3*math.pi/2, 2*math.pi]
>>> radii = [1.0, 1.2, 1.5, 1.5, 1.2, 1.0]
>>> profile = PointArrayProfile(angles=angles, radii=radii)
>>> profile.evaluate(math.pi/4)  # Approximately 1.2
1.2
property angles: NDArray[np.float64]

Return the angle values.

base_radius
evaluate(angle: float) float

Evaluate cam radius at given angle using spline interpolation.

evaluate_derivative(angle: float) float

Evaluate cam radius derivative at given angle.

get_constraints() tuple[float, ...]

Return optimizable constraints (the radius values).

name
property radii: NDArray[np.float64]

Return the radius values.

set_constraints(*radii: float | None) None

Set radius values and recompute spline.

Parameters:

*radii – New radius values (same length as original).

to_numba_data() tuple[int, NDArray[np.float64]]

Convert to numba-compatible representation.

class pylinkage.cam.PolynomialMotionLaw(coefficients: list[float] | None = None)

Bases: MotionLaw

General polynomial motion law.

s(u) = sum(coefficients[i] * u^i)

The polynomial must satisfy boundary conditions:
  • s(0) = 0, s(1) = 1 (displacement)

  • ds/du(0) = 0, ds/du(1) = 0 (velocity, typically)

Common polynomials:
  • 3-4-5 polynomial: [0, 0, 0, 10, -15, 6]

  • 4-5-6-7 polynomial: [0, 0, 0, 0, 35, -84, 70, -20]

Example

>>> # 3-4-5 polynomial (zero velocity and acceleration at ends)
>>> law = PolynomialMotionLaw([0, 0, 0, 10, -15, 6])
>>> law.displacement(0.0)
0.0
>>> law.displacement(1.0)
1.0
>>> law.velocity(0.0)
0.0
acceleration(u: float) float

Compute polynomial acceleration.

property coefficients: NDArray[np.float64]

Return polynomial coefficients.

displacement(u: float) float

Compute polynomial displacement.

property profile_type: int

Return polynomial profile type code.

to_numba_coefficients() NDArray[np.float64]

Return polynomial coefficients for numba evaluation.

velocity(u: float) float

Compute polynomial velocity.

pylinkage.cam.polynomial_345() PolynomialMotionLaw

Create a 3-4-5 polynomial motion law.

s(u) = 10*u^3 - 15*u^4 + 6*u^5

This polynomial has:
  • Zero velocity at u=0 and u=1

  • Zero acceleration at u=0 and u=1

Returns:

PolynomialMotionLaw instance.

pylinkage.cam.polynomial_4567() PolynomialMotionLaw

Create a 4-5-6-7 polynomial motion law.

s(u) = 35*u^4 - 84*u^5 + 70*u^6 - 20*u^7

This polynomial has:
  • Zero velocity at u=0 and u=1

  • Zero acceleration at u=0 and u=1

  • Zero jerk at u=0 and u=1

Returns:

PolynomialMotionLaw instance.