Note
Go to the end to download the full example code.
Biplanar coil design¶
First example in the paper, showing a basic biplanar coil producing homogeneous field in a target region between the two coil planes.
import pyvista as pv
from bfieldtools._example_plots import point_cloud, set_view
pv.set_plot_theme("document")
PLOT = True
SAVE_FIGURES = False
SAVE_DIR = "./Biplanar coil/"
import numpy as np
import trimesh
from bfieldtools.mesh_conductor import MeshConductor
from bfieldtools.coil_optimize import optimize_streamfunctions
from bfieldtools.contour import scalar_contour
from bfieldtools.viz import plot_3d_current_loops
from bfieldtools.utils import load_example_mesh, combine_meshes
# Set unit, e.g. meter or millimeter.
# This doesn't matter, the problem is scale-invariant
scaling_factor = 1
# Load simple plane mesh that is centered on the origin
planemesh = load_example_mesh("10x10_plane_hires")
# Specify coil plane geometry
center_offset = np.array([0, 0, 0]) * scaling_factor
standoff = np.array([0, 5, 0]) * scaling_factor
# Create coil plane pairs
coil_plus = trimesh.Trimesh(
planemesh.vertices + center_offset + standoff, planemesh.faces, process=False
)
coil_minus = trimesh.Trimesh(
planemesh.vertices + center_offset - standoff, planemesh.faces, process=False
)
joined_planes = combine_meshes((coil_plus, coil_minus))
joined_planes = joined_planes.subdivide()
# Create mesh class object
coil = MeshConductor(
verts=joined_planes.vertices,
tris=joined_planes.faces,
fix_normals=True,
basis_name="suh",
N_suh=100,
)
Calculating surface harmonics expansion...
Computing the laplacian matrix...
Computing the mass matrix...
Set up target and stray field points
# Here, the target points are on a volumetric grid within a sphere
center = np.array([0, 0, 0]) * scaling_factor
sidelength = 2 * scaling_factor
n = 8
xx = np.linspace(-sidelength / 2, sidelength / 2, n)
yy = np.linspace(-sidelength / 2, sidelength / 2, n)
zz = np.linspace(-sidelength / 2, sidelength / 2, n)
X, Y, Z = np.meshgrid(xx, yy, zz, indexing="ij")
x = X.ravel()
y = Y.ravel()
z = Z.ravel()
target_points = np.array([x, y, z]).T
# Turn cube into sphere by rejecting points "in the corners"
target_points = (
target_points[np.linalg.norm(target_points, axis=1) < sidelength / 2] + center
)
# #Here, the stray field points are on a spherical surface
stray_radius = 20 * scaling_factor
stray_points_mesh = trimesh.creation.icosphere(subdivisions=3, radius=stray_radius)
stray_points = stray_points_mesh.vertices + center
n_stray_points = len(stray_points)
Plot geometry
if PLOT:
plotter = f = pv.Plotter(window_size=(800, 800))
plotter = coil.plot_mesh(style="wireframe", opacity=0.1, color=(0, 0, 0), figure=f)
plotter = coil.plot_mesh(style="surface", opacity=0.1, color=(0, 0, 0), figure=f)
plotter.add_mesh(
point_cloud(*target_points.T).glyph(
scale=False, geom=pv.Sphere(radius=0.05 / 2)
),
color=(0, 0, 1),
show_scalar_bar=False,
)
plotter.add_mesh(
point_cloud(*stray_points.T).glyph(scale=False, geom=pv.Sphere(radius=0.3 / 2)),
color=(1, 0, 0),
show_scalar_bar=False,
)
f.view_isometric()
f.camera.zoom(1.5)
if SAVE_FIGURES:
f.screenshot(SAVE_DIR + "biplanar_geometry.png", scale=4)
plotter.close()

/home/runner/work/bfieldtools.github.io/bfieldtools.github.io/examples/publication_software/pub_biplanar_coil_design.py:108: UserWarning: No vector-like data to use for orient. orient will be set to False.
point_cloud(*target_points.T).glyph(
/home/runner/work/bfieldtools.github.io/bfieldtools.github.io/examples/publication_software/pub_biplanar_coil_design.py:115: UserWarning: No vector-like data to use for orient. orient will be set to False.
point_cloud(*stray_points.T).glyph(scale=False, geom=pv.Sphere(radius=0.3 / 2)),
Create bfield specifications used when optimizing the coil geometry
# The absolute target field amplitude is not of importance,
# and it is scaled to match the C matrix in the optimization function
target_field = np.zeros(target_points.shape)
target_field[:, 0] += 1 # Homogeneous field on X-axis
target_spec = {
"coupling": coil.B_coupling(target_points),
"abs_error": 0.005,
"target": target_field,
}
stray_spec = {
"coupling": coil.B_coupling(stray_points),
"abs_error": 0.01,
"target": np.zeros((n_stray_points, 3)),
}
bfield_specification = [target_spec, stray_spec]
Computing magnetic field coupling matrix, 12442 vertices by 160 target points... took 1.34 seconds.
Computing magnetic field coupling matrix, 12442 vertices by 642 target points... took 5.32 seconds.
Run QP solver
coil.s, prob = optimize_streamfunctions(
coil,
[target_spec, stray_spec],
objective=(0, 1),
solver="CLARABEL",
)
Computing the resistance matrix...
Passing problem to solver...
===============================================================================
CVXPY
v1.9.2
===============================================================================
-------------------------------------------------------------------------------
Compilation
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Numerical solver
-------------------------------------------------------------------------------
-------------------------------------------------------------
Clarabel.rs v0.11.1 - Clever Acronym
(c) Paul Goulart
University of Oxford, 2022
-------------------------------------------------------------
problem:
variables = 100
constraints = 4812
nnz(P) = 5045
nnz(A) = 481200
cones (total) = 1
: Nonnegative = 1, numel = 4812
settings:
linear algebra: direct / faer, precision: 64 bit (4 threads)
max iter = 200, time limit = Inf, max step = 0.990
tol_feas = 1.0e-8, tol_gap_abs = 1.0e-8, tol_gap_rel = 1.0e-8,
static reg : on, ϵ1 = 1.0e-8, ϵ2 = 4.9e-32
dynamic reg: on, ϵ = 1.0e-13, δ = 2.0e-7
iter refine: on, reltol = 1.0e-13, abstol = 1.0e-12,
max iter = 10, stop ratio = 5.0
equilibrate: on, min_scale = 1.0e-4, max_scale = 1.0e4
max iter = 10
iter pcost dcost gap pres dres k/t μ step
---------------------------------------------------------------------------------------------
0 +1.4699e+02 -3.1572e+05 2.15e+03 2.33e-01 9.06e-18 1.00e+00 1.17e+02 ------
1 +2.7934e+02 -9.9609e+04 3.58e+02 7.90e-02 5.12e-17 7.11e+00 4.85e+01 6.64e-01
2 +5.8415e+02 -9.4337e+04 1.62e+02 6.00e-02 4.29e-17 1.03e+01 4.50e+01 2.18e-01
3 +1.7353e+03 -2.1981e+04 1.37e+01 9.22e-03 8.52e-17 3.97e+00 1.24e+01 8.00e-01
4 +2.0412e+03 -2.1671e+04 1.16e+01 8.59e-03 2.10e-17 4.20e+00 1.22e+01 9.15e-02
5 +2.3066e+03 -6.5609e+03 3.84e+00 3.03e-03 3.96e-17 1.73e+00 4.72e+00 6.84e-01
6 +2.5634e+03 -2.9345e+03 2.14e+00 1.79e-03 2.74e-17 1.25e+00 2.88e+00 6.65e-01
7 +2.4822e+03 -4.0640e+03 2.64e+00 2.16e-03 1.17e-16 1.71e+00 2.88e+00 2.98e-02
8 +8.9087e+03 -1.0046e+04 2.13e+00 1.57e-03 7.56e-16 5.46e+00 1.13e+00 9.33e-01
9 +7.8852e+03 -1.0787e+04 2.37e+00 1.59e-03 4.37e-16 5.81e+00 1.11e+00 1.62e-01
10 +1.3900e+04 +9.0757e+03 5.32e-01 1.85e-04 9.24e-17 1.47e+00 1.67e-01 8.80e-01
11 +1.5456e+04 +1.4413e+04 7.23e-02 3.40e-05 2.76e-17 4.29e-01 3.24e-02 9.90e-01
12 +1.6291e+04 +1.6142e+04 9.24e-03 3.91e-06 7.31e-17 5.64e-02 4.06e-03 8.88e-01
13 +1.6313e+04 +1.6197e+04 7.17e-03 3.03e-06 1.15e-16 4.44e-02 3.15e-03 4.38e-01
14 +1.6402e+04 +1.6398e+04 2.14e-04 8.40e-08 2.53e-16 1.29e-03 9.12e-05 9.72e-01
15 +1.6404e+04 +1.6404e+04 2.61e-06 1.12e-09 1.42e-16 1.57e-05 1.11e-06 9.89e-01
16 +1.6404e+04 +1.6404e+04 3.00e-08 5.85e-11 2.48e-16 1.80e-07 1.27e-08 9.89e-01
17 +1.6404e+04 +1.6404e+04 3.09e-10 6.54e-12 3.25e-17 1.84e-09 1.30e-10 9.90e-01
---------------------------------------------------------------------------------------------
Terminated with status = Solved
solve time = 2.722779804s
-------------------------------------------------------------------------------
Summary
-------------------------------------------------------------------------------
Plot coil windings and target points
N_contours = 6
loops = scalar_contour(coil.mesh, coil.s.vert, N_contours=N_contours)
if PLOT:
plotter = f = pv.Plotter(window_size=(650, 750))
plotter.clear()
plotter = plot_3d_current_loops(loops, colors="auto", figure=f)
f.view_isometric()
if SAVE_FIGURES:
f.screenshot(SAVE_DIR + "biplanar_loops.png", scale=4)
plotter.close()

Processing contour, value: -349520255.04506946
Processing contour, value: -209751115.80495754
Processing contour, value: -69981976.56484568
Processing contour, value: 69787162.67526627
Processing contour, value: 209556301.9153781
Processing contour, value: 349325441.15549004
Plot continuous stream function
if PLOT:
from bfieldtools.viz import plot_data_on_vertices
plotter = f = pv.Plotter(window_size=(800, 800))
plotter.clear()
plotter = plot_data_on_vertices(coil.mesh, coil.s.vert, figure=f, ncolors=256)
f.camera.parallel_projection = 1
set_view(plotter, 90, 90)
f.camera.zoom(1.5)
if SAVE_FIGURES:
f.screenshot(SAVE_DIR + "biplanar_streamfunction.png", scale=4)
plotter.close()
# Display the assembled scene (Sphinx-Gallery captures it during docs builds).
if "plotter" in globals() and not plotter._closed:
plotter.show()

Total running time of the script: (0 minutes 24.262 seconds)