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.Point2D;
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 2D 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 devices where
46   * physical access is not possible), this implementation will estimate the
47   * equivalent transmitted power as: Pte = Pt * Gt * Gr.
48   * If RssiReadings 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 LMedSRobustRssiRadioSourceEstimator2D<S extends RadioSource> extends RobustRssiRadioSourceEstimator2D<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 LMedSRobustRssiRadioSourceEstimator2D() {
114         super();
115     }
116 
117     /**
118      * Constructor.
119      * Sets signal readings belonging to the same radio source.
120      *
121      * @param readings signal readings belonging to the same radio source.
122      * @throws IllegalArgumentException if readings are not valid.
123      */
124     public LMedSRobustRssiRadioSourceEstimator2D(final List<? extends RssiReadingLocated<S, Point2D>> 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 LMedSRobustRssiRadioSourceEstimator2D(final RobustRssiRadioSourceEstimatorListener<S, Point2D> 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 LMedSRobustRssiRadioSourceEstimator2D(
146             final List<? extends RssiReadingLocated<S, Point2D>> readings,
147             final RobustRssiRadioSourceEstimatorListener<S, Point2D> 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 LMedSRobustRssiRadioSourceEstimator2D(
161             final List<? extends RssiReadingLocated<S, Point2D>> readings, final Point2D 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 LMedSRobustRssiRadioSourceEstimator2D(final Point2D 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 LMedSRobustRssiRadioSourceEstimator2D(
183             final Point2D initialPosition, final RobustRssiRadioSourceEstimatorListener<S, Point2D> 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 LMedSRobustRssiRadioSourceEstimator2D(
198             final List<? extends RssiReadingLocated<S, Point2D>> readings, final Point2D initialPosition,
199             final RobustRssiRadioSourceEstimatorListener<S, Point2D> 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 LMedSRobustRssiRadioSourceEstimator2D(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 LMedSRobustRssiRadioSourceEstimator2D(
225             final List<? extends RssiReadingLocated<S, Point2D>> 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 LMedSRobustRssiRadioSourceEstimator2D(
238             final Double initialTransmittedPowerdBm,
239             final RobustRssiRadioSourceEstimatorListener<S, Point2D> 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 LMedSRobustRssiRadioSourceEstimator2D(
255             final List<? extends RssiReadingLocated<S, Point2D>> readings, final Double initialTransmittedPowerdBm,
256             final RobustRssiRadioSourceEstimatorListener<S, Point2D> listener) {
257         super(readings, initialTransmittedPowerdBm, listener);
258     }
259 
260     /**
261      * Constructor.
262      * Sets signal readings belonging to the same radio source.
263      *
264      * @param readings                   signal readings belonging to the same radio source.
265      * @param initialPosition            initial position to start the estimation of radio
266      *                                   source position.
267      * @param initialTransmittedPowerdBm initial transmitted power to start the
268      *                                   estimation of radio source transmitted power
269      *                                   (expressed in dBm's).
270      * @throws IllegalArgumentException if readings are not valid.
271      */
272     public LMedSRobustRssiRadioSourceEstimator2D(
273             final List<? extends RssiReadingLocated<S, Point2D>> readings,
274             final Point2D initialPosition, Double initialTransmittedPowerdBm) {
275         super(readings, initialPosition, initialTransmittedPowerdBm);
276     }
277 
278     /**
279      * Constructor.
280      *
281      * @param initialPosition            initial position to start the estimation of radio
282      *                                   source position.
283      * @param initialTransmittedPowerdBm initial transmitted power to start the
284      *                                   estimation of radio source transmitted power
285      *                                   (expressed in dBm's).
286      */
287     public LMedSRobustRssiRadioSourceEstimator2D(
288             final Point2D initialPosition, final Double initialTransmittedPowerdBm) {
289         super(initialPosition, initialTransmittedPowerdBm);
290     }
291 
292     /**
293      * Constructor.
294      *
295      * @param initialPosition            initial position to start the estimation of radio
296      *                                   source position.
297      * @param initialTransmittedPowerdBm initial transmitted power to start the
298      *                                   estimation of radio source transmitted power
299      *                                   (expressed in dBm's).
300      * @param listener                   in charge of attending events raised by this instance.
301      */
302     public LMedSRobustRssiRadioSourceEstimator2D(
303             final Point2D initialPosition, final Double initialTransmittedPowerdBm,
304             final RobustRssiRadioSourceEstimatorListener<S, Point2D> listener) {
305         super(initialPosition, initialTransmittedPowerdBm, listener);
306     }
307 
308     /**
309      * Constructor.
310      * Sets signal readings belonging to the same radio source.
311      *
312      * @param readings                   signal readings belonging to the same radio source.
313      * @param initialPosition            initial position to start the estimation of radio
314      *                                   source position.
315      * @param initialTransmittedPowerdBm initial transmitted power to start the
316      *                                   estimation of radio source transmitted power
317      *                                   (expressed in dBm's).
318      * @param listener                   listener in charge of attending events raised by this instance.
319      * @throws IllegalArgumentException if readings are not valid.
320      */
321     public LMedSRobustRssiRadioSourceEstimator2D(
322             final List<? extends RssiReadingLocated<S, Point2D>> readings,
323             final Point2D initialPosition, final Double initialTransmittedPowerdBm,
324             final RobustRssiRadioSourceEstimatorListener<S, Point2D> listener) {
325         super(readings, initialPosition, initialTransmittedPowerdBm, listener);
326     }
327 
328     /**
329      * Constructor.
330      * Sets signal readings belonging to the same radio source.
331      *
332      * @param readings                   signal readings belonging to the same radio source.
333      * @param initialPosition            initial position to start the estimation of radio
334      *                                   source position.
335      * @param initialTransmittedPowerdBm initial transmitted power to start the
336      *                                   estimation of radio source transmitted power
337      *                                   (expressed in dBm's).
338      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
339      * @throws IllegalArgumentException if readings are not valid.
340      */
341     public LMedSRobustRssiRadioSourceEstimator2D(
342             final List<? extends RssiReadingLocated<S, Point2D>> readings,
343             final Point2D initialPosition, final Double initialTransmittedPowerdBm,
344             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 LMedSRobustRssiRadioSourceEstimator2D(
359             final Point2D 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 LMedSRobustRssiRadioSourceEstimator2D(
376             final Point2D initialPosition, final Double initialTransmittedPowerdBm,
377             final double initialPathLossExponent,
378             final RobustRssiRadioSourceEstimatorListener<S, Point2D> listener) {
379         super(initialPosition, initialTransmittedPowerdBm, initialPathLossExponent, listener);
380     }
381 
382     /**
383      * Constructor.
384      * Sets signal readings belonging to the same radio source.
385      *
386      * @param readings                   signal readings belonging to the same radio source.
387      * @param initialPosition            initial position to start the estimation of radio
388      *                                   source position.
389      * @param initialTransmittedPowerdBm initial transmitted power to start the
390      *                                   estimation of radio source transmitted power
391      *                                   (expressed in dBm's).
392      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
393      * @param listener                   listener in charge of attending events raised by this instance.
394      * @throws IllegalArgumentException if readings are not valid.
395      */
396     public LMedSRobustRssiRadioSourceEstimator2D(
397             final List<? extends RssiReadingLocated<S, Point2D>> readings, final Point2D initialPosition,
398             final Double initialTransmittedPowerdBm, final double initialPathLossExponent,
399             final RobustRssiRadioSourceEstimatorListener<S, Point2D> listener) {
400         super(readings, initialPosition, initialTransmittedPowerdBm, initialPathLossExponent, listener);
401     }
402 
403     /**
404      * Returns threshold to be used to keep the algorithm iterating in case that
405      * best estimated threshold using median of residuals is not small enough.
406      * Once a solution is found that generates a threshold below this value, the
407      * algorithm will stop.
408      * The stop threshold can be used to prevent the LMedS algorithm to iterate
409      * too many times in cases where samples have a very similar accuracy.
410      * For instance, in cases where proportion of outliers is very small (close
411      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
412      * iterate for a long time trying to find the best solution when indeed
413      * there is no need to do that if a reasonable threshold has already been
414      * reached.
415      * Because of this behaviour the stop threshold can be set to a value much
416      * lower than the one typically used in RANSAC, and yet the algorithm could
417      * still produce even smaller thresholds in estimated results.
418      *
419      * @return stop threshold to stop the algorithm prematurely when a certain
420      * accuracy has been reached.
421      */
422     public double getStopThreshold() {
423         return stopThreshold;
424     }
425 
426     /**
427      * Sets threshold to be used to keep the algorithm iterating in case that
428      * best estimated threshold using median of residuals is not small enough.
429      * Once a solution is found that generates a threshold below this value,
430      * the algorithm will stop.
431      * The stop threshold can be used to prevent the LMedS algorithm to iterate
432      * too many times in cases where samples have a very similar accuracy.
433      * For instance, in cases where proportion of outliers is very small (close
434      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
435      * iterate for a long time trying to find the best solution when indeed
436      * there is no need to do that if a reasonable threshold has already been
437      * reached.
438      * Because of this behaviour the stop threshold can be set to a value much
439      * lower than the one typically used in RANSAC, and yet the algorithm could
440      * still produce even smaller thresholds in estimated results.
441      *
442      * @param stopThreshold stop threshold to stop the algorithm prematurely
443      *                      when a certain accuracy has been reached.
444      * @throws IllegalArgumentException if provided value is zero or negative.
445      * @throws LockedException          if this solver is locked.
446      */
447     public void setStopThreshold(final double stopThreshold) throws LockedException {
448         if (isLocked()) {
449             throw new LockedException();
450         }
451         if (stopThreshold <= MIN_STOP_THRESHOLD) {
452             throw new IllegalArgumentException();
453         }
454 
455         this.stopThreshold = stopThreshold;
456     }
457 
458     /**
459      * Robustly estimates position, transmitted power and path-loss exponent for a
460      * radio source.
461      *
462      * @throws LockedException          if instance is busy during estimation.
463      * @throws NotReadyException        if estimator is not ready.
464      * @throws RobustEstimatorException if estimation fails for any reason
465      *                                  (i.e. numerical instability, no solution available, etc).
466      */
467     @Override
468     public void estimate() throws LockedException, NotReadyException, RobustEstimatorException {
469         if (isLocked()) {
470             throw new LockedException();
471         }
472         if (!isReady()) {
473             throw new NotReadyException();
474         }
475 
476         final var innerEstimator = new LMedSRobustEstimator<>(new LMedSRobustEstimatorListener<Solution<Point2D>>() {
477             @Override
478             public int getTotalSamples() {
479                 return readings.size();
480             }
481 
482             @Override
483             public int getSubsetSize() {
484                 return Math.max(preliminarySubsetSize, getMinReadings());
485             }
486 
487             @Override
488             public void estimatePreliminarSolutions(
489                     final int[] samplesIndices, final List<Solution<Point2D>> solutions) {
490                 solvePreliminarySolutions(samplesIndices, solutions);
491             }
492 
493             @Override
494             public double computeResidual(final Solution<Point2D> currentEstimation, final int i) {
495                 return residual(currentEstimation, i);
496             }
497 
498             @Override
499             public boolean isReady() {
500                 return LMedSRobustRssiRadioSourceEstimator2D.this.isReady();
501             }
502 
503             @Override
504             public void onEstimateStart(final RobustEstimator<Solution<Point2D>> estimator) {
505                 // no action needed
506             }
507 
508             @Override
509             public void onEstimateEnd(final RobustEstimator<Solution<Point2D>> estimator) {
510                 // no action needed
511             }
512 
513             @Override
514             public void onEstimateNextIteration(
515                     final RobustEstimator<Solution<Point2D>> estimator, final int iteration) {
516                 if (listener != null) {
517                     listener.onEstimateNextIteration(LMedSRobustRssiRadioSourceEstimator2D.this, iteration);
518                 }
519             }
520 
521             @Override
522             public void onEstimateProgressChange(
523                     final RobustEstimator<Solution<Point2D>> estimator, final float progress) {
524                 if (listener != null) {
525                     listener.onEstimateProgressChange(LMedSRobustRssiRadioSourceEstimator2D.this, progress);
526                 }
527             }
528         });
529 
530         try {
531             locked = true;
532 
533             if (listener != null) {
534                 listener.onEstimateStart(this);
535             }
536 
537             inliersData = null;
538             innerEstimator.setConfidence(confidence);
539             innerEstimator.setMaxIterations(maxIterations);
540             innerEstimator.setProgressDelta(progressDelta);
541             final var result = innerEstimator.estimate();
542             inliersData = innerEstimator.getInliersData();
543             attemptRefine(result);
544 
545             if (listener != null) {
546                 listener.onEstimateEnd(this);
547             }
548 
549         } catch (final com.irurueta.numerical.LockedException e) {
550             throw new LockedException(e);
551         } catch (final com.irurueta.numerical.NotReadyException e) {
552             throw new NotReadyException(e);
553         } finally {
554             locked = false;
555         }
556     }
557 
558     /**
559      * Returns method being used for robust estimation.
560      *
561      * @return method being used for robust estimation.
562      */
563     @Override
564     public RobustEstimatorMethod getMethod() {
565         return RobustEstimatorMethod.LMEDS;
566     }
567 }