Magnets and arrangements¶
Magnet¶
from microcubed import Magnet
magnet = Magnet(
size=[100, 50, 20],
center=[10, 0, -5],
magnetization=[0, 8e5, 0],
)
All three inputs are stored internally as column vectors with shape (3, 1).
Zero or negative dimensions are not allowed.
Important properties:
Property |
Meaning |
|---|---|
|
edge lengths |
|
cuboid centre |
|
magnetisation in A/m |
|
volume in the third power of the length unit |
|
bounding box with shape |
|
eight corners with shape |
|
internal field prefactor |
Constructing a magnet from two bounding-box points:
magnet = Magnet.from_bbox(
p1=[-50, -25, -10],
p2=[50, 25, 10],
magnetization=[0, 8e5, 0],
)
Geometric operations¶
Operations return new objects and do not modify the original:
moved = magnet.moved_by([100, 0, 0])
centered = magnet.moved_to([0, 0, 0])
mirrored = magnet.mirrored(x=0, z=10)
Testing contact and volume overlap:
magnet.touching(moved)
magnet.overlapping(moved)
Use the union boundary to obtain the projected material outline:
xy_outline = magnet.union_boundary("xy")
Arrangement¶
Fields from multiple magnets are added by superposition:
from microcubed import Arrangement, Magnet
prototype = Magnet([100, 100, 50], [0, 0, 0], [0, 0, 1e6])
arrangement = Arrangement(
[
prototype.moved_to([-150, 0, 0]),
prototype,
prototype.moved_to([150, 0, 0]),
]
)
B = arrangement.Bfield([0, 0, -200])
With validate=True, construction checks for overlapping cuboids. The check
scales quadratically with the number of magnets. It can be disabled for
geometries loaded from a previously verified grid:
arrangement = Arrangement(magnets, validate=False)
Arrangements support iteration, length, indexing, slices, and +/- with
magnets and other arrangements.
Geometry generators¶
Arrangement can approximate selected 2D cross-sections with cuboid strips:
Constructor |
Cross-section |
|---|---|
|
circle |
|
ellipse |
|
rounded rectangle |
|
superellipse |
|
right triangle |
Example:
ellipse = Arrangement.from_ellipsis(
size=[400, 200, 30],
center=[0, 0, 0],
mag=[0, 0, 8e5],
dx=5,
)
dx controls strip width and therefore geometry error, runtime, and memory.
Scientific use requires a convergence study over multiple dx values.
Arbitrary 2D shapes¶
cuboidize rasterizes an arbitrary two-dimensional shape, merges occupied
cells into large rectangles, and extrudes those rectangles by a thickness
t. The result is a normal Arrangement and can immediately be used for
field and gradient calculations:
from microcubed import cuboidize
polygon = [
(-200, -100),
(200, -100),
(200, 0),
(50, 0),
(50, 150),
(-200, 150),
]
magnet = cuboidize(
polygon,
t=30,
delta=5,
mag=[0, 0, 8e5],
z=15,
)
B = magnet.Bfield([0, 0, -100])
The equivalent class constructor is
Arrangement.from_shape(shape, t, delta, mag, ...).
Supported shape representations are:
an
(N, 2)array or sequence of polygon vertices;a
matplotlib.path.Path; ora callable
shape(x, y)returning a Boolean mask. A callable requiresbounds=(xmin, ymin, xmax, ymax).
For example, an annulus can be represented without constructing polygon vertices:
def annulus(x, y):
radius_squared = x**2 + y**2
return (radius_squared <= 200**2) & (radius_squared >= 80**2)
ring = cuboidize(
annulus,
t=20,
delta=(5, 5),
mag=[1e6, 0, 0],
bounds=(-200, -200, 200, 200),
)
delta is either a scalar or (dx, dy) and specifies the maximum sampling
cell size. A cell belongs to the approximation when its centre is inside the
shape. The occupied cells are then partitioned by repeatedly selecting the
largest remaining rectangle. The produced cuboids are non-overlapping and
preserve the rasterized area exactly.
The greedy rectangle partition is deterministic and usually reduces the
number of magnets substantially compared with one cuboid per occupied cell.
It does not guarantee the global minimum number for every possible binary
shape; finding that optimum is a combinatorial covering problem. The geometric
boundary remains a centre-sampled approximation, so scientific calculations
should be repeated with progressively smaller delta.
See Examples for a self-contained polygon decomposition notebook.
Voronoi grain structures¶
Voronoi grains can be generated in rectangular 2D or cuboid 3D domains. The
requested grain_size is interpreted as an equivalent circle or sphere
diameter and is converted automatically into a seed count:
from microcubed import cuboidize_voronoi, generate_voronoi_grains
grains = generate_voronoi_grains(
size=(100, 60, 20),
grain_size=10,
cell_size=2,
origin=(-50, -30, -10),
seed=42,
)
magnet = cuboidize_voronoi(grains, mag=[0, 0, 8e5])
print(grains.grain_count)
print(grains.equivalent_diameters)
For a polygon, Path, or callable 2D geometry, use
generate_voronoi_grains_from_shape. This first rasterizes the full shape,
creates one global Voronoi tessellation inside its occupied mask, and only
then merges each grain into cuboids. This ordering avoids artificial grain
boundaries at geometry-decomposition seams.
Parallel calculation¶
Use get_backend("rust").Arrangement for the integrated parallel implementation.
See Calculation backends for selection and comparisons with NumPy.
Union boundary¶
union_boundary(plane="xy", atol=None) is available on both Magnet and
Arrangement, with either numerical backend. It returns the boundary of the
union of projected cuboid footprints, retaining concavities, disconnected
pieces, and holes. Shared internal edges disappear; overlapping footprints merge.
for boundary in arrangement.union_boundary("xy"):
ax.plot(*boundary, color="cyan", label="Material boundary")
Each returned array has shape (2, N) and repeats its first vertex at the end.
Outer rings run counterclockwise and hole rings clockwise. Corner-touching
components have separate rings; an empty arrangement returns []. The list is
not a hierarchy associating holes with their enclosing components.
Supported planes are xy, xz, and yz. Reversed aliases such as yx use
canonical axis order (x, then y). This is a projection, not a slice: cuboids
separated in z can still have overlapping XY footprints.
By default, coordinates within 32 machine epsilons times the largest absolute
projected coordinate are snapped together to remove floating-point seams.
Use atol=0 for exact coordinate comparisons or supply an absolute tolerance
in the geometry’s length unit. Features smaller than the snapping tolerance may
collapse; use a tolerance well below any gap or feature you need to resolve.
The sweep keeps one coordinate-coverage vector in memory rather than allocating
a dense 2D raster; its worst-case running time is quadratic in the cuboid count.
See the polygon example for input-polygon and union-boundary overlays. Refining the source raster improves the union boundary’s approximation of the input polygon.
chull and chull_points remain available when a geometric envelope is
specifically required, for example in an external optimisation routine. They do
not describe an arrangement’s material footprint when it has gaps, holes, or
concavities.