Skip to content

Improve performance with block FFT and iterative solver - #46

Draft
Alexboiboi wants to merge 26 commits into
mainfrom
feat/improve-performance
Draft

Improve performance with block FFT and iterative solver#46
Alexboiboi wants to merge 26 commits into
mainfrom
feat/improve-performance

Conversation

@Alexboiboi

Copy link
Copy Markdown
Member

No description provided.

Alexboiboi and others added 26 commits May 6, 2026 16:33
- Added `demag_fft.py` for FFT-based matvec operations on uniform Cartesian cuboid grids, leveraging the Newell formula for demagnetization.
- Introduced `newell.py` to compute the analytical demagnetization tensor for rectangular prisms, including auxiliary functions and self-demagnetization factors.
- Created performance tests in `test_demag_performance.py` to validate the accuracy and efficiency of the FFT solver against direct methods.
- Developed unit tests in `test_newell.py` to ensure correctness of the Newell tensor calculations and their integration with existing functionality.

Co-authored-by: Copilot <copilot@github.com>
…tization calculations

Co-authored-by: Copilot <copilot@github.com>
…th sparse matrices

Co-authored-by: Copilot <copilot@github.com>
…ed coverage

Both solvers now share one interaction model and agree to solver_tol for
any input (verified: mixed bodies, rotations, anisotropy, 180-deg groups):

- newell: generalized volume-averaged tensor for different-size parallel
  prisms (4-point axis differences, observer-volume normalization, exact
  volume-weighted reciprocity), validated against Gauss-Legendre volume
  averages of magpylib's exact cuboid field
- demag_fft: analyze_structure partitions cells into grid/loose/generic
  clusters covering every cell exactly once (fixes silently dropped cells,
  92% error); stable quaternion canonicalization (fixes 180-deg splits);
  grid spacing decoupled from cell size (gapped grids FFT-eligible);
  per-component grid detection (two identical meshed bodies = two grids)
- demag: single pair rule (parallel cuboids -> Newell, else point-matched
  getH) evaluated dense for solver="direct" and matrix-free for
  solver="iterative"; solve always in the global frame (fixes anisotropic
  susceptibility under rotation, 5.5% error); chi.V/r^3 triage replaced by
  exact per-row dropped-sum budget (rigorous 0.1*solver_tol bound); GMRES
  non-convergence now raises; reciprocity transpose + displacement dedup
  cut build costs (direct assembly ~6x faster, n=27000 iterative in ~2s)
- fix latent write-back misalignment (sources_all vs magnets_list) and
  restore polarizations mutated during getH tensor evaluation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mple

- demag_fft.analyze_collection(sources): collection-level structure
  analysis (extracts positions/dims/rotations/cuboid mask); demag.py and
  the docs example now share it instead of duplicating the plumbing
- solver_performance example: warm-up both solvers before timing (first
  call was inflating the reference ~10x), reuse the meshed collection for
  the traced memory run (halves meshing, removes mesh allocations from
  peak), robust normalization baseline, harmonized crossover thresholds,
  cylinder-cell prose fix, single-run timing caveat
- correct dense-matrix figure at N=27000: ~50 GB, in example and method.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… solution

Meshes a spherical TriangularMesh into Tetrahedron cells via TetGen,
shows the cells take the point-matched generic path, and reproduces the
analytical soft-sphere polarization J0/(1+chi/3) to 0.01%. Adds the
tetgen extra to the docs dependency group so the page executes on RTD.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The plotly sphinx_gallery renderer embeds MathJax v2 synchronously in the
page body; it clobbers window.MathJax before Sphinx's deferred MathJax v3
runs, so math never typeset on any page containing a chart. Strip the
injected v2 tags in an html-page-context hook (no TeX is used in chart
labels; PlotlyConfig.MathJaxConfig='local' already isolates plotly).

Also set nb_execution_timeout=120 for benchmark cells on slow builders.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
_collection_from_obj_and_cells copied susceptibility to the mesh cells
but silently dropped H_ext, so an external field set on a magnet was
ignored after meshing. Both material attributes now carry over; add a
regression test (high-chi soft cube in a 0.1 T field).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tion

- quickstart: executed minimal workflow plus the input conventions
  (susceptibility attribute vs kwarg precedence, anisotropic values,
  H_ext in Tesla units)
- API reference page via autodoc for all public functions
- README: installation (pip/conda/[tetgen] extra), key-features list,
  fixed warning callout
- navigation: Getting started / Reference toctrees, examples index with
  descriptions in reading order, changelog page wired into the site

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- newell: demag_tensor_newell docstring said the mu_0 division was not
  applied when it is — corrected wording
- method.md: working source links on the rendered site, sign-convention
  section now describes the default block assembly (legacy 4-index layout
  noted separately), correct Chadebec 2006 citation, and coverage of the
  cluster size gates, GMRES restart/warm-start semantics, current-source
  RHS contribution, conditional reciprocity/dedup, and the legacy branch
- examples: soft_magnets plots distance in meters instead of a raw path
  index, magpylib>=5 guard added, unused json imports removed, SI-unit
  comments and cross-links to the solver-performance page
- demag: drop stale TODOs (currents and H_ext are implemented)
- demag_fft: harmonize British spellings; conf.py: dynamic copyright year

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Correctness (each with a regression test):
- 3-magnet collections with per-object scalar susceptibilities were
  misread as one anisotropic 3-vector (hierarchy values are per-source)
- legacy split crashed on single-source subsets (getH squeezes)
- max_dist>0 crashed on entry (unpacked 4 values from a 3-tuple) and,
  with pairs_matching, used magpylib's deprecated functional string
  interface — migrated to magpy.func.cuboid_field
- match_pairs keyed pairs on dimension DIFFERENCES (colliding distinct
  geometries) with absolute 1e-8 rounding (collapsing sub-1e-8 m
  meshes); now absolute source dims + scale-relative rounding
- serialization dropped H_ext, Collection-level susceptibility, and
  crashed on numpy-typed values
- meshing edge crashes: thin-segment nphi/nh and voxelize grid counts
  clamped; empty voxel grids raise a clear error
- H_ext docstrings now state the Tesla (B-field) convention the code
  implements; dead H_ext kwarg removed from get_H_ext

Performance:
- timelog exit blocked up to min_log_time/5 per block (sleep+join);
  Event.wait makes it instant — a 27-cell solve drops from ~0.5 s to
  ~5 ms and the test suite runs 5x faster
- direct solver assembles Q in place (T *= -chi; diag += 1; LAPACK
  overwrite), cutting peak memory ~2-3x

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New example driving the material response with current sources: a
three-cuboid U-core with a multi-turn coil built from the polyline
helpers (filleted square loop, winding-pack grid swept along it), the
field mapped on a plane above the poles, and a cuboid- vs
tetrahedral-mesh comparison at three refinement levels with solve
times in the plot legends. Also documents the two public polyline
functions on the API page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- move the field plane to 30 mm above the poles and double its
  resolution (161x81, 1 mm step); show it semi-transparent in the scene
- compare cuboid vs tetrahedral meshes at matched refinement levels up
  to target_elems=1000, with solve times in the legend and adaptive
  Blues/Oranges shade ramps (hue = cell type, darker = finer); halve the
  tet target since TetGen's quality refinement ~doubles the requested
  count
- evaluate the comparison on the y=0 line only to keep getB cheap
- replace the trimesh-vs-tetmesh figure with a side-by-side of the
  finest demagnetized cuboid and tet systems (magnetization coloring)
- state that the remaining gap is discretization error: both families
  converge to the same limit, cuboids ~10% low at 1000 cells because
  the corner-concentrated magnetization is under-resolved
- exclude jupytext-paired *.ipynb from the sphinx build; they shadowed
  the .md source and shipped stale outputs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bugs:
- remove stray $$ at the end of method.md references
- fix reST :func: role to MyST {func} syntax in method.md
- drop brittle magpylib major-version checks from the two older
  examples (pyproject already enforces magpylib>=5.0)
- correct stale 'field map' wording in the U-core intro

API coverage:
- document voxelize, trimesh_from_model3d, and the legacy
  filter_distance/match_pairs helpers on the API page
- document anisotropic susceptibility consistently: accepted shapes in
  the apply_demag docstring, global-frame convention in method.md and
  quickstart

Polish:
- rewrite the padded intros of the two oldest examples; retitle
  'Cuboids demagnetization' to 'Cuboid Demagnetization'
- README: minimal runnable example + current-sources feature bullet
- quickstart: current sources as drive term, link U-core example
- cross-link tetrahedral example to the U-core mesh comparison
- serialization: drop duplicate import, list supported types
- clarify FEM template-dataframe comments, unify magpy.show usage,
  remove empty module index from the landing page

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- _data: import importlib.resources explicitly — get_dataset relied on
  another package having imported the submodule first (latent
  AttributeError in a clean import graph)
- demag: accept 0-d numpy arrays as scalar susceptibility instead of
  raising 'len() of unsized object'; add regression test
- polyline: correct max_ratio docstring default (1, not 0.5)
- meshing_utils: get_volume docstring says m³ (SI), not mm³
- meshing: voxelize docstring parameter name strict_inside

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- demag_tensor: document that point matching leaves None-polarization
  sources with the last unit polarization (apply_demag unaffected)
- meshing_utils: make Sphere and CylinderSegment inside-masks use
  tolerances relative to the object size (scale-invariant, consistent
  with Cuboid/Cylinder); document local-frame convention on mask_inside
- trimesh_from_model3d: vertex dedup quantization relative to geometry
  scale (absolute 1e-10 rounding collapsed sub-1e-10 m shapes), keep
  original coordinates, single np.unique pass; add regression test
- fix stray quote in ElapsedTimeThread docstring; add get_dataset
  docstring (renders on the API page)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- _assemble_T_dense: derive reverse Newell cross-blocks by
  volume-weighted reciprocity transpose instead of a second analytical
  evaluation, matching the iterative operator build. 12-body/1500-cell
  direct solve: 2.03 s -> 1.21 s, result identical to 3e-13 relative.
- timelog: skip the 'Starting:' watchdog thread while package logging
  is silent (the default) — ~1 µs per step instead of a thread
  spawn+join. The loguru activation probe falls back to the previous
  always-spawn behaviour on any surprise, and a test pins the
  loguru-internal contract (activation names carry a trailing dot).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
to_json/from_json now cover the full magpylib magnet family, so
demagnetized tetrahedral collections from mesh_TriangularMesh can be
persisted and reloaded as drop-in field sources. Includes an
end-to-end test of the mesh -> apply_demag -> JSON round trip and
updated serialization docs/changelog.

Co-Authored-By: Claude Fable 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