Changelog
All notable changes to pylinkage are documented here.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning. What the version number promises, and how a name is retired, is in the Deprecations page.
[Unreleased]
[1.2.2] - 2026-09-18
Fixed
Mechanism.set_constraints()now changes the mechanism. Link lengths live in per-link distance caches and in the Assur-group solver’sDimensions, both captured when the mechanism is built;set_constraintsonly moved the crank tip, so the nextstep()snapped every joint back to the original geometry andget_constraints()read the old lengths. Every optimizer run on aMechanismwas a no-op since 0.8.0. The values are now written to the caches (newLink.set_distance()), the group solver is rebuilt, andLink.lengthreads the maintained constraint likeDriverLink.radiusdoes. Only binary links and driver radii are part of the constraint vector, as before.
[1.2.1] - 2026-09-12
Added
The documentation’s code runs in CI.
tests/docsexecutes every script underdocs/examples/and every python block of every page underdocs/source/tutorials/, in a subprocess withDeprecationWarningas an error. Markeddocsand deselected from the defaultpytestrun (minutes, every optional backend); the “Executable docs” workflow runs them next to the notebooks. Four examples and ten tutorials had been broken for a release without anything noticing.
Fixed
compute_mobility/compute_dofcount rigid bodies, not graph elements. They used to count every edge and every hyperedge as a link and every node as a joint, which is only true of the topology catalog’s hyperedge-only graphs. Any geometric graph — a coupler point, aHyperedgelabelling a triangle of edges, whatLinkage.to_hypergraph()emits for aFixedDyad, every classical walker leggedsnake ships — came out with nonsense (8 DOF for a coupler four-bar, 17 for a Jansen leg). Links are now the ground, one body per edge and per hyperedge, merged whenever two share two or more nodes; a node inkbodies isk - 1joints, so coupler points are not joints and multiple joints count; aPRISMATICnode is a slider block with a prismatic joint to its guide hyperedge. The 19 catalog entries are unchanged; a grounded triangle now reports DOF 0 and a lone crank DOF 1, which the old tests documented as wrong.HypergraphLinkage.to_simple_graph()expands a hyperedge to its clique, one edge per pair of nodes, astopology.isomorphismandassur.from_hypergraphalready did. It produced a chain ofN - 1edges, soto_mechanism()could not solve a ternary link written as a hyperedge alone (“Could not determine solve order”) unless its edges were repeated by hand. Pairs already joined by an edge are kept, not duplicated.Hyperedge.to_edges()still returns the documented chain.The graph tutorial’s mobility section describes the counting rules and no longer tells the reader to analyze a coupler mechanism “without the coupler point”.
[1.2.0] - 2026-09-12
Added
A defined public surface. A name is public when it is in a package’s
__all__; modules inside a package are implementation; each name has one home. The rule is on the Deprecations page, and the surface is pinned name by name intests/test_public_api.py, so it cannot change without a diff in that file. Newer subsystems are marked provisional there (topology,solver, co-design, multi-topology synthesis, hierarchical hypergraphs, CAD export): exported and documented, but not yet promised.Every subpackage is reachable from the top level (
pylinkage.components,pylinkage.hypergraph,pylinkage.optimization, …; before, onlyassur,dyads,mechanism,symbolicandsynthesiswere), and the definition path is importable straight frompylinkage:Ground,PointTracker,Crank,ArcCrank,LinearActuator,RRRDyad,RRPDyad,PPDyad,FixedDyad, alongsideLinkage.A deprecation policy, at
docs/source/deprecations.md. One page stating what pylinkage promises about public names: a name is announced with aDeprecationWarningnaming its replacement and removal release, and is removed no earlier than the next major version. It carries the table of everything currently deprecated, and how to surface the warnings, which are silent by default in Python.pylinkage._deprecation, the machinery behind that: deprecated names are removed from their module’s namespace and served by a module-level__getattr__(PEP 562), so reading one warns whilefrom ... import Namekeeps resolving exactly as before.A “Which API should I use?” section in the README, naming
components/actuators/dyads/simulationas the definition path and saying in one line each whatMechanismBuilder,hypergraph,assurandMechanismare for. The API reference index opens with the same guidance instead of an alphabetical list.pylinkage.synthesis.crank_angle_limits(crank, coupler, rocker, ground), the angular range of a crank that cannot rotate fully. It sits besidegrashof_checkandis_crank_rocker, and returnsNonefor a crank that rotates fully. It wassynthesis.conversion._compute_crank_limits, a private name that only the editor’s backend ever called; it now has a public name and a test that checks the returned bounds against the collinear positions.solution_to_linkage()honoursFourBarSolution.arc_limits. The field existed but nothing in pylinkage read it. Set it — typically fromcrank_angle_limits()— and the linkage is built around anArcCranksweeping that range instead of aCrank, so a double-rocker or non-Grashof solution can be stepped and animated; a full turn would stop at an unbuildable position. The limits are relative to the ground line, ascrank_angle_limits()returns them, and are placed on whichever side the crank starts.orientation_resolutiononpath_generation(), replacingn_orientation_samples. It is the number of angles sampled per free orientation, so the cost model is now readable from the signature: the search grid holdsorientation_resolution ** (n_precision_points - 1)candidates. The default of 6 reproduces the old default exactly.A warning when path generation is about to be slow. Cost grows exponentially in the number of precision points, and nothing said so. Five points reach a four-dimensional grid that can run for seconds and still return nothing. Calls whose grid exceeds a thousand candidates now warn up front, and the message is repeated in
SynthesisResult.warnings.Ensemble.to_pareto_front()— the members as aParetoFront, one objective per score column, for itsbest_compromise(),filter(),hypervolume()andplot(). The inverse ofEnsemble.from_pareto_front().PointTrackerin the numba solver. It hasFixedDyadgeometry and now gets the same kernel; before,step_fast()silently froze a tracker at its initial position and returned that as its trajectory.Slider rails in every drawing backend.
pylinkage.visualizer.coregainsget_rail_pairs()/build_rails(), andget_parent_pairs()knowsRRPDyad.revolute_anchor, so a slider-crank draws its connecting rod and the line it slides on in matplotlib, SVG, DXF and STEP alike.pylinkage.mechanism.TrackerJointandArcDriverLinkare exported. Both leggedsnake and the editor’s backend were importing them from themechanism.jointandmechanism.linkmodules because the package did not offer them.pylinkage.dimensionsandpylinkage.exceptionshave an__all__(Dimensions,DriverAngle; the four exception classes).pylinkage.synthesis.BurmesterDyad, the new name for what waspylinkage.synthesis.Dyad. Same class, same behaviour; the old name still works and now warns.
Changed
54 implementation names left the
__all__of their packages. They stay importable exactly as before; they are no longer advertised as public andimport *no longer pulls them in. Among them: the numba kernels andstep_single*helpers insolver(simulate,simulate_with_kinematics,SolverData, theJOINT_*constants and the conversion functions remain public);AnyJoint,AnyLink, the per-entityjoint_to_dict/link_from_dicthalves ofmechanism_to_dictandis_legacy_formatinmechanism; thePoint2D/ComplexPoint/AnglePairaliases,point_to_complex,complex_to_pointandcompute_metricsinsynthesis;SymCoordinsymbolic; the styling constants andLinkStyle/SymbolTypeinvisualizer;DYAD_TYPES,identify_dyad_type,identify_group_typeinassur;DEFAULT_ANGULAR_VELOCITYinactuators;MutableAgentinoptimization.collections;_AnchorProxyincomponentsanddyads.path_generation()is 2-8x faster, with byte-identical results. Verifying a candidate by simulating it was 67% of the runtime, and ran through the pure-PythonLinkage.step(); it now uses the numba solver, which executes the samesolve_*functions roughly twenty times faster. Measured end to end:Precision points
Before
After
README example
345 ms
163 ms
Benchmarked example
1689 ms
678 ms
Three points
430 ms
55 ms
The two paths report an unassemblable mechanism differently —
step()raises, the solver writesNaN— so a naive switch would silently change which candidates survive. Both are treated as rejection, and the solution sets are verified identical across six point sets.step()is still used when numba is absent or the mechanism contains a component the solver cannot represent. This became possible only oncestep_fast()stopped returningNaNforFixedDyad, the component carrying the coupler point.Linkage.step()is about a quarter faster — 257,550 to 325,718 steps/s on the reference four-bar — which speeds up every simulation, not only synthesis. Two things in the innermost loop:step()re-ranisinstance(component, (Crank, ArcCrank, LinearActuator))for every component on every iteration although the classification is a property of the solve order, and_get_anchor_positionran anisinstancecheck against an abstract base class whose two branches returned the same expression. The publishedstep_fast()speedup falls from 6.4x to 5.1x as a result: the numerator got faster, the solver did not regress.
Deprecated
The sibling re-exports in
pylinkage.dyads:Ground,PointTracker,Component,ConnectedComponent,Crank,ArcCrank,LinearActuatorandLinkage. Each still resolves to the same object and warns, naming its home (pylinkage.components,pylinkage.actuators,pylinkage.simulation).pylinkage.dyads.Groundandpylinkage.components.Groundwere both correct, which is why tutorials disagreed on which to write.pylinkage.optimization.Ensemble, in favour ofpylinkage.population.Ensemble, its home.pylinkage.assur.MobilityResultandStructuralAnalysis. Nothing in the package produced them; they were dataclasses exported without a function to fill them. The mobility analysis that exists ispylinkage.topology.compute_mobility(), returning aMobilityInfo.n_orientation_samples, which never denoted a number of samples. It was folded into a per-axis grid resolution throughmax(6, round(n_samples ** (1 / free))), so the floor swallowed it: with four precision points every value from 6 to 216 produced an identical search. Measured on the README’s points, 6, 12, 36 and 72 all took the same time and returned the same ten solutions. It now emits aDeprecationWarning, is translated through the old formula so no result moves, and is scheduled for removal in 2.0.0. Useorientation_resolution.Redefining it to mean what its name says — roughly that many candidates in total — was implemented, measured and rejected: on three of six test point sets the search then returned nothing where it had found ten. Reordering the grid coarse-to-fine or shuffling it was also measured, in case truncation was merely sampling a biased corner; neither changed the solutions found nor the time taken. The dense grid earns its cost, so the search is unchanged and only the name was wrong.
The three
Dyadnames that were not what they said. Three unrelated classes were reachable asDyad, and only two were dyads at all:pylinkage.assur.Dyad(an Assur group, unaffected and keeping its name),pylinkage.synthesis.Dyad(a Burmester dyad, nowBurmesterDyad), andpylinkage.components.Dyad/pylinkage.dyads.Dyadwith theirConnectedDyadcounterparts, which were plain aliases ofComponentandConnectedComponent— aGroundpoint is aComponent, and calling it a dyad is simply wrong. All four aliases now emit aDeprecationWarningand are scheduled for removal in 2.0.0. Nothing breaks: each name still resolves to the same object it always did, and stays in its module’s__all__.The concrete cost of the collision was that Sphinx could not tell the three apart, so cross-references landed on the wrong class. With the aliases deprecated and the rename in place, the documentation build goes from 8 warnings to 0.
Removed
The
api/directory, the[api]extra andtask api. The FastAPI server was the backend of pylinkage-editor and now lives there, inserver/, with pylinkage as a dependency. It was never part of the wheel:pip install pylinkage[api]installed fastapi and uvicorn for code that was not there, and anyone setting up the editor had to find the server by reading this repository’s source (#46). If you were running it from a checkout,cd pylinkage-editor/server && uv run pylinkage-editor-serverreplacesuv run task api.
Fixed
Optimizers accept every linkage again.
Ensemblecompiled its numba solver template on construction, and since the solver started refusing dyads it cannot represent, every optimizer that returns anEnsemble(particle_swarm_optimization,multi_objective_optimization, …) raisedNotImplementedErrorfor a linkage holding aPPDyad, a cam follower, or leggedsnake’sWalker— none of which the optimizer itself needed the solver for. The template is now compiled on first use (Ensemble.template,simulate(),topology_key), so those linkages optimize as they did in 1.1.1 and the clear error only appears where batch simulation is asked for.step_fast()silently returned NaN for every dyad exceptRRRDyad.linkage_to_solver_data()typed every dyad asJOINT_REVOLUTEand readdistance1/distance2off it. OnlyRRRDyadhas those attributes, so aFixedDyad— which hasdistanceandangle— reached the solver as two zero-radius circles that never intersect, and the joint plus everything downstream of it came outNaN. No exception was raised: callers got a full-shaped array ofNaNwhere positions should be, whilestep()computed the same mechanism correctly. Since aFixedDyadis how a traced point is rigidly attached to a coupler link, this covered most mechanisms worth simulating quickly, including everythingpylinkage.synthesisemits. The conversion now dispatches on the actual dyad class:JOINT_FIXEDwith(distance, angle)forFixedDyad,JOINT_PRISMATICwith three parents forRRPDyad,JOINT_REVOLUTEonly forRRRDyad. Both were already implemented insolver/simulation.pyand simply unreachable.PPDyadand the cam followers, which the solver cannot represent, now raiseNotImplementedErrornamingstep()as the alternative rather than producing a wrong answer.update_solver_constraints()shares the same table, so the two cannot drift.step()andstep_fast()now agree bit for bit on all three supported dyad types, and are tested to.step_fast()refuses a component it cannot represent — a customComponent, say — withNotImplementedErrornaming it, instead of typing it as a fixed point and reporting a frozen trajectory.The SVG, DXF and STEP exporters draw the component API. All three walked the legacy
linkage.joints/joint0/joint1attributes, soplot_linkage_svg()produced joints and trajectories but no bars for asimulation.Linkage, andplot_linkage_dxf()/build_linkage_3d()raisedAttributeError. They now sharebuild_connections()with the matplotlib backend.show_kinematics(),animate_kinematics()andplot_linkage_plotly_with_velocity()work on asimulation.Linkage. Same legacy attributes, plus anomegacheck that never saw the valueset_input_velocity()stores; their tests wrapped the linkage in an adapter that mimicked the old API and passed around the bug.Ensemble.show(),plot_plotly()andsave_svg()no longer require a priorsimulate(). They simulated the member on the spot and then looked for the trajectory in the batch cache, which the one-off simulation does not fill, and raisedValueError.parallel_coordinates_plot()with unbuildable particles. A score of±inf(thekinematic_*decorators’ penalty) made the score axis NaN; such particles now sit at the bad end of the axis.pip install pylinkageworks on Python 3.10.mechanism.builderimportsSelffromtyping_extensionsbelow 3.11 but the package never declared it, so a fresh 3.10 environment failed atimport pylinkage.The tutorials run. Ten of the eleven pages under
docs/source/tutorials/failed on 1.1.1:pylinkage.joints, keyword arguments that never existed (show_linkage(animated=…),generate_bounds(max_ratio=…)),len(result)on aSynthesisResult, tuple-unpacking anEnsemble, an invented hypergraph API, change-point four-bars that no perturbation survives.custom_joints,graph_representationandvisualizationare rewritten against the current API; the others are corrected in place, with expected outputs regenerated. Every code block of every tutorial now executes in order withDeprecationWarningas an error.The example scripts run again. The three synthesis demos still called
len(result)andif result:on aSynthesisResult, which lost those since 1.0; every demo animated whatever came out of synthesis with a full-turnCrank, which stops on a double-rocker; and the Grashof-type table infourbar_from_lengths_demo.pylabelled two change-point linkages as double-crank and double-rocker. The demos now animate throughcrank_angle_limits()+arc_limits, the table’s lengths match their labels, and three demos that always came back empty — five-position approximate function generation, the straight-line path (three collinear points are degenerate for Burmester theory), and the two fixed-ground-pivot demos (arbitrary frame points never lie on the center-point curve) — now show the workflow that does produce a linkage. All twelve scripts underdocs/examples/run to completion withDeprecationWarningas an error.The benchmarks page figures are regenerated against the above, and its breakdown of
path_generation()is rewritten: Burmester synthesis over the orientation grid is now 87% of the runtime and verification under 10%, the reverse of what the page described. It also now warns that cost grows exponentially in the number of precision points — five points can take seconds and return nothing — which no documentation said.The benchmarks page gave wrong advice about
path_generation(). It attributed the cost to the orientation sweep and said loweringn_orientation_samplestrades coverage for time roughly linearly. Profiling shows neither is true: verifying candidates by simulation is 67% of the runtime and Burmester synthesis only 21%, andn_orientation_samplesis close to inert — the values 6, 12, 36 and 72 take the same time and return the same ten solutions on the README’s points, because the per-axis grid resolution it feeds is floored at 6. The real control ismax_solutions, measured from 124 ms at 1 solution to 2316 ms for all 79. The page and thepath_generation()docstring now say so, and the page notes that cost depends heavily on which points are asked for: two four-point problems returning ten solutions each measured 336 ms and 1676 ms. Tracked in #29.The cited DOI pointed at 1.0.0 and would have gone stale every release.
CITATION.cffand the README badge carried10.5281/zenodo.21207487, the version DOI Zenodo minted for 1.0.0. Zenodo also mints a concept DOI per project —10.5281/zenodo.21207486here — which never changes and always resolves to the newest version. Both now cite that instead, so neither needs touching at release time; the 1.0.0 version DOI is kept as a second identifier, since anything already citing it should keep resolving.CITATION.cffwas bumped by hand. Itsversionanddate-releasedwere not in the bump-my-version file list, so every release depended on remembering to edit them. They are now bumped automatically, the date from{now}.CONTRIBUTING.md’s release section is corrected to match, and gains theuv lockstep it was missing —uv.lockrecords the project version and bump-my-version does not touch it.
[1.1.1] - 2026-09-04
Added
Public benchmarks.
docs/source/benchmarks.mdreports figures for the numba solver, PSO throughput, and the three synthesis entry points, together with the hardware they came from and the command that reproduces them. The harness behind it isbenchmarks/run_benchmarks.py, which measures the public API only, takes the median of repeated runs, and discards warmup so numba’s one-off JIT compilation is not charged to steady-state figures. The pre-existing scripts inbenchmarks/are now marked as development explorations, since two of them benchmark reimplementations and a hypothetical joint rather than shipped code.pylinkage.dyads.to_mechanism()is now public. It converts a componentLinkageinto aMechanism, and previously existed only aspylinkage.dyads._conversion.to_mechanism, so it could not be used or documented without reaching into a private module. The conversion remains one-way: there is noMechanismtoLinkagedirection, the oldmechanism_to_linkage()having been removed with the legacy joints module.
Changed
One docstring style across the codebase. Docstrings were split between two conventions: 85 files used Google style (
Args:) while 16 used reST field lists (:param:), andlinkage/transmission.pyused both at once. The 16 reST files — concentrated insymbolic/,optimization/andgeometry/, the oldest code in the package — are converted to Google style, so all 100 documented modules now read the same way. This is presentation only: 393 fields were rewritten with no change to wording, and the sole vocabulary removed across all 16 files is theparam/returns/raisesmarkers themselves. Sphinx cross-reference roles such as:func:`max`are untouched, being valid in Google-style docstrings.The
fullextra now includespymoo, sopip install pylinkage[full]covers multi-objective optimization as the README’s “all optional backends” description promises.mooremains available on its own, and is now listed in the README’s extras table.
Fixed
Documentation links that pointed at the wrong class.
pylinkage.dyadsexportsDyadas a plain alias ofComponent(andConnectedDyadofConnectedComponent), and the package annotated its anchors with that alias —revolute_anchor: Dyad | _AnchorProxy. Two unrelated classes are also namedDyad(pylinkage.assur.Dyad, an Assur group, andpylinkage.synthesis.Dyad, a Burmester construct), so Sphinx resolved those annotations to one of them and sent readers to a class the code never referred to. The annotations now nameComponentdirectly. TheDyadandConnectedDyadaliases remain exported and unchanged, so no import breaks. Docs build warnings drop from 38 to 8.Broken README links, on three surfaces at once. The README linked to the 15 tutorial notebooks,
CONTRIBUTING.mdandCODE_OF_CONDUCT.mdwith repository-relative paths. Those resolve only on GitHub: on the PyPI landing page and in the rendered documentation they were dead. They are now absolute GitHub URLs, matching how the README already linked its images. The in-page[tutorials](#tutorials)link is fixed by enablingmyst_heading_anchors, so heading anchors resolve in the docs as they do on GitHub.The README was rendered twice in the documentation.
index.rstlisted it in the toctree and pulled it in again with.. include::, so the front page duplicated the entire README and every warning it produced was counted twice. The inline include is removed; the README remains as its own page, linked first under Introduction. Docs build warnings drop from 76 to 38.Docstrings rendered wrongly in the API reference. The codebase writes Google-style docstrings (
Args:,Returns:,Raises:), but the docs never enabledsphinx.ext.napoleon, so every one of them was parsed as a definition list: parameter descriptions were swallowed as stray indentation, and*args/**kwargswere read as emphasis markup. Enabling napoleon renders them as proper parameter tables.napoleon_use_ivaris set alongside it so a documented attribute does not collide with the entry autodoc already generates for the same dataclass field, and package-levelautomoduledirectives are marked:no-index:so each object is indexed once rather than once per re-export. Nine genuinely malformed docstrings were fixed by hand: bullet lists and formula blocks missing their preceding blank line, isomer signatures such asRRT_that RST read as link targets, and an emptyConversion:section inpylinkage.mechanismleft behind when the mechanism/linkage conversion helpers were removed. Docs build warnings drop from 496 to 76, with none remaining from docstrings.Stale API reference.
docs/source/api/was checked-insphinx-apidocoutput that had not been regenerated since before 1.0. It documented three modules that no longer exist — the legacypylinkage.jointspackage removed with the joints API, pluspylinkage.collectionsandpylinkage.linkage.linkage— which failed to import on every docs build. More importantly it covered only 6 of the package’s 18 subpackages, sosynthesis,mechanism,hypergraph,cam,symbolic,solver,components,actuators,dyads,simulation,assur,bridge,populationandtopologyhad no API reference at all. Regenerated against the current package.Type information was not exposed to users. The package is fully annotated and passes
mypy --stricton all 133 source files, but shipped no PEP 561py.typedmarker, so type checkers and editors in downstream projects silently ignored every annotation and treatedpylinkageas untyped. The marker is now included in the wheel; no packaging change was needed, since[tool.hatch.build.targets.wheel]already covers the whole package directory.The front-page example did not say what to install. The first snippet in the README — the one PyPI shows above the fold, and the one reported in #25 — calls
path_generation()andshow_linkage(), which live behind thescipyandvizextras. Every other example needing an extra said so; this one did not, so after a plainpip install pylinkageit raisedModuleNotFoundErrorbefore reaching any pylinkage code. It now names the install line it needs.Broken examples on the PyPI landing page. Both README visualization snippets called
plot_kinematic_linkage(linkage), but that function takes(linkage, fig, axis, loci)and always has — the signature is identical in 1.0.0, so these examples never ran. They now callshow_linkage(), which builds the figure and runs the simulation itself. The PSO snippet separately usedget_num_constraints()/set_num_constraints(), removed earlier in the 1.x line; it now usesget_constraints()/set_constraints().Tutorial notebooks failing to execute. Seven of the fifteen notebooks raised on execution, against removed API:
get_num_constraints()/set_num_constraints()(notebooks 02, 03, 09, 10, 11, 13),SymbolicLinkage.componentsin place of.joints(05), andmechanism_to_linkage()(10). Notebook 10 additionally described the component/dyadLinkageas a “legacy” API throughout; the legacy API waspylinkage.joints, which no longer exists. ANotebooksCI workflow now executes every notebook on push and pull request.
[1.1.0] - 2026-08-12
Fixed
Velocities and accelerations no longer treat an unknown anchor as a stationary one.
Mechanism.step_with_derivativesread each anchor’s kinematics asanchor.velocity or (0.0, 0.0). A joint solved against an anchor whose own velocity was undefined was therefore solved against one wrongly assumed at rest, and returned a plausible number instead ofNone— so a single unresolved joint quietly corrupted everything downstream of it rather than announcing itself. Checked against finite differences of the position stream, one leggedsnake walker had a foot reporting ~5x its true speed and another ~0.3x. Unknown kinematics now propagate: if an anchor’s velocity or acceleration is undefined, so is the dependent joint’s.Ground joints are unaffected — their zero velocity is a fact about the frame, not a stand-in for a missing value.
A joint collinear with its two anchors is no longer mistaken for a dead centre.
solve_revolute_velocityintersects the two differentiated distance constraints, whose Jacobian is singular when the joint lies on the line through its anchors. That test conflated two unrelated situations: a genuine toggle of an RRR dyad, where the velocity really is indeterminate, and a joint riding on a rigid body together with both its anchors, where the motion is fully determined and only the formulation degenerates. The second is a normal design — a ternary link carrying its coupler point on the line through its other two ports, as in the Chebyshev and Hoeken straight-line linkages — and it silently produced no derivative at all.Such joints are now resolved by propagating the body’s motion. The two cases are told apart structurally rather than numerically: the fallback applies only when a link holds the two anchors at a fixed distance, so a real dead centre still yields
None.
Added
solve_rigid_body_velocity/solve_rigid_body_accelerationinpylinkage.solver. Given two points of a planar rigid body and their kinematics, these return the velocity and acceleration of any third point on that body. They agree with the constraint-intersection solvers wherever both apply, and stay well-conditioned for collinear points, where those are singular — they need two distinct points on the body rather than two independent constraint directions.
[1.0.0] - 2026-07-05
Added
multi_objective_optimizationparallel evaluation. Newn_workersandlinkage_factorykeyword arguments route candidate evaluation through aconcurrent.futures.ProcessPoolExecutorwhenn_workers > 1.linkage_factoryis an escape hatch for linkages that are not picklable (e.g. carry cached numbaSolverData): each worker builds its own linkage via the factory instead of receiving a pickled copy. This unblocks downstream packages (notablyleggedsnake) dropping their custom parallel NSGA problem wrappers.simulation.Linkage.to_hypergraph()(+ the module-level :func:pylinkage.hypergraph.from_sim_linkage). Converts a modernsimulation.Linkage(the object produced by synthesis andco_optimize) into a(HypergraphLinkage, Dimensions)pair so downstream hypergraph-native consumers (notablyleggedsnake.Walker.from_synthesis) can ingest synthesis output directly without a local shim. CoversGround,Crank,ArcCrank,LinearActuator,RRRDyad,FixedDyad,RRPDyadandPPDyad; unknown component types raiseNotImplementedErrorinstead of silently dropping topology.Dimensions.to_dict/Dimensions.from_dict(and matchingDriverAngle.to_dict/DriverAngle.from_dict). Returns a JSON-safe representation so downstream consumers (notablyleggedsnake.serialization) can drop their manual hyperedge-key stringification helpers. Hyperedge pairwise constraints are emitted as[node_a, node_b, distance]triples;from_dictalso accepts the legacy"('a', 'b')"stringified-tuple form for back-compat with previously saved files.Modern-container parity with the legacy
Linkage. Bothpylinkage.simulation.Linkageandpylinkage.mechanism.Mechanismnow carry the full kinematic and analysis surface the legacy class used to expose:compile()+step_fast()— pre-compile the numba SolverData once and reuse it across calls (viapylinkage.bridge).step_fast_with_kinematics()— batched numba simulation returning(positions, velocities, accelerations)trajectory arrays.set_input_velocity(driver_or_crank, omega, alpha=0.0)+get_velocities()/get_accelerations()— per-joint velocity/acceleration accessors.step_with_derivatives()— per-step Python path that computes velocities and accelerations viasolver.velocity/solver.acceleration.analyze_transmission()/analyze_stroke()/analyze_sensitivity()/analyze_tolerance()— bound-method shims over the free functions inpylinkage.linkage.transmission_angle()/stroke_position()— single-shot accessors at the current pose.set_completely(constraints, positions)— apply a flat constraint vector and joint positions in one call.simulation(iterations=None, dt=1.0)— context manager that restores the initial joint positions on exit (new helper inpylinkage._simulation_context.Simulation).indeterminacy()— planar Gruebler-Kutzbach mobility (a standard Grashof four-bar returns1).
Mechanismcross-API aliases.get_coords/set_coordsnow work on aMechanismas well, delegating to the nativeget_joint_positions/set_joint_positions.Mechanism.rebuild(initial_positions=None)— matchessimulation.Linkage.rebuild. Optionally writes new joint positions and always clears the cached SolverData so the nextstep_fast()recompiles.Mechanism.step(iterations=None, dt=1.0)— accepts aniterationskeyword (matching both legacy and modern Linkage).Joint.velocity/Joint.acceleration(runtime state,compare=False) on allMechanismjoints — populated bystep_with_derivativesandstep_fast_with_kinematics.
Changed
LinkageProblemreuses one process pool across generations.LinkageProblemnow creates theProcessPoolExecutorlazily on the first parallel batch and reuses it for every subsequent batch.multi_objective_optimizationcallsproblem.close()in afinallyblock so workers don’t outlive the optimization. The previous code forked N workers per generation, taxing every batch with ~50–500 ms of pool startup (heavier still when worker imports are large). Apples-to-apples savings scale with generation count; expect 15–25 % wall-time wins atn_workers ≥ 4on multi-gen runs. Behaviour is unchanged forn_workers == 1.pylinkage._compatnow targets only the modern surface. The joint-legacy branches (Static/_StaticBase/Revolute/Pivot/Fixed/Prismatic/Linearname matches) were dead after phase 2c;is_ground/is_dyadnow only recognise the modern component classes and the Mechanism joint types.pylinkage._compat:is_groundnow recognises a MechanismGroundJoint;is_driverrecognises aRevoluteJointthat sits as the output of aDriverLink/ArcDriverLink;is_dyadrecognises a non-ground, non-driverRevoluteJoint/PrismaticJoint. The container-agnostic analysis helpers inpylinkage.linkage.*now auto-detect four-bar joints on aMechanismwithout additional hints.pylinkage.bridge.solver_conversion.linkage_to_solver_datagains a_mechanism_to_solver_datadispatch path.Mechanism’s Links-on-constraints data model is now translated toSolverDatafor numba simulation (driver outputs becomeJOINT_CRANKwith radius/angular-velocity pulled from the owningDriverLink; driven revolute joints becomeJOINT_REVOLUTEwith anchor distances walked from the joint’s_links).pylinkage.bridge.solver_conversion: collapsed to a single compatibility-agnostic implementation that dispatches throughpylinkage._compat— no more legacy joint-type dispatch.pylinkage.synthesis.nbar_solution_to_linkage/_generic_nbar_to_linkage: now build a modernpylinkage.simulation.Linkagefrom the component/actuator/dyad API instead of a legacy joint-basedLinkage.pylinkage.synthesis.linkage_to_synthesis_params: accepts the component API only; raisesValueErrorfor legacy linkages.
Removed
pylinkage.hypergraph._typesre-export module (deprecated since 0.8.0). Import the canonical types frompylinkage._types(JointType,NodeRole,NodeId, …) directly. All internal callers have been migrated.pylinkage.solver.JOINT_LINEARconstant. This was always an alias forJOINT_PRISMATIC = 4; the duplicate name has been removed. UseJOINT_PRISMATIC."Linear"entry inpylinkage.visualizer.SYMBOL_SPECSand the matching"Linear"branch in the auto-detect path insidepylinkage.linkage.transmission. These matched the legacyjoints.Linearclass name, which is gone. Modern prismatic components are resolved through"Prismatic"/"RRPDyad"/"LinearActuator".get_num_constraints/set_num_constraintson bothsimulation.LinkageandMechanism. These deprecated wrappers were added to ease the rename toget_constraints/set_constraintsand are now gone. Calling them raisesAttributeError.pylinkage.linkage.Linkageandpylinkage.linkage.Simulation: the legacyLinkageclass is gone.pl.Linkagenow points at :class:pylinkage.simulation.Linkage(component/actuator/dyad API);pl.Simulationpoints at the shared :class:pylinkage._simulation_context.Simulationcontext manager. Internal TYPE_CHECKING imports that referencedpylinkage.linkage.Linkagehave been repointed topylinkage.simulation.Linkage. User code that built linkages viapl.Linkage(joints=[...])must migrate to the component API — see the migration notes in the 1.0.0 notebooks and tutorials.pylinkage.jointsmodule (legacy joint API —Static,Crank,Revolute,Pivot,Fixed,Prismatic,Joint). Deprecated since 0.7.0 (Pivot since 0.6.0). Use the component/actuator/dyad API:pylinkage.components.Ground,pylinkage.actuators.Crank,pylinkage.dyads.RRRDyad/FixedDyad/RRPDyad. Top-level re-exports (pl.Static,pl.Crank, …) are gone.pylinkage.linkage.Linkage.to_dict/from_dict/to_json/from_json: serialization of legacy joint-based linkages is no longer supported. Usepylinkage.mechanism.mechanism_to_dict/from_dicton aMechanisminstead.pylinkage.linkage.serialization: module removed — served legacy joints only.pylinkage.mechanism.mechanism_from_linkage/mechanism_to_linkage/convert_legacy_dict: bridged the legacyLinkage↔Mechanismmodels, neither of which needs the bridge now that the legacy joint API is gone. Usepylinkage.mechanism.fourbarand friends to build aMechanismdirectly.pylinkage.hypergraph.from_linkageandpylinkage.assur.linkage_to_graph: same rationale as the legacyto_linkage()/graph_to_linkage()removed in 0.9.0. Usefrom_mechanism/mechanism_to_graphrespectively.pylinkage.symbolic.linkage_to_symbolic/symbolic_to_linkage: removed. BuildSymbolicLinkagedirectly with :class:SymCrank/ :class:SymRevolute/ :class:SymStatic, or use :func:fourbar_symbolic.pylinkage.optimization.grid_search.tqdm_verbosity(): overdue since 0.7.0. Usetqdm.tqdm(iterable, disable=not verbose)directly.SynthesisResult.__len__/__iter__/__getitem__/__bool__: deprecated in 0.9.0. Accessresult.solutions(orresult.ensemblefor batch operations) instead — e.g.len(result.solutions),for linkage in result.solutions,result.solutions[i].pylinkage.hypergraph.to_linkage(): deprecated in 0.8.0. Usepylinkage.hypergraph.to_mechanism()for conversion to the currentMechanismmodel.pylinkage.assur.graph_to_linkage(): deprecated in 0.8.0. Usepylinkage.assur.graph_to_mechanism()for conversion to the currentMechanismmodel.
Fixed
multi_objective_optimizationsingle-objective runs. pymoo returns the single best solution withFshape(n_obj,)andXshape(n_var,)whenn_obj == 1, not(n_pop, n_obj). The previous code indexedresult.F[:, k]unconditionally and crashed. Results are now normalised to 2-D before building the Ensemble son_obj == 1works uniformly.Solver-cache invalidation on constraint mutation.
simulation.Linkage.set_num_constraintsandMechanism.set_constraintsnow clear_solver_databefore applying the new constraints, so a subsequentstep_fast()rebuilds the numba arrays. Without this, optimizers that round-tripped candidate constraints would silently keep simulating the previous parameters.
[0.9.0] - 2026-04-14
Added
extract_trajectory(loci, joint=-1)inpylinkage.linkage.analysis(re-exported frompylinkage): returns(xs, ys)numpy arrays for one joint’s path, skipping unbuildable frames. Replaces the recurring[(p[i][0], p[i][1]) for p in loci if p[i][0] is not None]boilerplate. Accepts integer index, joint name, or joint instance (whenlinkageis given).extract_trajectories(loci, linkage=None): all-joints variant of the above. Returns{joint_name: (xs, ys)}whenlinkageis given, or{index: (xs, ys)}otherwise. SkipsNoneframes per joint.pylinkage.mechanism.fourbar(crank, coupler, rocker, ground, ...)andslider_crank(crank, rod, ...)factory functions: collapse the eight-lineMechanismBuilderchain for the canonical four-bar and slider-crank topologies into a single call, returning an assembledMechanism.TransmissionAngleAnalysis.plot(ax=None): one-line replacement for the matplotlib boilerplate (axhlineat the acceptable-range bounds, the 90° optimum, fixed[0, 180]y-axis, crank-angle x-axis). Accepts an existing axes for use inside subplot grids.Population abstractions for batch mechanism work (
pylinkage.population):Member: universal single-mechanism record (dimensions, scores, trajectory).to_loci()converts trajectories to the tuple format the visualizer expects.Ensemble: topology-bound population — one linkage structure with N parameter variants. Batch simulation via the numba solver, numpy-style indexing (ens[i]→ Member,ens[1:3]→ Ensemble), columnar scores for vectorizedrank(),top(),filter(),filter_by_score(). Visualization shortcuts:show(),plot_plotly(),save_svg().Population: heterogeneous collection of Ensembles, keyed by topology label.simulate_all(),rank(),top()across topologies.from_members()auto-groups by topology key.from_topology_solutions()wraps multi-topology synthesis results withQualityMetricsas score columns.SynthesisResult.ensembleproperty: lazily builds an Ensemble from synthesis solutions with link lengths as score columns.
skip_unbuildablemode forLinkage.step(): new boolean parameter that catchesUnbuildableErrorper iteration and yieldsNone-coordinate tuples instead of aborting the entire simulation. Non-Grashof and double-rocker linkages now recover the valid trajectory on both sides of dead zones.Dual Annealing optimizer:
dual_annealing_optimization()wraps scipy’s generalized simulated annealing — a single-trajectory global optimizer effective for problems with many local minima and expensive evaluations.Optimizer chaining:
chain_optimizers()runs multiple optimizers in sequence, automatically feeding each result as the starting point for the next stage. Common pattern: global search (DE/PSO) → local refinement (Nelder-Mead).Co-optimization of topology + dimensions:
Mixed-variable evolutionary optimizer (
co_optimize()) jointly searching discrete topology space and continuous dimensional space using NSGA-II/III via pymoo with custom genetic operators.Topology neighborhood graph (
build_neighborhood_graph(),topology_neighbors(),topology_distance()) defining adjacency between all 19 catalog topologies via add_dyad, remove_dyad, swap_variant, and restructure operations.Custom pymoo operators:
MixedCrossover(BLX-alpha blend + topology swap),MixedMutation(Gaussian perturbation + topology neighbor mutation),warm_start_sampling()(seed population from Phase 3 synthesis results).Virtual edge encoding: expands hyperedges (ternary links) into pairwise distances and adds implicit ground-link virtual edges for chromosome representation.
Simultaneous triad placement via
scipy.optimize.least_squaresfor topologies with circular dependencies (e.g., Stephenson six-bar).Warm-start pipeline:
warm_start_co_optimization()converts Phase 3TopologySolutionresults toMixedChromosomeseeds for NSGA-II.New types:
MixedChromosome,CoOptimizationConfig,CoOptSolution,CoOptimizationResult.TopologyCatalog.topology_index()andtopology_by_index()for integer-indexed topology lookup.
Triad solving in mechanism simulation:
Mechanism.step()now uses Assur group decomposition internally, solving dyads and triads viasolve_group()dispatch. Six-bar linkages (Watt and Stephenson types) can be simulated end-to-end.graph_to_mechanism()handles triad groups (2 internal nodes, 4+ edges), creating the appropriate joints and links.Convenience builders for six-bar linkages:
watt_from_lengths(): build a Watt six-bar from seven link lengths + ground length. Returns aSimLinkageready for simulation.stephenson_from_lengths(): build a Stephenson six-bar from the same parameter pattern. Both include ASCII kinematic chain diagrams in docstrings.Exported from
pylinkage.synthesisalongsidefourbar_from_lengths().
Topology enumeration:
Graph isomorphism detection via WL-1 color refinement + backtracking verification:
canonical_form(),canonical_hash(),are_isomorphic().Systematic enumeration of all non-isomorphic 1-DOF planar linkage topologies up to 8 links:
enumerate_topologies(),enumerate_all(). Validated against Mruthyunjaya 1984: 1 four-bar + 2 six-bars + 16 eight-bars = 19.Built-in topology catalog (
TopologyCatalog,CatalogEntry,load_catalog()) with JSON-serializedHypergraphLinkagegraphs and metadata (link assortment, family, joint count).All new symbols exported from
pylinkage.topology.
Fixed
compute_dofhyperedge counting:compute_mobility()counted each hyperedge with k nodes as (k−1) links instead of 1 rigid body, giving wrong DOF for any mechanism with ternary or higher links (all six-bars and eight-bars).
Deprecated
SynthesisResultcollection protocol:len(result),result[i],for linkage in result, andbool(result)now emitDeprecationWarning. Useresult.ensembleinstead. Will be removed in 1.0.0.
Changed
fourbar_from_lengths()now returnsSimLinkage(frompylinkage.simulation) instead of the legacyLinkage(frompylinkage.linkage). The new object uses the component/actuator/dyad API: access joints via.componentsinstead of.joints. The.step()method is unchanged.linkage_to_synthesis_params()accepts both old and new linkage types.All optimization functions now return
Ensembleinstead oflist[Agent],list[MutableAgent], orParetoFront. Affected functions:particle_swarm_optimization(),trials_and_errors_optimization(),differential_evolution_optimization(),dual_annealing_optimization(),minimize_linkage(),chain_optimizers(),multi_objective_optimization(), and all async variants. Migration: replacescore, dims, pos = result[0](Agent tuple unpacking) withmember = result[0]; member.score.Default simulation resolution increased from ~63 to 360 steps per rotation: The default angular velocity for
Crank,ArcCrank,DriverLink, andArcDriverLinkchanged from0.1rad/step totau / 360(~0.01745 rad/step), giving one sample per degree. TheMechanism.get_rotation_period()fallback and all synthesis/visualizer iteration defaults changed from100to360accordingly. ADEFAULT_ANGULAR_VELOCITYconstant is now exported frompylinkage.actuators.PSO is now pure NumPy:
particle_swarm_optimization()no longer depends on pyswarms (unmaintained since 2021). Replaced with a built-in local-best ring-topology PSO. Thepsooptional extra is kept but empty for backwards compatibility. The API is unchanged.Renamed abbreviated parameters for clarity (#17): All old names are still accepted as keyword arguments for backwards compatibility.
iters→iterationsinparticle_swarm_optimization()and its async variant.pos→initial_positionsinLinkage.rebuild().init_positions→initial_positionsinAgent,MutableAgent, andParetoSolution.
Removed
HypostaticErroralias removed. UseUnderconstrainedErrordirectly (alias was deprecated since 0.7.0).Linearjoint alias removed. UsePrismaticdirectly (alias was deprecated since 0.7.0).pyswarms dependency removed from all extras (
pso,full, dev group).
[0.8.0] - 2026-03-28
Added
Multi-objective optimization:
New
multi_objective_optimization()function using NSGA-II/NSGA-III algorithms via pymoo.ParetoFrontclass for storing and analyzing non-dominated solutions.ParetoSolutiondataclass for individual Pareto-optimal solutions.Pareto front visualization with
pareto.plot().Hypervolume indicator computation with
pareto.hypervolume().Best compromise solution selection with
pareto.best_compromise().Crowding distance-based filtering with
pareto.filter().New optional dependency group:
pip install pylinkage[moo].
Cam-follower mechanisms:
New
pylinkage.cammodule with motion laws and profile definitions.Motion laws:
HarmonicMotionLaw,CycloidalMotionLaw,ModifiedTrapezoidalMotionLaw,PolynomialMotionLaw(withpolynomial_345()andpolynomial_4567()factory functions).Profile types:
FunctionProfile(motion law-based) andPointArrayProfile(spline interpolation).TranslatingCamFollowerdyad for linear follower motion driven by cam rotation.OscillatingCamFollowerdyad for rocker arm motion driven by cam rotation.Both knife-edge (roller_radius=0) and roller followers supported.
Numba-compiled profile evaluation for high-performance simulation.
Triad (Class II) Assur groups:
New
DyadandTriadclasses parameterized by signature string, replacing the per-type classes (DyadRRR,DyadRRP, etc.) which are kept as aliases.signature_to_hypergraph()now generates triad topologies (6-joint signatures).decompose_assur_groups()detects triads when no dyad can be formed, enabling decomposition of six-bar mechanisms (Watt and Stephenson types).Solver dispatches by
solver_category(circle-circle, circle-line, line-line) instead ofisinstance(), automatically supporting new group signatures.
Topology analysis:
New
pylinkage.topologymodule withcompute_dof()implementing Grübler’s formula (DOF = 3(n−1) − 2j₁ − j₂) onHypergraphLinkage.compute_mobility()returns fullMobilityInfo(DOF, link count, joint counts).
SymPy for analytical optimization.
Native computation of velocity and acceleration with visualizations.
Linkage synthesis with Burgmester’s theory, function, path and motion generation.
Adds scipy.
Exact optimization solving (better than numpy) + support constraints.
Adds a new optimization: differential evolution.
High-level velocity/acceleration API:
Component.velocityandComponent.accelerationproperties on all components.simulation.Linkage.set_input_velocity(actuator, omega, alpha)to set crank angular velocity.simulation.Linkage.step_with_derivatives()generator yielding (positions, velocities, accelerations).simulation.Linkage.get_velocities()andget_accelerations()batch query methods.solver.step_single_acceleration()numba-compiled acceleration solver.Exported acceleration solvers:
solve_crank_acceleration,solve_revolute_acceleration,solve_fixed_acceleration,solve_prismatic_acceleration.
Fixed
PSO score sign:
particle_swarm_optimization()returned the negated pyswarms cost whenorder_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 depending on which connected port was solved first. Constraints are now sorted by center position before intersection, making branch 0/1 deterministic.
Changed
Breaking: Dropped Python 3.9 support. Minimum version is now Python 3.10.
Added Python 3.14 to CI test matrix.
Breaking:
Linkage.step_fast_with_kinematics()now returns a 3-tuple(positions, velocities, accelerations)instead of 2-tuple.Breaking:
LinearActuator.velocityattribute renamed toLinearActuator.speedto avoid conflict with the newComponent.velocityproperty.simulate_with_kinematics()now computes accelerations in addition to velocities.
[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
HypostaticErrortoUnderconstrainedErrorandhyperstaticity()toindeterminacy(). 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
Linearjoint term is now deprecated in favor ofPrismatic.
Removed in 0.7.0
Removed support for Python 3.9.
[0.6.0] - 2024-10-02
Added in 0.6.0
New joint: the
Linearjoint!New sub-package: optimization.collections.
optimization.collections.Agentandoptimization.collections.MutableAgentare two new classes that should standardize the format of optimization, related to (#5).Agentis immutable and inherits from a namedtuple. It is recommended to use it, as it is a bit faster.MutableAgentis mutable. It may be deprecated/removed ifAgentis satisfactory.
New sub-package: geometry.
It introduces two new functions
line_from_pointsandcircle_line_intersection.
New examples:
examples/strider.pyfrom leggedsnake, based on the Strider Linkage.examples/inverted_stroke_engine.pyis a demo of a four-stroke engine featuring a Linear joint.
Linkage.set_completelyis a new method combining bothLinkage.set_num_constraintsandLinkage.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 in 0.6.0
Optimization return type changed (#5):
trials_and_error_optimizationreturn an array ofMutableAgent.particle_swarm_optimizationreturn an array of oneAgent.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_reprtakes (index, swarm) as input argument. swarm is (score, dim, pos) for each agent for this iteration.
repr_polar_swarmreload 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
geometrymodule is now a package (pylinkage/geometry)New package
pylinkage/linkage:pylinkage/linkage.pyseparated and inserted in this package.
New package:
pylinkage/jointsJoints definition are in respective files.
New package
pylinkage/optimization/pylinkage/optimizer.pysplit and inserted in.Trials-and-errors related functions goes to
grid_search.py.Particle swarm optimization is at
particle_swarm.py.New file
utils.pyforgenerate_bounds.
Tests follow the same renaming.
From the user perspective, no change (execution may be a bit faster)
source/renamed tosphinx/because it was confusing and only for Sphinx configuration.
Transition from Numpydoc to reST for docstrings (#12).
__secant_circles_intersections__renamed tosecant_circles_intersections(inpylinkage/geometry/secants.py).
Fixed in 0.6.0
swarm_tiled_reprinvisualizer.pywas wrongly assigning dimensions.Setting
locus_highlightinplot_static_linkagewould result in an error.Pivot.reloadwas returning arbitrary point when we had an infinity of solutions.The highlighted locus was sometimes buggy in
plot_static_linkageinvisualizer.py.
Deprecated in 0.6.0
Using
tqdm_verbosityis deprecated in favor of usingdisable=Truein a tqdm object.The
Pivotclass is deprecated in favor of theRevoluteclass. The name “Pivot joint” is not standard. Related to #13.The
hyperstaticitymethod is renamedindeterminacyinLinkage(linkage.py)
Removed in 0.6.0
Drops support for Python 3.7 and 3.8 as both versions reached end-of-life.
movement_bounding_bowis replaced bymovement_bounding_box(typo in function name).
[0.5.3] - 2023-06-23
Added in 0.5.3
We now checked compatibility with Python 3.10 and 3.11.
pyproject.tomlis now the official definition of the package.Linkage.hyperstaticitynow clearly outputs a warning when used.
Changed in 0.5.3
masterbranch is nowmain.docs/example/fourbar_linkage.pycan now be used as a module (not the target but anyway).docs/examplesmoved toexamples/(main folder).Now
docs/only contains sphinx documentation.
docs/examples/imagesmoved toimages/.
Fixed in 0.5.3
Setting a motor with a negative rotation angle do no longer break
get_rotation_period(#7).Pivot.reloadandLinkage.__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_convergenceis 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 in 0.5.3
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!
[0.5.2] - 2021-07-21
Added in 0.5.2
You can see the best score and best dimensions updating in
trials_and_errors_optimization.
Changed in 0.5.2
The optimizer tests are 5 times quicker (~1 second now) and raise less false positive.
The sidebar in the documentation makes navigation easier.
A bit of reorganization in optimizers, it should not affect users.
[0.5.1] - 2021-07-14
Added in 0.5.1
The trial and errors optimization now have a progress bar (same kind of the one in particle swarm optimization), using tqdm.
Changed in 0.5.1
matplotlib and tqdm now required.
[0.5.0] - 2021-07-12
End of 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 an “-alpha” mention.
Added in 0.5.0
It is now possible and advised to import useful functions from pylinkage.{object}, without full path. For instance, use
from pylinkage import Linkageinstead offrom pylinkage.linkage import Linkage.Each module had his header improved.
The
generate_boundsfunctions is a simple way to generate bounds before optimization.The
order_relationarguments ofparticle_swarm_optimizationandtrials_and_errors_optimizationlet you choose between maximization and minimization problem.You can specify a custom order relation with
trials_and_errors_optimization.The
verboseargument in optimizers can disable verbosity.Staticjoints can now be defined implicitly.The
utilitymodule provides two useful decoratorskinematic_minimizationandkinematic_optimizatino. They greatly simplify the workflow of defining fitness functions.Versioning is now done thanks to bump2version.
Changed in 0.5.0
The
particle_swarm_optimizationeval_funcsignature is now similar to the one ottrials_and_errorsoptimization. Wrappers are no longer needed!The
trials_and_errors_optimizationfunction now asks for bounds instead of dilatation and compression factors.In
trials_and_errors_optimizationabsolute stepdelta_dimis now replaced by number of subdivisionsdivisions.
Fixed in 0.5.0
After many hours of computations, default parameters in
particle_swarm_optimizationare much more efficient. With the demofourbar_linkage, the output wasn’t even convergent sometimes. Now we have a high convergence rate (~100%), and results equivalent to thetrials_and_errors_optimization(in the example).variatorfunction ofoptimizermodule was poorly working.The docstrings were not displayed properly in documentation, this is fixed.
[0.4.1] - 2021-07-11
Added in 0.4.1
The legend in
visualizer.pyis back!Documentation published to GitHub pages! It is contained in the
docs/folder.setup.cfgnow include links to the website.
Changed in 0.4.1
Examples moved from
pylinkage/examples/todocs/examples/.Tests moved from
pylinkage/tests/totests/.
[0.4.0] - 2021-07-06
Added in 0.4.0
The
bounding_boxmethod of geometry allows computing the bounding box of a 2D points finite set.You can now customize colors of linkage’s bars with the
COLOR_SWITCHERvariable ofvisualizer.py.movement_bounding_boxinvisualizer.pyto get the bounding box of multiple loci.parametersis optional intrials_and_errors_optimization(formerexhaustive_optimization)pylinkage/tests/test_optimizer.pyfor testing the optimizers, but it is a bit ugly as for now.Flake8 validation in
tox.ini
Fixed in 0.4.0
set_num_constraintsinLinkagewas misbehaving due to update 0.3.0.Cost history is no longer plotted automatically after a PSO.
Changed in 0.4.0
exhaustive_optimizationis now known astrials_and_errors_optimizattion.Axes on linkage visualization are now named “x” and “y”. It was “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_posmethod ofLinkageis now known asget_coordsfor consistency.Parameters renamed, reorganized and removed in
particle_swarm_optimizationto align to PySwarms.README.mdupdated consequently to the changes.
Removed in 0.4.0
Legacy built-in Particle Swarm Optimization, to avoid confusion.
We do no longer show a default legend on static representation.
[0.3.0] - 2021-07-05
Added in 0.3.0
Jointobjects now have aget_constraintsmethod, consistent with theirset_constraintsone.Linkagenow has aget_num_constraintsmethod as syntactic sugar.Code vulnerabilities checker
Walkthrough’s example has been expanded and now seems to be complete.
Changed in 0.3.0
Linkage’s methodset_num_constraintsbehaviour changed! You should now addflat=Falseto come back to the previous behavior.pylinkage/examples/fourbar_linkage.pyexpanded and finished.The
beginparameter ofarticle_swarm_optimizationis no longer mandatory.linkage.get_num_constraints()will be used ifbeginis not provided.More flexible package version in
environment.ymlOutput file name now is formatted as “Kinematic {linkage.name}” in
plot_kinematic_linkagefunction ofpylinkage/visualizer.pyPython 3.6 is no longer tested in
tox.ini. Python 3.9 is now tested.
Fixed in 0.3.0
When linkage animation was saved, last frames were often missing in
pylinkage/visualizer.py, functionplot_kinematic_linkage.
[0.2.2] - 2021-06-22
Added in 0.2.2
More continuous integration workflows for multiple Python versions.
Fixed in 0.2.2
README.mdcould not be seen in PyPi.Various types
[0.2.1] - 2021-06-16
Added in 0.2.1
swarm_tiled_reprfunction forpylinkage/visualizer.py, for visualization of PySwarms.EXPERIMENTAL!
hyperstaticitymethodLinkage’s hyperstaticity (over constrained) calculation.
Changed in 0.2.1
pylinkage/exception.pynow handles exceptions in another file.Documentation improvements.
Python style improvements.
.gitignorenow modified from the standard GitHub gitignore example for Python.
Fixed in 0.2.1
circlemethod ofPivotinpylinkage/linkage.py. It was causing errorstox.ininow fixed.
[0.2.0] - 2021-06-14
Added in 0.2.0
pylinkage/vizualizer.pyview your linkages using matplotlib!Issue templates in
.github/ISSUE_TEMPLATE/.github/workflows/python-package-conda.yml: conda tests with unittest workflow.CODE_OF_CONDUCT.mdMANIFEST.inREADME.mdenvironment.ymlsetup.cfgnow replacessetup.pytox.iniCHANGELOG.md
Changed in 0.2.0
.gitignorePython Package specific extensions addedMIT License→LICENSElib/→pylinkage/tests/→pylinkage/tests/Revamped package organization.
Cleared
setup.py
[0.0.1] - 2021-06-12
Added in 0.0.1
lib/geometry.pyas a mathematical basis for kinematic optimizationlib/linkage.py, linkage builderlib/optimizer.py, with Particle Swarm Optimization (built-in and PySwarms), and exhaustive optimization.MIT License.requirements.txt.setup.py.tests/__init__.py.tests/test_geometry.py.tests/test_linkage.py..gitignore.