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 }