Skip to content

Magnetometry

The magnetometry subpackage computes stray magnetic fields above a 2-D magnetic sample using the Biot-Savart law, and evaluates the resulting ODMR frequency shifts across a scan.


SpinDefectSim.magnetometry.geometry

Sample geometry classes define the spatial domain of a 2-D magnetic sample.


SampleGeometry (abstract)

from SpinDefectSim.magnetometry.geometry import SampleGeometry

Abstract base class. All concrete geometries derive from this.

Abstract interface

Member Description
boundary_vertices (property) CCW-ordered polygon vertices, shape (N_verts, 2), in metres
make_grid(n_pts) Return (xx, yy) meshgrid, each (n_pts, n_pts)

Concrete methods


edge_current_segments
def edge_current_segments(M_at_vertices: np.ndarray) -> tuple

Decompose the sample boundary into wire segments carrying edge currents proportional to the local magnetization.

Parameters:

Name Type Description
M_at_vertices np.ndarray (N_verts,) Magnetization magnitude at each boundary vertex (A)

Returns: (seg_start, seg_end, seg_current) — each (N_seg, 2), (N_seg, 2), (N_seg,) in SI


sample_M_at_vertices
def sample_M_at_vertices(
    magnetization: np.ndarray,
    *,
    xx: Optional[np.ndarray] = None,
    yy: Optional[np.ndarray] = None,
) -> np.ndarray

Interpolate a 2-D magnetization grid onto the boundary vertices.

Returns: np.ndarray shape (N_verts,) in A


bulk_current_density
def bulk_current_density(
    M_grid: np.ndarray,
    xx: np.ndarray,
    yy: np.ndarray,
) -> tuple[np.ndarray, np.ndarray]

Compute the bulk sheet current density K = ∇ × M_z at each grid point using finite differences.

Returns: (Kx, Ky) — each shape (ny, nx) in A/m


interior_mask
def interior_mask(xx: np.ndarray, yy: np.ndarray) -> np.ndarray

Boolean mask that is True inside the polygon and False outside.


PolygonGeometry

from SpinDefectSim.magnetometry.geometry import PolygonGeometry

Arbitrary closed polygon sample.

Constructor

PolygonGeometry(
    vertices: np.ndarray,
    n_boundary_pts: int = 0,
)

Parameters:

Name Type Description
vertices np.ndarray (N, 2) Polygon vertices in CCW order (m)
n_boundary_pts int If > 0, linearly interpolate to this many boundary points

SquareGeometry

from SpinDefectSim.magnetometry.geometry import SquareGeometry

Axis-aligned square sample.

Constructor

SquareGeometry(
    side: float,
    center: tuple = (0, 0),
    n_boundary_pts: int = 200,
)

Parameters:

Name Type Description
side float Side length (m)
center (float, float) Centre position (m)
n_boundary_pts int Number of boundary discretisation points

Example:

geom = SquareGeometry(side=2e-6, center=(0, 0))
xx, yy = geom.make_grid(n_pts=100)


DiskGeometry

from SpinDefectSim.magnetometry.geometry import DiskGeometry

Circular disk sample (approximated as a regular N-gon).

Constructor

DiskGeometry(
    radius: float,
    center: tuple = (0, 0),
    n_sides: int = 128,
)

Parameters:

Name Type Description
radius float Disk radius (m)
center (float, float) Centre position (m)
n_sides int Number of polygon sides (angular resolution)

Example:

geom = DiskGeometry(radius=1e-6)
xx, yy = geom.make_grid(n_pts=80)
mask = geom.interior_mask(xx, yy)


SpinDefectSim.magnetometry.bfield

Low-level Biot-Savart functions.


B_from_wire_segment

from SpinDefectSim.magnetometry.bfield import B_from_wire_segment

def B_from_wire_segment(
    r_start: np.ndarray,
    r_end: np.ndarray,
    current_A: float,
    r_obs_xyz: np.ndarray,
    *,
    r_min: float = 1e-15,
) -> np.ndarray

Analytic Biot-Savart result for a finite straight wire segment carrying a steady current.

Parameters:

Name Type Description
r_start np.ndarray (2,) or (3,) Start of the wire (m)
r_end np.ndarray (2,) or (3,) End of the wire (m)
current_A float Current (A)
r_obs_xyz np.ndarray (3,) Observation point (m)
r_min float Regulariser to avoid 1/r singularity (m)

Returns: np.ndarray shape (3,) in T


B_from_edge_segments

from SpinDefectSim.magnetometry.bfield import B_from_edge_segments

def B_from_edge_segments(
    seg_start: np.ndarray,
    seg_end: np.ndarray,
    seg_current: np.ndarray,
    r_obs_xyz: np.ndarray,
    *,
    r_min: float = 1e-15,
) -> np.ndarray

Vectorised Biot-Savart sum over all boundary wire segments (edge currents from magnetization).

Parameters:

Name Type Description
seg_start np.ndarray (N_seg, 2-3) Segment start points (m)
seg_end np.ndarray (N_seg, 2-3) Segment end points (m)
seg_current np.ndarray (N_seg,) Segment currents (A)
r_obs_xyz np.ndarray (3,) Observation point (m)

Returns: np.ndarray shape (3,) in T


B_from_bulk_current_density

from SpinDefectSim.magnetometry.bfield import B_from_bulk_current_density

def B_from_bulk_current_density(
    xx: np.ndarray,
    yy: np.ndarray,
    Kx: np.ndarray,
    Ky: np.ndarray,
    r_obs_xyz: np.ndarray,
    *,
    mask: Optional[np.ndarray] = None,
    r_min: float = 1e-15,
) -> np.ndarray

Compute the B-field contribution from a 2-D sheet current density K(r) = (Kx, Ky) A/m.

Parameters:

Name Type Description
xx, yy np.ndarray (ny, nx) Coordinate meshgrids (m)
Kx, Ky np.ndarray (ny, nx) Sheet current density components (A/m)
r_obs_xyz np.ndarray (3,) Observation point (m)
mask np.ndarray (ny, nx) bool \| None Only include grid points where mask is True

Returns: np.ndarray shape (3,) in T


B_from_magnetization_grid

from SpinDefectSim.magnetometry.bfield import B_from_magnetization_grid

def B_from_magnetization_grid(
    geom: SampleGeometry,
    M_grid: np.ndarray,
    r_obs_xyz: np.ndarray,
    *,
    n_pts: int = 100,
    include_bulk: bool = True,
    include_edge: bool = True,
    erode_bulk_boundary: bool = False,
    r_min: float = 1e-15,
) -> np.ndarray

Total B-field from a 2-D out-of-plane magnetization M_z distribution, combining edge and bulk current contributions.

Parameters:

Name Type Description
geom SampleGeometry Sample geometry
M_grid np.ndarray (ny, nx) M_z grid (A, or equivalently surface current equivalent)
r_obs_xyz np.ndarray (3,) Observation point (m)
n_pts int Grid resolution for bulk integration
include_bulk bool Include ∇×M bulk term
include_edge bool Include boundary edge term
erode_bulk_boundary bool Exclude the outermost grid row from bulk to reduce double-counting at the edge

Returns: np.ndarray shape (3,) in T


SpinDefectSim.magnetometry.magnetometry

MagnetometryExperiment

from SpinDefectSim.magnetometry.magnetometry import MagnetometryExperiment

Computes stray B-field maps and ODMR observables above a 2-D magnetic sample.

Inherits: PhysicalParams


Constructor

MagnetometryExperiment(
    geometry: SampleGeometry,
    magnetization: Union[np.ndarray, Callable[[float, float], float]],
    defaults: Optional[Defaults] = None,
    *,
    z_defect: Optional[float] = None,
    n_pts: int = 100,
    include_bulk: bool = True,
    include_edge: bool = True,
    bias_B_T: Optional[np.ndarray] = None,
)

Parameters:

Name Type Description
geometry SampleGeometry Sample geometry object
magnetization np.ndarray (ny, nx) \| Callable M_z grid (A) or callable (x, y) → float
defaults Defaults \| None Global defaults
z_defect float \| None Defect height above sample surface (m)
n_pts int Grid resolution for Biot-Savart integration
include_bulk bool Include bulk ∇×M contribution
include_edge bool Include edge current contribution
bias_B_T np.ndarray (3,) \| None Uniform bias B-field (T) applied to all defects

B_field

def B_field(
    x_obs: float,
    y_obs: float,
    z_obs: Optional[float] = None,
) -> np.ndarray

Stray B-field at a single point.

Returns: np.ndarray shape (3,) in T


transition_frequencies

def transition_frequencies(
    x_obs: float,
    y_obs: float,
    z_obs: Optional[float] = None,
) -> np.ndarray

ODMR transition frequencies at a single point.

Returns: np.ndarray shape (2,) in Hz


B_field_map

def B_field_map(
    x_obs: np.ndarray,
    y_obs: np.ndarray,
    z_obs: Optional[float] = None,
) -> np.ndarray

Stray B-field on a 2-D scan grid.

Parameters:

Name Type Description
x_obs np.ndarray 1-D x coordinates (m)
y_obs np.ndarray 1-D y coordinates (m)
z_obs float \| None z height (m)

Returns: np.ndarray shape (Ny, Nx, 3) in T


B_z_map

def B_z_map(
    x_obs: np.ndarray,
    y_obs: np.ndarray,
    z_obs: Optional[float] = None,
) -> np.ndarray

Bz component on a 2-D scan grid.

Returns: np.ndarray shape (Ny, Nx) in T


transition_frequency_map

def transition_frequency_map(
    x_obs: np.ndarray,
    y_obs: np.ndarray,
    z_obs: Optional[float] = None,
) -> np.ndarray

ODMR transition frequencies on a 2-D scan grid.

Returns: np.ndarray shape (Ny, Nx, 2) in Hz


odmr_spectrum

def odmr_spectrum(
    f_axis: np.ndarray,
    x_obs: float,
    y_obs: float,
    z_obs: Optional[float] = None,
    *,
    linewidth_Hz: Optional[float] = None,
    contrast: Optional[float] = None,
) -> np.ndarray

CW ODMR PL spectrum at a single point.

Parameters:

Name Type Description
f_axis np.ndarray MW frequency axis (Hz)
x_obs, y_obs float Observation point (m)
linewidth_Hz float \| None Lorentzian FWHM; uses defaults if None
contrast float \| None ODMR contrast; uses defaults if None

Returns: np.ndarray normalised PL, same shape as f_axis


frequency_shift_map

def frequency_shift_map(
    x_obs: np.ndarray,
    y_obs: np.ndarray,
    z_obs: Optional[float] = None,
    *,
    reference_xy: tuple = (1e6, 1e6),
    which: int = 0,
) -> np.ndarray

Map of ODMR frequency shift Δf(x, y) = f(x, y) − f(reference), useful for imaging relative magnetic contrast.

Parameters:

Name Type Description
x_obs, y_obs np.ndarray 1-D coordinate arrays (m)
reference_xy (float, float) Reference position (far from the sample)
which int Which transition to plot: 0 (lower) or 1 (upper)

Returns: np.ndarray shape (Ny, Nx) in Hz

Example:

import numpy as np
from SpinDefectSim.magnetometry.geometry import SquareGeometry
from SpinDefectSim.magnetometry.magnetometry import MagnetometryExperiment

# 2-µm square sample with uniform magnetization
geom = SquareGeometry(side=2e-6)
xx, yy = geom.make_grid(100)
M = np.ones_like(xx) * 1e-3   # 1 mA equivalent

exp = MagnetometryExperiment(geom, M, z_defect=50e-9)

# Scan from −3 µm to +3 µm
x = np.linspace(-3e-6, 3e-6, 60)
y = np.linspace(-3e-6, 3e-6, 60)

Bz = exp.B_z_map(x, y)           # (60, 60) array in T
dfreq = exp.frequency_shift_map(x, y)  # (60, 60) array in Hz