View Javadoc
1   /*
2    * Copyright (C) 2020 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.Matrix;
19  import com.irurueta.algebra.WrongSizeException;
20  import com.irurueta.navigation.LockedException;
21  import com.irurueta.navigation.NotReadyException;
22  import com.irurueta.navigation.frames.CoordinateTransformation;
23  import com.irurueta.navigation.frames.FrameType;
24  import com.irurueta.navigation.frames.converters.ECEFtoNEDFrameConverter;
25  import com.irurueta.navigation.inertial.BodyKinematics;
26  import com.irurueta.navigation.inertial.BodyMagneticFluxDensity;
27  import com.irurueta.navigation.inertial.calibration.CalibrationException;
28  import com.irurueta.navigation.inertial.calibration.MagneticFluxDensityTriad;
29  import com.irurueta.navigation.inertial.calibration.StandardDeviationFrameBodyMagneticFluxDensity;
30  import com.irurueta.navigation.inertial.estimators.BodyMagneticFluxDensityEstimator;
31  import com.irurueta.navigation.inertial.wmm.WMMEarthMagneticFluxDensityEstimator;
32  import com.irurueta.navigation.inertial.wmm.WorldMagneticModel;
33  import com.irurueta.numerical.robust.InliersData;
34  import com.irurueta.numerical.robust.RobustEstimatorMethod;
35  import com.irurueta.units.MagneticFluxDensity;
36  import com.irurueta.units.MagneticFluxDensityConverter;
37  import com.irurueta.units.MagneticFluxDensityUnit;
38  
39  import java.io.IOException;
40  import java.util.ArrayList;
41  import java.util.List;
42  
43  /**
44   * This is an abstract class to robustly estimate magnetometer
45   * hard-iron biases, soft-iron cross couplings and scaling factors.
46   * <p>
47   * To use this calibrator at least 4 measurements at different known
48   * frames must be provided. In other words, magnetometer samples must
49   * be obtained at 4 different positions or orientations.
50   * Notice that frame velocities are ignored by this calibrator.
51   * <p>
52   * Measured magnetic flux density is assumed to follow the model shown below:
53   * <pre>
54   *     mBmeas = bm + (I + Mm) * mBtrue + w
55   * </pre>
56   * Where:
57   * - mBmeas is the measured magnetic flux density. This is a 3x1 vector.
58   * - bm is magnetometer hard-iron bias. Ideally, on a perfect magnetometer,
59   * this should be a 3x1 zero vector.
60   * - I is the 3x3 identity matrix.
61   * - Mm is the 3x3 soft-iron matrix containing cross-couplings and scaling
62   * factors. Ideally, on a perfect magnetometer, this should be a 3x3 zero
63   * matrix.
64   * - mBtrue is ground-truth magnetic flux density. This is a 3x1 vector.
65   * - w is measurement noise. This is a 3x1 vector.
66   */
67  public abstract class RobustKnownFrameMagnetometerCalibrator implements MagnetometerNonLinearCalibrator,
68          UnknownHardIronNonLinearMagnetometerCalibrator,
69          OrderedStandardDeviationFrameBodyMagneticFluxDensityMagnetometerCalibrator,
70          QualityScoredMagnetometerCalibrator {
71  
72      /**
73       * Indicates whether by default a common z-axis is assumed for the accelerometer,
74       * gyroscope and magnetometer.
75       */
76      public static final boolean DEFAULT_USE_COMMON_Z_AXIS = false;
77  
78      /**
79       * Required minimum number of measurements.
80       */
81      public static final int MINIMUM_MEASUREMENTS = 4;
82  
83      /**
84       * Indicates that by default a linear calibrator is used for preliminary solution estimation.
85       * The result obtained on each preliminary solution might be later refined.
86       */
87      public static final boolean DEFAULT_USE_LINEAR_CALIBRATOR = true;
88  
89      /**
90       * Indicates that by default preliminary solutions are refined.
91       */
92      public static final boolean DEFAULT_REFINE_PRELIMINARY_SOLUTIONS = false;
93  
94      /**
95       * Default robust estimator method when none is provided.
96       */
97      public static final RobustEstimatorMethod DEFAULT_ROBUST_METHOD = RobustEstimatorMethod.LMEDS;
98  
99      /**
100      * Indicates that result is refined by default using a non-linear calibrator
101      * (which uses a Levenberg-Marquardt fitter).
102      */
103     public static final boolean DEFAULT_REFINE_RESULT = true;
104 
105     /**
106      * Indicates that covariance is kept by default after refining result.
107      */
108     public static final boolean DEFAULT_KEEP_COVARIANCE = true;
109 
110     /**
111      * Default amount of progress variation before notifying a change in estimation progress.
112      * By default this is set to 5%.
113      */
114     public static final float DEFAULT_PROGRESS_DELTA = 0.05f;
115 
116     /**
117      * Minimum allowed value for progress delta.
118      */
119     public static final float MIN_PROGRESS_DELTA = 0.0f;
120 
121     /**
122      * Maximum allowed value for progress delta.
123      */
124     public static final float MAX_PROGRESS_DELTA = 1.0f;
125 
126     /**
127      * Constant defining default confidence of the estimated result, which is
128      * 99%. This means that with a probability of 99% estimation will be
129      * accurate because chosen sub-samples will be inliers.
130      */
131     public static final double DEFAULT_CONFIDENCE = 0.99;
132 
133     /**
134      * Default maximum allowed number of iterations.
135      */
136     public static final int DEFAULT_MAX_ITERATIONS = 5000;
137 
138     /**
139      * Minimum allowed confidence value.
140      */
141     public static final double MIN_CONFIDENCE = 0.0;
142 
143     /**
144      * Maximum allowed confidence value.
145      */
146     public static final double MAX_CONFIDENCE = 1.0;
147 
148     /**
149      * Minimum allowed number of iterations.
150      */
151     public static final int MIN_ITERATIONS = 1;
152 
153     /**
154      * Contains a list of body magnetic flux density measurements taken
155      * at different frames (positions and orientations) and containing the
156      * standard deviation of magnetometer measurements.
157      * If a single device magnetometer needs to be calibrated, typically all
158      * measurements are taken at the same position, with zero velocity and
159      * multiple orientations.
160      * However, if we just want to calibrate a given magnetometer model (e.g.
161      * obtain an average and less precise calibration for the magnetometer of
162      * a given phone model), we could take measurements collected throughout
163      * the planet at multiple positions while the phone remains static (e.g.
164      * while charging), hence each measurement position will change, velocity
165      * will remain zero and orientation will be typically constant at
166      * horizontal orientation while the phone remains on a
167      * flat surface.
168      */
169     protected List<StandardDeviationFrameBodyMagneticFluxDensity> measurements;
170 
171     /**
172      * Listener to be notified of events such as when calibration starts, ends or its
173      * progress significantly changes.
174      */
175     protected RobustKnownFrameMagnetometerCalibratorListener listener;
176 
177     /**
178      * Indicates whether estimator is running.
179      */
180     protected boolean running;
181 
182     /**
183      * Amount of progress variation before notifying a progress change during calibration.
184      */
185     protected float progressDelta = DEFAULT_PROGRESS_DELTA;
186 
187     /**
188      * Amount of confidence expressed as a value between 0.0 and 1.0 (which is equivalent
189      * to 100%). The amount of confidence indicates the probability that the estimated
190      * result is correct. Usually this value will be close to 1.0, but not exactly 1.0.
191      */
192     protected double confidence = DEFAULT_CONFIDENCE;
193 
194     /**
195      * Maximum allowed number of iterations. When the maximum number of iterations is
196      * exceeded, result will not be available, however an approximate result will be
197      * available for retrieval.
198      */
199     protected int maxIterations = DEFAULT_MAX_ITERATIONS;
200 
201     /**
202      * Data related to inlier found after calibration.
203      */
204     protected InliersData inliersData;
205 
206     /**
207      * Indicates whether result must be refined using a non linear calibrator over
208      * found inliers.
209      * If true, inliers will be computed and kept in any implementation regardless of the
210      * settings.
211      */
212     protected boolean refineResult = DEFAULT_REFINE_RESULT;
213 
214     /**
215      * Size of subsets to be checked during robust estimation.
216      */
217     protected int preliminarySubsetSize = MINIMUM_MEASUREMENTS;
218 
219     /**
220      * This flag indicates whether z-axis is assumed to be common for accelerometer,
221      * gyroscope and magnetometer.
222      * When enabled, this eliminates 3 variables from soft-iron (Mm) matrix.
223      */
224     private boolean commonAxisUsed = DEFAULT_USE_COMMON_Z_AXIS;
225 
226     /**
227      * Initial x-coordinate of hard-iron bias to be used to find a solution.
228      * This is expressed in Teslas (T).
229      */
230     private double initialHardIronX;
231 
232     /**
233      * Initial y-coordinate of hard-iron bias to be used to find a solution.
234      * This is expressed in Teslas (T).
235      */
236     private double initialHardIronY;
237 
238     /**
239      * Initial z-coordinate of hard-iron bias to be used to find a solution.
240      * This is expressed in Teslas (T).
241      */
242     private double initialHardIronZ;
243 
244     /**
245      * Initial x scaling factor.
246      */
247     private double initialSx;
248 
249     /**
250      * Initial y scaling factor.
251      */
252     private double initialSy;
253 
254     /**
255      * Initial z scaling factor.
256      */
257     private double initialSz;
258 
259     /**
260      * Initial x-y cross coupling error.
261      */
262     private double initialMxy;
263 
264     /**
265      * Initial x-z cross coupling error.
266      */
267     private double initialMxz;
268 
269     /**
270      * Initial y-x cross coupling error.
271      */
272     private double initialMyx;
273 
274     /**
275      * Initial y-z cross coupling error.
276      */
277     private double initialMyz;
278 
279     /**
280      * Initial z-x cross coupling error.
281      */
282     private double initialMzx;
283 
284     /**
285      * Initial z-y cross coupling error.
286      */
287     private double initialMzy;
288 
289     /**
290      * Indicates whether a linear calibrator is used or not for preliminary
291      * solutions.
292      */
293     private boolean useLinearCalibrator = DEFAULT_USE_LINEAR_CALIBRATOR;
294 
295     /**
296      * Indicates whether preliminary solutions must be refined after an initial linear solution
297      * is found.
298      */
299     private boolean refinePreliminarySolutions = DEFAULT_REFINE_PRELIMINARY_SOLUTIONS;
300 
301     /**
302      * Estimated magnetometer hard-iron biases for each magnetometer axis
303      * expressed in Teslas (T).
304      */
305     private double[] estimatedHardIron;
306 
307     /**
308      * Estimated magnetometer soft-iron matrix containing scale factors
309      * and cross coupling errors.
310      * This is the product of matrix Tm containing cross coupling errors and Km
311      * containing scaling factors.
312      * So tat:
313      * <pre>
314      *     Mm = [sx    mxy  mxz] = Tm*Km
315      *          [myx   sy   myz]
316      *          [mzx   mzy  sz ]
317      * </pre>
318      * Where:
319      * <pre>
320      *     Km = [sx 0   0 ]
321      *          [0  sy  0 ]
322      *          [0  0   sz]
323      * </pre>
324      * and
325      * <pre>
326      *     Tm = [1          -alphaXy    alphaXz ]
327      *          [alphaYx    1           -alphaYz]
328      *          [-alphaZx   alphaZy     1       ]
329      * </pre>
330      * Hence:
331      * <pre>
332      *     Mm = [sx    mxy  mxz] = Tm*Km =  [sx             -sy * alphaXy   sz * alphaXz ]
333      *          [myx   sy   myz]            [sx * alphaYx   sy              -sz * alphaYz]
334      *          [mzx   mzy  sz ]            [-sx * alphaZx  sy * alphaZy    sz           ]
335      * </pre>
336      * This instance allows any 3x3 matrix however, typically alphaYx, alphaZx and alphaZy
337      * are considered to be zero if the accelerometer z-axis is assumed to be the same
338      * as the body z-axis. When this is assumed, myx = mzx = mzy = 0 and the Mm matrix
339      * becomes upper diagonal:
340      * <pre>
341      *     Mm = [sx    mxy  mxz]
342      *          [0     sy   myz]
343      *          [0     0    sz ]
344      * </pre>
345      * Values of this matrix are unit-less.
346      */
347     private Matrix estimatedMm;
348 
349     /**
350      * Indicates whether covariance must be kept after refining result.
351      * This setting is only taken into account if result is refined.
352      */
353     private boolean keepCovariance = DEFAULT_KEEP_COVARIANCE;
354 
355     /**
356      * Estimated covariance matrix for estimated parameters.
357      */
358     private Matrix estimatedCovariance;
359 
360     /**
361      * Estimated chi square value.
362      */
363     private double estimatedChiSq;
364 
365     /**
366      * Estimated degrees of freedom of chi square value. Degrees of freedom is equal to the number of sampled data
367      * minus the number of estimated parameters.
368      */
369     private int estimatedChiSqDegreesOfFreedom;
370 
371     /**
372      * Estimated reduced chi square value. This is equal to estimated chi square value divided by its degrees of
373      * freedom. Ideally this value should be close to 1.0.
374      */
375     private double estimatedReducedChiSq;
376 
377     /**
378      * Estimated mean square error respect to provided measurements.
379      */
380     private double estimatedMse;
381 
382     /**
383      * Estimated probability of finding a smaller chi square value expressed as a value between 0.0 and 1.0. The smaller
384      * the found chi square value is, the better the fit of the estimated parameters to the actual parameter. Thus, the
385      * smaller the chance of finding a smaller chi square value, then the better the estimated fit is.
386      */
387     private double estimatedP;
388 
389     /**
390      * Estimated measure of quality of estimated fit as a value between 0.0 and 1.0. The larger the quality value is,
391      * the better the fit that has been estimated.
392      */
393     private double estimatedQ;
394 
395     /**
396      * Contains Earth's magnetic model.
397      */
398     private WorldMagneticModel magneticModel;
399 
400     /**
401      * A linear least squares calibrator.
402      */
403     private final KnownFrameMagnetometerLinearLeastSquaresCalibrator linearCalibrator =
404             new KnownFrameMagnetometerLinearLeastSquaresCalibrator();
405 
406     /**
407      * A non-linear least squares calibrator.
408      */
409     private final KnownFrameMagnetometerNonLinearLeastSquaresCalibrator nonLinearCalibrator =
410             new KnownFrameMagnetometerNonLinearLeastSquaresCalibrator();
411 
412     /**
413      * World Magnetic Model estimator.
414      */
415     private WMMEarthMagneticFluxDensityEstimator wmmEstimator;
416 
417     /**
418      * Constructor.
419      */
420     protected RobustKnownFrameMagnetometerCalibrator() {
421     }
422 
423     /**
424      * Constructor.
425      *
426      * @param listener listener to be notified of events such as when estimation
427      *                 starts, ends or its progress significantly changes.
428      */
429     protected RobustKnownFrameMagnetometerCalibrator(final RobustKnownFrameMagnetometerCalibratorListener listener) {
430         this.listener = listener;
431     }
432 
433     /**
434      * Constructor.
435      *
436      * @param measurements list of body magnetic flux density measurements with standard
437      *                     deviations taken at different frames (positions and
438      *                     orientations).
439      */
440     protected RobustKnownFrameMagnetometerCalibrator(
441             final List<StandardDeviationFrameBodyMagneticFluxDensity> measurements) {
442         this.measurements = measurements;
443     }
444 
445     /**
446      * Constructor.
447      *
448      * @param measurements list of body magnetic flux density measurements with standard
449      *                     deviations taken at different frames (positions and
450      *                     orientations).
451      * @param listener     listener to handle events raised by this calibrator.
452      */
453     protected RobustKnownFrameMagnetometerCalibrator(
454             final List<StandardDeviationFrameBodyMagneticFluxDensity> measurements,
455             final RobustKnownFrameMagnetometerCalibratorListener listener) {
456         this(measurements);
457         this.listener = listener;
458     }
459 
460     /**
461      * Constructor.
462      *
463      * @param commonAxisUsed indicates whether z-axis is assumed to be common
464      *                       for the accelerometer, gyroscope and magnetometer.
465      */
466     protected RobustKnownFrameMagnetometerCalibrator(final boolean commonAxisUsed) {
467         this.commonAxisUsed = commonAxisUsed;
468     }
469 
470     /**
471      * Constructor.
472      *
473      * @param commonAxisUsed indicates whether z-axis is assumed to be common
474      *                       for the accelerometer, gyroscope and magnetometer.
475      * @param listener       listener to handle events raised by this calibrator.
476      */
477     protected RobustKnownFrameMagnetometerCalibrator(
478             final boolean commonAxisUsed, final RobustKnownFrameMagnetometerCalibratorListener listener) {
479         this(commonAxisUsed);
480         this.listener = listener;
481     }
482 
483     /**
484      * Constructor.
485      *
486      * @param measurements   list of body magnetic flux density measurements with standard
487      *                       deviations taken at different frames (positions and
488      *                       orientations).
489      * @param commonAxisUsed indicates whether z-axis is assumed to be common
490      *                       for the accelerometer, gyroscope and magnetometer.
491      */
492     protected RobustKnownFrameMagnetometerCalibrator(
493             final List<StandardDeviationFrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed) {
494         this(measurements);
495         this.commonAxisUsed = commonAxisUsed;
496     }
497 
498     /**
499      * Constructor.
500      *
501      * @param measurements   list of body magnetic flux density measurements with standard
502      *                       deviations taken at different frames (positions and
503      *                       orientations).
504      * @param commonAxisUsed indicates whether z-axis is assumed to be common
505      *                       for the accelerometer, gyroscope and magnetometer.
506      * @param listener       listener to handle events raised by this calibrator.
507      */
508     protected RobustKnownFrameMagnetometerCalibrator(
509             final List<StandardDeviationFrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
510             final RobustKnownFrameMagnetometerCalibratorListener listener) {
511         this(measurements, commonAxisUsed);
512         this.listener = listener;
513     }
514 
515     /**
516      * Gets initial x-coordinate of magnetometer hard-iron bias to be used
517      * to find a solution.
518      * This is expressed in Teslas (T).
519      *
520      * @return initial x-coordinate of magnetometer hard-iron bias.
521      */
522     @Override
523     public double getInitialHardIronX() {
524         return initialHardIronX;
525     }
526 
527     /**
528      * Sets initial x-coordinate of magnetometer hard-iron bias to be used
529      * to find a solution.
530      * This is expressed in Teslas (T).
531      *
532      * @param initialHardIronX initial x-coordinate of magnetometer
533      *                         hard-iron bias.
534      * @throws LockedException if calibrator is currently running.
535      */
536     @Override
537     public void setInitialHardIronX(final double initialHardIronX) throws LockedException {
538         if (running) {
539             throw new LockedException();
540         }
541         this.initialHardIronX = initialHardIronX;
542     }
543 
544     /**
545      * Gets initial y-coordinate of magnetometer hard-iron bias to be used
546      * to find a solution.
547      * This is expressed in Teslas (T).
548      *
549      * @return initial y-coordinate of magnetometer hard-iron bias.
550      */
551     @Override
552     public double getInitialHardIronY() {
553         return initialHardIronY;
554     }
555 
556     /**
557      * Sets initial y-coordinate of magnetometer hard-iron bias to be used
558      * to find a solution.
559      * This is expressed in Teslas (T).
560      *
561      * @param initialHardIronY initial y-coordinate of magnetometer
562      *                         hard-iron bias.
563      * @throws LockedException if calibrator is currently running.
564      */
565     @Override
566     public void setInitialHardIronY(final double initialHardIronY) throws LockedException {
567         if (running) {
568             throw new LockedException();
569         }
570         this.initialHardIronY = initialHardIronY;
571     }
572 
573     /**
574      * Gets initial z-coordinate of magnetometer hard-iron bias to be used
575      * to find a solution.
576      * This is expressed in Teslas (T).
577      *
578      * @return initial z-coordinate of magnetometer hard-iron bias.
579      */
580     @Override
581     public double getInitialHardIronZ() {
582         return initialHardIronZ;
583     }
584 
585     /**
586      * Sets initial z-coordinate of magnetometer hard-iron bias to be used
587      * to find a solution.
588      * This is expressed in meters Teslas (T).
589      *
590      * @param initialHardIronZ initial z-coordinate of magnetometer
591      *                         hard-iron bias.
592      * @throws LockedException if calibrator is currently running.
593      */
594     @Override
595     public void setInitialHardIronZ(final double initialHardIronZ) throws LockedException {
596         if (running) {
597             throw new LockedException();
598         }
599         this.initialHardIronZ = initialHardIronZ;
600     }
601 
602     /**
603      * Gets initial x-coordinate of magnetometer hard iron bias to be used
604      * to find a solution.
605      *
606      * @return initial x-coordinate of magnetometer hard-iron bias.
607      */
608     @Override
609     public MagneticFluxDensity getInitialHardIronXAsMagneticFluxDensity() {
610         return new MagneticFluxDensity(initialHardIronX, MagneticFluxDensityUnit.TESLA);
611     }
612 
613     /**
614      * Gets initial x-coordinate of magnetometer hard iron bias to be used
615      * to find a solution.
616      *
617      * @param result instance where result will be stored.
618      */
619     @Override
620     public void getInitialHardIronXAsMagneticFluxDensity(final MagneticFluxDensity result) {
621         result.setValue(initialHardIronX);
622         result.setUnit(MagneticFluxDensityUnit.TESLA);
623     }
624 
625     /**
626      * Sets initial x-coordinate of magnetometer hard iron bias to be used
627      * to find a solution.
628      *
629      * @param initialHardIronX initial x-coordinate of magnetometer bias.
630      * @throws LockedException if calibrator is currently running.
631      */
632     @Override
633     public void setInitialHardIronX(final MagneticFluxDensity initialHardIronX) throws LockedException {
634         if (running) {
635             throw new LockedException();
636         }
637         this.initialHardIronX = convertMagneticFluxDensity(initialHardIronX);
638     }
639 
640     /**
641      * Gets initial y-coordinate of magnetometer hard iron bias to be used
642      * to find a solution.
643      *
644      * @return initial y-coordinate of magnetometer hard-iron bias.
645      */
646     @Override
647     public MagneticFluxDensity getInitialHardIronYAsMagneticFluxDensity() {
648         return new MagneticFluxDensity(initialHardIronY, MagneticFluxDensityUnit.TESLA);
649     }
650 
651     /**
652      * Gets initial y-coordinate of magnetometer hard iron bias to be used
653      * to find a solution.
654      *
655      * @param result instance where result will be stored.
656      */
657     @Override
658     public void getInitialHardIronYAsMagneticFluxDensity(final MagneticFluxDensity result) {
659         result.setValue(initialHardIronY);
660         result.setUnit(MagneticFluxDensityUnit.TESLA);
661     }
662 
663     /**
664      * Sets initial y-coordinate of magnetometer hard iron bias to be used
665      * to find a solution.
666      *
667      * @param initialHardIronY initial y-coordinate of magnetometer bias.
668      * @throws LockedException if calibrator is currently running.
669      */
670     @Override
671     public void setInitialHardIronY(final MagneticFluxDensity initialHardIronY) throws LockedException {
672         if (running) {
673             throw new LockedException();
674         }
675         this.initialHardIronY = convertMagneticFluxDensity(initialHardIronY);
676     }
677 
678     /**
679      * Gets initial z-coordinate of magnetometer hard iron bias to be used
680      * to find a solution.
681      *
682      * @return initial z-coordinate of magnetometer hard-iron bias.
683      */
684     @Override
685     public MagneticFluxDensity getInitialHardIronZAsMagneticFluxDensity() {
686         return new MagneticFluxDensity(initialHardIronZ, MagneticFluxDensityUnit.TESLA);
687     }
688 
689     /**
690      * Gets initial z-coordinate of magnetometer hard iron bias to be used
691      * to find a solution.
692      *
693      * @param result instance where result will be stored.
694      */
695     @Override
696     public void getInitialHardIronZAsMagneticFluxDensity(final MagneticFluxDensity result) {
697         result.setValue(initialHardIronZ);
698         result.setUnit(MagneticFluxDensityUnit.TESLA);
699     }
700 
701     /**
702      * Sets initial z-coordinate of magnetometer hard iron bias to be used
703      * to find a solution.
704      *
705      * @param initialHardIronZ initial z-coordinate of magnetometer bias.
706      * @throws LockedException if calibrator is currently running.
707      */
708     @Override
709     public void setInitialHardIronZ(final MagneticFluxDensity initialHardIronZ) throws LockedException {
710         if (running) {
711             throw new LockedException();
712         }
713         this.initialHardIronZ = convertMagneticFluxDensity(initialHardIronZ);
714     }
715 
716     /**
717      * Sets initial hard-iron bias coordinates of magnetometer used to find
718      * a solution expressed in Teslas (T).
719      *
720      * @param initialHardIronX initial x-coordinate of magnetometer
721      *                         hard-iron bias.
722      * @param initialHardIronY initial y-coordinate of magnetometer
723      *                         hard-iron bias.
724      * @param initialHardIronZ initial z-coordinate of magnetometer
725      *                         hard-iron bias.
726      * @throws LockedException if calibrator is currently running.
727      */
728     @Override
729     public void setInitialHardIron(
730             final double initialHardIronX, final double initialHardIronY, final double initialHardIronZ)
731             throws LockedException {
732         if (running) {
733             throw new LockedException();
734         }
735         this.initialHardIronX = initialHardIronX;
736         this.initialHardIronY = initialHardIronY;
737         this.initialHardIronZ = initialHardIronZ;
738     }
739 
740     /**
741      * Sets initial hard iron coordinates of magnetometer used to find a solution.
742      *
743      * @param initialHardIronX initial x-coordinate of magnetometer bias.
744      * @param initialHardIronY initial y-coordinate of magnetometer bias.
745      * @param initialHardIronZ initial z-coordinate of magnetometer bias.
746      * @throws LockedException if calibrator is currently running.
747      */
748     @Override
749     public void setInitialHardIron(
750             final MagneticFluxDensity initialHardIronX, final MagneticFluxDensity initialHardIronY,
751             final MagneticFluxDensity initialHardIronZ) throws LockedException {
752         if (running) {
753             throw new LockedException();
754         }
755 
756         this.initialHardIronX = convertMagneticFluxDensity(initialHardIronX);
757         this.initialHardIronY = convertMagneticFluxDensity(initialHardIronY);
758         this.initialHardIronZ = convertMagneticFluxDensity(initialHardIronZ);
759     }
760 
761     /**
762      * Gets initial hard-iron used to find a solution.
763      *
764      * @return initial hard-iron.
765      */
766     @Override
767     public MagneticFluxDensityTriad getInitialHardIronAsTriad() {
768         return new MagneticFluxDensityTriad(MagneticFluxDensityUnit.TESLA,
769                 initialHardIronX, initialHardIronY, initialHardIronZ);
770     }
771 
772     /**
773      * Gets initial hard-iron used to find a solution.
774      *
775      * @param result instance where result will be stored.
776      */
777     @Override
778     public void getInitialHardIronAsTriad(final MagneticFluxDensityTriad result) {
779         result.setValueCoordinatesAndUnit(initialHardIronX, initialHardIronY, initialHardIronZ,
780                 MagneticFluxDensityUnit.TESLA);
781     }
782 
783     /**
784      * Sets initial hard-iron used to find a solution.
785      *
786      * @param initialHardIron initial hard-iron to be set.
787      * @throws LockedException if calibrator is currently running.
788      */
789     @Override
790     public void setInitialHardIron(final MagneticFluxDensityTriad initialHardIron) throws LockedException {
791         if (running) {
792             throw new LockedException();
793         }
794 
795         initialHardIronX = convertMagneticFluxDensity(initialHardIron.getValueX(), initialHardIron.getUnit());
796         initialHardIronY = convertMagneticFluxDensity(initialHardIron.getValueY(), initialHardIron.getUnit());
797         initialHardIronZ = convertMagneticFluxDensity(initialHardIron.getValueZ(), initialHardIron.getUnit());
798     }
799 
800     /**
801      * Gets initial x scaling factor.
802      *
803      * @return initial x scaling factor.
804      */
805     @Override
806     public double getInitialSx() {
807         return initialSx;
808     }
809 
810     /**
811      * Sets initial x scaling factor.
812      *
813      * @param initialSx initial x scaling factor.
814      * @throws LockedException if calibrator is currently running.
815      */
816     @Override
817     public void setInitialSx(final double initialSx) throws LockedException {
818         if (running) {
819             throw new LockedException();
820         }
821         this.initialSx = initialSx;
822     }
823 
824     /**
825      * Gets initial y scaling factor.
826      *
827      * @return initial y scaling factor.
828      */
829     @Override
830     public double getInitialSy() {
831         return initialSy;
832     }
833 
834     /**
835      * Sets initial y scaling factor.
836      *
837      * @param initialSy initial y scaling factor.
838      * @throws LockedException if calibrator is currently running.
839      */
840     @Override
841     public void setInitialSy(final double initialSy) throws LockedException {
842         if (running) {
843             throw new LockedException();
844         }
845         this.initialSy = initialSy;
846     }
847 
848     /**
849      * Gets initial z scaling factor.
850      *
851      * @return initial z scaling factor.
852      */
853     @Override
854     public double getInitialSz() {
855         return initialSz;
856     }
857 
858     /**
859      * Sets initial z scaling factor.
860      *
861      * @param initialSz initial z scaling factor.
862      * @throws LockedException if calibrator is currently running.
863      */
864     @Override
865     public void setInitialSz(final double initialSz) throws LockedException {
866         if (running) {
867             throw new LockedException();
868         }
869         this.initialSz = initialSz;
870     }
871 
872     /**
873      * Gets initial x-y cross coupling error.
874      *
875      * @return initial x-y cross coupling error.
876      */
877     @Override
878     public double getInitialMxy() {
879         return initialMxy;
880     }
881 
882     /**
883      * Sets initial x-y cross coupling error.
884      *
885      * @param initialMxy initial x-y cross coupling error.
886      * @throws LockedException if calibrator is currently running.
887      */
888     @Override
889     public void setInitialMxy(final double initialMxy) throws LockedException {
890         if (running) {
891             throw new LockedException();
892         }
893         this.initialMxy = initialMxy;
894     }
895 
896     /**
897      * Gets initial x-z cross coupling error.
898      *
899      * @return initial x-z cross coupling error.
900      */
901     @Override
902     public double getInitialMxz() {
903         return initialMxz;
904     }
905 
906     /**
907      * Sets initial x-z cross coupling error.
908      *
909      * @param initialMxz initial x-z cross coupling error.
910      * @throws LockedException if calibrator is currently running.
911      */
912     @Override
913     public void setInitialMxz(final double initialMxz) throws LockedException {
914         if (running) {
915             throw new LockedException();
916         }
917         this.initialMxz = initialMxz;
918     }
919 
920     /**
921      * Gets initial y-x cross coupling error.
922      *
923      * @return initial y-x cross coupling error.
924      */
925     @Override
926     public double getInitialMyx() {
927         return initialMyx;
928     }
929 
930     /**
931      * Sets initial y-x cross coupling error.
932      *
933      * @param initialMyx initial y-x cross coupling error.
934      * @throws LockedException if calibrator is currently running.
935      */
936     @Override
937     public void setInitialMyx(final double initialMyx) throws LockedException {
938         if (running) {
939             throw new LockedException();
940         }
941         this.initialMyx = initialMyx;
942     }
943 
944     /**
945      * Gets initial y-z cross coupling error.
946      *
947      * @return initial y-z cross coupling error.
948      */
949     @Override
950     public double getInitialMyz() {
951         return initialMyz;
952     }
953 
954     /**
955      * Sets initial y-z cross coupling error.
956      *
957      * @param initialMyz initial y-z cross coupling error.
958      * @throws LockedException if calibrator is currently running.
959      */
960     @Override
961     public void setInitialMyz(final double initialMyz) throws LockedException {
962         if (running) {
963             throw new LockedException();
964         }
965         this.initialMyz = initialMyz;
966     }
967 
968     /**
969      * Gets initial z-x cross coupling error.
970      *
971      * @return initial z-x cross coupling error.
972      */
973     @Override
974     public double getInitialMzx() {
975         return initialMzx;
976     }
977 
978     /**
979      * Sets initial z-x cross coupling error.
980      *
981      * @param initialMzx initial z-x cross coupling error.
982      * @throws LockedException if calibrator is currently running.
983      */
984     @Override
985     public void setInitialMzx(final double initialMzx) throws LockedException {
986         if (running) {
987             throw new LockedException();
988         }
989         this.initialMzx = initialMzx;
990     }
991 
992     /**
993      * Gets initial z-y cross coupling error.
994      *
995      * @return initial z-y cross coupling error.
996      */
997     @Override
998     public double getInitialMzy() {
999         return initialMzy;
1000     }
1001 
1002     /**
1003      * Sets initial z-y cross coupling error.
1004      *
1005      * @param initialMzy initial z-y cross coupling error.
1006      * @throws LockedException if calibrator is currently running.
1007      */
1008     @Override
1009     public void setInitialMzy(final double initialMzy) throws LockedException {
1010         if (running) {
1011             throw new LockedException();
1012         }
1013         this.initialMzy = initialMzy;
1014     }
1015 
1016     /**
1017      * Sets initial scaling factors.
1018      *
1019      * @param initialSx initial x scaling factor.
1020      * @param initialSy initial y scaling factor.
1021      * @param initialSz initial z scaling factor.
1022      * @throws LockedException if calibrator is currently running.
1023      */
1024     @Override
1025     public void setInitialScalingFactors(
1026             final double initialSx, final double initialSy, final double initialSz) throws LockedException {
1027         if (running) {
1028             throw new LockedException();
1029         }
1030         this.initialSx = initialSx;
1031         this.initialSy = initialSy;
1032         this.initialSz = initialSz;
1033     }
1034 
1035     /**
1036      * Sets initial cross coupling errors.
1037      *
1038      * @param initialMxy initial x-y cross coupling error.
1039      * @param initialMxz initial x-z cross coupling error.
1040      * @param initialMyx initial y-x cross coupling error.
1041      * @param initialMyz initial y-z cross coupling error.
1042      * @param initialMzx initial z-x cross coupling error.
1043      * @param initialMzy initial z-y cross coupling error.
1044      * @throws LockedException if calibrator is currently running.
1045      */
1046     @Override
1047     public void setInitialCrossCouplingErrors(
1048             final double initialMxy, final double initialMxz, final double initialMyx,
1049             final double initialMyz, final double initialMzx, final double initialMzy) throws LockedException {
1050         if (running) {
1051             throw new LockedException();
1052         }
1053         this.initialMxy = initialMxy;
1054         this.initialMxz = initialMxz;
1055         this.initialMyx = initialMyx;
1056         this.initialMyz = initialMyz;
1057         this.initialMzx = initialMzx;
1058         this.initialMzy = initialMzy;
1059     }
1060 
1061     /**
1062      * Sets initial scaling factors and cross coupling errors.
1063      *
1064      * @param initialSx  initial x scaling factor.
1065      * @param initialSy  initial y scaling factor.
1066      * @param initialSz  initial z scaling factor.
1067      * @param initialMxy initial x-y cross coupling error.
1068      * @param initialMxz initial x-z cross coupling error.
1069      * @param initialMyx initial y-x cross coupling error.
1070      * @param initialMyz initial y-z cross coupling error.
1071      * @param initialMzx initial z-x cross coupling error.
1072      * @param initialMzy initial z-y cross coupling error.
1073      * @throws LockedException if calibrator is currently running.
1074      */
1075     @Override
1076     public void setInitialScalingFactorsAndCrossCouplingErrors(
1077             final double initialSx, final double initialSy, final double initialSz,
1078             final double initialMxy, final double initialMxz, final double initialMyx,
1079             final double initialMyz, final double initialMzx, final double initialMzy) throws LockedException {
1080         if (running) {
1081             throw new LockedException();
1082         }
1083         setInitialScalingFactors(initialSx, initialSy, initialSz);
1084         setInitialCrossCouplingErrors(initialMxy, initialMxz, initialMyx, initialMyz, initialMzx, initialMzy);
1085     }
1086 
1087     /**
1088      * Gets initial hard-iron bias to be used to find a solution as an array.
1089      * Array values are expressed in Teslas (T).
1090      *
1091      * @return array containing coordinates of initial bias.
1092      */
1093     @Override
1094     public double[] getInitialHardIron() {
1095         final var result = new double[BodyMagneticFluxDensity.COMPONENTS];
1096         getInitialHardIron(result);
1097         return result;
1098     }
1099 
1100     /**
1101      * Gets initial hard-iron bias to be used to find a solution as an array.
1102      * Array values are expressed in Teslas (T).
1103      *
1104      * @param result instance where result data will be copied to.
1105      * @throws IllegalArgumentException if provided array does not have
1106      *                                  length 3.
1107      */
1108     @Override
1109     public void getInitialHardIron(final double[] result) {
1110         if (result.length != BodyMagneticFluxDensity.COMPONENTS) {
1111             throw new IllegalArgumentException();
1112         }
1113         result[0] = initialHardIronX;
1114         result[1] = initialHardIronY;
1115         result[2] = initialHardIronZ;
1116     }
1117 
1118     /**
1119      * Sets initial hard-iron bias to be used to find a solution as an array.
1120      * Array values are expressed in Teslas (T).
1121      *
1122      * @param initialHardIron initial hard-iron to find a solution.
1123      * @throws LockedException          if calibrator is currently running.
1124      * @throws IllegalArgumentException if provided array does not have length 3.
1125      */
1126     @Override
1127     public void setInitialHardIron(final double[] initialHardIron) throws LockedException {
1128         if (running) {
1129             throw new LockedException();
1130         }
1131 
1132         if (initialHardIron.length != BodyMagneticFluxDensity.COMPONENTS) {
1133             throw new IllegalArgumentException();
1134         }
1135         initialHardIronX = initialHardIron[0];
1136         initialHardIronY = initialHardIron[1];
1137         initialHardIronZ = initialHardIron[2];
1138     }
1139 
1140     /**
1141      * Gets initial hard-iron bias to be used to find a solution as a
1142      * column matrix.
1143      * Values are expressed in Teslas (T).
1144      *
1145      * @return initial hard-iron bias to be used to find a solution as a
1146      * column matrix.
1147      */
1148     @Override
1149     public Matrix getInitialHardIronAsMatrix() {
1150         Matrix result;
1151         try {
1152             result = new Matrix(BodyMagneticFluxDensity.COMPONENTS, 1);
1153             getInitialHardIronAsMatrix(result);
1154         } catch (final WrongSizeException ignore) {
1155             // never happens
1156             result = null;
1157         }
1158         return result;
1159     }
1160 
1161     /**
1162      * Gets initial hard-iron bias to be used to find a solution as a
1163      * column matrix.
1164      * Values are expressed in Teslas (T).
1165      *
1166      * @param result instance where result data will be copied to.
1167      * @throws IllegalArgumentException if provided matrix is not 3x1.
1168      */
1169     @Override
1170     public void getInitialHardIronAsMatrix(final Matrix result) {
1171         if (result.getRows() != BodyMagneticFluxDensity.COMPONENTS || result.getColumns() != 1) {
1172             throw new IllegalArgumentException();
1173         }
1174         result.setElementAtIndex(0, initialHardIronX);
1175         result.setElementAtIndex(1, initialHardIronY);
1176         result.setElementAtIndex(2, initialHardIronZ);
1177     }
1178 
1179     /**
1180      * Sets initial hard-iron bias to be used to find a solution as a column
1181      * matrix with values expressed in Teslas (T).
1182      *
1183      * @param initialHardIron initial hard-iron bias to find a solution.
1184      * @throws LockedException          if calibrator is currently running.
1185      * @throws IllegalArgumentException if provided matrix is not 3x1.
1186      */
1187     @Override
1188     public void setInitialHardIron(final Matrix initialHardIron) throws LockedException {
1189         if (running) {
1190             throw new LockedException();
1191         }
1192         if (initialHardIron.getRows() != BodyMagneticFluxDensity.COMPONENTS || initialHardIron.getColumns() != 1) {
1193             throw new IllegalArgumentException();
1194         }
1195 
1196         initialHardIronX = initialHardIron.getElementAtIndex(0);
1197         initialHardIronY = initialHardIron.getElementAtIndex(1);
1198         initialHardIronZ = initialHardIron.getElementAtIndex(2);
1199     }
1200 
1201     /**
1202      * Gets initial scale factors and cross coupling errors matrix.
1203      *
1204      * @return initial scale factors and cross coupling errors matrix.
1205      */
1206     @Override
1207     public Matrix getInitialMm() {
1208         Matrix result;
1209         try {
1210             result = new Matrix(BodyMagneticFluxDensity.COMPONENTS, BodyMagneticFluxDensity.COMPONENTS);
1211             getInitialMm(result);
1212         } catch (final WrongSizeException ignore) {
1213             // never happens
1214             result = null;
1215         }
1216         return result;
1217     }
1218 
1219     /**
1220      * Gets initial scale factors and cross coupling errors matrix.
1221      *
1222      * @param result instance where data will be stored.
1223      * @throws IllegalArgumentException if provided matrix is not 3x3.
1224      */
1225     @Override
1226     public void getInitialMm(final Matrix result) {
1227         if (result.getRows() != BodyKinematics.COMPONENTS || result.getColumns() != BodyKinematics.COMPONENTS) {
1228             throw new IllegalArgumentException();
1229         }
1230         result.setElementAtIndex(0, initialSx);
1231         result.setElementAtIndex(1, initialMyx);
1232         result.setElementAtIndex(2, initialMzx);
1233 
1234         result.setElementAtIndex(3, initialMxy);
1235         result.setElementAtIndex(4, initialSy);
1236         result.setElementAtIndex(5, initialMzy);
1237 
1238         result.setElementAtIndex(6, initialMxz);
1239         result.setElementAtIndex(7, initialMyz);
1240         result.setElementAtIndex(8, initialSz);
1241     }
1242 
1243     /**
1244      * Sets initial scale factors and cross coupling errors matrix.
1245      *
1246      * @param initialMm initial scale factors and cross coupling errors matrix.
1247      * @throws IllegalArgumentException if provided matrix is not 3x3.
1248      * @throws LockedException          if calibrator is currently running.
1249      */
1250     @Override
1251     public void setInitialMm(final Matrix initialMm) throws LockedException {
1252         if (running) {
1253             throw new LockedException();
1254         }
1255         if (initialMm.getRows() != BodyKinematics.COMPONENTS || initialMm.getColumns() != BodyKinematics.COMPONENTS) {
1256             throw new IllegalArgumentException();
1257         }
1258 
1259         initialSx = initialMm.getElementAtIndex(0);
1260         initialMyx = initialMm.getElementAtIndex(1);
1261         initialMzx = initialMm.getElementAtIndex(2);
1262 
1263         initialMxy = initialMm.getElementAtIndex(3);
1264         initialSy = initialMm.getElementAtIndex(4);
1265         initialMzy = initialMm.getElementAtIndex(5);
1266 
1267         initialMxz = initialMm.getElementAtIndex(6);
1268         initialMyz = initialMm.getElementAtIndex(7);
1269         initialSz = initialMm.getElementAtIndex(8);
1270     }
1271 
1272     /**
1273      * Gets a list of body magnetic flux density measurements taken at different
1274      * frames (positions, orientations and velocities).
1275      * If a single device IMU needs to be calibrated, typically all measurements are
1276      * taken at the same position, with zero velocity and multiple orientations.
1277      * However, if we just want to calibrate a given IMU model (e.g. obtain
1278      * an average and less precise calibration for the IMU of a given phone model),
1279      * we could take measurements collected throughout the planet at multiple positions
1280      * while the phone remains static (e.g. while charging), hence each measurement
1281      * position will change, velocity will remain zero and orientation will be
1282      * typically constant at horizontal orientation while the phone remains on a
1283      * flat surface.
1284      *
1285      * @return a collection of body magnetic flux density measurements taken at different
1286      * frames (positions, orientations and velocities).
1287      */
1288     @Override
1289     public List<StandardDeviationFrameBodyMagneticFluxDensity> getMeasurements() {
1290         return measurements;
1291     }
1292 
1293     /**
1294      * Sets a list of body magnetic flux density measurements taken at different
1295      * frames (positions, orientations and velocities).
1296      * If a single device IMU needs to be calibrated, typically all measurements are
1297      * taken at the same position, with zero velocity and multiple orientations.
1298      * However, if we just want to calibrate the a given IMU model (e.g. obtain
1299      * an average and less precise calibration for the IMU of a given phone model),
1300      * we could take measurements collected throughout the planet at multiple positions
1301      * while the phone remains static (e.g. while charging), hence each measurement
1302      * position will change, velocity will remain zero and orientation will be
1303      * typically constant at horizontal orientation while the phone remains on a
1304      * flat surface.
1305      *
1306      * @param measurements collection of body magnetic flux density measurements
1307      *                     taken at different frames (positions, orientations
1308      *                     and velocities).
1309      * @throws LockedException if estimator is currently running.
1310      */
1311     @Override
1312     public void setMeasurements(
1313             final List<StandardDeviationFrameBodyMagneticFluxDensity> measurements) throws LockedException {
1314         if (running) {
1315             throw new LockedException();
1316         }
1317         this.measurements = measurements;
1318     }
1319 
1320     /**
1321      * Indicates the type of measurement used by this calibrator.
1322      *
1323      * @return type of measurement used by this calibrator.
1324      */
1325     @Override
1326     public MagnetometerCalibratorMeasurementType getMeasurementType() {
1327         return MagnetometerCalibratorMeasurementType.STANDARD_DEVIATION_FRAME_BODY_MAGNETIC_FLUX_DENSITY;
1328     }
1329 
1330     /**
1331      * Indicates whether this calibrator requires ordered measurements in a
1332      * list or not.
1333      *
1334      * @return true if measurements must be ordered, false otherwise.
1335      */
1336     @Override
1337     public boolean isOrderedMeasurementsRequired() {
1338         return true;
1339     }
1340 
1341     /**
1342      * Indicates whether z-axis is assumed to be common for accelerometer,
1343      * gyroscope and magnetometer.
1344      * When enabled, this eliminates 3 variables from Mm (soft-iron) matrix.
1345      *
1346      * @return true if z-axis is assumed to be common for accelerometer,
1347      * gyroscope and magnetometer, false otherwise.
1348      */
1349     @Override
1350     public boolean isCommonAxisUsed() {
1351         return commonAxisUsed;
1352     }
1353 
1354     /**
1355      * Specifies whether z-axis is assumed to be common for accelerometer and
1356      * gyroscope.
1357      * When enabled, this eliminates 3 variables from Mm matrix.
1358      *
1359      * @param commonAxisUsed true if z-axis is assumed to be common for
1360      *                       accelerometer, gyroscope and magnetometer, false
1361      *                       otherwise.
1362      * @throws LockedException if estimator is currently running.
1363      */
1364     @Override
1365     public void setCommonAxisUsed(final boolean commonAxisUsed) throws LockedException {
1366         if (running) {
1367             throw new LockedException();
1368         }
1369 
1370         this.commonAxisUsed = commonAxisUsed;
1371     }
1372 
1373     /**
1374      * Gets listener to handle events raised by this calibrator.
1375      *
1376      * @return listener to handle events raised by this calibrator.
1377      */
1378     public RobustKnownFrameMagnetometerCalibratorListener getListener() {
1379         return listener;
1380     }
1381 
1382     /**
1383      * Sets listener to handle events raised by this calibrator.
1384      *
1385      * @param listener listener to handle events raised by this calibrator.
1386      * @throws LockedException if calibrator is currently running.
1387      */
1388     public void setListener(
1389             final RobustKnownFrameMagnetometerCalibratorListener listener) throws LockedException {
1390         if (running) {
1391             throw new LockedException();
1392         }
1393 
1394         this.listener = listener;
1395     }
1396 
1397     /**
1398      * Gets minimum number of required measurements.
1399      *
1400      * @return minimum number of required measurements.
1401      */
1402     @Override
1403     public int getMinimumRequiredMeasurements() {
1404         return MINIMUM_MEASUREMENTS;
1405     }
1406 
1407     /**
1408      * Indicates whether calibrator is ready to start the estimator.
1409      *
1410      * @return true if calibrator is ready, false otherwise.
1411      */
1412     @Override
1413     public boolean isReady() {
1414         return measurements != null && measurements.size() >= MINIMUM_MEASUREMENTS;
1415     }
1416 
1417     /**
1418      * Indicates whether calibrator is currently running or no.
1419      *
1420      * @return true if calibrator is running, false otherwise.
1421      */
1422     @Override
1423     public boolean isRunning() {
1424         return running;
1425     }
1426 
1427     /**
1428      * Gets Earth's magnetic model.
1429      *
1430      * @return Earth's magnetic model or null if not provided.
1431      */
1432     public WorldMagneticModel getMagneticModel() {
1433         return magneticModel;
1434     }
1435 
1436     /**
1437      * Sets Earth's magnetic model.
1438      *
1439      * @param magneticModel Earth's magnetic model to be set.
1440      * @throws LockedException if calibrator is currently running.
1441      */
1442     public void setMagneticModel(final WorldMagneticModel magneticModel) throws LockedException {
1443         if (running) {
1444             throw new LockedException();
1445         }
1446         this.magneticModel = magneticModel;
1447     }
1448 
1449     /**
1450      * Indicates whether a linear calibrator is used or not for preliminary
1451      * solutions.
1452      *
1453      * @return indicates whether a linear calibrator is used or not for
1454      * preliminary solutions.
1455      */
1456     public boolean isLinearCalibratorUsed() {
1457         return useLinearCalibrator;
1458     }
1459 
1460     /**
1461      * Specifies whether a linear calibrator is used or not for preliminary
1462      * solutions.
1463      *
1464      * @param linearCalibratorUsed indicates whether a linear calibrator is used
1465      *                             or not for preliminary solutions.
1466      * @throws LockedException if calibrator is currently running.
1467      */
1468     public void setLinearCalibratorUsed(final boolean linearCalibratorUsed) throws LockedException {
1469         if (running) {
1470             throw new LockedException();
1471         }
1472         useLinearCalibrator = linearCalibratorUsed;
1473     }
1474 
1475     /**
1476      * Indicates whether preliminary solutions must be refined after an initial linear solution is found.
1477      * If no initial solution is found using a linear solver, a non linear solver will be
1478      * used regardless of this value using an average solution as the initial value to be
1479      * refined.
1480      *
1481      * @return true if preliminary solutions must be refined after an initial linear solution, false
1482      * otherwise.
1483      */
1484     public boolean isPreliminarySolutionRefined() {
1485         return refinePreliminarySolutions;
1486     }
1487 
1488     /**
1489      * Specifies whether preliminary solutions must be refined after an initial linear solution is found.
1490      * If no initial solution is found using a linear solver, a non linear solver will be
1491      * used regardless of this value using an average solution as the initial value to be
1492      * refined.
1493      *
1494      * @param preliminarySolutionRefined true if preliminary solutions must be refined after an
1495      *                                   initial linear solution, false otherwise.
1496      * @throws LockedException if calibrator is currently running.
1497      */
1498     public void setPreliminarySolutionRefined(final boolean preliminarySolutionRefined) throws LockedException {
1499         if (running) {
1500             throw new LockedException();
1501         }
1502 
1503         refinePreliminarySolutions = preliminarySolutionRefined;
1504     }
1505 
1506     /**
1507      * Returns amount of progress variation before notifying a progress change during
1508      * calibration.
1509      *
1510      * @return amount of progress variation before notifying a progress change during
1511      * calibration.
1512      */
1513     public float getProgressDelta() {
1514         return progressDelta;
1515     }
1516 
1517     /**
1518      * Sets amount of progress variation before notifying a progress change during
1519      * calibration.
1520      *
1521      * @param progressDelta amount of progress variation before notifying a progress
1522      *                      change during calibration.
1523      * @throws IllegalArgumentException if progress delta is less than zero or greater than 1.
1524      * @throws LockedException          if calibrator is currently running.
1525      */
1526     public void setProgressDelta(final float progressDelta) throws LockedException {
1527         if (running) {
1528             throw new LockedException();
1529         }
1530         if (progressDelta < MIN_PROGRESS_DELTA || progressDelta > MAX_PROGRESS_DELTA) {
1531             throw new IllegalArgumentException();
1532         }
1533         this.progressDelta = progressDelta;
1534     }
1535 
1536     /**
1537      * Returns amount of confidence expressed as a value between 0.0 and 1.0
1538      * (which is equivalent to 100%). The amount of confidence indicates the probability
1539      * that the estimated result is correct. Usually this value will be close to 1.0, but
1540      * not exactly 1.0.
1541      *
1542      * @return amount of confidence as a value between 0.0 and 1.0.
1543      */
1544     public double getConfidence() {
1545         return confidence;
1546     }
1547 
1548     /**
1549      * Sets amount of confidence expressed as a value between 0.0 and 1.0 (which is
1550      * equivalent to 100%). The amount of confidence indicates the probability that
1551      * the estimated result is correct. Usually this value will be close to 1.0, but
1552      * not exactly 1.0.
1553      *
1554      * @param confidence confidence to be set as a value between 0.0 and 1.0.
1555      * @throws IllegalArgumentException if provided value is not between 0.0 and 1.0.
1556      * @throws LockedException          if calibrator is currently running.
1557      */
1558     public void setConfidence(final double confidence) throws LockedException {
1559         if (running) {
1560             throw new LockedException();
1561         }
1562         if (confidence < MIN_CONFIDENCE || confidence > MAX_CONFIDENCE) {
1563             throw new IllegalArgumentException();
1564         }
1565         this.confidence = confidence;
1566     }
1567 
1568     /**
1569      * Returns maximum allowed number of iterations. If maximum allowed number of
1570      * iterations is achieved without converging to a result when calling calibrate(),
1571      * a RobustEstimatorException will be raised.
1572      *
1573      * @return maximum allowed number of iterations.
1574      */
1575     public int getMaxIterations() {
1576         return maxIterations;
1577     }
1578 
1579     /**
1580      * Sets maximum allowed number of iterations. When the maximum number of iterations
1581      * is exceeded, result will not be available, however an approximate result will be
1582      * available for retrieval.
1583      *
1584      * @param maxIterations maximum allowed number of iterations to be set.
1585      * @throws IllegalArgumentException if provided value is less than 1.
1586      * @throws LockedException          if calibrator is currently running.
1587      */
1588     public void setMaxIterations(final int maxIterations) throws LockedException {
1589         if (running) {
1590             throw new LockedException();
1591         }
1592         if (maxIterations < MIN_ITERATIONS) {
1593             throw new IllegalArgumentException();
1594         }
1595         this.maxIterations = maxIterations;
1596     }
1597 
1598     /**
1599      * Gets data related to inliers found after estimation.
1600      *
1601      * @return data related to inliers found after estimation.
1602      */
1603     public InliersData getInliersData() {
1604         return inliersData;
1605     }
1606 
1607     /**
1608      * Indicates whether result must be refined using a non-linear solver over found inliers.
1609      *
1610      * @return true to refine result, false to simply use result found by robust estimator
1611      * without further refining.
1612      */
1613     public boolean isResultRefined() {
1614         return refineResult;
1615     }
1616 
1617     /**
1618      * Specifies whether result must be refined using a non-linear solver over found inliers.
1619      *
1620      * @param refineResult true to refine result, false to simply use result found by robust
1621      *                     estimator without further refining.
1622      * @throws LockedException if calibrator is currently running.
1623      */
1624     public void setResultRefined(final boolean refineResult) throws LockedException {
1625         if (running) {
1626             throw new LockedException();
1627         }
1628         this.refineResult = refineResult;
1629     }
1630 
1631     /**
1632      * Indicates whether covariance must be kept after refining result.
1633      * This setting is only taken into account if result is refined.
1634      *
1635      * @return true if covariance must be kept after refining result, false otherwise.
1636      */
1637     public boolean isCovarianceKept() {
1638         return keepCovariance;
1639     }
1640 
1641     /**
1642      * Specifies whether covariance must be kept after refining result.
1643      * This setting is only taken into account if result is refined.
1644      *
1645      * @param keepCovariance true if covariance must be kept after refining result,
1646      *                       false otherwise.
1647      * @throws LockedException if calibrator is currently running.
1648      */
1649     public void setCovarianceKept(final boolean keepCovariance) throws LockedException {
1650         if (running) {
1651             throw new LockedException();
1652         }
1653         this.keepCovariance = keepCovariance;
1654     }
1655 
1656     /**
1657      * Returns quality scores corresponding to each measurement.
1658      * The larger the score value the better the quality of the sample.
1659      * This implementation always returns null.
1660      * Subclasses using quality scores must implement proper behavior.
1661      *
1662      * @return quality scores corresponding to each sample.
1663      */
1664     @Override
1665     public double[] getQualityScores() {
1666         return null;
1667     }
1668 
1669     /**
1670      * Sets quality scores corresponding to each measurement.
1671      * The larger the score value the better the quality of the sample.
1672      * This implementation makes no action.
1673      * Subclasses using quality scores must implement proper behaviour.
1674      *
1675      * @param qualityScores quality scores corresponding to each pair of
1676      *                      matched points.
1677      * @throws IllegalArgumentException if provided quality scores length
1678      *                                  is smaller than minimum required samples.
1679      * @throws LockedException          if calibrator is currently running.
1680      */
1681     @Override
1682     public void setQualityScores(final double[] qualityScores) throws LockedException {
1683     }
1684 
1685     /**
1686      * Gets array containing x,y,z components of estimated magnetometer
1687      * hard-iron biases expressed in Teslas (T).
1688      *
1689      * @return array containing x,y,z components of estimated magnetometer
1690      * hard-iron biases.
1691      */
1692     @Override
1693     public double[] getEstimatedHardIron() {
1694         return estimatedHardIron;
1695     }
1696 
1697     /**
1698      * Gets array containing x,y,z components of estimated magnetometer
1699      * hard-iron biases expressed in Teslas (T).
1700      *
1701      * @param result instance where estimated magnetometer biases will be
1702      *               stored.
1703      * @return true if result instance was updated, false otherwise (when
1704      * estimation is not yet available).
1705      */
1706     @Override
1707     public boolean getEstimatedHardIron(final double[] result) {
1708         if (estimatedHardIron != null) {
1709             System.arraycopy(estimatedHardIron, 0, result, 0, estimatedHardIron.length);
1710             return true;
1711         } else {
1712             return false;
1713         }
1714     }
1715 
1716     /**
1717      * Gets column matrix containing x,y,z components of estimated
1718      * magnetometer hard-iron biases expressed in Teslas (T).
1719      *
1720      * @return column matrix containing x,y,z components of estimated
1721      * magnetometer hard-iron biases.
1722      */
1723     @Override
1724     public Matrix getEstimatedHardIronAsMatrix() {
1725         return estimatedHardIron != null ? Matrix.newFromArray(estimatedHardIron) : null;
1726     }
1727 
1728     /**
1729      * Gets column matrix containing x,y,z components of estimated
1730      * magnetometer hard-iron biases expressed in Teslas (T).
1731      *
1732      * @param result instance where result data will be stored.
1733      * @return true if result was updated, false otherwise.
1734      * @throws WrongSizeException if provided result instance has invalid size.
1735      */
1736     @Override
1737     public boolean getEstimatedHardIronAsMatrix(final Matrix result) throws WrongSizeException {
1738         if (estimatedHardIron != null) {
1739             result.fromArray(estimatedHardIron);
1740             return true;
1741         } else {
1742             return false;
1743         }
1744     }
1745 
1746     /**
1747      * Gets x coordinate of estimated magnetometer bias expressed in
1748      * Teslas (T).
1749      *
1750      * @return x coordinate of estimated magnetometer bias or null if not
1751      * available.
1752      */
1753     @Override
1754     public Double getEstimatedHardIronX() {
1755         return estimatedHardIron != null ? estimatedHardIron[0] : null;
1756     }
1757 
1758     /**
1759      * Gets y coordinate of estimated magnetometer bias expressed in
1760      * Teslas (T).
1761      *
1762      * @return y coordinate of estimated magnetometer bias or null if not
1763      * available.
1764      */
1765     @Override
1766     public Double getEstimatedHardIronY() {
1767         return estimatedHardIron != null ? estimatedHardIron[1] : null;
1768     }
1769 
1770     /**
1771      * Gets z coordinate of estimated magnetometer bias expressed in
1772      * Teslas (T).
1773      *
1774      * @return z coordinate of estimated magnetometer bias or null if not
1775      * available.
1776      */
1777     @Override
1778     public Double getEstimatedHardIronZ() {
1779         return estimatedHardIron != null ? estimatedHardIron[2] : null;
1780     }
1781 
1782     /**
1783      * Gets x coordinate of estimated magnetometer bias.
1784      *
1785      * @return x coordinate of estimated magnetometer bias.
1786      */
1787     @Override
1788     public MagneticFluxDensity getEstimatedHardIronXAsMagneticFluxDensity() {
1789         return estimatedHardIron != null
1790                 ? new MagneticFluxDensity(estimatedHardIron[0], MagneticFluxDensityUnit.TESLA) : null;
1791     }
1792 
1793     /**
1794      * Gets x coordinate of estimated magnetometer bias.
1795      *
1796      * @param result instance where result will be stored.
1797      * @return true if estimated magnetometer bias is available, false otherwise.
1798      */
1799     @Override
1800     public boolean getEstimatedHardIronXAsMagneticFluxDensity(final MagneticFluxDensity result) {
1801         if (estimatedHardIron != null) {
1802             result.setValue(estimatedHardIron[0]);
1803             result.setUnit(MagneticFluxDensityUnit.TESLA);
1804             return true;
1805         } else {
1806             return false;
1807         }
1808     }
1809 
1810     /**
1811      * Gets y coordinate of estimated magnetometer bias.
1812      *
1813      * @return y coordinate of estimated magnetometer bias.
1814      */
1815     @Override
1816     public MagneticFluxDensity getEstimatedHardIronYAsMagneticFluxDensity() {
1817         return estimatedHardIron != null
1818                 ? new MagneticFluxDensity(estimatedHardIron[1], MagneticFluxDensityUnit.TESLA) : null;
1819     }
1820 
1821     /**
1822      * Gets y coordinate of estimated magnetometer bias.
1823      *
1824      * @param result instance where result will be stored.
1825      * @return true if estimated magnetometer bias is available, false otherwise.
1826      */
1827     @Override
1828     public boolean getEstimatedHardIronYAsMagneticFluxDensity(final MagneticFluxDensity result) {
1829         if (estimatedHardIron != null) {
1830             result.setValue(estimatedHardIron[1]);
1831             result.setUnit(MagneticFluxDensityUnit.TESLA);
1832             return true;
1833         } else {
1834             return false;
1835         }
1836     }
1837 
1838     /**
1839      * Gets z coordinate of estimated magnetometer bias.
1840      *
1841      * @return z coordinate of estimated magnetometer bias.
1842      */
1843     @Override
1844     public MagneticFluxDensity getEstimatedHardIronZAsMagneticFluxDensity() {
1845         return estimatedHardIron != null
1846                 ? new MagneticFluxDensity(estimatedHardIron[2], MagneticFluxDensityUnit.TESLA) : null;
1847     }
1848 
1849     /**
1850      * Gets z coordinate of estimated magnetometer bias.
1851      *
1852      * @param result instance where result will be stored.
1853      * @return true if estimated magnetometer bias is available, false otherwise.
1854      */
1855     @Override
1856     public boolean getEstimatedHardIronZAsMagneticFluxDensity(final MagneticFluxDensity result) {
1857         if (estimatedHardIron != null) {
1858             result.setValue(estimatedHardIron[2]);
1859             result.setUnit(MagneticFluxDensityUnit.TESLA);
1860             return true;
1861         } else {
1862             return false;
1863         }
1864     }
1865 
1866     /**
1867      * Gets estimated magnetometer bias.
1868      *
1869      * @return estimated magnetometer bias or null if not available.
1870      */
1871     @Override
1872     public MagneticFluxDensityTriad getEstimatedHardIronAsTriad() {
1873         return estimatedHardIron != null
1874                 ? new MagneticFluxDensityTriad(MagneticFluxDensityUnit.TESLA,
1875                 estimatedHardIron[0], estimatedHardIron[1], estimatedHardIron[2]) : null;
1876     }
1877 
1878     /**
1879      * Gets estimated magnetometer bias.
1880      *
1881      * @param result instance where result will be stored.
1882      * @return true if estimated magnetometer bias is available and result was
1883      * modified, false otherwise.
1884      */
1885     @Override
1886     public boolean getEstimatedHardIronAsTriad(final MagneticFluxDensityTriad result) {
1887         if (estimatedHardIron != null) {
1888             result.setValueCoordinatesAndUnit(
1889                     estimatedHardIron[0], estimatedHardIron[1], estimatedHardIron[2], MagneticFluxDensityUnit.TESLA);
1890             return true;
1891         } else {
1892             return false;
1893         }
1894     }
1895 
1896     /**
1897      * Gets estimated magnetometer soft-iron matrix containing scale factors
1898      * and cross coupling errors.
1899      * This is the product of matrix Tm containing cross coupling errors and Km
1900      * containing scaling factors.
1901      * So tat:
1902      * <pre>
1903      *     Mm = [sx    mxy  mxz] = Tm*Km
1904      *          [myx   sy   myz]
1905      *          [mzx   mzy  sz ]
1906      * </pre>
1907      * Where:
1908      * <pre>
1909      *     Km = [sx 0   0 ]
1910      *          [0  sy  0 ]
1911      *          [0  0   sz]
1912      * </pre>
1913      * and
1914      * <pre>
1915      *     Tm = [1          -alphaXy    alphaXz ]
1916      *          [alphaYx    1           -alphaYz]
1917      *          [-alphaZx   alphaZy     1       ]
1918      * </pre>
1919      * Hence:
1920      * <pre>
1921      *     Mm = [sx    mxy  mxz] = Tm*Km =  [sx             -sy * alphaXy   sz * alphaXz ]
1922      *          [myx   sy   myz]            [sx * alphaYx   sy              -sz * alphaYz]
1923      *          [mzx   mzy  sz ]            [-sx * alphaZx  sy * alphaZy    sz           ]
1924      * </pre>
1925      * This instance allows any 3x3 matrix however, typically alphaYx, alphaZx and alphaZy
1926      * are considered to be zero if the accelerometer z-axis is assumed to be the same
1927      * as the body z-axis. When this is assumed, myx = mzx = mzy = 0 and the Mm matrix
1928      * becomes upper diagonal:
1929      * <pre>
1930      *     Mm = [sx    mxy  mxz]
1931      *          [0     sy   myz]
1932      *          [0     0    sz ]
1933      * </pre>
1934      * Values of this matrix are unit-less.
1935      *
1936      * @return estimated magnetometer soft-iron scale factors and cross coupling errors,
1937      * or null if not available.
1938      */
1939     @Override
1940     public Matrix getEstimatedMm() {
1941         return estimatedMm;
1942     }
1943 
1944     /**
1945      * Gets estimated x-axis scale factor.
1946      *
1947      * @return estimated x-axis scale factor or null if not available.
1948      */
1949     @Override
1950     public Double getEstimatedSx() {
1951         return estimatedMm != null ? estimatedMm.getElementAt(0, 0) : null;
1952     }
1953 
1954     /**
1955      * Gets estimated y-axis scale factor.
1956      *
1957      * @return estimated y-axis scale factor or null if not available.
1958      */
1959     @Override
1960     public Double getEstimatedSy() {
1961         return estimatedMm != null ? estimatedMm.getElementAt(1, 1) : null;
1962     }
1963 
1964     /**
1965      * Gets estimated z-axis scale factor.
1966      *
1967      * @return estimated z-axis scale factor or null if not available.
1968      */
1969     @Override
1970     public Double getEstimatedSz() {
1971         return estimatedMm != null ? estimatedMm.getElementAt(2, 2) : null;
1972     }
1973 
1974     /**
1975      * Gets estimated x-y cross-coupling error.
1976      *
1977      * @return estimated x-y cross-coupling error or null if not available.
1978      */
1979     @Override
1980     public Double getEstimatedMxy() {
1981         return estimatedMm != null ? estimatedMm.getElementAt(0, 1) : null;
1982     }
1983 
1984     /**
1985      * Gets estimated x-z cross-coupling error.
1986      *
1987      * @return estimated x-z cross-coupling error or null if not available.
1988      */
1989     @Override
1990     public Double getEstimatedMxz() {
1991         return estimatedMm != null ? estimatedMm.getElementAt(0, 2) : null;
1992     }
1993 
1994     /**
1995      * Gets estimated y-x cross-coupling error.
1996      *
1997      * @return estimated y-x cross-coupling error or null if not available.
1998      */
1999     @Override
2000     public Double getEstimatedMyx() {
2001         return estimatedMm != null ? estimatedMm.getElementAt(1, 0) : null;
2002     }
2003 
2004     /**
2005      * Gets estimated y-z cross-coupling error.
2006      *
2007      * @return estimated y-z cross-coupling error or null if not available.
2008      */
2009     @Override
2010     public Double getEstimatedMyz() {
2011         return estimatedMm != null ? estimatedMm.getElementAt(1, 2) : null;
2012     }
2013 
2014     /**
2015      * Gets estimated z-x cross-coupling error.
2016      *
2017      * @return estimated z-x cross-coupling error or null if not available.
2018      */
2019     @Override
2020     public Double getEstimatedMzx() {
2021         return estimatedMm != null ? estimatedMm.getElementAt(2, 0) : null;
2022     }
2023 
2024     /**
2025      * Gets estimated z-y cross-coupling error.
2026      *
2027      * @return estimated z-y cross-coupling error or null if not available.
2028      */
2029     @Override
2030     public Double getEstimatedMzy() {
2031         return estimatedMm != null ? estimatedMm.getElementAt(2, 1) : null;
2032     }
2033 
2034     /**
2035      * Gets estimated chi square value.
2036      *
2037      * @return estimated chi square value.
2038      */
2039     @Override
2040     public double getEstimatedChiSq() {
2041         return estimatedChiSq;
2042     }
2043 
2044     /**
2045      * Gets estimated chi square degrees of freedom. Degrees of freedom is equal to the number of sampled data minus the
2046      * number of estimated parameters.
2047      *
2048      * @return estimated degrees of freedom of chi square value
2049      */
2050     @Override
2051     public int getEstimatedChiSqDegreesOfFreedom() {
2052         return estimatedChiSqDegreesOfFreedom;
2053     }
2054 
2055     /**
2056      * Gets estimated reduced chi square value. This is equal to estimated chi square value divided by its degrees of
2057      * freedom. Ideally this value should be close to 1.0, indicating that fit is optimal.
2058      * A value larger than 1.0 indicates that fit is not good or noise has been underestimated, and a value smaller than
2059      * 1.0 indicates that there is overfitting or noise has been overestimated.
2060      *
2061      * @return estimated reduced chi square value
2062      */
2063     @Override
2064     public double getEstimatedReducedChiSq() {
2065         return estimatedReducedChiSq;
2066     }
2067 
2068     /**
2069      * Gets estimated mean square error respect to provided measurements.
2070      *
2071      * @return estimated mean square error respect to provided measurements.
2072      */
2073     @Override
2074     public double getEstimatedMse() {
2075         return estimatedMse;
2076     }
2077 
2078     /**
2079      * Gets estimated probability of finding a smaller chi square value expressed as a value between 0.0 and 1.0. The
2080      * smaller the found chi square value is, the better the fit of the estimated parameters to the actual parameter.
2081      * Thus, the smaller the chance of finding a smaller chi square value, then the better the estimated fit is.
2082      *
2083      * @return estimated probability of finding a smaller chi square value.
2084      */
2085     @Override
2086     public double getEstimatedP() {
2087         return estimatedP;
2088     }
2089 
2090     /**
2091      * Gets estimated measure of quality of estimated fit as a value between 0.0 and 1.0. The larger the quality value
2092      * is, the better the fit that has been estimated.
2093      *
2094      * @return estimated measure of quality of estimated fit.
2095      */
2096     @Override
2097     public double getEstimatedQ() {
2098         return estimatedQ;
2099     }
2100 
2101     /**
2102      * Gets estimated covariance matrix for estimated calibration parameters.
2103      * Diagonal elements of the matrix contains variance for the following
2104      * parameters (following indicated order): bx, by, bz, sx, sy, sz,
2105      * mxy, mxz, myx, myz, mzx, mzy.
2106      *
2107      * @return estimated covariance matrix for estimated calibration parameters.
2108      */
2109     @Override
2110     public Matrix getEstimatedCovariance() {
2111         return estimatedCovariance;
2112     }
2113 
2114     /**
2115      * Gets variance of estimated x coordinate of magnetometer bias expressed in
2116      * squared Teslas (T^2).
2117      *
2118      * @return variance of estimated x coordinate of magnetometer bias or null if
2119      * not available.
2120      */
2121     public Double getEstimatedHardIronXVariance() {
2122         return estimatedCovariance != null ? estimatedCovariance.getElementAt(0, 0) : null;
2123     }
2124 
2125     /**
2126      * Gets standard deviation of estimated x coordinate of magnetometer bias
2127      * expressed in Teslas (T).
2128      *
2129      * @return standard deviation of estimated x coordinate of magnetometer bias
2130      * or null if not available.
2131      */
2132     public Double getEstimatedHardIronXStandardDeviation() {
2133         final var variance = getEstimatedHardIronXVariance();
2134         return variance != null ? Math.sqrt(variance) : null;
2135     }
2136 
2137     /**
2138      * Gets standard deviation of estimated x coordinate of magnetometer bias.
2139      *
2140      * @return standard deviation of estimated x coordinate of magnetometer bias
2141      * or null if not available.
2142      */
2143     public MagneticFluxDensity getEstimatedHardIronXStandardDeviationAsMagneticFluxDensity() {
2144         return estimatedCovariance != null
2145                 ? new MagneticFluxDensity(getEstimatedHardIronXStandardDeviation(), MagneticFluxDensityUnit.TESLA)
2146                 : null;
2147     }
2148 
2149     /**
2150      * Gets standard deviation of estimated x coordinate of magnetometer bias.
2151      *
2152      * @param result instance where result will be stored.
2153      * @return true if standard deviation of estimated x coordinate of
2154      * magnetometer bias is available, false otherwise.
2155      */
2156     public boolean getEstimatedHardIronXStandardDeviationAsMagneticFluxDensity(final MagneticFluxDensity result) {
2157         if (estimatedCovariance != null) {
2158             result.setValue(getEstimatedHardIronXStandardDeviation());
2159             result.setUnit(MagneticFluxDensityUnit.TESLA);
2160             return true;
2161         } else {
2162             return false;
2163         }
2164     }
2165 
2166     /**
2167      * Gets variance of estimated y coordinate of magnetometer bias expressed in
2168      * squared Teslas (T^2).
2169      *
2170      * @return variance of estimated y coordinate of magnetometer bias or null if
2171      * not available.
2172      */
2173     public Double getEstimatedHardIronYVariance() {
2174         return estimatedCovariance != null ? estimatedCovariance.getElementAt(1, 1) : null;
2175     }
2176 
2177     /**
2178      * Gets standard deviation of estimated y coordinate of magnetometer bias
2179      * expressed in Teslas (T).
2180      *
2181      * @return standard deviation of estimated y coordinate of magnetometer bias
2182      * or null if not available.
2183      */
2184     public Double getEstimatedHardIronYStandardDeviation() {
2185         final var variance = getEstimatedHardIronYVariance();
2186         return variance != null ? Math.sqrt(variance) : null;
2187     }
2188 
2189     /**
2190      * Gets standard deviation of estimated y coordinate of magnetometer bias.
2191      *
2192      * @return standard deviation of estimated y coordinate of magnetometer bias
2193      * or null if not available.
2194      */
2195     public MagneticFluxDensity getEstimatedHardIronYStandardDeviationAsMagneticFluxDensity() {
2196         return estimatedCovariance != null
2197                 ? new MagneticFluxDensity(getEstimatedHardIronYStandardDeviation(), MagneticFluxDensityUnit.TESLA)
2198                 : null;
2199     }
2200 
2201     /**
2202      * Gets standard deviation of estimated y coordinate of magnetometer bias.
2203      *
2204      * @param result instance where result will be stored.
2205      * @return true if standard deviation of estimated y coordinate of
2206      * magnetometer bias is available, false otherwise.
2207      */
2208     public boolean getEstimatedHardIronYStandardDeviationAsMagneticFluxDensity(final MagneticFluxDensity result) {
2209         if (estimatedCovariance != null) {
2210             result.setValue(getEstimatedHardIronYStandardDeviation());
2211             result.setUnit(MagneticFluxDensityUnit.TESLA);
2212             return true;
2213         } else {
2214             return false;
2215         }
2216     }
2217 
2218     /**
2219      * Gets variance of estimated z coordinate of magnetometer bias expressed in
2220      * squared Teslas (T^2).
2221      *
2222      * @return variance of estimated z coordinate of magnetometer bias or null if
2223      * not available.
2224      */
2225     public Double getEstimatedHardIronZVariance() {
2226         return estimatedCovariance != null ? estimatedCovariance.getElementAt(2, 2) : null;
2227     }
2228 
2229     /**
2230      * Gets standard deviation of estimated z coordinate of magnetometer bias
2231      * expressed in Teslas (T).
2232      *
2233      * @return standard deviation of estimated z coordinate of magnetometer bias
2234      * or null if not available.
2235      */
2236     public Double getEstimatedHardIronZStandardDeviation() {
2237         final var variance = getEstimatedHardIronZVariance();
2238         return variance != null ? Math.sqrt(variance) : null;
2239     }
2240 
2241     /**
2242      * Gets standard deviation of estimated z coordinate of magnetometer bias.
2243      *
2244      * @return standard deviation of estimated z coordinate of magnetometer bias
2245      * or null if not available.
2246      */
2247     public MagneticFluxDensity getEstimatedHardIronZStandardDeviationAsMagneticFluxDensity() {
2248         return estimatedCovariance != null
2249                 ? new MagneticFluxDensity(getEstimatedHardIronZStandardDeviation(), MagneticFluxDensityUnit.TESLA)
2250                 : null;
2251     }
2252 
2253     /**
2254      * Gets standard deviation of estimated z coordinate of magnetometer bias.
2255      *
2256      * @param result instance where result will be stored.
2257      * @return true if standard deviation of estimated z coordinate of
2258      * magnetometer bias is available, false otherwise.
2259      */
2260     public boolean getEstimatedHardIronZStandardDeviationAsMagneticFluxDensity(final MagneticFluxDensity result) {
2261         if (estimatedCovariance != null) {
2262             result.setValue(getEstimatedHardIronZStandardDeviation());
2263             result.setUnit(MagneticFluxDensityUnit.TESLA);
2264             return true;
2265         } else {
2266             return false;
2267         }
2268     }
2269 
2270     /**
2271      * Gets standard deviation of estimated magnetometer bias coordinates.
2272      *
2273      * @return standard deviation of estimated magnetometer bias coordinates.
2274      */
2275     public MagneticFluxDensityTriad getEstimatedHardIronStandardDeviation() {
2276         return estimatedCovariance != null
2277                 ? new MagneticFluxDensityTriad(MagneticFluxDensityUnit.TESLA,
2278                 getEstimatedHardIronXStandardDeviation(),
2279                 getEstimatedHardIronYStandardDeviation(),
2280                 getEstimatedHardIronZStandardDeviation())
2281                 : null;
2282     }
2283 
2284     /**
2285      * Gets standard deviation of estimated magnetometer bias coordinates.
2286      *
2287      * @param result instance where result will be stored.
2288      * @return true if standard deviation of magnetometer bias was available,
2289      * false otherwise.
2290      */
2291     public boolean getEstimatedHardIronStandardDeviation(final MagneticFluxDensityTriad result) {
2292         if (estimatedCovariance != null) {
2293             result.setValueCoordinatesAndUnit(
2294                     getEstimatedHardIronXStandardDeviation(),
2295                     getEstimatedHardIronYStandardDeviation(),
2296                     getEstimatedHardIronZStandardDeviation(),
2297                     MagneticFluxDensityUnit.TESLA);
2298             return true;
2299         } else {
2300             return false;
2301         }
2302     }
2303 
2304     /**
2305      * Gets average of estimated standard deviation of magnetometer bias coordinates
2306      * expressed in Teslas (T).
2307      *
2308      * @return average of estimated standard deviation of magnetometer bias coordinates,
2309      * or null if not available.
2310      */
2311     public Double getEstimatedHardIronStandardDeviationAverage() {
2312         return estimatedCovariance != null
2313                 ? (getEstimatedHardIronXStandardDeviation() + getEstimatedHardIronYStandardDeviation()
2314                 + getEstimatedHardIronZStandardDeviation()) / 3.0
2315                 : null;
2316     }
2317 
2318     /**
2319      * Gets average of estimated standard deviation of magnetometer bias coordinates.
2320      *
2321      * @return average of estimated standard deviation of magnetometer bias coordinates,
2322      * or null if not available.
2323      */
2324     public MagneticFluxDensity getEstimatedHardIronStandardDeviationAverageAsMagneticFluxDensity() {
2325         return estimatedCovariance != null
2326                 ? new MagneticFluxDensity(getEstimatedHardIronStandardDeviationAverage(),
2327                 MagneticFluxDensityUnit.TESLA)
2328                 : null;
2329     }
2330 
2331     /**
2332      * Gets average of estimated standard deviation of magnetometer bias coordinates.
2333      *
2334      * @param result instance where result will be stored.
2335      * @return true if average of estimated standard deviation of magnetometer bias is available,
2336      * false otherwise.
2337      */
2338     public boolean getEstimatedHardIronStandardDeviationAverageAsMagneticFluxDensity(
2339             final MagneticFluxDensity result) {
2340         if (estimatedCovariance != null) {
2341             result.setValue(getEstimatedHardIronStandardDeviationAverage());
2342             result.setUnit(MagneticFluxDensityUnit.TESLA);
2343             return true;
2344         } else {
2345             return false;
2346         }
2347     }
2348 
2349     /**
2350      * Gets norm of estimated standard deviation of magnetometer bias expressed in
2351      * Teslas (T).
2352      *
2353      * @return norm of estimated standard deviation of magnetometer bias or null
2354      * if not available.
2355      */
2356     public Double getEstimatedHardIronStandardDeviationNorm() {
2357         return estimatedCovariance != null
2358                 ? Math.sqrt(getEstimatedHardIronXVariance()
2359                 + getEstimatedHardIronYVariance()
2360                 + getEstimatedHardIronZVariance())
2361                 : null;
2362     }
2363 
2364     /**
2365      * Gets norm of estimated standard deviation of magnetometer bias.
2366      *
2367      * @return norm of estimated standard deviation of magnetometer bias or null
2368      * if not available.
2369      */
2370     public MagneticFluxDensity getEstimatedHardIronStandardDeviationNormAsMagneticFluxDensity() {
2371         return estimatedCovariance != null
2372                 ? new MagneticFluxDensity(getEstimatedHardIronStandardDeviationNorm(), MagneticFluxDensityUnit.TESLA)
2373                 : null;
2374     }
2375 
2376     /**
2377      * Gets norm of estimated standard deviation of magnetometer bias.
2378      *
2379      * @param result instance where result will be stored.
2380      * @return true if norm of estimated standard deviation of magnetometer bias
2381      * is available, false otherwise.
2382      */
2383     public boolean getEstimatedHardIronStandardDeviationNormAsMagneticFluxDensity(final MagneticFluxDensity result) {
2384         if (estimatedCovariance != null) {
2385             result.setValue(getEstimatedHardIronStandardDeviationNorm());
2386             result.setUnit(MagneticFluxDensityUnit.TESLA);
2387             return true;
2388         } else {
2389             return false;
2390         }
2391     }
2392 
2393     /**
2394      * Gets size of subsets to be checked during robust estimation.
2395      * This has to be at least {@link #MINIMUM_MEASUREMENTS}.
2396      *
2397      * @return size of subsets to be checked during robust estimation.
2398      */
2399     public int getPreliminarySubsetSize() {
2400         return preliminarySubsetSize;
2401     }
2402 
2403     /**
2404      * Sets size of subsets to be checked during robust estimation.
2405      * This has to be at least {@link #MINIMUM_MEASUREMENTS}.
2406      *
2407      * @param preliminarySubsetSize size of subsets to be checked during robust estimation.
2408      * @throws LockedException          if calibrator is currently running.
2409      * @throws IllegalArgumentException if provided value is less than {@link #MINIMUM_MEASUREMENTS}.
2410      */
2411     public void setPreliminarySubsetSize(final int preliminarySubsetSize) throws LockedException {
2412         if (running) {
2413             throw new LockedException();
2414         }
2415         if (preliminarySubsetSize < MINIMUM_MEASUREMENTS) {
2416             throw new IllegalArgumentException();
2417         }
2418 
2419         this.preliminarySubsetSize = preliminarySubsetSize;
2420     }
2421 
2422     /**
2423      * Returns method being used for robust estimation.
2424      *
2425      * @return method being used for robust estimation.
2426      */
2427     public abstract RobustEstimatorMethod getMethod();
2428 
2429     /**
2430      * Creates a robust known frame magnetometer calibrator.
2431      *
2432      * @param method robust estimator method.
2433      * @return a robust known frame magnetometer calibrator.
2434      */
2435     public static RobustKnownFrameMagnetometerCalibrator create(final RobustEstimatorMethod method) {
2436         return switch (method) {
2437             case RANSAC -> new RANSACRobustKnownFrameMagnetometerCalibrator();
2438             case LMEDS -> new LMedSRobustKnownFrameMagnetometerCalibrator();
2439             case MSAC -> new MSACRobustKnownFrameMagnetometerCalibrator();
2440             case PROSAC -> new PROSACRobustKnownFrameMagnetometerCalibrator();
2441             default -> new PROMedSRobustKnownFrameMagnetometerCalibrator();
2442         };
2443     }
2444 
2445     /**
2446      * Creates a robust known frame magnetometer calibrator.
2447      *
2448      * @param listener listener to be notified of events such as when estimation
2449      *                 starts, ends or its progress significantly changes.
2450      * @param method   robust estimator method.
2451      * @return a robust known frame magnetometer calibrator.
2452      */
2453     public static RobustKnownFrameMagnetometerCalibrator create(
2454             final RobustKnownFrameMagnetometerCalibratorListener listener, final RobustEstimatorMethod method) {
2455         return switch (method) {
2456             case RANSAC -> new RANSACRobustKnownFrameMagnetometerCalibrator(listener);
2457             case LMEDS -> new LMedSRobustKnownFrameMagnetometerCalibrator(listener);
2458             case MSAC -> new MSACRobustKnownFrameMagnetometerCalibrator(listener);
2459             case PROSAC -> new PROSACRobustKnownFrameMagnetometerCalibrator(listener);
2460             default -> new PROMedSRobustKnownFrameMagnetometerCalibrator(listener);
2461         };
2462     }
2463 
2464     /**
2465      * Creates a robust known frame magnetometer calibrator.
2466      *
2467      * @param measurements list of body magnetic flux density measurements with standard
2468      *                     deviations taken at different frames (positions and
2469      *                     orientations).
2470      * @param method       robust estimator method.
2471      * @return a robust known frame magnetometer calibrator.
2472      */
2473     public static RobustKnownFrameMagnetometerCalibrator create(
2474             final List<StandardDeviationFrameBodyMagneticFluxDensity> measurements,
2475             final RobustEstimatorMethod method) {
2476         return switch (method) {
2477             case RANSAC -> new RANSACRobustKnownFrameMagnetometerCalibrator(measurements);
2478             case LMEDS -> new LMedSRobustKnownFrameMagnetometerCalibrator(measurements);
2479             case MSAC -> new MSACRobustKnownFrameMagnetometerCalibrator(measurements);
2480             case PROSAC -> new PROSACRobustKnownFrameMagnetometerCalibrator(measurements);
2481             default -> new PROMedSRobustKnownFrameMagnetometerCalibrator(measurements);
2482         };
2483     }
2484 
2485     /**
2486      * Creates a robust known frame magnetometer calibrator.
2487      *
2488      * @param measurements list of body magnetic flux density measurements with standard
2489      *                     deviations taken at different frames (positions and
2490      *                     orientations).
2491      * @param listener     listener to handle events raised by this calibrator.
2492      * @param method       robust estimator method.
2493      * @return a robust known frame magnetometer calibrator.
2494      */
2495     public static RobustKnownFrameMagnetometerCalibrator create(
2496             final List<StandardDeviationFrameBodyMagneticFluxDensity> measurements,
2497             final RobustKnownFrameMagnetometerCalibratorListener listener, final RobustEstimatorMethod method) {
2498         return switch (method) {
2499             case RANSAC -> new RANSACRobustKnownFrameMagnetometerCalibrator(measurements, listener);
2500             case LMEDS -> new LMedSRobustKnownFrameMagnetometerCalibrator(measurements, listener);
2501             case MSAC -> new MSACRobustKnownFrameMagnetometerCalibrator(measurements, listener);
2502             case PROSAC -> new PROSACRobustKnownFrameMagnetometerCalibrator(measurements, listener);
2503             default -> new PROMedSRobustKnownFrameMagnetometerCalibrator(measurements, listener);
2504         };
2505     }
2506 
2507     /**
2508      * Creates a robust known frame magnetometer calibrator.
2509      *
2510      * @param commonAxisUsed indicates whether z-axis is assumed to be common
2511      *                       for the accelerometer, gyroscope and magnetometer.
2512      * @param method         robust estimator method.
2513      * @return a robust known frame magnetometer calibrator.
2514      */
2515     public static RobustKnownFrameMagnetometerCalibrator create(
2516             final boolean commonAxisUsed, final RobustEstimatorMethod method) {
2517         return switch (method) {
2518             case RANSAC -> new RANSACRobustKnownFrameMagnetometerCalibrator(commonAxisUsed);
2519             case LMEDS -> new LMedSRobustKnownFrameMagnetometerCalibrator(commonAxisUsed);
2520             case MSAC -> new MSACRobustKnownFrameMagnetometerCalibrator(commonAxisUsed);
2521             case PROSAC -> new PROSACRobustKnownFrameMagnetometerCalibrator(commonAxisUsed);
2522             default -> new PROMedSRobustKnownFrameMagnetometerCalibrator(commonAxisUsed);
2523         };
2524     }
2525 
2526     /**
2527      * Creates a robust known frame magnetometer calibrator.
2528      *
2529      * @param commonAxisUsed indicates whether z-axis is assumed to be common
2530      *                       for the accelerometer, gyroscope and magnetometer.
2531      * @param listener       listener to handle events raised by this calibrator.
2532      * @param method         robust estimator method.
2533      * @return a robust known frame magnetometer calibrator.
2534      */
2535     public static RobustKnownFrameMagnetometerCalibrator create(
2536             final boolean commonAxisUsed, final RobustKnownFrameMagnetometerCalibratorListener listener,
2537             final RobustEstimatorMethod method) {
2538         return switch (method) {
2539             case RANSAC -> new RANSACRobustKnownFrameMagnetometerCalibrator(commonAxisUsed, listener);
2540             case LMEDS -> new LMedSRobustKnownFrameMagnetometerCalibrator(commonAxisUsed, listener);
2541             case MSAC -> new MSACRobustKnownFrameMagnetometerCalibrator(commonAxisUsed, listener);
2542             case PROSAC -> new PROSACRobustKnownFrameMagnetometerCalibrator(commonAxisUsed, listener);
2543             default -> new PROMedSRobustKnownFrameMagnetometerCalibrator(commonAxisUsed, listener);
2544         };
2545     }
2546 
2547     /**
2548      * Creates a robust known frame magnetometer calibrator.
2549      *
2550      * @param measurements   list of body magnetic flux density measurements with standard
2551      *                       deviations taken at different frames (positions and
2552      *                       orientations).
2553      * @param commonAxisUsed indicates whether z-axis is assumed to be common
2554      *                       for the accelerometer, gyroscope and magnetometer.
2555      * @param method         robust estimator method.
2556      * @return a robust known frame magnetometer calibrator.
2557      */
2558     public static RobustKnownFrameMagnetometerCalibrator create(
2559             final List<StandardDeviationFrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
2560             final RobustEstimatorMethod method) {
2561         return switch (method) {
2562             case RANSAC -> new RANSACRobustKnownFrameMagnetometerCalibrator(measurements, commonAxisUsed);
2563             case LMEDS -> new LMedSRobustKnownFrameMagnetometerCalibrator(measurements, commonAxisUsed);
2564             case MSAC -> new MSACRobustKnownFrameMagnetometerCalibrator(measurements, commonAxisUsed);
2565             case PROSAC -> new PROSACRobustKnownFrameMagnetometerCalibrator(measurements, commonAxisUsed);
2566             default -> new PROMedSRobustKnownFrameMagnetometerCalibrator(measurements, commonAxisUsed);
2567         };
2568     }
2569 
2570     /**
2571      * Creates a robust known frame magnetometer calibrator.
2572      *
2573      * @param measurements   list of body magnetic flux density measurements with standard
2574      *                       deviations taken at different frames (positions and
2575      *                       orientations).
2576      * @param commonAxisUsed indicates whether z-axis is assumed to be common
2577      *                       for the accelerometer, gyroscope and magnetometer.
2578      * @param listener       listener to handle events raised by this calibrator.
2579      * @param method         robust estimator method.
2580      * @return a robust known frame magnetometer calibrator.
2581      */
2582     public static RobustKnownFrameMagnetometerCalibrator create(
2583             final List<StandardDeviationFrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
2584             final RobustKnownFrameMagnetometerCalibratorListener listener, final RobustEstimatorMethod method) {
2585         return switch (method) {
2586             case RANSAC -> new RANSACRobustKnownFrameMagnetometerCalibrator(measurements, commonAxisUsed, listener);
2587             case LMEDS -> new LMedSRobustKnownFrameMagnetometerCalibrator(measurements, commonAxisUsed, listener);
2588             case MSAC -> new MSACRobustKnownFrameMagnetometerCalibrator(measurements, commonAxisUsed, listener);
2589             case PROSAC -> new PROSACRobustKnownFrameMagnetometerCalibrator(measurements, commonAxisUsed, listener);
2590             default -> new PROMedSRobustKnownFrameMagnetometerCalibrator(measurements, commonAxisUsed, listener);
2591         };
2592     }
2593 
2594     /**
2595      * Creates a robust known frame magnetometer calibrator.
2596      *
2597      * @param qualityScores quality scores corresponding to each provided
2598      *                      measurement. The larger the score value the better
2599      *                      the quality of the sample.
2600      * @param method        robust estimator method.
2601      * @return a robust known frame magnetometer calibrator.
2602      * @throws IllegalArgumentException if provided quality scores length
2603      *                                  is smaller than 4 samples.
2604      */
2605     public static RobustKnownFrameMagnetometerCalibrator create(
2606             final double[] qualityScores, final RobustEstimatorMethod method) {
2607         return switch (method) {
2608             case RANSAC -> new RANSACRobustKnownFrameMagnetometerCalibrator();
2609             case LMEDS -> new LMedSRobustKnownFrameMagnetometerCalibrator();
2610             case MSAC -> new MSACRobustKnownFrameMagnetometerCalibrator();
2611             case PROSAC -> new PROSACRobustKnownFrameMagnetometerCalibrator(qualityScores);
2612             default -> new PROMedSRobustKnownFrameMagnetometerCalibrator(qualityScores);
2613         };
2614     }
2615 
2616     /**
2617      * Creates a robust known frame magnetometer calibrator.
2618      *
2619      * @param qualityScores quality scores corresponding to each provided
2620      *                      measurement. The larger the score value the better
2621      *                      the quality of the sample.
2622      * @param listener      listener to be notified of events such as when estimation
2623      *                      starts, ends or its progress significantly changes.
2624      * @param method        robust estimator method.
2625      * @return a robust known frame magnetometer calibrator.
2626      * @throws IllegalArgumentException if provided quality scores length
2627      *                                  is smaller than 4 samples.
2628      */
2629     public static RobustKnownFrameMagnetometerCalibrator create(
2630             final double[] qualityScores, final RobustKnownFrameMagnetometerCalibratorListener listener,
2631             final RobustEstimatorMethod method) {
2632         return switch (method) {
2633             case RANSAC -> new RANSACRobustKnownFrameMagnetometerCalibrator(listener);
2634             case LMEDS -> new LMedSRobustKnownFrameMagnetometerCalibrator(listener);
2635             case MSAC -> new MSACRobustKnownFrameMagnetometerCalibrator(listener);
2636             case PROSAC -> new PROSACRobustKnownFrameMagnetometerCalibrator(qualityScores, listener);
2637             default -> new PROMedSRobustKnownFrameMagnetometerCalibrator(qualityScores, listener);
2638         };
2639     }
2640 
2641     /**
2642      * Creates a robust known frame magnetometer calibrator.
2643      *
2644      * @param qualityScores quality scores corresponding to each provided
2645      *                      measurement. The larger the score value the better
2646      *                      the quality of the sample.
2647      * @param measurements  list of body magnetic flux density measurements with standard
2648      *                      deviations taken at different frames (positions and
2649      *                      orientations).
2650      * @param method        robust estimator method.
2651      * @return a robust known frame magnetometer calibrator.
2652      * @throws IllegalArgumentException if provided quality scores length
2653      *                                  is smaller than 4 samples.
2654      */
2655     public static RobustKnownFrameMagnetometerCalibrator create(
2656             final double[] qualityScores, final List<StandardDeviationFrameBodyMagneticFluxDensity> measurements,
2657             final RobustEstimatorMethod method) {
2658         return switch (method) {
2659             case RANSAC -> new RANSACRobustKnownFrameMagnetometerCalibrator(measurements);
2660             case LMEDS -> new LMedSRobustKnownFrameMagnetometerCalibrator(measurements);
2661             case MSAC -> new MSACRobustKnownFrameMagnetometerCalibrator(measurements);
2662             case PROSAC -> new PROSACRobustKnownFrameMagnetometerCalibrator(qualityScores, measurements);
2663             default -> new PROMedSRobustKnownFrameMagnetometerCalibrator(qualityScores, measurements);
2664         };
2665     }
2666 
2667     /**
2668      * Creates a robust known frame magnetometer calibrator.
2669      *
2670      * @param qualityScores quality scores corresponding to each provided
2671      *                      measurement. The larger the score value the better
2672      *                      the quality of the sample.
2673      * @param measurements  list of body magnetic flux density measurements with standard
2674      *                      deviations taken at different frames (positions and
2675      *                      orientations).
2676      * @param listener      listener to handle events raised by this calibrator.
2677      * @param method        robust estimator method.
2678      * @return a robust known frame magnetometer calibrator.
2679      * @throws IllegalArgumentException if provided quality scores length
2680      *                                  is smaller than 4 samples.
2681      */
2682     public static RobustKnownFrameMagnetometerCalibrator create(
2683             final double[] qualityScores, final List<StandardDeviationFrameBodyMagneticFluxDensity> measurements,
2684             final RobustKnownFrameMagnetometerCalibratorListener listener, final RobustEstimatorMethod method) {
2685         return switch (method) {
2686             case RANSAC -> new RANSACRobustKnownFrameMagnetometerCalibrator(measurements, listener);
2687             case LMEDS -> new LMedSRobustKnownFrameMagnetometerCalibrator(measurements, listener);
2688             case MSAC -> new MSACRobustKnownFrameMagnetometerCalibrator(measurements, listener);
2689             case PROSAC -> new PROSACRobustKnownFrameMagnetometerCalibrator(qualityScores, measurements, listener);
2690             default -> new PROMedSRobustKnownFrameMagnetometerCalibrator(qualityScores, measurements, listener);
2691         };
2692     }
2693 
2694     /**
2695      * Creates a robust known frame magnetometer calibrator.
2696      *
2697      * @param qualityScores  quality scores corresponding to each provided
2698      *                       measurement. The larger the score value the better
2699      *                       the quality of the sample.
2700      * @param commonAxisUsed indicates whether z-axis is assumed to be common
2701      *                       for the accelerometer, gyroscope and magnetometer.
2702      * @param method         robust estimator method.
2703      * @return a robust known frame magnetometer calibrator.
2704      * @throws IllegalArgumentException if provided quality scores length
2705      *                                  is smaller than 4 samples.
2706      */
2707     public static RobustKnownFrameMagnetometerCalibrator create(
2708             final double[] qualityScores, final boolean commonAxisUsed, final RobustEstimatorMethod method) {
2709         return switch (method) {
2710             case RANSAC -> new RANSACRobustKnownFrameMagnetometerCalibrator(commonAxisUsed);
2711             case LMEDS -> new LMedSRobustKnownFrameMagnetometerCalibrator(commonAxisUsed);
2712             case MSAC -> new MSACRobustKnownFrameMagnetometerCalibrator(commonAxisUsed);
2713             case PROSAC -> new PROSACRobustKnownFrameMagnetometerCalibrator(qualityScores, commonAxisUsed);
2714             default -> new PROMedSRobustKnownFrameMagnetometerCalibrator(qualityScores, commonAxisUsed);
2715         };
2716     }
2717 
2718     /**
2719      * Creates a robust known frame magnetometer calibrator.
2720      *
2721      * @param qualityScores  quality scores corresponding to each provided
2722      *                       measurement. The larger the score value the better
2723      *                       the quality of the sample.
2724      * @param commonAxisUsed indicates whether z-axis is assumed to be common
2725      *                       for the accelerometer, gyroscope and magnetometer.
2726      * @param listener       listener to handle events raised by this calibrator.
2727      * @param method         robust estimator method.
2728      * @return a robust known frame magnetometer calibrator.
2729      * @throws IllegalArgumentException if provided quality scores length
2730      *                                  is smaller than 4 samples.
2731      */
2732     public static RobustKnownFrameMagnetometerCalibrator create(
2733             final double[] qualityScores, final boolean commonAxisUsed,
2734             final RobustKnownFrameMagnetometerCalibratorListener listener, final RobustEstimatorMethod method) {
2735         return switch (method) {
2736             case RANSAC -> new RANSACRobustKnownFrameMagnetometerCalibrator(commonAxisUsed, listener);
2737             case LMEDS -> new LMedSRobustKnownFrameMagnetometerCalibrator(commonAxisUsed, listener);
2738             case MSAC -> new MSACRobustKnownFrameMagnetometerCalibrator(commonAxisUsed, listener);
2739             case PROSAC -> new PROSACRobustKnownFrameMagnetometerCalibrator(qualityScores, commonAxisUsed, listener);
2740             default -> new PROMedSRobustKnownFrameMagnetometerCalibrator(qualityScores, commonAxisUsed, listener);
2741         };
2742     }
2743 
2744     /**
2745      * Creates a robust known frame magnetometer calibrator.
2746      *
2747      * @param qualityScores  quality scores corresponding to each provided
2748      *                       measurement. The larger the score value the better
2749      *                       the quality of the sample.
2750      * @param measurements   list of body magnetic flux density measurements with standard
2751      *                       deviations taken at different frames (positions and
2752      *                       orientations).
2753      * @param commonAxisUsed indicates whether z-axis is assumed to be common
2754      *                       for the accelerometer, gyroscope and magnetometer.
2755      * @param method         robust estimator method.
2756      * @return a robust known frame magnetometer calibrator.
2757      * @throws IllegalArgumentException if provided quality scores length
2758      *                                  is smaller than 4 samples.
2759      */
2760     public static RobustKnownFrameMagnetometerCalibrator create(
2761             final double[] qualityScores, final List<StandardDeviationFrameBodyMagneticFluxDensity> measurements,
2762             final boolean commonAxisUsed, final RobustEstimatorMethod method) {
2763         return switch (method) {
2764             case RANSAC -> new RANSACRobustKnownFrameMagnetometerCalibrator(measurements, commonAxisUsed);
2765             case LMEDS -> new LMedSRobustKnownFrameMagnetometerCalibrator(measurements, commonAxisUsed);
2766             case MSAC -> new MSACRobustKnownFrameMagnetometerCalibrator(measurements, commonAxisUsed);
2767             case PROSAC -> new PROSACRobustKnownFrameMagnetometerCalibrator(
2768                     qualityScores, measurements, commonAxisUsed);
2769             default -> new PROMedSRobustKnownFrameMagnetometerCalibrator(qualityScores, measurements, commonAxisUsed);
2770         };
2771     }
2772 
2773     /**
2774      * Creates a robust known frame magnetometer calibrator.
2775      *
2776      * @param qualityScores  quality scores corresponding to each provided
2777      *                       measurement. The larger the score value the better
2778      *                       the quality of the sample.
2779      * @param measurements   list of body magnetic flux density measurements with standard
2780      *                       deviations taken at different frames (positions and
2781      *                       orientations).
2782      * @param commonAxisUsed indicates whether z-axis is assumed to be common
2783      *                       for the accelerometer, gyroscope and magnetometer.
2784      * @param listener       listener to handle events raised by this calibrator.
2785      * @param method         robust estimator method.
2786      * @return a robust known frame magnetometer calibrator.
2787      * @throws IllegalArgumentException if provided quality scores length
2788      *                                  is smaller than 4 samples.
2789      */
2790     public static RobustKnownFrameMagnetometerCalibrator create(
2791             final double[] qualityScores, final List<StandardDeviationFrameBodyMagneticFluxDensity> measurements,
2792             final boolean commonAxisUsed, final RobustKnownFrameMagnetometerCalibratorListener listener,
2793             final RobustEstimatorMethod method) {
2794         return switch (method) {
2795             case RANSAC -> new RANSACRobustKnownFrameMagnetometerCalibrator(measurements, commonAxisUsed, listener);
2796             case LMEDS -> new LMedSRobustKnownFrameMagnetometerCalibrator(measurements, commonAxisUsed, listener);
2797             case MSAC -> new MSACRobustKnownFrameMagnetometerCalibrator(measurements, commonAxisUsed, listener);
2798             case PROSAC -> new PROSACRobustKnownFrameMagnetometerCalibrator(qualityScores, measurements, commonAxisUsed,
2799                     listener);
2800             default -> new PROMedSRobustKnownFrameMagnetometerCalibrator(qualityScores, measurements, commonAxisUsed,
2801                     listener);
2802         };
2803     }
2804 
2805     /**
2806      * Creates a robust known frame magnetometer calibrator using default robust method.
2807      *
2808      * @return a robust known frame magnetometer calibrator.
2809      */
2810     public static RobustKnownFrameMagnetometerCalibrator create() {
2811         return create(DEFAULT_ROBUST_METHOD);
2812     }
2813 
2814     /**
2815      * Creates a robust known frame magnetometer calibrator using default robust method.
2816      *
2817      * @param listener listener to be notified of events such as when estimation
2818      *                 starts, ends or its progress significantly changes.
2819      * @return a robust known frame magnetometer calibrator.
2820      */
2821     public static RobustKnownFrameMagnetometerCalibrator create(
2822             final RobustKnownFrameMagnetometerCalibratorListener listener) {
2823         return create(listener, DEFAULT_ROBUST_METHOD);
2824     }
2825 
2826     /**
2827      * Creates a robust known frame magnetometer calibrator using default robust method.
2828      *
2829      * @param measurements list of body magnetic flux density measurements with standard
2830      *                     deviations taken at different frames (positions and
2831      *                     orientations).
2832      * @return a robust known frame magnetometer calibrator.
2833      */
2834     public static RobustKnownFrameMagnetometerCalibrator create(
2835             final List<StandardDeviationFrameBodyMagneticFluxDensity> measurements) {
2836         return create(measurements, DEFAULT_ROBUST_METHOD);
2837     }
2838 
2839     /**
2840      * Creates a robust known frame magnetometer calibrator using default robust method.
2841      *
2842      * @param measurements list of body magnetic flux density measurements with standard
2843      *                     deviations taken at different frames (positions and
2844      *                     orientations).
2845      * @param listener     listener to handle events raised by this calibrator.
2846      * @return a robust known frame magnetometer calibrator.
2847      */
2848     public static RobustKnownFrameMagnetometerCalibrator create(
2849             final List<StandardDeviationFrameBodyMagneticFluxDensity> measurements,
2850             final RobustKnownFrameMagnetometerCalibratorListener listener) {
2851         return create(measurements, listener, DEFAULT_ROBUST_METHOD);
2852     }
2853 
2854     /**
2855      * Creates a robust known frame magnetometer calibrator using default robust method.
2856      *
2857      * @param commonAxisUsed indicates whether z-axis is assumed to be common
2858      *                       for the accelerometer, gyroscope and magnetometer.
2859      * @return a robust known frame magnetometer calibrator.
2860      */
2861     public static RobustKnownFrameMagnetometerCalibrator create(final boolean commonAxisUsed) {
2862         return create(commonAxisUsed, DEFAULT_ROBUST_METHOD);
2863     }
2864 
2865     /**
2866      * Creates a robust known frame magnetometer calibrator using default robust method.
2867      *
2868      * @param commonAxisUsed indicates whether z-axis is assumed to be common
2869      *                       for the accelerometer, gyroscope and magnetometer.
2870      * @param listener       listener to handle events raised by this calibrator.
2871      * @return a robust known frame magnetometer calibrator.
2872      */
2873     public static RobustKnownFrameMagnetometerCalibrator create(
2874             final boolean commonAxisUsed, final RobustKnownFrameMagnetometerCalibratorListener listener) {
2875         return create(commonAxisUsed, listener, DEFAULT_ROBUST_METHOD);
2876     }
2877 
2878     /**
2879      * Creates a robust known frame magnetometer calibrator using default robust method.
2880      *
2881      * @param measurements   list of body magnetic flux density measurements with standard
2882      *                       deviations taken at different frames (positions and
2883      *                       orientations).
2884      * @param commonAxisUsed indicates whether z-axis is assumed to be common
2885      *                       for the accelerometer, gyroscope and magnetometer.
2886      * @return a robust known frame magnetometer calibrator.
2887      */
2888     public static RobustKnownFrameMagnetometerCalibrator create(
2889             final List<StandardDeviationFrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed) {
2890         return create(measurements, commonAxisUsed, DEFAULT_ROBUST_METHOD);
2891     }
2892 
2893     /**
2894      * Creates a robust known frame magnetometer calibrator using default robust method.
2895      *
2896      * @param measurements   list of body magnetic flux density measurements with standard
2897      *                       deviations taken at different frames (positions and
2898      *                       orientations).
2899      * @param commonAxisUsed indicates whether z-axis is assumed to be common
2900      *                       for the accelerometer, gyroscope and magnetometer.
2901      * @param listener       listener to handle events raised by this calibrator.
2902      * @return a robust known frame magnetometer calibrator.
2903      */
2904     public static RobustKnownFrameMagnetometerCalibrator create(
2905             final List<StandardDeviationFrameBodyMagneticFluxDensity> measurements, final boolean commonAxisUsed,
2906             final RobustKnownFrameMagnetometerCalibratorListener listener) {
2907         return create(measurements, commonAxisUsed, listener, DEFAULT_ROBUST_METHOD);
2908     }
2909 
2910     /**
2911      * Setups World Magnetic Model estimator.
2912      *
2913      * @throws IOException if model cannot be loaded.
2914      */
2915     protected void setupWmmEstimator() throws IOException {
2916         if (magneticModel != null) {
2917             wmmEstimator = new WMMEarthMagneticFluxDensityEstimator(magneticModel);
2918         } else {
2919             wmmEstimator = new WMMEarthMagneticFluxDensityEstimator();
2920         }
2921     }
2922 
2923     /**
2924      * Computes error of a preliminary result respect a given measurement.
2925      *
2926      * @param measurement       a measurement.
2927      * @param preliminaryResult a preliminary result.
2928      * @return computed error.
2929      */
2930     protected double computeError(
2931             final StandardDeviationFrameBodyMagneticFluxDensity measurement,
2932             final PreliminaryResult preliminaryResult) {
2933         // The magnetometer model is:
2934         // mBmeas = ba + (I + Mm) * mBtrue
2935 
2936         // Hence:
2937         // [mBmeasx] = [bx] + ( [1  0   0] + [sx    mxy mxz]) [mBtruex]
2938         // [mBmeasy] = [by]     [0  1   0]   [myx   sy  myz]  [mBtruey]
2939         // [mBmeasz] = [bz]     [0  0   1]   [mzx   mzy sz ]  [mBtruez]
2940 
2941         final var measuredMagneticFluxDensity = measurement.getMagneticFluxDensity();
2942         final var ecefFrame = measurement.getFrame();
2943 
2944         final var nedFrame = ECEFtoNEDFrameConverter.convertECEFtoNEDAndReturnNew(ecefFrame);
2945         final var year = measurement.getYear();
2946 
2947         final var latitude = nedFrame.getLatitude();
2948         final var longitude = nedFrame.getLongitude();
2949         final var height = nedFrame.getHeight();
2950 
2951         final var earthB = wmmEstimator.estimate(latitude, longitude, height, year);
2952 
2953         final var cbn = new CoordinateTransformation(FrameType.BODY_FRAME, FrameType.LOCAL_NAVIGATION_FRAME);
2954         final var cnb = new CoordinateTransformation(FrameType.LOCAL_NAVIGATION_FRAME, FrameType.BODY_FRAME);
2955 
2956         nedFrame.getCoordinateTransformation(cbn);
2957         cbn.inverse(cnb);
2958 
2959         final var expectedMagneticFluxDensity = BodyMagneticFluxDensityEstimator.estimate(earthB, cnb);
2960 
2961         final var bMeasX1 = measuredMagneticFluxDensity.getBx();
2962         final var bMeasY1 = measuredMagneticFluxDensity.getBy();
2963         final var bMeasZ1 = measuredMagneticFluxDensity.getBz();
2964 
2965         final var bTrueX = expectedMagneticFluxDensity.getBx();
2966         final var bTrueY = expectedMagneticFluxDensity.getBy();
2967         final var bTrueZ = expectedMagneticFluxDensity.getBz();
2968 
2969         final var b = preliminaryResult.estimatedHardIron;
2970         final var bx = b[0];
2971         final var by = b[1];
2972         final var bz = b[2];
2973 
2974         final var mm = preliminaryResult.estimatedMm;
2975 
2976         try {
2977             final var m = Matrix.identity(BodyKinematics.COMPONENTS, BodyKinematics.COMPONENTS);
2978             m.add(mm);
2979 
2980             final var btrue = new Matrix(BodyKinematics.COMPONENTS, 1);
2981             btrue.setElementAtIndex(0, bTrueX);
2982             btrue.setElementAtIndex(1, bTrueY);
2983             btrue.setElementAtIndex(2, bTrueZ);
2984 
2985             m.multiply(btrue);
2986 
2987             final var bMeasX2 = bx + m.getElementAtIndex(0);
2988             final var bMeasY2 = by + m.getElementAtIndex(1);
2989             final var bMeasZ2 = bz + m.getElementAtIndex(2);
2990 
2991             final var diffX = bMeasX2 - bMeasX1;
2992             final var diffY = bMeasY2 - bMeasY1;
2993             final var diffZ = bMeasZ2 - bMeasZ1;
2994 
2995             return Math.sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ);
2996 
2997         } catch (final WrongSizeException e) {
2998             return Double.MAX_VALUE;
2999         }
3000     }
3001 
3002     /**
3003      * Computes a preliminary solution for a subset of samples picked by a robust estimator.
3004      *
3005      * @param samplesIndices indices of samples picked by the robust estimator.
3006      * @param solutions      list where estimated preliminary solution will be stored.
3007      */
3008     protected void computePreliminarySolutions(final int[] samplesIndices, final List<PreliminaryResult> solutions) {
3009         final var meas = new ArrayList<StandardDeviationFrameBodyMagneticFluxDensity>();
3010 
3011         for (final var samplesIndex : samplesIndices) {
3012             meas.add(this.measurements.get(samplesIndex));
3013         }
3014 
3015         try {
3016             final var result = new PreliminaryResult();
3017             result.estimatedHardIron = getInitialHardIron();
3018             result.estimatedMm = getInitialMm();
3019 
3020             if (useLinearCalibrator) {
3021                 linearCalibrator.setCommonAxisUsed(commonAxisUsed);
3022                 linearCalibrator.setMeasurements(meas);
3023                 linearCalibrator.calibrate();
3024 
3025                 linearCalibrator.getEstimatedHardIron(result.estimatedHardIron);
3026                 result.estimatedMm = linearCalibrator.getEstimatedMm();
3027             }
3028 
3029             if (refinePreliminarySolutions) {
3030                 nonLinearCalibrator.setInitialHardIron(result.estimatedHardIron);
3031                 nonLinearCalibrator.setInitialMm(result.estimatedMm);
3032                 nonLinearCalibrator.setCommonAxisUsed(commonAxisUsed);
3033                 nonLinearCalibrator.setMeasurements(meas);
3034                 nonLinearCalibrator.calibrate();
3035 
3036                 nonLinearCalibrator.getEstimatedHardIron(result.estimatedHardIron);
3037                 result.estimatedMm = nonLinearCalibrator.getEstimatedMm();
3038 
3039                 if (keepCovariance) {
3040                     result.covariance = nonLinearCalibrator.getEstimatedCovariance();
3041                 } else {
3042                     result.covariance = null;
3043                 }
3044 
3045                 result.estimatedMse = nonLinearCalibrator.getEstimatedMse();
3046                 result.estimatedChiSq = nonLinearCalibrator.getEstimatedChiSq();
3047                 result.estimatedChiSqDegreesOfFreedom = nonLinearCalibrator.getEstimatedChiSqDegreesOfFreedom();
3048                 result.estimatedReducedChiSq = nonLinearCalibrator.getEstimatedReducedChiSq();
3049                 result.estimatedP = nonLinearCalibrator.getEstimatedP();
3050                 result.estimatedQ = nonLinearCalibrator.getEstimatedQ();
3051             }
3052 
3053             solutions.add(result);
3054         } catch (final LockedException | CalibrationException | NotReadyException e) {
3055             solutions.clear();
3056         }
3057     }
3058 
3059     /**
3060      * Attempts to refine calibration parameters if refinement is requested.
3061      * This method returns a refined solution or provided input if refinement is not
3062      * requested or has failed.
3063      * If refinement is enabled and it is requested to keep covariance, this method
3064      * will also keep covariance of refined position.
3065      *
3066      * @param preliminaryResult a preliminary result.
3067      */
3068     protected void attemptRefine(final PreliminaryResult preliminaryResult) {
3069         if (refineResult && inliersData != null) {
3070             final var inliers = inliersData.getInliers();
3071             final var nSamples = measurements.size();
3072 
3073             final var inlierMeasurements = new ArrayList<StandardDeviationFrameBodyMagneticFluxDensity>();
3074             for (var i = 0; i < nSamples; i++) {
3075                 if (inliers.get(i)) {
3076                     // sample is inlier
3077                     inlierMeasurements.add(measurements.get(i));
3078                 }
3079             }
3080 
3081             try {
3082                 nonLinearCalibrator.setInitialHardIron(preliminaryResult.estimatedHardIron);
3083                 nonLinearCalibrator.setInitialMm(preliminaryResult.estimatedMm);
3084                 nonLinearCalibrator.setCommonAxisUsed(commonAxisUsed);
3085                 nonLinearCalibrator.setMeasurements(inlierMeasurements);
3086                 nonLinearCalibrator.calibrate();
3087 
3088                 estimatedHardIron = nonLinearCalibrator.getEstimatedHardIron();
3089                 estimatedMm = nonLinearCalibrator.getEstimatedMm();
3090                 estimatedMse = nonLinearCalibrator.getEstimatedMse();
3091                 estimatedChiSq = nonLinearCalibrator.getEstimatedChiSq();
3092                 estimatedChiSqDegreesOfFreedom = nonLinearCalibrator.getEstimatedChiSqDegreesOfFreedom();
3093                 estimatedReducedChiSq = nonLinearCalibrator.getEstimatedReducedChiSq();
3094                 estimatedP = nonLinearCalibrator.getEstimatedP();
3095                 estimatedQ = nonLinearCalibrator.getEstimatedQ();
3096 
3097                 if (keepCovariance) {
3098                     estimatedCovariance = nonLinearCalibrator.getEstimatedCovariance();
3099                 } else {
3100                     estimatedCovariance = null;
3101                 }
3102 
3103             } catch (final LockedException | CalibrationException | NotReadyException e) {
3104                 estimatedCovariance = preliminaryResult.covariance;
3105                 estimatedHardIron = preliminaryResult.estimatedHardIron;
3106                 estimatedMm = preliminaryResult.estimatedMm;
3107                 estimatedMse = preliminaryResult.estimatedMse;
3108                 estimatedChiSq = preliminaryResult.estimatedChiSq;
3109                 estimatedChiSqDegreesOfFreedom = preliminaryResult.estimatedChiSqDegreesOfFreedom;
3110                 estimatedReducedChiSq = preliminaryResult.estimatedReducedChiSq;
3111                 estimatedP = preliminaryResult.estimatedP;
3112                 estimatedQ = preliminaryResult.estimatedQ;
3113             }
3114         } else {
3115             estimatedCovariance = preliminaryResult.covariance;
3116             estimatedHardIron = preliminaryResult.estimatedHardIron;
3117             estimatedMm = preliminaryResult.estimatedMm;
3118             estimatedMse = preliminaryResult.estimatedMse;
3119             estimatedChiSq = preliminaryResult.estimatedChiSq;
3120             estimatedChiSqDegreesOfFreedom = preliminaryResult.estimatedChiSqDegreesOfFreedom;
3121             estimatedReducedChiSq = preliminaryResult.estimatedReducedChiSq;
3122             estimatedP = preliminaryResult.estimatedP;
3123             estimatedQ = preliminaryResult.estimatedQ;
3124         }
3125     }
3126 
3127     /**
3128      * Converts magnetic flux density value and unit to Teslas.
3129      *
3130      * @param value magnetic flux density value.
3131      * @param unit  unit of magnetic flux density value.
3132      * @return converted value.
3133      */
3134     private static double convertMagneticFluxDensity(final double value, final MagneticFluxDensityUnit unit) {
3135         return MagneticFluxDensityConverter.convert(value, unit, MagneticFluxDensityUnit.TESLA);
3136     }
3137 
3138     /**
3139      * Converts magnetic flux density instance to Teslas.
3140      *
3141      * @param magneticFluxDensity magnetic flux density instance to be converted.
3142      * @return converted value.
3143      */
3144     private static double convertMagneticFluxDensity(final MagneticFluxDensity magneticFluxDensity) {
3145         return convertMagneticFluxDensity(magneticFluxDensity.getValue().doubleValue(), magneticFluxDensity.getUnit());
3146     }
3147 
3148     /**
3149      * Internal class containing estimated preliminary result.
3150      */
3151     protected static class PreliminaryResult {
3152         /**
3153          * Estimated magnetometer hard-iron biases for each magnetometer axis
3154          * expressed in Teslas (T).
3155          */
3156         private double[] estimatedHardIron;
3157 
3158         /**
3159          * Estimated magnetometer soft-iron matrix containing scale factors
3160          * and cross coupling errors.
3161          * This is the product of matrix Tm containing cross coupling errors and Km
3162          * containing scaling factors.
3163          * So tat:
3164          * <pre>
3165          *     Mm = [sx    mxy  mxz] = Tm*Km
3166          *          [myx   sy   myz]
3167          *          [mzx   mzy  sz ]
3168          * </pre>
3169          * Where:
3170          * <pre>
3171          *     Km = [sx 0   0 ]
3172          *          [0  sy  0 ]
3173          *          [0  0   sz]
3174          * </pre>
3175          * and
3176          * <pre>
3177          *     Tm = [1          -alphaXy    alphaXz ]
3178          *          [alphaYx    1           -alphaYz]
3179          *          [-alphaZx   alphaZy     1       ]
3180          * </pre>
3181          * Hence:
3182          * <pre>
3183          *     Mm = [sx    mxy  mxz] = Tm*Km =  [sx             -sy * alphaXy   sz * alphaXz ]
3184          *          [myx   sy   myz]            [sx * alphaYx   sy              -sz * alphaYz]
3185          *          [mzx   mzy  sz ]            [-sx * alphaZx  sy * alphaZy    sz           ]
3186          * </pre>
3187          * This instance allows any 3x3 matrix however, typically alphaYx, alphaZx and alphaZy
3188          * are considered to be zero if the accelerometer z-axis is assumed to be the same
3189          * as the body z-axis. When this is assumed, myx = mzx = mzy = 0 and the Mm matrix
3190          * becomes upper diagonal:
3191          * <pre>
3192          *     Mm = [sx    mxy  mxz]
3193          *          [0     sy   myz]
3194          *          [0     0    sz ]
3195          * </pre>
3196          * Values of this matrix are unit-less.
3197          */
3198         private Matrix estimatedMm;
3199 
3200         /**
3201          * Estimated covariance matrix.
3202          */
3203         private Matrix covariance;
3204 
3205         /**
3206          * Estimated Mean Squared Error (MSE).
3207          */
3208         private double estimatedMse;
3209 
3210         /**
3211          * Estimated chi square value.
3212          */
3213         private double estimatedChiSq;
3214 
3215         /**
3216          * Estimated degrees of freedom of chi square value. Degrees of freedom is equal to the number of sampled data
3217          * minus the number of estimated parameters.
3218          */
3219         private int estimatedChiSqDegreesOfFreedom;
3220 
3221         /**
3222          * Estimated reduced chi square value. This is equal to estimated chi square value divided by its degrees of
3223          * freedom. Ideally this value should be close to 1.0.
3224          */
3225         private double estimatedReducedChiSq;
3226 
3227         /**
3228          * Estimated probability of finding a smaller chi square value expressed as a value between 0.0 and 1.0. The smaller
3229          * the found chi square value is, the better the fit of the estimated parameters to the actual parameter. Thus, the
3230          * smaller the chance of finding a smaller chi square value, then the better the estimated fit is.
3231          */
3232         private double estimatedP;
3233 
3234         /**
3235          * Estimated measure of quality of estimated fit as a value between 0.0 and 1.0. The larger the quality value is,
3236          * the better the fit that has been estimated.
3237          */
3238         private double estimatedQ;
3239     }
3240 }