22 Commits

Author SHA1 Message Date
forbes-0023
9e07ef8679 test: add console test reproducing planar drag quaternion flip (#338)
Adds console_test_planar_drag.py — a live FreeCAD console test that
reproduces the quaternion branch-jump failure from #338.

Test 2 (realistic geometry) reliably triggers the bug: 10/40 drag
steps rejected by the C++ validateNewPlacements() simulator when
the solver converges to an equivalent but distinct quaternion branch
around 240-330 deg axial rotation.

Key findings from the test:
- The failure is NOT simple hemisphere negation (q vs -q)
- The solver finds geometrically valid but quaternion-distinct
  solutions when Cylindrical + Planar constraints have multiple
  satisfying orientations
- _enforce_quat_continuity only catches sign flips, not these
  deeper branch jumps
- The C++ validator uses acos(w) not acos(|w|), so opposite-
  hemisphere quaternions show as ~360 deg rotation
2026-02-27 09:30:27 -06:00
5802d45a7f fix(solver): skip single_equation_pass during drag to prevent stale constraints
single_equation_pass analytically solves variables and bakes their values
as Const() nodes into downstream residual expressions. During drag, the
cached residuals use these stale constants even though part positions have
changed, causing constraints like Planar distance=0 to silently stop
being enforced.

Skip single_equation_pass in the pre_drag() path. Only substitution_pass
(which replaces genuinely grounded parameters) is safe to cache across
drag steps. Newton-Raphson converges in 1-2 iterations from a nearby
initial guess anyway, so the prepass optimization is unnecessary for drag.

Add regression tests covering the bug scenario and the fix.
2026-02-25 12:57:43 -06:00
forbes-0023
8e521b4519 fix(solver): use all 3 cross-product components to avoid XY-plane singularity
The parallel-normal constraints (ParallelConstraint, PlanarConstraint,
ConcentricConstraint, RevoluteConstraint, CylindricalConstraint,
SliderConstraint, ScrewConstraint) and point-on-line constraints
previously used only the x and y components of the cross product,
dropping the z component.

This created a singularity when both normal vectors lay in the XY
plane: a yaw rotation produced a cross product entirely along Z,
which was discarded, making the constraint blind to the rotation.

Fix: return all 3 cross-product components. The Jacobian has a
rank deficiency at the solution (3 residuals, rank 2), but the
Newton solver handles this correctly via its pseudoinverse.

Similarly, point_line_perp_components now returns all 3 components
of the displacement cross product to avoid singularity when the
line direction aligns with a coordinate axis.
2026-02-22 15:51:59 -06:00
forbes-0023
64b1e24467 feat(solver): compile symbolic Jacobian to flat Python for fast evaluation
Add a code generation pipeline that compiles Expr DAGs into flat Python
functions, eliminating recursive tree-walk dispatch in the Newton-Raphson
inner loop.

Key changes:
- Add to_code() method to all 11 Expr node types (expr.py)
- New codegen.py module with CSE (common subexpression elimination),
  sparsity detection, and compile()/exec() compilation pipeline
- Add ParamTable.env_ref() to avoid dict copies per iteration (params.py)
- Newton and BFGS solvers accept pre-built jac_exprs and compiled_eval
  to avoid redundant diff/simplify and enable compiled evaluation
- count_dof() and diagnostics accept pre-built jac_exprs
- solver.py builds symbolic Jacobian once, compiles once, passes to all
  consumers (_monolithic_solve, count_dof, diagnostics)
- Automatic fallback: if codegen fails, tree-walk eval is used

Expected performance impact:
- ~10-20x faster Jacobian evaluation (no recursive dispatch)
- ~2-5x additional from CSE on quaternion-heavy systems
- ~3x fewer entries evaluated via sparsity detection
- Eliminates redundant diff().simplify() in DOF/diagnostics
2026-02-21 11:22:36 -06:00
forbes-0023
adaa0f9a69 test(solver): add in-client console tests for Phase 5 assembly integration
Paste-into-console test script exercising the full pipeline:
- Solver registry and loading
- Preference switching between kindred/ondsel
- Fixed joint placement matching
- Revolute joint DOF reporting
- No-ground error code
- Solve determinism/stability
- Standalone kcsolve API (no FreeCAD Assembly objects)
- Diagnose API for overconstrained detection
2026-02-20 23:34:39 -06:00
forbes-0023
b4b8724ff1 feat(solver): diagnostics, half-space preference, and weight vectors (phase 4)
- Add per-entity DOF analysis via Jacobian SVD (diagnostics.py)
- Add overconstrained detection: redundant vs conflicting constraints
- Add half-space tracking to preserve configuration branch (preference.py)
- Add minimum-movement weighting for least-squares solve
- Extend BFGS fallback with weight vector and quaternion renormalization
- Add snapshot/restore and env accessor to ParamTable
- Fix DistancePointPointConstraint sign for half-space tracking
2026-02-20 23:32:45 -06:00
forbes-0023
92ae57751f feat(solver): graph decomposition for cluster-by-cluster solving (phase 3)
Add a Python decomposition layer using NetworkX that partitions the
constraint graph into biconnected components (rigid clusters), orders
them via a block-cut tree, and solves each cluster independently.
Articulation-point bodies propagate as boundary conditions between
clusters.

New module kindred_solver/decompose.py:
- DOF table mapping BaseJointKind to residual counts
- Constraint graph construction (nx.MultiGraph)
- Biconnected component detection + articulation points
- Block-cut tree solve ordering (root-first from grounded cluster)
- Cluster-by-cluster solver with boundary body fix/unfix cycling
- Pebble game integration for per-cluster rigidity classification

Changes to existing modules:
- params.py: add unfix() for boundary body cycling
- solver.py: extract _monolithic_solve(), add decomposition branch
  for assemblies with >= 8 free bodies

Performance: for k clusters of ~n/k params each, total cost drops
from O(n^3) to O(n^3/k^2).

220 tests passing (up from 207).
2026-02-20 22:19:35 -06:00
forbes-0023
533ca91774 feat(solver): full constraint vocabulary — all 24 BaseJointKind types (phase 2)
Add 18 new constraint classes covering all BaseJointKind types from Types.h:
- Point: PointOnLine (2r), PointInPlane (1r)
- Orientation: Parallel (2r), Perpendicular (1r), Angle (1r)
- Surface: Concentric (4r), Tangent (1r), Planar (3r), LineInPlane (2r)
- Kinematic: Ball (3r), Revolute (5r), Cylindrical (4r), Slider (5r),
  Screw (5r), Universal (4r)
- Mechanical: Gear (1r), RackPinion (1r)
- Stubs: Cam, Slot, DistanceCylSph

New modules:
- geometry.py: marker axis extraction, vector ops (dot3, cross3, sub3),
  geometric primitives (point_plane_distance, point_line_perp_components)
- bfgs.py: L-BFGS-B fallback solver via scipy for when Newton fails

solver.py changes:
- Wire all 20 supported types in _build_constraint()
- BFGS fallback after Newton-Raphson in solve()

183 tests passing (up from 82), including:
- DOF counting for every joint type
- Solve convergence from displaced initial conditions
- Multi-body mechanisms (four-bar linkage, slider-crank, revolute chain)
2026-02-20 21:15:15 -06:00
forbes-0023
98051ba0c9 feat: add Phase 1 constraint solver addon, move prior content to GNN/
- Move existing OndselSolver, GNN ML layer, and tooling into GNN/
  directory for integration in later phases
- Add Create addon scaffold: package.xml, Init.py
- Add expression DAG with eval, symbolic diff, simplification
- Add parameter table with fixed/free variable tracking
- Add quaternion rotation as polynomial Expr trees
- Add RigidBody entity (7 DOF: position + unit quaternion)
- Add constraint classes: Coincident, DistancePointPoint, Fixed
- Add Newton-Raphson solver with symbolic Jacobian + numpy lstsq
- Add pre-solve passes: substitution + single-equation
- Add DOF counting via Jacobian SVD rank
- Add KindredSolver IKCSolver bridge for kcsolve integration
- Add 82 unit tests covering all modules

Registers as 'kindred' solver via kcsolve.register_solver() when
loaded by Create's addon_loader.
2026-02-20 20:35:47 -06:00
93bda28f67 feat(mates): add mate-level ground truth labels
Some checks failed
CI / lint (push) Successful in 1m45s
CI / type-check (push) Successful in 2m32s
CI / test (push) Failing after 3m36s
MateLabel and MateAssemblyLabels dataclasses with label_mate_assembly()
that back-attributes joint-level independence to originating mates.
Detects redundant and degenerate mates with pattern membership tracking.

Closes #15
2026-02-03 13:08:23 -06:00
239e45c7f9 feat(mates): add mate-based synthetic assembly generator
Some checks failed
CI / lint (push) Has been cancelled
CI / type-check (push) Has been cancelled
CI / test (push) Has been cancelled
SyntheticMateGenerator wraps existing joint generator with reverse
mapping (joint->mates) and configurable noise injection (redundant,
missing, incompatible mates). Batch generation via
generate_mate_training_batch().

Closes #14
2026-02-03 13:05:58 -06:00
118474f892 feat(mates): add mate-to-joint conversion and assembly analysis
Some checks failed
CI / lint (push) Has been cancelled
CI / type-check (push) Has been cancelled
CI / test (push) Has been cancelled
convert_mates_to_joints() bridges mate-level constraints to the existing
joint-based analysis pipeline. analyze_mate_assembly() orchestrates the
full pipeline with bidirectional mate-joint traceability.

Closes #13
2026-02-03 13:03:13 -06:00
e8143cf64c feat(mates): add joint pattern recognition
Some checks failed
CI / lint (push) Has been cancelled
CI / type-check (push) Has been cancelled
CI / test (push) Has been cancelled
JointPattern enum (9 patterns), PatternMatch dataclass, and
recognize_patterns() function with data-driven pattern rules.
Supports canonical, partial, and ambiguous pattern matching.

Closes #12
2026-02-03 12:59:53 -06:00
9f53fdb154 feat(mates): add mate type definitions and geometry references
Some checks failed
CI / lint (push) Has been cancelled
CI / type-check (push) Has been cancelled
CI / test (push) Has been cancelled
MateType enum (8 types), GeometryType enum (5 types), GeometryRef and
Mate dataclasses with validation, serialization, and context-dependent
DOF removal via dof_removed().

Closes #11
2026-02-03 12:55:37 -06:00
5d1988b513 Merge remote-tracking branch 'public/main'
Some checks failed
CI / lint (push) Successful in 38s
CI / type-check (push) Successful in 1m47s
CI / test (push) Failing after 3m2s
# Conflicts:
#	.gitignore
#	README.md
2026-02-03 10:53:48 -06:00
f29060491e feat(datagen): add dataset generation CLI with sharding and checkpointing
Some checks failed
CI / lint (push) Has been cancelled
CI / type-check (push) Has been cancelled
CI / test (push) Has been cancelled
- Add solver/datagen/dataset.py with DatasetConfig, DatasetGenerator,
  ShardSpec/ShardResult dataclasses, parallel shard generation via
  ProcessPoolExecutor, checkpoint/resume support, index and stats output
- Add scripts/generate_synthetic.py CLI entry point with Hydra-first
  and argparse fallback modes
- Add minimal YAML parser (parse_simple_yaml) for config loading
  without PyYAML dependency
- Add progress display with tqdm fallback to print-based ETA
- Update configs/dataset/synthetic.yaml with shard_size, checkpoint_every
- Update solver/datagen/__init__.py with DatasetConfig, DatasetGenerator
  exports
- Add tests/datagen/test_dataset.py with 28 tests covering config,
  YAML parsing, seed derivation, end-to-end generation, resume,
  stats/index structure, determinism, and CLI integration

Closes #10
2026-02-03 08:44:31 -06:00
8a49f8ef40 feat: ground truth labeling pipeline
Some checks failed
CI / lint (push) Failing after 25m6s
CI / type-check (push) Has been cancelled
CI / test (push) Has been cancelled
- Create solver/datagen/labeling.py with label_assembly() function
- Add dataclasses: ConstraintLabel, JointLabel, BodyDofLabel,
  AssemblyLabel, AssemblyLabels
- Per-constraint labels: pebble_independent + jacobian_independent
- Per-joint labels: aggregated independent/redundant/total counts
- Per-body DOF: translational + rotational from nullspace projection
- Assembly label: classification, total_dof, has_degeneracy flag
- AssemblyLabels.to_dict() for JSON-serializable output
- Integrate into generate_training_batch (adds 'labels' field)
- Export AssemblyLabels and label_assembly from datagen package
- Add 25 labeling tests + 1 batch structure test (184 total)

Closes #9
2026-02-02 15:20:02 -06:00
78289494e2 feat: geometric diversity for synthetic assembly generation
Some checks failed
CI / lint (push) Has been cancelled
CI / type-check (push) Has been cancelled
CI / test (push) Has been cancelled
- Add AxisStrategy type (cardinal, random, near_parallel)
- Add random body orientations via scipy.spatial.transform.Rotation
- Add parallel axis injection with configurable probability
- Add grounded parameter on all 7 generators (grounded/floating)
- Add axis sampling strategies: cardinal, random, near-parallel
- Update _create_joint with orientation-aware anchor offsets
- Add _resolve_axis helper for parallel axis propagation
- Update generate_training_batch with axis_strategy, parallel_axis_prob,
  grounded_ratio parameters
- Add body_orientations and grounded fields to batch output
- Export AxisStrategy from datagen package
- Add 28 new tests (72 total generator tests, 158 total)

Closes #8
2026-02-02 14:57:49 -06:00
0b5813b5a9 feat: parameterized assembly templates and complexity tiers
Some checks failed
CI / lint (push) Has been cancelled
CI / type-check (push) Has been cancelled
CI / test (push) Has been cancelled
Add 4 new topology generators to SyntheticAssemblyGenerator:
- generate_tree_assembly: random spanning tree with configurable branching
- generate_loop_assembly: closed ring producing overconstrained data
- generate_star_assembly: hub-and-spoke topology
- generate_mixed_assembly: tree + loops with configurable edge density

Each accepts joint_types as JointType | list[JointType] for per-joint
type sampling.

Add complexity tiers (simple/medium/complex) with predefined body count
ranges via COMPLEXITY_RANGES dict and ComplexityTier type alias.

Update generate_training_batch with 7-way generator selection,
complexity_tier parameter, and generator_type field in output dicts.

Extract private helpers (_random_position, _random_axis,
_select_joint_type, _create_joint) to reduce duplication.

44 generator tests, 130 total — all passing.

Closes #7
2026-02-02 14:38:05 -06:00
dc742bfc82 test: add unit tests for datagen modules
Some checks failed
CI / lint (push) Has been cancelled
CI / type-check (push) Has been cancelled
CI / test (push) Has been cancelled
- test_types.py: JointType enum values/count, dataclass defaults/isolation
- test_pebble_game.py: DOF accounting, rigidity, classification, edge results
- test_jacobian.py: Jacobian shape per joint type, rank, parallel axis degeneracy
- test_analysis.py: demo scenarios (revolute, fixed, triangle, parallel axes)
- test_generator.py: chain/rigid/overconstrained generation, training batch

Bug fixes found during testing:
- JointType enum: duplicate int values caused aliasing (SLIDER=REVOLUTE etc).
  Changed to (ordinal, dof) tuple values with a .dof property.
- pebble_game.py: .value -> .dof for constraint count
- analysis.py: classify from effective DOF (not raw pebble game with virtual
  ground body skew)

105 tests, all passing.

Closes #6
2026-02-02 14:08:22 -06:00
363b49281b build: phase 0 infrastructure setup
Some checks failed
CI / lint (push) Has been cancelled
CI / type-check (push) Has been cancelled
CI / test (push) Has been cancelled
- Project structure: solver/, freecad/, export/, configs/, scripts/, tests/, docs/
- pyproject.toml with dependency groups: core, train, freecad, dev
- Hydra configs: dataset (synthetic, fusion360), model (baseline, gat), training (pretrain, finetune), export (production)
- Dockerfile with CUDA+PyG GPU and CPU-only targets
- docker-compose.yml for train, test, data-gen services
- Makefile with targets: train, test, lint, format, type-check, data-gen, export, check
- Pre-commit hooks: ruff, mypy, conventional commits
- Gitea Actions CI: lint, type-check, test on push/PR
- README with setup and usage instructions
2026-02-02 13:26:38 -06:00
aiksiongkoh
3d6a23a678 Cmake gtest (#72)
* Check rackpin and gear for zero radii

* rebase zero-radii-check (#69)

* contributing

* Update push-freecad.yml

updated actions/checkout to v4

* dragging log for debugging

* fix calcdxNorm crash

* setDebug and remove MBDyn*

* Update cmakelists.txt

* fix includes for gcc-14

gcc-14 is more disciplined about not including <algorithm> transitively.

* fix runDragStep

* backhoe files (#65)

* Mark unused variables to silence compiler warnings. (#64)

* Backhoe issues (#67)

* backhoe issues

* runDragStep edit

* backhoe issues

* runDragStep edit

* Reduce large drag step progressively until convergence.

* Switch to using built-in M_PI, even on MSVC (#68)

---------

Co-authored-by: Brad Collette <bradcollette@pop-os.localdomain>
Co-authored-by: mosfet80 <realeandrea@yahoo.it>
Co-authored-by: PaddleStroke <pierrelouis.boyer@gmail.com>
Co-authored-by: Jed Brown <jed@jedbrown.org>
Co-authored-by: sliptonic <shopinthewoods@gmail.com>
Co-authored-by: Chris Hennes <chennes@pioneerlibrarysystem.org>

* in progress

* Gtest added

---------

Co-authored-by: Brad Collette <bradcollette@pop-os.localdomain>
Co-authored-by: mosfet80 <realeandrea@yahoo.it>
Co-authored-by: PaddleStroke <pierrelouis.boyer@gmail.com>
Co-authored-by: Jed Brown <jed@jedbrown.org>
Co-authored-by: sliptonic <shopinthewoods@gmail.com>
Co-authored-by: Chris Hennes <chennes@pioneerlibrarysystem.org>
2024-07-30 14:45:59 -06:00