Skip to content

Grid-free magnetostatic oracle + fix cancelling multilayer coils#692

Merged
ecto merged 3 commits into
mainfrom
claude/certified-motor-oracle
Jul 25, 2026
Merged

Grid-free magnetostatic oracle + fix cancelling multilayer coils#692
ecto merged 3 commits into
mainfrom
claude/certified-motor-oracle

Conversation

@ecto

@ecto ecto commented Jul 25, 2026

Copy link
Copy Markdown
Owner

First step of the certified-motor work: an exact field oracle to grade calc_motor's closed form against, plus a real bug it surfaced.

add_coil multilayer was electromagnetically wrong

Stacked layers reused one spiral, so series current ran outer→inner on even layers and inner→outer on odd ones through identical copper. Consecutive layers circulated opposite ways and their axial fields cancelled — a 4-layer stack made roughly one layer's field, an even stack roughly nothing.

Confirmed numerically before touching it: the new test reconstructs the current path from board data (chaining traces, following stitch vias) and found per-layer dipole moments alternating −,+,−,+.

The stack is now one continuous spiral in angle — θ marches monotonically across every layer while the radius zigzags inner↔outer, with a stitch via at each turnaround. Circulation is constant so fields add, and consecutive layers meet at the identical (r, θ) so vias align by construction.

Also fixes a pre-existing hole collision (present in the old code, not introduced here): with an integer turn count every inner turnaround landed on the same spoke, stacking two vias in one hole (HoleToHole −0.400mm). Successive turnarounds are now fanned by a stagger angle sized from via drill + hole-to-hole minimum at the binding inner radius.

Tests cover 2/4/8-layer stacks for sign consistency, ~linear moment scaling, and DRC-clean.

vcad-kernel-magnetostatic

No iron ⇒ μ = μ₀ everywhere ⇒ linear ⇒ superposition is exact. That collapses the usual FE machinery into a sum over current segments: closed-form Biot-Savart for B, closed-form ∮A·dl for flux linkage. No mesh, no iteration, no truncation boundary — and fast enough to sit inside a design search later.

Validated four independent ways:

  • infinite-wire limit
  • on-axis loop closed form
  • off-axis loop vs vcad-kernel-particle's elliptic integrals — a different crate, written for a different purpose
  • ∇×A = B by finite differences, tying the two separately-derived integrals together

Back-iron by method of images

Real "air-core" machines are only air-core in the winding region; steel back-irons close the magnetic circuit. For the examples/pcb-motor geometry that steel is worth ~2.4× in airgap flux (65 mT free space → 106 mT one plane → 155 mT full circuit), so ignoring it is not a small error. Unsaturated mild steel is μ_r ≳ 1000, so the μ→∞ mirror is accurate to ~0.1%.

Measured precondition, now documented and enforced: the two-plane image series converges only for a magnetically balanced source set.

source depth 8 depth 16 depth 32
one loop (net moment) 2.29e-4 T 7.28e-5 T 2.21e-5 T — never settles
6 alternating poles 2.258e-2 T 2.2598e-2 T 2.2598e-2 T — converged

Real multi-pole rotors are balanced by construction, but balance_residual() enforces it and tail_fraction() reports achieved truncation rather than assuming it.

Scope, stated honestly

  • Images assume flat, parallel, unbounded plates. Real back-irons are finite discs, so ID/OD fringing is unmodelled — to be quantified against the grid solver.
  • No saturation; flux_density_estimate() surfaces the working iron flux so the assumption is checkable.
  • Statics only — no eddy currents, skin or proximity effect.
  • Filamentary self-inductance depends on the regularization radius; L is less trustworthy than Kt.

Note for the roadmap

examples/pcb-motor was never physically built — its "verified numbers" (B_gap 0.155 T, Kt 3.7 mN·m/A) are kernel-computed, not measured; only the clearances are measured. So this oracle can grade the closed form, but the third measured column needs an actual build and dyno.

Test plan

  • cargo test -p vcad-kernel-magnetostatic — 14 tests
  • cargo run -p vcad-kernel-magnetostatic --example image_convergence --release
  • npx vitest run in packages/mcp — 934 tests, no regressions

🤖 Generated with Claude Code

…coils

Two independent pieces of the exact-oracle work for motor certification.

## add_coil multilayer was electromagnetically wrong

Stacked layers reused one spiral, so series current ran outer->inner on even
layers and inner->outer on odd ones through identical copper. Consecutive
layers circulated opposite ways and their axial fields cancelled: a 4-layer
stack produced roughly the field of one layer, and an even stack roughly
nothing. Confirmed numerically first -- the new test reconstructs the current
path from board data (chaining traces, following stitch vias) and found
per-layer dipole moments alternating -,+,-,+.

The stack is now one continuous spiral in angle: theta marches monotonically
across every layer while the radius zigzags inner<->outer, with a stitch via at
each turnaround. Circulation is constant, so fields add, and consecutive layers
meet at the identical (r, theta) so vias align by construction.

Also fixes a pre-existing hole collision (not introduced here): with an integer
turn count every inner turnaround landed on the same spoke, stacking two vias
in one hole (HoleToHole -0.400mm). Successive turnarounds are now fanned by a
stagger angle sized from via drill plus the hole-to-hole minimum at the binding
inner radius. Tests cover 2/4/8-layer stacks for sign consistency, ~linear
moment scaling, and DRC-clean.

## vcad-kernel-magnetostatic

Air-core machines have no iron, so mu = mu_0 everywhere, the problem is linear,
and superposition is exact. That collapses the usual FE machinery into a sum
over current segments: closed-form Biot-Savart for B, closed-form integral of
A.dl for flux linkage. No mesh, no iteration, no truncation boundary.

Validated four independent ways: infinite-wire limit, on-axis loop closed form,
off-axis loop against vcad-kernel-particle's elliptic integrals (a different
crate written for a different purpose), and curl A = B by finite differences,
which ties the two separately-derived integrals together.

Back-iron via method of images. Real "air-core" machines are only air-core in
the winding region; steel back-irons close the circuit and are worth ~2.4x in
airgap flux for the reference geometry (65 mT free space -> 106 mT one plane ->
155 mT full circuit), so ignoring them is not a small error. Unsaturated mild
steel is mu_r >~ 1000, making the mu -> infinity mirror accurate to ~0.1%.

Measured precondition, documented and enforced: the two-plane image series
converges only for a magnetically balanced source set. A lone magnet between
two infinite plates never settles (7.9e-4 -> 2.2e-5 T over depths 2..32); a
6-pole alternating rotor converges to 6e-5 by depth 16. Real multi-pole rotors
are balanced by construction, but balance_residual() enforces it and
tail_fraction() reports the achieved truncation rather than assuming it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chojiai

chojiai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Choji has used up today's reviews

Your team has reached the free plan's limit of 5 reviews per day, so Choji stepped aside on this pull request. That's a plan limit, not a reflection of the change itself.

Your free reviews reset tomorrow. To have Choji review every push without waiting, upgrade to Pro for unlimited reviews.

You're on the free plan · 5 reviews/day.

@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vcad-mcp Building Building Preview, Comment Jul 25, 2026 7:24pm
3 Skipped Deployments
Project Deployment Actions Updated (UTC)
mecheval Ignored Ignored Jul 25, 2026 7:24pm
vcad Ignored Ignored Jul 25, 2026 7:24pm
vcad-docs Ignored Ignored Jul 25, 2026 7:24pm

Request Review

ecto and others added 2 commits July 25, 2026 13:00
…rrents

A uniformly magnetized body is exactly equivalent to K_b = M x n on its
surface. For a prism magnetized along z that vanishes on the flat faces and
reduces, traced around the side walls, to a closed loop following the
footprint. So any axially-magnetized magnet -- disc, arc, rectangle -- becomes
a stack of loops around its outline, each carrying M*dz, feeding the same
segment integrator as the coils. One representation covers every magnet shape a
machine uses.

Validated against the closed form for an axially magnetized cylinder (within
2% on axis), plus polarity antisymmetry and winding-order invariance.

Anchors the solver-path decision numerically: a Y30 D15x3 ferrite disc gives
~65 mT at 1 mm in free space, and adding the rotor back-iron lifts that by
1.4-2.1x toward the ~155 mT the reluctance model predicts for the full
circuit -- which is why an iron-free oracle cannot grade this machine.

MagnetRing::discs builds an alternating-polarity rotor, verified magnetically
balanced so the two-plane image series converges.

Fidelity: exact for uniform M with recoil permeability 1. Real sintered magnets
are mu_rec ~ 1.05 (NdFeB) to 1.1 (ferrite), so working flux is over-predicted
by 5-10% -- the dominant magnet-side error, documented rather than hidden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ecto
ecto merged commit 34f3119 into main Jul 25, 2026
10 of 12 checks passed
ecto added a commit that referenced this pull request Jul 25, 2026
The same PR (#692) also trips clippy 1.95's manual_is_multiple_of, which
is the next gate after the format check — so main fails clippy too, and
fixing only the formatting just moved the red one step down the job.

`k % 2 == 0` -> `k.is_multiple_of(2)` on usize: identical behaviour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ecto added a commit that referenced this pull request Jul 25, 2026
* style(magnetostatic): rustfmt the crate

`Grid-free magnetostatic oracle` (#692) landed unformatted, so
`cargo fmt --check` fails on main and every open PR is red for a reason
that has nothing to do with its own diff.

Pure `rustfmt --edition 2021` output, no hand edits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(magnetostatic): satisfy clippy::manual_is_multiple_of

The same PR (#692) also trips clippy 1.95's manual_is_multiple_of, which
is the next gate after the format check — so main fails clippy too, and
fixing only the formatting just moved the red one step down the job.

`k % 2 == 0` -> `k.is_multiple_of(2)` on usize: identical behaviour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant