Skip to content

Releases: HugoFara/pylinkage

v0.9.0: Co-optimization and the Topology Catalog

14 Apr 11:51

Choose a tag to compare

Pylinkage 0.9.0 is a research-plus-polish release. Two new capabilities: topology enumeration and co-optimization of topology + dimensions. It moves pylinkage from "simulate and optimize a given mechanism" to "design the mechanism itself." Alongside them, a round of API ergonomics (factory helpers, analysis plot methods, trajectory extraction, skip_unbuildable) cuts visible boilerplate across every notebook. It is a release to stabilize the API around the features that really matter.

pip install pylinkage            # Lightweight core
pip install pylinkage[full]      # Everything

Highlights

Topology catalog and enumeration

The first pylinkage release to treat mechanism structure itself as a searchable object. Systematic enumeration of every non-isomorphic 1-DOF planar linkage up to 8 links — 19 topologies total (1 four-bar + 2 six-bars + 16 eight-bars), validated against Mruthyunjaya's 1984 three-part catalog (I, II, III). Graph isomorphism detection via 1-WL color refinement plus backtracking verification. Ships as a pre-built TopologyCatalog with JSON-serialized hypergraphs and metadata.

from pylinkage.topology import load_catalog

catalog = load_catalog()                          # 19 canonical topologies
for entry in catalog.entries.values():
    print(entry.family, entry.num_joints, entry.link_assortment)

Co-optimization of topology + dimensions

A mixed-variable NSGA-II optimizer that jointly searches the discrete topology space and the continuous dimensional space via pymoo. Custom operators: MixedCrossover blends dimensions with BLX-α while swapping topologies via a neighborhood graph; MixedMutation does Gaussian perturbation plus topology-neighbor jumps. Simultaneous triad placement via scipy.optimize.least_squares handles circular dependencies in Stephenson six-bars. Warm-start from Phase 3 synthesis results.

from pylinkage.optimization import co_optimize, CoOptimizationConfig
from pylinkage.topology import load_catalog

result = co_optimize(
    objectives=[path_error, stroke_range],
    catalog=load_catalog(),
    config=CoOptimizationConfig(n_generations=50),
)
for sol in result.solutions:
    print(sol.topology_entry.family, sol.chromosome.dimensions, sol.scores)

Six-bar mechanisms end-to-end

Mechanism.step() now uses Assur group decomposition internally, solving dyads and triads via solve_group() dispatch. Watt and Stephenson six-bars can be simulated start to finish. Convenience builders watt_from_lengths() and stephenson_from_lengths() from seven link lengths.

Population abstractions

pylinkage.population introduces Member / Ensemble / Population — numpy-style containers for batch mechanism work. Topology-bound ensembles run all variants through the numba-compiled solver in one pass, with columnar scores for vectorized rank(), top(), filter_by_score(). Every optimizer now returns an Ensemble instead of ad-hoc lists. SynthesisResult.ensemble lazily builds one from synthesis solutions.

result = particle_swarm_optimization(fitness, linkage, ...)  # → Ensemble
top3 = result.top(3, ascending=False)           # three best by score
good = result.filter_by_score(min_val=0.8)      # every member above threshold
good.show(idx=0)                                # render a chosen member

Ergonomic helpers

  • fourbar(crank, coupler, rocker, ground, ...) and slider_crank(crank, rod, ...) in pylinkage.mechanism — canonical topologies in one line instead of the eight-line MechanismBuilder chain.
  • extract_trajectory(loci, joint=-1) / extract_trajectories(loci, linkage=None) — replaces the recurring [(p[i][0], p[i][1]) for p in loci if p[i][0] is not None] pattern with (xs, ys) numpy arrays.
  • TransmissionAngleAnalysis.plot(ax=None) — one call for the acceptable-range + 90° optimum diagram.
  • skip_unbuildable=True on Linkage.step() — yields None tuples for unbuildable frames instead of aborting, so non-Grashof and double-rocker linkages recover the valid trajectory on both sides of their dead zones.

More optimizers

  • dual_annealing_optimization() — scipy's generalized simulated annealing for expensive single-objective problems with many local minima.
  • chain_optimizers() — run optimizers in sequence, piping each stage's best into the next (e.g. DE → Nelder-Mead).

Notebooks

Now we have 15 notebooks showing the extent of the app with all the user facing features. Have a look for examples, tutorials and more!

Breaking changes

  • Optimizers return Ensemble, not list[Agent], list[MutableAgent], or ParetoFront. Unpack via member = result[0]; member.score, member.dimensions instead of Agent tuple unpacking.
  • fourbar_from_lengths() now returns SimLinkage (component/actuator/dyad API) instead of the legacy Linkage. Access joints via .components, not .joints.
  • Default simulation resolution: 63 → 360 steps per rotation. Default angular velocity for Crank, ArcCrank, DriverLink, ArcDriverLink changed from 0.1 rad/step to tau / 360 (~one sample per degree). Old value is exported as DEFAULT_ANGULAR_VELOCITY if you need it.
  • PSO is now pure NumPy. Dropped the pyswarms dependency (unmaintained since 2021); replaced with a built-in local-best ring-topology PSO. API unchanged. The pso extra still exists but is empty.
  • Removed deprecated aliases: HypostaticError (use UnderconstrainedError), Linear (use Prismatic).
  • Parameter renames (old names still accepted as kwargs): itersiterations, posinitial_positions, init_positionsinitial_positions.

Deprecations

  • SynthesisResult collection protocol (len(result), result[i], iteration, bool(result)) now emits DeprecationWarning. Use result.ensemble instead. Will be removed in 1.0.0.

Fixes

  • compute_dof hyperedge counting — was counting a k-node hyperedge as (k−1) links instead of 1 rigid body, producing wrong DOF for every mechanism with ternary-or-higher links (all six-bars and eight-bars).
  • Change-point kinematics flake in test_fourbar_simulation_with_kinematics — test geometry was on the Grashof boundary, hitting singular configurations non-deterministically on some CI hosts.
  • API exception leakvalidate_and_build in api/services/ echoed raw exception messages into API responses (CodeQL "information exposure" alert). Domain-level UnbuildableError messages still pass through; unexpected exceptions are now logged server-side with a generic response.

Security

  • Pillow 12.1.1 → 12.2.0 (FITS GZIP decompression bomb, CVE high).
  • pytest 9.0.2 → 9.0.3 (insecure tmpdir handling, CVE moderate).

Full changelog: v0.8.0...v0.9.0

v0.8.0: Symbolic, Synthetic, and Complete

28 Mar 22:27

Choose a tag to compare

Pylinkage 0.8.0 is the biggest release in the project's history. It transforms pylinkage from a kinematic simulator with PSO into a complete planar mechanism design toolkit. It covers synthesis, simulation, optimization, analysis, and CAD export in a single pip install.

pip install pylinkage            # Lightweight core (numpy + tqdm)
pip install pylinkage[full]      # Everything included

Highlights

Classical linkage synthesis. Design four-bar linkages from motion requirements using Burmester theory, Freudenstein's equation, and path/function/motion generation. Specify precision points and get back buildable linkages. No manual geometry needed.

from pylinkage.synthesis import path_generation, solution_to_linkage

result = path_generation([(0, 1), (1, 2), (2, 1.5), (3, 0)])
for sol in result.solutions:
    linkage = solution_to_linkage(sol)

Symbolic computation. Get closed-form trajectory expressions via SymPy, compute analytical gradients, and run gradient-based optimization with SymbolicOptimizer. Mix symbolic parameters with numerical simulation seamlessly.

Multi-objective optimization. Pareto-optimal solutions via NSGA-II/III (pymoo). Optimize competing objectives — path accuracy vs. transmission angle, stroke range vs. compactness; with pareto.plot(), hypervolume indicators, and best-compromise selection.

Velocity & acceleration analysis. Full first and second-order kinematics with Numba-compiled solvers. Query joint.velocity and joint.acceleration directly, or use linkage.step_with_derivatives() for batch computation.

Cam-follower mechanisms. New pylinkage.cam module with translating and oscillating followers, standard motion laws (harmonic, cycloidal, modified trapezoidal, polynomial 3-4-5), and Numba-compiled profile evaluation.

Triad support & topology analysis. Class II Assur groups (triads) enable six-bar mechanisms (Watt, Stephenson), addressing the long-standing question of multi-bar support (#2). New compute_dof() implements Grübler's formula on the hypergraph representation.

Sensitivity & tolerance analysis. Monte Carlo tolerance analysis for manufacturing variation, sensitivity ranking of constraints, and DataFrame export for further analysis.

Transmission angle & stroke analysis. Built-in quality metrics for mechanism evaluation, directly usable as optimization constraints.

CAD export. DXF (via ezdxf) and STEP (via build123d) export for fabrication workflows, alongside publication-quality SVG.

SciPy integration. Differential evolution optimizer and scipy.optimize.minimize wrappers, complementing the existing PSO.

Numba JIT solver. High-performance compiled simulation backend for optimization hot loops (#11).

Lightweight by default

The core install now requires only numpy and tqdm. Everything else is optional:

Extra What it adds
numba JIT-compiled solvers
scipy Differential evolution, synthesis solvers
pso Particle Swarm Optimization (pyswarms)
symbolic SymPy closed-form expressions & gradient optimization
viz Matplotlib visualization and animation
plotly Interactive HTML visualization
svg Publication-quality SVG (drawsvg)
cad DXF and STEP export (ezdxf, build123d)
moo Multi-objective optimization (pymoo)
analysis Pandas DataFrame export for sensitivity data
api FastAPI REST backend
full All visualization, solver, and optimization extras

Combine as needed: pip install pylinkage[viz,scipy,pso]

Modular API

New component-based API replaces the legacy joint classes (#13):

Legacy (pylinkage.joints) New Module
Static Ground pylinkage.components
Crank Crank pylinkage.actuators
Revolute / Pivot RRRDyad pylinkage.dyads
Fixed FixedDyad pylinkage.dyads
Linear RRPDyad pylinkage.dyads
LinearActuator pylinkage.actuators
ArcCrank pylinkage.actuators

Plus a link-first fluent MechanismBuilder API with JSON serialization.

11 tutorial notebooks

New Jupyter notebooks walk through real design problems end-to-end:

  1. Straight-line synthesis — approximate straight-line coupler paths
  2. Coupler curve optimization — PSO-driven curve matching
  3. Tolerance analysis — Monte Carlo manufacturing variation
  4. Cam-follower timing — motion laws and profile design
  5. Symbolic coupler curves — closed-form SymPy expressions
  6. Function generation — Freudenstein equation synthesis
  7. Motion generation — rigid body guidance (Burmester theory)
  8. Velocity & acceleration — first/second-order kinematics
  9. Transmission angle & DOF — Grübler's formula, quality metrics
  10. MechanismBuilder — fluent API vs. legacy comparison
  11. Multi-objective & SciPy optimization — DE, Nelder-Mead, NSGA-II Pareto fronts

Breaking changes

  • Python ≥ 3.10 required (3.9 dropped). Python 3.14 added to CI.
  • Linkage.step_fast_with_kinematics() now returns (positions, velocities, accelerations) instead of a 2-tuple.
  • LinearActuator.velocity renamed to LinearActuator.speed (conflict with new Component.velocity property).

Bug fixes

  • PSO score sign: particle_swarm_optimization() returned the negated pyswarms cost when using order_relation=max, producing incorrect (often negative) scores.
  • Mechanism builder branch selection: MechanismBuilder.set_branch() produced inconsistent assembly configurations because circle-circle constraints arrived in non-deterministic order. Constraints are now sorted by center position before intersection.

See Also

  • pylinkage-editor — Visual linkage design tool (#9). Draw mechanisms interactively, run synthesis from the GUI, and export results.
  • bastidas/Acinonyx — A polished visualization frontend for pylinkage. @bastidas's project was the inspiration to finally get these changes out of dev and publish this release.

Full changelog: v0.7.0...v0.8.0

0.7.0: abstractions and visualisation!

13 Dec 15:09

Choose a tag to compare

This release goes back to the roots of the project: linkage analysis.
It separates the abstractions between fast numerical solving (numba), meaningful abstraction (hypergraphs),
while aligning the user API with elegant classical concepts (Assur groups)

I ended up releasing a bit faster than expected as I noticed 0.6.0 was apparently broken.

[0.7.0] - 2025-12-13

Added in 0.7.0

  • Serialization: adds linkage serialization features.
  • Typing: adds typing.
  • Test: adds complete testing coverage.
  • Adds support for Python 3.14.
  • Hypergraph as the base theory for linkages.

Changed in 0.7.0

  • Switches to uv.
  • Renames HypostaticError to UnderconstrainedError and hyperstaticity() to indeterminacy().
    Old names kept as deprecated aliases.
  • Separate linkage definition from actual solving:
    • The internal solver is now numba + NumPy, almost 100x faster!
    • The user-facing code is now based on Assur groups, that is more formal.

Fixed in 0.7.0

  • __find_solving_order__() is now properly tested and implemented (#16).

Deprecated in 0.7.0

  • Linear joint term is now deprecated in favor of Prismatic.

Removed in 0.7.0

  • Removed support for Python 3.9.

Full Changelog: v0.6.0...v0.7.0

Linear joint / Package Structure Rewrite

02 Oct 15:38

Choose a tag to compare

This release is a huge effort to make pylinkage better!
It features abstract Joint, the new Linear joint, and the package structure was rewritten to make contributions easier.

Added

  • New joint: the Linear joint!
  • New sub-package: optimization.collections.
    optimization.collections.Agent and optimization.collections.MutableAgent are two new classes that should standardize the format of
    optimization, related to (#5).
    • Agent is immutable and inherits from a namedtuple. It is recommended to use it, as it is a bit faster.
    • MutableAgent is mutable. It may be deprecated/removed if Agent is satisfactory.
  • New sub-package: geometry.
    • It introduces two new functions line_from_points and circle_line_intersection.
  • New examples:
  • Linkage.set_completely is a new method combining both Linkage.set_num_constraints and Linkage.set_coords.
  • New exception NotCompletelyDefinedError, when a joint is reloading but its anchor coordinates are set to None.
  • Some run configuration files added for users of PyCharm:
    • Run all tests with "All Tests".
    • Regenerate documentation with "Sphinx Documentation".

Changed

  • Optimization return type changed (#5):
    • trials_and_error_optimization return an array of MutableAgent.
    • particle_swarm_optimization return an array of one Agent.
    • It should not be a breaking change for most users.
  • Changes to the "history" style.
    • It is no longer a global variable in example scripts.
    • It was in format iterations[dimensions, score], now it is a standard iterations[score, dimensions, initial pos].
    • repr_polar_swarm (in example scripts) changed to follow the new format.
    • swarm_tiled_repr takes (index, swarm) as input argument. swarm is (score, dim, pos) for each agent for this
      iteration.
  • repr_polar_swarm reload frame only when a new buildable linkage is generated.
    • This makes the display much faster.
    • For each iteration, you may see linkages that do not exist anymore.
  • Folders reorganization:
    • The geometry module is now a package (pylinkage/geometry)
    • New package pylinkage/linkage:
      • pylinkage/linkage.py separated and inserted in this package.
    • New package: pylinkage/joints
      • Joints definition are in respective files.
    • New package pylinkage/optimization/
      • pylinkage/optimizer.py split and inserted in.
      • Trials-and-errors related functions goes to grid_search.py.
      • Particle swarm optimization is at particle_swarm.py.
      • New file utils.py for generate_bounds.
    • Tests follow the same renaming.
    • From the user perspective, no change (execution may be a bit faster)
    • source/ renamed to sphinx/ because it was confusing and only for Sphinx configuration.
  • Transition from Numpydoc to reST for docstrings (#12).
  • __secant_circles_intersections__ renamed to
    secant_circles_intersections (in pylinkage/geometry/secants.py).

Fixed

  • swarm_tiled_repr in visualizer.py was wrongly assigning dimensions.
  • Setting locus_highlight in plot_static_linkage would result in an error.
  • Pivot.reload was returning arbitrary point when we had an infinity of solutions.
  • The highlighted locus was sometimes buggy in plot_static_linkage in
    visualizer.py.

Deprecated

  • Using tqdm_verbosity is deprecated in favor of using disable=True in a tqdm object.
  • The Pivot class is deprecated in favor of the Revolute class.
    The name "Pivot joint" is not standard.
    Related to #13.
  • The hyperstaticity method is renamed indeterminacy in Linkage
    (linkage.py)

Removed

  • Drops support for Python 3.7 and 3.8 as both versions reached end-of-life.
  • movement_bounding_bow is replaced by movement_bounding_box (typo in function name).

Big Dust Blowing

23 Jun 07:47

Choose a tag to compare

This is the first release in almost two years of inactivity, and as such it contains a lot of practice changes.
It mainly prepares code for a non-backward compatible leap in a future 0.6.0.

Added

  • We now checked compatibility with Python 3.10 and 3.11.
  • pyproject.toml is now the official definition of the package.
  • Linkage.hyperstaticity now clearly outputs a warning when used.

Changed

  • master branch is now main.
  • docs/example/fourbar_linkage.py can now be used as a module (not the target but anyway).
  • docs/examples moved to examples/ (main folder).
    • Now docs/ only contains sphinx documentation.
  • docs/examples/images moved to images/.

Fixed

  • Setting a motor with a negative rotation angle do no longer break get_rotation_period
    (#7).
  • Pivot.reload and Linkage.__find_solving_order__ were raising Warnings (stopping the code), when they should only print a message (intended behavior).
  • Fixed many typos in documentation as well as in code.
  • The TestPSO.test_convergence is now faster on average, and when it fails in the first time, it launches a bigger test.
  • Minor linting in the demo file docs/example/fourbar_linkage.py.

Deprecated

  • Using Python 3.7 is officially deprecated (end of life by 2023-06-27). It will no longer be tested, use it at your own risks!

A bit of user-friendlyness

21 Jul 12:14

Choose a tag to compare

This release bring some convenient functionalities.

Added

  • You can see the best score and best dimensions updating in
    trials_and_errors_optimization.

Changed

  • The optimizers tests are 5 times quicker (~1 second now) and raise less
    false positive.
  • The sidebar in the documentation make navigation easier.
  • A bit of reorganization in optimizers, it should not affect users.

Progress bar is progress, some requirements added

14 Jul 11:04

Choose a tag to compare

Added

  • The trials and errors optimization now have a progress bar (same kind of the one in particle swarm optimization), using the tqdm.

Changed

Optimized optimizers, simplified workflow

12 Jul 11:36

Choose a tag to compare

End alpha development! The package is now robust enough to be used by a mere human. This version introduces a lot of changes and simplifications, so everything is not perfect yet, but it is complete enough to be considered a beta version.

Git tags will no longer receive a "-alpha" mention.

Added

  • It is now possible and advised to import useful functions from pylinkage.{object}, without full path. For instance, use from pylinkage import Linkage instead of from pylinkage.linkage import Linkage.
  • Each module had his header improved.
  • The generate_bounds functions is a simple way to generate bounds before optimization.
  • The order_relation arguments of particle_swarm_optimization and trials_and_errors_optimization let you choose between maximization and minimization problem.
  • You can specify a custom order relation with trials_and_errors_optimization.
  • The verbose argument in optimizers can disable verbosity.
  • Static joints can now be defined implicitely.
  • The utility module provides two useful decarators kinematic_minimization and kinematic_optimizatino. They greatly simplify the workflow of defining fitness functions.
  • Versionning is now done thanks to bump2version.

Changed

  • The particle_swarm_optimization eval_func signature is now similar to the one of trials_and_errors optimization. Wrappers are no longer needed!
  • The trials_and_errors_optimization function now asks for bounds instead of dilatation and compression factors.
  • In trials_and_errors_optimization absolute step delta_dim is now replaced by number of subdivisions divisions.

Fixed

  • After many hours of computations, default parameters in particle_swarm_optimization are much more efficient. With the demo fourbar_linkage, the output wasn't even convergent some times. Now we have a high convergence rate (~100%), and results equivalent to the trials_and_errors_optimization (in the example).
  • variator function of optimizer module was poorly working.
  • The docstrings were not displayed properly in documentation, this is fixed.

Documentation upgraded and package reorganisation

11 Jul 09:20

Choose a tag to compare

This release introduces a good documentation with sphinx and a website hosted on GitHub pages.

Added

  • The legend in visualizer.py is back!
  • Documentation published to GitHub pages! It is contained in the docs/ folder.
  • setup.cfg now include links to the website.

Changed

  • Examples moved from pylinkage/examples/ to docs/examples/.
  • Tests moved from pylinkage/tests/ to tests/.

Visualization and code linting

06 Jul 16:05

Choose a tag to compare

This release introduces a lot of renaming. It also correct bugs introduced in alpha version v0.3.0. We ultimately deleted the built-in PSO.

Added

  • The bounding_box method of geometry allow to compute the bounding box of a finite set of 2D points.
  • You can now customize colors of linkage's bars with the COLOR_SWITCHER variable of visualizer.py.
  • movement_bounding_box in visualizer.py to get the bounding box of multiple loci.
  • parameters is optional in trials_and_errors_optimization (former exhaustive_optimization)
  • pylinkage/tests/test_optimizer.py for testing the optimizers, but it is a bit ugly as for now.

Fixed

  • set_num_constraints in Linkage was misbehaving due to update 0.3.0.
  • Cost history is no longer plotted automatically after a PSO.

Changed

  • exhaustive_optimization is now known as trials_and_errors_optimizattion.
  • Axis on linkage visualization are now named "x" and "y", and no longer "Points abcsices" and "Ordinates".
  • A default view of the linkage is displayed in plot_static_linkage.
  • Default padding in linkage representation was changed from an absolute value of 0.5 to a relative 20%.
  • Static view of linkage is now aligned with its kinematic one.
  • get_pos method of Linkage is now known as get_coords for consistency.
  • Parameters renamed, reorganized and removed in particle_swarm_optimization to align to PySwarms.
  • README.md updated consequently to the changes.

Removed

  • Legacy built-in Particle Swarm Optimization, to avoid confusions.
  • We do no longer show a default legend on static representation.

Security

  • Flake8 validation in tox.ini