Magnetometer#
Magnetometer — 3-axis geomagnetic field sensor.
The
magnetometerclass models a strapdown 3-axis magnetometer. It measures the local geomagnetic field vector expressed in the vehicle body frame: a fixed reference field in the navigation frame, rotated into the body frame by the true attitude, then corrupted by soft-iron and hard-iron distortion and sample-to-sample white Gaussian noise.- Parameters:
noise_std (float or array_like, optional) – Standard deviation of the field measurement noise, per body axis, in the same units as
field_intensity(dimensionless when the field is left normalized). A scalar is broadcast to all three axes. Defaults to0.02.hard_iron (array_like, optional) – Constant additive bias
[bx, by, bz](hard-iron offset), in the same units asfield_intensity. Defaults to[0, 0, 0].soft_iron (array_like, optional) –
3 x 3soft-iron distortion matrix applied to the body-frame field before the hard-iron offset and the noise. Defaults to the identity.field_intensity (float, optional) – Total intensity \(F\) of the reference geomagnetic field. Defaults to
1.0(the field is returned normalized). Set a real value (e.g.50e-6T or50for µT) to work in physical units —noise_stdandhard_ironthen carry the same unit.inclination (float, optional) – Inclination (dip) angle \(I\) of the reference field, [rad], positive pointing down. Defaults to
np.pi / 3(\(60^\circ\), a typical mid-latitude value).declination (float, optional) – Declination angle \(D\) of the reference field, [rad], positive east of north. Defaults to
0.isideal (bool, optional) – If
True, overridesnoise_std/hard_iron/soft_ironto produce an ideal (noise-free, distortion-free) magnetometer. The reference field itself is unaffected. Defaults toFalse.
Functionality
The reference field is built from its total intensity, inclination and declination and held fixed in the navigation frame associated with the state’s 3-2-1 Euler angles (
xforward/north,yright/east,zdown):\[m_{ref} = F \cdot [\cos I \cos D,\ \cos I \sin D,\ \sin I]^T\]At each sample, given the 12-state vector
\[X = [x, y, z, v_x, v_y, v_z, \varphi, \theta, \psi, p, q, r]^T\]the reference field is rotated into the body frame by the true attitude and distorted:
\[ \begin{align}\begin{aligned}m_{body} = [BI](\varphi, \theta, \psi) \cdot m_{ref}\\z = S_i \cdot m_{body} + b_i + n\end{aligned}\end{align} \]where \([BI]\) is the body-from-inertial 3-2-1 DCM (
dcm321), \(S_i\) is the soft-iron matrix, \(b_i\) is the hard-iron offset, and \(n\) is a zero-mean Gaussian vector with per-axis standard deviationnoise_std.measurereturns the full 3-axis body-frame field vector. A consumer that needs a heading derives it from the horizontal components after de-rotating roll and pitch (tilt compensation); a filter that consumes the vector directly (seeekf) needs no yaw-wrapping, because the measurement is linear in the rotated field rather than in the angle itself. In a typical setup the magnetometer is sampled at a lower rate than the IMU, e.g. \(50\,Hz\).Errors Model
The magnetometer measurement is subject to three error sources:
Hard iron: a constant additive offset[bx, by, bz](magnetized material fixed to the body). Set through thehard_ironparameter and unchanged between measurements. Defaults to[0, 0, 0].Soft iron: a constant3 x 3linear distortion \(S_i\) (nearby ferrous material that reshapes the field). Set through thesoft_ironparameter. Defaults to the identity.Noise: a zero-mean Gaussian sample, drawn independently per axis at every call tomeasure, with standard deviationnoise_std.
The errors model can be disabled by passing
isideal = Trueat construction, which forcesnoise_std = 0,hard_iron = [0, 0, 0]andsoft_iron = Iregardless of the arguments. Unlike theseekermodel, the magnetometer does not generate a random bias during construction; the supplied distortion is deterministic for a given instance.Construction
A magnetometer instance is created by making a direct call to the constructor:
>>> mag_sensor = c4d.sensors.magnetometer()
The noise, the hard-iron / soft-iron distortion and the reference-field geometry can all be specified when creating the sensor.
Examples
Import required packages:
>>> import c4dynamics as c4d >>> import numpy as np
True attitude
For the examples below, build a 12-state vector and set its attitude entries (indices 6, 7, 8 = roll, pitch, yaw):
>>> x_true = np.zeros(12) >>> x_true[8] = 0.5 # true heading [rad]
Ideal magnetometer
An ideal magnetometer can be created by muting the errors model. Level and heading north, it reads the reference field directly — horizontal component forward, vertical component (the dip) down:
>>> mag_ideal = c4d.sensors.magnetometer(isideal=True) >>> mag_ideal.measure(np.zeros(12)) [0.5 0. 0.866]
Rotating to a heading of
0.5rad swings the horizontal field into the bodyyaxis while its magnitude and the vertical component are preserved:>>> mag_ideal.measure(x_true) [0.439 -0.24 0.866]
Non-ideal magnetometer
A non-ideal magnetometer adds white measurement noise (and, optionally, hard-iron / soft-iron distortion). Set the random seed to make the example reproducible:
>>> np.random.seed(42) >>> mag_sensor = c4d.sensors.magnetometer(noise_std=0.02) >>> mag_sensor.measure(x_true) [0.449 -0.242 0.879]
Hard iron
The hard-iron offset is constant across all measurements. A magnetometer with a
[0.1, 0, 0]offset can be created as follows:>>> mag_hi = c4d.sensors.magnetometer(noise_std=0, hard_iron=[0.1, 0, 0]) >>> mag_ref = c4d.sensors.magnetometer(isideal=True) >>> mag_hi.measure(x_true) - mag_ref.measure(x_true) [0.1 0. 0.]
The difference between the measurement and the ideal field is the specified offset.
Soft iron
The soft-iron matrix scales / mixes the body-frame field. A diagonal
soft_ironwith a1.2gain on the bodyxaxis:>>> mag_si = c4d.sensors.magnetometer( ... noise_std=0, soft_iron=np.diag([1.2, 1.0, 1.0])) >>> mag_si.measure(np.zeros(12)) [0.6 0. 0.866]
Measurement noise
With no distortion, repeated measurements of the same state demonstrate the random per-axis noise generated at every call to
measure:>>> np.random.seed(1) >>> mag_noise = c4d.sensors.magnetometer(noise_std=0.02) >>> for _ in range(3): ... print(mag_noise.measure(x_true)) [0.471 -0.252 0.855] [0.417 -0.222 0.820] [0.474 -0.255 0.872]
Demo
The built-in
demomethod provides a compact demonstration of the magnetometer errors model and plots the true and measured body-frame field components through an attitude sweep:>>> fig = c4d.sensors.magnetometer.demo(show=True)
The same demonstration can be run without displaying the figure by using
show=False.Methods
magnetometer.measure(x_true)Measure the body-frame geomagnetic field.