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.algebra.Matrix;
19  import com.irurueta.geometry.Point;
20  import com.irurueta.navigation.LockedException;
21  import com.irurueta.navigation.NotReadyException;
22  import com.irurueta.navigation.indoor.RadioSourceLocated;
23  import com.irurueta.navigation.indoor.ReadingLocated;
24  import com.irurueta.numerical.robust.InliersData;
25  import com.irurueta.numerical.robust.RobustEstimatorException;
26  import com.irurueta.numerical.robust.RobustEstimatorMethod;
27  
28  import java.util.List;
29  
30  /**
31   * Robustly estimates a radio source. Usually this implies at least the estimation of
32   * the radio source location, however, implementations of this class might estimate
33   * additional parameters.
34   *
35   * @param <P> a {@link Point} type.
36   * @param <R> a {@link ReadingLocated} type.
37   * @param <L> a {@link RobustRadioSourceEstimatorListener} type.
38   */
39  public abstract class RobustRadioSourceEstimator<P extends Point<?>, R extends ReadingLocated<P>,
40          L extends RobustRadioSourceEstimatorListener<? extends RobustRadioSourceEstimator<?, ?, ?>>> {
41  
42      /**
43       * Default robust estimator method when none is provided.
44       */
45      public static final RobustEstimatorMethod DEFAULT_ROBUST_METHOD = RobustEstimatorMethod.PROMEDS;
46  
47      /**
48       * Indicates that result is refined by default using all found inliers.
49       */
50      public static final boolean DEFAULT_REFINE_RESULT = true;
51  
52      /**
53       * Indicates that covariance is kept by default after refining result.
54       */
55      public static final boolean DEFAULT_KEEP_COVARIANCE = true;
56  
57      /**
58       * Default amount of progress variation before notifying a change in estimation progress.
59       * By default, this is set to 5%.
60       */
61      public static final float DEFAULT_PROGRESS_DELTA = 0.05f;
62  
63      /**
64       * Minimum allowed value for progress delta.
65       */
66      public static final float MIN_PROGRESS_DELTA = 0.0f;
67  
68      /**
69       * Maximum allowed value for progress delta.
70       */
71      public static final float MAX_PROGRESS_DELTA = 1.0f;
72  
73      /**
74       * Constant defining default confidence of the estimated result, which is
75       * 99%. This means that with a probability of 99% estimation will be
76       * accurate because chosen sub-samples will be inliers.
77       */
78      public static final double DEFAULT_CONFIDENCE = 0.99;
79  
80      /**
81       * Default maximum allowed number of iterations.
82       */
83      public static final int DEFAULT_MAX_ITERATIONS = 5000;
84  
85      /**
86       * Minimum allowed confidence value.
87       */
88      public static final double MIN_CONFIDENCE = 0.0;
89  
90      /**
91       * Maximum allowed confidence value.
92       */
93      public static final double MAX_CONFIDENCE = 1.0;
94  
95      /**
96       * Minimum allowed number of iterations.
97       */
98      public static final int MIN_ITERATIONS = 1;
99  
100     /**
101      * Signal readings belonging to the same radio source to be estimated.
102      */
103     protected List<? extends R> readings;
104 
105     /**
106      * Listener to be notified of events such as when estimation starts, ends or its
107      * progress significantly changes.
108      */
109     protected L listener;
110 
111     /**
112      * Estimated position.
113      */
114     protected P estimatedPosition;
115 
116     /**
117      * Indicates if this instance is locked because estimation is being executed.
118      */
119     protected boolean locked;
120 
121     /**
122      * Amount of progress variation before notifying a progress change during estimation.
123      */
124     protected float progressDelta = DEFAULT_PROGRESS_DELTA;
125 
126     /**
127      * Amount of confidence expressed as a value between 0.0 and 1.0 (which is equivalent
128      * to 100%). The amount of confidence indicates the probability that the estimated
129      * result is correct. Usually this value will be close to 1.0, but not exactly 1.0.
130      */
131     protected double confidence = DEFAULT_CONFIDENCE;
132 
133     /**
134      * Maximum allowed number of iterations. When the maximum number of iterations is
135      * exceeded, result will not be available, however an approximate result will be
136      * available for retrieval.
137      */
138     protected int maxIterations = DEFAULT_MAX_ITERATIONS;
139 
140     /**
141      * Data related to inliers found after estimation.
142      */
143     protected InliersData inliersData;
144 
145     /**
146      * Indicates whether result must be refined using found inliers.
147      * If true, inliers will be computed and kept in any implementation regardless of the
148      * settings.
149      */
150     protected boolean refineResult = DEFAULT_REFINE_RESULT;
151 
152     /**
153      * Indicates whether covariance must be kept after refining result.
154      * This setting is only taken into account if result is refined.
155      */
156     protected boolean keepCovariance = DEFAULT_KEEP_COVARIANCE;
157 
158     /**
159      * Covariance of estimated position, power and/or path-loss exponent.
160      * This is only available when result has been refined and covariance is kept.
161      */
162     protected Matrix covariance;
163 
164     /**
165      * Covariance of estimated position.
166      * Size of this matrix will depend on the number of dimensions
167      * of estimated position (either 2 or 3).
168      * This value will only be available when position estimation is enabled.
169      */
170     protected Matrix estimatedPositionCovariance;
171 
172     /**
173      * Size of subsets to be checked during robust estimation.
174      */
175     protected int preliminarySubsetSize;
176 
177     /**
178      * Constructor.
179      */
180     protected RobustRadioSourceEstimator() {
181     }
182 
183     /**
184      * Constructor.
185      * Sets located radio signal readings belonging to the same radio source.
186      *
187      * @param readings radio signal readings belonging to the same
188      *                 radio source.
189      * @throws IllegalArgumentException if readings are not valid.
190      */
191     protected RobustRadioSourceEstimator(final List<? extends R> readings) {
192         internalSetReadings(readings);
193     }
194 
195     /**
196      * Constructor.
197      *
198      * @param listener listener in charge of attending events raised by this instance.
199      */
200     protected RobustRadioSourceEstimator(final L listener) {
201         this.listener = listener;
202     }
203 
204     /**
205      * Constructor.
206      * Sets located radio signal readings belonging to the same radio source.
207      *
208      * @param readings radio signal readings belonging to the same
209      *                 radio source.
210      * @param listener listener in charge of attending events raised by this instance.
211      * @throws IllegalArgumentException if readings are not valid.
212      */
213     protected RobustRadioSourceEstimator(final List<? extends R> readings, final L listener) {
214         this(readings);
215         this.listener = listener;
216     }
217 
218     /**
219      * Indicates whether estimator is locked during estimation.
220      *
221      * @return true if estimator is locked, false otherwise.
222      */
223     public boolean isLocked() {
224         return locked;
225     }
226 
227     /**
228      * Returns amount of progress variation before notifying a progress change during
229      * estimation.
230      *
231      * @return amount of progress variation before notifying a progress change during
232      * estimation.
233      */
234     public float getProgressDelta() {
235         return progressDelta;
236     }
237 
238     /**
239      * Sets amount of progress variation before notifying a progress change during
240      * estimation.
241      *
242      * @param progressDelta amount of progress variation before notifying a progress
243      *                      change during estimation.
244      * @throws IllegalArgumentException if progress delta is less than zero or greater than 1.
245      * @throws LockedException          if this estimator is locked.
246      */
247     public void setProgressDelta(final float progressDelta) throws LockedException {
248         if (isLocked()) {
249             throw new LockedException();
250         }
251         if (progressDelta < MIN_PROGRESS_DELTA || progressDelta > MAX_PROGRESS_DELTA) {
252             throw new IllegalArgumentException();
253         }
254         this.progressDelta = progressDelta;
255     }
256 
257     /**
258      * Returns amount of confidence expressed as a value between 0.0 and 1.0
259      * (which is equivalent to 100%). The amount of confidence indicates the probability
260      * that the estimated result is correct. Usually this value will be close to 1.0, but
261      * not exactly 1.0.
262      *
263      * @return amount of confidence as a value between 0.0 and 1.0.
264      */
265     public double getConfidence() {
266         return confidence;
267     }
268 
269     /**
270      * Sets amount of confidence expressed as a value between 0.0 and 1.0 (which is
271      * equivalent to 100%). The amount of confidence indicates the probability that
272      * the estimated result is correct. Usually this value will be close to 1.0, but
273      * not exactly 1.0.
274      *
275      * @param confidence confidence to be set as a value between 0.0 and 1.0.
276      * @throws IllegalArgumentException if provided value is not between 0.0 and 1.0.
277      * @throws LockedException          if estimator is locked.
278      */
279     public void setConfidence(final double confidence) throws LockedException {
280         if (isLocked()) {
281             throw new LockedException();
282         }
283         if (confidence < MIN_CONFIDENCE || confidence > MAX_CONFIDENCE) {
284             throw new IllegalArgumentException();
285         }
286         this.confidence = confidence;
287     }
288 
289     /**
290      * Returns maximum allowed number of iterations. If maximum allowed number of
291      * iterations is achieved without converging to a result when calling estimate(),
292      * a RobustEstimatorException will be raised.
293      *
294      * @return maximum allowed number of iterations.
295      */
296     public int getMaxIterations() {
297         return maxIterations;
298     }
299 
300     /**
301      * Sets maximum allowed number of iterations. When the maximum number of iterations
302      * is exceeded, result will not be available, however an approximate result will be
303      * available for retrieval.
304      *
305      * @param maxIterations maximum allowed number of iterations to be set.
306      * @throws IllegalArgumentException if provided value is less than 1.
307      * @throws LockedException          if this estimator is locked.
308      */
309     public void setMaxIterations(final int maxIterations) throws LockedException {
310         if (isLocked()) {
311             throw new LockedException();
312         }
313         if (maxIterations < MIN_ITERATIONS) {
314             throw new IllegalArgumentException();
315         }
316         this.maxIterations = maxIterations;
317     }
318 
319     /**
320      * Gets data related to inliers found after estimation.
321      *
322      * @return data related to inliers found after estimation.
323      */
324     public InliersData getInliersData() {
325         return inliersData;
326     }
327 
328     /**
329      * Indicates whether result must be refined using a non-linear solver over found inliers.
330      *
331      * @return true to refine result, false to simply use result found by robust estimator
332      * without further refining.
333      */
334     public boolean isResultRefined() {
335         return refineResult;
336     }
337 
338     /**
339      * Specifies whether result must be refined using a non-linear solver over found inliers.
340      *
341      * @param refineResult true to refine result, false to simply use result found by robust
342      *                     estimator without further refining.
343      * @throws LockedException if estimator is locked.
344      */
345     public void setResultRefined(final boolean refineResult) throws LockedException {
346         if (isLocked()) {
347             throw new LockedException();
348         }
349         this.refineResult = refineResult;
350     }
351 
352     /**
353      * Indicates whether covariance must be kept after refining result.
354      * This setting is only taken into account if result is refined.
355      *
356      * @return true if covariance must be kept after refining result, false otherwise.
357      */
358     public boolean isCovarianceKept() {
359         return keepCovariance;
360     }
361 
362     /**
363      * Specifies whether covariance must be kept after refining result.
364      * This setting is only taken into account if result is refined.
365      *
366      * @param keepCovariance true if covariance must be kept after refining result,
367      *                       false otherwise.
368      * @throws LockedException if estimator is locked.
369      */
370     public void setCovarianceKept(final boolean keepCovariance) throws LockedException {
371         if (isLocked()) {
372             throw new LockedException();
373         }
374         this.keepCovariance = keepCovariance;
375     }
376 
377     /**
378      * Gets signal readings belonging to the same radio source.
379      *
380      * @return signal readings belonging to the same radio source.
381      */
382     public List<R> getReadings() {
383         //noinspection unchecked
384         return (List<R>) readings;
385     }
386 
387     /**
388      * Sets signal readings belonging to the same radio source.
389      *
390      * @param readings signal readings belonging to the same
391      *                 radio source.
392      * @throws LockedException          if estimator is locked.
393      * @throws IllegalArgumentException if readings are not valid.
394      */
395     public void setReadings(final List<? extends R> readings) throws LockedException {
396         if (isLocked()) {
397             throw new LockedException();
398         }
399 
400         internalSetReadings(readings);
401     }
402 
403     /**
404      * Gets listener in charge of attending events raised by this instance.
405      *
406      * @return listener in charge of attending events raised by this instance.
407      */
408     public L getListener() {
409         return listener;
410     }
411 
412     /**
413      * Sets listener in charge of attending events raised by this instance.
414      *
415      * @param listener listener in charge of attending events raised by this
416      *                 instance.
417      * @throws LockedException if estimator is locked.
418      */
419     public void setListener(final L listener) throws LockedException {
420         if (isLocked()) {
421             throw new LockedException();
422         }
423 
424         this.listener = listener;
425     }
426 
427     /**
428      * Returns quality scores corresponding to each pair of
429      * positions and distances (i.e. sample).
430      * The larger the score value the better the quality of the sample.
431      * This implementation always returns null.
432      * Subclasses using quality scores must implement proper behavior.
433      *
434      * @return quality scores corresponding to each sample.
435      */
436     public double[] getQualityScores() {
437         return null;
438     }
439 
440     /**
441      * Sets quality scores corresponding to each pair of positions and
442      * distances (i.e. sample).
443      * The larger the score value the better the quality of the sample.
444      * This implementation makes no action.
445      * Subclasses using quality scores must implement proper behaviour.
446      *
447      * @param qualityScores quality scores corresponding to each pair of
448      *                      matched points.
449      * @throws IllegalArgumentException if provided quality scores length
450      *                                  is smaller than minimum required samples.
451      * @throws LockedException          if robust solver is locked because an
452      *                                  estimation is already in progress.
453      */
454     public void setQualityScores(final double[] qualityScores) throws LockedException {
455     }
456 
457     /**
458      * Gets size of subsets to be checked during robust estimation.
459      * This has to be at least {@link #getMinReadings()}
460      *
461      * @return size of subsets to be checked during robust estimation.
462      */
463     public int getPreliminarySubsetSize() {
464         return preliminarySubsetSize;
465     }
466 
467     /**
468      * Sets size of subsets to be checked during estimation.
469      * This has to be at least {@link #getMinReadings()}.
470      *
471      * @param preliminarySubsetSize size of subsets to be checked during robust estimation.
472      * @throws LockedException          if instance is busy solving the lateration problem.
473      * @throws IllegalArgumentException if provided value is less than {@link #getMinReadings()}.
474      */
475     public void setPreliminarySubsetSize(final int preliminarySubsetSize) throws LockedException {
476         if (isLocked()) {
477             throw new LockedException();
478         }
479         if (preliminarySubsetSize < getMinReadings()) {
480             throw new IllegalArgumentException();
481         }
482 
483         this.preliminarySubsetSize = preliminarySubsetSize;
484     }
485 
486     /**
487      * Gets covariance for estimated position, power and path-loss.
488      * Matrix contains information in the following order:
489      * Top-left sub-matrix contains covariance of position,
490      * then follows transmitted power variance, and finally
491      * the last element contains path-loss exponent variance.
492      * This is only available when result has been refined and covariance is kept.
493      *
494      * @return covariance for estimated position and power.
495      */
496     public Matrix getCovariance() {
497         return covariance;
498     }
499 
500     /**
501      * Gets estimated position covariance.
502      * Size of this matrix will depend on the number of dimensions
503      * of estimated position (either 2 or 3).
504      * This is only available when result has been refined and covariance is kept.
505      *
506      * @return estimated position covariance.
507      */
508     public Matrix getEstimatedPositionCovariance() {
509         return estimatedPositionCovariance;
510     }
511 
512     /**
513      * Gets estimated position.
514      *
515      * @return estimated position.
516      */
517     public P getEstimatedPosition() {
518         return estimatedPosition;
519     }
520 
521     /**
522      * Indicates whether readings are valid or not.
523      * Readings are considered valid when there are enough readings.
524      *
525      * @param readings readings to be validated.
526      * @return true if readings are valid, false otherwise.
527      */
528     public boolean areValidReadings(final List<? extends R> readings) {
529         return readings != null && readings.size() >= getMinReadings();
530     }
531 
532     /**
533      * Indicates whether this instance is ready to start the estimation.
534      *
535      * @return true if this instance is ready, false otherwise.
536      */
537     public abstract boolean isReady();
538 
539     /**
540      * Gets minimum required number of readings to estimate
541      * power, position and path-loss exponent.
542      * This value depends on the number of parameters to
543      * be estimated, but for position only, this is 3
544      * readings for 2D, and 4 readings for 3D.
545      *
546      * @return minimum required number of readings.
547      */
548     public abstract int getMinReadings();
549 
550     /**
551      * Gets number of dimensions of position points.
552      *
553      * @return number of dimensions of position points.
554      */
555     public abstract int getNumberOfDimensions();
556 
557     /**
558      * Robustly estimates position, transmitted power and path-loss exponent for a
559      * radio source.
560      *
561      * @throws LockedException          if instance is busy during estimation.
562      * @throws NotReadyException        if estimator is not ready.
563      * @throws RobustEstimatorException if estimation fails for any reason
564      *                                  (i.e. numerical instability, no solution available, etc).
565      */
566     public abstract void estimate() throws LockedException, NotReadyException, RobustEstimatorException;
567 
568     /**
569      * Gets estimated located radio source.
570      *
571      * @param <S> type of located radio source.
572      * @return estimated located radio source.
573      */
574     public abstract <S extends RadioSourceLocated<P>> S getEstimatedRadioSource();
575 
576     /**
577      * Internally sets signal readings belonging to the same radio source.
578      *
579      * @param readings signal readings belonging to the same radio source.
580      * @throws IllegalArgumentException if readings are null, not enough readings
581      *                                  are available, or readings do not belong to the same access point.
582      */
583     protected void internalSetReadings(final List<? extends R> readings) {
584         if (!areValidReadings(readings)) {
585             throw new IllegalArgumentException();
586         }
587 
588         this.readings = readings;
589     }
590 }