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.lateration; 17 18 import com.irurueta.algebra.AlgebraException; 19 import com.irurueta.algebra.Matrix; 20 import com.irurueta.geometry.Point; 21 import com.irurueta.navigation.LockedException; 22 import com.irurueta.navigation.NotReadyException; 23 import com.irurueta.numerical.NumericalException; 24 import com.irurueta.numerical.fitting.FittingException; 25 import com.irurueta.numerical.fitting.LevenbergMarquardtMultiDimensionFitter; 26 import com.irurueta.numerical.fitting.LevenbergMarquardtMultiDimensionFunctionEvaluator; 27 28 import java.util.Arrays; 29 30 /** 31 * Solves a Trilateration problem with an instance of the least squares optimizer. 32 * By solving the lateration problem linearly, this class is able to estimate 33 * the covariance of estimated position. 34 * To achieve better results, it is usually better to provide an initial coarse 35 * solution. 36 * This class is base on the implementation found at: 37 * <a href="https://github.com/lemmingapex/trilateration">https://github.com/lemmingapex/trilateration</a> 38 * 39 * @param <P> a {@link Point} type. 40 */ 41 public abstract class NonLinearLeastSquaresLaterationSolver<P extends Point<?>> extends LaterationSolver<P> { 42 43 /** 44 * Default standard deviation assumed for provided distances when none is 45 * explicitly provided. 46 */ 47 public static final double DEFAULT_DISTANCE_STANDARD_DEVIATION = 1.0e-3; 48 49 /** 50 * Levenberg-Marquardt fitter to find a non-linear solution. 51 */ 52 private final LevenbergMarquardtMultiDimensionFitter fitter = new LevenbergMarquardtMultiDimensionFitter(); 53 54 /** 55 * Estimated covariance matrix for estimated position. 56 */ 57 private Matrix covariance; 58 59 /** 60 * Estimated chi square value. 61 */ 62 private double chiSq; 63 64 /** 65 * Initial position to start lateration solving. 66 * If not defined, centroid of provided position points will be used. 67 */ 68 private P initialPosition; 69 70 /** 71 * Standard deviations of provided distances. 72 */ 73 private double[] distanceStandardDeviations; 74 75 /** 76 * Constructor. 77 */ 78 protected NonLinearLeastSquaresLaterationSolver() { 79 super(); 80 } 81 82 /** 83 * Constructor. 84 * 85 * @param positions known positions of static nodes. 86 * @param distances euclidean distances from static nodes to mobile node. 87 * @throws IllegalArgumentException if either positions or distances are null, don't have the same length or their 88 * length is smaller than required 2 points. 89 */ 90 protected NonLinearLeastSquaresLaterationSolver(final P[] positions, final double[] distances) { 91 super(); 92 internalSetPositionsAndDistances(positions, distances); 93 } 94 95 /** 96 * Constructor. 97 * 98 * @param initialPosition initial position to start lateration solving. 99 */ 100 protected NonLinearLeastSquaresLaterationSolver(final P initialPosition) { 101 super(); 102 this.initialPosition = initialPosition; 103 } 104 105 /** 106 * Constructor. 107 * 108 * @param positions known positions of static nodes. 109 * @param distances euclidean distances from static nodes to mobile node. 110 * @param initialPosition initial position to start lateration solving. 111 * @throws IllegalArgumentException if either positions or distances are null, don't have the same length or their 112 * length is smaller than required (3 for 2D points or 4 for 3D points) or fitter 113 * is null. 114 */ 115 protected NonLinearLeastSquaresLaterationSolver( 116 final P[] positions, final double[] distances, final P initialPosition) { 117 this(initialPosition); 118 internalSetPositionsAndDistances(positions, distances); 119 } 120 121 /** 122 * Constructor. 123 * 124 * @param listener listener to be notified of events raised by this instance. 125 */ 126 protected NonLinearLeastSquaresLaterationSolver(final LaterationSolverListener<P> listener) { 127 super(listener); 128 } 129 130 /** 131 * Constructor. 132 * 133 * @param positions known positions of static nodes. 134 * @param distances euclidean distances from static nodes to mobile node. 135 * @param listener listener to be notified of events raised by this instance. 136 * @throws IllegalArgumentException if either positions or distances are null, don't have the same length or their 137 * length is smaller than required 2 points. 138 */ 139 protected NonLinearLeastSquaresLaterationSolver( 140 final P[] positions, final double[] distances, final LaterationSolverListener<P> listener) { 141 super(listener); 142 internalSetPositionsAndDistances(positions, distances); 143 } 144 145 /** 146 * Constructor. 147 * 148 * @param initialPosition initial position to start lateration solving. 149 * @param listener listener to be notified of events raised by this instance. 150 */ 151 protected NonLinearLeastSquaresLaterationSolver( 152 final P initialPosition, final LaterationSolverListener<P> listener) { 153 super(listener); 154 this.initialPosition = initialPosition; 155 } 156 157 /** 158 * Constructor. 159 * 160 * @param positions known positions of static nodes. 161 * @param distances euclidean distances from static nodes to mobile node. 162 * @param initialPosition initial position to start lateration solving. 163 * @param listener listener to be notified of events raised by this instance. 164 * @throws IllegalArgumentException if either positions or distances are null, don't have the same length or their 165 * length is smaller than required (3 for 2D points or 4 for 3D points) or fitter 166 * is null. 167 */ 168 protected NonLinearLeastSquaresLaterationSolver( 169 final P[] positions, final double[] distances, final P initialPosition, 170 final LaterationSolverListener<P> listener) { 171 this(initialPosition, listener); 172 internalSetPositionsAndDistances(positions, distances); 173 } 174 175 /** 176 * Constructor. 177 * 178 * @param positions known positions of static nodes. 179 * @param distances euclidean distances from static nodes to mobile node. 180 * @param distanceStandardDeviations standard deviations of provided measured distances. 181 * @throws IllegalArgumentException if either positions, distances or standard deviations 182 * are null, don't have the same length of their length is smaller than required 183 * (2 points). 184 */ 185 protected NonLinearLeastSquaresLaterationSolver( 186 final P[] positions, final double[] distances, final double[] distanceStandardDeviations) { 187 super(); 188 internalSetPositionsDistancesAndStandardDeviations(positions, distances, distanceStandardDeviations); 189 } 190 191 /** 192 * Constructor. 193 * 194 * @param positions known positions of static nodes. 195 * @param distances euclidean distances from static nodes to mobile node. 196 * @param distanceStandardDeviations standard deviations of provided measured distances. 197 * @param initialPosition initial position to start lateration solving. 198 * @throws IllegalArgumentException if either positions, distances or standard deviations 199 * are null, don't have the same length of their length is smaller than required 200 * (2 points). 201 */ 202 protected NonLinearLeastSquaresLaterationSolver( 203 final P[] positions, final double[] distances, final double[] distanceStandardDeviations, 204 final P initialPosition) { 205 this(initialPosition); 206 internalSetPositionsDistancesAndStandardDeviations(positions, distances, distanceStandardDeviations); 207 } 208 209 /** 210 * Constructor. 211 * 212 * @param positions known positions of static nodes. 213 * @param distances euclidean distances from static nodes to mobile node. 214 * @param distanceStandardDeviations standard deviations of provided measured distances. 215 * @param listener listener to be notified of events raised by this instance. 216 * @throws IllegalArgumentException if either positions, distances or standard deviations 217 * are null, don't have the same length of their length is smaller than required 218 * (2 points). 219 */ 220 protected NonLinearLeastSquaresLaterationSolver( 221 final P[] positions, final double[] distances, final double[] distanceStandardDeviations, 222 final LaterationSolverListener<P> listener) { 223 super(listener); 224 internalSetPositionsDistancesAndStandardDeviations(positions, distances, distanceStandardDeviations); 225 } 226 227 /** 228 * Constructor. 229 * 230 * @param positions known positions of static nodes. 231 * @param distances euclidean distances from static nodes to mobile node. 232 * @param distanceStandardDeviations standard deviations of provided measured distances. 233 * @param initialPosition initial position to start lateration solving. 234 * @param listener listener to be notified of events raised by this instance. 235 * @throws IllegalArgumentException if either positions, distances or standard deviations 236 * are null, don't have the same length of their length is smaller than required 237 * (2 points). 238 */ 239 protected NonLinearLeastSquaresLaterationSolver( 240 final P[] positions, final double[] distances, final double[] distanceStandardDeviations, 241 final P initialPosition, final LaterationSolverListener<P> listener) { 242 this(initialPosition, listener); 243 internalSetPositionsDistancesAndStandardDeviations(positions, distances, distanceStandardDeviations); 244 } 245 246 /** 247 * Sets known positions and Euclidean distances. 248 * If any distance value is zero or negative, it will be fixed assuming an EPSILON value. 249 * 250 * @param positions known positions of static nodes. 251 * @param distances euclidean distances from static nodes to mobile node. 252 * @throws IllegalArgumentException if either positions or distances are null, don't have the same length or their 253 * length is smaller than required (2 points). 254 * @throws LockedException if instance is busy solving the lateration problem. 255 */ 256 @Override 257 public void setPositionsAndDistances(final P[] positions, final double[] distances) throws LockedException { 258 if (isLocked()) { 259 throw new LockedException(); 260 } 261 internalSetPositionsAndDistances(positions, distances); 262 } 263 264 /** 265 * Sets known positions, Euclidean distances and the respective standard deviations of 266 * measured distances. 267 * If any distance value is zero or negative, it will be fixed assuming an EPSILON value. 268 * 269 * @param positions known positions of static nodes. 270 * @param distances euclidean distances from static nodes to mobile node. 271 * @param distanceStandardDeviations standard deviations of provided measured distances. 272 * @throws IllegalArgumentException if either positions, distances or standard deviations 273 * are null, don't have the same length of their length is smaller than required 274 * (2 points). 275 * @throws LockedException if instance is busy solving the trilateration problem. 276 */ 277 public void setPositionsDistancesAndStandardDeviations( 278 final P[] positions, final double[] distances, final double[] distanceStandardDeviations) 279 throws LockedException { 280 if (isLocked()) { 281 throw new LockedException(); 282 } 283 internalSetPositionsDistancesAndStandardDeviations(positions, distances, distanceStandardDeviations); 284 } 285 286 /** 287 * Gets standard deviations of provided distances. 288 * 289 * @return standard deviations of provided distances. 290 */ 291 public double[] getDistanceStandardDeviations() { 292 return distanceStandardDeviations; 293 } 294 295 /** 296 * Gets estimated covariance matrix for estimated position. 297 * 298 * @return estimated covariance matrix for estimated position. 299 */ 300 public Matrix getCovariance() { 301 return covariance; 302 } 303 304 /** 305 * Gets estimated chi square value. 306 * 307 * @return estimated chi square value. 308 */ 309 public double getChiSq() { 310 return chiSq; 311 } 312 313 /** 314 * Gets initial position to start lateration solving. 315 * If not defined, centroid of provided position points will be used to start lateration. 316 * 317 * @return initial position to start lateration. 318 */ 319 public P getInitialPosition() { 320 return initialPosition; 321 } 322 323 /** 324 * Sets initial position to start lateration solving. 325 * If not defined, centroid of provided position points will be used to start lateration. 326 * 327 * @param initialPosition initial position to start lateration. 328 * @throws LockedException if instance is busy solving the lateration problem. 329 */ 330 public void setInitialPosition(final P initialPosition) throws LockedException { 331 if (isLocked()) { 332 throw new LockedException(); 333 } 334 this.initialPosition = initialPosition; 335 } 336 337 /** 338 * Solves the lateration problem. 339 * 340 * @throws LaterationException if lateration fails. 341 * @throws NotReadyException is solver is not ready. 342 * @throws LockedException if instance is busy solving the lateration problem. 343 */ 344 @Override 345 public void solve() throws LaterationException, NotReadyException, LockedException { 346 if (!isReady()) { 347 throw new NotReadyException(); 348 } 349 if (isLocked()) { 350 throw new LockedException(); 351 } 352 353 try { 354 locked = true; 355 356 if (listener != null) { 357 listener.onSolveStart(this); 358 } 359 360 setupFitter(); 361 362 fitter.fit(); 363 364 // estimated position 365 estimatedPositionCoordinates = fitter.getA(); 366 covariance = fitter.getCovar(); 367 chiSq = fitter.getChisq(); 368 369 if (listener != null) { 370 listener.onSolveEnd(this); 371 } 372 } catch (final NumericalException e) { 373 throw new LaterationException(e); 374 } finally { 375 locked = false; 376 } 377 } 378 379 /** 380 * Gets lateration solver type. 381 * 382 * @return lateration solver type. 383 */ 384 @Override 385 public LaterationSolverType getType() { 386 return LaterationSolverType.NON_LINEAR_TRILATERATION_SOLVER; 387 } 388 389 /** 390 * Internally sets known positions and Euclidean distances. 391 * If any distance value is zero or negative, it will be fixed assuming an EPSILON value. 392 * 393 * @param positions known positions of static nodes. 394 * @param distances euclidean distances from static nodes to mobile node. 395 * @throws IllegalArgumentException if either positions or distances are null, don't have the same length or their 396 * length is smaller than required (2 points). 397 */ 398 @Override 399 protected void internalSetPositionsAndDistances(final P[] positions, final double[] distances) { 400 super.internalSetPositionsAndDistances(positions, distances); 401 402 // initialize distances standard deviations to default values 403 distanceStandardDeviations = new double[distances.length]; 404 Arrays.fill(distanceStandardDeviations, DEFAULT_DISTANCE_STANDARD_DEVIATION); 405 } 406 407 /** 408 * Internally sets known positions, Euclidean distances and the respective standard deviations of 409 * measured distances. 410 * If any distance value is zero or negative, it will be fixed assuming an EPSILON value. 411 * 412 * @param positions known positions of static nodes. 413 * @param distances euclidean distances from static nodes to mobile node. 414 * @param distanceStandardDeviations standard deviations of provided measured distances. 415 * @throws IllegalArgumentException if either positions, distances or standard deviations 416 * are null, don't have the same length of their length is smaller than required 417 * (2 points). 418 */ 419 protected void internalSetPositionsDistancesAndStandardDeviations( 420 final P[] positions, final double[] distances, final double[] distanceStandardDeviations) { 421 if (distanceStandardDeviations == null || distances == null) { 422 throw new IllegalArgumentException(); 423 } 424 if (distances.length != distanceStandardDeviations.length) { 425 throw new IllegalArgumentException(); 426 } 427 428 super.internalSetPositionsAndDistances(positions, distances); 429 this.distanceStandardDeviations = distanceStandardDeviations; 430 } 431 432 /** 433 * Setups fitter to solve lateration problem. 434 * 435 * @throws FittingException if Levenberg-Marquardt fitting fails. 436 */ 437 private void setupFitter() throws FittingException { 438 fitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() { 439 @Override 440 public int getNumberOfDimensions() { 441 return NonLinearLeastSquaresLaterationSolver.this.getNumberOfDimensions(); 442 } 443 444 @Override 445 public double[] createInitialParametersArray() { 446 final var dims = getNumberOfDimensions(); 447 final var initial = new double[dims]; 448 449 if (initialPosition == null) { 450 // use centroid of positions as initial value 451 final var numSamples = positions.length; 452 453 for (var i = 0; i < dims; i++) { 454 initial[i] = 0.0; 455 for (final var position : positions) { 456 initial[i] += position.getInhomogeneousCoordinate(i) / numSamples; 457 } 458 } 459 } else { 460 // use provided initial position 461 for (var i = 0; i < dims; i++) { 462 initial[i] = initialPosition.getInhomogeneousCoordinate(i); 463 } 464 } 465 466 return initial; 467 } 468 469 @Override 470 public double evaluate(final int i, final double[] point, final double[] params, 471 final double[] derivatives) { 472 // we want to estimate the position contained as inhomogeneous coordinates in params array. 473 // the function evaluates the distance to provided point respect to current parameter 474 // (estimated position) 475 // sqrDist = (x - px)^2 + (y - py)^2 476 // grad = [2*(x - px), 2*(y - py)] 477 final var dims = getNumberOfDimensions(); 478 var result = 0.0; 479 for (var j = 0; j < dims; j++) { 480 final var param = params[j]; 481 final var diff = param - point[j]; 482 result += diff * diff; 483 derivatives[j] = 2.0 * diff; 484 } 485 486 return result; 487 } 488 }); 489 490 final var dims = getNumberOfDimensions(); 491 try { 492 final var x = new Matrix(positions.length, dims); 493 final var y = new double[positions.length]; 494 for (var i = 0; i < positions.length; i++) { 495 for (var j = 0; j < dims; j++) { 496 x.setElementAt(i, j, positions[i].getInhomogeneousCoordinate(j)); 497 } 498 499 final var dist = distances[i]; 500 y[i] = dist * dist; 501 } 502 503 fitter.setInputData(x, y, distanceStandardDeviations); 504 } catch (final AlgebraException ignore) { 505 // never happens 506 } 507 } 508 }