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 }