Validation¶
Cross-validation of the Elmer solve against analytical magpylib fields, a sanity bound on the result.
femforge.validation
¶
Cross-validation of the Elmer solve against magpylib's analytical fields, a sanity bound rather than an equality check.
Modules:
-
compare–Spot-check error metrics between two vector fields sampled at the same points.
-
field–Sample the magnetic flux density B from magpylib or from a solved Elmer VTU at a common set of points.
-
sources–Analytical magnet sources: a
DomainConfigand built geometry turned into SIMagnetSources for magpylib. -
viz–PyVista rendering of the analytical magpylib field, drawn like the solved Elmer result so both can share a
viz.Scene.
Classes:
-
MagnetSource–One uniformly-magnetized cuboid magnet, in SI units, ready to hand to magpylib.
-
FieldComparison–Aggregate error of a test field versus a reference field sampled at the same points.
-
MagpylibField–A
viz.scene.Renderablethat draws the analytical magpylib field (|B| streamlines + magnet boxes) for a set ofMagnetSources. -
GridBoundsMode–Whether a sampling grid stays inside its target bounds or covers them.
Functions:
-
magnet_source–Build the
MagnetSourcefor one body, or None if it is not a magnet; a magnet with no usableMagnetizationdirection raisesValueError. -
sources_from_assembly–Walk a built build123d assembly and return a
MagnetSourceper magnet leaf, reading only its label and bounding box (mm → m). -
material_for_label–The material whose
tagequals (casefold) the label'sMaterialtag value, or None — mirrorsMeshEntity._match_material. -
magnetization_for_label–The usable (directed)
Magnetizationcarried by anEntityTagmatching the label's tags, or None — mirrorselmer.sim's magnet wiring. -
gap_line–A line of
samplespoints along Z through the centroid of the magnet centres — for a dual-Halbach array it threads the air gap. -
sample_magpylib_b–Evaluate the analytical B (tesla) of
sourcesat each point: one SI magpylibCuboidper source, superposed viaCollection.getB. -
sample_vtu_b–Interpolate the solved Elmer B (tesla) at
pointsfrom acase.vtu(pyvistasample; elemental arrays are projected to point data first). -
extract_vtu_b–Extract the solved Elmer B (tesla) from a
case.vtuas(positions, B)arrays of shape(n_points, 3). -
compare_fields–Compare two
(N, 3)fields sampled at the samepointsand return aFieldComparison; raisesValueErroron mismatched or empty inputs. -
grid_spec–The
(origin, spacing, dimensions)of apv.ImageDatasamplingboundswith near-cubic cells; raisesValueErrorfor fewer than 8 samples or a zero-volume box. -
bounds_of_sources–The axis-aligned bounding box (metres) enclosing every source's full extent, grown by
paddingon each side; raisesValueErrorfor no sources. -
bounded_grid–A
pv.ImageDatagrid spanningboundswith ~cubic cells andsamplesin total, ready to carry a sampled vector field as point data. -
magpylib_field_grid–Sample the analytical B of
sourcesonto a regular grid over their padded bounding box, apv.ImageDataready for the streamline path. -
load_gmsh_mesh–Load a Gmsh
.msh(via meshio) into apv.UnstructuredGridwith the region names attached as the"Region_Name"cell-data array.
Attributes:
-
DEFAULT_PADDING–[m]: The padding around the magnets on each axis, so the field grid covers the surrounding air and not only the magnet volume.
DEFAULT_PADDING
module-attribute
¶
DEFAULT_PADDING = Vec3(0.05, 0.05, 0.01)
[m]: The padding around the magnets on each axis, so the field grid covers the surrounding air and not only the magnet volume.
MagnetSource
pydantic-model
¶
Bases: BaseModel
One uniformly-magnetized cuboid magnet, in SI units, ready to hand to magpylib.
polarization is J = Bᵣ · direction̂ (tesla) — the same physical drive Elmer applies via Magnetization (J = μ₀·M).
Config:
arbitrary_types_allowed:Trueuse_attribute_docstrings:True
Fields:
FieldComparison
pydantic-model
¶
Bases: BaseModel
Aggregate error of a test field versus a reference field sampled at the same points.
Errors are on |B_test − B_ref| in tesla; the headline metric is the field-weighted rel_rms_error.
Config:
arbitrary_types_allowed:Trueuse_attribute_docstrings:True
Fields:
-
n_points(int) -
rms_error(float) -
max_abs_error(float) -
max_error_point(Vec3Field) -
ref_rms(float) -
test_rms(float) -
rel_rms_error(float | None) -
median_relative_error(float | None) -
mean_relative_error(float | None)
rms_error
pydantic-field
¶
rms_error: float
Root-mean-square of |B_test − B_ref| over the points (tesla).
max_error_point
pydantic-field
¶
max_error_point: Vec3Field
The sample point where max_abs_error occurred (metres) — where to look first when the fields disagree.
ref_rms
pydantic-field
¶
ref_rms: float
RMS |B_ref| over the points (tesla): the reference field scale, so rms_error is interpretable.
rel_rms_error
pydantic-field
¶
rel_rms_error: float | None = None
The headline metric. Field-weighted relative error rms(|ΔB|) / rms(|B_ref|); robust to low-field points. None if every reference point is zero.
median_relative_error
pydantic-field
¶
median_relative_error: float | None = None
Median of |B_test − B_ref| / |B_ref| over points with non-zero reference, or None if every reference point is zero. The robust middle of the per-point distribution.
mean_relative_error
pydantic-field
¶
mean_relative_error: float | None = None
Mean of |B_test − B_ref| / |B_ref| over points with non-zero reference, or None if every reference point is zero. Outlier-sensitive — prefer rel_rms_error.
summary
¶
summary() -> str
A human-readable multi-line report for the spot-check script.
Source code in src/femforge/validation/compare.py
MagpylibField
dataclass
¶
MagpylibField(sources: list[MagnetSource], options: FieldLineOptions | None = None, padding: Vec3 = DEFAULT_PADDING, samples: int = 32, show_magnets: bool = True)
A viz.scene.Renderable that draws the analytical magpylib field (|B| streamlines + magnet boxes) for a set of MagnetSources.
Sampling and streamline integration run in __post_init__ (whichever worker thread constructs it); render only draws precomputed actors, keeping the plotter thread free.
Methods:
-
render–Draw the precomputed magpylib field lines (and, when
show_magnets, the magnet bodies) intoplotter(on the plotter thread).
Attributes:
-
sources(list[MagnetSource]) –The magnets whose analytical field is drawn.
-
options(FieldLineOptions | None) –The streamline options; None uses the defaults.
-
padding(Vec3) –[m]: The padding around the magnets on each axis that the field grid covers. -
samples(int) –The approximate total point count of the field grid.
-
show_magnets(bool) –Whether to draw the magnet boxes alongside the streamlines.
sources
instance-attribute
¶
sources: list[MagnetSource]
The magnets whose analytical field is drawn.
options
class-attribute
instance-attribute
¶
options: FieldLineOptions | None = None
The streamline options; None uses the defaults.
padding
class-attribute
instance-attribute
¶
padding: Vec3 = DEFAULT_PADDING
[m]: The padding around the magnets on each axis that the field grid covers.
samples
class-attribute
instance-attribute
¶
samples: int = 32
The approximate total point count of the field grid.
show_magnets
class-attribute
instance-attribute
¶
show_magnets: bool = True
Whether to draw the magnet boxes alongside the streamlines.
render
¶
Draw the precomputed magpylib field lines (and, when show_magnets, the magnet bodies) into plotter (on the plotter thread).
Source code in src/femforge/validation/viz.py
GridBoundsMode
¶
Bases: Enum
Whether a sampling grid stays inside its target bounds or covers them.
Attributes:
magnet_source
¶
magnet_source(label: str, center_m: Vec3, size_m: Vec3, domain: DomainConfig, *, at: Mapping[str, Quantity] | None = None) -> MagnetSource | None
Build the MagnetSource for one body, or None if it is not a magnet; a magnet with no usable Magnetization direction raises ValueError.
Parameters:
-
label(str) –The body's label, carrying its tags.
-
center_m(Vec3) –The body's center, in metres.
-
size_m(Vec3) –The body's axis-aligned extent, in metres.
-
domain(DomainConfig) –The domain config whose materials and tags the label matches.
-
at(Mapping[str, Quantity] | None, default:None) –The operating point the remanence is evaluated at; None is room temperature.
Source code in src/femforge/validation/sources.py
sources_from_assembly
¶
sources_from_assembly(assembly: Compound, domain: DomainConfig, *, at: Mapping[str, Quantity] | None = None) -> list[MagnetSource]
Walk a built build123d assembly and return a MagnetSource per magnet leaf, reading only its label and bounding box (mm → m).
Parameters:
-
assembly(Compound) –The built assembly, whose magnets turn only by multiples of 90° and so stay axis-aligned.
-
domain(DomainConfig) –The domain config whose materials and tags the leaf labels match.
-
at(Mapping[str, Quantity] | None, default:None) –The operating point the remanence is evaluated at; None is room temperature.
Source code in src/femforge/validation/sources.py
material_for_label
¶
material_for_label(label: str, materials: list[MaterialProperties]) -> MaterialProperties | None
The material whose tag equals (casefold) the label's Material tag value, or None — mirrors MeshEntity._match_material.
Source code in src/femforge/validation/sources.py
magnetization_for_label
¶
magnetization_for_label(label: str, tags: list[EntityTag]) -> Magnetization | None
The usable (directed) Magnetization carried by an EntityTag matching the label's tags, or None — mirrors elmer.sim's magnet wiring.
Source code in src/femforge/validation/sources.py
gap_line
¶
gap_line(sources: list[MagnetSource], samples: int) -> list[Vec3]
A line of samples points along Z through the centroid of the magnet centres — for a dual-Halbach array it threads the air gap.
Returns a single mid-point when samples < 2; raises ValueError for an empty list.
Source code in src/femforge/validation/field.py
sample_magpylib_b
¶
Evaluate the analytical B (tesla) of sources at each point: one SI magpylib Cuboid per source, superposed via Collection.getB.
Source code in src/femforge/validation/field.py
sample_vtu_b
¶
Interpolate the solved Elmer B (tesla) at points from a case.vtu (pyvista sample; elemental arrays are projected to point data first).
Source code in src/femforge/validation/field.py
extract_vtu_b
¶
Extract the solved Elmer B (tesla) from a case.vtu as (positions, B) arrays of shape (n_points, 3).
Source code in src/femforge/validation/field.py
compare_fields
¶
compare_fields(points: NDArray[float64], b_reference: NDArray[float64], b_test: NDArray[float64]) -> FieldComparison
Compare two (N, 3) fields sampled at the same points and return a FieldComparison; raises ValueError on mismatched or empty inputs.
Parameters:
-
points(NDArray[float64]) –The
(N, 3)sample points, in metres. -
b_reference(NDArray[float64]) –The reference field at
points; points where it is exactly zero are left out of the relative errors. -
b_test(NDArray[float64]) –The field under test at
points.
Source code in src/femforge/validation/compare.py
grid_spec
¶
grid_spec(bounds: Bounds, samples: int, mode: GridBoundsMode = OUTER) -> tuple[Point3, Point3, tuple[int, int, int]]
The (origin, spacing, dimensions) of a pv.ImageData sampling bounds with near-cubic cells; raises ValueError for fewer than 8 samples or a zero-volume box.
Parameters:
-
bounds(Bounds) –The target box, as
(xmin, xmax, ymin, ymax, zmin, zmax). -
samples(int) –The approximate total point count.
-
mode(GridBoundsMode, default:OUTER) –Keep the sampled extent inside the bounds (
INNER) or covering them (OUTER).
Source code in src/femforge/validation/viz.py
bounds_of_sources
¶
bounds_of_sources(sources: list[MagnetSource], *, padding: Vec3) -> Bounds
The axis-aligned bounding box (metres) enclosing every source's full extent, grown by padding on each side; raises ValueError for no sources.
Source code in src/femforge/validation/viz.py
bounded_grid
¶
A pv.ImageData grid spanning bounds with ~cubic cells and samples in total, ready to carry a sampled vector field as point data.
Source code in src/femforge/validation/viz.py
magpylib_field_grid
¶
magpylib_field_grid(sources: list[MagnetSource], *, padding: Vec3 = DEFAULT_PADDING, samples: int = 32) -> ImageData
Sample the analytical B of sources onto a regular grid over their padded bounding box, a pv.ImageData ready for the streamline path.
Parameters:
-
sources(list[MagnetSource]) –The magnets.
-
padding(Vec3, default:DEFAULT_PADDING) –The air added around the magnets on each side, in metres.
-
samples(int, default:32) –The approximate total point count.
Source code in src/femforge/validation/viz.py
load_gmsh_mesh
¶
Load a Gmsh .msh (via meshio) into a pv.UnstructuredGrid with the region names attached as the "Region_Name" cell-data array.