View Javadoc
1   /*
2    * Copyright (C) 2018 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.indoor.fingerprint;
17  
18  import com.irurueta.algebra.AlgebraException;
19  import com.irurueta.algebra.Matrix;
20  import com.irurueta.geometry.Point;
21  import com.irurueta.navigation.LockedException;
22  import com.irurueta.navigation.NotReadyException;
23  import com.irurueta.navigation.indoor.RadioSource;
24  import com.irurueta.navigation.indoor.RadioSourceKNearestFinder;
25  import com.irurueta.navigation.indoor.RadioSourceLocated;
26  import com.irurueta.navigation.indoor.RadioSourceNoMeanKNearestFinder;
27  import com.irurueta.navigation.indoor.RadioSourceWithPower;
28  import com.irurueta.navigation.indoor.RssiFingerprint;
29  import com.irurueta.navigation.indoor.RssiFingerprintLocated;
30  import com.irurueta.navigation.indoor.RssiReading;
31  import com.irurueta.numerical.NumericalException;
32  import com.irurueta.numerical.fitting.FittingException;
33  import com.irurueta.numerical.fitting.LevenbergMarquardtMultiDimensionFitter;
34  import com.irurueta.numerical.fitting.LevenbergMarquardtMultiDimensionFunctionEvaluator;
35  
36  import java.util.ArrayList;
37  import java.util.Collection;
38  import java.util.List;
39  
40  /**
41   * Base class for position estimators based on located fingerprints containing only
42   * RSSI readings and having as well prior knowledge of the location of radio sources
43   * associated to those readings.
44   * This implementation uses a Taylor approximation over provided located
45   * fingerprints to determine an approximate position for a non-located fingerprint using
46   * a non-linear solving algorithm.
47   * An initial position can be provided as a starting point to solve the position,
48   * otherwise the average point of selected nearest fingerprints is used as a starting
49   * point.
50   *
51   * @param <P> a {@link Point} type.
52   */
53  public abstract class NonLinearFingerprintPositionEstimator<P extends Point<?>> extends
54          FingerprintPositionEstimator<P> {
55  
56      /**
57       * Default RSSI standard deviation assumed for provided fingerprints as a fallback
58       * when none can be determined.
59       */
60      public static final double FALLBACK_RSSI_STANDARD_DEVIATION = 1.0;
61  
62      /**
63       * Indicates that by default measured RSSI standard deviation of closest fingerprint
64       * must be propagated into measured RSSI reading variance at unknown location.
65       */
66      public static final boolean DEFAULT_PROPAGATE_FINGERPRINT_RSSI_STANDARD_DEVIATION = true;
67  
68      /**
69       * Indicates that by default path-loss exponent standard deviation of radio source
70       * must be propagated into measured RSSI reading variance at unknown location.
71       */
72      public static final boolean DEFAULT_PROPAGATE_PATHLOSS_EXPONENT_STANDARD_DEVIATION = true;
73  
74      /**
75       * Indicates that by default covariance of closest fingerprint position must be
76       * propagated into measured RSSI reading variance at unknown location.
77       */
78      public static final boolean DEFAULT_PROPAGATE_FINGERPRINT_POSITION_COVARIANCE = true;
79  
80      /**
81       * Indicates that by default covariance of radio source position must be propagated
82       * into measured RSSI reading variance at unknown location.
83       */
84      public static final boolean DEFAULT_PROPAGATE_RADIO_SOURCE_POSITION_COVARIANCE = true;
85  
86      /**
87       * Default type to be used when none is provided.
88       */
89      public static final NonLinearFingerprintPositionEstimatorType DEFAULT_TYPE =
90              NonLinearFingerprintPositionEstimatorType.THIRD_ORDER;
91  
92      /**
93       * Small value to be used as the minimum allowed RSSI standard deviations. A value
94       * larger than this must be provided to allow convergence to a solution
95       */
96      public static final double TINY_RSSI_STD = 1e-12;
97  
98      /**
99       * Initial position to start the solving algorithm.
100      * This should be a value close to the expected solution.
101      * If no value is provided, the average position among all selected nearest
102      * located fingerprints will be used.
103      */
104     private P mInitialPosition;
105 
106     /**
107      * RSSI standard deviation fallback value to use when none can be
108      * determined from provided readings. This fallback value is only used if
109      * no variance is propagated or the resulting value is too small to allow
110      * convergence to a solution.
111      */
112     private double mFallbackRssiStandardDeviation =
113             FALLBACK_RSSI_STANDARD_DEVIATION;
114 
115     /**
116      * Indicates whether measured RSSI standard deviation of closest fingerprint must
117      * be propagated into measured RSSI reading variance at unknown location.
118      */
119     private boolean mPropagateFingerprintRssiStandardDeviation =
120             DEFAULT_PROPAGATE_FINGERPRINT_RSSI_STANDARD_DEVIATION;
121 
122     /**
123      * Indicates whether path-loss exponent standard deviation of radio source must
124      * be propagated into measured RSSI reading variance at unknown location.
125      */
126     private boolean mPropagatePathlossExponentStandardDeviation =
127             DEFAULT_PROPAGATE_PATHLOSS_EXPONENT_STANDARD_DEVIATION;
128 
129     /**
130      * Indicates whether covariance of closest fingerprint position must be
131      * propagated into measured RSSI reading variance at unknown location.
132      */
133     private boolean mPropagateFingerprintPositionCovariance =
134             DEFAULT_PROPAGATE_FINGERPRINT_POSITION_COVARIANCE;
135 
136     /**
137      * Indicates whether covariance of radio source position must be propagated
138      * into measured RSSI reading variance at unknown location.
139      */
140     private boolean mPropagateRadioSourcePositionCovariance =
141             DEFAULT_PROPAGATE_RADIO_SOURCE_POSITION_COVARIANCE;
142 
143     /**
144      * Levenberg-Marquardt fitter to find a non-linear solution.
145      */
146     private final LevenbergMarquardtMultiDimensionFitter mFitter = new LevenbergMarquardtMultiDimensionFitter();
147 
148     /**
149      * Estimated covariance matrix for estimated position.
150      */
151     private Matrix mCovariance;
152 
153     /**
154      * Estimated chi square value.
155      */
156     private double mChiSq;
157 
158     /**
159      * Constructor.
160      */
161     protected NonLinearFingerprintPositionEstimator() {
162     }
163 
164     /**
165      * Constructor.
166      *
167      * @param listener listener in charge of handling events.
168      */
169     protected NonLinearFingerprintPositionEstimator(
170             final FingerprintPositionEstimatorListener<P> listener) {
171         super(listener);
172     }
173 
174     /**
175      * Constructor.
176      *
177      * @param locatedFingerprints located fingerprints containing RSSI readings.
178      * @param fingerprint         fingerprint containing readings at an unknown location
179      *                            for provided located fingerprints.
180      * @param sources             located radio sources.
181      * @throws IllegalArgumentException if provided non located fingerprint is null,
182      *                                  located fingerprints value is null or there are not enough fingerprints or
183      *                                  readings within provided fingerprints (for 2D position estimation at least 2
184      *                                  located total readings are required among all fingerprints, for example 2
185      *                                  readings are required in a single fingerprint, or at least 2 fingerprints at
186      *                                  different locations containing a single reading are required. For 3D position
187      *                                  estimation 3 located total readings are required among all fingerprints).
188      */
189     protected NonLinearFingerprintPositionEstimator(
190             final List<? extends RssiFingerprintLocated<? extends RadioSource,
191                     ? extends RssiReading<? extends RadioSource>, P>> locatedFingerprints,
192             final RssiFingerprint<? extends RadioSource,
193                     ? extends RssiReading<? extends RadioSource>> fingerprint,
194             final List<? extends RadioSourceLocated<P>> sources) {
195         super(locatedFingerprints, fingerprint, sources);
196     }
197 
198     /**
199      * Constructor.
200      *
201      * @param locatedFingerprints located fingerprints containing RSSI readings.
202      * @param fingerprint         fingerprint containing readings at an unknown location
203      *                            for provided located fingerprints.
204      * @param sources             located radio sources.
205      * @param listener            listener in charge of handling events.
206      * @throws IllegalArgumentException if provided non located fingerprint is null,
207      *                                  located fingerprints value is null or there are not enough fingerprints or
208      *                                  readings within provided fingerprints (for 2D position estimation at least 2
209      *                                  located total readings are required among all fingerprints, for example 2
210      *                                  readings are required in a single fingerprint, or at least 2 fingerprints at
211      *                                  different locations containing a single reading are required. For 3D position
212      *                                  estimation 3 located total readings are required among all fingerprints).
213      */
214     protected NonLinearFingerprintPositionEstimator(
215             final List<? extends RssiFingerprintLocated<? extends RadioSource,
216                     ? extends RssiReading<? extends RadioSource>, P>> locatedFingerprints,
217             final RssiFingerprint<? extends RadioSource,
218                     ? extends RssiReading<? extends RadioSource>> fingerprint,
219             final List<? extends RadioSourceLocated<P>> sources,
220             final FingerprintPositionEstimatorListener<P> listener) {
221         super(locatedFingerprints, fingerprint, sources, listener);
222     }
223 
224     /**
225      * Constructor.
226      *
227      * @param locatedFingerprints located fingerprints containing RSSI readings.
228      * @param fingerprint         fingerprint containing readings at an unknown location
229      *                            for provided located fingerprints.
230      * @param sources             located radio sources.
231      * @param initialPosition     initial position to start the solving algorithm or null.
232      * @throws IllegalArgumentException if provided non located fingerprint is null,
233      *                                  located fingerprints value is null or there are not enough fingerprints or
234      *                                  readings within provided fingerprints (for 2D position estimation at least 2
235      *                                  located total readings are required among all fingerprints, for example 2
236      *                                  readings are required in a single fingerprint, or at least 2 fingerprints at
237      *                                  different locations containing a single reading are required. For 3D position
238      *                                  estimation 3 located total readings are required among all fingerprints).
239      */
240     protected NonLinearFingerprintPositionEstimator(
241             final List<? extends RssiFingerprintLocated<? extends RadioSource,
242                     ? extends RssiReading<? extends RadioSource>, P>> locatedFingerprints,
243             final RssiFingerprint<? extends RadioSource,
244                     ? extends RssiReading<? extends RadioSource>> fingerprint,
245             final List<? extends RadioSourceLocated<P>> sources, P initialPosition) {
246         super(locatedFingerprints, fingerprint, sources);
247         mInitialPosition = initialPosition;
248     }
249 
250     /**
251      * Constructor.
252      *
253      * @param locatedFingerprints located fingerprints containing RSSI readings.
254      * @param fingerprint         fingerprint containing readings at an unknown location
255      *                            for provided located fingerprints.
256      * @param sources             located radio sources.
257      * @param initialPosition     initial position to start the solving algorithm or null.
258      * @param listener            listener in charge of handling events.
259      * @throws IllegalArgumentException if provided non located fingerprint is null,
260      *                                  located fingerprints value is null or there are not enough fingerprints or
261      *                                  readings within provided fingerprints (for 2D position estimation at least 2
262      *                                  located total readings are required among all fingerprints, for example 2
263      *                                  readings are required in a single fingerprint, or at least 2 fingerprints at
264      *                                  different locations containing a single reading are required. For 3D position
265      *                                  estimation 3 located total readings are required among all fingerprints).
266      */
267     protected NonLinearFingerprintPositionEstimator(
268             final List<? extends RssiFingerprintLocated<? extends RadioSource,
269                     ? extends RssiReading<? extends RadioSource>, P>> locatedFingerprints,
270             final RssiFingerprint<? extends RadioSource,
271                     ? extends RssiReading<? extends RadioSource>> fingerprint,
272             final List<? extends RadioSourceLocated<P>> sources, P initialPosition,
273             final FingerprintPositionEstimatorListener<P> listener) {
274         super(locatedFingerprints, fingerprint, sources, listener);
275         mInitialPosition = initialPosition;
276     }
277 
278     /**
279      * Gets initial position to start the solving algorithm.
280      * This should be a value close to the expected solution.
281      * If no value is provided, the average position among all selected nearest
282      * located fingerprints will be used.
283      *
284      * @return initial position to start the solving algorithm or null.
285      */
286     public P getInitialPosition() {
287         return mInitialPosition;
288     }
289 
290     /**
291      * Sets initial position to start the solving algorithm.
292      * This should be a value close to the expected solution.
293      * If no value is provided, the average position among all selected nearest
294      * located fingerprints will be used.
295      *
296      * @param initialPosition initial position to start the solving algorithm or null.
297      * @throws LockedException if estimator is locked.
298      */
299     public void setInitialPosition(final P initialPosition) throws LockedException {
300         if (isLocked()) {
301             throw new LockedException();
302         }
303 
304         mInitialPosition = initialPosition;
305     }
306 
307     /**
308      * Gets RSSI standard deviation fallback value to use when none can be
309      * determined from provided readings.
310      *
311      * @return RSSI standard deviation fallback.
312      */
313     public double getFallbackRssiStandardDeviation() {
314         return mFallbackRssiStandardDeviation;
315     }
316 
317     /**
318      * Sets RSSI standard deviation fallback value to use when none can be
319      * determined from provided readings.
320      *
321      * @param fallbackRssiStandardDeviation RSSI standard deviation fallback
322      * @throws LockedException          if estimator is locked.
323      * @throws IllegalArgumentException if provided value is smaller than
324      *                                  {@link #TINY_RSSI_STD}.
325      */
326     public void setFallbackRssiStandardDeviation(
327             final double fallbackRssiStandardDeviation) throws LockedException {
328         if (isLocked()) {
329             throw new LockedException();
330         }
331         if (fallbackRssiStandardDeviation < TINY_RSSI_STD) {
332             throw new IllegalArgumentException();
333         }
334         mFallbackRssiStandardDeviation = fallbackRssiStandardDeviation;
335     }
336 
337     /**
338      * Indicates whether measured RSSI standard deviation of closest fingerprint must be
339      * propagated into measured RSSI reading variance at unknown location.
340      *
341      * @return true to propagate RSSI standard deviation of closest fingerprint,
342      * false otherwise.
343      */
344     public boolean isFingerprintRssiStandardDeviationPropagated() {
345         return mPropagateFingerprintRssiStandardDeviation;
346     }
347 
348     /**
349      * Specifies whether measured RSSI standard deviation of closest fingerprint must be
350      * propagated into measured RSSI reading variance at unknown location.
351      *
352      * @param propagateFingerprintRssiStandardDeviation true to propagate RSSI standard
353      *                                                  deviation of closest fingerprint,
354      *                                                  false otherwise.
355      * @throws LockedException if estimator is locked.
356      */
357     public void setFingerprintRssiStandardDeviationPropagated(
358             final boolean propagateFingerprintRssiStandardDeviation) throws LockedException {
359         if (isLocked()) {
360             throw new LockedException();
361         }
362         mPropagateFingerprintRssiStandardDeviation =
363                 propagateFingerprintRssiStandardDeviation;
364     }
365 
366     /**
367      * Indicates whether path-loss exponent standard deviation of radio source must be
368      * propagated into measured RSSI reading variance at unknown location.
369      *
370      * @return true to propagate  path-loss exponent standard deviation of radio source,
371      * false otherwise.
372      */
373     public boolean isPathlossExponentStandardDeviationPropagated() {
374         return mPropagatePathlossExponentStandardDeviation;
375     }
376 
377     /**
378      * Specifies whether path-loss exponent standard deviation of radio source must be
379      * propagated into measured RSSI reading variance at unknown location.
380      *
381      * @param propagatePathlossExponentStandardDeviation true to propagate path-loss
382      *                                                   exponent standard deviation of
383      *                                                   radio source, false otherwise.
384      * @throws LockedException if estimator is locked.
385      */
386     public void setPathlossExponentStandardDeviationPropagated(
387             final boolean propagatePathlossExponentStandardDeviation) throws LockedException {
388         if (isLocked()) {
389             throw new LockedException();
390         }
391         mPropagatePathlossExponentStandardDeviation =
392                 propagatePathlossExponentStandardDeviation;
393     }
394 
395     /**
396      * Indicates whether covariance of closest fingerprint position must be propagated
397      * into measured RSSI reading variance at unknown location.
398      *
399      * @return true to propagate fingerprint position covariance, false otherwise.
400      */
401     public boolean isFingerprintPositionCovariancePropagated() {
402         return mPropagateFingerprintPositionCovariance;
403     }
404 
405     /**
406      * Specifies whether covariance of closest fingerprint position must be propagated
407      * into measured RSSI reading variance at unknown location.
408      *
409      * @param propagateFingerprintPositionCovariance true to propagate fingerprint
410      *                                               position covariance, false otherwise.
411      * @throws LockedException if estimator is locked.
412      */
413     public void setFingerprintPositionCovariancePropagated(
414             final boolean propagateFingerprintPositionCovariance) throws LockedException {
415         if (isLocked()) {
416             throw new LockedException();
417         }
418         mPropagateFingerprintPositionCovariance =
419                 propagateFingerprintPositionCovariance;
420     }
421 
422     /**
423      * Indicates whether covariance of radio source position must be propagated into
424      * measured RSSI reading variance at unknown location.
425      *
426      * @return true to propagate radio source position covariance, false otherwise.
427      */
428     public boolean isRadioSourcePositionCovariancePropagated() {
429         return mPropagateRadioSourcePositionCovariance;
430     }
431 
432     /**
433      * Specifies whether covariance of radio source position must be propagated into
434      * measured RSSI reading variance at unknown location.
435      *
436      * @param propagateRadioSourcePositionCovariance true to propagate radio source
437      *                                               position covariance, false otherwise.
438      * @throws LockedException if estimator is locked.
439      */
440     public void setRadioSourcePositionCovariancePropagated(
441             final boolean propagateRadioSourcePositionCovariance) throws LockedException {
442         if (isLocked()) {
443             throw new LockedException();
444         }
445         mPropagateRadioSourcePositionCovariance =
446                 propagateRadioSourcePositionCovariance;
447     }
448 
449     /**
450      * Gets estimated covariance matrix for estimated position.
451      *
452      * @return estimated covariance matrix for estimated position.
453      */
454     public Matrix getCovariance() {
455         return mCovariance;
456     }
457 
458     /**
459      * Gets estimated chi square value.
460      *
461      * @return estimated chi square value.
462      */
463     public double getChiSq() {
464         return mChiSq;
465     }
466 
467     /**
468      * Estimates position based on provided located radio sources and readings of such radio sources at
469      * an unknown location.
470      *
471      * @throws LockedException                if estimator is locked.
472      * @throws NotReadyException              if estimator is not ready.
473      * @throws FingerprintEstimationException if estimation fails for some other reason.
474      */
475     @Override
476     @SuppressWarnings("Duplicates")
477     public void estimate() throws LockedException, NotReadyException,
478             FingerprintEstimationException {
479 
480         if (!isReady()) {
481             throw new NotReadyException();
482         }
483         if (isLocked()) {
484             throw new LockedException();
485         }
486 
487         try {
488             locked = true;
489 
490             if (listener != null) {
491                 listener.onEstimateStart(this);
492             }
493 
494             RadioSourceNoMeanKNearestFinder<P, RadioSource> noMeanFinder = null;
495             RadioSourceKNearestFinder<P, RadioSource> finder = null;
496             if (useNoMeanNearestFingerprintFinder) {
497                 //noinspection unchecked
498                 noMeanFinder = new RadioSourceNoMeanKNearestFinder<>(
499                         (Collection<RssiFingerprintLocated<RadioSource,
500                                 RssiReading<RadioSource>, P>>) locatedFingerprints);
501             } else {
502                 //noinspection unchecked
503                 finder = new RadioSourceKNearestFinder<>(
504                         (Collection<RssiFingerprintLocated<RadioSource,
505                                 RssiReading<RadioSource>, P>>) locatedFingerprints);
506             }
507 
508             estimatedPositionCoordinates = null;
509             mCovariance = null;
510             nearestFingerprints = null;
511 
512             final int max = maxNearestFingerprints < 0 ?
513                     locatedFingerprints.size() :
514                     Math.min(maxNearestFingerprints, locatedFingerprints.size());
515             for (int k = minNearestFingerprints; k <= max; k++) {
516                 if (noMeanFinder != null) {
517                     //noinspection unchecked
518                     nearestFingerprints = noMeanFinder.findKNearestTo(
519                             (RssiFingerprint<RadioSource, RssiReading<RadioSource>>) fingerprint, k);
520                 } else {
521                     //noinspection unchecked
522                     nearestFingerprints = finder.findKNearestTo(
523                             (RssiFingerprint<RadioSource, RssiReading<RadioSource>>) fingerprint, k);
524                 }
525 
526                 // Demonstration in 2D:
527                 // --------------------
528                 // Taylor series expansion can be expressed as:
529                 // f(x) = f(a) + 1/1!*f'(a)*(x - a) + 1/2!*f''(a)*(x - a)^2 + ...
530 
531                 // where f'(x) is the derivative of f respect x, which can also be expressed as:
532                 // f'(x) = diff(f(x))/diff(x)
533 
534                 // and f'(a) is the derivative of f respect x evaluated at "a", which can be expressed
535                 // as f'(a) = diff(f(a))/diff(x)
536 
537                 // consequently f''(a) is the second derivative respect x evaluated at "a", which can
538                 // be expressed as:
539                 // f''(x) = diff(f(x))/diff(x^2)
540 
541                 // and:
542                 // f''(a) = diff(f(a))/diff(x^2)
543 
544                 // Received power expressed in dBm is:
545                 // k = (c/(4*pi*f))
546                 // Pr = Pte*k^n / d^n
547 
548                 // where c is the speed of light, pi is 3.14159..., f is the frequency of the radio source,
549                 // Pte is the equivalent transmitted power by the radio source, n is the path-loss exponent
550                 // (typically 2.0), and d is the distance from a point to the location of the radio source.
551 
552                 // Hence:
553                 // Pr(dBm) = 10*log(Pte*k^n/d^n) = 10*n*log(k) + 10*log(Pte) - 10*n*log(d) =
554                 //           10*n*log(k) + 10*log(Pte) - 5*n*log(d^2)
555 
556                 // The former 2 terms are constant, and only the last term depends on distance
557 
558                 // Hence, assuming the constant K = 10*n*log(k) + Pte(dBm), where Pte(dBm) = 10*log(Pte),
559                 // assuming that transmitted power by the radio source Pte is known (so that K is also known),
560                 // and assuming that the location of the radio source is known, and it is located at pa = (xa, ya)
561                 // so that d^2 = (x - xa)^2 + (y - ya)^2 then the received power at an unknown point pi = (xi, yi) is:
562 
563                 // Pr(pi) = Pr(xi,yi) = K - 5*n*log(d^2) = K - 5*n*log((xi - xa)^2 + (yi - ya)^2)
564 
565                 // Suppose that received power at point p1=(x1,y1) is known on a located fingerprint
566                 // containing readings Pr(p1).
567 
568                 // Then, for an unknown point pi=(xi,yi) close to fingerprint 1 located at p1 where we
569                 // have measured received power Pr(pi), we can get the following second-order Taylor
570                 // approximation:
571 
572                 // Pr(pi) ~ Pr(p1) + JPr(p1)*(pi - p1) + 1/2*(pi - p1)^T*HPr(p1)*(pi - p1) + ...
573 
574                 // where JPr(p1) is the Jacobian of Pr evaluated at p1. Since Pr is a multivariate function
575                 // with scalar result, the Jacobian has size 1x2 and is equal to the gradient.
576                 // HPr(p1) is the Hessian matrix evaluated at p1, which is a symmetric matrix of size 2x2,
577                 // and (pi-p1)^T is the transposed vector of (pi-p1)
578 
579                 // Hence, the Jacobian at any point p=(x,y) is equal to:
580                 // JPr(p = (x,y)) = [diff(Pr(x,y))/diff(x)   diff(Pr(x,y))/diff(y)]
581 
582                 // And the Hessian matrix is equal to
583                 // HPr(p = (x,y)) =  [diff(Pr(x,y))/diff(x^2)    diff(Pr(x,y))/diff(x*y)]
584                 //                   [diff(Pr(x,y))/diff(x*y)    diff(Pr(x,y))/diff(y^2)]
585 
586                 // where the first order derivatives of Pr(p = (x,y)) are:
587                 // diff(Pr(x,y))/diff(x) = -5*n/(ln(10)*((x - xa)^2 + (y - ya)^2)*2*(x - xa)
588                 // diff(Pr(x,y))/diff(x) = -10*n*(x - xa)/(ln(10)*((x - xa)^2 + (y - ya)^2))
589 
590                 // diff(Pr(x,y))/diff(y) = -5*n/(ln(10)*((x - xa)^2 + (y - ya)^2)*2*(y - ya)
591                 // diff(Pr(x,y))/diff(y) = -10*n*(y - ya)/(ln(10)*((x - xa)^2 + (y - ya)^2))
592 
593                 // If we evaluate first order derivatives at p1 = (x1,y1), we get:
594                 // diff(Pr(p1))/diff(x) = -10*n*(x1 - xa)/(ln(10)*((x1 - xa)^2 + (y1 - ya)^2))
595                 // diff(Pr(p1))/diff(y) = -10*n*(y1 - ya)/(ln(10)*((x1 - xa)^2 + (y1 - ya)^2))
596 
597                 // where square distance from fingerprint 1 to radio source a can be expressed as:
598                 // d1a^2 = (x1 - xa)^2 + (y1 - ya)^2
599 
600                 // where both the fingerprint and radio source positions are known, and hence d1a is known.
601 
602                 // Then first order derivatives can be expressed as:
603                 // diff(Pr(p1))/diff(x) = -10*n*(x1 - xa)/(ln(10)*d1a^2)
604                 // diff(Pr(p1))/diff(y) = -10*n*(y1 - ya)/(ln(10)*d1a^2)
605 
606                 // To obtain second order derivatives we take into account that:
607                 // (f(x)/g(x))' = (f'(x)*g(x) - f(x)*g'(x))/g(x)^2
608 
609                 // hence, second order derivatives of Pr(p = (x,y)) are:
610                 // diff(Pr(x,y))/diff(x^2) = -10*n/ln(10)*(1*((x - xa)^2 + (y - ya)^2) - (x - xa)*2*(x - xa)) / ((x - xa)^2 + (y - ya)^2)^2
611                 // diff(Pr(x,y))/diff(x^2) = -10*n*((y - ya)^2 - (x - xa)^2)/(ln(10)*((x - xa)^2 + (y - ya)^2)^2)
612 
613                 // diff(Pr(x,y))/diff(y^2) = -10*n/ln(10)*(1*((x - xa)^2 + (y - ya)^2) - (y - ya)*2*(y - ya)) / ((x - xa)^2 + (y - ya)^2)^2
614                 // diff(Pr(x,y))/diff(y^2) = -10*n*((x - xa)^2 - (y - ya)^2)/(ln(10)*((x - xa)^2 + (y - ya)^2)^2)
615 
616                 // diff(Pr(x,y))/diff(x*y) = -10*n/ln(10)*(0*((x - xa)^2 + (y - ya)^2) - (x - xa)*2*(y - ya))/((x - xa)^2 + (y - ya)^2)^2
617                 // diff(Pr(x,y))/diff(x*y) = 20*n*((x - xa)*(y - ya))/(ln(10)*((x - xa)^2 + (y - ya)^2)^2)
618 
619                 // If we evaluate second order derivatives at p1 = (x1,y1), we get:
620                 // diff(Pr(p1))/diff(x^2) = -10*n*((y1 - ya)^2 - (x1 - xa)^2))/(ln(10)*((x1 - xa)^2 + (y1 - ya)^2)^2)
621                 // diff(Pr(p1))/diff(y^2) = -10*n*((x1 - xa)^2 - (y1 - ya)^2)/(ln(10)*((x1 - xa)^2 + (y1 - ya)^2)^2)
622                 // diff(Pr(p1))/diff(x*y) = 20*n*(x1 - xa)*(y1 - ya)/(ln(10)*((x1 - xa)^2 + (y1 - ya)^2)^2)
623 
624                 // and expressing the second order derivatives in terms of distance between
625                 // fingerprint 1 and radio source a d1a, we get:
626                 // diff(Pr(p1))/diff(x^2) = -10*n*((y1 - ya)^2 - (x1 - xa)^2))/(ln(10)*d1a^4)
627                 // diff(Pr(p1))/diff(y^2) = -10*n*((x1 - xa)^2 - (y1 - ya)^2)/(ln(10)*d1a^4)
628                 // diff(Pr(p1))/diff(x*y) = 20*n*(x1 - xa)*(y1 - ya)/(ln(10)*d1a^4)
629 
630                 // Hence, second order Taylor expansion can be expressed as:
631                 // Pr(pi) = Pr(p1) + diff(Pr(p1))/diff(x)*(xi - x1) + diff(Pr(p1))/diff(y)*(yi - y1) +
632                 // 1/2*diff(Pr(p1))/diff(x^2)*(xi - x1)^2 + 1/2*diff(Pr(p1))/diff(y^2)*(yi - y1)^2 +
633                 // diff(Pr(p1))/diff(x*y)*(xi - x1)*(yi - y1)
634 
635                 // Pr(pi) = Pr(p1) - 10*n*(x1 - xa)/(ln(10)*d1a^2)*(xi - x1) -10*n*(y1 - ya)/(ln(10)*d1a^2)*(yi - y1)
636                 // - 5*n*((y1 - ya)^2 - (x1 - xa)^2)/(ln(10)*d1a^4)*(xi - x1)^2
637                 // - 5*n*((x1 - xa)^2 - (y1 - ya)^2)/(ln(10)*d1a^4)*(yi - y1)^2 +
638                 // 20*n*(x1 - xa)*(y1 - ya)/(ln(10)*d1a^4))*(xi - x1)*(yi - y1)
639 
640                 // The equation above can be solved using a non-linear fitter such as Levenberg-Marquardt
641 
642 
643                 // Demonstration in 3D:
644                 // --------------------
645                 // Taylor series expansion can be expressed as:
646                 // f(x) = f(a) + 1/1!*f'(a)*(x - a) + 1/2!*f''(a)*(x - a)^2 + ...
647 
648                 // where f'(x) is the derivative of f respect x, which can also be expressed as:
649                 // f'(x) = diff(f(x))/diff(x)
650 
651                 // and f'(a) is the derivative of f respect x evaluated at "a", which can be expressed
652                 // as f'(a) = diff(f(a))/diff(x)
653 
654                 // consequently f''(a) is the second derivative respect x evaluated at "a", which can
655                 // be expressed as:
656                 // f''(x) = diff(f(x))/diff(x^2)
657 
658                 // and:
659                 // f''(a) = diff(f(a))/diff(x^2)
660 
661                 // Received power expressed in dBm is:
662                 // k = (c/(4*pi*f))
663                 // Pr = Pte*k^n / d^n
664 
665                 // where c is the speed of light, pi is 3.14159..., f is the frequency of the radio source,
666                 // Pte is the equivalent transmitted power by the radio source, n is the path-loss exponent
667                 // (typically 2.0), and d is the distance from a point to the location of the radio source.
668 
669                 // Hence:
670                 // Pr(dBm) = 10*log(Pte*k^n/d^n) = 10*n*log(k) + 10*log(Pte) - 10*n*log(d) =
671                 //          10*n*log(k) + 10*log(Pte) - 5*n*log(d^2)
672 
673                 // The former 2 terms are constant, and only the last term depends on distance
674 
675                 // Hence, assuming the constant K = 10*n*log(k) + Pte(dBm), where Pte(dBm) = 10*log(Pte),
676                 // assuming that transmitted power by the radio source Pte is known (so that K is also known),
677                 // and assuming that the location of the radio source is known, and it is located at pa = (xa, ya, za)
678                 // so that d^2 = (x - xa)^2 + (y - ya)^2 + (z - za)^2 then the received power at an unknown point
679                 // pi = (xi, yi, zi) is:
680 
681                 // Pr(pi) = Pr(xi,yi,zi) = K - 5*n*log(d^2) = K - 5*n*log((xi - xa)^2 + (yi - ya)^2 + (zi - za)^2)
682 
683                 // Suppose that received power at point p1=(x1,y1,z1) is known on a located fingerprint
684                 // containing readings Pr(p1).
685 
686                 // Then, for an unknown point pi=(xi,yi,zi) close to fingerprint 1 located at p1 where we
687                 // have measured received power Pr(pi), we can get the following second-order Taylor
688                 // approximation:
689 
690                 // Pr(pi) ~ Pr(p1) + JPtr(p1)*(pi - p1) + 1/2*(pi - p1)^T*HPr(p1)*(pi - p1) + ...
691 
692                 // where JPr(p1) is the Jacobian of Pr evaluated at p1. Since Pr is a multivariate function
693                 // with scalar result, the Jacobian has size 1x3 and is equal to the gradient.
694                 // HPtr(p1) is the Hessian matrix evaluated at p1, which is a symmetric matrix of size 3x3,
695                 // and (pi-p1)^T is the transposed vector of (pi-p1)
696 
697                 // Hence, the Jacobian at any point p=(x,y,z) is equal to:
698                 // JPr(p = (x,y,z)) = [diff(Pr(x,y,z))/diff(x)     diff(Pr(x,y,z))/diff(y)     diff(Pr(x,y,z))/diff(z)]
699 
700                 // And the Hessian matrix is equal to
701                 // HPr(p = (x,y,z)) = [diff(Pr(x,y,z))/diff(x^2)    diff(Pr(x,y,z))/diff(x*y)     diff(Pr(x,y,z))/diff(x*z)]
702                 //                    [diff(Pr(x,y,z))/diff(x*y)    diff(Pr(x,y,z))/diff(y^2)     diff(Pr(x,y,z))/diff(y*z)]
703                 //                    [diff(Pr(x,y,z))/diff(x*z)    diff(Pr(x,y,z))/diff(y*z)     diff(Pr(x,y,z))/diff(z^2)]
704 
705                 // where the first order derivatives of Pr(p = (x,y)) are:
706                 // diff(Pr(x,y,z))/diff(x) = -5*n/(ln(10)*((x - xa)^2 + (y - ya)^2 + (z - za)^2)*2*(x - xa)
707                 // diff(Pr(x,y,z))/diff(x) = -10*n*(x - xa)/(ln(10)*((x - xa)^2 + (y - ya)^2 + (z - za)^2))
708 
709                 // diff(Pr(x,y,z))/diff(y) = -5*n/(ln(10)*((x - xa)^2 + (y - ya)^2 + (z - za)^2)*2*(y - ya)
710                 // diff(Pr(x,y,z))/diff(y) = -10*n*(y - ya)/(ln(10)*((x - xa)^2 + (y - ya)^2 + (z - za)^2))
711 
712                 // diff(Pr(x,y,z))/diff(z) = -5*n/(ln(10)*((x - xa)^2 + (y - ya)^2 + (z - za)^2)*2*(z - za)
713                 // diff(Pr(x,y,z))/diff(z) = -10*n*(z - za)/(ln(10)*((x - xa)^2 + (y - ya)^2 + (z - za)^2))
714 
715                 // If we evaluate derivatives at p1 = (x1,y1,z1), we get:
716                 // diff(Pr(p1))/diff(x) = -10*n*(x1 - xa)/(ln(10)*((x1 - xa)^2 + (y1 - ya)^2 + (z1 - za)^2))
717                 // diff(Pr(p1))/diff(y) = -10*n*(y1 - ya)/(ln(10)*((x1 - xa)^2 + (y1 - ya)^2 + (z1 - za)^2))
718                 // diff(Pr(p1))/diff(z) = -10*n*(z1 - za)/(ln(10)*((x1 - xa)^2 + (y1 - ya)^2 + (z1 - za)^2))
719 
720                 // where square distance from fingerprint 1 to radio source a can be expressed as:
721                 // d1a^2 = (x1 - xa)^2 + (y1 - ya)^2 + (z1 - za)^2
722 
723                 // where both the fingerprint and radio source positions are known, and hence d1a is known.
724 
725                 // Then first order derivatives can be expressed as:
726                 // diff(Pr(p1))/diff(x) = -10*n*(x1 - xa)/(ln(10)*d1a^2)
727                 // diff(Pr(p1))/diff(y) = -10*n*(y1 - ya)/(ln(10)*d1a^2)
728                 // diff(Pr(p1))/diff(z) = -10*n*(z1 - za)/(ln(10)*d1a^2)
729 
730                 // To obtain second order derivatives we take into account that:
731                 // (f(x)/g(x))' = (f'(x)*g(x) - f(x)*g'(x))/g(x)^2
732 
733                 // hence, second order derivatives of Pr(p = (x,y,z)) are:
734                 // diff(Pr(x,y,z))/diff(x^2) = -10*n/ln(10)*(1*((x - xa)^2 + (y - ya)^2 + (z - za)^2) - (x - xa)*2*(x - xa))/((x - xa)^2 + (y - ya)^2 + (z - za)^2)^2
735                 // diff(Pr(x,y,z))/diff(x^2) = -10*n*((y - ya)^2 + (z - za)^2 - (x - xa)^2)/(ln(10)*((x - xa)^2 + (y - ya)^2 + (z - za)^2)^2)
736 
737                 // diff(Pr(x,y,z))/diff(y^2) = -10*n/ln(10)*(1*((x - xa)^2 + (y - ya)^2 + (z - za)^2) - (y - ya)*2*(y - ya))/((x - xa)^2 + (y - ya)^2 + (z - za)^2)^2
738                 // diff(Pr(x,y,z))/diff(y^2) = -10*n*((x - xa)^2 - (y - ya)^2 + (z - za)^2)/(ln(10)*((x - xa)^2 + (y - ya)^2 + (z - za)^2)^2)
739 
740                 // diff(Pr(x,y,z))/diff(z^2) = -10*n/ln(10)*(1*((x - xa)^2 + (y - ya)^2 + (z - za)^2) - (z - za)*2*(z - za))/((x - xa)^2 + (y - ya)^2 + (z - za)^2)^2
741                 // diff(Pr(x,y,z))/diff(z^2) = -10*n*((x - xa)^2 + (y - ya)^2 - (z - za)^2)/(ln(10)*((x - xa)^2 + (y - ya)^2 + (z - za)^2)^2)
742 
743                 // diff(Pr(x,y,z))/diff(x*y) = -10*n/ln(10)*(0*((x - xa)^2 + (y - ya)^2 + (z - za)^2) - (x - xa)*2*(y - ya))/((x - xa)^2 + (y - ya)^2 + (z - za)^2)^2
744                 // diff(Pr(x,y,z))/diff(x*y) = 20*n*(x - xa)*(y - ya)/(ln(10)*((x - xa)^2 + (y - ya)^2 + (z - za)^2)^2)
745 
746                 // diff(Pr(x,y,z))/diff(x*z) = -10*n/ln(10)*(0*((x - xa)^2 + (y - ya)^2 + (z - za)^2) - (x - xa)*2*(z - za))/((x - xa)^2 + (y - ya)^2 + (z - za)^2)^2
747                 // diff(Pr(x,y,z))/diff(x*z) = 20*n*(x - xa)*(z - za)/(ln(10)*((x - xa)^2 + (y - ya)^2 + (z - za)^2)^2)
748 
749                 // diff(Pr(x,y,z))/diff(y*z) = -10*n/ln(10)*(0*((x - xa)^2 + (y - ya)^2 + (z - za)^2) - (y - ya)*2*(z - za))/((x - xa)^2 + (y - ya)^2 + (z - za)^2)^2
750                 // diff(Pr(x,y,z))/diff(y*z) = 20*n*(y - ya)*(z - za)/(ln(10)*((x - xa)^2 + (y - ya)^2 + (z - za)^2)^2)
751 
752                 // If we evaluate second order derivatives at p1 = (x1,y1,z1), we get:
753                 // diff(Pr(p1))/diff(x^2) = -10*n*((y1 - ya)^2 + (z1 - za)^2 - (x1 - xa)^2)/(ln(10)*((x1 - xa)^2 + (y1 - ya)^2 + (z1 - za)^2)^2)
754                 // diff(Pr(p1))/diff(y^2) = -10*n*((x1 - xa)^2 - (y1 - ya)^2 + (z1 - za)^2)/(ln(10)*((x1 - xa)^2 + (y1 - ya)^2 + (z1 - za)^2)^2)
755                 // diff(Pr(p1))/diff(z^2) = -10*n*((x1 - xa)^2 + (y1 - ya)^2 - (z1 - za)^2)/(ln(10)*((x1 - xa)^2 + (y1 - ya)^2 + (z1 - za)^2)^2)
756                 // diff(Pr(p1))/diff(x*y) = 20*n*(x1 - xa)*(y1 - ya)/(ln(10)*((x1 - xa)^2 + (y1 - ya)^2 + (z1 - za)^2)^2)
757                 // diff(Pr(p1))/diff(x*z) = 20*n*(x1 - xa)*(z1 - za)/(ln(10)*((x1 - xa)^2 + (y1 - ya)^2 + (z1 - za)^2)^2)
758                 // diff(Pr(p1))/diff(y*z) = 20*n*(y1 - ya)*(z1 - za)/(ln(10)*((x1 - xa)^2 + (y1 - ya)^2 + (z1 - za)^2)^2)
759 
760                 // and expressing the second order derivatives in terms of distance between
761                 // fingerprint 1 and radio source a d1a, we get:
762                 // diff(Pr(p1))/diff(x^2) = -10*n*((y1 - ya)^2 + (z1 - za)^2 - (x1 - xa)^2)/(ln(10)*d1a^4)
763                 // diff(Pr(p1))/diff(y^2) = -10*n*((x1 - xa)^2 - (y1 - ya)^2 + (z1 - za)^2)/(ln(10)*d1a^4)
764                 // diff(Pr(p1))/diff(z^2) = -10*n*((x1 - xa)^2 + (y1 - ya)^2 - (z1 - za)^2)/(ln(10)*d1a^4)
765                 // diff(Pr(p1))/diff(x*y) = 20*n*(x1 - xa)*(y1 - ya)/(ln(10)*d1a^4)
766                 // diff(Pr(p1))/diff(x*z) = 20*n*(x1 - xa)*(z1 - za)/(ln(10)*d1a^4)
767                 // diff(Pr(p1))/diff(y*z) = 20*n*(y1 - ya)*(z1 - za)/(ln(10)*d1a^4)
768 
769                 // Hence, second order Taylor expansion can be expressed as:
770                 // Pr(pi) = Pr(p1) + diff(Pr(p1))/diff(x)*(x - x1) +
771                 //       diff(Pr(p1))/diff(y)*(y - y1) +
772                 //       diff(Pr(p1))/diff(z)*(z - z1) +
773                 //       1/2*diff(Pr(p1))/diff(x^2)*(x - x1)^2 +
774                 //       1/2*diff(Pr(p1))/diff(y^2)*(y - y1)^2 +
775                 //	     1/2*diff(Pr(p1))/diff(z^2)*(z - z1)^2 +
776                 //	     diff(Pr(p1))/diff(x*y)*(x - x1)*(y - y1) +
777                 //	     diff(Pr(p1))/diff(y*z)*(y - y1)*(z - z1) +
778                 //	     diff(Pr(p1))/diff(x*z)*(x - x1)*(z - z1)
779 
780                 // Pr(pi) = Pr(p1) - 10*n*(x1 - xa)/(ln(10)*d1a^2)*(xi -x1)
781                 //       - 10*n*(y1 - ya)/(ln(10)*d1a^2)*(yi - y1)
782                 //       - 10*n*(z1 - za)/(ln(10)*d1a^2)*(zi - z1)
783                 //       - 5*n*((y1 - ya)^2 + (z1 - za)^2) - (x1 - xa)^2)/(ln(10)*d1a^4)*(xi - x1)^2
784                 //       - 5*n*((x1 - xa)^2 - (y1 - ya)^2 + (z1 - za)^2))/(ln(10)*d1a^4)*(yi - y1)^2
785                 //       - 5*n*((x1 - xa)^2 + (y1 - ya)^2 - (z1 - za)^2))/(ln(10)*d1a^4)*(zi - z1)^2
786                 //       + 20*n*(x1 - xa)*(y1 - ya)/(ln(10)*d1a^4)*(xi - x1)*(yi - y1)
787                 //       + 20*n*(y1 - ya)*(z1 - za)/(ln(10)*d1a^4)*(yi - y1)*(zi - z1)
788                 //       + 20*n*(x1 - xa)*(z1 - za)/(ln(10)*d1a^4)*(xi - x1)*(zi - z1)
789 
790                 // The equation above can be solved using a non-linear fitter such as Levenberg-Marquardt
791                 try {
792                     setupFitter();
793 
794                     mFitter.fit();
795 
796                     // estimated position
797                     estimatedPositionCoordinates = mFitter.getA();
798                     mCovariance = mFitter.getCovar();
799                     mChiSq = mFitter.getChisq();
800 
801                     // a solution was found so we exit loop
802                     break;
803                 } catch (NumericalException e) {
804                     // solution could not be found with current data
805                     // Iterate to use additional nearby fingerprints
806                     estimatedPositionCoordinates = null;
807                     mCovariance = null;
808                     nearestFingerprints = null;
809                 }
810             }
811 
812             if (estimatedPositionCoordinates == null) {
813                 // no solution could be found
814                 throw new FingerprintEstimationException();
815             }
816 
817             if (listener != null) {
818                 listener.onEstimateEnd(this);
819             }
820         } finally {
821             locked = false;
822         }
823     }
824 
825     /**
826      * Gets type of position estimator.
827      *
828      * @return type of position estimator.
829      */
830     public abstract NonLinearFingerprintPositionEstimatorType getType();
831 
832     /**
833      * Evaluates a non-linear multi dimension function at provided point using
834      * provided parameters and returns its evaluation and derivatives of the
835      * function respect the function parameters.
836      *
837      * @param i           number of sample being evaluated.
838      * @param point       point where function will be evaluated.
839      * @param params      initial parameters estimation to be tried. These will
840      *                    change as the Levenberg-Marquardt algorithm iterates to the best solution.
841      *                    These are used as input parameters along with point to evaluate function.
842      * @param derivatives partial derivatives of the function respect to each
843      *                    provided parameter.
844      * @return function evaluation at provided point.
845      */
846     protected abstract double evaluate(
847             final int i, final double[] point, final double[] params, final double[] derivatives);
848 
849     /**
850      * Propagates provided variances into RSSI variance of non-located fingerprint
851      * reading.
852      *
853      * @param fingerprintRssi               closest located fingerprint reading RSSI expressed in dBm's.
854      * @param pathlossExponent              path-loss exponent.
855      * @param fingerprintPosition           position of closest located fingerprint.
856      * @param radioSourcePosition           radio source position associated to fingerprint reading.
857      * @param estimatedPosition             position to be estimated. Usually this is equal to the
858      *                                      initial position used by a non-linear algorithm.
859      * @param fingerprintRssiVariance       variance of fingerprint RSSI or null if unknown.
860      * @param pathlossExponentVariance      variance of path-loss exponent or null if unknown.
861      * @param fingerprintPositionCovariance covariance of fingerprint position or null if
862      *                                      unknown.
863      * @param radioSourcePositionCovariance covariance of radio source position or null if
864      *                                      unknown.
865      * @return variance of RSSI measured at non located fingerprint reading.
866      */
867     protected abstract Double propagateVariances(
868             final double fingerprintRssi, final double pathlossExponent, final P fingerprintPosition,
869             final P radioSourcePosition, final P estimatedPosition, final Double fingerprintRssiVariance,
870             final Double pathlossExponentVariance, final Matrix fingerprintPositionCovariance,
871             final Matrix radioSourcePositionCovariance);
872 
873     /**
874      * Builds data required to solve the problem.
875      *
876      * @param allReceivedPower        list of received powers for readings at unknown positions.
877      * @param allFingerprintPower     list of power readings at fingerprint positions.
878      * @param allFingerprintPositions list of fingerprint positions.
879      * @param allSourcesPositions     list of radio sources positions.
880      * @param allPathLossExponents    list of path loss exponents.
881      * @param allStandardDeviations   list of standard deviations for readings being used.
882      */
883     @SuppressWarnings("Duplicates")
884     private void buildData(
885             final List<Double> allReceivedPower,
886             final List<Double> allFingerprintPower,
887             final List<P> allFingerprintPositions,
888             final List<P> allSourcesPositions,
889             final List<Double> allPathLossExponents,
890             final List<Double> allStandardDeviations) {
891         for (final var locatedFingerprint : nearestFingerprints) {
892 
893             final var locatedReadings = locatedFingerprint.getReadings();
894             if (locatedReadings == null) {
895                 continue;
896             }
897 
898             final var fingerprintPosition = locatedFingerprint.getPosition();
899             final var fingerprintPositionCovariance = locatedFingerprint.getPositionCovariance();
900 
901             var locatedMeanRssi = 0.0;
902             var meanRssi = 0.0;
903             if (removeMeansFromFingerprintReadings) {
904                 locatedMeanRssi = locatedFingerprint.getMeanRssi();
905             }
906 
907             for (final var locatedReading : locatedReadings) {
908                 final var source = locatedReading.getSource();
909 
910                 // find within the list of located sources the source of
911                 // current located fingerprint reading.
912                 // Radio sources are compared by their id
913                 // regardless of them being located or not
914 
915                 //noinspection SuspiciousMethodCalls
916                 final var pos = sources.indexOf(source);
917                 if (pos < 0) {
918                     continue;
919                 }
920 
921                 final var locatedSource = sources.get(pos);
922                 var pathLossExponent = this.pathLossExponent;
923                 Double pathLossExponentVariance = null;
924                 if (useSourcesPathLossExponentWhenAvailable
925                         && locatedSource instanceof RadioSourceWithPower locatedSourceWithPower) {
926                     pathLossExponent = locatedSourceWithPower.getPathLossExponent();
927                     final var std = locatedSourceWithPower.getPathLossExponentStandardDeviation();
928                     pathLossExponentVariance = std != null ? std * std : null;
929                 }
930 
931                 final var sourcePosition = locatedSource.getPosition();
932                 final var sourcePositionCovariance = locatedSource.getPositionCovariance();
933                 var locatedRssi = locatedReading.getRssi();
934                 locatedRssi -= locatedMeanRssi;
935 
936                 final var locatedRssiStd = locatedReading.getRssiStandardDeviation();
937                 final var locatedRssiVariance = locatedRssiStd != null ? locatedRssiStd * locatedRssiStd : null;
938                 if (removeMeansFromFingerprintReadings) {
939                     meanRssi = fingerprint.getMeanRssi();
940                 }
941 
942                 final var readings = fingerprint.getReadings();
943                 for (final var reading : readings) {
944                     if (reading.getSource() == null || !reading.getSource().equals(locatedSource)) {
945                         continue;
946                     }
947 
948                     // only take into account reading for matching sources on located and
949                     // non-located readings
950                     var rssi = reading.getRssi();
951                     rssi -= meanRssi;
952 
953                     Double standardDeviation = null;
954                     if (mPropagateFingerprintRssiStandardDeviation || mPropagatePathlossExponentStandardDeviation
955                             || mPropagateFingerprintPositionCovariance || mPropagateRadioSourcePositionCovariance) {
956 
957                         // compute initial position
958                         final var initialPosition = mInitialPosition != null ? mInitialPosition : fingerprintPosition;
959 
960                         final var variance = propagateVariances(locatedRssi, pathLossExponent, fingerprintPosition,
961                                 sourcePosition, initialPosition,
962                                 mPropagateFingerprintRssiStandardDeviation ? locatedRssiVariance : null,
963                                 mPropagatePathlossExponentStandardDeviation ? pathLossExponentVariance : null,
964                                 mPropagateFingerprintPositionCovariance ? fingerprintPositionCovariance : null,
965                                 mPropagateRadioSourcePositionCovariance ? sourcePositionCovariance : null);
966                         if (variance != null) {
967                             standardDeviation = Math.sqrt(variance);
968                         }
969                     }
970 
971                     if (standardDeviation == null) {
972                         standardDeviation = reading.getRssiStandardDeviation();
973                     } else if (reading.getRssiStandardDeviation() != null) {
974                         // consider propagated variance and reading variance independent, so we
975                         // sum them both
976                         standardDeviation = standardDeviation * standardDeviation +
977                                 reading.getRssiStandardDeviation() * reading.getRssiStandardDeviation();
978                         standardDeviation = Math.sqrt(standardDeviation);
979                     }
980 
981                     if (standardDeviation == null || standardDeviation < TINY_RSSI_STD) {
982                         standardDeviation = mFallbackRssiStandardDeviation;
983                     }
984 
985                     allReceivedPower.add(rssi);
986                     allFingerprintPower.add(locatedRssi);
987                     allFingerprintPositions.add(fingerprintPosition);
988                     allSourcesPositions.add(sourcePosition);
989                     allPathLossExponents.add(pathLossExponent);
990                     allStandardDeviations.add(standardDeviation);
991                 }
992             }
993         }
994     }
995 
996     /**
997      * Setups fitter to solve position.
998      *
999      * @throws FittingException if Levenberg-Marquardt fitting fails.
1000      */
1001     @SuppressWarnings("Duplicates")
1002     private void setupFitter() throws FittingException {
1003         // build lists of data
1004         final var allReceivedPower = new ArrayList<Double>();
1005         final var allFingerprintPower = new ArrayList<Double>();
1006         final var allFingerprintPositions = new ArrayList<P>();
1007         final var allSourcesPosition = new ArrayList<P>();
1008         final var allPathLossExponents = new ArrayList<Double>();
1009         final var allStandardDeviations = new ArrayList<Double>();
1010         buildData(allReceivedPower, allFingerprintPower, allFingerprintPositions, allSourcesPosition,
1011                 allPathLossExponents, allStandardDeviations);
1012 
1013         final var totalReadings = allReceivedPower.size();
1014         final var dims = getNumberOfDimensions();
1015         final var n = 2 + 2 * dims;
1016 
1017         mFitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
1018             @Override
1019             public int getNumberOfDimensions() {
1020                 return n;
1021             }
1022 
1023             @Override
1024             public double[] createInitialParametersArray() {
1025 
1026                 final var initial = new double[dims];
1027 
1028                 if (mInitialPosition == null) {
1029                     // use centroid of nearest fingerprints as initial value
1030                     var num = 0;
1031                     for (var fingerprint : nearestFingerprints) {
1032                         final var position = fingerprint.getPosition();
1033                         if (position == null) {
1034                             continue;
1035                         }
1036 
1037                         for (var i = 0; i < dims; i++) {
1038                             initial[i] += position.getInhomogeneousCoordinate(i);
1039                         }
1040                         num++;
1041                     }
1042 
1043                     if (num > 0) {
1044                         for (var i = 0; i < dims; i++) {
1045                             initial[i] /= num;
1046                         }
1047                     }
1048                 } else {
1049                     // use provided initial position
1050                     for (var i = 0; i < dims; i++) {
1051                         initial[i] = mInitialPosition.getInhomogeneousCoordinate(i);
1052                     }
1053                 }
1054                 return initial;
1055             }
1056 
1057             @Override
1058             public double evaluate(
1059                     final int i, final double[] point, final double[] params, final double[] derivatives) {
1060                 return NonLinearFingerprintPositionEstimator.this.evaluate(i, point, params, derivatives);
1061             }
1062         });
1063 
1064         try {
1065             final var x = new Matrix(totalReadings, n);
1066             final var y = new double[totalReadings];
1067             final var standardDeviations = new double[totalReadings];
1068             for (var i = 0; i < totalReadings; i++) {
1069                 // fingerprint power Pr(p1)
1070                 x.setElementAt(i, 0, allFingerprintPower.get(i));
1071                 for (var j = 0; j < dims; j++) {
1072                     x.setElementAt(i, j + 1, allFingerprintPositions.get(i).getInhomogeneousCoordinate(j));
1073                     x.setElementAt(i, j + 1 + dims, allSourcesPosition.get(i).getInhomogeneousCoordinate(j));
1074                 }
1075                 x.setElementAt(i, 1 + 2 * dims, allPathLossExponents.get(i));
1076 
1077                 y[i] = allReceivedPower.get(i);
1078 
1079                 standardDeviations[i] = allStandardDeviations.get(i);
1080             }
1081 
1082             mFitter.setInputData(x, y, standardDeviations);
1083         } catch (final AlgebraException e) {
1084             throw new FittingException(e);
1085         }
1086     }
1087 }