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.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.navigation.geodesic.Constants;
24  import com.irurueta.navigation.indoor.RadioSource;
25  import com.irurueta.navigation.indoor.RssiReadingLocated;
26  import com.irurueta.navigation.indoor.Utils;
27  import com.irurueta.numerical.NumericalException;
28  import com.irurueta.numerical.fitting.FittingException;
29  import com.irurueta.numerical.fitting.LevenbergMarquardtMultiDimensionFitter;
30  import com.irurueta.numerical.fitting.LevenbergMarquardtMultiDimensionFunctionEvaluator;
31  
32  import java.util.List;
33  
34  /**
35   * Estimates position, transmitted power and path loss exponent of a
36   * radio source (e.g. Wi-Fi access point or bluetooth beacon) assuming
37   * that the radio source emits isotropically following the expression
38   * below:
39   * Pr = Pt*Gt*Gr*lambda^2 / (4*pi*d)^2,
40   * where Pr is the received power (expressed in mW),
41   * Gt is the Gain of the transmission antenna
42   * Gr is the Gain of the receiver antenna
43   * d is the distance between emitter and receiver
44   * and lambda is the wavelength and is equal to: lambda = c / f,
45   * where c is the speed of light
46   * and f is the carrier frequency of the radio signal.
47   * Because usually information about the antenna of the radio source cannot be
48   * retrieved (because many measurements are made on unknown devices where
49   * physical access is not possible), this implementation will estimate the
50   * equivalent transmitted power as: Pte = Pt * Gt * Gr.
51   * If Readings contain RSSI standard deviations, those values will be used,
52   * otherwise it will be assumed an RSSI standard deviation of 1 dB.
53   * <p>
54   * IMPORTANT: Implementations of this class can choose to estimate a
55   * combination of radio source position, transmitted power and path loss
56   * exponent. However enabling all three estimations usually achieves
57   * inaccurate results. When using this class, estimation must be of at least
58   * one parameter (position, transmitted power or path loss exponent) when
59   * initial values are provided for the other two, and at most it should consist
60   * of two parameters (either position and transmitted power, position and
61   * path loss exponent or transmitted power and path loss exponent), providing an
62   * initial value for the remaining parameter.
63   *
64   * @param <S> a {@link RadioSource} type.
65   * @param <P> a {@link Point} type.
66   */
67  @SuppressWarnings("DuplicatedCode")
68  public abstract class RssiRadioSourceEstimator<S extends RadioSource, P extends Point<P>>
69          extends RadioSourceEstimator<P, RssiReadingLocated<S, P>, RssiRadioSourceEstimatorListener<S, P>> {
70  
71      /**
72       * Speed of light expressed in meters per second (m/s).
73       */
74      public static final double SPEED_OF_LIGHT = Constants.SPEED_OF_LIGHT;
75  
76      /**
77       * Default standard deviations assumed for RSSI readings being fitted.
78       */
79      public static final double DEFAULT_POWER_STANDARD_DEVIATION = 1.0;
80  
81      /**
82       * Default exponent typically used on free space for path loss propagation in
83       * terms of distance. This value is used for free space environments.
84       */
85      public static final double DEFAULT_PATH_LOSS_EXPONENT = 2.0;
86  
87      /**
88       * Indicates whether radio source position estimation is enabled or not by default.
89       */
90      public static final boolean DEFAULT_POSITION_ESTIMATION_ENABLED = true;
91  
92      /**
93       * Indicates whether radio source transmitted power estimation is enabled or not by
94       * default. Typically, this data is required for Wi-Fi Access points, but it is already
95       * provided for Beacons (and hence its estimation is not needed).
96       */
97      public static final boolean DEFAULT_TRANSMITTED_POWER_ESTIMATION_ENABLED = true;
98  
99      /**
100      * Indicates whether path loss estimation is enabled or not by default.
101      */
102     public static final boolean DEFAULT_PATHLOSS_ESTIMATION_ENABLED = false;
103 
104 
105     /**
106      * Indicates whether radio source position estimation is enabled or not.
107      */
108     private boolean positionEstimationEnabled = DEFAULT_POSITION_ESTIMATION_ENABLED;
109 
110     /**
111      * Estimated transmitted power expressed in dBm's.
112      */
113     private double estimatedTransmittedPowerdBm;
114 
115     /**
116      * Indicates whether transmitted power estimation is enabled or not.
117      */
118     private boolean transmittedPowerEstimationEnabled = DEFAULT_TRANSMITTED_POWER_ESTIMATION_ENABLED;
119 
120     /**
121      * Estimated exponent typically used on free space for path loss propagation in
122      * terms of distance.
123      * On different environments path loss exponent might have different values:
124      * - Free space: 2.0
125      * - Urban Area: 2.7 to 3.5
126      * - Suburban Area: 3 to 5
127      * - Indoor (line-of-sight): 1.6 to 1.8
128      * If path loss exponent estimation is not enabled, this value will always be equal to
129      * {@link #DEFAULT_PATH_LOSS_EXPONENT}
130      */
131     private double estimatedPathLossExponent = DEFAULT_PATH_LOSS_EXPONENT;
132 
133     /**
134      * Variance of estimated transmitted power.
135      * This value will only be available when transmitted power
136      * estimation is enabled.
137      */
138     private Double estimatedTransmittedPowerVariance;
139 
140     /**
141      * Variance of estimated path loss exponent.
142      * This value will only be available when path-loss
143      * exponent estimation is enabled.
144      */
145     private Double estimatedPathLossExponentVariance;
146 
147     /**
148      * Estimated chi square value.
149      */
150     private double chiSq;
151 
152     /**
153      * Initial transmitted power to start the estimation of radio source
154      * transmitted power.
155      * If not defined, average value of received power readings will be used.
156      */
157     private Double initialTransmittedPowerdBm;
158 
159     /**
160      * Initial position to start the estimation of radio source position.
161      * If not defined, centroid of provided readings will be used.
162      */
163     private P initialPosition;
164 
165     /**
166      * Initial exponent typically used on free space for path loss propagation in
167      * terms of distance.
168      * On different environments path loss exponent might have different values:
169      * - Free space: 2.0
170      * - Urban Area: 2.7 to 3.5
171      * - Suburban Area: 3 to 5
172      * - Indoor (line-of-sight): 1.6 to 1.8
173      * <p>
174      * If path loss exponent estimation is enabled, estimation will start at this
175      * value and will converge to the most appropriate value.
176      * If path loss exponent estimation is disabled, this value will be assumed
177      * to be exact and the estimated path loss exponent will be equal to this
178      * value.
179      */
180     private double initialPathLossExponent = DEFAULT_PATH_LOSS_EXPONENT;
181 
182     /**
183      * Indicates whether path loss estimation is enabled or not.
184      */
185     private boolean pathLossEstimationEnabled = DEFAULT_PATHLOSS_ESTIMATION_ENABLED;
186 
187     /**
188      * Levenberg-Marquardt fitter to find a solution.
189      */
190     private final LevenbergMarquardtMultiDimensionFitter fitter = new LevenbergMarquardtMultiDimensionFitter();
191 
192     /**
193      * Constructor.
194      */
195     protected RssiRadioSourceEstimator() {
196         super();
197     }
198 
199     /**
200      * Constructor.
201      * Sets radio signal readings belonging to the same radio source.
202      *
203      * @param readings radio signal readings belonging to the same
204      *                 radio source.
205      * @throws IllegalArgumentException if readings are not valid.
206      */
207     protected RssiRadioSourceEstimator(final List<? extends RssiReadingLocated<S, P>> readings) {
208         super(readings);
209     }
210 
211     /**
212      * Constructor.
213      *
214      * @param listener listener in charge of attending events raised by this instance.
215      */
216     protected RssiRadioSourceEstimator(final RssiRadioSourceEstimatorListener<S, P> listener) {
217         super(listener);
218     }
219 
220     /**
221      * Constructor.
222      * Sets radio signal readings belonging to the same radio source.
223      *
224      * @param readings radio signal readings belonging to the same radio source.
225      * @param listener listener in charge of attending events raised by this instance.
226      * @throws IllegalArgumentException if readings are not valid.
227      */
228     protected RssiRadioSourceEstimator(
229             final List<? extends RssiReadingLocated<S, P>> readings,
230             final RssiRadioSourceEstimatorListener<S, P> listener) {
231         super(readings, listener);
232     }
233 
234     /**
235      * Constructor.
236      *
237      * @param initialPosition initial position to start the estimation of radio
238      *                        source position.
239      */
240     protected RssiRadioSourceEstimator(final P initialPosition) {
241         this.initialPosition = initialPosition;
242     }
243 
244     /**
245      * Constructor.
246      * Sets radio signal readings belonging to the same radio source.
247      *
248      * @param readings        radio signal readings belonging to the same radio source.
249      * @param initialPosition initial position to start the estimation of radio
250      *                        source position.
251      * @throws IllegalArgumentException if readings are not valid.
252      */
253     protected RssiRadioSourceEstimator(
254             final List<? extends RssiReadingLocated<S, P>> readings, final P initialPosition) {
255         super(readings);
256         this.initialPosition = initialPosition;
257     }
258 
259     /**
260      * Constructor.
261      *
262      * @param initialPosition initial position to start the estimation of radio
263      *                        source position.
264      * @param listener        listener in charge of attending events raised by this instance.
265      */
266     protected RssiRadioSourceEstimator(final P initialPosition, final RssiRadioSourceEstimatorListener<S, P> listener) {
267         super(listener);
268         this.initialPosition = initialPosition;
269     }
270 
271     /**
272      * Constructor.
273      * Sets radio signal readings belonging to the same radio source.
274      *
275      * @param readings        radio signal readings belonging to the same radio source.
276      * @param initialPosition initial position to start the estimation of radio
277      *                        source position.
278      * @param listener        listener in charge of attending events raised by this instance.
279      * @throws IllegalArgumentException if readings are not valid.
280      */
281     protected RssiRadioSourceEstimator(
282             final List<? extends RssiReadingLocated<S, P>> readings, final P initialPosition,
283             final RssiRadioSourceEstimatorListener<S, P> listener) {
284         super(readings, listener);
285         this.initialPosition = initialPosition;
286     }
287 
288     /**
289      * Constructor.
290      *
291      * @param initialTransmittedPowerdBm initial transmitted power to start the
292      *                                   estimation of radio source transmitted power
293      *                                   (expressed in dBm's).
294      */
295     protected RssiRadioSourceEstimator(final Double initialTransmittedPowerdBm) {
296         this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
297     }
298 
299     /**
300      * Constructor.
301      * Sets radio signal readings belonging to the same radio source.
302      *
303      * @param readings                   radio signal readings belonging to the same radio source.
304      * @param initialTransmittedPowerdBm initial transmitted power to start the
305      *                                   estimation of radio source transmitted power
306      *                                   (expressed in dBm's).
307      * @throws IllegalArgumentException if readings are not valid.
308      */
309     protected RssiRadioSourceEstimator(
310             final List<? extends RssiReadingLocated<S, P>> readings, final Double initialTransmittedPowerdBm) {
311         super(readings);
312         this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
313     }
314 
315     /**
316      * Constructor.
317      *
318      * @param initialTransmittedPowerdBm initial transmitted power to start the
319      *                                   estimation of radio source transmitted power
320      *                                   (expressed in dBm's)
321      * @param listener                   listener in charge of attending events raised by this instance.
322      */
323     protected RssiRadioSourceEstimator(
324             final Double initialTransmittedPowerdBm, final RssiRadioSourceEstimatorListener<S, P> listener) {
325         super(listener);
326         this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
327     }
328 
329     /**
330      * Constructor.
331      * Sets radio signal readings belonging to the same radio source.
332      *
333      * @param readings                   radio signal readings belonging to the same radio source.
334      * @param initialTransmittedPowerdBm initial transmitted power to start the
335      *                                   estimation of radio source transmitted power
336      *                                   (expressed in dBm's)
337      * @param listener                   listener in charge of attending events raised by this instance.
338      * @throws IllegalArgumentException if readings are not valid.
339      */
340     protected RssiRadioSourceEstimator(
341             final List<? extends RssiReadingLocated<S, P>> readings, final Double initialTransmittedPowerdBm,
342             final RssiRadioSourceEstimatorListener<S, P> listener) {
343         super(readings, listener);
344         this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
345     }
346 
347     /**
348      * Constructor.
349      * Sets radio signal readings belonging to the same radio source.
350      *
351      * @param readings                   radio signal readings belonging to the same radio source.
352      * @param initialPosition            initial position to start the estimation of radio
353      *                                   source position.
354      * @param initialTransmittedPowerdBm initial transmitted power to start the
355      *                                   estimation of radio source transmitted power
356      *                                   (expressed in dBm's)
357      * @throws IllegalArgumentException if readings are not valid.
358      */
359     protected RssiRadioSourceEstimator(
360             final List<? extends RssiReadingLocated<S, P>> readings, final P initialPosition,
361             final Double initialTransmittedPowerdBm) {
362         super(readings);
363         this.initialPosition = initialPosition;
364         this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
365     }
366 
367     /**
368      * Constructor.
369      *
370      * @param initialPosition            initial position to start the estimation of radio
371      *                                   source position.
372      * @param initialTransmittedPowerdBm initial transmitted power to start the
373      *                                   estimation of radio source transmitted power
374      *                                   (expressed in dBm's)
375      */
376     protected RssiRadioSourceEstimator(final P initialPosition, final Double initialTransmittedPowerdBm) {
377         this.initialPosition = initialPosition;
378         this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
379     }
380 
381     /**
382      * Constructor.
383      *
384      * @param initialPosition            initial position to start the estimation of radio
385      *                                   source position.
386      * @param initialTransmittedPowerdBm initial transmitted power to start the
387      *                                   estimation of radio source transmitted power
388      *                                   (expressed in dBm's)
389      * @param listener                   listener in charge of attending events raised by this instance.
390      */
391     protected RssiRadioSourceEstimator(
392             final P initialPosition, final Double initialTransmittedPowerdBm,
393             final RssiRadioSourceEstimatorListener<S, P> listener) {
394         super(listener);
395         this.initialPosition = initialPosition;
396         this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
397     }
398 
399     /**
400      * Constructor.
401      * Sets radio signal readings belonging to the same radio source.
402      *
403      * @param readings                   radio signal readings belonging to the same radio source.
404      * @param initialPosition            initial position to start the estimation of radio
405      *                                   source position.
406      * @param initialTransmittedPowerdBm initial transmitted power to start the
407      *                                   estimation of radio source transmitted power
408      *                                   (expressed in dBm's)
409      * @param listener                   listener in charge of attending events raised by this instance.
410      * @throws IllegalArgumentException if readings are not valid.
411      */
412     protected RssiRadioSourceEstimator(
413             final List<? extends RssiReadingLocated<S, P>> readings, final P initialPosition,
414             final Double initialTransmittedPowerdBm, final RssiRadioSourceEstimatorListener<S, P> listener) {
415         super(readings, listener);
416         this.initialPosition = initialPosition;
417         this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
418     }
419 
420     /**
421      * Constructor.
422      * Sets radio signal readings belonging to the same radio source.
423      *
424      * @param readings                   radio signal readings belonging to the same radio source.
425      * @param initialPosition            initial position to start the estimation of radio
426      *                                   source position.
427      * @param initialTransmittedPowerdBm initial transmitted power to start the
428      *                                   estimation of radio source transmitted power
429      *                                   (expressed in dBm's).
430      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
431      * @throws IllegalArgumentException if readings are not valid.
432      */
433     protected RssiRadioSourceEstimator(
434             final List<? extends RssiReadingLocated<S, P>> readings, final P initialPosition,
435             final Double initialTransmittedPowerdBm, final double initialPathLossExponent) {
436         this(readings, initialPosition, initialTransmittedPowerdBm);
437         this.initialPathLossExponent = initialPathLossExponent;
438     }
439 
440     /**
441      * Constructor.
442      *
443      * @param initialPosition            initial position to start the estimation of radio
444      *                                   source position.
445      * @param initialTransmittedPowerdBm initial transmitted power to start the
446      *                                   estimation of radio source transmitted power
447      *                                   (expressed in dBm's).
448      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
449      */
450     protected RssiRadioSourceEstimator(
451             final P initialPosition, final Double initialTransmittedPowerdBm, final double initialPathLossExponent) {
452         this(initialPosition, initialTransmittedPowerdBm);
453         this.initialPathLossExponent = initialPathLossExponent;
454     }
455 
456     /**
457      * Constructor.
458      *
459      * @param initialPosition            initial position to start the estimation of radio
460      *                                   source position.
461      * @param initialTransmittedPowerdBm initial transmitted power to start the
462      *                                   estimation of radio source transmitted power
463      *                                   (expressed in dBm's)
464      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
465      * @param listener                   listener in charge of attending events raised by this instance.
466      */
467     protected RssiRadioSourceEstimator(
468             final P initialPosition, final Double initialTransmittedPowerdBm, final double initialPathLossExponent,
469             final RssiRadioSourceEstimatorListener<S, P> listener) {
470         this(initialPosition, initialTransmittedPowerdBm, listener);
471         this.initialPathLossExponent = initialPathLossExponent;
472     }
473 
474     /**
475      * Constructor.
476      * Sets radio signal readings belonging to the same radio source.
477      *
478      * @param readings                   radio signal readings belonging to the same radio source.
479      * @param initialPosition            initial position to start the estimation of radio
480      *                                   source position.
481      * @param initialTransmittedPowerdBm initial transmitted power to start the
482      *                                   estimation of radio source transmitted power
483      *                                   (expressed in dBm's)
484      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
485      * @param listener                   listener in charge of attending events raised by this instance.
486      * @throws IllegalArgumentException if readings are not valid.
487      */
488     protected RssiRadioSourceEstimator(
489             final List<? extends RssiReadingLocated<S, P>> readings, final P initialPosition,
490             final Double initialTransmittedPowerdBm, final double initialPathLossExponent,
491             final RssiRadioSourceEstimatorListener<S, P> listener) {
492         this(readings, initialPosition, initialTransmittedPowerdBm, listener);
493         this.initialPathLossExponent = initialPathLossExponent;
494     }
495 
496     /**
497      * Gets initial transmitted power to start the estimation of radio source
498      * transmitted power (expressed in dBm's).
499      * If not defined, average value of received power readings will be used.
500      * <p>
501      * If transmitted power estimation is enabled, estimation will start at this
502      * value and will be converted to the most appropriate value.
503      * If transmitted power estimation is disabled, this value will be assumed to be
504      * exact and the estimated transmitted power will be equal to this value
505      * (converted to dBm's).
506      *
507      * @return initial transmitted power to start the estimation of radio source
508      * transmitted power.
509      */
510     public Double getInitialTransmittedPowerdBm() {
511         return initialTransmittedPowerdBm;
512     }
513 
514     /**
515      * Sets initial transmitted power to start the estimation of radio source
516      * transmitted power (expressed in dBm's).
517      * If not defined, average value of received power readings will be used.
518      * <p>
519      * If transmitted power estimation is enabled, estimation will start at this
520      * value and will be converted to the most appropriate value.
521      * If transmitted power estimation is disabled, this value will be assumed to be
522      * exact and the estimated transmitted power will be equal to this value
523      * (converted to dBm's).
524      *
525      * @param initialTransmittedPowerdBm initial transmitted power to start the
526      *                                   estimation of radio source transmitted
527      *                                   power.
528      * @throws LockedException if estimator is locked.
529      */
530     public void setInitialTransmittedPowerdBm(final Double initialTransmittedPowerdBm) throws LockedException {
531         if (isLocked()) {
532             throw new LockedException();
533         }
534         this.initialTransmittedPowerdBm = initialTransmittedPowerdBm;
535     }
536 
537     /**
538      * Gets initial transmitted power to start the estimation of radio source
539      * transmitted power (expressed in mW).
540      * If not defined, average value of received power readings will be used.
541      * <p>
542      * If transmitted power estimation is enabled, estimation will start at this
543      * value and will be converted to the most appropriate value.
544      * If transmitted power estimation is disabled, this value will be assumed to be
545      * exact and the estimated transmitted power will be equal to this value
546      * (converted to dBm's).
547      *
548      * @return initial transmitted power to start the estimation of radio source
549      * transmitted power.
550      */
551     public Double getInitialTransmittedPower() {
552         return initialTransmittedPowerdBm != null ? Utils.dBmToPower(initialTransmittedPowerdBm) : null;
553     }
554 
555     /**
556      * Sets initial transmitted power to start the estimation of radio source
557      * transmitted power (expressed in mW).
558      * If not defined, average value of received power readings will be used.
559      * <p>
560      * If transmitted power estimation is enabled, estimation will start at this
561      * value and will be converted to the most appropriate value.
562      * If transmitted power estimation is disabled, this value will be assumed to be
563      * exact and the estimated transmitted power will be equal to this value
564      * (converted to dBm's).
565      *
566      * @param initialTransmittedPower initial transmitted power to start the
567      *                                estimation of radio source transmitted power.
568      * @throws LockedException          if estimator is locked.
569      * @throws IllegalArgumentException if provided value is negative.
570      */
571     public void setInitialTransmittedPower(final Double initialTransmittedPower) throws LockedException {
572         if (isLocked()) {
573             throw new LockedException();
574         }
575         if (initialTransmittedPower != null) {
576             if (initialTransmittedPower < 0.0) {
577                 throw new IllegalArgumentException();
578             }
579             initialTransmittedPowerdBm = Utils.powerTodBm(initialTransmittedPower);
580         } else {
581             initialTransmittedPowerdBm = null;
582         }
583     }
584 
585     /**
586      * Indicates whether transmitted power estimation is enabled or not.
587      *
588      * @return true if transmitted power estimation is enabled, false otherwise.
589      */
590     public boolean isTransmittedPowerEstimationEnabled() {
591         return transmittedPowerEstimationEnabled;
592     }
593 
594     /**
595      * Specifies whether transmitted power estimation is enabled or not.
596      *
597      * @param transmittedPowerEstimationEnabled true if transmitted power estimation is enabled,
598      *                                          false otherwise.
599      * @throws LockedException if estimator is locked.
600      */
601     public void setTransmittedPowerEstimationEnabled(final boolean transmittedPowerEstimationEnabled)
602             throws LockedException {
603         if (isLocked()) {
604             throw new LockedException();
605         }
606         this.transmittedPowerEstimationEnabled = transmittedPowerEstimationEnabled;
607     }
608 
609     /**
610      * Gets initial position to start the estimation of radio source position.
611      * If not defined, centroid of provided readings will be used.
612      * <p>
613      * If position estimation is enabled, estimation will start at this value
614      * and will converge to the most appropriate value.
615      * If position estimation is disabled, this value will be assumed to
616      * be exact and the estimated position will be equal to this value.
617      *
618      * @return initial position to start the estimation of radio source position.
619      */
620     public P getInitialPosition() {
621         return initialPosition;
622     }
623 
624     /**
625      * Sets initial position to start the estimation of radio source position.
626      * If not defined, centroid of provided fingerprints will be used.
627      * <p>
628      * If position estimation is enabled, estimation will start at this value
629      * and will converge to the most appropriate value.
630      * If position estimation is disabled, this value will be assumed to
631      * be exact and the estimated position will be equal to this value.
632      *
633      * @param initialPosition initial position to start the estimation of radio
634      *                        source position.
635      * @throws LockedException if estimator is locked.
636      */
637     public void setInitialPosition(final P initialPosition) throws LockedException {
638         if (isLocked()) {
639             throw new LockedException();
640         }
641         this.initialPosition = initialPosition;
642     }
643 
644     /**
645      * Indicates whether radio source position estimation is enabled or not.
646      *
647      * @return true if position estimation is enabled, false otherwise.
648      */
649     public boolean isPositionEstimationEnabled() {
650         return positionEstimationEnabled;
651     }
652 
653     /**
654      * Specifies whether radio source position estimation is enabled or not.
655      *
656      * @param positionEstimationEnabled true if position estimation is enabled,
657      *                                  false otherwise.
658      * @throws LockedException if estimator is locked.
659      */
660     public void setPositionEstimationEnabled(final boolean positionEstimationEnabled) throws LockedException {
661         if (isLocked()) {
662             throw new LockedException();
663         }
664         this.positionEstimationEnabled = positionEstimationEnabled;
665     }
666 
667     /**
668      * Gets initial exponent typically used on free space for path loss propagation
669      * in terms of distance.
670      * On different environments path loss exponent might have different value:
671      * - Free space: 2.0
672      * - Urban Area: 2.7 to 3.5
673      * - Suburban Area: 3 to 5
674      * - Indoor (line-of-sight): 1.6 to 1.8
675      * <p>
676      * If path loss exponent estimation is enabled, estimation will start at this
677      * value and will converge to the most appropriate value.
678      * If path loss exponent estimation is disabled, this value will be assumed
679      * to be exact and the estimated path loss exponent will be equal to this
680      * value.
681      *
682      * @return initial path loss exponent.
683      */
684     public double getInitialPathLossExponent() {
685         return initialPathLossExponent;
686     }
687 
688     /**
689      * Sets initial exponent typically used on free space for path loss propagation
690      * in terms of distance.
691      * On different environments path loss exponent might have different value:
692      * - Free space: 2.0
693      * - Urban Area: 2.7 to 3.5
694      * - Suburban Area: 3 to 5
695      * - Indoor (line-of-sight): 1.6 to 1.8
696      * <p>
697      * If path loss exponent estimation is enabled, estimation will start at this
698      * value and will converge to the most appropriate value.
699      * If path loss exponent estimation is disabled, this value will be assumed
700      * to be exact and the estimated path loss exponent will be equal to this
701      * value.
702      *
703      * @param initialPathLossExponent initial path loss exponent.
704      * @throws LockedException if estimator is locked.
705      */
706     public void setInitialPathLossExponent(final double initialPathLossExponent) throws LockedException {
707         if (isLocked()) {
708             throw new LockedException();
709         }
710         this.initialPathLossExponent = initialPathLossExponent;
711     }
712 
713     /**
714      * Indicates whether path loss estimation is enabled or not.
715      *
716      * @return true if path loss estimation is enabled, false otherwise.
717      */
718     public boolean isPathLossEstimationEnabled() {
719         return pathLossEstimationEnabled;
720     }
721 
722     /**
723      * Specifies whether path loss estimation is enabled or not.
724      *
725      * @param pathLossEstimationEnabled true if path loss estimation is enabled,
726      *                                  false otherwise.
727      * @throws LockedException if estimator is locked.
728      */
729     public void setPathLossEstimationEnabled(final boolean pathLossEstimationEnabled) throws LockedException {
730         if (isLocked()) {
731             throw new LockedException();
732         }
733         this.pathLossEstimationEnabled = pathLossEstimationEnabled;
734     }
735 
736     /**
737      * Indicates whether this instance is ready to start the estimation.
738      *
739      * @return true if this instance is ready, false otherwise.
740      */
741     @Override
742     public boolean isReady() {
743         // at least one parameter estimation must be enabled
744         return (positionEstimationEnabled || transmittedPowerEstimationEnabled || pathLossEstimationEnabled)
745                 // if position estimation is disabled, an initial position must be provided
746                 && !(!positionEstimationEnabled && initialPosition == null)
747                 // if transmitted power estimation is disabled, an initial transmitted power must be provided
748                 && !(!transmittedPowerEstimationEnabled && initialTransmittedPowerdBm == null)
749                 // readings must also be valid
750                 && areValidReadings(readings);
751     }
752 
753     /**
754      * Estimate position, transmitted power and path loss exponent.
755      *
756      * @throws RadioSourceEstimationException if estimation fails.
757      * @throws NotReadyException              if estimator is not ready.
758      * @throws LockedException                if estimator is locked.
759      */
760     @SuppressWarnings("all")
761     @Override
762     public void estimate() throws RadioSourceEstimationException, NotReadyException, LockedException {
763         if (isLocked()) {
764             throw new LockedException();
765         }
766         if (!isReady()) {
767             throw new NotReadyException();
768         }
769 
770         try {
771             locked = true;
772 
773             if (listener != null) {
774                 listener.onEstimateStart(this);
775             }
776 
777             if (positionEstimationEnabled && !transmittedPowerEstimationEnabled && !pathLossEstimationEnabled) {
778                 // only position estimation is enabled
779                 setupFitterPosition();
780             } else if (!positionEstimationEnabled && transmittedPowerEstimationEnabled && !pathLossEstimationEnabled) {
781                 // only transmitted power estimation is enabled
782                 setupFitterTransmittedPower();
783             } else if (!positionEstimationEnabled && !transmittedPowerEstimationEnabled && pathLossEstimationEnabled) {
784                 // only pathloss estimation is enabled
785                 setupFitterPathLossExponent();
786             } else if (positionEstimationEnabled && transmittedPowerEstimationEnabled && !pathLossEstimationEnabled) {
787                 // position and transmitted power enabled
788                 setupFitterPositionAndTransmittedPower();
789             } else if (positionEstimationEnabled && !transmittedPowerEstimationEnabled && pathLossEstimationEnabled) {
790                 // position and pathloss enabled
791                 setupFitterPositionAndPathLossExponent();
792             } else if (!positionEstimationEnabled && transmittedPowerEstimationEnabled && pathLossEstimationEnabled) {
793                 // transmitted power and pathloss enabled
794                 setupFitterTransmittedPowerAndPathLossExponent();
795             } else {
796                 // position, transmitted power and pathloss enabled
797                 setupFitterPositionTransmittedPowerAndPathLossExponent();
798             }
799 
800             fitter.fit();
801 
802             // estimated position and transmitted power
803             final var a = fitter.getA();
804             final var dims = getNumberOfDimensions();
805 
806             estimatedCovariance = fitter.getCovar();
807             chiSq = fitter.getChisq();
808 
809             var pos = 0;
810             estimatedPositionCoordinates = new double[dims];
811             if (positionEstimationEnabled) {
812                 // position estimation enabled
813                 System.arraycopy(a, 0, estimatedPositionCoordinates, 0, dims);
814 
815                 if (estimatedCovariance != null) {
816                     final var d = dims - 1;
817                     if (estimatedPositionCovariance == null) {
818                         estimatedPositionCovariance = estimatedCovariance.getSubmatrix(0, 0, d,
819                                 d);
820                     } else {
821                         estimatedCovariance.getSubmatrix(0, 0, d, d,
822                                 estimatedPositionCovariance);
823                     }
824                 }
825                 pos += dims;
826             } else {
827                 // position estimation disabled
828                 if (initialPosition != null) {
829                     for (var i = 0; i < dims; i++) {
830                         estimatedPositionCoordinates[i] = initialPosition.getInhomogeneousCoordinate(i);
831                     }
832                 }
833 
834                 estimatedPositionCovariance = null;
835             }
836 
837             if (transmittedPowerEstimationEnabled) {
838                 // transmitted power estimation enabled
839                 estimatedTransmittedPowerdBm = a[pos];
840 
841                 if (estimatedCovariance != null) {
842                     estimatedTransmittedPowerVariance = estimatedCovariance.getElementAt(pos, pos);
843                 }
844                 pos++;
845             } else {
846                 // transmitted power estimation disabled
847                 if (initialTransmittedPowerdBm != null) {
848                     estimatedTransmittedPowerdBm = initialTransmittedPowerdBm;
849                 }
850                 estimatedTransmittedPowerVariance = null;
851             }
852 
853             if (pathLossEstimationEnabled) {
854                 // pathloss exponent estimation enabled
855                 estimatedPathLossExponent = a[pos];
856 
857                 if (estimatedCovariance != null) {
858                     estimatedPathLossExponentVariance = estimatedCovariance.getElementAt(pos, pos);
859                 }
860             } else {
861                 // pathloss exponent estimation disabled
862                 estimatedPathLossExponent = initialPathLossExponent;
863                 estimatedPathLossExponentVariance = null;
864             }
865 
866 
867             if (listener != null) {
868                 listener.onEstimateEnd(this);
869             }
870         } catch (final NumericalException e) {
871             throw new RadioSourceEstimationException(e);
872         } finally {
873             locked = false;
874         }
875     }
876 
877     /**
878      * Gets estimated transmitted power expressed in milli watts (mW).
879      *
880      * @return estimated transmitted power expressed in milli watts.
881      */
882     public double getEstimatedTransmittedPower() {
883         return Utils.dBmToPower(estimatedTransmittedPowerdBm);
884     }
885 
886     /**
887      * Gets estimated transmitted power expressed in dBm's.
888      *
889      * @return estimated transmitted power expressed in dBm's.
890      */
891     public double getEstimatedTransmittedPowerdBm() {
892         return estimatedTransmittedPowerdBm;
893     }
894 
895     /**
896      * Gets estimated exponent typically used on free space for path loss propagation in
897      * terms of distance.
898      * On different environments path loss exponent might have different values:
899      * - Free space: 2.0
900      * - Urban Area: 2.7 to 3.5
901      * - Suburban Area: 3 to 5
902      * - Indoor (line-of-sight): 1.6 to 1.8
903      * If path loss exponent estimation is not enabled, this value will always be equal to
904      * {@link #DEFAULT_PATH_LOSS_EXPONENT}
905      *
906      * @return estimated path loss exponent.
907      */
908     public double getEstimatedPathLossExponent() {
909         return estimatedPathLossExponent;
910     }
911 
912     /**
913      * Gets estimated transmitted power variance.
914      * This value will only be available when transmitted power
915      * estimation is enabled.
916      *
917      * @return estimated transmitted power variance or null.
918      */
919     public Double getEstimatedTransmittedPowerVariance() {
920         return estimatedTransmittedPowerVariance;
921     }
922 
923     /**
924      * Gets estimated path loss exponent variance.
925      * This value will only be available when path-loss
926      * exponent estimation is enabled.
927      *
928      * @return estimated path loss exponent variance or null.
929      */
930     public Double getEstimatedPathLossExponentVariance() {
931         return estimatedPathLossExponentVariance;
932     }
933 
934     /**
935      * Gets estimated chi square value.
936      *
937      * @return estimated chi square value.
938      */
939     public double getChiSq() {
940         return chiSq;
941     }
942 
943     /**
944      * Setups fitter to estimated position.
945      *
946      * @throws FittingException if Levenberg-Marquardt fitting fails.
947      */
948     private void setupFitterPosition() throws FittingException {
949         // because all readings must belong to the same radio source, we
950         // obtain the frequency of the first radio source on the first reading
951         var reading = readings.get(0);
952         final var frequency = reading.getSource().getFrequency();
953 
954         // n = 2.0, is the path loss exponent (which is typically 2.0)
955 
956         // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n,    where Pr is the received power
957         // lambda = c/f, where lambda is wavelength,
958         // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the received Gain
959         // Pr = Pte*c^n/((4*pi*f)^n * d^n)
960 
961 
962         // compute k as the constant part of the isotropic received power formula
963         // so that: Pr = Pte*k/d^n
964         final var k = Math.pow(SPEED_OF_LIGHT / (4.0 * Math.PI * frequency), initialPathLossExponent);
965         final var kdB = 10.0 * Math.log10(k);
966 
967         final var dims = getNumberOfDimensions();
968         final var initialTransmittedPowerdBm = computeInitialTransmittedPowerdBm();
969 
970         // for numerical accuracy reasons, a logarithmic version of the previous
971         // formula will be used instead
972         // Pr (dBm) = 10 * log(Pte * k / d^n) = 10*log(k) + 10*log(Pte) - 10*n*log(d)
973 
974         fitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
975             @Override
976             public int getNumberOfDimensions() {
977                 return dims;
978             }
979 
980             @Override
981             public double[] createInitialParametersArray() {
982                 final var initial = new double[dims];
983                 computeInitialPosition(initial, dims);
984                 return initial;
985             }
986 
987             @Override
988             public double evaluate(
989                     final int i, final double[] point, final double[] params, final double[] derivatives) {
990                 var sqrDistance = 0.0;
991                 for (var j = 0; j < dims; j++) {
992                     final var diff = params[j] - point[j];
993                     sqrDistance += diff * diff;
994 
995                     // n is mInitialPathLossExponent, which is typically 2.0
996                     derivatives[j] = -10.0 * initialPathLossExponent * diff;
997                 }
998 
999                 // derivatives respect position coordinates are (2D case):
1000                 // f(x,y) = -5*n*log((x - xap)^2 + (y - yap)^2)
1001                 // df/dx = -5*n*2*(x - xap)/(ln(10)*((x - xap)^2 + (y - yap)^2)) = -10*n*diffX/(ln(10)*sqrDistance)
1002                 // df/dy = -5*n*2*(y - yap)/(ln(10)*((x - xap)^2 + (y - yap)^2)) = -10*n*diffY/(ln(10)*sqrDistance)
1003                 final var ln10PerSqrDistance = Math.log(10.0) * sqrDistance;
1004                 if (ln10PerSqrDistance != 0.0) {
1005                     for (var j = 0; j < dims; j++) {
1006                         derivatives[j] /= ln10PerSqrDistance;
1007                     }
1008                 }
1009 
1010                 // d^2 = (x - xap)^2 + (y - yap)^2
1011                 // d^n = (d^2)^n/2
1012 
1013                 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n,    where Pr is the received power
1014                 // n is the path loss exponent
1015                 // lambda = c/f, where lambda is wavelength,
1016                 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the
1017                 //           received Gain
1018                 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1019                 // Pr (dBm) = 10*log(k) + 10*log(Pte) - 10*log(d^n) =
1020                 // 10*log(k) + 10*log(Pte) - 10*log((d^2)^n/2) =
1021                 // 10*log(k) + 10*log(Pte) - 10*n/2*log(d^2) =
1022                 // 10*log(k) + 10*log(Pte) - 5*n*log(d^2) =
1023                 return kdB + initialTransmittedPowerdBm - 5.0 * initialPathLossExponent * Math.log10(sqrDistance);
1024             }
1025         });
1026 
1027         final var numReadings = readings.size();
1028         try {
1029             final var x = new Matrix(numReadings, dims);
1030             final var y = new double[numReadings];
1031             final var standardDeviations = new double[numReadings];
1032             for (var i = 0; i < numReadings; i++) {
1033                 reading = readings.get(i);
1034                 final var position = reading.getPosition();
1035 
1036                 for (var j = 0; j < dims; j++) {
1037                     x.setElementAt(i, j, position.getInhomogeneousCoordinate(j));
1038                 }
1039 
1040                 standardDeviations[i] = reading.getRssiStandardDeviation() != null ? reading.getRssiStandardDeviation()
1041                         : DEFAULT_POWER_STANDARD_DEVIATION;
1042                 y[i] = reading.getRssi();
1043             }
1044 
1045             fitter.setInputData(x, y, standardDeviations);
1046         } catch (final AlgebraException ignore) {
1047             // never happens
1048         }
1049     }
1050 
1051     /**
1052      * Setups fitter to estimate transmitted power.
1053      *
1054      * @throws FittingException if Levenberg-Marquardt fitting fails.
1055      */
1056     private void setupFitterTransmittedPower() throws FittingException {
1057         // because all readings must belong to the same radio source, we
1058         // obtain the frequency of the first radio source on the first reading
1059         var reading = readings.get(0);
1060         final var frequency = reading.getSource().getFrequency();
1061 
1062         // n = 2.0, is the path loss exponent (which is typically 2.0)
1063 
1064         // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n,    where Pr is the received power
1065         // lambda = c/f, where lambda is wavelength,
1066         // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the received Gain
1067         // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1068 
1069 
1070         // compute k as the constant part of the isotropic received power formula
1071         // so that: Pr = Pte*k/d^n
1072         final var k = Math.pow(SPEED_OF_LIGHT / (4.0 * Math.PI * frequency), initialPathLossExponent);
1073         final var kdB = 10.0 * Math.log10(k);
1074         final var initialTransmittedPowerdBm = computeInitialTransmittedPowerdBm();
1075 
1076         // for numerical accuracy reasons, a logarithmic version of the previous
1077         // formula will be used instead
1078         // Pr (dBm) = 10 * log(Pte * k / d^n) = 10*log(k) + 10*log(Pte) - 10*n*log(d)
1079 
1080         fitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
1081             @Override
1082             public int getNumberOfDimensions() {
1083                 return 1;
1084             }
1085 
1086             @Override
1087             public double[] createInitialParametersArray() {
1088                 final var initial = new double[1];
1089                 initial[0] = initialTransmittedPowerdBm;
1090                 return initial;
1091             }
1092 
1093             @Override
1094             public double evaluate(
1095                     final int i, final double[] point, final double[] params, final double[] derivatives) {
1096                 final var sqrDistance = initialPosition.sqrDistanceTo(readings.get(i).getPosition());
1097 
1098                 final var transmittedPowerdBm = params[0];
1099 
1100                 // derivative respect transmitted power Pt (dBm) = 10*log(Pte)
1101                 derivatives[0] = 1.0;
1102 
1103                 // d^2 = (x - xap)^2 + (y - yap)^2
1104                 // d^n = (d^2)^n/2
1105 
1106                 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n,    where Pr is the received power
1107                 // n is the path loss exponent
1108                 // lambda = c/f, where lambda is wavelength,
1109                 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the
1110                 //          received Gain
1111                 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1112                 // Pr (dBm) = 10*log(k) + 10*log(Pte) - 10*log(d^n) =
1113                 // 10*log(k) + 10*log(Pte) - 10*log((d^2)^n/2) =
1114                 // 10*log(k) + 10*log(Pte) - 10*n/2*log(d^2) =
1115                 // 10*log(k) + 10*log(Pte) - 5*n*log(d^2) =
1116                 return kdB + transmittedPowerdBm - 5.0 * initialPathLossExponent * Math.log10(sqrDistance);
1117             }
1118         });
1119 
1120         final var numReadings = readings.size();
1121         try {
1122             final var x = new Matrix(numReadings, 1);
1123             final var y = new double[numReadings];
1124             final var standardDeviations = new double[numReadings];
1125 
1126             x.initialize(initialTransmittedPowerdBm);
1127             for (var i = 0; i < numReadings; i++) {
1128                 reading = readings.get(i);
1129 
1130                 standardDeviations[i] = reading.getRssiStandardDeviation() != null ? reading.getRssiStandardDeviation()
1131                         : DEFAULT_POWER_STANDARD_DEVIATION;
1132                 y[i] = reading.getRssi();
1133             }
1134 
1135             fitter.setInputData(x, y, standardDeviations);
1136         } catch (final AlgebraException ignore) {
1137             // never happens
1138         }
1139     }
1140 
1141     /**
1142      * Setups fitter to estimated path loss exponent.
1143      *
1144      * @throws FittingException if Levenberg-Marquardt fitting fails.
1145      */
1146     private void setupFitterPathLossExponent() throws FittingException {
1147         // because all readings must belong to the same radio source, we
1148         // obtain the frequency of the first radio source on the first reading
1149         var reading = readings.get(0);
1150         final var frequency = reading.getSource().getFrequency();
1151 
1152         // n = 2.0, is the path loss exponent
1153 
1154         // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n,    where Pr is the received power
1155         // lambda = c/f, where lambda is wavelength,
1156         // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the received Gain
1157         // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1158 
1159 
1160         // k is defined so that: Pr = Pte * k^n / d^n so that
1161         // k = (c/(4*pi*f))
1162         final var k = SPEED_OF_LIGHT / (4.0 * Math.PI * frequency);
1163         final var kdB = 10.0 * Math.log10(k);
1164         final var initialTransmittedPowerdBm = computeInitialTransmittedPowerdBm();
1165 
1166         // for numerical accuracy reasons, a logarithmic version of the previous
1167         // formula will be used instead
1168         // Pr (dBm) = 10 * log(Pte * k^n / d^n) = 10*n*log(k) + 10*log(Pte) - 10*n*log(d)
1169 
1170         fitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
1171             @Override
1172             public int getNumberOfDimensions() {
1173                 return 1;
1174             }
1175 
1176             @Override
1177             public double[] createInitialParametersArray() {
1178                 final var initial = new double[1];
1179                 initial[0] = initialPathLossExponent;
1180                 return initial;
1181             }
1182 
1183             @Override
1184             public double evaluate(
1185                     final int i, final double[] point, final double[] params, final double[] derivatives) {
1186                 final var sqrDistance = initialPosition.sqrDistanceTo(readings.get(i).getPosition());
1187                 final var pathLossExponent = params[0];
1188 
1189                 // derivative respect to path loss exponent
1190                 // f(x,y,n) = n*kdB -5*n*log((x - xap)^2 + (y - yap)^2)
1191                 // df/dn = kdB -5*log((x - xap)^2 + (y - yap)^2) = kdB - 5*log(sqrDistance)
1192                 final var logSqrDistance = Math.log10(sqrDistance);
1193                 derivatives[0] = kdB - 5 * logSqrDistance;
1194 
1195                 // d^2 = (x - xap)^2 + (y - yap)^2
1196                 // d^n = (d^2)^n/2
1197 
1198                 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n,    where Pr is the received power
1199                 // n is the path loss exponent
1200                 // lambda = c/f, where lambda is wavelength,
1201                 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the
1202                 //           received Gain
1203                 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1204                 // Pr (dBm) = 10*log(k^n) + 10*log(Pte) - 10*log(d^n) =
1205                 // 10*log(k^n) + 10*log(Pte) - 10*log((d^2)^n/2) =
1206                 // 10*n*log(k) + 10*log(Pte) - 10*n/2*log(d^2) =
1207                 // 10*n*log(k) + 10*log(Pte) - 5*n*log(d^2) =
1208                 return pathLossExponent * kdB + initialTransmittedPowerdBm - 5.0 * pathLossExponent * logSqrDistance;
1209             }
1210         });
1211 
1212         final var numReadings = readings.size();
1213         try {
1214             final var x = new Matrix(numReadings, 1);
1215             final var y = new double[numReadings];
1216             final var standardDeviations = new double[numReadings];
1217 
1218             x.initialize(initialPathLossExponent);
1219             for (var i = 0; i < numReadings; i++) {
1220                 reading = readings.get(i);
1221 
1222                 standardDeviations[i] = reading.getRssiStandardDeviation() != null ? reading.getRssiStandardDeviation()
1223                         : DEFAULT_POWER_STANDARD_DEVIATION;
1224                 y[i] = reading.getRssi();
1225             }
1226 
1227             fitter.setInputData(x, y, standardDeviations);
1228         } catch (final AlgebraException ignore) {
1229             // never happens
1230         }
1231     }
1232 
1233     /**
1234      * Setups fitter to estimate transmitted power and position.
1235      *
1236      * @throws FittingException if Levenberg-Marquardt fitting fails.
1237      */
1238     private void setupFitterPositionAndTransmittedPower() throws FittingException {
1239         // because all readings must belong to the same radio source, we
1240         // obtain the frequency of the first radio source on the first reading
1241         var reading = readings.get(0);
1242         final var frequency = reading.getSource().getFrequency();
1243 
1244         // n = 2.0, is the path loss exponent (which is typically 2.0)
1245 
1246         // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n,    where Pr is the received power
1247         // lambda = c/f, where lambda is wavelength,
1248         // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the received Gain
1249         // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1250 
1251 
1252         // compute k as the constant part of the isotropic received power formula
1253         // so that: Pr = Pte*k/d^n
1254         final var k = Math.pow(SPEED_OF_LIGHT / (4.0 * Math.PI * frequency), initialPathLossExponent);
1255         final var kdB = 10.0 * Math.log10(k);
1256 
1257         final var dims = getNumberOfDimensions();
1258         final var dimsPlus1 = dims + 1;
1259 
1260         final var initialTransmittedPowerdBm = computeInitialTransmittedPowerdBm();
1261 
1262         // for numerical accuracy reasons, a logarithmic version of the previous
1263         // formula will be used instead
1264         // Pr (dBm) = 10 * log(Pte * k / d^n) = 10*log(k) + 10*log(Pte) - 10*n*log(d)
1265 
1266         fitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
1267 
1268             @Override
1269             public int getNumberOfDimensions() {
1270                 return dimsPlus1;
1271             }
1272 
1273             @Override
1274             public double[] createInitialParametersArray() {
1275                 final var initial = new double[dimsPlus1];
1276 
1277                 // initial position
1278                 computeInitialPosition(initial, dims);
1279 
1280                 // initial transmitted power
1281                 initial[dims] = initialTransmittedPowerdBm;
1282 
1283                 return initial;
1284             }
1285 
1286             @Override
1287             public double evaluate(
1288                     final int i, final double[] point, final double[] params, final double[] derivatives) {
1289                 var sqrDistance = 0.0;
1290                 for (var j = 0; j < dims; j++) {
1291                     final var diff = params[j] - point[j];
1292                     sqrDistance += diff * diff;
1293 
1294                     // n is mInitialPathLossExponent, which is typically 2.0
1295                     derivatives[j] = -10.0 * initialPathLossExponent * diff;
1296                 }
1297 
1298                 final var transmittedPowerdBm = params[dims];
1299 
1300                 // derivatives respect position coordinates are (2D case):
1301                 // f(x,y) = -5*n*log((x - xap)^2 + (y - yap)^2)
1302                 // df/dx = -5*n*2*(x - xap)/(ln(10)*((x - xap)^2 + (y - yap)^2)) = -10*n*diffX/(ln(10)*sqrDistance)
1303                 // df/dy = -5*n*2*(y - yap)/(ln(10)*((x - xap)^2 + (y - yap)^2)) = -10*n*diffY/(ln(10)*sqrDistance)
1304                 final var ln10PerSqrDistance = Math.log(10.0) * sqrDistance;
1305                 if (ln10PerSqrDistance != 0.0) {
1306                     for (var j = 0; j < dims; j++) {
1307                         derivatives[j] /= ln10PerSqrDistance;
1308                     }
1309                 }
1310 
1311                 // derivative respect transmitted power
1312                 derivatives[dims] = 1.0;
1313 
1314                 // d^2 = (x - xap)^2 + (y - yap)^2
1315                 // d^n = (d^2)^n/2
1316 
1317                 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n,    where Pr is the received power
1318                 // n is the path loss exponent
1319                 // lambda = c/f, where lambda is wavelength,
1320                 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the
1321                 //               received Gain
1322                 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1323                 // Pr (dBm) = 10*log(k) + 10*log(Pte) - 10*log(d^n) =
1324                 // 10*log(k) + 10*log(Pte) - 10*log((d^2)^n/2) =
1325                 // 10*log(k) + 10*log(Pte) - 10*n/2*log(d^2) =
1326                 // 10*log(k) + 10*log(Pte) - 5*n*log(d^2) =
1327                 return kdB + transmittedPowerdBm - 5.0 * initialPathLossExponent * Math.log10(sqrDistance);
1328             }
1329         });
1330 
1331         final var numReadings = readings.size();
1332         try {
1333             final var x = new Matrix(numReadings, dimsPlus1);
1334             final var y = new double[numReadings];
1335             final var standardDeviations = new double[numReadings];
1336             for (var i = 0; i < numReadings; i++) {
1337                 reading = readings.get(i);
1338                 final var position = reading.getPosition();
1339 
1340                 for (var j = 0; j < dims; j++) {
1341                     x.setElementAt(i, j, position.getInhomogeneousCoordinate(j));
1342                 }
1343                 x.setElementAt(i, dims, initialTransmittedPowerdBm);
1344 
1345                 standardDeviations[i] = reading.getRssiStandardDeviation() != null ? reading.getRssiStandardDeviation()
1346                         : DEFAULT_POWER_STANDARD_DEVIATION;
1347                 y[i] = reading.getRssi();
1348             }
1349 
1350             fitter.setInputData(x, y, standardDeviations);
1351         } catch (final AlgebraException ignore) {
1352             // never happens
1353         }
1354     }
1355 
1356     /**
1357      * Setups fitter to estimate position and path loss exponent.
1358      *
1359      * @throws FittingException if Levenberg-Marquardt fitting fails.
1360      */
1361     private void setupFitterPositionAndPathLossExponent() throws FittingException {
1362         // because all readings must belong to the same radio source, we
1363         // obtain the frequency of the first radio source on the first reading
1364         var reading = readings.get(0);
1365         final var frequency = reading.getSource().getFrequency();
1366 
1367         // n = 2.0, is the path loss exponent (which is typically 2.0)
1368 
1369         // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n,    where Pr is the received power
1370         // lambda = c/f, where lambda is wavelength,
1371         // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the received Gain
1372         // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1373 
1374 
1375         // compute k as the constant part of the isotropic received power formula
1376         // so that: Pr = Pte*k/d^n
1377         final var k = SPEED_OF_LIGHT / (4.0 * Math.PI * frequency);
1378         final var kdB = 10.0 * Math.log10(k);
1379 
1380         final var dims = getNumberOfDimensions();
1381         final var dimsPlus1 = dims + 1;
1382 
1383         final var initialTransmittedPowerdBm = computeInitialTransmittedPowerdBm();
1384 
1385         // for numerical accuracy reasons, a logarithmic version of the previous
1386         // formula will be used instead
1387         // Pr (dBm) = 10 * log(Pte * k / d^n) = 10*log(k) + 10*log(Pte) - 10*n*log(d)
1388 
1389         fitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
1390             @Override
1391             public int getNumberOfDimensions() {
1392                 return dimsPlus1;
1393             }
1394 
1395             @Override
1396             public double[] createInitialParametersArray() {
1397                 final var initial = new double[dimsPlus1];
1398 
1399                 // initial position
1400                 computeInitialPosition(initial, dims);
1401 
1402                 // initial path loss exponent
1403                 initial[dims] = initialPathLossExponent;
1404                 return initial;
1405             }
1406 
1407             @Override
1408             public double evaluate(
1409                     final int i, final double[] point, final double[] params, final double[] derivatives) {
1410                 var sqrDistance = 0.0;
1411                 final var pathLossExponent = params[dims];
1412                 for (var j = 0; j < dims; j++) {
1413                     final var diff = params[j] - point[j];
1414                     sqrDistance += diff * diff;
1415 
1416                     // n is mInitialPathLossExponent, which is typically 2.0
1417                     derivatives[j] = -10.0 * pathLossExponent * diff;
1418                 }
1419 
1420                 // derivatives respect position coordinates are (2D case):
1421                 // f(x,y,n) = n*kdB -5*n*log((x - xap)^2 + (y - yap)^2)
1422                 // df/dx = -5*n*2*(x - xap)/(ln(10)*((x - xap)^2 + (y - yap)^2)) = -10*n*diffX/(ln(10)*sqrDistance)
1423                 // df/dy = -5*n*2*(y - yap)/(ln(10)*((x - xap)^2 + (y - yap)^2)) = -10*n*diffY/(ln(10)*sqrDistance)
1424                 // df/dn = kdB -5*log((x - xap)^2 + (y - yap)^2) = kdB - 5*log(sqrDistance)
1425                 final var ln10PerSqrDistance = Math.log(10.0) * sqrDistance;
1426                 if (ln10PerSqrDistance != 0.0) {
1427                     for (var j = 0; j < dims; j++) {
1428                         derivatives[j] /= ln10PerSqrDistance;
1429                     }
1430                 }
1431 
1432                 // derivative respect to path loss exponent
1433                 final var logSqrDistance = Math.log10(sqrDistance);
1434                 derivatives[dims] = kdB - 5 * logSqrDistance;
1435 
1436                 // d^2 = (x - xap)^2 + (y - yap)^2
1437                 // d^n = (d^2)^n/2
1438                 // k = (c/(4*pi*f))^n
1439 
1440                 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n,    where Pr is the received power
1441                 // n is the path loss exponent
1442                 // lambda = c/f, where lambda is wavelength,
1443                 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the
1444                 //               received Gain
1445                 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1446                 // Pr (dBm) = 10*log(k^n) + 10*log(Pte) - 10*log(d^n) =
1447                 // 10*log(k^n) + 10*log(Pte) - 10*log((d^2)^n/2) =
1448                 // 10*n*log(k) + 10*log(Pte) - 10*n/2*log(d^2) =
1449                 // 10*n*log(k) + 10*log(Pte) - 5*n*log(d^2) =
1450 
1451                 return pathLossExponent * kdB + initialTransmittedPowerdBm - 5.0 * pathLossExponent * logSqrDistance;
1452             }
1453         });
1454 
1455         final var numReadings = readings.size();
1456         try {
1457             final var x = new Matrix(numReadings, dimsPlus1);
1458             final var y = new double[numReadings];
1459             final var standardDeviations = new double[numReadings];
1460             for (var i = 0; i < numReadings; i++) {
1461                 reading = readings.get(i);
1462                 final var position = reading.getPosition();
1463 
1464                 for (var j = 0; j < dims; j++) {
1465                     x.setElementAt(i, j, position.getInhomogeneousCoordinate(j));
1466                 }
1467                 x.setElementAt(i, dims, initialPathLossExponent);
1468 
1469                 standardDeviations[i] = reading.getRssiStandardDeviation() != null ? reading.getRssiStandardDeviation()
1470                         : DEFAULT_POWER_STANDARD_DEVIATION;
1471                 y[i] = reading.getRssi();
1472             }
1473 
1474             fitter.setInputData(x, y, standardDeviations);
1475         } catch (final AlgebraException ignore) {
1476             // never happens
1477         }
1478     }
1479 
1480     /**
1481      * Setups fitter to estimate transmitted power and path loss exponent.
1482      *
1483      * @throws FittingException if Levenberg-Marquardt fitting fails.
1484      */
1485     private void setupFitterTransmittedPowerAndPathLossExponent() throws FittingException {
1486         // because all readings must belong to the same radio source, we
1487         // obtain the frequency of the first radio source on the first reading
1488         var reading = readings.get(0);
1489         final var frequency = reading.getSource().getFrequency();
1490 
1491         // n = 2.0, is the path loss exponent
1492 
1493         // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n,    where Pr is the received power
1494         // lambda = c/f, where lambda is wavelength,
1495         // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the received Gain
1496         // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1497 
1498 
1499         // k is defined so that: Pr = Pte * k^n / d^n so that
1500         // k = (c/(4*pi*f))
1501         final var k = SPEED_OF_LIGHT / (4.0 * Math.PI * frequency);
1502         final var kdB = 10.0 * Math.log10(k);
1503 
1504         final var initialTransmittedPowerdBm = computeInitialTransmittedPowerdBm();
1505 
1506         // for numerical accuracy reasons, a logarithmic version of the previous
1507         // formula will be used instead
1508         // Pr (dBm) = 10 * log(Pte * k^n / d^n) = 10*n*log(k) + 10*log(Pte) - 10*n*log(d)
1509 
1510         fitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
1511             @Override
1512             public int getNumberOfDimensions() {
1513                 return 2;
1514             }
1515 
1516             @Override
1517             public double[] createInitialParametersArray() {
1518                 final var initial = new double[2];
1519 
1520                 // initial transmitted power
1521                 initial[0] = initialTransmittedPowerdBm;
1522 
1523                 // initial path loss exponent
1524                 initial[1] = initialPathLossExponent;
1525 
1526                 return initial;
1527             }
1528 
1529             @Override
1530             public double evaluate(
1531                     final int i, final double[] point, final double[] params, final double[] derivatives) {
1532                 final var sqrDistance = initialPosition.sqrDistanceTo(readings.get(i).getPosition());
1533                 final var transmittedPowerdBm = params[0];
1534                 final var pathLossExponent = params[1];
1535 
1536                 // derivative respect transmitted power
1537                 derivatives[0] = 1.0;
1538 
1539                 // derivative respect to path loss exponent
1540                 final var logSqrDistance = Math.log10(sqrDistance);
1541                 derivatives[1] = kdB - 5 * logSqrDistance;
1542 
1543                 // d^2 = (x - xap)^2 + (y - yap)^2
1544                 // d^n = (d^2)^n/2
1545                 // k = (c/(4*pi*f))^n
1546 
1547                 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n,    where Pr is the received power
1548                 // n is the path loss exponent
1549                 // lambda = c/f, where lambda is wavelength,
1550                 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the
1551                 //                  received Gain
1552                 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1553                 // Pr (dBm) = 10*log(k^n) + 10*log(Pte) - 10*log(d^n) =
1554                 // 10*log(k^n) + 10*log(Pte) - 10*log((d^2)^n/2) =
1555                 // 10*n*log(k) + 10*log(Pte) - 10*n/2*log(d^2) =
1556                 // 10*n*log(k) + 10*log(Pte) - 5*n*log(d^2) =
1557 
1558                 return pathLossExponent * kdB + transmittedPowerdBm - 5.0 * pathLossExponent * logSqrDistance;
1559             }
1560         });
1561 
1562         final var numReadings = readings.size();
1563         try {
1564             final var x = new Matrix(numReadings, 2);
1565             final var y = new double[numReadings];
1566             final var standardDeviations = new double[numReadings];
1567             for (var i = 0; i < numReadings; i++) {
1568                 reading = readings.get(i);
1569 
1570                 x.setElementAt(i, 0, initialTransmittedPowerdBm);
1571                 x.setElementAt(i, 1, initialPathLossExponent);
1572 
1573                 standardDeviations[i] = reading.getRssiStandardDeviation() != null ? reading.getRssiStandardDeviation()
1574                         : DEFAULT_POWER_STANDARD_DEVIATION;
1575                 y[i] = reading.getRssi();
1576             }
1577 
1578             fitter.setInputData(x, y, standardDeviations);
1579         } catch (AlgebraException ignore) {
1580             // never happens
1581         }
1582     }
1583 
1584     /**
1585      * Setups fitter to estimate transmitted power, position and path
1586      * loss exponent.
1587      *
1588      * @throws FittingException if Levenberg-Marquardt fitting fails.
1589      */
1590     private void setupFitterPositionTransmittedPowerAndPathLossExponent() throws FittingException {
1591         // because all readings must belong to the same radio source, we
1592         // obtain the frequency of the first radio source on the first reading
1593         var reading = readings.get(0);
1594         final var frequency = reading.getSource().getFrequency();
1595 
1596         // n = 2.0, is the path loss exponent
1597 
1598         // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n,    where Pr is the received power
1599         // lambda = c/f, where lambda is wavelength,
1600         // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the received Gain
1601         // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1602 
1603         // k is defined so that: Pr = Pte * k^n / d^n so that
1604         // k = (c/(4*pi*f))
1605         final var k = SPEED_OF_LIGHT / (4.0 * Math.PI * frequency);
1606         final var kdB = 10.0 * Math.log10(k);
1607 
1608         final var dims = getNumberOfDimensions();
1609         final var dimsPlus1 = dims + 1;
1610         final var dimsPlus2 = dims + 2;
1611 
1612         final var initialTransmittedPowerdBm = computeInitialTransmittedPowerdBm();
1613 
1614         // for numerical accuracy reasons, a logarithmic version of the previous
1615         // formula will be used instead
1616         // Pr (dBm) = 10 * log(Pte * k^n / d^n) = 10*n*log(k) + 10*log(Pte) - 10*n*log(d)
1617 
1618         fitter.setFunctionEvaluator(new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
1619 
1620             @Override
1621             public int getNumberOfDimensions() {
1622                 return dimsPlus2;
1623             }
1624 
1625             @Override
1626             public double[] createInitialParametersArray() {
1627                 final var initial = new double[dimsPlus2];
1628 
1629                 // initial position
1630                 computeInitialPosition(initial, dims);
1631 
1632                 // initial transmitted power
1633                 initial[dims] = initialTransmittedPowerdBm;
1634 
1635                 // initial path loss exponent
1636                 initial[dimsPlus1] = initialPathLossExponent;
1637 
1638                 return initial;
1639             }
1640 
1641             @Override
1642             public double evaluate(
1643                     final int i, final double[] point, final double[] params, final double[] derivatives) {
1644                 var sqrDistance = 0.0;
1645                 final var pathLossExponent = params[dimsPlus1];
1646                 for (var j = 0; j < dims; j++) {
1647                     final var diff = params[j] - point[j];
1648                     sqrDistance += diff * diff;
1649 
1650                     // n is mInitialPathLossExponent, which is typically 2.0
1651                     derivatives[j] = -10.0 * pathLossExponent * diff;
1652                 }
1653 
1654                 final var transmittedPowerdBm = params[dims];
1655 
1656                 // derivatives respect position coordinates are (2D case):
1657                 // f(x,y,n) = n*kdB -5*n*log((x - xap)^2 + (y - yap)^2)
1658                 // df/dx = -5*n*2*(x - xap)/(ln(10)*((x - xap)^2 + (y - yap)^2)) = -10*n*diffX/(ln(10)*sqrDistance)
1659                 // df/dy = -5*n*2*(y - yap)/(ln(10)*((x - xap)^2 + (y - yap)^2)) = -10*n*diffY/(ln(10)*sqrDistance)
1660                 // df/dn = kdB -5*log((x - xap)^2 + (y - yap)^2) = kdB - 5*log(sqrDistance)
1661                 final var ln10PerSqrDistance = Math.log(10.0) * sqrDistance;
1662                 if (ln10PerSqrDistance != 0.0) {
1663                     for (var j = 0; j < dims; j++) {
1664                         derivatives[j] /= ln10PerSqrDistance;
1665                     }
1666                 }
1667 
1668                 // derivative respect transmitted power
1669                 derivatives[dims] = 1.0;
1670 
1671                 // derivative respect to path loss exponent
1672                 final var logSqrDistance = Math.log10(sqrDistance);
1673                 derivatives[dimsPlus1] = kdB - 5 * logSqrDistance;
1674 
1675                 // d^2 = (x - xap)^2 + (y - yap)^2
1676                 // d^n = (d^2)^n/2
1677                 // k = (c/(4*pi*f))^n
1678 
1679                 // Pr = Pt*Gt*Gr*lambda^n/(4*pi*d)^n,    where Pr is the received power
1680                 // n is the path loss exponent
1681                 // lambda = c/f, where lambda is wavelength,
1682                 // Pte = Pt*Gt*Gr, is the equivalent transmitted power, Gt is the transmitted Gain and Gr is the
1683                 //           received Gain
1684                 // Pr = Pte*c^n/((4*pi*f)^n * d^n)
1685                 // Pr (dBm) = 10*log(k^n) + 10*log(Pte) - 10*log(d^n) =
1686                 // 10*log(k^n) + 10*log(Pte) - 10*log((d^2)^n/2) =
1687                 // 10*n*log(k) + 10*log(Pte) - 10*n/2*log(d^2) =
1688                 // 10*n*log(k) + 10*log(Pte) - 5*n*log(d^2) =
1689 
1690                 return pathLossExponent * kdB + transmittedPowerdBm - 5.0 * pathLossExponent * logSqrDistance;
1691             }
1692         });
1693 
1694         final var numReadings = readings.size();
1695         try {
1696             final var x = new Matrix(numReadings, dimsPlus2);
1697             final var y = new double[numReadings];
1698             final var standardDeviations = new double[numReadings];
1699             for (var i = 0; i < numReadings; i++) {
1700                 reading = readings.get(i);
1701                 final var position = reading.getPosition();
1702 
1703                 for (var j = 0; j < dims; j++) {
1704                     x.setElementAt(i, j, position.getInhomogeneousCoordinate(j));
1705                 }
1706                 x.setElementAt(i, dims, initialTransmittedPowerdBm);
1707                 x.setElementAt(i, dimsPlus1, initialPathLossExponent);
1708 
1709                 standardDeviations[i] = reading.getRssiStandardDeviation() != null ? reading.getRssiStandardDeviation()
1710                         : DEFAULT_POWER_STANDARD_DEVIATION;
1711                 y[i] = reading.getRssi();
1712             }
1713 
1714             fitter.setInputData(x, y, standardDeviations);
1715         } catch (final AlgebraException ignore) {
1716             // never happens
1717         }
1718     }
1719 
1720     /**
1721      * Computes initial transmitted power expressed in dBm's.
1722      * If no initial transmitted power is provided, the average of all measures
1723      * is used, otherwise provided value is used.
1724      *
1725      * @return initial transmitted power.
1726      */
1727     private double computeInitialTransmittedPowerdBm() {
1728         if (initialTransmittedPowerdBm == null) {
1729             // compute average transmitted power (in mW)
1730             final var num = readings.size();
1731             var result = 0.0;
1732             for (final var reading : readings) {
1733                 final var rssi = reading.getRssi();
1734                 result += rssi / num;
1735             }
1736             return result;
1737         } else {
1738             // convert initial value
1739             return initialTransmittedPowerdBm;
1740         }
1741     }
1742 
1743     /**
1744      * Computes initial position.
1745      *
1746      * @param result array where result will be stored.
1747      * @param dims   number of dimensions of position coordinates (either 2 or 3).
1748      */
1749     private void computeInitialPosition(final double[] result, final int dims) {
1750         if (initialPosition == null) {
1751             final var num = readings.size();
1752 
1753             // compute average centroid of fingerprint positions
1754             for (final var reading : readings) {
1755                 final var position = reading.getPosition();
1756                 for (var i = 0; i < dims; i++) {
1757                     result[i] += position.getInhomogeneousCoordinate(i) / num;
1758                 }
1759             }
1760         } else {
1761             // copy initial position
1762             for (var i = 0; i < dims; i++) {
1763                 result[i] = initialPosition.getInhomogeneousCoordinate(i);
1764             }
1765         }
1766     }
1767 }