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.radiosource;
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.geodesic.Constants;
24 import com.irurueta.navigation.indoor.RadioSource;
25 import com.irurueta.navigation.indoor.RssiReadingLocated;
26 import com.irurueta.navigation.indoor.Utils;
27 import com.irurueta.numerical.NumericalException;
28 import com.irurueta.numerical.fitting.FittingException;
29 import com.irurueta.numerical.fitting.LevenbergMarquardtMultiDimensionFitter;
30 import com.irurueta.numerical.fitting.LevenbergMarquardtMultiDimensionFunctionEvaluator;
31
32 import java.util.List;
33
34 /**
35 * Estimates position, transmitted power and path loss exponent of a
36 * radio source (e.g. Wi-Fi access point or bluetooth beacon) assuming
37 * that the radio source emits isotropically following the expression
38 * below:
39 * Pr = Pt*Gt*Gr*lambda^2 / (4*pi*d)^2,
40 * where Pr is the received power (expressed in mW),
41 * Gt is the Gain of the transmission antenna
42 * Gr is the Gain of the receiver antenna
43 * d is the distance between emitter and receiver
44 * and lambda is the wavelength and is equal to: lambda = c / f,
45 * where c is the speed of light
46 * and f is the carrier frequency of the radio signal.
47 * Because usually information about the antenna of the radio source cannot be
48 * retrieved (because many measurements are made on unknown devices where
49 * physical access is not possible), this implementation will estimate the
50 * equivalent transmitted power as: Pte = Pt * Gt * Gr.
51 * If Readings contain RSSI standard deviations, those values will be used,
52 * otherwise it will be assumed an RSSI standard deviation of 1 dB.
53 * <p>
54 * IMPORTANT: Implementations of this class can choose to estimate a
55 * combination of radio source position, transmitted power and path loss
56 * exponent. However enabling all three estimations usually achieves
57 * inaccurate results. When using this class, estimation must be of at least
58 * one parameter (position, transmitted power or path loss exponent) when
59 * initial values are provided for the other two, and at most it should consist
60 * of two parameters (either position and transmitted power, position and
61 * path loss exponent or transmitted power and path loss exponent), providing an
62 * initial value for the remaining parameter.
63 *
64 * @param <S> a {@link RadioSource} type.
65 * @param <P> a {@link Point} type.
66 */
67 @SuppressWarnings("DuplicatedCode")
68 public abstract class RssiRadioSourceEstimator<S extends RadioSource, P extends Point<P>>
69 extends RadioSourceEstimator<P, RssiReadingLocated<S, P>, RssiRadioSourceEstimatorListener<S, P>> {
70
71 /**
72 * Speed of light expressed in meters per second (m/s).
73 */
74 public static final double SPEED_OF_LIGHT = Constants.SPEED_OF_LIGHT;
75
76 /**
77 * Default standard deviations assumed for RSSI readings being fitted.
78 */
79 public static final double DEFAULT_POWER_STANDARD_DEVIATION = 1.0;
80
81 /**
82 * Default exponent typically used on free space for path loss propagation in
83 * terms of distance. This value is used for free space environments.
84 */
85 public static final double DEFAULT_PATH_LOSS_EXPONENT = 2.0;
86
87 /**
88 * Indicates whether radio source position estimation is enabled or not by default.
89 */
90 public static final boolean DEFAULT_POSITION_ESTIMATION_ENABLED = true;
91
92 /**
93 * Indicates whether radio source transmitted power estimation is enabled or not by
94 * default. Typically, this data is required for Wi-Fi Access points, but it is already
95 * provided for Beacons (and hence its estimation is not needed).
96 */
97 public static final boolean DEFAULT_TRANSMITTED_POWER_ESTIMATION_ENABLED = true;
98
99 /**
100 * Indicates whether path loss estimation is enabled or not by default.
101 */
102 public static final boolean DEFAULT_PATHLOSS_ESTIMATION_ENABLED = false;
103
104
105 /**
106 * Indicates whether radio source position estimation is enabled or not.
107 */
108 private boolean positionEstimationEnabled = DEFAULT_POSITION_ESTIMATION_ENABLED;
109
110 /**
111 * Estimated transmitted power expressed in dBm's.
112 */
113 private double estimatedTransmittedPowerdBm;
114
115 /**
116 * Indicates whether transmitted power estimation is enabled or not.
117 */
118 private boolean transmittedPowerEstimationEnabled = DEFAULT_TRANSMITTED_POWER_ESTIMATION_ENABLED;
119
120 /**
121 * Estimated exponent typically used on free space for path loss propagation in
122 * terms of distance.
123 * On different environments path loss exponent might have different values:
124 * - Free space: 2.0
125 * - Urban Area: 2.7 to 3.5
126 * - Suburban Area: 3 to 5
127 * - Indoor (line-of-sight): 1.6 to 1.8
128 * If path loss exponent estimation is not enabled, this value will always be equal to
129 * {@link #DEFAULT_PATH_LOSS_EXPONENT}
130 */
131 private double estimatedPathLossExponent = DEFAULT_PATH_LOSS_EXPONENT;
132
133 /**
134 * Variance of estimated transmitted power.
135 * This value will only be available when transmitted power
136 * estimation is enabled.
137 */
138 private Double estimatedTransmittedPowerVariance;
139
140 /**
141 * Variance of estimated path loss exponent.
142 * This value will only be available when path-loss
143 * exponent estimation is enabled.
144 */
145 private Double estimatedPathLossExponentVariance;
146
147 /**
148 * Estimated chi square value.
149 */
150 private double chiSq;
151
152 /**
153 * Initial transmitted power to start the estimation of radio source
154 * transmitted power.
155 * If not defined, average value of received power readings will be used.
156 */
157 private Double initialTransmittedPowerdBm;
158
159 /**
160 * Initial position to start the estimation of radio source position.
161 * If not defined, centroid of provided readings will be used.
162 */
163 private P initialPosition;
164
165 /**
166 * Initial exponent typically used on free space for path loss propagation in
167 * terms of distance.
168 * On different environments path loss exponent might have different values:
169 * - Free space: 2.0
170 * - Urban Area: 2.7 to 3.5
171 * - Suburban Area: 3 to 5
172 * - Indoor (line-of-sight): 1.6 to 1.8
173 * <p>
174 * If path loss exponent estimation is enabled, estimation will start at this
175 * value and will converge to the most appropriate value.
176 * If path loss exponent estimation is disabled, this value will be assumed
177 * to be exact and the estimated path loss exponent will be equal to this
178 * value.
179 */
180 private double initialPathLossExponent = DEFAULT_PATH_LOSS_EXPONENT;
181
182 /**
183 * Indicates whether path loss estimation is enabled or not.
184 */
185 private boolean pathLossEstimationEnabled = DEFAULT_PATHLOSS_ESTIMATION_ENABLED;
186
187 /**
188 * Levenberg-Marquardt fitter to find a solution.
189 */
190 private final LevenbergMarquardtMultiDimensionFitter fitter = new LevenbergMarquardtMultiDimensionFitter();
191
192 /**
193 * Constructor.
194 */
195 protected RssiRadioSourceEstimator() {
196 super();
197 }
198
199 /**
200 * Constructor.
201 * Sets radio signal readings belonging to the same radio source.
202 *
203 * @param readings radio signal readings belonging to the same
204 * radio source.
205 * @throws IllegalArgumentException if readings are not valid.
206 */
207 protected RssiRadioSourceEstimator(final List<? extends RssiReadingLocated<S, P>> readings) {
208 super(readings);
209 }
210
211 /**
212 * Constructor.
213 *
214 * @param listener listener in charge of attending events raised by this instance.
215 */
216 protected RssiRadioSourceEstimator(final RssiRadioSourceEstimatorListener<S, P> listener) {
217 super(listener);
218 }
219
220 /**
221 * Constructor.
222 * Sets radio signal readings belonging to the same radio source.
223 *
224 * @param readings radio signal readings belonging to the same radio source.
225 * @param listener listener in charge of attending events raised by this instance.
226 * @throws IllegalArgumentException if readings are not valid.
227 */
228 protected RssiRadioSourceEstimator(
229 final List<? extends RssiReadingLocated<S, P>> readings,
230 final RssiRadioSourceEstimatorListener<S, P> listener) {
231 super(readings, listener);
232 }
233
234 /**
235 * Constructor.
236 *
237 * @param initialPosition initial position to start the estimation of radio
238 * source position.
239 */
240 protected RssiRadioSourceEstimator(final P initialPosition) {
241 this.initialPosition = initialPosition;
242 }
243
244 /**
245 * Constructor.
246 * Sets radio signal readings belonging to the same radio source.
247 *
248 * @param readings radio signal readings belonging to the same radio source.
249 * @param initialPosition initial position to start the estimation of radio
250 * source position.
251 * @throws IllegalArgumentException if readings are not valid.
252 */
253 protected RssiRadioSourceEstimator(
254 final List<? extends RssiReadingLocated<S, P>> readings, final P initialPosition) {
255 super(readings);
256 this.initialPosition = initialPosition;
257 }
258
259 /**
260 * Constructor.
261 *
262 * @param initialPosition initial position to start the estimation of radio
263 * source position.
264 * @param listener listener in charge of attending events raised by this instance.
265 */
266 protected RssiRadioSourceEstimator(final P initialPosition, final RssiRadioSourceEstimatorListener<S, P> listener) {
267 super(listener);
268 this.initialPosition = initialPosition;
269 }
270
271 /**
272 * Constructor.
273 * Sets radio signal readings belonging to the same radio source.
274 *
275 * @param readings radio signal readings belonging to the same radio source.
276 * @param initialPosition initial position to start the estimation of radio
277 * source position.
278 * @param listener listener in charge of attending events raised by this instance.
279 * @throws IllegalArgumentException if readings are not valid.
280 */
281 protected RssiRadioSourceEstimator(
282 final List<? extends RssiReadingLocated<S, P>> readings, final P initialPosition,
283 final RssiRadioSourceEstimatorListener<S, P> listener) {
284 super(readings, listener);
285 this.initialPosition = initialPosition;
286 }
287
288 /**
289 * Constructor.
290 *
291 * @param initialTransmittedPowerdBm initial transmitted power to start the
292 * estimation of radio source transmitted power
293 * (expressed in dBm's).
294 */
295 protected RssiRadioSourceEstimator(final Double initialTransmittedPowerdBm) {
296 this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
297 }
298
299 /**
300 * Constructor.
301 * Sets radio signal readings belonging to the same radio source.
302 *
303 * @param readings radio signal readings belonging to the same radio source.
304 * @param initialTransmittedPowerdBm initial transmitted power to start the
305 * estimation of radio source transmitted power
306 * (expressed in dBm's).
307 * @throws IllegalArgumentException if readings are not valid.
308 */
309 protected RssiRadioSourceEstimator(
310 final List<? extends RssiReadingLocated<S, P>> readings, final Double initialTransmittedPowerdBm) {
311 super(readings);
312 this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
313 }
314
315 /**
316 * Constructor.
317 *
318 * @param initialTransmittedPowerdBm initial transmitted power to start the
319 * estimation of radio source transmitted power
320 * (expressed in dBm's)
321 * @param listener listener in charge of attending events raised by this instance.
322 */
323 protected RssiRadioSourceEstimator(
324 final Double initialTransmittedPowerdBm, final RssiRadioSourceEstimatorListener<S, P> listener) {
325 super(listener);
326 this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
327 }
328
329 /**
330 * Constructor.
331 * Sets radio signal readings belonging to the same radio source.
332 *
333 * @param readings radio signal readings belonging to the same radio source.
334 * @param initialTransmittedPowerdBm initial transmitted power to start the
335 * estimation of radio source transmitted power
336 * (expressed in dBm's)
337 * @param listener listener in charge of attending events raised by this instance.
338 * @throws IllegalArgumentException if readings are not valid.
339 */
340 protected RssiRadioSourceEstimator(
341 final List<? extends RssiReadingLocated<S, P>> readings, final Double initialTransmittedPowerdBm,
342 final RssiRadioSourceEstimatorListener<S, P> listener) {
343 super(readings, listener);
344 this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
345 }
346
347 /**
348 * Constructor.
349 * Sets radio signal readings belonging to the same radio source.
350 *
351 * @param readings radio signal readings belonging to the same radio source.
352 * @param initialPosition initial position to start the estimation of radio
353 * source position.
354 * @param initialTransmittedPowerdBm initial transmitted power to start the
355 * estimation of radio source transmitted power
356 * (expressed in dBm's)
357 * @throws IllegalArgumentException if readings are not valid.
358 */
359 protected RssiRadioSourceEstimator(
360 final List<? extends RssiReadingLocated<S, P>> readings, final P initialPosition,
361 final Double initialTransmittedPowerdBm) {
362 super(readings);
363 this.initialPosition = initialPosition;
364 this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
365 }
366
367 /**
368 * Constructor.
369 *
370 * @param initialPosition initial position to start the estimation of radio
371 * source position.
372 * @param initialTransmittedPowerdBm initial transmitted power to start the
373 * estimation of radio source transmitted power
374 * (expressed in dBm's)
375 */
376 protected RssiRadioSourceEstimator(final P initialPosition, final Double initialTransmittedPowerdBm) {
377 this.initialPosition = initialPosition;
378 this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
379 }
380
381 /**
382 * Constructor.
383 *
384 * @param initialPosition initial position to start the estimation of radio
385 * source position.
386 * @param initialTransmittedPowerdBm initial transmitted power to start the
387 * estimation of radio source transmitted power
388 * (expressed in dBm's)
389 * @param listener listener in charge of attending events raised by this instance.
390 */
391 protected RssiRadioSourceEstimator(
392 final P initialPosition, final Double initialTransmittedPowerdBm,
393 final RssiRadioSourceEstimatorListener<S, P> listener) {
394 super(listener);
395 this.initialPosition = initialPosition;
396 this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
397 }
398
399 /**
400 * Constructor.
401 * Sets radio signal readings belonging to the same radio source.
402 *
403 * @param readings radio signal readings belonging to the same radio source.
404 * @param initialPosition initial position to start the estimation of radio
405 * source position.
406 * @param initialTransmittedPowerdBm initial transmitted power to start the
407 * estimation of radio source transmitted power
408 * (expressed in dBm's)
409 * @param listener listener in charge of attending events raised by this instance.
410 * @throws IllegalArgumentException if readings are not valid.
411 */
412 protected RssiRadioSourceEstimator(
413 final List<? extends RssiReadingLocated<S, P>> readings, final P initialPosition,
414 final Double initialTransmittedPowerdBm, final RssiRadioSourceEstimatorListener<S, P> listener) {
415 super(readings, listener);
416 this.initialPosition = initialPosition;
417 this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
418 }
419
420 /**
421 * Constructor.
422 * Sets radio signal readings belonging to the same radio source.
423 *
424 * @param readings radio signal readings belonging to the same radio source.
425 * @param initialPosition initial position to start the estimation of radio
426 * source position.
427 * @param initialTransmittedPowerdBm initial transmitted power to start the
428 * estimation of radio source transmitted power
429 * (expressed in dBm's).
430 * @param initialPathLossExponent initial path loss exponent. A typical value is 2.0.
431 * @throws IllegalArgumentException if readings are not valid.
432 */
433 protected RssiRadioSourceEstimator(
434 final List<? extends RssiReadingLocated<S, P>> readings, final P initialPosition,
435 final Double initialTransmittedPowerdBm, final double initialPathLossExponent) {
436 this(readings, initialPosition, initialTransmittedPowerdBm);
437 this.initialPathLossExponent = initialPathLossExponent;
438 }
439
440 /**
441 * Constructor.
442 *
443 * @param initialPosition initial position to start the estimation of radio
444 * source position.
445 * @param initialTransmittedPowerdBm initial transmitted power to start the
446 * estimation of radio source transmitted power
447 * (expressed in dBm's).
448 * @param initialPathLossExponent initial path loss exponent. A typical value is 2.0.
449 */
450 protected RssiRadioSourceEstimator(
451 final P initialPosition, final Double initialTransmittedPowerdBm, final double initialPathLossExponent) {
452 this(initialPosition, initialTransmittedPowerdBm);
453 this.initialPathLossExponent = initialPathLossExponent;
454 }
455
456 /**
457 * Constructor.
458 *
459 * @param initialPosition initial position to start the estimation of radio
460 * source position.
461 * @param initialTransmittedPowerdBm initial transmitted power to start the
462 * estimation of radio source transmitted power
463 * (expressed in dBm's)
464 * @param initialPathLossExponent initial path loss exponent. A typical value is 2.0.
465 * @param listener listener in charge of attending events raised by this instance.
466 */
467 protected RssiRadioSourceEstimator(
468 final P initialPosition, final Double initialTransmittedPowerdBm, final double initialPathLossExponent,
469 final RssiRadioSourceEstimatorListener<S, P> listener) {
470 this(initialPosition, initialTransmittedPowerdBm, listener);
471 this.initialPathLossExponent = initialPathLossExponent;
472 }
473
474 /**
475 * Constructor.
476 * Sets radio signal readings belonging to the same radio source.
477 *
478 * @param readings radio signal readings belonging to the same radio source.
479 * @param initialPosition initial position to start the estimation of radio
480 * source position.
481 * @param initialTransmittedPowerdBm initial transmitted power to start the
482 * estimation of radio source transmitted power
483 * (expressed in dBm's)
484 * @param initialPathLossExponent initial path loss exponent. A typical value is 2.0.
485 * @param listener listener in charge of attending events raised by this instance.
486 * @throws IllegalArgumentException if readings are not valid.
487 */
488 protected RssiRadioSourceEstimator(
489 final List<? extends RssiReadingLocated<S, P>> readings, final P initialPosition,
490 final Double initialTransmittedPowerdBm, final double initialPathLossExponent,
491 final RssiRadioSourceEstimatorListener<S, P> listener) {
492 this(readings, initialPosition, initialTransmittedPowerdBm, listener);
493 this.initialPathLossExponent = initialPathLossExponent;
494 }
495
496 /**
497 * Gets initial transmitted power to start the estimation of radio source
498 * transmitted power (expressed in dBm's).
499 * If not defined, average value of received power readings will be used.
500 * <p>
501 * If transmitted power estimation is enabled, estimation will start at this
502 * value and will be converted to the most appropriate value.
503 * If transmitted power estimation is disabled, this value will be assumed to be
504 * exact and the estimated transmitted power will be equal to this value
505 * (converted to dBm's).
506 *
507 * @return initial transmitted power to start the estimation of radio source
508 * transmitted power.
509 */
510 public Double getInitialTransmittedPowerdBm() {
511 return initialTransmittedPowerdBm;
512 }
513
514 /**
515 * Sets initial transmitted power to start the estimation of radio source
516 * transmitted power (expressed in dBm's).
517 * If not defined, average value of received power readings will be used.
518 * <p>
519 * If transmitted power estimation is enabled, estimation will start at this
520 * value and will be converted to the most appropriate value.
521 * If transmitted power estimation is disabled, this value will be assumed to be
522 * exact and the estimated transmitted power will be equal to this value
523 * (converted to dBm's).
524 *
525 * @param initialTransmittedPowerdBm initial transmitted power to start the
526 * estimation of radio source transmitted
527 * power.
528 * @throws LockedException if estimator is locked.
529 */
530 public void setInitialTransmittedPowerdBm(final Double initialTransmittedPowerdBm) throws LockedException {
531 if (isLocked()) {
532 throw new LockedException();
533 }
534 this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
535 }
536
537 /**
538 * Gets initial transmitted power to start the estimation of radio source
539 * transmitted power (expressed in mW).
540 * If not defined, average value of received power readings will be used.
541 * <p>
542 * If transmitted power estimation is enabled, estimation will start at this
543 * value and will be converted to the most appropriate value.
544 * If transmitted power estimation is disabled, this value will be assumed to be
545 * exact and the estimated transmitted power will be equal to this value
546 * (converted to dBm's).
547 *
548 * @return initial transmitted power to start the estimation of radio source
549 * transmitted power.
550 */
551 public Double getInitialTransmittedPower() {
552 return initialTransmittedPowerdBm != null ? Utils.dBmToPower(initialTransmittedPowerdBm) : null;
553 }
554
555 /**
556 * Sets initial transmitted power to start the estimation of radio source
557 * transmitted power (expressed in mW).
558 * If not defined, average value of received power readings will be used.
559 * <p>
560 * If transmitted power estimation is enabled, estimation will start at this
561 * value and will be converted to the most appropriate value.
562 * If transmitted power estimation is disabled, this value will be assumed to be
563 * exact and the estimated transmitted power will be equal to this value
564 * (converted to dBm's).
565 *
566 * @param initialTransmittedPower initial transmitted power to start the
567 * estimation of radio source transmitted power.
568 * @throws LockedException if estimator is locked.
569 * @throws IllegalArgumentException if provided value is negative.
570 */
571 public void setInitialTransmittedPower(final Double initialTransmittedPower) throws LockedException {
572 if (isLocked()) {
573 throw new LockedException();
574 }
575 if (initialTransmittedPower != null) {
576 if (initialTransmittedPower < 0.0) {
577 throw new IllegalArgumentException();
578 }
579 initialTransmittedPowerdBm = Utils.powerTodBm(initialTransmittedPower);
580 } else {
581 initialTransmittedPowerdBm = null;
582 }
583 }
584
585 /**
586 * Indicates whether transmitted power estimation is enabled or not.
587 *
588 * @return true if transmitted power estimation is enabled, false otherwise.
589 */
590 public boolean isTransmittedPowerEstimationEnabled() {
591 return transmittedPowerEstimationEnabled;
592 }
593
594 /**
595 * Specifies whether transmitted power estimation is enabled or not.
596 *
597 * @param transmittedPowerEstimationEnabled true if transmitted power estimation is enabled,
598 * false otherwise.
599 * @throws LockedException if estimator is locked.
600 */
601 public void setTransmittedPowerEstimationEnabled(final boolean transmittedPowerEstimationEnabled)
602 throws LockedException {
603 if (isLocked()) {
604 throw new LockedException();
605 }
606 this.transmittedPowerEstimationEnabled = transmittedPowerEstimationEnabled;
607 }
608
609 /**
610 * Gets initial position to start the estimation of radio source position.
611 * If not defined, centroid of provided readings will be used.
612 * <p>
613 * If position estimation is enabled, estimation will start at this value
614 * and will converge to the most appropriate value.
615 * If position estimation is disabled, this value will be assumed to
616 * be exact and the estimated position will be equal to this value.
617 *
618 * @return initial position to start the estimation of radio source position.
619 */
620 public P getInitialPosition() {
621 return initialPosition;
622 }
623
624 /**
625 * Sets initial position to start the estimation of radio source position.
626 * If not defined, centroid of provided fingerprints will be used.
627 * <p>
628 * If position estimation is enabled, estimation will start at this value
629 * and will converge to the most appropriate value.
630 * If position estimation is disabled, this value will be assumed to
631 * be exact and the estimated position will be equal to this value.
632 *
633 * @param initialPosition initial position to start the estimation of radio
634 * source position.
635 * @throws LockedException if estimator is locked.
636 */
637 public void setInitialPosition(final P initialPosition) throws LockedException {
638 if (isLocked()) {
639 throw new LockedException();
640 }
641 this.initialPosition = initialPosition;
642 }
643
644 /**
645 * Indicates whether radio source position estimation is enabled or not.
646 *
647 * @return true if position estimation is enabled, false otherwise.
648 */
649 public boolean isPositionEstimationEnabled() {
650 return positionEstimationEnabled;
651 }
652
653 /**
654 * Specifies whether radio source position estimation is enabled or not.
655 *
656 * @param positionEstimationEnabled true if position estimation is enabled,
657 * false otherwise.
658 * @throws LockedException if estimator is locked.
659 */
660 public void setPositionEstimationEnabled(final boolean positionEstimationEnabled) throws LockedException {
661 if (isLocked()) {
662 throw new LockedException();
663 }
664 this.positionEstimationEnabled = positionEstimationEnabled;
665 }
666
667 /**
668 * Gets initial exponent typically used on free space for path loss propagation
669 * in terms of distance.
670 * On different environments path loss exponent might have different value:
671 * - Free space: 2.0
672 * - Urban Area: 2.7 to 3.5
673 * - Suburban Area: 3 to 5
674 * - Indoor (line-of-sight): 1.6 to 1.8
675 * <p>
676 * If path loss exponent estimation is enabled, estimation will start at this
677 * value and will converge to the most appropriate value.
678 * If path loss exponent estimation is disabled, this value will be assumed
679 * to be exact and the estimated path loss exponent will be equal to this
680 * value.
681 *
682 * @return initial path loss exponent.
683 */
684 public double getInitialPathLossExponent() {
685 return initialPathLossExponent;
686 }
687
688 /**
689 * Sets initial exponent typically used on free space for path loss propagation
690 * in terms of distance.
691 * On different environments path loss exponent might have different value:
692 * - Free space: 2.0
693 * - Urban Area: 2.7 to 3.5
694 * - Suburban Area: 3 to 5
695 * - Indoor (line-of-sight): 1.6 to 1.8
696 * <p>
697 * If path loss exponent estimation is enabled, estimation will start at this
698 * value and will converge to the most appropriate value.
699 * If path loss exponent estimation is disabled, this value will be assumed
700 * to be exact and the estimated path loss exponent will be equal to this
701 * value.
702 *
703 * @param initialPathLossExponent initial path loss exponent.
704 * @throws LockedException if estimator is locked.
705 */
706 public void setInitialPathLossExponent(final double initialPathLossExponent) throws LockedException {
707 if (isLocked()) {
708 throw new LockedException();
709 }
710 this.initialPathLossExponent = initialPathLossExponent;
711 }
712
713 /**
714 * Indicates whether path loss estimation is enabled or not.
715 *
716 * @return true if path loss estimation is enabled, false otherwise.
717 */
718 public boolean isPathLossEstimationEnabled() {
719 return pathLossEstimationEnabled;
720 }
721
722 /**
723 * Specifies whether path loss estimation is enabled or not.
724 *
725 * @param pathLossEstimationEnabled true if path loss estimation is enabled,
726 * false otherwise.
727 * @throws LockedException if estimator is locked.
728 */
729 public void setPathLossEstimationEnabled(final boolean pathLossEstimationEnabled) throws LockedException {
730 if (isLocked()) {
731 throw new LockedException();
732 }
733 this.pathLossEstimationEnabled = pathLossEstimationEnabled;
734 }
735
736 /**
737 * Indicates whether this instance is ready to start the estimation.
738 *
739 * @return true if this instance is ready, false otherwise.
740 */
741 @Override
742 public boolean isReady() {
743 // at least one parameter estimation must be enabled
744 return (positionEstimationEnabled || transmittedPowerEstimationEnabled || pathLossEstimationEnabled)
745 // if position estimation is disabled, an initial position must be provided
746 && !(!positionEstimationEnabled && initialPosition == null)
747 // if transmitted power estimation is disabled, an initial transmitted power must be provided
748 && !(!transmittedPowerEstimationEnabled && initialTransmittedPowerdBm == null)
749 // readings must also be valid
750 && areValidReadings(readings);
751 }
752
753 /**
754 * Estimate position, transmitted power and path loss exponent.
755 *
756 * @throws RadioSourceEstimationException if estimation fails.
757 * @throws NotReadyException if estimator is not ready.
758 * @throws LockedException if estimator is locked.
759 */
760 @SuppressWarnings("all")
761 @Override
762 public void estimate() throws RadioSourceEstimationException, NotReadyException, LockedException {
763 if (isLocked()) {
764 throw new LockedException();
765 }
766 if (!isReady()) {
767 throw new NotReadyException();
768 }
769
770 try {
771 locked = true;
772
773 if (listener != null) {
774 listener.onEstimateStart(this);
775 }
776
777 if (positionEstimationEnabled && !transmittedPowerEstimationEnabled && !pathLossEstimationEnabled) {
778 // only position estimation is enabled
779 setupFitterPosition();
780 } else if (!positionEstimationEnabled && transmittedPowerEstimationEnabled && !pathLossEstimationEnabled) {
781 // only transmitted power estimation is enabled
782 setupFitterTransmittedPower();
783 } else if (!positionEstimationEnabled && !transmittedPowerEstimationEnabled && pathLossEstimationEnabled) {
784 // only pathloss estimation is enabled
785 setupFitterPathLossExponent();
786 } else if (positionEstimationEnabled && transmittedPowerEstimationEnabled && !pathLossEstimationEnabled) {
787 // position and transmitted power enabled
788 setupFitterPositionAndTransmittedPower();
789 } else if (positionEstimationEnabled && !transmittedPowerEstimationEnabled && pathLossEstimationEnabled) {
790 // position and pathloss enabled
791 setupFitterPositionAndPathLossExponent();
792 } else if (!positionEstimationEnabled && transmittedPowerEstimationEnabled && pathLossEstimationEnabled) {
793 // transmitted power and pathloss enabled
794 setupFitterTransmittedPowerAndPathLossExponent();
795 } else {
796 // position, transmitted power and pathloss enabled
797 setupFitterPositionTransmittedPowerAndPathLossExponent();
798 }
799
800 fitter.fit();
801
802 // estimated position and transmitted power
803 final var a = fitter.getA();
804 final var dims = getNumberOfDimensions();
805
806 estimatedCovariance = fitter.getCovar();
807 chiSq = fitter.getChisq();
808
809 var pos = 0;
810 estimatedPositionCoordinates = new double[dims];
811 if (positionEstimationEnabled) {
812 // position estimation enabled
813 System.arraycopy(a, 0, estimatedPositionCoordinates, 0, dims);
814
815 if (estimatedCovariance != null) {
816 final var d = dims - 1;
817 if (estimatedPositionCovariance == null) {
818 estimatedPositionCovariance = estimatedCovariance.getSubmatrix(0, 0, d,
819 d);
820 } else {
821 estimatedCovariance.getSubmatrix(0, 0, d, d,
822 estimatedPositionCovariance);
823 }
824 }
825 pos += dims;
826 } else {
827 // position estimation disabled
828 if (initialPosition != null) {
829 for (var i = 0; i < dims; i++) {
830 estimatedPositionCoordinates[i] = initialPosition.getInhomogeneousCoordinate(i);
831 }
832 }
833
834 estimatedPositionCovariance = null;
835 }
836
837 if (transmittedPowerEstimationEnabled) {
838 // transmitted power estimation enabled
839 estimatedTransmittedPowerdBm = a[pos];
840
841 if (estimatedCovariance != null) {
842 estimatedTransmittedPowerVariance = estimatedCovariance.getElementAt(pos, pos);
843 }
844 pos++;
845 } else {
846 // transmitted power estimation disabled
847 if (initialTransmittedPowerdBm != null) {
848 estimatedTransmittedPowerdBm = initialTransmittedPowerdBm;
849 }
850 estimatedTransmittedPowerVariance = null;
851 }
852
853 if (pathLossEstimationEnabled) {
854 // pathloss exponent estimation enabled
855 estimatedPathLossExponent = a[pos];
856
857 if (estimatedCovariance != null) {
858 estimatedPathLossExponentVariance = estimatedCovariance.getElementAt(pos, pos);
859 }
860 } else {
861 // pathloss exponent estimation disabled
862 estimatedPathLossExponent = initialPathLossExponent;
863 estimatedPathLossExponentVariance = null;
864 }
865
866
867 if (listener != null) {
868 listener.onEstimateEnd(this);
869 }
870 } catch (final NumericalException e) {
871 throw new RadioSourceEstimationException(e);
872 } finally {
873 locked = false;
874 }
875 }
876
877 /**
878 * Gets estimated transmitted power expressed in milli watts (mW).
879 *
880 * @return estimated transmitted power expressed in milli watts.
881 */
882 public double getEstimatedTransmittedPower() {
883 return Utils.dBmToPower(estimatedTransmittedPowerdBm);
884 }
885
886 /**
887 * Gets estimated transmitted power expressed in dBm's.
888 *
889 * @return estimated transmitted power expressed in dBm's.
890 */
891 public double getEstimatedTransmittedPowerdBm() {
892 return estimatedTransmittedPowerdBm;
893 }
894
895 /**
896 * Gets estimated exponent typically used on free space for path loss propagation in
897 * terms of distance.
898 * On different environments path loss exponent might have different values:
899 * - Free space: 2.0
900 * - Urban Area: 2.7 to 3.5
901 * - Suburban Area: 3 to 5
902 * - Indoor (line-of-sight): 1.6 to 1.8
903 * If path loss exponent estimation is not enabled, this value will always be equal to
904 * {@link #DEFAULT_PATH_LOSS_EXPONENT}
905 *
906 * @return estimated path loss exponent.
907 */
908 public double getEstimatedPathLossExponent() {
909 return estimatedPathLossExponent;
910 }
911
912 /**
913 * Gets estimated transmitted power variance.
914 * This value will only be available when transmitted power
915 * estimation is enabled.
916 *
917 * @return estimated transmitted power variance or null.
918 */
919 public Double getEstimatedTransmittedPowerVariance() {
920 return estimatedTransmittedPowerVariance;
921 }
922
923 /**
924 * Gets estimated path loss exponent variance.
925 * This value will only be available when path-loss
926 * exponent estimation is enabled.
927 *
928 * @return estimated path loss exponent variance or null.
929 */
930 public Double getEstimatedPathLossExponentVariance() {
931 return estimatedPathLossExponentVariance;
932 }
933
934 /**
935 * Gets estimated chi square value.
936 *
937 * @return estimated chi square value.
938 */
939 public double getChiSq() {
940 return chiSq;
941 }
942
943 /**
944 * Setups fitter to estimated position.
945 *
946 * @throws FittingException if Levenberg-Marquardt fitting fails.
947 */
948 private void setupFitterPosition() throws FittingException {
949 // because all readings must belong to the same radio source, we
950 // obtain the frequency of the first radio source on the first reading
951 var reading = readings.get(0);
952 final var frequency = reading.getSource().getFrequency();
953
954 // n = 2.0, is the path loss exponent (which is typically 2.0)
955
956 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n, where Pr is the received power
957 // lambda = c/f, where lambda is wavelength,
958 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the received Gain
959 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
960
961
962 // compute k as the constant part of the isotropic received power formula
963 // so that: Pr = Pte*k/d^n
964 final var k = Math.pow(SPEED_OF_LIGHT / (4.0 * Math.PI * frequency), initialPathLossExponent);
965 final var kdB = 10.0 * Math.log10(k);
966
967 final var dims = getNumberOfDimensions();
968 final var initialTransmittedPowerdBm = computeInitialTransmittedPowerdBm();
969
970 // for numerical accuracy reasons, a logarithmic version of the previous
971 // formula will be used instead
972 // Pr (dBm) = 10 * log(Pte * k / d^n) = 10*log(k) + 10*log(Pte) - 10*n*log(d)
973
974 fitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
975 @Override
976 public int getNumberOfDimensions() {
977 return dims;
978 }
979
980 @Override
981 public double[] createInitialParametersArray() {
982 final var initial = new double[dims];
983 computeInitialPosition(initial, dims);
984 return initial;
985 }
986
987 @Override
988 public double evaluate(
989 final int i, final double[] point, final double[] params, final double[] derivatives) {
990 var sqrDistance = 0.0;
991 for (var j = 0; j < dims; j++) {
992 final var diff = params[j] - point[j];
993 sqrDistance += diff * diff;
994
995 // n is mInitialPathLossExponent, which is typically 2.0
996 derivatives[j] = -10.0 * initialPathLossExponent * diff;
997 }
998
999 // derivatives respect position coordinates are (2D case):
1000 // f(x,y) = -5*n*log((x - xap)^2 + (y - yap)^2)
1001 // df/dx = -5*n*2*(x - xap)/(ln(10)*((x - xap)^2 + (y - yap)^2)) = -10*n*diffX/(ln(10)*sqrDistance)
1002 // df/dy = -5*n*2*(y - yap)/(ln(10)*((x - xap)^2 + (y - yap)^2)) = -10*n*diffY/(ln(10)*sqrDistance)
1003 final var ln10PerSqrDistance = Math.log(10.0) * sqrDistance;
1004 if (ln10PerSqrDistance != 0.0) {
1005 for (var j = 0; j < dims; j++) {
1006 derivatives[j] /= ln10PerSqrDistance;
1007 }
1008 }
1009
1010 // d^2 = (x - xap)^2 + (y - yap)^2
1011 // d^n = (d^2)^n/2
1012
1013 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n, where Pr is the received power
1014 // n is the path loss exponent
1015 // lambda = c/f, where lambda is wavelength,
1016 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the
1017 // received Gain
1018 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1019 // Pr (dBm) = 10*log(k) + 10*log(Pte) - 10*log(d^n) =
1020 // 10*log(k) + 10*log(Pte) - 10*log((d^2)^n/2) =
1021 // 10*log(k) + 10*log(Pte) - 10*n/2*log(d^2) =
1022 // 10*log(k) + 10*log(Pte) - 5*n*log(d^2) =
1023 return kdB + initialTransmittedPowerdBm - 5.0 * initialPathLossExponent * Math.log10(sqrDistance);
1024 }
1025 });
1026
1027 final var numReadings = readings.size();
1028 try {
1029 final var x = new Matrix(numReadings, dims);
1030 final var y = new double[numReadings];
1031 final var standardDeviations = new double[numReadings];
1032 for (var i = 0; i < numReadings; i++) {
1033 reading = readings.get(i);
1034 final var position = reading.getPosition();
1035
1036 for (var j = 0; j < dims; j++) {
1037 x.setElementAt(i, j, position.getInhomogeneousCoordinate(j));
1038 }
1039
1040 standardDeviations[i] = reading.getRssiStandardDeviation() != null ? reading.getRssiStandardDeviation()
1041 : DEFAULT_POWER_STANDARD_DEVIATION;
1042 y[i] = reading.getRssi();
1043 }
1044
1045 fitter.setInputData(x, y, standardDeviations);
1046 } catch (final AlgebraException ignore) {
1047 // never happens
1048 }
1049 }
1050
1051 /**
1052 * Setups fitter to estimate transmitted power.
1053 *
1054 * @throws FittingException if Levenberg-Marquardt fitting fails.
1055 */
1056 private void setupFitterTransmittedPower() throws FittingException {
1057 // because all readings must belong to the same radio source, we
1058 // obtain the frequency of the first radio source on the first reading
1059 var reading = readings.get(0);
1060 final var frequency = reading.getSource().getFrequency();
1061
1062 // n = 2.0, is the path loss exponent (which is typically 2.0)
1063
1064 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n, where Pr is the received power
1065 // lambda = c/f, where lambda is wavelength,
1066 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the received Gain
1067 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1068
1069
1070 // compute k as the constant part of the isotropic received power formula
1071 // so that: Pr = Pte*k/d^n
1072 final var k = Math.pow(SPEED_OF_LIGHT / (4.0 * Math.PI * frequency), initialPathLossExponent);
1073 final var kdB = 10.0 * Math.log10(k);
1074 final var initialTransmittedPowerdBm = computeInitialTransmittedPowerdBm();
1075
1076 // for numerical accuracy reasons, a logarithmic version of the previous
1077 // formula will be used instead
1078 // Pr (dBm) = 10 * log(Pte * k / d^n) = 10*log(k) + 10*log(Pte) - 10*n*log(d)
1079
1080 fitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
1081 @Override
1082 public int getNumberOfDimensions() {
1083 return 1;
1084 }
1085
1086 @Override
1087 public double[] createInitialParametersArray() {
1088 final var initial = new double[1];
1089 initial[0] = initialTransmittedPowerdBm;
1090 return initial;
1091 }
1092
1093 @Override
1094 public double evaluate(
1095 final int i, final double[] point, final double[] params, final double[] derivatives) {
1096 final var sqrDistance = initialPosition.sqrDistanceTo(readings.get(i).getPosition());
1097
1098 final var transmittedPowerdBm = params[0];
1099
1100 // derivative respect transmitted power Pt (dBm) = 10*log(Pte)
1101 derivatives[0] = 1.0;
1102
1103 // d^2 = (x - xap)^2 + (y - yap)^2
1104 // d^n = (d^2)^n/2
1105
1106 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n, where Pr is the received power
1107 // n is the path loss exponent
1108 // lambda = c/f, where lambda is wavelength,
1109 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the
1110 // received Gain
1111 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1112 // Pr (dBm) = 10*log(k) + 10*log(Pte) - 10*log(d^n) =
1113 // 10*log(k) + 10*log(Pte) - 10*log((d^2)^n/2) =
1114 // 10*log(k) + 10*log(Pte) - 10*n/2*log(d^2) =
1115 // 10*log(k) + 10*log(Pte) - 5*n*log(d^2) =
1116 return kdB + transmittedPowerdBm - 5.0 * initialPathLossExponent * Math.log10(sqrDistance);
1117 }
1118 });
1119
1120 final var numReadings = readings.size();
1121 try {
1122 final var x = new Matrix(numReadings, 1);
1123 final var y = new double[numReadings];
1124 final var standardDeviations = new double[numReadings];
1125
1126 x.initialize(initialTransmittedPowerdBm);
1127 for (var i = 0; i < numReadings; i++) {
1128 reading = readings.get(i);
1129
1130 standardDeviations[i] = reading.getRssiStandardDeviation() != null ? reading.getRssiStandardDeviation()
1131 : DEFAULT_POWER_STANDARD_DEVIATION;
1132 y[i] = reading.getRssi();
1133 }
1134
1135 fitter.setInputData(x, y, standardDeviations);
1136 } catch (final AlgebraException ignore) {
1137 // never happens
1138 }
1139 }
1140
1141 /**
1142 * Setups fitter to estimated path loss exponent.
1143 *
1144 * @throws FittingException if Levenberg-Marquardt fitting fails.
1145 */
1146 private void setupFitterPathLossExponent() throws FittingException {
1147 // because all readings must belong to the same radio source, we
1148 // obtain the frequency of the first radio source on the first reading
1149 var reading = readings.get(0);
1150 final var frequency = reading.getSource().getFrequency();
1151
1152 // n = 2.0, is the path loss exponent
1153
1154 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n, where Pr is the received power
1155 // lambda = c/f, where lambda is wavelength,
1156 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the received Gain
1157 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1158
1159
1160 // k is defined so that: Pr = Pte * k^n / d^n so that
1161 // k = (c/(4*pi*f))
1162 final var k = SPEED_OF_LIGHT / (4.0 * Math.PI * frequency);
1163 final var kdB = 10.0 * Math.log10(k);
1164 final var initialTransmittedPowerdBm = computeInitialTransmittedPowerdBm();
1165
1166 // for numerical accuracy reasons, a logarithmic version of the previous
1167 // formula will be used instead
1168 // Pr (dBm) = 10 * log(Pte * k^n / d^n) = 10*n*log(k) + 10*log(Pte) - 10*n*log(d)
1169
1170 fitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
1171 @Override
1172 public int getNumberOfDimensions() {
1173 return 1;
1174 }
1175
1176 @Override
1177 public double[] createInitialParametersArray() {
1178 final var initial = new double[1];
1179 initial[0] = initialPathLossExponent;
1180 return initial;
1181 }
1182
1183 @Override
1184 public double evaluate(
1185 final int i, final double[] point, final double[] params, final double[] derivatives) {
1186 final var sqrDistance = initialPosition.sqrDistanceTo(readings.get(i).getPosition());
1187 final var pathLossExponent = params[0];
1188
1189 // derivative respect to path loss exponent
1190 // f(x,y,n) = n*kdB -5*n*log((x - xap)^2 + (y - yap)^2)
1191 // df/dn = kdB -5*log((x - xap)^2 + (y - yap)^2) = kdB - 5*log(sqrDistance)
1192 final var logSqrDistance = Math.log10(sqrDistance);
1193 derivatives[0] = kdB - 5 * logSqrDistance;
1194
1195 // d^2 = (x - xap)^2 + (y - yap)^2
1196 // d^n = (d^2)^n/2
1197
1198 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n, where Pr is the received power
1199 // n is the path loss exponent
1200 // lambda = c/f, where lambda is wavelength,
1201 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the
1202 // received Gain
1203 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1204 // Pr (dBm) = 10*log(k^n) + 10*log(Pte) - 10*log(d^n) =
1205 // 10*log(k^n) + 10*log(Pte) - 10*log((d^2)^n/2) =
1206 // 10*n*log(k) + 10*log(Pte) - 10*n/2*log(d^2) =
1207 // 10*n*log(k) + 10*log(Pte) - 5*n*log(d^2) =
1208 return pathLossExponent * kdB + initialTransmittedPowerdBm - 5.0 * pathLossExponent * logSqrDistance;
1209 }
1210 });
1211
1212 final var numReadings = readings.size();
1213 try {
1214 final var x = new Matrix(numReadings, 1);
1215 final var y = new double[numReadings];
1216 final var standardDeviations = new double[numReadings];
1217
1218 x.initialize(initialPathLossExponent);
1219 for (var i = 0; i < numReadings; i++) {
1220 reading = readings.get(i);
1221
1222 standardDeviations[i] = reading.getRssiStandardDeviation() != null ? reading.getRssiStandardDeviation()
1223 : DEFAULT_POWER_STANDARD_DEVIATION;
1224 y[i] = reading.getRssi();
1225 }
1226
1227 fitter.setInputData(x, y, standardDeviations);
1228 } catch (final AlgebraException ignore) {
1229 // never happens
1230 }
1231 }
1232
1233 /**
1234 * Setups fitter to estimate transmitted power and position.
1235 *
1236 * @throws FittingException if Levenberg-Marquardt fitting fails.
1237 */
1238 private void setupFitterPositionAndTransmittedPower() throws FittingException {
1239 // because all readings must belong to the same radio source, we
1240 // obtain the frequency of the first radio source on the first reading
1241 var reading = readings.get(0);
1242 final var frequency = reading.getSource().getFrequency();
1243
1244 // n = 2.0, is the path loss exponent (which is typically 2.0)
1245
1246 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n, where Pr is the received power
1247 // lambda = c/f, where lambda is wavelength,
1248 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the received Gain
1249 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1250
1251
1252 // compute k as the constant part of the isotropic received power formula
1253 // so that: Pr = Pte*k/d^n
1254 final var k = Math.pow(SPEED_OF_LIGHT / (4.0 * Math.PI * frequency), initialPathLossExponent);
1255 final var kdB = 10.0 * Math.log10(k);
1256
1257 final var dims = getNumberOfDimensions();
1258 final var dimsPlus1 = dims + 1;
1259
1260 final var initialTransmittedPowerdBm = computeInitialTransmittedPowerdBm();
1261
1262 // for numerical accuracy reasons, a logarithmic version of the previous
1263 // formula will be used instead
1264 // Pr (dBm) = 10 * log(Pte * k / d^n) = 10*log(k) + 10*log(Pte) - 10*n*log(d)
1265
1266 fitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
1267
1268 @Override
1269 public int getNumberOfDimensions() {
1270 return dimsPlus1;
1271 }
1272
1273 @Override
1274 public double[] createInitialParametersArray() {
1275 final var initial = new double[dimsPlus1];
1276
1277 // initial position
1278 computeInitialPosition(initial, dims);
1279
1280 // initial transmitted power
1281 initial[dims] = initialTransmittedPowerdBm;
1282
1283 return initial;
1284 }
1285
1286 @Override
1287 public double evaluate(
1288 final int i, final double[] point, final double[] params, final double[] derivatives) {
1289 var sqrDistance = 0.0;
1290 for (var j = 0; j < dims; j++) {
1291 final var diff = params[j] - point[j];
1292 sqrDistance += diff * diff;
1293
1294 // n is mInitialPathLossExponent, which is typically 2.0
1295 derivatives[j] = -10.0 * initialPathLossExponent * diff;
1296 }
1297
1298 final var transmittedPowerdBm = params[dims];
1299
1300 // derivatives respect position coordinates are (2D case):
1301 // f(x,y) = -5*n*log((x - xap)^2 + (y - yap)^2)
1302 // df/dx = -5*n*2*(x - xap)/(ln(10)*((x - xap)^2 + (y - yap)^2)) = -10*n*diffX/(ln(10)*sqrDistance)
1303 // df/dy = -5*n*2*(y - yap)/(ln(10)*((x - xap)^2 + (y - yap)^2)) = -10*n*diffY/(ln(10)*sqrDistance)
1304 final var ln10PerSqrDistance = Math.log(10.0) * sqrDistance;
1305 if (ln10PerSqrDistance != 0.0) {
1306 for (var j = 0; j < dims; j++) {
1307 derivatives[j] /= ln10PerSqrDistance;
1308 }
1309 }
1310
1311 // derivative respect transmitted power
1312 derivatives[dims] = 1.0;
1313
1314 // d^2 = (x - xap)^2 + (y - yap)^2
1315 // d^n = (d^2)^n/2
1316
1317 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n, where Pr is the received power
1318 // n is the path loss exponent
1319 // lambda = c/f, where lambda is wavelength,
1320 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the
1321 // received Gain
1322 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1323 // Pr (dBm) = 10*log(k) + 10*log(Pte) - 10*log(d^n) =
1324 // 10*log(k) + 10*log(Pte) - 10*log((d^2)^n/2) =
1325 // 10*log(k) + 10*log(Pte) - 10*n/2*log(d^2) =
1326 // 10*log(k) + 10*log(Pte) - 5*n*log(d^2) =
1327 return kdB + transmittedPowerdBm - 5.0 * initialPathLossExponent * Math.log10(sqrDistance);
1328 }
1329 });
1330
1331 final var numReadings = readings.size();
1332 try {
1333 final var x = new Matrix(numReadings, dimsPlus1);
1334 final var y = new double[numReadings];
1335 final var standardDeviations = new double[numReadings];
1336 for (var i = 0; i < numReadings; i++) {
1337 reading = readings.get(i);
1338 final var position = reading.getPosition();
1339
1340 for (var j = 0; j < dims; j++) {
1341 x.setElementAt(i, j, position.getInhomogeneousCoordinate(j));
1342 }
1343 x.setElementAt(i, dims, initialTransmittedPowerdBm);
1344
1345 standardDeviations[i] = reading.getRssiStandardDeviation() != null ? reading.getRssiStandardDeviation()
1346 : DEFAULT_POWER_STANDARD_DEVIATION;
1347 y[i] = reading.getRssi();
1348 }
1349
1350 fitter.setInputData(x, y, standardDeviations);
1351 } catch (final AlgebraException ignore) {
1352 // never happens
1353 }
1354 }
1355
1356 /**
1357 * Setups fitter to estimate position and path loss exponent.
1358 *
1359 * @throws FittingException if Levenberg-Marquardt fitting fails.
1360 */
1361 private void setupFitterPositionAndPathLossExponent() throws FittingException {
1362 // because all readings must belong to the same radio source, we
1363 // obtain the frequency of the first radio source on the first reading
1364 var reading = readings.get(0);
1365 final var frequency = reading.getSource().getFrequency();
1366
1367 // n = 2.0, is the path loss exponent (which is typically 2.0)
1368
1369 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n, where Pr is the received power
1370 // lambda = c/f, where lambda is wavelength,
1371 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the received Gain
1372 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1373
1374
1375 // compute k as the constant part of the isotropic received power formula
1376 // so that: Pr = Pte*k/d^n
1377 final var k = SPEED_OF_LIGHT / (4.0 * Math.PI * frequency);
1378 final var kdB = 10.0 * Math.log10(k);
1379
1380 final var dims = getNumberOfDimensions();
1381 final var dimsPlus1 = dims + 1;
1382
1383 final var initialTransmittedPowerdBm = computeInitialTransmittedPowerdBm();
1384
1385 // for numerical accuracy reasons, a logarithmic version of the previous
1386 // formula will be used instead
1387 // Pr (dBm) = 10 * log(Pte * k / d^n) = 10*log(k) + 10*log(Pte) - 10*n*log(d)
1388
1389 fitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
1390 @Override
1391 public int getNumberOfDimensions() {
1392 return dimsPlus1;
1393 }
1394
1395 @Override
1396 public double[] createInitialParametersArray() {
1397 final var initial = new double[dimsPlus1];
1398
1399 // initial position
1400 computeInitialPosition(initial, dims);
1401
1402 // initial path loss exponent
1403 initial[dims] = initialPathLossExponent;
1404 return initial;
1405 }
1406
1407 @Override
1408 public double evaluate(
1409 final int i, final double[] point, final double[] params, final double[] derivatives) {
1410 var sqrDistance = 0.0;
1411 final var pathLossExponent = params[dims];
1412 for (var j = 0; j < dims; j++) {
1413 final var diff = params[j] - point[j];
1414 sqrDistance += diff * diff;
1415
1416 // n is mInitialPathLossExponent, which is typically 2.0
1417 derivatives[j] = -10.0 * pathLossExponent * diff;
1418 }
1419
1420 // derivatives respect position coordinates are (2D case):
1421 // f(x,y,n) = n*kdB -5*n*log((x - xap)^2 + (y - yap)^2)
1422 // df/dx = -5*n*2*(x - xap)/(ln(10)*((x - xap)^2 + (y - yap)^2)) = -10*n*diffX/(ln(10)*sqrDistance)
1423 // df/dy = -5*n*2*(y - yap)/(ln(10)*((x - xap)^2 + (y - yap)^2)) = -10*n*diffY/(ln(10)*sqrDistance)
1424 // df/dn = kdB -5*log((x - xap)^2 + (y - yap)^2) = kdB - 5*log(sqrDistance)
1425 final var ln10PerSqrDistance = Math.log(10.0) * sqrDistance;
1426 if (ln10PerSqrDistance != 0.0) {
1427 for (var j = 0; j < dims; j++) {
1428 derivatives[j] /= ln10PerSqrDistance;
1429 }
1430 }
1431
1432 // derivative respect to path loss exponent
1433 final var logSqrDistance = Math.log10(sqrDistance);
1434 derivatives[dims] = kdB - 5 * logSqrDistance;
1435
1436 // d^2 = (x - xap)^2 + (y - yap)^2
1437 // d^n = (d^2)^n/2
1438 // k = (c/(4*pi*f))^n
1439
1440 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n, where Pr is the received power
1441 // n is the path loss exponent
1442 // lambda = c/f, where lambda is wavelength,
1443 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the
1444 // received Gain
1445 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1446 // Pr (dBm) = 10*log(k^n) + 10*log(Pte) - 10*log(d^n) =
1447 // 10*log(k^n) + 10*log(Pte) - 10*log((d^2)^n/2) =
1448 // 10*n*log(k) + 10*log(Pte) - 10*n/2*log(d^2) =
1449 // 10*n*log(k) + 10*log(Pte) - 5*n*log(d^2) =
1450
1451 return pathLossExponent * kdB + initialTransmittedPowerdBm - 5.0 * pathLossExponent * logSqrDistance;
1452 }
1453 });
1454
1455 final var numReadings = readings.size();
1456 try {
1457 final var x = new Matrix(numReadings, dimsPlus1);
1458 final var y = new double[numReadings];
1459 final var standardDeviations = new double[numReadings];
1460 for (var i = 0; i < numReadings; i++) {
1461 reading = readings.get(i);
1462 final var position = reading.getPosition();
1463
1464 for (var j = 0; j < dims; j++) {
1465 x.setElementAt(i, j, position.getInhomogeneousCoordinate(j));
1466 }
1467 x.setElementAt(i, dims, initialPathLossExponent);
1468
1469 standardDeviations[i] = reading.getRssiStandardDeviation() != null ? reading.getRssiStandardDeviation()
1470 : DEFAULT_POWER_STANDARD_DEVIATION;
1471 y[i] = reading.getRssi();
1472 }
1473
1474 fitter.setInputData(x, y, standardDeviations);
1475 } catch (final AlgebraException ignore) {
1476 // never happens
1477 }
1478 }
1479
1480 /**
1481 * Setups fitter to estimate transmitted power and path loss exponent.
1482 *
1483 * @throws FittingException if Levenberg-Marquardt fitting fails.
1484 */
1485 private void setupFitterTransmittedPowerAndPathLossExponent() throws FittingException {
1486 // because all readings must belong to the same radio source, we
1487 // obtain the frequency of the first radio source on the first reading
1488 var reading = readings.get(0);
1489 final var frequency = reading.getSource().getFrequency();
1490
1491 // n = 2.0, is the path loss exponent
1492
1493 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n, where Pr is the received power
1494 // lambda = c/f, where lambda is wavelength,
1495 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the received Gain
1496 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1497
1498
1499 // k is defined so that: Pr = Pte * k^n / d^n so that
1500 // k = (c/(4*pi*f))
1501 final var k = SPEED_OF_LIGHT / (4.0 * Math.PI * frequency);
1502 final var kdB = 10.0 * Math.log10(k);
1503
1504 final var initialTransmittedPowerdBm = computeInitialTransmittedPowerdBm();
1505
1506 // for numerical accuracy reasons, a logarithmic version of the previous
1507 // formula will be used instead
1508 // Pr (dBm) = 10 * log(Pte * k^n / d^n) = 10*n*log(k) + 10*log(Pte) - 10*n*log(d)
1509
1510 fitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
1511 @Override
1512 public int getNumberOfDimensions() {
1513 return 2;
1514 }
1515
1516 @Override
1517 public double[] createInitialParametersArray() {
1518 final var initial = new double[2];
1519
1520 // initial transmitted power
1521 initial[0] = initialTransmittedPowerdBm;
1522
1523 // initial path loss exponent
1524 initial[1] = initialPathLossExponent;
1525
1526 return initial;
1527 }
1528
1529 @Override
1530 public double evaluate(
1531 final int i, final double[] point, final double[] params, final double[] derivatives) {
1532 final var sqrDistance = initialPosition.sqrDistanceTo(readings.get(i).getPosition());
1533 final var transmittedPowerdBm = params[0];
1534 final var pathLossExponent = params[1];
1535
1536 // derivative respect transmitted power
1537 derivatives[0] = 1.0;
1538
1539 // derivative respect to path loss exponent
1540 final var logSqrDistance = Math.log10(sqrDistance);
1541 derivatives[1] = kdB - 5 * logSqrDistance;
1542
1543 // d^2 = (x - xap)^2 + (y - yap)^2
1544 // d^n = (d^2)^n/2
1545 // k = (c/(4*pi*f))^n
1546
1547 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n, where Pr is the received power
1548 // n is the path loss exponent
1549 // lambda = c/f, where lambda is wavelength,
1550 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the
1551 // received Gain
1552 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1553 // Pr (dBm) = 10*log(k^n) + 10*log(Pte) - 10*log(d^n) =
1554 // 10*log(k^n) + 10*log(Pte) - 10*log((d^2)^n/2) =
1555 // 10*n*log(k) + 10*log(Pte) - 10*n/2*log(d^2) =
1556 // 10*n*log(k) + 10*log(Pte) - 5*n*log(d^2) =
1557
1558 return pathLossExponent * kdB + transmittedPowerdBm - 5.0 * pathLossExponent * logSqrDistance;
1559 }
1560 });
1561
1562 final var numReadings = readings.size();
1563 try {
1564 final var x = new Matrix(numReadings, 2);
1565 final var y = new double[numReadings];
1566 final var standardDeviations = new double[numReadings];
1567 for (var i = 0; i < numReadings; i++) {
1568 reading = readings.get(i);
1569
1570 x.setElementAt(i, 0, initialTransmittedPowerdBm);
1571 x.setElementAt(i, 1, initialPathLossExponent);
1572
1573 standardDeviations[i] = reading.getRssiStandardDeviation() != null ? reading.getRssiStandardDeviation()
1574 : DEFAULT_POWER_STANDARD_DEVIATION;
1575 y[i] = reading.getRssi();
1576 }
1577
1578 fitter.setInputData(x, y, standardDeviations);
1579 } catch (AlgebraException ignore) {
1580 // never happens
1581 }
1582 }
1583
1584 /**
1585 * Setups fitter to estimate transmitted power, position and path
1586 * loss exponent.
1587 *
1588 * @throws FittingException if Levenberg-Marquardt fitting fails.
1589 */
1590 private void setupFitterPositionTransmittedPowerAndPathLossExponent() throws FittingException {
1591 // because all readings must belong to the same radio source, we
1592 // obtain the frequency of the first radio source on the first reading
1593 var reading = readings.get(0);
1594 final var frequency = reading.getSource().getFrequency();
1595
1596 // n = 2.0, is the path loss exponent
1597
1598 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n, where Pr is the received power
1599 // lambda = c/f, where lambda is wavelength,
1600 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the received Gain
1601 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1602
1603 // k is defined so that: Pr = Pte * k^n / d^n so that
1604 // k = (c/(4*pi*f))
1605 final var k = SPEED_OF_LIGHT / (4.0 * Math.PI * frequency);
1606 final var kdB = 10.0 * Math.log10(k);
1607
1608 final var dims = getNumberOfDimensions();
1609 final var dimsPlus1 = dims + 1;
1610 final var dimsPlus2 = dims + 2;
1611
1612 final var initialTransmittedPowerdBm = computeInitialTransmittedPowerdBm();
1613
1614 // for numerical accuracy reasons, a logarithmic version of the previous
1615 // formula will be used instead
1616 // Pr (dBm) = 10 * log(Pte * k^n / d^n) = 10*n*log(k) + 10*log(Pte) - 10*n*log(d)
1617
1618 fitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
1619
1620 @Override
1621 public int getNumberOfDimensions() {
1622 return dimsPlus2;
1623 }
1624
1625 @Override
1626 public double[] createInitialParametersArray() {
1627 final var initial = new double[dimsPlus2];
1628
1629 // initial position
1630 computeInitialPosition(initial, dims);
1631
1632 // initial transmitted power
1633 initial[dims] = initialTransmittedPowerdBm;
1634
1635 // initial path loss exponent
1636 initial[dimsPlus1] = initialPathLossExponent;
1637
1638 return initial;
1639 }
1640
1641 @Override
1642 public double evaluate(
1643 final int i, final double[] point, final double[] params, final double[] derivatives) {
1644 var sqrDistance = 0.0;
1645 final var pathLossExponent = params[dimsPlus1];
1646 for (var j = 0; j < dims; j++) {
1647 final var diff = params[j] - point[j];
1648 sqrDistance += diff * diff;
1649
1650 // n is mInitialPathLossExponent, which is typically 2.0
1651 derivatives[j] = -10.0 * pathLossExponent * diff;
1652 }
1653
1654 final var transmittedPowerdBm = params[dims];
1655
1656 // derivatives respect position coordinates are (2D case):
1657 // f(x,y,n) = n*kdB -5*n*log((x - xap)^2 + (y - yap)^2)
1658 // df/dx = -5*n*2*(x - xap)/(ln(10)*((x - xap)^2 + (y - yap)^2)) = -10*n*diffX/(ln(10)*sqrDistance)
1659 // df/dy = -5*n*2*(y - yap)/(ln(10)*((x - xap)^2 + (y - yap)^2)) = -10*n*diffY/(ln(10)*sqrDistance)
1660 // df/dn = kdB -5*log((x - xap)^2 + (y - yap)^2) = kdB - 5*log(sqrDistance)
1661 final var ln10PerSqrDistance = Math.log(10.0) * sqrDistance;
1662 if (ln10PerSqrDistance != 0.0) {
1663 for (var j = 0; j < dims; j++) {
1664 derivatives[j] /= ln10PerSqrDistance;
1665 }
1666 }
1667
1668 // derivative respect transmitted power
1669 derivatives[dims] = 1.0;
1670
1671 // derivative respect to path loss exponent
1672 final var logSqrDistance = Math.log10(sqrDistance);
1673 derivatives[dimsPlus1] = kdB - 5 * logSqrDistance;
1674
1675 // d^2 = (x - xap)^2 + (y - yap)^2
1676 // d^n = (d^2)^n/2
1677 // k = (c/(4*pi*f))^n
1678
1679 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n, where Pr is the received power
1680 // n is the path loss exponent
1681 // lambda = c/f, where lambda is wavelength,
1682 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the
1683 // received Gain
1684 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1685 // Pr (dBm) = 10*log(k^n) + 10*log(Pte) - 10*log(d^n) =
1686 // 10*log(k^n) + 10*log(Pte) - 10*log((d^2)^n/2) =
1687 // 10*n*log(k) + 10*log(Pte) - 10*n/2*log(d^2) =
1688 // 10*n*log(k) + 10*log(Pte) - 5*n*log(d^2) =
1689
1690 return pathLossExponent * kdB + transmittedPowerdBm - 5.0 * pathLossExponent * logSqrDistance;
1691 }
1692 });
1693
1694 final var numReadings = readings.size();
1695 try {
1696 final var x = new Matrix(numReadings, dimsPlus2);
1697 final var y = new double[numReadings];
1698 final var standardDeviations = new double[numReadings];
1699 for (var i = 0; i < numReadings; i++) {
1700 reading = readings.get(i);
1701 final var position = reading.getPosition();
1702
1703 for (var j = 0; j < dims; j++) {
1704 x.setElementAt(i, j, position.getInhomogeneousCoordinate(j));
1705 }
1706 x.setElementAt(i, dims, initialTransmittedPowerdBm);
1707 x.setElementAt(i, dimsPlus1, initialPathLossExponent);
1708
1709 standardDeviations[i] = reading.getRssiStandardDeviation() != null ? reading.getRssiStandardDeviation()
1710 : DEFAULT_POWER_STANDARD_DEVIATION;
1711 y[i] = reading.getRssi();
1712 }
1713
1714 fitter.setInputData(x, y, standardDeviations);
1715 } catch (final AlgebraException ignore) {
1716 // never happens
1717 }
1718 }
1719
1720 /**
1721 * Computes initial transmitted power expressed in dBm's.
1722 * If no initial transmitted power is provided, the average of all measures
1723 * is used, otherwise provided value is used.
1724 *
1725 * @return initial transmitted power.
1726 */
1727 private double computeInitialTransmittedPowerdBm() {
1728 if (initialTransmittedPowerdBm == null) {
1729 // compute average transmitted power (in mW)
1730 final var num = readings.size();
1731 var result = 0.0;
1732 for (final var reading : readings) {
1733 final var rssi = reading.getRssi();
1734 result += rssi / num;
1735 }
1736 return result;
1737 } else {
1738 // convert initial value
1739 return initialTransmittedPowerdBm;
1740 }
1741 }
1742
1743 /**
1744 * Computes initial position.
1745 *
1746 * @param result array where result will be stored.
1747 * @param dims number of dimensions of position coordinates (either 2 or 3).
1748 */
1749 private void computeInitialPosition(final double[] result, final int dims) {
1750 if (initialPosition == null) {
1751 final var num = readings.size();
1752
1753 // compute average centroid of fingerprint positions
1754 for (final var reading : readings) {
1755 final var position = reading.getPosition();
1756 for (var i = 0; i < dims; i++) {
1757 result[i] += position.getInhomogeneousCoordinate(i) / num;
1758 }
1759 }
1760 } else {
1761 // copy initial position
1762 for (var i = 0; i < dims; i++) {
1763 result[i] = initialPosition.getInhomogeneousCoordinate(i);
1764 }
1765 }
1766 }
1767 }