Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d31c72b
Implement FFT-accelerated demagnetization tensor computation
Alexboiboi May 6, 2026
157abb7
feat: enhance performance with block FFT and grid grouping in demagne…
Alexboiboi May 7, 2026
7a7a6f4
feat: add method documentation and improve cross-block computation wi…
Alexboiboi May 7, 2026
4a86560
Merge branch 'main' of https://github.com/magpylib/magpylib-material-…
Alexboiboi May 7, 2026
2912400
refactor: unify demag solvers around a single pair rule with guarante…
Alexboiboi Jul 12, 2026
e8503f7
feat: add documentation for solvers and performance analysis in apply…
Alexboiboi Jul 12, 2026
3ad2aea
feat: public analyze_collection API; apply review fixes to solver exa…
Alexboiboi Jul 13, 2026
3043b4a
docs: add tetrahedral-mesh example validated against the exact sphere…
Alexboiboi Jul 13, 2026
4792c4c
docs: fix MathJax on pages with plotly figures; raise nb cell timeout
Alexboiboi Jul 13, 2026
fdbe791
fix: propagate H_ext from meshed objects to their cells
Alexboiboi Jul 14, 2026
6e8ec70
docs: add quickstart, API reference, install instructions, and naviga…
Alexboiboi Jul 14, 2026
a65869e
docs: accuracy fixes from whole-docs review
Alexboiboi Jul 14, 2026
011f63c
style: pre-commit fixes
pre-commit-ci[bot] Jul 14, 2026
90fd6d7
pylint fixes
Alexboiboi Jul 14, 2026
280f84f
pylint fixes
Alexboiboi Jul 14, 2026
c51a9e2
fix: correctness and performance fixes from whole-package review
Alexboiboi Jul 15, 2026
e12d075
style: allow demag.py over pylint's 1000-line default
Alexboiboi Jul 15, 2026
fdbe727
test: separate assertions for voxelize dimensions validity
Alexboiboi Jul 15, 2026
2403a6c
docs: add U-core electromagnet example (current sources + polyline coil)
Alexboiboi Jul 15, 2026
7a806cb
docs: rework U-core example comparison after convergence analysis
Alexboiboi Jul 15, 2026
c492710
docs: update intersphinx mapping and nitpick_ignore_regex for improve…
Alexboiboi Jul 16, 2026
b89ae51
docs: full documentation review pass
Alexboiboi Jul 16, 2026
ed8a229
fix: correctness fixes from whole-package review (group A)
Alexboiboi Jul 16, 2026
38f45cb
fix: robustness fixes from whole-package review (group B)
Alexboiboi Jul 16, 2026
ea3024e
perf: performance fixes from whole-package review (group C)
Alexboiboi Jul 16, 2026
c54ef56
feat: serialize Sphere, Tetrahedron, and TriangularMesh magnets
Alexboiboi Jul 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ and this project adheres to

### Added

- Serialization (`to_json` / `from_json`) now supports `magnet.Sphere`,
`magnet.Tetrahedron`, and `magnet.TriangularMesh` — demagnetized tetrahedral
meshes produced by `mesh_TriangularMesh()` can be persisted and reloaded.

- `mesh_TriangularMesh()` — meshes a `magnet.TriangularMesh` into conforming
tetrahedral cells using [TetGen](https://tetgen.pyvista.org/). Quality and
cell count are controlled via `target_elems`, `minratio`, and `mindihedral`.
Expand Down
65 changes: 59 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,63 @@

<!-- prettier-ignore-end -->

> **Warning** > **This package is experimental and in development phase,
> breaking API changes may happen at any time.**
> **Warning** This package is experimental and in development phase, breaking
> API changes may happen at any time.

Magpylib-Material-Response is an extension to the Magpylib library, providing
magnetic field analysis for soft materials and demagnetization of hard magnets.
Leveraging the Method of Moments, it calculates magnetic material response by
meshing sources in an arbitrary number of unit elements.
Magpylib-Material-Response is an extension to the
[Magpylib](https://magpylib.readthedocs.io/) library, providing magnetic field
analysis for soft materials and demagnetization of hard magnets. Leveraging the
Method of Moments, it calculates the self-consistent magnetic material response
by meshing sources into an arbitrary number of unit cells.

Key features:

- **`apply_demag`** — self-consistent demagnetization of magpylib `Collection`
objects, with per-cell scalar or anisotropic susceptibility; external fields
and current sources in the collection drive the response.
- **Two solvers, one physics** — an exact dense solver and an FFT-accelerated
iterative solver that agree to solver tolerance for any input; meshes with
tens of thousands of cells solve in seconds.
- **Analytical interactions** — volume-averaged Newell tensors for cuboid cells
(generalized to different cell sizes), point matching for everything else.
- **Meshing helpers** — cuboids, cylinders, and arbitrary closed surfaces via
tetrahedral meshing (`mesh_TriangularMesh`, using TetGen).

## Installation

```bash
pip install magpylib-material-response
```

or with
[conda](https://github.com/conda-forge/magpylib-material-response-feedstock):

```bash
conda install -c conda-forge magpylib-material-response
```

Tetrahedral meshing of `TriangularMesh` magnets requires the optional
[TetGen](https://tetgen.pyvista.org/) dependency:

```bash
pip install magpylib-material-response[tetgen]
```

## Minimal example

```python
import magpylib as magpy
from magpylib_material_response.demag import apply_demag
from magpylib_material_response.meshing import mesh_Cuboid

# a magnet with finite susceptibility, SI units (m, T)
magnet = magpy.magnet.Cuboid(polarization=(0, 0, 1), dimension=(1e-3, 1e-3, 1e-3))
magnet.susceptibility = 0.3 # µr = 1.3

# mesh into cells, solve the material response — result is a field source
mesh_demag = apply_demag(mesh_Cuboid(magnet, target_elems=125))
print(magpy.getB(mesh_demag, (0, 0, 1.5e-3)))
```

See the [documentation](https://magpylib-material-response.readthedocs.io/) for
a quickstart, worked examples, and the method description.
92 changes: 92 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# API Reference

The public functions of `magpylib-material-response`, grouped by module.

## Demagnetization solver — `demag`

```{eval-rst}
.. autofunction:: magpylib_material_response.demag.apply_demag

.. autofunction:: magpylib_material_response.demag.demag_tensor

.. autofunction:: magpylib_material_response.demag.get_susceptibilities

.. autofunction:: magpylib_material_response.demag.get_H_ext
```

Legacy helpers of the historical all-point-matching path (used by the
`pairs_matching` and `max_dist` options of `apply_demag`):

```{eval-rst}
.. autofunction:: magpylib_material_response.demag.filter_distance

.. autofunction:: magpylib_material_response.demag.match_pairs
```

## Meshing — `meshing`

```{eval-rst}
.. autofunction:: magpylib_material_response.meshing.mesh_all

.. autofunction:: magpylib_material_response.meshing.mesh_Cuboid

.. autofunction:: magpylib_material_response.meshing.slice_Cuboid

.. autofunction:: magpylib_material_response.meshing.mesh_Cylinder

.. autofunction:: magpylib_material_response.meshing.mesh_TriangularMesh

.. autofunction:: magpylib_material_response.meshing.mesh_thin_CylinderSegment_with_cuboids

.. autofunction:: magpylib_material_response.meshing.voxelize

.. autofunction:: magpylib_material_response.meshing_utils.trimesh_from_model3d
```

## Coil construction — `polyline`

```{eval-rst}
.. autofunction:: magpylib_material_response.polyline.create_polyline_fillet

.. autofunction:: magpylib_material_response.polyline.move_grid_along_polyline
```

## Structure analysis and FFT kernels — `demag_fft`

```{eval-rst}
.. autofunction:: magpylib_material_response.demag_fft.analyze_collection

.. autofunction:: magpylib_material_response.demag_fft.analyze_structure

.. autofunction:: magpylib_material_response.demag_fft.detect_uniform_grid

.. autofunction:: magpylib_material_response.demag_fft.build_fft_kernel

.. autofunction:: magpylib_material_response.demag_fft.demag_fft_matvec
```

## Analytical Newell tensors — `newell`

```{eval-rst}
.. autofunction:: magpylib_material_response.newell.demag_block

.. autofunction:: magpylib_material_response.newell.demag_block_general

.. autofunction:: magpylib_material_response.newell.demag_tensor_newell

.. autofunction:: magpylib_material_response.newell.self_demag_factors
```

## Utilities

```{eval-rst}
.. autofunction:: magpylib_material_response.utils.to_json

.. autofunction:: magpylib_material_response.utils.from_json

.. autofunction:: magpylib_material_response.get_dataset

.. autofunction:: magpylib_material_response.configure_logging

.. autofunction:: magpylib_material_response.disable_logging
```
5 changes: 5 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changelog

```{include} ../CHANGELOG.md
:start-line: 1
```
50 changes: 49 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from __future__ import annotations

import datetime
import importlib.metadata
import os
import re
from typing import Any

# Make plotly output self-contained HTML (text/html) instead of
Expand All @@ -11,7 +13,7 @@
os.environ["PLOTLY_RENDERER"] = "sphinx_gallery"

project = "magpylib-material-response"
copyright = "2025, Alexandre Boisselet"
copyright = f"{datetime.datetime.now(tz=datetime.UTC).year}, Alexandre Boisselet"
author = "Alexandre Boisselet"
version = release = importlib.metadata.version("magpylib_material_response")

Expand All @@ -25,6 +27,11 @@
"sphinx_copybutton",
]

# Benchmark cells in the examples (e.g. solver_performance) run for tens of
# seconds locally; give slower CI builders comfortable headroom over the
# myst-nb default of 30 s per cell.
nb_execution_timeout = 120

source_suffix = [".rst", ".md"]
exclude_patterns = [
"_build",
Expand All @@ -33,6 +40,9 @@
".DS_Store",
".env",
".venv",
# jupytext-paired notebooks (gitignored, created by IDE pairing) — the
# MyST .md files are the single documentation source
"**/*.ipynb",
]

html_theme = "pydata_sphinx_theme"
Expand Down Expand Up @@ -89,13 +99,27 @@

intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
"numpy": ("https://numpy.org/doc/stable", None),
"scipy": ("https://docs.scipy.org/doc/scipy", None),
"magpylib": ("https://magpylib.readthedocs.io/en/stable", None),
}

nitpick_ignore = [
("py:class", "_io.StringIO"),
("py:class", "_io.BytesIO"),
]

# The API docstrings use NumPy-style type strings ("array_like, shape (n, 3)",
# "int, optional, default=8", "magpy.Collection", ...). Nitpicky mode tries to
# resolve every fragment as a class reference; silence those without masking
# genuinely broken cross-references to fully-qualified targets.
nitpick_ignore_regex = [
("py:class", r"[^.]*"), # any dot-free fragment: array_like, optional, n, 3, ...
("py:class", r"default.*"), # "default=0.5", "default 1.5"
("py:class", r"(np|magpy|magnet)\..*"), # docstring shorthand aliases
("py:class", r".*\bobject\b.*"), # free text like "magpylib.Collection object ..."
]

always_document_param_types = True

suppress_warnings = ["mystnb.unknown_mime_type"]
Expand All @@ -107,3 +131,27 @@
# Static files (CSS/JS)
html_static_path = ["_static"]
html_css_files = ["fullwidth.css"]

# ── Fix MathJax on pages with plotly figures ────────────────────────────────
# The plotly "sphinx_gallery" renderer embeds MathJax *v2* synchronously in
# the page body (for TeX in chart labels). Sphinx's own MathJax v3 loads
# deferred in <head>, so on any page with a plotly figure the v2 script runs
# first, clobbers ``window.MathJax``, and v3 never typesets the page math —
# raw ``\(...\)`` delimiters appear. We use no TeX inside chart labels, so
# strip the injected v2 tags at build time; plotly is unaffected
# (``PlotlyConfig.MathJaxConfig = 'local'`` is set by the renderer itself).
_PLOTLY_MATHJAX2_SCRIPTS = re.compile(
r'<script src="https://[^"]*/mathjax/2[^"]*"></script>'
r"(\s*<script>if \(window\.MathJax.*?</script>)?",
re.DOTALL,
)


def _strip_plotly_mathjax2(_app, _pagename, _templatename, context, _doctree):
body = context.get("body")
if body and "/mathjax/2" in body:
context["body"] = _PLOTLY_MATHJAX2_SCRIPTS.sub("", body)


def setup(app):
app.connect("html-page-context", _strip_plotly_mathjax2)
42 changes: 18 additions & 24 deletions docs/examples/cuboids_demagnetization.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,20 @@ kernelspec:
name: python3
---

# Cuboids demagnetization

The following example demonstrates how to create magnetic sources with different
susceptibilities using the Magpylib library. It defines three cuboid magnets
with varying susceptibilities and positions, creates a collection of these
magnets, and computes their magnetic field responses using different levels of
meshing. The results are then compared to a Finite Element Method (FEM) analysis
to evaluate the performance of the Magpylib-Material-Response approach. The
comparison is presented in two separate plots, one showing the magnetic field
values and the other showing the difference between the Magpylib results and the
FEM reference data. The code demonstrates that even with a low number of mesh
elements, the Magpylib results quickly approach the reference FEM values.
# Cuboid Demagnetization

Three hard cuboid magnets with different susceptibilities, positions, and
orientations are meshed at increasing resolution and solved with `apply_demag`.
The field along a sensor line is compared against a Finite Element Method (FEM)
reference solution — both directly and as the difference to the reference.
Already coarse meshes land close to the FEM values, and refining the mesh
converges toward them.

+++

## Define magnetic sources with their susceptibilities

```{code-cell} ipython3
import json

import magpylib as magpy
import numpy as np
import pandas as pd
Expand All @@ -44,14 +38,9 @@ from magpylib_material_response.meshing import mesh_all
# from magpylib_material_response import configure_logging
# configure_logging(min_log_time=5) # log steps taking longer than 5 s

if magpy.__version__.split(".")[0] != "5":
raise RuntimeError(
f"Magpylib version must be >=5, (installed: {magpy.__version__})"
)

magpy.defaults.display.backend = "plotly"

# some low quality magnets with different susceptibilities
# some low quality magnets with different susceptibilities, SI units (m, T)
cube1 = magpy.magnet.Cuboid(polarization=(0, 0, 1), dimension=(0.001, 0.001, 0.001))
cube1.move((-0.0015, 0, 0))
cube1.susceptibility = 0.3 # µr=1.3
Expand Down Expand Up @@ -84,7 +73,7 @@ magpy.show(*coll, sensor)
coll_meshed = mesh_all(
coll, target_elems=50, per_child_elems=False, style_label="No demag - meshed"
)
coll_meshed.show()
magpy.show(coll_meshed)
```

## Compute material response - demagnetization
Expand All @@ -111,15 +100,16 @@ for target_elems in [1, 2, 8, 16, 32, 64, 128, 256]:
## Compare with FEM analysis

```{code-cell} ipython3
# compute field before demag
B_no_demag_df = magpy.getB(coll_meshed, sensor, output="dataframe")
# template dataframe with the right index/path structure; the FEM values
# from the reference dataset are filled into a copy of it below
B_template_df = magpy.getB(coll_meshed, sensor, output="dataframe")

B_cols = ["Bx", "By", "Bz"]


def get_FEM_dataframe(sim):
res = sim["results"][0]
df = B_no_demag_df.copy()
df = B_template_df.copy()
for Bk in B_cols:
df[Bk] = res["value"].get(Bk, np.nan)
df["computation"] = res["computation"]
Expand Down Expand Up @@ -182,3 +172,7 @@ display(fig1, fig2)

As shown above, already with a low number of mesh elements, the result is
approaching the reference FEM values and improves while refining the mesh.

For larger meshes the default dense solver becomes the bottleneck — see
[solvers and performance](solver_performance.md) for the FFT-accelerated
iterative solver and guidance on choosing between them.
25 changes: 21 additions & 4 deletions docs/examples/index.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
# Examples

This section contains examples demonstrating the use of
Magpylib-Material-Response.
Worked, executable examples — in suggested reading order:

- [Cuboid demagnetization](cuboids_demagnetization.md) — two hard cuboid
magnets; mesh-refinement convergence against FEM reference data.
- [Soft magnets](soft_magnets.md) — a hard magnet next to a high-permeability
soft cuboid; field comparison against FEM.
- [U-core electromagnet](ucore_electromagnet.md) — current sources driving a
soft core: a swept multi-turn coil built with the polyline helpers, the field
on a plane above the poles, and a cuboid- vs tetrahedral-mesh comparison.
- [Solvers and performance](solver_performance.md) — direct vs. iterative
solver: agreement guarantee, scaling of wall time and memory, and how the
model topology decides which interaction paths do the work.
- [Arbitrary shapes with tetrahedral meshes](tetrahedral_meshes.md) — meshing a
`TriangularMesh` magnet with TetGen, validated against the exact soft-sphere
solution.

```{toctree}
:maxdepth: 1
:glob:
:hidden:

*
cuboids_demagnetization
soft_magnets
ucore_electromagnet
solver_performance
tetrahedral_meshes
```
Loading