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 }