View Javadoc
1   /*
2    * Copyright (C) 2022 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.navigation.inertial.calibration.magnetometer;
17  
18  import com.irurueta.algebra.AlgebraException;
19  import com.irurueta.algebra.Matrix;
20  import com.irurueta.algebra.Utils;
21  import com.irurueta.algebra.WrongSizeException;
22  import com.irurueta.navigation.LockedException;
23  import com.irurueta.navigation.NotReadyException;
24  import com.irurueta.navigation.inertial.BodyKinematics;
25  import com.irurueta.navigation.inertial.BodyMagneticFluxDensity;
26  import com.irurueta.navigation.inertial.calibration.CalibrationException;
27  import com.irurueta.navigation.inertial.calibration.MagneticFluxDensityTriad;
28  import com.irurueta.navigation.inertial.calibration.StandardDeviationBodyMagneticFluxDensity;
29  import com.irurueta.numerical.EvaluationException;
30  import com.irurueta.numerical.GradientEstimator;
31  import com.irurueta.numerical.fitting.FittingException;
32  import com.irurueta.numerical.fitting.LevenbergMarquardtMultiDimensionFitter;
33  import com.irurueta.numerical.fitting.LevenbergMarquardtMultiDimensionFunctionEvaluator;
34  import com.irurueta.statistics.MaxIterationsExceededException;
35  import com.irurueta.units.MagneticFluxDensity;
36  import com.irurueta.units.MagneticFluxDensityConverter;
37  import com.irurueta.units.MagneticFluxDensityUnit;
38  
39  import java.util.Collection;
40  
41  /**
42   * Abstract class to estimate magnetometer cross couplings and scaling factors.
43   * This calibrator uses Levenberg-Marquardt to find a minimum least squared
44   * error solution.
45   * <p>
46   * To use this calibrator at least 7 measurements taken at a single unknown
47   * and unknown orientations when common z-axis is assumed, otherwise at least 10
48   * measurements are required.
49   * <p>
50   * Measured magnetic flux density is assumed to follow the model shown below:
51   * <pre>
52   *     mBmeas = bm + (I + Mm) * mBtrue + w
53   * </pre>
54   * Where:
55   * - mBmeas is the measured magnetic flux density. This is a 3x1 vector.
56   * - bm is magnetometer hard-iron bias. Ideally, on a perfect magnetometer,
57   * this should be a 3x1 zero vector.
58   * - I is the 3x3 identity matrix.
59   * - Mm is the 3x3 soft-iron matrix containing cross-couplings and scaling
60   * factors. Ideally, on a perfect magnetometer, this should be a 3x3 zero
61   * matrix.
62   * - mBtrue is ground-truth magnetic flux density. This is a 3x1 vector.
63   * - w is measurement noise. This is a 3x1 vector.
64   * Notice that this calibrator assumes that all measurements are taken in
65   * a short span of time where Earth magnetic field can be assumed to be
66   * constant at provided location and instant.
67   *
68   * @param <C> Calibrator type.
69   * @param <L> Listener type.
70   */
71  public class BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator<
72          C extends BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator<?, ?>,
73          L extends BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibratorListener<C>>
74          implements MagnetometerNonLinearCalibrator, KnownHardIronMagnetometerCalibrator,
75          UnorderedStandardDeviationBodyMagneticFluxDensityMagnetometerCalibrator {
76  
77      /**
78       * Indicates whether by default a common z-axis is assumed for the accelerometer,
79       * gyroscope and magnetometer.
80       */
81      public static final boolean DEFAULT_USE_COMMON_Z_AXIS = false;
82  
83      /**
84       * Number of unknowns when common z-axis is assumed for the accelerometer,
85       * gyroscope and magnetometer.
86       */
87      private static final int COMMON_Z_AXIS_UNKNOWNS = 6;
88  
89      /**
90       * Number of unknowns for the general case.
91       */
92      private static final int GENERAL_UNKNOWNS = 9;
93  
94      /**
95       * Required minimum number of measurements when common z-axis is assumed.
96       */
97      public static final int MINIMUM_MEASUREMENTS_COMMON_Z_AXIS = COMMON_Z_AXIS_UNKNOWNS + 1;
98  
99      /**
100      * Required minimum number of measurements for the general case.
101      */
102     public static final int MINIMUM_MEASUREMENTS_GENERAL = GENERAL_UNKNOWNS + 1;
103 
104     /**
105      * Ground truth magnetic flux density norm to be expected at location where measurements have been made,
106      * expressed in Teslas (T).
107      */
108     protected Double groundTruthMagneticFluxDensityNorm;
109 
110     /**
111      * Levenberg-Marquardt fitter to find a non-linear solution.
112      */
113     private final LevenbergMarquardtMultiDimensionFitter fitter = new LevenbergMarquardtMultiDimensionFitter();
114 
115     /**
116      * Known x-coordinate of hard-iron bias to be used to find a solution.
117      * This is expressed in Teslas (T).
118      */
119     private double hardIronX;
120 
121     /**
122      * Known y-coordinate of hard-iron bias to be used to find a solution.
123      * This is expressed in Teslas (T).
124      */
125     private double hardIronY;
126 
127     /**
128      * Known z-coordinate of hard-iron bias to be used to find a solution.
129      * This is expressed in Teslas (T).
130      */
131     private double hardIronZ;
132 
133     /**
134      * Initial x scaling factor.
135      */
136     private double initialSx;
137 
138     /**
139      * Initial y scaling factor.
140      */
141     private double initialSy;
142 
143     /**
144      * Initial z scaling factor.
145      */
146     private double initialSz;
147 
148     /**
149      * Initial x-y cross coupling error.
150      */
151     private double initialMxy;
152 
153     /**
154      * Initial x-z cross coupling error.
155      */
156     private double initialMxz;
157 
158     /**
159      * Initial y-x cross coupling error.
160      */
161     private double initialMyx;
162 
163     /**
164      * Initial y-z cross coupling error.
165      */
166     private double initialMyz;
167 
168     /**
169      * Initial z-x cross coupling error.
170      */
171     private double initialMzx;
172 
173     /**
174      * Initial z-y cross coupling error.
175      */
176     private double initialMzy;
177 
178     /**
179      * Contains a collection of body magnetic flux density measurements taken
180      * at a given position with different unknown orientations and containing the
181      * standard deviation of magnetometer measurements.
182      */
183     private Collection<StandardDeviationBodyMagneticFluxDensity> measurements;
184 
185     /**
186      * This flag indicates whether z-axis is assumed to be common for accelerometer,
187      * gyroscope and magnetometer.
188      * When enabled, this eliminates 3 variables from Mm matrix.
189      */
190     private boolean commonAxisUsed = DEFAULT_USE_COMMON_Z_AXIS;
191 
192     /**
193      * Listener to handle events raised by this calibrator.
194      */
195     private L listener;
196 
197     /**
198      * Estimated magnetometer soft-iron matrix containing scale factors
199      * and cross coupling errors.
200      * This is the product of matrix Tm containing cross coupling errors and Km
201      * containing scaling factors.
202      * So tat:
203      * <pre>
204      *     Mm = [sx    mxy  mxz] = Tm*Km
205      *          [myx   sy   myz]
206      *          [mzx   mzy  sz ]
207      * </pre>
208      * Where:
209      * <pre>
210      *     Km = [sx 0   0 ]
211      *          [0  sy  0 ]
212      *          [0  0   sz]
213      * </pre>
214      * and
215      * <pre>
216      *     Tm = [1          -alphaXy    alphaXz ]
217      *          [alphaYx    1           -alphaYz]
218      *          [-alphaZx   alphaZy     1       ]
219      * </pre>
220      * Hence:
221      * <pre>
222      *     Mm = [sx    mxy  mxz] = Tm*Km =  [sx             -sy * alphaXy   sz * alphaXz ]
223      *          [myx   sy   myz]            [sx * alphaYx   sy              -sz * alphaYz]
224      *          [mzx   mzy  sz ]            [-sx * alphaZx  sy * alphaZy    sz           ]
225      * </pre>
226      * This instance allows any 3x3 matrix however, typically alphaYx, alphaZx and alphaZy
227      * are considered to be zero if the accelerometer z-axis is assumed to be the same
228      * as the body z-axis. When this is assumed, myx = mzx = mzy = 0 and the Mm matrix
229      * becomes upper diagonal:
230      * <pre>
231      *     Mm = [sx    mxy  mxz]
232      *          [0     sy   myz]
233      *          [0     0    sz ]
234      * </pre>
235      * Values of this matrix are unit-less.
236      */
237     private Matrix estimatedMm;
238 
239     /**
240      * Estimated covariance matrix for estimated parameters.
241      */
242     private Matrix estimatedCovariance;
243 
244     /**
245      * Estimated chi square value.
246      */
247     private double estimatedChiSq;
248 
249     /**
250      * Estimated degrees of freedom of chi square value. Degrees of freedom is equal to the number of sampled data
251      * minus the number of estimated parameters.
252      */
253     private int estimatedChiSqDegreesOfFreedom;
254 
255     /**
256      * Estimated reduced chi square value. This is equal to estimated chi square value divided by its degrees of
257      * freedom. Ideally this value should be close to 1.0.
258      */
259     private double estimatedReducedChiSq;
260 
261     /**
262      * Estimated mean square error respect to provided measurements.
263      */
264     private double estimatedMse;
265 
266     /**
267      * Estimated probability of finding a smaller chi square value expressed as a value between 0.0 and 1.0. The smaller
268      * the found chi square value is, the better the fit of the estimated parameters to the actual parameter. Thus, the
269      * smaller the chance of finding a smaller chi square value, then the better the estimated fit is.
270      */
271     private double estimatedP;
272 
273     /**
274      * Estimated measure of quality of estimated fit as a value between 0.0 and 1.0. The larger the quality value is,
275      * the better the fit that has been estimated.
276      */
277     private double estimatedQ;
278 
279     /**
280      * Indicates whether calibrator is running.
281      */
282     private boolean running;
283 
284     /**
285      * Internally holds x-coordinate of measured magnetic flux density
286      * during calibration.
287      */
288     private double bmeasX;
289 
290     /**
291      * Internally holds y-coordinate of measured magnetic flux density
292      * during calibration.
293      */
294     private double bmeasY;
295 
296     /**
297      * Internally holds z-coordinate of measured magnetic flux density
298      * during calibration.
299      */
300     private double bmeasZ;
301 
302     /**
303      * Internally holds measured magnetic flux density during calibration
304      * expressed as a column matrix.
305      */
306     private Matrix bmeas;
307 
308     /**
309      * Internally holds cross-coupling errors during calibration.
310      */
311     private Matrix m;
312 
313     /**
314      * Internally holds inverse of cross-coupling errors during calibration.
315      */
316     private Matrix invM;
317 
318     /**
319      * Internally holds biases during calibration.
320      */
321     private Matrix b;
322 
323     /**
324      * Internally holds computed true magnetic flux density during
325      * calibration.
326      */
327     private Matrix btrue;
328 
329     /**
330      * Internally hold biases during calibration in external format.
331      */
332     private Matrix bm;
333 
334     /**
335      * Constructor.
336      */
337     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator() {
338     }
339 
340     /**
341      * Constructor.
342      *
343      * @param listener listener to handle events raised by this calibrator.
344      */
345     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(final L listener) {
346         this.listener = listener;
347     }
348 
349     /**
350      * Constructor.
351      *
352      * @param measurements collection of body magnetic flux density
353      *                     measurements with standard deviation of
354      *                     magnetometer measurements taken at the same
355      *                     position with zero velocity and unknown different
356      *                     orientations.
357      */
358     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
359             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements) {
360         this.measurements = measurements;
361     }
362 
363     /**
364      * Constructor.
365      *
366      * @param measurements collection of body magnetic flux density
367      *                     measurements with standard deviation of
368      *                     magnetometer measurements taken at the same
369      *                     position with zero velocity and unknown different
370      *                     orientations.
371      * @param listener     listener to handle events raised by this calibrator.
372      */
373     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
374             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final L listener) {
375         this(measurements);
376         this.listener = listener;
377     }
378 
379     /**
380      * Constructor.
381      *
382      * @param commonAxisUsed indicates whether z-axis is assumed to be common
383      *                       for the accelerometer, gyroscope and magnetometer.
384      */
385     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(final boolean commonAxisUsed) {
386         this.commonAxisUsed = commonAxisUsed;
387     }
388 
389     /**
390      * Constructor.
391      *
392      * @param measurements   collection of body magnetic flux density
393      *                       measurements with standard deviation of
394      *                       magnetometer measurements taken at the same
395      *                       position with zero velocity and unknown different
396      *                       orientations.
397      * @param commonAxisUsed indicates whether z-axis is assumed to be common
398      *                       for the accelerometer, gyroscope and magnetometer.
399      */
400     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
401             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed) {
402         this.measurements = measurements;
403         this.commonAxisUsed = commonAxisUsed;
404     }
405 
406     /**
407      * Constructor.
408      *
409      * @param measurements   collection of body magnetic flux density
410      *                       measurements with standard deviation of
411      *                       magnetometer measurements taken at the same
412      *                       position with zero velocity and unknown different
413      *                       orientations.
414      * @param commonAxisUsed indicates whether z-axis is assumed to be common
415      *                       for the accelerometer, gyroscope and magnetometer.
416      * @param listener       listener to handle events raised by this calibrator.
417      */
418     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
419             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements,
420             final boolean commonAxisUsed, final L listener) {
421         this(measurements, commonAxisUsed);
422         this.listener = listener;
423     }
424 
425     /**
426      * Constructor.
427      *
428      * @param hardIron known hard-iron.
429      * @throws IllegalArgumentException if provided hard-iron array does
430      *                                  not have length 3.
431      */
432     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(final double[] hardIron) {
433         try {
434             setHardIron(hardIron);
435         } catch (final LockedException ignore) {
436             // never happens
437         }
438     }
439 
440     /**
441      * Constructor.
442      *
443      * @param measurements collection of body magnetic flux density
444      *                     measurements with standard deviation of
445      *                     magnetometer measurements taken at the same
446      *                     position with zero velocity and unknown different
447      *                     orientations.
448      * @param hardIron     known hard-iron.
449      * @throws IllegalArgumentException if provided hard-iron array does
450      *                                  not have length 3.
451      */
452     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
453             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final double[] hardIron) {
454         this(hardIron);
455         this.measurements = measurements;
456     }
457 
458     /**
459      * Constructor.
460      *
461      * @param measurements collection of body magnetic flux density
462      *                     measurements with standard deviation of
463      *                     magnetometer measurements taken at the same
464      *                     position with zero velocity and unknown different
465      *                     orientations.
466      * @param hardIron     known hard-iron.
467      * @param listener     listener to handle events raised by this calibrator.
468      * @throws IllegalArgumentException if provided hard-iron array does
469      *                                  not have length 3.
470      */
471     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
472             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final double[] hardIron,
473             final L listener) {
474         this(measurements, hardIron);
475         this.listener = listener;
476     }
477 
478     /**
479      * Constructor.
480      *
481      * @param measurements   collection of body magnetic flux density
482      *                       measurements with standard deviation of
483      *                       magnetometer measurements taken at the same
484      *                       position with zero velocity and unknown different
485      *                       orientations.
486      * @param commonAxisUsed indicates whether z-axis is assumed to be common
487      *                       for the accelerometer, gyroscope and magnetometer.
488      * @param hardIron       known hard-iron.
489      * @throws IllegalArgumentException if provided hard-iron array does
490      *                                  not have length 3.
491      */
492     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
493             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
494             final double[] hardIron) {
495         this(measurements, hardIron);
496         this.commonAxisUsed = commonAxisUsed;
497     }
498 
499     /**
500      * Constructor.
501      *
502      * @param measurements   collection of body magnetic flux density
503      *                       measurements with standard deviation of
504      *                       magnetometer measurements taken at the same
505      *                       position with zero velocity and unknown different
506      *                       orientations.
507      * @param commonAxisUsed indicates whether z-axis is assumed to be common
508      *                       for the accelerometer, gyroscope and magnetometer.
509      * @param hardIron       known hard-iron.
510      * @param listener       listener to handle events raised by this calibrator.
511      * @throws IllegalArgumentException if provided hard-iron array does
512      *                                  not have length 3.
513      */
514     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
515             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
516             final double[] hardIron, final L listener) {
517         this(measurements, commonAxisUsed, hardIron);
518         this.listener = listener;
519     }
520 
521     /**
522      * Constructor.
523      *
524      * @param hardIron known hard-iron.
525      * @throws IllegalArgumentException if provided hard-iron matrix is not
526      *                                  3x1.
527      */
528     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(final Matrix hardIron) {
529         try {
530             setHardIron(hardIron);
531         } catch (final LockedException ignore) {
532             // never happens
533         }
534     }
535 
536     /**
537      * Constructor.
538      *
539      * @param measurements collection of body magnetic flux density
540      *                     measurements with standard deviation of
541      *                     magnetometer measurements taken at the same
542      *                     position with zero velocity and unknown different
543      *                     orientations.
544      * @param hardIron     known hard-iron.
545      * @throws IllegalArgumentException if provided hard-iron matrix is not
546      *                                  3x1.
547      */
548     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
549             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final Matrix hardIron) {
550         this(hardIron);
551         this.measurements = measurements;
552     }
553 
554     /**
555      * Constructor.
556      *
557      * @param measurements collection of body magnetic flux density
558      *                     measurements with standard deviation of
559      *                     magnetometer measurements taken at the same
560      *                     position with zero velocity and unknown different
561      *                     orientations.
562      * @param hardIron     known hard-iron.
563      * @param listener     listener to handle events raised by this calibrator.
564      * @throws IllegalArgumentException if provided hard-iron matrix is not
565      *                                  3x1.
566      */
567     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
568             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final Matrix hardIron,
569             final L listener) {
570         this(measurements, hardIron);
571         this.listener = listener;
572     }
573 
574     /**
575      * Constructor.
576      *
577      * @param measurements   collection of body magnetic flux density
578      *                       measurements with standard deviation of
579      *                       magnetometer measurements taken at the same
580      *                       position with zero velocity and unknown different
581      *                       orientations.
582      * @param commonAxisUsed indicates whether z-axis is assumed to be common
583      *                       for the accelerometer, gyroscope and magnetometer.
584      * @param hardIron       known hard-iron.
585      * @throws IllegalArgumentException if provided hard-iron matrix is not
586      *                                  3x1.
587      */
588     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
589             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
590             final Matrix hardIron) {
591         this(measurements, hardIron);
592         this.commonAxisUsed = commonAxisUsed;
593     }
594 
595     /**
596      * Constructor.
597      *
598      * @param measurements   collection of body magnetic flux density
599      *                       measurements with standard deviation of
600      *                       magnetometer measurements taken at the same
601      *                       position with zero velocity and unknown different
602      *                       orientations.
603      * @param commonAxisUsed indicates whether z-axis is assumed to be common
604      *                       for the accelerometer, gyroscope and magnetometer.
605      * @param hardIron       known hard-iron.
606      * @param listener       listener to handle events raised by this calibrator.
607      * @throws IllegalArgumentException if provided hard-iron matrix is not
608      *                                  3x1.
609      */
610     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
611             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
612             final Matrix hardIron, final L listener) {
613         this(measurements, commonAxisUsed, hardIron);
614         this.listener = listener;
615     }
616 
617     /**
618      * Constructor.
619      *
620      * @param hardIron  known hard-iron.
621      * @param initialMm initial soft-iron matrix containing scale factors
622      *                  and cross coupling errors.
623      * @throws IllegalArgumentException if provided hard-iron matrix is not
624      *                                  3x1 or if soft-iron matrix is not
625      *                                  3x3.
626      */
627     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
628             final Matrix hardIron, final Matrix initialMm) {
629         this(hardIron);
630         try {
631             setInitialMm(initialMm);
632         } catch (final LockedException ignore) {
633             // never happens
634         }
635     }
636 
637     /**
638      * Constructor.
639      *
640      * @param measurements collection of body magnetic flux density
641      *                     measurements with standard deviation of
642      *                     magnetometer measurements taken at the same
643      *                     position with zero velocity and unknown different
644      *                     orientations.
645      * @param hardIron     known hard-iron.
646      * @param initialMm    initial soft-iron matrix containing scale factors
647      *                     and cross coupling errors.
648      * @throws IllegalArgumentException if provided hard-iron matrix is not
649      *                                  3x1 or if soft-iron matrix is not
650      *                                  3x3.
651      */
652     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
653             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final Matrix hardIron,
654             final Matrix initialMm) {
655         this(hardIron, initialMm);
656         this.measurements = measurements;
657     }
658 
659     /**
660      * Constructor.
661      *
662      * @param measurements collection of body magnetic flux density
663      *                     measurements with standard deviation of
664      *                     magnetometer measurements taken at the same
665      *                     position with zero velocity and unknown different
666      *                     orientations.
667      * @param hardIron     known hard-iron.
668      * @param initialMm    initial soft-iron matrix containing scale factors
669      *                     and cross coupling errors.
670      * @param listener     listener to handle events raised by this calibrator.
671      * @throws IllegalArgumentException if provided hard-iron matrix is not
672      *                                  3x1 or if soft-iron matrix is not
673      *                                  3x3.
674      */
675     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
676             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final Matrix hardIron,
677             final Matrix initialMm, final L listener) {
678         this(measurements, hardIron, initialMm);
679         this.listener = listener;
680     }
681 
682     /**
683      * Constructor.
684      *
685      * @param measurements   collection of body magnetic flux density
686      *                       measurements with standard deviation of
687      *                       magnetometer measurements taken at the same
688      *                       position with zero velocity and unknown different
689      *                       orientations.
690      * @param commonAxisUsed indicates whether z-axis is assumed to be common
691      *                       for the accelerometer, gyroscope and magnetometer.
692      * @param hardIron       known hard-iron.
693      * @param initialMm      initial soft-iron matrix containing scale factors
694      *                       and cross coupling errors.
695      * @throws IllegalArgumentException if provided hard-iron matrix is not
696      *                                  3x1 or if soft-iron matrix is not
697      *                                  3x3.
698      */
699     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
700             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
701             final Matrix hardIron, final Matrix initialMm) {
702         this(measurements, hardIron, initialMm);
703         this.commonAxisUsed = commonAxisUsed;
704     }
705 
706     /**
707      * Constructor.
708      *
709      * @param measurements   collection of body magnetic flux density
710      *                       measurements with standard deviation of
711      *                       magnetometer measurements taken at the same
712      *                       position with zero velocity and unknown different
713      *                       orientations.
714      * @param commonAxisUsed indicates whether z-axis is assumed to be common
715      *                       for the accelerometer, gyroscope and magnetometer.
716      * @param hardIron       known hard-iron.
717      * @param initialMm      initial soft-iron matrix containing scale factors
718      *                       and cross coupling errors.
719      * @param listener       listener to handle events raised by this calibrator.
720      * @throws IllegalArgumentException if provided hard-iron matrix is not
721      *                                  3x1 or if soft-iron matrix is not
722      *                                  3x3.
723      */
724     public BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
725             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
726             final Matrix hardIron, final Matrix initialMm, final L listener) {
727         this(measurements, commonAxisUsed, hardIron, initialMm);
728         this.listener = listener;
729     }
730 
731     /**
732      * Constructor.
733      *
734      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
735      * @throws IllegalArgumentException if provided magnetic flux norm value is negative.
736      */
737     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
738             final Double groundTruthMagneticFluxDensityNorm) {
739         internalSetGroundTruthMagneticFluxDensityNorm(groundTruthMagneticFluxDensityNorm);
740     }
741 
742     /**
743      * Constructor.
744      *
745      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
746      * @param listener                           listener to handle events raised by this calibrator.
747      * @throws IllegalArgumentException if provided magnetic flux norm value is negative.
748      */
749     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
750             final Double groundTruthMagneticFluxDensityNorm, final L listener) {
751         this(groundTruthMagneticFluxDensityNorm);
752         this.listener = listener;
753     }
754 
755     /**
756      * Constructor.
757      *
758      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
759      * @param measurements                       collection of body magnetic flux density
760      *                                           measurements with standard deviation of
761      *                                           magnetometer measurements taken at the same
762      *                                           position with zero velocity and unknown different
763      *                                           orientations.
764      * @throws IllegalArgumentException if provided magnetic flux norm value is negative.
765      */
766     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
767             final Double groundTruthMagneticFluxDensityNorm,
768             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements) {
769         this(groundTruthMagneticFluxDensityNorm);
770         this.measurements = measurements;
771     }
772 
773     /**
774      * Constructor.
775      *
776      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
777      * @param measurements                       collection of body magnetic flux density
778      *                                           measurements with standard deviation of
779      *                                           magnetometer measurements taken at the same
780      *                                           position with zero velocity and unknown different
781      *                                           orientations.
782      * @param listener                           listener to handle events raised by this calibrator.
783      * @throws IllegalArgumentException if provided magnetic flux norm value is negative.
784      */
785     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
786             final Double groundTruthMagneticFluxDensityNorm,
787             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final L listener) {
788         this(groundTruthMagneticFluxDensityNorm, measurements);
789         this.listener = listener;
790     }
791 
792     /**
793      * Constructor.
794      *
795      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
796      * @param measurements                       collection of body magnetic flux density
797      *                                           measurements with standard deviation of
798      *                                           magnetometer measurements taken at the same
799      *                                           position with zero velocity and unknown different
800      *                                           orientations.
801      * @param commonAxisUsed                     indicates whether z-axis is assumed to be common
802      *                                           for the accelerometer, gyroscope and magnetometer.
803      * @throws IllegalArgumentException if provided magnetic flux norm value is negative.
804      */
805     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
806             final Double groundTruthMagneticFluxDensityNorm,
807             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed) {
808         this(groundTruthMagneticFluxDensityNorm, measurements);
809         this.commonAxisUsed = commonAxisUsed;
810     }
811 
812     /**
813      * Constructor.
814      *
815      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
816      * @param measurements                       collection of body magnetic flux density
817      *                                           measurements with standard deviation of
818      *                                           magnetometer measurements taken at the same
819      *                                           position with zero velocity and unknown different
820      *                                           orientations.
821      * @param commonAxisUsed                     indicates whether z-axis is assumed to be common
822      *                                           for the accelerometer, gyroscope and magnetometer.
823      * @param listener                           listener to handle events raised by this calibrator.
824      * @throws IllegalArgumentException if provided magnetic flux norm value is negative.
825      */
826     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
827             final Double groundTruthMagneticFluxDensityNorm,
828             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
829             final L listener) {
830         this(groundTruthMagneticFluxDensityNorm, measurements, commonAxisUsed);
831         this.listener = listener;
832     }
833 
834     /**
835      * Constructor.
836      *
837      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
838      * @param measurements                       collection of body magnetic flux density
839      *                                           measurements with standard deviation of
840      *                                           magnetometer measurements taken at the same
841      *                                           position with zero velocity and unknown different
842      *                                           orientations.
843      * @param hardIron                           known hard-iron.
844      * @throws IllegalArgumentException if provided magnetic flux norm value is
845      *                                  negative, or if provided hard-iron array does
846      *                                  not have length 3.
847      */
848     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
849             final Double groundTruthMagneticFluxDensityNorm,
850             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final double[] hardIron) {
851         this(hardIron);
852         internalSetGroundTruthMagneticFluxDensityNorm(groundTruthMagneticFluxDensityNorm);
853         this.measurements = measurements;
854     }
855 
856     /**
857      * Constructor.
858      *
859      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
860      * @param measurements                       collection of body magnetic flux density
861      *                                           measurements with standard deviation of
862      *                                           magnetometer measurements taken at the same
863      *                                           position with zero velocity and unknown different
864      *                                           orientations.
865      * @param hardIron                           known hard-iron.
866      * @param listener                           listener to handle events raised by this calibrator.
867      * @throws IllegalArgumentException if provided magnetic flux norm value is
868      *                                  negative, or if provided hard-iron array does
869      *                                  not have length 3.
870      */
871     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
872             final Double groundTruthMagneticFluxDensityNorm,
873             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final double[] hardIron,
874             final L listener) {
875         this(groundTruthMagneticFluxDensityNorm, measurements, hardIron);
876         this.listener = listener;
877     }
878 
879     /**
880      * Constructor.
881      *
882      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
883      * @param measurements                       collection of body magnetic flux density
884      *                                           measurements with standard deviation of
885      *                                           magnetometer measurements taken at the same
886      *                                           position with zero velocity and unknown different
887      *                                           orientations.
888      * @param commonAxisUsed                     indicates whether z-axis is assumed to be common
889      *                                           for the accelerometer, gyroscope and magnetometer.
890      * @param hardIron                           known hard-iron.
891      * @throws IllegalArgumentException if provided magnetic flux norm value is
892      *                                  negative, or if provided hard-iron array does
893      *                                  not have length 3.
894      */
895     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
896             final Double groundTruthMagneticFluxDensityNorm,
897             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
898             final double[] hardIron) {
899         this(groundTruthMagneticFluxDensityNorm, measurements, hardIron);
900         this.commonAxisUsed = commonAxisUsed;
901     }
902 
903     /**
904      * Constructor.
905      *
906      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
907      * @param measurements                       collection of body magnetic flux density
908      *                                           measurements with standard deviation of
909      *                                           magnetometer measurements taken at the same
910      *                                           position with zero velocity and unknown different
911      *                                           orientations.
912      * @param commonAxisUsed                     indicates whether z-axis is assumed to be common
913      *                                           for the accelerometer, gyroscope and magnetometer.
914      * @param hardIron                           known hard-iron.
915      * @param listener                           listener to handle events raised by this calibrator.
916      * @throws IllegalArgumentException if provided magnetic flux norm value is
917      *                                  negative, or if provided hard-iron array does
918      *                                  not have length 3.
919      */
920     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
921             final Double groundTruthMagneticFluxDensityNorm,
922             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
923             final double[] hardIron, final L listener) {
924         this(groundTruthMagneticFluxDensityNorm, measurements, commonAxisUsed, hardIron);
925         this.listener = listener;
926     }
927 
928     /**
929      * Constructor.
930      *
931      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
932      * @param measurements                       collection of body magnetic flux density
933      *                                           measurements with standard deviation of
934      *                                           magnetometer measurements taken at the same
935      *                                           position with zero velocity and unknown different
936      *                                           orientations.
937      * @param hardIron                           known hard-iron.
938      * @throws IllegalArgumentException if provided magnetic flux norm value is
939      *                                  negative, or if provided hard-iron matrix is not
940      *                                  3x1.
941      */
942     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
943             final Double groundTruthMagneticFluxDensityNorm,
944             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final Matrix hardIron) {
945         this(hardIron);
946         internalSetGroundTruthMagneticFluxDensityNorm(groundTruthMagneticFluxDensityNorm);
947         this.measurements = measurements;
948     }
949 
950     /**
951      * Constructor.
952      *
953      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
954      * @param measurements                       collection of body magnetic flux density
955      *                                           measurements with standard deviation of
956      *                                           magnetometer measurements taken at the same
957      *                                           position with zero velocity and unknown different
958      *                                           orientations.
959      * @param hardIron                           known hard-iron.
960      * @param listener                           listener to handle events raised by this calibrator.
961      * @throws IllegalArgumentException if provided magnetic flux norm value is
962      *                                  negative, or if provided hard-iron matrix is not
963      *                                  3x1.
964      */
965     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
966             final Double groundTruthMagneticFluxDensityNorm,
967             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final Matrix hardIron,
968             final L listener) {
969         this(groundTruthMagneticFluxDensityNorm, measurements, hardIron);
970         this.listener = listener;
971     }
972 
973     /**
974      * Constructor.
975      *
976      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
977      * @param measurements                       collection of body magnetic flux density
978      *                                           measurements with standard deviation of
979      *                                           magnetometer measurements taken at the same
980      *                                           position with zero velocity and unknown different
981      *                                           orientations.
982      * @param commonAxisUsed                     indicates whether z-axis is assumed to be common
983      *                                           for the accelerometer, gyroscope and magnetometer.
984      * @param hardIron                           known hard-iron.
985      * @throws IllegalArgumentException if provided magnetic flux norm value is
986      *                                  negative, or if provided hard-iron matrix is not
987      *                                  3x1.
988      */
989     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
990             final Double groundTruthMagneticFluxDensityNorm,
991             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
992             final Matrix hardIron) {
993         this(groundTruthMagneticFluxDensityNorm, measurements, hardIron);
994         this.commonAxisUsed = commonAxisUsed;
995     }
996 
997     /**
998      * Constructor.
999      *
1000      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
1001      * @param measurements                       collection of body magnetic flux density
1002      *                                           measurements with standard deviation of
1003      *                                           magnetometer measurements taken at the same
1004      *                                           position with zero velocity and unknown different
1005      *                                           orientations.
1006      * @param commonAxisUsed                     indicates whether z-axis is assumed to be common
1007      *                                           for the accelerometer, gyroscope and magnetometer.
1008      * @param hardIron                           known hard-iron.
1009      * @param listener                           listener to handle events raised by this calibrator.
1010      * @throws IllegalArgumentException if provided magnetic flux norm value is
1011      *                                  negative, or if provided hard-iron matrix is not
1012      *                                  3x1.
1013      */
1014     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
1015             final Double groundTruthMagneticFluxDensityNorm,
1016             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
1017             final Matrix hardIron, final L listener) {
1018         this(groundTruthMagneticFluxDensityNorm, measurements, commonAxisUsed, hardIron);
1019         this.listener = listener;
1020     }
1021 
1022     /**
1023      * Constructor.
1024      *
1025      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
1026      * @param measurements                       collection of body magnetic flux density
1027      *                                           measurements with standard deviation of
1028      *                                           magnetometer measurements taken at the same
1029      *                                           position with zero velocity and unknown different
1030      *                                           orientations.
1031      * @param hardIron                           known hard-iron.
1032      * @param initialMm                          initial soft-iron matrix containing scale factors
1033      *                                           and cross coupling errors.
1034      * @throws IllegalArgumentException if provided magnetic flux norm value is
1035      *                                  negative, or if provided hard-iron matrix is not
1036      *                                  3x1 or if soft-iron matrix is not
1037      *                                  3x3.
1038      */
1039     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
1040             final Double groundTruthMagneticFluxDensityNorm,
1041             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final Matrix hardIron,
1042             final Matrix initialMm) {
1043         this(hardIron, initialMm);
1044         internalSetGroundTruthMagneticFluxDensityNorm(groundTruthMagneticFluxDensityNorm);
1045         this.measurements = measurements;
1046     }
1047 
1048     /**
1049      * Constructor.
1050      *
1051      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
1052      * @param measurements                       collection of body magnetic flux density
1053      *                                           measurements with standard deviation of
1054      *                                           magnetometer measurements taken at the same
1055      *                                           position with zero velocity and unknown different
1056      *                                           orientations.
1057      * @param hardIron                           known hard-iron.
1058      * @param initialMm                          initial soft-iron matrix containing scale factors
1059      *                                           and cross coupling errors.
1060      * @param listener                           listener to handle events raised by this calibrator.
1061      * @throws IllegalArgumentException if provided magnetic flux norm value is
1062      *                                  negative, or if provided hard-iron matrix is not
1063      *                                  3x1 or if soft-iron matrix is not
1064      *                                  3x3.
1065      */
1066     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
1067             final Double groundTruthMagneticFluxDensityNorm,
1068             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final Matrix hardIron,
1069             final Matrix initialMm, final L listener) {
1070         this(groundTruthMagneticFluxDensityNorm, measurements, hardIron, initialMm);
1071         this.listener = listener;
1072     }
1073 
1074     /**
1075      * Constructor.
1076      *
1077      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
1078      * @param measurements                       collection of body magnetic flux density
1079      *                                           measurements with standard deviation of
1080      *                                           magnetometer measurements taken at the same
1081      *                                           position with zero velocity and unknown different
1082      *                                           orientations.
1083      * @param commonAxisUsed                     indicates whether z-axis is assumed to be common
1084      *                                           for the accelerometer, gyroscope and magnetometer.
1085      * @param hardIron                           known hard-iron.
1086      * @param initialMm                          initial soft-iron matrix containing scale factors
1087      *                                           and cross coupling errors.
1088      * @throws IllegalArgumentException if provided magnetic flux norm value is
1089      *                                  negative, or if provided hard-iron matrix is not
1090      *                                  3x1 or if soft-iron matrix is not
1091      *                                  3x3.
1092      */
1093     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
1094             final Double groundTruthMagneticFluxDensityNorm,
1095             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
1096             final Matrix hardIron, final Matrix initialMm) {
1097         this(groundTruthMagneticFluxDensityNorm, measurements, hardIron, initialMm);
1098         this.commonAxisUsed = commonAxisUsed;
1099     }
1100 
1101     /**
1102      * Constructor.
1103      *
1104      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm expressed in Teslas (T).
1105      * @param measurements                       collection of body magnetic flux density
1106      *                                           measurements with standard deviation of
1107      *                                           magnetometer measurements taken at the same
1108      *                                           position with zero velocity and unknown different
1109      *                                           orientations.
1110      * @param commonAxisUsed                     indicates whether z-axis is assumed to be common
1111      *                                           for the accelerometer, gyroscope and magnetometer.
1112      * @param hardIron                           known hard-iron.
1113      * @param initialMm                          initial soft-iron matrix containing scale factors
1114      *                                           and cross coupling errors.
1115      * @param listener                           listener to handle events raised by this calibrator.
1116      * @throws IllegalArgumentException if provided magnetic flux norm value is
1117      *                                  negative, or if provided hard-iron matrix is not
1118      *                                  3x1 or if soft-iron matrix is not
1119      *                                  3x3.
1120      */
1121     protected BaseKnownHardIronMagneticFluxDensityNormMagnetometerCalibrator(
1122             final Double groundTruthMagneticFluxDensityNorm,
1123             final Collection<StandardDeviationBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
1124             final Matrix hardIron, final Matrix initialMm, final L listener) {
1125         this(groundTruthMagneticFluxDensityNorm, measurements, commonAxisUsed, hardIron, initialMm);
1126         this.listener = listener;
1127     }
1128 
1129     /**
1130      * Gets ground truth magnetic flux density norm to be expected at location where measurements have been made,
1131      * expressed in Teslas (T).
1132      *
1133      * @return ground truth magnetic flux density or null.
1134      */
1135     public Double getGroundTruthMagneticFluxDensityNorm() {
1136         return groundTruthMagneticFluxDensityNorm;
1137     }
1138 
1139     /**
1140      * Gets ground truth magnetic flux density norm to be expected at location where measurements have been made.
1141      *
1142      * @return ground truth magnetic flux density or null.
1143      */
1144     public MagneticFluxDensity getGroundTruthMagneticFluxDensityNormAsMagneticFluxDensity() {
1145         return groundTruthMagneticFluxDensityNorm != null
1146                 ? new MagneticFluxDensity(groundTruthMagneticFluxDensityNorm, MagneticFluxDensityUnit.TESLA) : null;
1147     }
1148 
1149     /**
1150      * Gets ground truth magnetic flux density norm to be expected at location where measurements have been made.
1151      *
1152      * @param result instance where result will be stored.
1153      * @return true if ground truth magnetic flux density norm has been defined, false if it is not available yet.
1154      */
1155     public boolean getGroundTruthMagneticFluxDensityNormAsMagneticFluxDensity(final MagneticFluxDensity result) {
1156         if (groundTruthMagneticFluxDensityNorm != null) {
1157             result.setValue(groundTruthMagneticFluxDensityNorm);
1158             result.setUnit(MagneticFluxDensityUnit.TESLA);
1159             return true;
1160         } else {
1161             return false;
1162         }
1163     }
1164 
1165     /**
1166      * Gets known x-coordinate of magnetometer hard-iron bias.
1167      * This is expressed in Teslas (T).
1168      *
1169      * @return known x-coordinate of magnetometer hard-iron bias.
1170      */
1171     @Override
1172     public double getHardIronX() {
1173         return hardIronX;
1174     }
1175 
1176     /**
1177      * Sets known x-coordinate of magnetometer hard-iron bias.
1178      * This is expressed in Teslas (T).
1179      *
1180      * @param hardIronX known x-coordinate of magnetometer
1181      *                  hard-iron bias.
1182      * @throws LockedException if calibrator is currently running.
1183      */
1184     @Override
1185     public void setHardIronX(final double hardIronX) throws LockedException {
1186         if (running) {
1187             throw new LockedException();
1188         }
1189         this.hardIronX = hardIronX;
1190     }
1191 
1192     /**
1193      * Gets known y-coordinate of magnetometer hard-iron bias.
1194      * This is expressed in Teslas (T).
1195      *
1196      * @return known y-coordinate of magnetometer hard-iron bias.
1197      */
1198     @Override
1199     public double getHardIronY() {
1200         return hardIronY;
1201     }
1202 
1203     /**
1204      * Sets known y-coordinate of magnetometer hard-iron bias.
1205      * This is expressed in Teslas (T).
1206      *
1207      * @param hardIronY known y-coordinate of magnetometer
1208      *                  hard-iron bias.
1209      * @throws LockedException if calibrator is currently running.
1210      */
1211     @Override
1212     public void setHardIronY(final double hardIronY) throws LockedException {
1213         if (running) {
1214             throw new LockedException();
1215         }
1216         this.hardIronY = hardIronY;
1217     }
1218 
1219     /**
1220      * Gets known z-coordinate of magnetometer hard-iron bias.
1221      * This is expressed in Teslas (T).
1222      *
1223      * @return known z-coordinate of magnetometer hard-iron bias.
1224      */
1225     @Override
1226     public double getHardIronZ() {
1227         return hardIronZ;
1228     }
1229 
1230     /**
1231      * Sets known z-coordinate of magnetometer hard-iron bias.
1232      * This is expressed in meters Teslas (T).
1233      *
1234      * @param hardIronZ known z-coordinate of magnetometer
1235      *                  hard-iron bias.
1236      * @throws LockedException if calibrator is currently running.
1237      */
1238     @Override
1239     public void setHardIronZ(final double hardIronZ) throws LockedException {
1240         if (running) {
1241             throw new LockedException();
1242         }
1243         this.hardIronZ = hardIronZ;
1244     }
1245 
1246     /**
1247      * Gets known x coordinate of magnetometer hard-iron.
1248      *
1249      * @return x coordinate of magnetometer hard-iron.
1250      */
1251     @Override
1252     public MagneticFluxDensity getHardIronXAsMagneticFluxDensity() {
1253         return new MagneticFluxDensity(hardIronX, MagneticFluxDensityUnit.TESLA);
1254     }
1255 
1256     /**
1257      * Gets known x coordinate of magnetometer hard-iron.
1258      *
1259      * @param result instance where result will be stored.
1260      */
1261     @Override
1262     public void getHardIronXAsMagneticFluxDensity(final MagneticFluxDensity result) {
1263         result.setValue(hardIronX);
1264         result.setUnit(MagneticFluxDensityUnit.TESLA);
1265     }
1266 
1267     /**
1268      * Sets known x-coordinate of magnetometer hard-iron.
1269      *
1270      * @param hardIronX known x-coordinate of magnetometer hard-iron.
1271      * @throws LockedException if calibrator is currently running.
1272      */
1273     @Override
1274     public void setHardIronX(final MagneticFluxDensity hardIronX) throws LockedException {
1275         if (running) {
1276             throw new LockedException();
1277         }
1278         this.hardIronX = convertMagneticFluxDensity(hardIronX);
1279     }
1280 
1281     /**
1282      * Gets known y coordinate of magnetometer hard-iron.
1283      *
1284      * @return y coordinate of magnetometer hard-iron.
1285      */
1286     @Override
1287     public MagneticFluxDensity getHardIronYAsMagneticFluxDensity() {
1288         return new MagneticFluxDensity(hardIronY, MagneticFluxDensityUnit.TESLA);
1289     }
1290 
1291     /**
1292      * Gets known y coordinate of magnetometer hard-iron.
1293      *
1294      * @param result instance where result will be stored.
1295      */
1296     @Override
1297     public void getHardIronYAsMagneticFluxDensity(final MagneticFluxDensity result) {
1298         result.setValue(hardIronY);
1299         result.setUnit(MagneticFluxDensityUnit.TESLA);
1300     }
1301 
1302     /**
1303      * Sets known y-coordinate of magnetometer hard-iron.
1304      *
1305      * @param hardIronY known y-coordinate of magnetometer hard-iron.
1306      * @throws LockedException if calibrator is currently running.
1307      */
1308     @Override
1309     public void setHardIronY(final MagneticFluxDensity hardIronY) throws LockedException {
1310         if (running) {
1311             throw new LockedException();
1312         }
1313         this.hardIronY = convertMagneticFluxDensity(hardIronY);
1314     }
1315 
1316     /**
1317      * Gets known z coordinate of magnetometer hard-iron.
1318      *
1319      * @return z coordinate of magnetometer hard-iron.
1320      */
1321     @Override
1322     public MagneticFluxDensity getHardIronZAsMagneticFluxDensity() {
1323         return new MagneticFluxDensity(hardIronZ, MagneticFluxDensityUnit.TESLA);
1324     }
1325 
1326     /**
1327      * Gets known z coordinate of magnetometer hard-iron.
1328      *
1329      * @param result instance where result will be stored.
1330      */
1331     @Override
1332     public void getHardIronZAsMagneticFluxDensity(final MagneticFluxDensity result) {
1333         result.setValue(hardIronZ);
1334         result.setUnit(MagneticFluxDensityUnit.TESLA);
1335     }
1336 
1337     /**
1338      * Sets known z-coordinate of magnetometer hard-iron.
1339      *
1340      * @param hardIronZ known z-coordinate of magnetometer hard-iron.
1341      * @throws LockedException if calibrator is currently running.
1342      */
1343     @Override
1344     public void setHardIronZ(final MagneticFluxDensity hardIronZ) throws LockedException {
1345         if (running) {
1346             throw new LockedException();
1347         }
1348         this.hardIronZ = convertMagneticFluxDensity(hardIronZ);
1349     }
1350 
1351     /**
1352      * Sets known hard-iron bias coordinates of magnetometer expressed in
1353      * Teslas (T).
1354      *
1355      * @param hardIronX x-coordinate of magnetometer
1356      *                  known hard-iron bias.
1357      * @param hardIronY y-coordinate of magnetometer
1358      *                  known hard-iron bias.
1359      * @param hardIronZ z-coordinate of magnetometer
1360      *                  known hard-iron bias.
1361      * @throws LockedException if calibrator is currently running.
1362      */
1363     @Override
1364     public void setHardIronCoordinates(
1365             final double hardIronX, final double hardIronY, final double hardIronZ) throws LockedException {
1366         if (running) {
1367             throw new LockedException();
1368         }
1369         this.hardIronX = hardIronX;
1370         this.hardIronY = hardIronY;
1371         this.hardIronZ = hardIronZ;
1372     }
1373 
1374     /**
1375      * Sets known hard-iron coordinates.
1376      *
1377      * @param hardIronX x-coordinate of magnetometer hard-iron.
1378      * @param hardIronY y-coordinate of magnetometer hard-iron.
1379      * @param hardIronZ z-coordinate of magnetometer hard-iron.
1380      * @throws LockedException if calibrator is currently running.
1381      */
1382     @Override
1383     public void setHardIronCoordinates(
1384             final MagneticFluxDensity hardIronX, final MagneticFluxDensity hardIronY,
1385             final MagneticFluxDensity hardIronZ) throws LockedException {
1386         if (running) {
1387             throw new LockedException();
1388         }
1389         this.hardIronX = convertMagneticFluxDensity(hardIronX);
1390         this.hardIronY = convertMagneticFluxDensity(hardIronY);
1391         this.hardIronZ = convertMagneticFluxDensity(hardIronZ);
1392     }
1393 
1394     /**
1395      * Gets known hard-iron.
1396      *
1397      * @return known hard-iron.
1398      */
1399     @Override
1400     public MagneticFluxDensityTriad getHardIronAsTriad() {
1401         return new MagneticFluxDensityTriad(MagneticFluxDensityUnit.TESLA, hardIronX, hardIronY, hardIronZ);
1402     }
1403 
1404     /**
1405      * Gets known hard-iron.
1406      *
1407      * @param result instance where result will be stored.
1408      */
1409     @Override
1410     public void getHardIronAsTriad(final MagneticFluxDensityTriad result) {
1411         result.setValueCoordinatesAndUnit(hardIronX, hardIronY, hardIronZ, MagneticFluxDensityUnit.TESLA);
1412     }
1413 
1414     /**
1415      * Sets known hard-iron.
1416      *
1417      * @param hardIron hard-iron to be set.
1418      * @throws LockedException if calibrator is currently running.
1419      */
1420     @Override
1421     public void setHardIron(final MagneticFluxDensityTriad hardIron) throws LockedException {
1422         if (running) {
1423             throw new LockedException();
1424         }
1425 
1426         hardIronX = convertMagneticFluxDensity(hardIron.getValueX(), hardIron.getUnit());
1427         hardIronY = convertMagneticFluxDensity(hardIron.getValueY(), hardIron.getUnit());
1428         hardIronZ = convertMagneticFluxDensity(hardIron.getValueZ(), hardIron.getUnit());
1429     }
1430 
1431     /**
1432      * Gets initial x scaling factor.
1433      *
1434      * @return initial x scaling factor.
1435      */
1436     @Override
1437     public double getInitialSx() {
1438         return initialSx;
1439     }
1440 
1441     /**
1442      * Sets initial x scaling factor.
1443      *
1444      * @param initialSx initial x scaling factor.
1445      * @throws LockedException if calibrator is currently running.
1446      */
1447     @Override
1448     public void setInitialSx(final double initialSx) throws LockedException {
1449         if (running) {
1450             throw new LockedException();
1451         }
1452         this.initialSx = initialSx;
1453     }
1454 
1455     /**
1456      * Gets initial y scaling factor.
1457      *
1458      * @return initial y scaling factor.
1459      */
1460     @Override
1461     public double getInitialSy() {
1462         return initialSy;
1463     }
1464 
1465     /**
1466      * Sets initial y scaling factor.
1467      *
1468      * @param initialSy initial y scaling factor.
1469      * @throws LockedException if calibrator is currently running.
1470      */
1471     @Override
1472     public void setInitialSy(final double initialSy) throws LockedException {
1473         if (running) {
1474             throw new LockedException();
1475         }
1476         this.initialSy = initialSy;
1477     }
1478 
1479     /**
1480      * Gets initial z scaling factor.
1481      *
1482      * @return initial z scaling factor.
1483      */
1484     @Override
1485     public double getInitialSz() {
1486         return initialSz;
1487     }
1488 
1489     /**
1490      * Sets initial z scaling factor.
1491      *
1492      * @param initialSz initial z scaling factor.
1493      * @throws LockedException if calibrator is currently running.
1494      */
1495     @Override
1496     public void setInitialSz(final double initialSz) throws LockedException {
1497         if (running) {
1498             throw new LockedException();
1499         }
1500         this.initialSz = initialSz;
1501     }
1502 
1503     /**
1504      * Gets initial x-y cross coupling error.
1505      *
1506      * @return initial x-y cross coupling error.
1507      */
1508     @Override
1509     public double getInitialMxy() {
1510         return initialMxy;
1511     }
1512 
1513     /**
1514      * Sets initial x-y cross coupling error.
1515      *
1516      * @param initialMxy initial x-y cross coupling error.
1517      * @throws LockedException if calibrator is currently running.
1518      */
1519     @Override
1520     public void setInitialMxy(final double initialMxy) throws LockedException {
1521         if (running) {
1522             throw new LockedException();
1523         }
1524         this.initialMxy = initialMxy;
1525     }
1526 
1527     /**
1528      * Gets initial x-z cross coupling error.
1529      *
1530      * @return initial x-z cross coupling error.
1531      */
1532     @Override
1533     public double getInitialMxz() {
1534         return initialMxz;
1535     }
1536 
1537     /**
1538      * Sets initial x-z cross coupling error.
1539      *
1540      * @param initialMxz initial x-z cross coupling error.
1541      * @throws LockedException if calibrator is currently running.
1542      */
1543     @Override
1544     public void setInitialMxz(final double initialMxz) throws LockedException {
1545         if (running) {
1546             throw new LockedException();
1547         }
1548         this.initialMxz = initialMxz;
1549     }
1550 
1551     /**
1552      * Gets initial y-x cross coupling error.
1553      *
1554      * @return initial y-x cross coupling error.
1555      */
1556     @Override
1557     public double getInitialMyx() {
1558         return initialMyx;
1559     }
1560 
1561     /**
1562      * Sets initial y-x cross coupling error.
1563      *
1564      * @param initialMyx initial y-x cross coupling error.
1565      * @throws LockedException if calibrator is currently running.
1566      */
1567     @Override
1568     public void setInitialMyx(final double initialMyx) throws LockedException {
1569         if (running) {
1570             throw new LockedException();
1571         }
1572         this.initialMyx = initialMyx;
1573     }
1574 
1575     /**
1576      * Gets initial y-z cross coupling error.
1577      *
1578      * @return initial y-z cross coupling error.
1579      */
1580     @Override
1581     public double getInitialMyz() {
1582         return initialMyz;
1583     }
1584 
1585     /**
1586      * Sets initial y-z cross coupling error.
1587      *
1588      * @param initialMyz initial y-z cross coupling error.
1589      * @throws LockedException if calibrator is currently running.
1590      */
1591     @Override
1592     public void setInitialMyz(final double initialMyz) throws LockedException {
1593         if (running) {
1594             throw new LockedException();
1595         }
1596         this.initialMyz = initialMyz;
1597     }
1598 
1599     /**
1600      * Gets initial z-x cross coupling error.
1601      *
1602      * @return initial z-x cross coupling error.
1603      */
1604     @Override
1605     public double getInitialMzx() {
1606         return initialMzx;
1607     }
1608 
1609     /**
1610      * Sets initial z-x cross coupling error.
1611      *
1612      * @param initialMzx initial z-x cross coupling error.
1613      * @throws LockedException if calibrator is currently running.
1614      */
1615     @Override
1616     public void setInitialMzx(final double initialMzx) throws LockedException {
1617         if (running) {
1618             throw new LockedException();
1619         }
1620         this.initialMzx = initialMzx;
1621     }
1622 
1623     /**
1624      * Gets initial z-y cross coupling error.
1625      *
1626      * @return initial z-y cross coupling error.
1627      */
1628     @Override
1629     public double getInitialMzy() {
1630         return initialMzy;
1631     }
1632 
1633     /**
1634      * Sets initial z-y cross coupling error.
1635      *
1636      * @param initialMzy initial z-y cross coupling error.
1637      * @throws LockedException if calibrator is currently running.
1638      */
1639     @Override
1640     public void setInitialMzy(final double initialMzy) throws LockedException {
1641         if (running) {
1642             throw new LockedException();
1643         }
1644         this.initialMzy = initialMzy;
1645     }
1646 
1647     /**
1648      * Sets initial scaling factors.
1649      *
1650      * @param initialSx initial x scaling factor.
1651      * @param initialSy initial y scaling factor.
1652      * @param initialSz initial z scaling factor.
1653      * @throws LockedException if calibrator is currently running.
1654      */
1655     @Override
1656     public void setInitialScalingFactors(
1657             final double initialSx, final double initialSy, final double initialSz) throws LockedException {
1658         if (running) {
1659             throw new LockedException();
1660         }
1661         this.initialSx = initialSx;
1662         this.initialSy = initialSy;
1663         this.initialSz = initialSz;
1664     }
1665 
1666     /**
1667      * Sets initial cross coupling errors.
1668      *
1669      * @param initialMxy initial x-y cross coupling error.
1670      * @param initialMxz initial x-z cross coupling error.
1671      * @param initialMyx initial y-x cross coupling error.
1672      * @param initialMyz initial y-z cross coupling error.
1673      * @param initialMzx initial z-x cross coupling error.
1674      * @param initialMzy initial z-y cross coupling error.
1675      * @throws LockedException if calibrator is currently running.
1676      */
1677     @Override
1678     public void setInitialCrossCouplingErrors(
1679             final double initialMxy, final double initialMxz, final double initialMyx,
1680             final double initialMyz, final double initialMzx, final double initialMzy) throws LockedException {
1681         if (running) {
1682             throw new LockedException();
1683         }
1684         this.initialMxy = initialMxy;
1685         this.initialMxz = initialMxz;
1686         this.initialMyx = initialMyx;
1687         this.initialMyz = initialMyz;
1688         this.initialMzx = initialMzx;
1689         this.initialMzy = initialMzy;
1690     }
1691 
1692     /**
1693      * Sets initial scaling factors and cross coupling errors.
1694      *
1695      * @param initialSx  initial x scaling factor.
1696      * @param initialSy  initial y scaling factor.
1697      * @param initialSz  initial z scaling factor.
1698      * @param initialMxy initial x-y cross coupling error.
1699      * @param initialMxz initial x-z cross coupling error.
1700      * @param initialMyx initial y-x cross coupling error.
1701      * @param initialMyz initial y-z cross coupling error.
1702      * @param initialMzx initial z-x cross coupling error.
1703      * @param initialMzy initial z-y cross coupling error.
1704      * @throws LockedException if calibrator is currently running.
1705      */
1706     @Override
1707     public void setInitialScalingFactorsAndCrossCouplingErrors(
1708             final double initialSx, final double initialSy, final double initialSz,
1709             final double initialMxy, final double initialMxz, final double initialMyx,
1710             final double initialMyz, final double initialMzx, final double initialMzy) throws LockedException {
1711         if (running) {
1712             throw new LockedException();
1713         }
1714         setInitialScalingFactors(initialSx, initialSy, initialSz);
1715         setInitialCrossCouplingErrors(initialMxy, initialMxz, initialMyx, initialMyz, initialMzx, initialMzy);
1716     }
1717 
1718     /**
1719      * Gets known hard-iron bias as an array.
1720      * Array values are expressed in Teslas (T).
1721      *
1722      * @return array containing coordinates of known bias.
1723      */
1724     @Override
1725     public double[] getHardIron() {
1726         final var result = new double[BodyMagneticFluxDensity.COMPONENTS];
1727         getHardIron(result);
1728         return result;
1729     }
1730 
1731     /**
1732      * Gets known hard-iron bias as an array.
1733      * Array values are expressed in Teslas (T).
1734      *
1735      * @param result instance where result data will be copied to.
1736      * @throws IllegalArgumentException if provided array does not have
1737      *                                  length 3.
1738      */
1739     @Override
1740     public void getHardIron(final double[] result) {
1741         if (result.length != BodyMagneticFluxDensity.COMPONENTS) {
1742             throw new IllegalArgumentException();
1743         }
1744         result[0] = hardIronX;
1745         result[1] = hardIronY;
1746         result[2] = hardIronZ;
1747     }
1748 
1749     /**
1750      * Sets known hard-iron bias as an array.
1751      * Array values are expressed in Teslas (T).
1752      *
1753      * @param hardIron known hard-iron.
1754      * @throws LockedException          if calibrator is currently running.
1755      * @throws IllegalArgumentException if provided array does not have length 3.
1756      */
1757     @Override
1758     public void setHardIron(final double[] hardIron) throws LockedException {
1759         if (running) {
1760             throw new LockedException();
1761         }
1762 
1763         if (hardIron.length != BodyMagneticFluxDensity.COMPONENTS) {
1764             throw new IllegalArgumentException();
1765         }
1766         hardIronX = hardIron[0];
1767         hardIronY = hardIron[1];
1768         hardIronZ = hardIron[2];
1769     }
1770 
1771     /**
1772      * Gets known hard-iron bias as a column matrix.
1773      *
1774      * @return known hard-iron bias as a column matrix.
1775      */
1776     @Override
1777     public Matrix getHardIronMatrix() {
1778         Matrix result;
1779         try {
1780             result = new Matrix(BodyMagneticFluxDensity.COMPONENTS, 1);
1781             getHardIronMatrix(result);
1782         } catch (final WrongSizeException ignore) {
1783             // never happens
1784             result = null;
1785         }
1786         return result;
1787     }
1788 
1789     /**
1790      * Gets known hard-iron bias as a column matrix.
1791      *
1792      * @param result instance where result data will be copied to.
1793      * @throws IllegalArgumentException if provided matrix is not 3x1.
1794      */
1795     @Override
1796     public void getHardIronMatrix(final Matrix result) {
1797         if (result.getRows() != BodyMagneticFluxDensity.COMPONENTS || result.getColumns() != 1) {
1798             throw new IllegalArgumentException();
1799         }
1800         result.setElementAtIndex(0, hardIronX);
1801         result.setElementAtIndex(1, hardIronY);
1802         result.setElementAtIndex(2, hardIronZ);
1803     }
1804 
1805     /**
1806      * Sets known hard-iron bias as a column matrix.
1807      *
1808      * @param hardIron known hard-iron bias.
1809      * @throws LockedException          if calibrator is currently running.
1810      * @throws IllegalArgumentException if provided matrix is not 3x1.
1811      */
1812     @Override
1813     public void setHardIron(final Matrix hardIron) throws LockedException {
1814         if (running) {
1815             throw new LockedException();
1816         }
1817         if (hardIron.getRows() != BodyMagneticFluxDensity.COMPONENTS || hardIron.getColumns() != 1) {
1818             throw new IllegalArgumentException();
1819         }
1820 
1821         hardIronX = hardIron.getElementAtIndex(0);
1822         hardIronY = hardIron.getElementAtIndex(1);
1823         hardIronZ = hardIron.getElementAtIndex(2);
1824     }
1825 
1826     /**
1827      * Gets initial scale factors and cross coupling errors matrix.
1828      *
1829      * @return initial scale factors and cross coupling errors matrix.
1830      */
1831     @Override
1832     public Matrix getInitialMm() {
1833         Matrix result;
1834         try {
1835             result = new Matrix(BodyMagneticFluxDensity.COMPONENTS, BodyMagneticFluxDensity.COMPONENTS);
1836             getInitialMm(result);
1837         } catch (final WrongSizeException ignore) {
1838             // never happens
1839             result = null;
1840         }
1841         return result;
1842     }
1843 
1844     /**
1845      * Gets initial scale factors and cross coupling errors matrix.
1846      *
1847      * @param result instance where data will be stored.
1848      * @throws IllegalArgumentException if provided matrix is not 3x3.
1849      */
1850     @Override
1851     public void getInitialMm(final Matrix result) {
1852         if (result.getRows() != BodyKinematics.COMPONENTS || result.getColumns() != BodyKinematics.COMPONENTS) {
1853             throw new IllegalArgumentException();
1854         }
1855         result.setElementAtIndex(0, initialSx);
1856         result.setElementAtIndex(1, initialMyx);
1857         result.setElementAtIndex(2, initialMzx);
1858 
1859         result.setElementAtIndex(3, initialMxy);
1860         result.setElementAtIndex(4, initialSy);
1861         result.setElementAtIndex(5, initialMzy);
1862 
1863         result.setElementAtIndex(6, initialMxz);
1864         result.setElementAtIndex(7, initialMyz);
1865         result.setElementAtIndex(8, initialSz);
1866     }
1867 
1868     /**
1869      * Sets initial scale factors and cross coupling errors matrix.
1870      *
1871      * @param initialMm initial scale factors and cross coupling errors matrix.
1872      * @throws IllegalArgumentException if provided matrix is not 3x3.
1873      * @throws LockedException          if calibrator is currently running.
1874      */
1875     @Override
1876     public void setInitialMm(final Matrix initialMm) throws LockedException {
1877         if (running) {
1878             throw new LockedException();
1879         }
1880         if (initialMm.getRows() != BodyKinematics.COMPONENTS || initialMm.getColumns() != BodyKinematics.COMPONENTS) {
1881             throw new IllegalArgumentException();
1882         }
1883 
1884         initialSx = initialMm.getElementAtIndex(0);
1885         initialMyx = initialMm.getElementAtIndex(1);
1886         initialMzx = initialMm.getElementAtIndex(2);
1887 
1888         initialMxy = initialMm.getElementAtIndex(3);
1889         initialSy = initialMm.getElementAtIndex(4);
1890         initialMzy = initialMm.getElementAtIndex(5);
1891 
1892         initialMxz = initialMm.getElementAtIndex(6);
1893         initialMyz = initialMm.getElementAtIndex(7);
1894         initialSz = initialMm.getElementAtIndex(8);
1895     }
1896 
1897     /**
1898      * Gets collection of body magnetic flux density measurements taken
1899      * at a given position with different unknown orientations and containing the
1900      * standard deviation of magnetometer measurements.
1901      *
1902      * @return collection of body magnetic flux density measurements at
1903      * a known position and timestamp with unknown orientations.
1904      */
1905     @Override
1906     public Collection<StandardDeviationBodyMagneticFluxDensity> getMeasurements() {
1907         return measurements;
1908     }
1909 
1910     /**
1911      * Sets collection of body magnetic flux density measurements taken
1912      * at a given position with different unknown orientations and containing the
1913      * standard deviation of magnetometer measurements.
1914      *
1915      * @param measurements collection of body magnetic flux density
1916      *                     measurements at a known position and timestamp
1917      *                     with unknown orientations.
1918      * @throws LockedException if calibrator is currently running.
1919      */
1920     @Override
1921     public void setMeasurements(final Collection<StandardDeviationBodyMagneticFluxDensity> measurements)
1922             throws LockedException {
1923         if (running) {
1924             throw new LockedException();
1925         }
1926         this.measurements = measurements;
1927     }
1928 
1929     /**
1930      * Indicates the type of measurement used by this calibrator.
1931      *
1932      * @return type of measurement used by this calibrator.
1933      */
1934     @Override
1935     public MagnetometerCalibratorMeasurementType getMeasurementType() {
1936         return MagnetometerCalibratorMeasurementType.STANDARD_DEVIATION_BODY_MAGNETIC_FLUX_DENSITY;
1937     }
1938 
1939     /**
1940      * Indicates whether this calibrator requires ordered measurements in a
1941      * list or not.
1942      *
1943      * @return true if measurements must be ordered, false otherwise.
1944      */
1945     @Override
1946     public boolean isOrderedMeasurementsRequired() {
1947         return false;
1948     }
1949 
1950     /**
1951      * Indicates whether this calibrator requires quality scores for each
1952      * measurement or not.
1953      *
1954      * @return true if quality scores are required, false otherwise.
1955      */
1956     @Override
1957     public boolean isQualityScoresRequired() {
1958         return false;
1959     }
1960 
1961     /**
1962      * Indicates whether z-axis is assumed to be common for accelerometer,
1963      * gyroscope and magnetometer.
1964      * When enabled, this eliminates 3 variables from Mm (soft-iron) matrix.
1965      *
1966      * @return true if z-axis is assumed to be common for accelerometer,
1967      * gyroscope and magnetometer, false otherwise.
1968      */
1969     @Override
1970     public boolean isCommonAxisUsed() {
1971         return commonAxisUsed;
1972     }
1973 
1974     /**
1975      * Specifies whether z-axis is assumed to be common for accelerometer and
1976      * gyroscope.
1977      * When enabled, this eliminates 3 variables from Mm matrix.
1978      *
1979      * @param commonAxisUsed true if z-axis is assumed to be common for
1980      *                       accelerometer, gyroscope and magnetometer, false
1981      *                       otherwise.
1982      * @throws LockedException if estimator is currently running.
1983      */
1984     @Override
1985     public void setCommonAxisUsed(final boolean commonAxisUsed) throws LockedException {
1986         if (running) {
1987             throw new LockedException();
1988         }
1989 
1990         this.commonAxisUsed = commonAxisUsed;
1991     }
1992 
1993     /**
1994      * Gets listener to handle events raised by this calibrator.
1995      *
1996      * @return listener to handle events raised by this calibrator.
1997      */
1998     public L getListener() {
1999         return listener;
2000     }
2001 
2002     /**
2003      * Sets listener to handle events raised by this calibrator.
2004      *
2005      * @param listener listener to handle events raised by this calibrator.
2006      * @throws LockedException if calibrator is currently running.
2007      */
2008     public void setListener(final L listener) throws LockedException {
2009         if (running) {
2010             throw new LockedException();
2011         }
2012 
2013         this.listener = listener;
2014     }
2015 
2016     /**
2017      * Gets minimum number of required measurements.
2018      *
2019      * @return minimum number of required measurements.
2020      */
2021     @Override
2022     public int getMinimumRequiredMeasurements() {
2023         return commonAxisUsed ? MINIMUM_MEASUREMENTS_COMMON_Z_AXIS : MINIMUM_MEASUREMENTS_GENERAL;
2024     }
2025 
2026     /**
2027      * Indicates whether calibrator is ready to start.
2028      *
2029      * @return true if calibrator is ready, false otherwise.
2030      */
2031     @Override
2032     public boolean isReady() {
2033         return measurements != null && measurements.size() >= getMinimumRequiredMeasurements();
2034     }
2035 
2036     /**
2037      * Indicates whether calibrator is currently running or not.
2038      *
2039      * @return true if calibrator is running, false otherwise.
2040      */
2041     @Override
2042     public boolean isRunning() {
2043         return running;
2044     }
2045 
2046     /**
2047      * Estimates magnetometer calibration parameters containing scale factors
2048      * and cross-coupling errors.
2049      *
2050      * @throws LockedException      if calibrator is currently running.
2051      * @throws NotReadyException    if calibrator is not ready.
2052      * @throws CalibrationException if calibration fails for numerical reasons.
2053      */
2054     @Override
2055     public void calibrate() throws LockedException, NotReadyException, CalibrationException {
2056         if (running) {
2057             throw new LockedException();
2058         }
2059 
2060         if (!isReady()) {
2061             throw new NotReadyException();
2062         }
2063 
2064         try {
2065             running = true;
2066 
2067             onBeforeCalibrate();
2068 
2069             if (listener != null) {
2070                 //noinspection unchecked
2071                 listener.onCalibrateStart((C) this);
2072             }
2073 
2074             if (commonAxisUsed) {
2075                 calibrateCommonAxis();
2076             } else {
2077                 calibrateGeneral();
2078             }
2079 
2080             if (listener != null) {
2081                 //noinspection unchecked
2082                 listener.onCalibrateEnd((C) this);
2083             }
2084 
2085         } catch (final AlgebraException | FittingException | com.irurueta.numerical.NotReadyException e) {
2086             throw new CalibrationException(e);
2087         } finally {
2088             running = false;
2089         }
2090     }
2091 
2092     /**
2093      * Gets estimated magnetometer soft-iron matrix containing scale factors
2094      * and cross coupling errors.
2095      * This is the product of matrix Tm containing cross coupling errors and Km
2096      * containing scaling factors.
2097      * So tat:
2098      * <pre>
2099      *     Mm = [sx    mxy  mxz] = Tm*Km
2100      *          [myx   sy   myz]
2101      *          [mzx   mzy  sz ]
2102      * </pre>
2103      * Where:
2104      * <pre>
2105      *     Km = [sx 0   0 ]
2106      *          [0  sy  0 ]
2107      *          [0  0   sz]
2108      * </pre>
2109      * and
2110      * <pre>
2111      *     Tm = [1          -alphaXy    alphaXz ]
2112      *          [alphaYx    1           -alphaYz]
2113      *          [-alphaZx   alphaZy     1       ]
2114      * </pre>
2115      * Hence:
2116      * <pre>
2117      *     Mm = [sx    mxy  mxz] = Tm*Km =  [sx             -sy * alphaXy   sz * alphaXz ]
2118      *          [myx   sy   myz]            [sx * alphaYx   sy              -sz * alphaYz]
2119      *          [mzx   mzy  sz ]            [-sx * alphaZx  sy * alphaZy    sz           ]
2120      * </pre>
2121      * This instance allows any 3x3 matrix however, typically alphaYx, alphaZx and alphaZy
2122      * are considered to be zero if the accelerometer z-axis is assumed to be the same
2123      * as the body z-axis. When this is assumed, myx = mzx = mzy = 0 and the Mm matrix
2124      * becomes upper diagonal:
2125      * <pre>
2126      *     Mm = [sx    mxy  mxz]
2127      *          [0     sy   myz]
2128      *          [0     0    sz ]
2129      * </pre>
2130      * Values of this matrix are unit-less.
2131      *
2132      * @return estimated magnetometer soft-iron scale factors and cross coupling errors,
2133      * or null if not available.
2134      */
2135     @Override
2136     public Matrix getEstimatedMm() {
2137         return estimatedMm;
2138     }
2139 
2140     /**
2141      * Gets estimated x-axis scale factor.
2142      *
2143      * @return estimated x-axis scale factor or null if not available.
2144      */
2145     @Override
2146     public Double getEstimatedSx() {
2147         return estimatedMm != null ? estimatedMm.getElementAt(0, 0) : null;
2148     }
2149 
2150     /**
2151      * Gets estimated y-axis scale factor.
2152      *
2153      * @return estimated y-axis scale factor or null if not available.
2154      */
2155     @Override
2156     public Double getEstimatedSy() {
2157         return estimatedMm != null ? estimatedMm.getElementAt(1, 1) : null;
2158     }
2159 
2160     /**
2161      * Gets estimated z-axis scale factor.
2162      *
2163      * @return estimated z-axis scale factor or null if not available.
2164      */
2165     @Override
2166     public Double getEstimatedSz() {
2167         return estimatedMm != null ? estimatedMm.getElementAt(2, 2) : null;
2168     }
2169 
2170     /**
2171      * Gets estimated x-y cross-coupling error.
2172      *
2173      * @return estimated x-y cross-coupling error or null if not available.
2174      */
2175     @Override
2176     public Double getEstimatedMxy() {
2177         return estimatedMm != null ? estimatedMm.getElementAt(0, 1) : null;
2178     }
2179 
2180     /**
2181      * Gets estimated x-z cross-coupling error.
2182      *
2183      * @return estimated x-z cross-coupling error or null if not available.
2184      */
2185     @Override
2186     public Double getEstimatedMxz() {
2187         return estimatedMm != null ? estimatedMm.getElementAt(0, 2) : null;
2188     }
2189 
2190     /**
2191      * Gets estimated y-x cross-coupling error.
2192      *
2193      * @return estimated y-x cross-coupling error or null if not available.
2194      */
2195     @Override
2196     public Double getEstimatedMyx() {
2197         return estimatedMm != null ? estimatedMm.getElementAt(1, 0) : null;
2198     }
2199 
2200     /**
2201      * Gets estimated y-z cross-coupling error.
2202      *
2203      * @return estimated y-z cross-coupling error or null if not available.
2204      */
2205     @Override
2206     public Double getEstimatedMyz() {
2207         return estimatedMm != null ? estimatedMm.getElementAt(1, 2) : null;
2208     }
2209 
2210     /**
2211      * Gets estimated z-x cross-coupling error.
2212      *
2213      * @return estimated z-x cross-coupling error or null if not available.
2214      */
2215     @Override
2216     public Double getEstimatedMzx() {
2217         return estimatedMm != null ? estimatedMm.getElementAt(2, 0) : null;
2218     }
2219 
2220     /**
2221      * Gets estimated z-y cross-coupling error.
2222      *
2223      * @return estimated z-y cross-coupling error or null if not available.
2224      */
2225     @Override
2226     public Double getEstimatedMzy() {
2227         return estimatedMm != null ? estimatedMm.getElementAt(2, 1) : null;
2228     }
2229 
2230     /**
2231      * Gets estimated covariance matrix for estimated calibration parameters.
2232      * Diagonal elements of the matrix contains variance for the following
2233      * parameters (following indicated order): sx, sy, sz, mxy, mxz, myx,
2234      * myz, mzx, mzy.
2235      *
2236      * @return estimated covariance matrix for estimated position.
2237      */
2238     @Override
2239     public Matrix getEstimatedCovariance() {
2240         return estimatedCovariance;
2241     }
2242 
2243     /**
2244      * Gets estimated chi square value.
2245      *
2246      * @return estimated chi square value.
2247      */
2248     @Override
2249     public double getEstimatedChiSq() {
2250         return estimatedChiSq;
2251     }
2252 
2253     /**
2254      * Gets estimated chi square degrees of freedom. Degrees of freedom is equal to the number of sampled data minus the
2255      * number of estimated parameters.
2256      *
2257      * @return estimated degrees of freedom of chi square value
2258      */
2259     @Override
2260     public int getEstimatedChiSqDegreesOfFreedom() {
2261         return estimatedChiSqDegreesOfFreedom;
2262     }
2263 
2264     /**
2265      * Gets estimated reduced chi square value. This is equal to estimated chi square value divided by its degrees of
2266      * freedom. Ideally this value should be close to 1.0, indicating that fit is optimal.
2267      * A value larger than 1.0 indicates that fit is not good or noise has been underestimated, and a value smaller than
2268      * 1.0 indicates that there is overfitting or noise has been overestimated.
2269      *
2270      * @return estimated reduced chi square value
2271      */
2272     @Override
2273     public double getEstimatedReducedChiSq() {
2274         return estimatedReducedChiSq;
2275     }
2276 
2277     /**
2278      * Gets estimated mean square error respect to provided measurements.
2279      *
2280      * @return estimated mean square error respect to provided measurements.
2281      */
2282     @Override
2283     public double getEstimatedMse() {
2284         return estimatedMse;
2285     }
2286 
2287     /**
2288      * Gets estimated probability of finding a smaller chi square value expressed as a value between 0.0 and 1.0. The
2289      * smaller the found chi square value is, the better the fit of the estimated parameters to the actual parameter.
2290      * Thus, the smaller the chance of finding a smaller chi square value, then the better the estimated fit is.
2291      *
2292      * @return estimated probability of finding a smaller chi square value.
2293      */
2294     @Override
2295     public double getEstimatedP() {
2296         return estimatedP;
2297     }
2298 
2299     /**
2300      * Gets estimated measure of quality of estimated fit as a value between 0.0 and 1.0. The larger the quality value
2301      * is, the better the fit that has been estimated.
2302      *
2303      * @return estimated measure of quality of estimated fit.
2304      */
2305     @Override
2306     public double getEstimatedQ() {
2307         return estimatedQ;
2308     }
2309 
2310     /**
2311      * Called before calibration occurs.
2312      * This can be overridden by subclasses.
2313      *
2314      * @throws CalibrationException if anything fails.
2315      */
2316     protected void onBeforeCalibrate() throws CalibrationException {
2317         // no action needed
2318     }
2319 
2320     /**
2321      * Internally sets ground truth magnetic flux density norm to be expected at location where
2322      * measurements have been made, expressed in Teslas (T).
2323      *
2324      * @param groundTruthMagneticFluxDensityNorm ground truth magnetic flux density norm or null if undefined.
2325      * @throws IllegalArgumentException if provided value is negative.
2326      */
2327     protected void internalSetGroundTruthMagneticFluxDensityNorm(final Double groundTruthMagneticFluxDensityNorm) {
2328         if (groundTruthMagneticFluxDensityNorm != null && groundTruthMagneticFluxDensityNorm < 0.0) {
2329             throw new IllegalArgumentException();
2330         }
2331         this.groundTruthMagneticFluxDensityNorm = groundTruthMagneticFluxDensityNorm;
2332     }
2333 
2334     /**
2335      * Sets input data into Levenberg-Marquardt fitter.
2336      *
2337      * @throws WrongSizeException never happens.
2338      */
2339     private void setInputData() throws WrongSizeException {
2340         final var gtb = groundTruthMagneticFluxDensityNorm;
2341         final var gtb2 = gtb * gtb;
2342 
2343         final var numMeasurements = measurements.size();
2344         final var x = new Matrix(numMeasurements, BodyMagneticFluxDensity.COMPONENTS);
2345         final var y = new double[numMeasurements];
2346         final var specificForceStandardDeviations = new double[numMeasurements];
2347         var i = 0;
2348         for (final var measurement : measurements) {
2349             final var measuredMagneticFluxDensity = measurement.getMagneticFluxDensity();
2350 
2351             final var bmeasuredX = measuredMagneticFluxDensity.getBx();
2352             final var bmeasuredY = measuredMagneticFluxDensity.getBy();
2353             final var bmeasuredZ = measuredMagneticFluxDensity.getBz();
2354 
2355             x.setElementAt(i, 0, bmeasuredX);
2356             x.setElementAt(i, 1, bmeasuredY);
2357             x.setElementAt(i, 2, bmeasuredZ);
2358 
2359             y[i] = gtb2;
2360 
2361             specificForceStandardDeviations[i] = measurement.getMagneticFluxDensityStandardDeviation();
2362 
2363             i++;
2364         }
2365 
2366         fitter.setInputData(x, y, specificForceStandardDeviations);
2367     }
2368 
2369     /**
2370      * Internal method to perform general calibration.
2371      *
2372      * @throws FittingException                         if Levenberg-Marquardt fails for numerical reasons.
2373      * @throws AlgebraException                         if there are numerical instabilities that prevent
2374      *                                                  matrix inversion.
2375      * @throws com.irurueta.numerical.NotReadyException never happens.
2376      */
2377     private void calibrateGeneral() throws AlgebraException, FittingException,
2378             com.irurueta.numerical.NotReadyException {
2379         // The magnetometer model is:
2380         // bmeas = bm + (I + Mm) * btrue + w
2381 
2382         // Ideally a least squares solution tries to minimize noise component, so:
2383         // bmeas = bm + (I + Mm) * btrue
2384 
2385         // For convergence purposes of the Levenberg-Marquardt algorithm, the
2386         // magnetometer model can be better expressed as:
2387         // bmeas = T*K*(btrue + b)
2388         // bmeas = M*(btrue + b)
2389         // bmeas = M*btrue + M*b
2390 
2391         // where:
2392         // M = I + Mm
2393         // bm = M*b = (I + Mm)*b --> b = M^-1*bm
2394 
2395         // We know that the norm of the true body magnetic flux density
2396         // is equal to the amount of Earth magnetic flux density at provided
2397         // position and timestamp
2398         // ||btrue|| = ||bEarth|| --> from 30 µT to 60 µT
2399 
2400         // Hence:
2401         // bmeas - M*b = M*btrue
2402 
2403         // M^-1 * (bmeas - M*b) = btrue
2404 
2405         // ||bEarth||^2 = ||btrue||^2 = (M^-1 * (bmeas - M*b))^T * (M^-1 * (bmeas - M*b))
2406         // ||bEarth||^2 = (bmeas - M*b)^T*(M^-1)^T * M^-1 * (bmeas - M*b)
2407         // ||bEarth||^2 = (bmeas - M * b)^T * ||M^-1||^2 * (bmeas - M * b)
2408         // ||bEarth||^2 = ||bmeas - M * b||^2 * ||M^-1||^2
2409 
2410         // Where:
2411 
2412         // b = [bx]
2413         //     [by]
2414         //     [bz]
2415 
2416         // M = [m11 	m12 	m13]
2417         //     [m21 	m22 	m23]
2418         //     [m31 	m32 	m33]
2419 
2420         final var gradientEstimator = new GradientEstimator(this::evaluateGeneral);
2421 
2422         final var initialM = Matrix.identity(BodyMagneticFluxDensity.COMPONENTS, BodyMagneticFluxDensity.COMPONENTS);
2423         initialM.add(getInitialMm());
2424 
2425         fitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
2426             @Override
2427             public int getNumberOfDimensions() {
2428                 // Input points are measured magnetic flux density coordinates
2429                 return BodyMagneticFluxDensity.COMPONENTS;
2430             }
2431 
2432             @Override
2433             public double[] createInitialParametersArray() {
2434                 // cross coupling errors M
2435                 return initialM.toArray();
2436             }
2437 
2438             @Override
2439             public double evaluate(
2440                     final int i, final double[] point, final double[] params, final double[] derivatives)
2441                     throws EvaluationException {
2442 
2443                 bmeasX = point[0];
2444                 bmeasY = point[1];
2445                 bmeasZ = point[2];
2446 
2447                 gradientEstimator.gradient(params, derivatives);
2448 
2449                 return evaluateGeneral(params);
2450             }
2451         });
2452 
2453         setInputData();
2454 
2455         fitter.fit();
2456 
2457         final var result = fitter.getA();
2458 
2459         final var m11 = result[0];
2460         final var m21 = result[1];
2461         final var m31 = result[2];
2462 
2463         final var m12 = result[3];
2464         final var m22 = result[4];
2465         final var m32 = result[5];
2466 
2467         final var m13 = result[6];
2468         final var m23 = result[7];
2469         final var m33 = result[8];
2470 
2471         final var mm = new Matrix(BodyMagneticFluxDensity.COMPONENTS, BodyMagneticFluxDensity.COMPONENTS);
2472         mm.setElementAtIndex(0, m11);
2473         mm.setElementAtIndex(1, m21);
2474         mm.setElementAtIndex(2, m31);
2475 
2476         mm.setElementAtIndex(3, m12);
2477         mm.setElementAtIndex(4, m22);
2478         mm.setElementAtIndex(5, m32);
2479 
2480         mm.setElementAtIndex(6, m13);
2481         mm.setElementAtIndex(7, m23);
2482         mm.setElementAtIndex(8, m33);
2483 
2484         setResult(mm);
2485     }
2486 
2487     /**
2488      * Internal method to perform calibration when common z-axis is assumed for both
2489      * the accelerometer and gyroscope.
2490      *
2491      * @throws FittingException                         if Levenberg-Marquardt fails for numerical reasons.
2492      * @throws AlgebraException                         if there are numerical instabilities that prevent
2493      *                                                  matrix inversion.
2494      * @throws com.irurueta.numerical.NotReadyException never happens.
2495      */
2496     private void calibrateCommonAxis() throws AlgebraException, FittingException,
2497             com.irurueta.numerical.NotReadyException {
2498         // The magnetometer model is:
2499         // bmeas = bm + (I + Mm) * btrue + w
2500 
2501         // Ideally a least squares solution tries to minimize noise component, so:
2502         // bmeas = bm + (I + Mm) * btrue
2503 
2504         // For convergence purposes of the Levenberg-Marquardt algorithm, the
2505         // magnetometer model can be better expressed as:
2506         // bmeas = T*K*(btrue + b)
2507         // bmeas = M*(btrue + b)
2508         // bmeas = M*btrue + M*b
2509 
2510         // where:
2511         // M = I + Mm
2512         // bm = M*b = (I + Mm)*b --> b = M^-1*bm
2513 
2514         // We know that the norm of the true body magnetic flux density
2515         // is equal to the amount of Earth magnetic flux density at provided
2516         // position and timestamp
2517         // ||btrue|| = ||bEarth|| --> from 30 µT to 60 µT
2518 
2519         // Hence:
2520         // bmeas - M*b = M*btrue
2521 
2522         // M^-1 * (bmeas - M*b) = btrue
2523 
2524         // ||bEarth||^2 = ||btrue||^2 = (M^-1 * (bmeas - M*b))^T * (M^-1 * (bmeas - M*b))
2525         // ||bEarth||^2 = (bmeas - M*b)^T*(M^-1)^T * M^-1 * (bmeas - M*b)
2526         // ||bEarth||^2 = (bmeas - M * b)^T * ||M^-1||^2 * (bmeas - M * b)
2527         // ||bEarth||^2 = ||bmeas - M * b||^2 * ||M^-1||^2
2528 
2529         // Where:
2530 
2531         // b = [bx]
2532         //     [by]
2533         //     [bz]
2534 
2535         // M = [m11 	m12 	m13]
2536         //     [0 		m22 	m23]
2537         //     [0 	 	0 		m33]
2538 
2539 
2540         final var gradientEstimator = new GradientEstimator(this::evaluateCommonAxis);
2541 
2542         final var initialM = Matrix.identity(BodyMagneticFluxDensity.COMPONENTS, BodyMagneticFluxDensity.COMPONENTS);
2543         initialM.add(getInitialMm());
2544 
2545         // Force initial M to be upper diagonal
2546         initialM.setElementAt(1, 0, 0.0);
2547         initialM.setElementAt(2, 0, 0.0);
2548         initialM.setElementAt(2, 1, 0.0);
2549 
2550         fitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
2551             @Override
2552             public int getNumberOfDimensions() {
2553                 // Input points are measured magnetic flux density coordinates
2554                 return BodyKinematics.COMPONENTS;
2555             }
2556 
2557             @Override
2558             public double[] createInitialParametersArray() {
2559                 final var initial = new double[COMMON_Z_AXIS_UNKNOWNS];
2560 
2561                 // upper diagonal cross coupling errors M
2562                 var k = 0;
2563                 for (var j = 0; j < BodyMagneticFluxDensity.COMPONENTS; j++) {
2564                     for (var i = 0; i < BodyMagneticFluxDensity.COMPONENTS; i++) {
2565                         if (i <= j) {
2566                             initial[k] = initialM.getElementAt(i, j);
2567                             k++;
2568                         }
2569                     }
2570                 }
2571 
2572                 return initial;
2573             }
2574 
2575             @Override
2576             public double evaluate(
2577                     final int i, final double[] point, final double[] params, final double[] derivatives)
2578                     throws EvaluationException {
2579 
2580                 bmeasX = point[0];
2581                 bmeasY = point[1];
2582                 bmeasZ = point[2];
2583 
2584                 gradientEstimator.gradient(params, derivatives);
2585 
2586                 return evaluateCommonAxis(params);
2587             }
2588         });
2589 
2590         setInputData();
2591 
2592         fitter.fit();
2593 
2594         final var result = fitter.getA();
2595 
2596         final var m11 = result[0];
2597 
2598         final var m12 = result[1];
2599         final var m22 = result[2];
2600 
2601         final var m13 = result[3];
2602         final var m23 = result[4];
2603         final var m33 = result[5];
2604 
2605         final var mm = new Matrix(BodyMagneticFluxDensity.COMPONENTS, BodyMagneticFluxDensity.COMPONENTS);
2606         mm.setElementAtIndex(0, m11);
2607         mm.setElementAtIndex(1, 0.0);
2608         mm.setElementAtIndex(2, 0.0);
2609 
2610         mm.setElementAtIndex(3, m12);
2611         mm.setElementAtIndex(4, m22);
2612         mm.setElementAtIndex(5, 0.0);
2613 
2614         mm.setElementAtIndex(6, m13);
2615         mm.setElementAtIndex(7, m23);
2616         mm.setElementAtIndex(8, m33);
2617 
2618         setResult(mm);
2619 
2620         // taking into account that:
2621         // Mm = [sx  mxy  mxz] = [m11  m12  m13]
2622         //      [myx sy   myz]   [m21  m22  m23]
2623         //      [mzx mzy  sz ]   [m31  m32  m33]
2624 
2625         // propagate covariance so that all parameters are taken into account
2626         // in the order: sx, sy, sz, mxy, mxz, myx, myz, mzx, mzy
2627 
2628         // Since estimated values are:
2629         // (m11, m12, m22, m13, m23, m33) = (sx, mxy, sy, mxz, myz, sz)
2630 
2631         // We define a lineal function mapping original parameters for the
2632         // common axis case to the general case
2633         // [sx'] = [1  0  0  0  0  0][sx ]
2634         // [sy']   [0  0  1  0  0  0][mxy]
2635         // [sz']   [0  0  0  0  0  1][sy ]
2636         // [mxy']  [0  1  0  0  0  0][mxz]
2637         // [mxz']  [0  0  0  1  0  0][myz]
2638         // [myx']  [0  0  0  0  0  0][sz ]
2639         // [myz']  [0  0  0  0  1  0]
2640         // [mzx']  [0  0  0  0  0  0]
2641         // [mzy']  [0  0  0  0  0  0]
2642 
2643         // As defined in com.irurueta.statistics.MultivariateNormalDist,
2644         // if we consider the jacobian of the lineal application the matrix shown
2645         // above, then covariance can be propagated as follows
2646         final var jacobian = new Matrix(GENERAL_UNKNOWNS, COMMON_Z_AXIS_UNKNOWNS);
2647         jacobian.setElementAt(0, 0, 1.0);
2648         jacobian.setElementAt(1, 2, 1.0);
2649         jacobian.setElementAt(2, 5, 1.0);
2650         jacobian.setElementAt(3, 1, 1.0);
2651         jacobian.setElementAt(4, 3, 1.0);
2652         jacobian.setElementAt(6, 4, 1.0);
2653         // propagated covariance is J * Cov * J'
2654         final var jacobianTrans = jacobian.transposeAndReturnNew();
2655         jacobian.multiply(estimatedCovariance);
2656         jacobian.multiply(jacobianTrans);
2657         estimatedCovariance = jacobian;
2658     }
2659 
2660     /**
2661      * Makes proper conversion of internal cross-coupling and bias matrices.
2662      *
2663      * @param m internal cross-coupling matrix.
2664      */
2665     private void setResult(final Matrix m) {
2666         // Because:
2667         // M = I + Mm
2668 
2669         // Then:
2670         // Mm = M - I
2671 
2672         if (estimatedMm == null) {
2673             estimatedMm = m;
2674         } else {
2675             estimatedMm.copyFrom(m);
2676         }
2677 
2678         for (var i = 0; i < BodyMagneticFluxDensity.COMPONENTS; i++) {
2679             estimatedMm.setElementAt(i, i, estimatedMm.getElementAt(i, i) - 1.0);
2680         }
2681 
2682         // since only a constant term is subtracted, covariance is preserved
2683         estimatedCovariance = fitter.getCovar();
2684         estimatedChiSq = fitter.getChisq();
2685         estimatedChiSqDegreesOfFreedom = fitter.getChisqDegreesOfFreedom();
2686         estimatedReducedChiSq = fitter.getReducedChisq();
2687         estimatedMse = fitter.getMse();
2688         try {
2689             estimatedP = fitter.getP();
2690             estimatedQ = fitter.getQ();
2691         } catch (final MaxIterationsExceededException ignore) {
2692             // if numerical instabilities arise, we assume worst case (no fit at all)
2693             // probability of finding a smaller chi square value is 1.0
2694             // quality of fit is 0.0
2695             estimatedP = 1.0;
2696             estimatedQ = 0.0;
2697         }
2698     }
2699 
2700     /**
2701      * Computes estimated true magnetic flux density squared norm using current measured
2702      * body magnetic flux density and provided parameters for the general case.
2703      * This method is internally executed during gradient estimation and
2704      * Levenberg-Marquardt fitting needed for calibration computation.
2705      *
2706      * @param params array containing current parameters for the general purpose case.
2707      *               Must have length 9.
2708      * @return estimated true specific force squared norm.
2709      * @throws EvaluationException if there are numerical instabilities.
2710      */
2711     private double evaluateGeneral(final double[] params) throws EvaluationException {
2712         final var m11 = params[0];
2713         final var m21 = params[1];
2714         final var m31 = params[2];
2715 
2716         final var m12 = params[3];
2717         final var m22 = params[4];
2718         final var m32 = params[5];
2719 
2720         final var m13 = params[6];
2721         final var m23 = params[7];
2722         final var m33 = params[8];
2723 
2724         return evaluate(m11, m21, m31, m12, m22, m32, m13, m23, m33);
2725     }
2726 
2727     /**
2728      * Computes estimated true magnetic flux density squared norm using current measured
2729      * body magnetic flux density and provided parameters when common z-axis is assumed.
2730      * This method is internally executed during gradient estimation and
2731      * Levenberg-Marquardt fitting needed for calibration computation.
2732      *
2733      * @param params array containing current parameters for the common z-axis case.
2734      *               Must have length 6.
2735      * @return estimated true specific force squared norm.
2736      * @throws EvaluationException if there are numerical instabilities.
2737      */
2738     private double evaluateCommonAxis(final double[] params) throws EvaluationException {
2739         final var m11 = params[0];
2740 
2741         final var m12 = params[1];
2742         final var m22 = params[2];
2743 
2744         final var m13 = params[3];
2745         final var m23 = params[4];
2746         final var m33 = params[5];
2747 
2748         return evaluate(m11, 0.0, 0.0, m12, m22, 0.0, m13, m23, m33);
2749     }
2750 
2751     /**
2752      * Computes estimated true magnetic flux density squared norm using current measured
2753      * body magnetic flux density and provided parameters.
2754      * This method is internally executed during gradient estimation and
2755      * Levenberg-Marquardt fitting needed for calibration computation.
2756      *
2757      * @param m11 element 1,1 of cross-coupling error matrix.
2758      * @param m21 element 2,1 of cross-coupling error matrix.
2759      * @param m31 element 3,1 of cross-coupling error matrix.
2760      * @param m12 element 1,2 of cross-coupling error matrix.
2761      * @param m22 element 2,2 of cross-coupling error matrix.
2762      * @param m32 element 3,2 of cross-coupling error matrix.
2763      * @param m13 element 1,3 of cross-coupling error matrix.
2764      * @param m23 element 2,3 of cross-coupling error matrix.
2765      * @param m33 element 3,3 of cross-coupling error matrix.
2766      * @return estimated true specific force squared norm.
2767      * @throws EvaluationException if there are numerical instabilities.
2768      */
2769     private double evaluate(
2770             final double m11, final double m21, final double m31,
2771             final double m12, final double m22, final double m32,
2772             final double m13, final double m23, final double m33) throws EvaluationException {
2773 
2774         // bmeas = M*(btrue + b)
2775 
2776         // btrue = M^-1*bmeas - b
2777 
2778         try {
2779             if (bmeas == null) {
2780                 bmeas = new Matrix(BodyMagneticFluxDensity.COMPONENTS, 1);
2781             }
2782             if (m == null) {
2783                 m = new Matrix(BodyMagneticFluxDensity.COMPONENTS, BodyMagneticFluxDensity.COMPONENTS);
2784             }
2785             if (invM == null) {
2786                 invM = new Matrix(BodyMagneticFluxDensity.COMPONENTS, BodyMagneticFluxDensity.COMPONENTS);
2787             }
2788             if (b == null) {
2789                 b = new Matrix(BodyMagneticFluxDensity.COMPONENTS, 1);
2790             }
2791             if (btrue == null) {
2792                 btrue = new Matrix(BodyMagneticFluxDensity.COMPONENTS, 1);
2793             }
2794             if (bm == null) {
2795                 bm = new Matrix(BodyMagneticFluxDensity.COMPONENTS, 1);
2796             }
2797 
2798             getHardIronMatrix(bm);
2799 
2800             bmeas.setElementAtIndex(0, bmeasX);
2801             bmeas.setElementAtIndex(1, bmeasY);
2802             bmeas.setElementAtIndex(2, bmeasZ);
2803 
2804             m.setElementAt(0, 0, m11);
2805             m.setElementAt(1, 0, m21);
2806             m.setElementAt(2, 0, m31);
2807 
2808             m.setElementAt(0, 1, m12);
2809             m.setElementAt(1, 1, m22);
2810             m.setElementAt(2, 1, m32);
2811 
2812             m.setElementAt(0, 2, m13);
2813             m.setElementAt(1, 2, m23);
2814             m.setElementAt(2, 2, m33);
2815 
2816             Utils.inverse(m, invM);
2817 
2818             // b = m^-1 * bm
2819             invM.multiply(bm, b);
2820 
2821             invM.multiply(bmeas, btrue);
2822             btrue.subtract(b);
2823 
2824             final var norm = Utils.normF(btrue);
2825             return norm * norm;
2826 
2827         } catch (final AlgebraException e) {
2828             throw new EvaluationException(e);
2829         }
2830     }
2831 
2832     /**
2833      * Converts magnetic flux density value and unit to Teslas.
2834      *
2835      * @param value magnetic flux density value.
2836      * @param unit  unit of magnetic flux density value.
2837      * @return converted value.
2838      */
2839     private static double convertMagneticFluxDensity(final double value, final MagneticFluxDensityUnit unit) {
2840         return MagneticFluxDensityConverter.convert(value, unit, MagneticFluxDensityUnit.TESLA);
2841     }
2842 
2843     /**
2844      * Converts magnetic flux density instance to Teslas.
2845      *
2846      * @param magneticFluxDensity magnetic flux density instance to be converted.
2847      * @return converted value.
2848      */
2849     private static double convertMagneticFluxDensity(final MagneticFluxDensity magneticFluxDensity) {
2850         return convertMagneticFluxDensity(magneticFluxDensity.getValue().doubleValue(), magneticFluxDensity.getUnit());
2851     }
2852 }