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