Skip to content

fix(mechanism): make set_constraints change the dimensions the solver maintains - #58

Merged
HugoFara merged 1 commit into
mainfrom
fix/mechanism-set-constraints
Sep 12, 2026
Merged

HugoFara merged 1 commit into
mainfrom
fix/mechanism-set-constraints

Conversation

@HugoFara

Copy link
Copy Markdown
Owner

Found while wiring the editor's optimization endpoint onto Mechanism (pylinkage-editor).

Bug

Mechanism.set_constraints() did not change the mechanism. Link lengths live in Link._cached_distances and in the Assur-group solver's Dimensions, both captured at construction. set_constraints only moved the driver's output joint, so the next step() re-solved every joint against the original lengths — and put the crank tip back at its original radius, since DriverLink.radius reads the cache. get_constraints() (position-based for binary links) then read the old values too.

Reproduce on 1.2.1:

m = mechanism_from_dict(four_bar)          # constraints [70, 25, 60]
m.set_constraints([84.0, 30.0, 72.0])
next(m.step(1, dt=0.0))
m.get_constraints()                        # → [70.0, 25.0, 60.0]

Consequence: every optimizer run on a Mechanism (documented as "used for optimization" since 0.8.0) evaluated the same geometry for every particle.

Fix

  • Link.set_distance(j1, j2, d) — writes the constraint (both orderings); positions are untouched and re-solved at the next step. Rejects non-positive distances.
  • Mechanism.set_constraints — writes driver radii and binary link lengths through set_distance, repositions the crank tip at its current angle as before, and rebuilds the Assur decomposition so the group solver's Dimensions follow (0.02 ms on a four-bar vs ~10 ms per cycle simulation). Skips exactly the links get_constraints skips, so the two stay aligned.
  • Link.length reads get_distance() (the maintained constraint once built), consistent with DriverLink.radius, so get/set_constraints round-trip before any step.

Scope unchanged: only binary links and driver radii form the constraint vector; n-ary links are still left out, as before.

Tests

TestGetSetConstraints: round trip before stepping; both solver paths (group_solver True/False) maintain the new lengths over 20 steps; to_dict/from_dict after a dt=0 step keeps them. TestLinkDistances: set_distance symmetry, positions untouched, validation. Full suite 2614 passed; ruff, mypy clean.

… maintains

Link lengths live in Link._cached_distances and in the group solver's
Dimensions, both captured when the mechanism is built. set_constraints
only moved the driver's output joint, so the next step() re-solved every
joint against the original lengths and snapped the crank tip back to its
original radius; get_constraints() (position-based for binary links)
then read the old values too. Optimizing a Mechanism was a silent no-op.

- Link.set_distance(j1, j2, d): write the constraint (both orderings),
  positions untouched, re-solved on the next step.
- Mechanism.set_constraints: write driver radii and binary link lengths
  through set_distance, reposition the crank tip at its current angle,
  rebuild the Assur decomposition so the group solver sees the new
  Dimensions. Skips the same links get_constraints skips.
- Link.length reads get_distance() (the cache when built), consistent
  with DriverLink.radius, so get/set_constraints round-trip before any
  step.

Tests cover the round trip, both solver paths maintaining the new
lengths over 20 steps, and dict serialization after a dt=0 step.
@HugoFara HugoFara added bug Something isn't working mechanical Any problem related to mechanical conception labels Sep 12, 2026
@HugoFara
HugoFara merged commit 857b377 into main Sep 12, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working mechanical Any problem related to mechanical conception

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant