View Javadoc
1   /*
2    * Copyright (C) 2018 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.navigation.indoor.radiosource;
17  
18  import com.irurueta.geometry.Point3D;
19  import com.irurueta.navigation.LockedException;
20  import com.irurueta.navigation.NotReadyException;
21  import com.irurueta.navigation.indoor.RadioSource;
22  import com.irurueta.navigation.indoor.RssiReadingLocated;
23  import com.irurueta.numerical.robust.LMedSRobustEstimator;
24  import com.irurueta.numerical.robust.LMedSRobustEstimatorListener;
25  import com.irurueta.numerical.robust.RobustEstimator;
26  import com.irurueta.numerical.robust.RobustEstimatorException;
27  import com.irurueta.numerical.robust.RobustEstimatorMethod;
28  
29  import java.util.List;
30  
31  /**
32   * Robustly estimate 3D position, transmitted power and path-loss exponent of a radio source
33   * (e.g. Wi-Fi access point or bluetooth beacon), by discarding outliers using LMedS
34   * algorithm and assuming that the radio source emits isotropically following the
35   * expression below:
36   * Pr = Pt*Gt*Gr*lambda^2 / (4*pi*d)^2,
37   * where Pr is the received power (expressed in mW),
38   * Gt is the Gain of the transmission antenna
39   * Gr is the Gain of the receiver antenna
40   * d is the distance between emitter and receiver
41   * and lambda is the wavelength and is equal to: lambda = c / f,
42   * where c is the speed of light
43   * and f is the carrier frequency of the radio signal.
44   * Because usually information about the antenna of the radio source cannot be
45   * retrieved (because many measurements are made on unknown access points where
46   * physical access is not possible), this implementation will estimate the
47   * equivalent transmitted power as: Pte = Pt * Gt * Gr.
48   * If WifiReadings contain RSSI standard deviations, those values will be used,
49   * otherwise it will be assumed an RSSI standard deviation of 1 dB.
50   * Implementations of this class should be able to detect and discard outliers in
51   * order to find the best solution.
52   * <p>
53   * IMPORTANT: When using this class estimation can be done using a
54   * combination of radio source position, transmitted power and path loss
55   * exponent. However enabling all three estimations usually achieves
56   * inaccurate results. When using this class, estimation must be of at least
57   * one parameter (position, transmitted power or path loss exponent) when
58   * initial values are provided for the other two, and at most it should consist
59   * of two parameters (either position and transmitted power, position and
60   * path loss exponent or transmitted power and path loss exponent), providing an
61   * initial value for the remaining parameter.
62   *
63   * @param <S> a {@link RadioSource} type.
64   */
65  @SuppressWarnings("Duplicates")
66  public class LMedSRobustRssiRadioSourceEstimator3D<S extends RadioSource> extends RobustRssiRadioSourceEstimator3D<S> {
67  
68      /**
69       * Default value to be used for stop threshold. Stop threshold can be used to
70       * avoid keeping the algorithm unnecessarily iterating in case that best
71       * estimated threshold using median of residuals is not small enough. Once a
72       * solution is found that generates a threshold below this value, the
73       * algorithm will stop.
74       * The stop threshold can be used to prevent the LMedS algorithm iterating
75       * too many times in cases where samples have a very similar accuracy.
76       * For instance, in cases where proportion of outliers is very small (close
77       * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
78       * iterate for a long time trying to find the best solution when indeed
79       * there is no need to do that if a reasonable threshold has already been
80       * reached.
81       * Because of this behaviour the stop threshold can be set to a value much
82       * lower than the one typically used in RANSAC, and yet the algorithm could
83       * still produce even smaller thresholds in estimated results.
84       */
85      public static final double DEFAULT_STOP_THRESHOLD = 1e-4;
86  
87      /**
88       * Minimum allowed stop threshold value.
89       */
90      public static final double MIN_STOP_THRESHOLD = 0.0;
91  
92      /**
93       * Threshold to be used to keep the algorithm iterating in case that best
94       * estimated threshold using median of residuals is not small enough. Once
95       * a solution is found that generates a threshold below this value, the
96       * algorithm will stop.
97       * The stop threshold can be used to prevent the LMedS algorithm iterating
98       * too many times in cases where samples have a very similar accuracy.
99       * For instance, in cases where proportion of outliers is very small (close
100      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
101      * iterate for a long time trying to find the best solution when indeed
102      * there is no need to do that if a reasonable threshold has already been
103      * reached.
104      * Because of this behaviour the stop threshold can be set to a value much
105      * lower than the one typically used in RANSAC, and yet the algorithm could
106      * still produce even smaller thresholds in estimated results.
107      */
108     private double stopThreshold = DEFAULT_STOP_THRESHOLD;
109 
110     /**
111      * Constructor.
112      */
113     public LMedSRobustRssiRadioSourceEstimator3D() {
114         super();
115     }
116 
117     /**
118      * Constructor.
119      * Sets signal readings belonging to the same radio source.
120      *
121      * @param readings Wi-Fi signal readings belonging to the same radio source.
122      * @throws IllegalArgumentException if readings are not valid.
123      */
124     public LMedSRobustRssiRadioSourceEstimator3D(final List<? extends RssiReadingLocated<S, Point3D>> readings) {
125         super(readings);
126     }
127 
128     /**
129      * Constructor.
130      *
131      * @param listener listener in charge of attending events raised by this instance.
132      */
133     public LMedSRobustRssiRadioSourceEstimator3D(final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
134         super(listener);
135     }
136 
137     /**
138      * Constructor.
139      * Sets signal readings belonging to the same radio source.
140      *
141      * @param readings signal readings belonging to the same radio source.
142      * @param listener listener in charge of attending events raised by this instance.
143      * @throws IllegalArgumentException if readings are not valid.
144      */
145     public LMedSRobustRssiRadioSourceEstimator3D(
146             final List<? extends RssiReadingLocated<S, Point3D>> readings,
147             final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
148         super(readings, listener);
149     }
150 
151     /**
152      * Constructor.
153      * Sets signal readings belonging to the same radio source.
154      *
155      * @param readings        signal readings belonging to the same radio source.
156      * @param initialPosition initial position to start the estimation of radio
157      *                        source position.
158      * @throws IllegalArgumentException if readings are not valid.
159      */
160     public LMedSRobustRssiRadioSourceEstimator3D(
161             final List<? extends RssiReadingLocated<S, Point3D>> readings, final Point3D initialPosition) {
162         super(readings, initialPosition);
163     }
164 
165     /**
166      * Constructor.
167      *
168      * @param initialPosition initial position to start the estimation of radio
169      *                        source position.
170      */
171     public LMedSRobustRssiRadioSourceEstimator3D(final Point3D initialPosition) {
172         super(initialPosition);
173     }
174 
175     /**
176      * Constructor.
177      *
178      * @param initialPosition initial position to start the estimation of radio
179      *                        source position.
180      * @param listener        listener in charge of attending events raised by this instance.
181      */
182     public LMedSRobustRssiRadioSourceEstimator3D(
183             final Point3D initialPosition, final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
184         super(initialPosition, listener);
185     }
186 
187     /**
188      * Constructor.
189      * Sets signal readings belonging to the same radio source.
190      *
191      * @param readings        signal readings belonging to the same radio source.
192      * @param initialPosition initial position to start the estimation of radio
193      *                        source position.
194      * @param listener        listener in charge of attending events raised by this instance.
195      * @throws IllegalArgumentException if readings are not valid.
196      */
197     public LMedSRobustRssiRadioSourceEstimator3D(
198             final List<? extends RssiReadingLocated<S, Point3D>> readings, final Point3D initialPosition,
199             final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
200         super(readings, initialPosition, listener);
201     }
202 
203     /**
204      * Constructor.
205      *
206      * @param initialTransmittedPowerdBm initial transmitted power to start the
207      *                                   estimation of radio source transmitted power
208      *                                   (expressed in dBm's)
209      */
210     public LMedSRobustRssiRadioSourceEstimator3D(final Double initialTransmittedPowerdBm) {
211         super(initialTransmittedPowerdBm);
212     }
213 
214     /**
215      * Constructor.
216      * Sets signal readings belonging to the same radio source.
217      *
218      * @param readings                   signal readings belonging to the same radio source.
219      * @param initialTransmittedPowerdBm initial transmitted power to start the
220      *                                   estimation of radio source transmitted power
221      *                                   (expressed in dBm's)
222      * @throws IllegalArgumentException if readings are not valid.
223      */
224     public LMedSRobustRssiRadioSourceEstimator3D(
225             final List<? extends RssiReadingLocated<S, Point3D>> readings, final Double initialTransmittedPowerdBm) {
226         super(readings, initialTransmittedPowerdBm);
227     }
228 
229     /**
230      * Constructor.
231      *
232      * @param initialTransmittedPowerdBm initial transmitted power to start the
233      *                                   estimation of radio source transmitted power
234      *                                   (expressed in dBm's).
235      * @param listener                   listener in charge of attending events raised by this instance.
236      */
237     public LMedSRobustRssiRadioSourceEstimator3D(
238             final Double initialTransmittedPowerdBm,
239             final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
240         super(initialTransmittedPowerdBm, listener);
241     }
242 
243     /**
244      * Constructor.
245      * Sets signal readings belonging to the same radio source.
246      *
247      * @param readings                   signal readings belonging to the same radio source.
248      * @param initialTransmittedPowerdBm initial transmitted power to start the
249      *                                   estimation of radio source transmitted power
250      *                                   (expressed in dBm's)
251      * @param listener                   listener in charge of attending events raised by this instance.
252      * @throws IllegalArgumentException if readings are not valid.
253      */
254     public LMedSRobustRssiRadioSourceEstimator3D(
255             final List<? extends RssiReadingLocated<S, Point3D>> readings,
256             final Double initialTransmittedPowerdBm,
257             final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
258         super(readings, initialTransmittedPowerdBm, listener);
259     }
260 
261     /**
262      * Constructor.
263      * Sets signal readings belonging to the same radio source.
264      *
265      * @param readings                   signal readings belonging to the same radio source.
266      * @param initialPosition            initial position to start the estimation of radio
267      *                                   source position.
268      * @param initialTransmittedPowerdBm initial transmitted power to start the
269      *                                   estimation of radio source transmitted power
270      *                                   (expressed in dBm's).
271      * @throws IllegalArgumentException if readings are not valid.
272      */
273     public LMedSRobustRssiRadioSourceEstimator3D(
274             final List<? extends RssiReadingLocated<S, Point3D>> readings,
275             final Point3D initialPosition, final Double initialTransmittedPowerdBm) {
276         super(readings, initialPosition, initialTransmittedPowerdBm);
277     }
278 
279     /**
280      * Constructor.
281      *
282      * @param initialPosition            initial position to start the estimation of radio
283      *                                   source position.
284      * @param initialTransmittedPowerdBm initial transmitted power to start the
285      *                                   estimation of radio source transmitted power
286      *                                   (expressed in dBm's).
287      */
288     public LMedSRobustRssiRadioSourceEstimator3D(
289             final Point3D initialPosition, final Double initialTransmittedPowerdBm) {
290         super(initialPosition, initialTransmittedPowerdBm);
291     }
292 
293     /**
294      * Constructor.
295      *
296      * @param initialPosition            initial position to start the estimation of radio
297      *                                   source position.
298      * @param initialTransmittedPowerdBm initial transmitted power to start the
299      *                                   estimation of radio source transmitted power
300      *                                   (expressed in dBm's).
301      * @param listener                   in charge of attending events raised by this instance.
302      */
303     public LMedSRobustRssiRadioSourceEstimator3D(
304             final Point3D initialPosition, final Double initialTransmittedPowerdBm,
305             final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
306         super(initialPosition, initialTransmittedPowerdBm, listener);
307     }
308 
309     /**
310      * Constructor.
311      * Sets signal readings belonging to the same radio source.
312      *
313      * @param readings                   signal readings belonging to the same radio source.
314      * @param initialPosition            initial position to start the estimation of radio
315      *                                   source position.
316      * @param initialTransmittedPowerdBm initial transmitted power to start the
317      *                                   estimation of radio source transmitted power
318      *                                   (expressed in dBm's).
319      * @param listener                   listener in charge of attending events raised by this instance.
320      * @throws IllegalArgumentException if readings are not valid.
321      */
322     public LMedSRobustRssiRadioSourceEstimator3D(
323             final List<? extends RssiReadingLocated<S, Point3D>> readings,
324             final Point3D initialPosition, final Double initialTransmittedPowerdBm,
325             final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
326         super(readings, initialPosition, initialTransmittedPowerdBm, listener);
327     }
328 
329     /**
330      * Constructor.
331      * Sets signal readings belonging to the same radio source.
332      *
333      * @param readings                   signal readings belonging to the same radio source.
334      * @param initialPosition            initial position to start the estimation of radio
335      *                                   source position.
336      * @param initialTransmittedPowerdBm initial transmitted power to start the
337      *                                   estimation of radio source transmitted power
338      *                                   (expressed in dBm's).
339      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
340      * @throws IllegalArgumentException if readings are not valid.
341      */
342     public LMedSRobustRssiRadioSourceEstimator3D(
343             final List<? extends RssiReadingLocated<S, Point3D>> readings, final Point3D initialPosition,
344             final Double initialTransmittedPowerdBm, final double initialPathLossExponent) {
345         super(readings, initialPosition, initialTransmittedPowerdBm, initialPathLossExponent);
346     }
347 
348     /**
349      * Constructor.
350      *
351      * @param initialPosition            initial position to start the estimation of radio
352      *                                   source position.
353      * @param initialTransmittedPowerdBm initial transmitted power to start the
354      *                                   estimation of radio source transmitted power
355      *                                   (expressed in dBm's).
356      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
357      */
358     public LMedSRobustRssiRadioSourceEstimator3D(
359             final Point3D initialPosition, final Double initialTransmittedPowerdBm,
360             final double initialPathLossExponent) {
361         super(initialPosition, initialTransmittedPowerdBm, initialPathLossExponent);
362     }
363 
364     /**
365      * Constructor.
366      *
367      * @param initialPosition            initial position to start the estimation of radio
368      *                                   source position.
369      * @param initialTransmittedPowerdBm initial transmitted power to start the
370      *                                   estimation of radio source transmitted power
371      *                                   (expressed in dBm's).
372      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
373      * @param listener                   listener in charge of attending events raised by this instance.
374      */
375     public LMedSRobustRssiRadioSourceEstimator3D(
376             final Point3D initialPosition, final Double initialTransmittedPowerdBm,
377             final double initialPathLossExponent, final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
378         super(initialPosition, initialTransmittedPowerdBm, initialPathLossExponent, listener);
379     }
380 
381     /**
382      * Constructor.
383      * Sets signal readings belonging to the same radio source.
384      *
385      * @param readings                   signal readings belonging to the same radio source.
386      * @param initialPosition            initial position to start the estimation of radio
387      *                                   source position.
388      * @param initialTransmittedPowerdBm initial transmitted power to start the
389      *                                   estimation of radio source transmitted power
390      *                                   (expressed in dBm's).
391      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
392      * @param listener                   listener in charge of attending events raised by this instance.
393      * @throws IllegalArgumentException if readings are not valid.
394      */
395     public LMedSRobustRssiRadioSourceEstimator3D(
396             final List<? extends RssiReadingLocated<S, Point3D>> readings,
397             final Point3D initialPosition, final Double initialTransmittedPowerdBm,
398             final double initialPathLossExponent, final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
399         super(readings, initialPosition, initialTransmittedPowerdBm, initialPathLossExponent, listener);
400     }
401 
402     /**
403      * Returns threshold to be used to keep the algorithm iterating in case that
404      * best estimated threshold using median of residuals is not small enough.
405      * Once a solution is found that generates a threshold below this value, the
406      * algorithm will stop.
407      * The stop threshold can be used to prevent the LMedS algorithm to iterate
408      * too many times in cases where samples have a very similar accuracy.
409      * For instance, in cases where proportion of outliers is very small (close
410      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
411      * iterate for a long time trying to find the best solution when indeed
412      * there is no need to do that if a reasonable threshold has already been
413      * reached.
414      * Because of this behaviour the stop threshold can be set to a value much
415      * lower than the one typically used in RANSAC, and yet the algorithm could
416      * still produce even smaller thresholds in estimated results.
417      *
418      * @return stop threshold to stop the algorithm prematurely when a certain
419      * accuracy has been reached.
420      */
421     public double getStopThreshold() {
422         return stopThreshold;
423     }
424 
425     /**
426      * Sets threshold to be used to keep the algorithm iterating in case that
427      * best estimated threshold using median of residuals is not small enough.
428      * Once a solution is found that generates a threshold below this value,
429      * the algorithm will stop.
430      * The stop threshold can be used to prevent the LMedS algorithm to iterate
431      * too many times in cases where samples have a very similar accuracy.
432      * For instance, in cases where proportion of outliers is very small (close
433      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
434      * iterate for a long time trying to find the best solution when indeed
435      * there is no need to do that if a reasonable threshold has already been
436      * reached.
437      * Because of this behaviour the stop threshold can be set to a value much
438      * lower than the one typically used in RANSAC, and yet the algorithm could
439      * still produce even smaller thresholds in estimated results.
440      *
441      * @param stopThreshold stop threshold to stop the algorithm prematurely
442      *                      when a certain accuracy has been reached.
443      * @throws IllegalArgumentException if provided value is zero or negative.
444      * @throws LockedException          if this solver is locked.
445      */
446     public void setStopThreshold(final double stopThreshold) throws LockedException {
447         if (isLocked()) {
448             throw new LockedException();
449         }
450         if (stopThreshold <= MIN_STOP_THRESHOLD) {
451             throw new IllegalArgumentException();
452         }
453 
454         this.stopThreshold = stopThreshold;
455     }
456 
457     /**
458      * Robustly estimates position, transmitted power and path-loss exponent for a
459      * radio source.
460      *
461      * @throws LockedException          if instance is busy during estimation.
462      * @throws NotReadyException        if estimator is not ready.
463      * @throws RobustEstimatorException if estimation fails for any reason
464      *                                  (i.e. numerical instability, no solution available, etc).
465      */
466     @Override
467     public void estimate() throws LockedException, NotReadyException, RobustEstimatorException {
468         if (isLocked()) {
469             throw new LockedException();
470         }
471         if (!isReady()) {
472             throw new NotReadyException();
473         }
474 
475         final var innerEstimator = new LMedSRobustEstimator<>(new LMedSRobustEstimatorListener<Solution<Point3D>>() {
476             @Override
477             public int getTotalSamples() {
478                 return readings.size();
479             }
480 
481             @Override
482             public int getSubsetSize() {
483                 return Math.max(preliminarySubsetSize, getMinReadings());
484             }
485 
486             @Override
487             public void estimatePreliminarSolutions(
488                     final int[] samplesIndices, final List<Solution<Point3D>> solutions) {
489                 solvePreliminarySolutions(samplesIndices, solutions);
490             }
491 
492             @Override
493             public double computeResidual(final Solution<Point3D> currentEstimation, final int i) {
494                 return residual(currentEstimation, i);
495             }
496 
497             @Override
498             public boolean isReady() {
499                 return LMedSRobustRssiRadioSourceEstimator3D.this.isReady();
500             }
501 
502             @Override
503             public void onEstimateStart(final RobustEstimator<Solution<Point3D>> estimator) {
504                 // no action needed
505             }
506 
507             @Override
508             public void onEstimateEnd(final RobustEstimator<Solution<Point3D>> estimator) {
509                 // no action needed
510             }
511 
512             @Override
513             public void onEstimateNextIteration(
514                     final RobustEstimator<Solution<Point3D>> estimator, final int iteration) {
515                 if (listener != null) {
516                     listener.onEstimateNextIteration(LMedSRobustRssiRadioSourceEstimator3D.this, iteration);
517                 }
518             }
519 
520             @Override
521             public void onEstimateProgressChange(
522                     final RobustEstimator<Solution<Point3D>> estimator, final float progress) {
523                 if (listener != null) {
524                     listener.onEstimateProgressChange(LMedSRobustRssiRadioSourceEstimator3D.this, progress);
525                 }
526             }
527         });
528 
529         try {
530             locked = true;
531 
532             if (listener != null) {
533                 listener.onEstimateStart(this);
534             }
535 
536             inliersData = null;
537             innerEstimator.setConfidence(confidence);
538             innerEstimator.setMaxIterations(maxIterations);
539             innerEstimator.setProgressDelta(progressDelta);
540             final var result = innerEstimator.estimate();
541             inliersData = innerEstimator.getInliersData();
542             attemptRefine(result);
543 
544             if (listener != null) {
545                 listener.onEstimateEnd(this);
546             }
547 
548         } catch (final com.irurueta.numerical.LockedException e) {
549             throw new LockedException(e);
550         } catch (final com.irurueta.numerical.NotReadyException e) {
551             throw new NotReadyException(e);
552         } finally {
553             locked = false;
554         }
555     }
556 
557     /**
558      * Returns method being used for robust estimation.
559      *
560      * @return method being used for robust estimation.
561      */
562     @Override
563     public RobustEstimatorMethod getMethod() {
564         return RobustEstimatorMethod.LMEDS;
565     }
566 }