1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  /*
19   * MIT License
20   *
21   * Copyright (c) 2019 Romain Serra
22   *
23   * Permission is hereby granted, free of charge, to any person obtaining a copy
24   * of this software and associated documentation files (the "Software"), to deal
25   * in the Software without restriction, including without limitation the rights
26   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27   * copies of the Software, and to permit persons to whom the Software is
28   * furnished to do so, subject to the following conditions:
29   *
30   * The above copyright notice and this permission notice shall be included in all
31   * copies or substantial portions of the Software.
32   *
33   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39   * SOFTWARE.
40   */
41  package org.orekit.ssa.collision.shorttermencounter.probability.twod;
42  
43  import org.hipparchus.CalculusFieldElement;
44  import org.hipparchus.Field;
45  import org.hipparchus.linear.FieldVector;
46  import org.hipparchus.linear.MatrixUtils;
47  import org.hipparchus.stat.StatUtils;
48  import org.hipparchus.util.FastMath;
49  import org.hipparchus.util.MathArrays;
50  import org.hipparchus.util.MathUtils;
51  import org.orekit.ssa.metrics.FieldProbabilityOfCollision;
52  import org.orekit.ssa.metrics.ProbabilityOfCollision;
53  
54  /**
55   * Compute the probability of collision using the method described in : "SERRA, Romain, ARZELIER, Denis, JOLDES, Mioara, et
56   * al. Fast and accurate computation of orbital collision probability for short-term encounters. Journal of Guidance,
57   * Control, and Dynamics, 2016, vol. 39, no 5, p. 1009-1021.".
58   * <p>
59   * It is one of the recommended methods to use.
60   * <p>
61   * It assumes :
62   *     <ul>
63   *         <li>Short encounter leading to a linear relative motion.</li>
64   *         <li>Spherical collision object.</li>
65   *         <li>Uncorrelated positional covariance.</li>
66   *         <li>Gaussian distribution of the position uncertainties.</li>
67   *         <li>Deterministic velocity i.e. no velocity uncertainties.</li>
68   *     </ul>
69   * <p>
70   * The following constants are defined when using the empty constructor :
71   * <ul>
72   *     <li>A default absolute accuracy of 1e-30.</li>
73   *     <li>A maximum number of computed terms of 37000.</li>
74   * </ul>
75   * <p>
76   * This implementation has been translated from python from the provided source code of Romain SERRA on the
77   * <a href="https://github.com/Serrof/SST/blob/master/collision/short_term_poc.py">following github account</a>
78   *
79   * @author Vincent Cucchietti
80   * @author Romain Serra
81   * @since 12.0
82   */
83  public class Laas2015 extends AbstractShortTermEncounter2DPOCMethod {
84  
85      /** Default scaling threshold to use when sum becomes large. */
86      public static final double DEFAULT_SCALING_THRESHOLD = 1e10;
87  
88      /**
89       * Defines the absolute accuracy of this method. For example, given an absolute accuracy of 1e-10, the probability of
90       * collision will be exact until its 1e-10 digit.
91       */
92      private final double absoluteAccuracy;
93  
94      /** Defines the max number of terms that the method will use, thus reducing the computation time in particular cases. */
95      private final int maxNumberOfTerms;
96  
97      /**
98       * Default constructor.
99       * <p>
100      * It uses a default absolute accuracy of 1e-30 and a maximum number of terms of 37000 which is the max number of terms
101      * computed based on Romain SERRA's observation (p.56 of "Romain Serra. Opérations de proximité en orbite : * évaluation
102      * du risque de collision et calcul de manoeuvres optimales pour l’évitement et le rendez-vous. Automatique / *
103      * Robotique. INSA de Toulouse, 2015. Français. NNT : 2015ISAT0035. tel-01261497") about Alfano test case 5 where he
104      * explains that 37000 terms were enough to meet the required precision of 5 significant digits.
105      */
106     public Laas2015() {
107         this(1.E-30, 37000);
108     }
109 
110     /** Simple constructor.
111      * @param absoluteAccuracy absolute accuracy of the result
112      * @param maxNumberOfTerms max number of terms to compute
113      */
114     public Laas2015(final double absoluteAccuracy, final int maxNumberOfTerms) {
115         super(ShortTermEncounter2DPOCMethodType.LAAS_2015.name());
116         this.absoluteAccuracy = absoluteAccuracy;
117         this.maxNumberOfTerms = maxNumberOfTerms;
118     }
119 
120     /** {@inheritDoc} */
121     public final ProbabilityOfCollision compute(final double xm, final double ym,
122                                                 final double sigmaX,
123                                                 final double sigmaY,
124                                                 final double radius) {
125 
126         // CHECKSTYLE: stop Indentation check
127         // Initializing recurrent terms
128         final double xmSquared     = xm * xm;
129         final double ymSquared     = ym * ym;
130         final double sigmaXSq      = sigmaX * sigmaX;
131         final double sigmaYSq      = sigmaY * sigmaY;
132         final double radiusSquared = radius * radius;
133 
134         final double p        = 1. / (2. * (sigmaX * sigmaX));
135         final double phiY     = 1. - (sigmaX / sigmaY * sigmaX / sigmaY);
136         final double omegaX   = (xm / (2. * sigmaXSq)) * (xm / (2. * sigmaXSq));
137         final double omegaY   = (ym / (2. * sigmaYSq)) * (ym / (2. * sigmaYSq));
138         final double bigOmega = phiY * 0.5 + (omegaX + omegaY) / p;
139 
140         final double alpha0 = 0.5 * FastMath.exp(-(xmSquared / sigmaXSq + ymSquared / sigmaYSq) * 0.5) / sigmaX / sigmaY;
141 
142         // Lower boundary
143         final double l0 = alpha0 * (1. - FastMath.exp(-p * radiusSquared)) / p;
144 
145         // Upper boundary
146         final double u0Temp = alpha0 * (FastMath.exp(p * bigOmega * radiusSquared) -
147                 FastMath.exp(-p * radiusSquared)) / (p * (1. + bigOmega));
148         final double u0 = u0Temp > 1 ? 1 : u0Temp;
149 
150         // If the boundaries are close enough to the actual value according to defined relative accuracy
151         if (u0 - l0 <= absoluteAccuracy) {
152             return new ProbabilityOfCollision(StatUtils.mean(u0, l0), u0, l0, getName(),
153                                               isAMaximumProbabilityOfCollisionMethod());
154         }
155         // Otherwise
156         else {
157             final int n1 = (int) (2. * FastMath.ceil(FastMath.E * p * radiusSquared * (1. + bigOmega)));
158             final double n2_inter =
159                     alpha0 * FastMath.exp(p * radiusSquared * bigOmega) /
160                             (absoluteAccuracy * p * FastMath.sqrt(MathUtils.TWO_PI * n1) *
161                                     (1. + bigOmega));
162             final int n2 = (int) FastMath.ceil(FastMath.log(2, n2_inter));
163 
164             // Number of terms to get the relative accuracy desired
165             int nMax = FastMath.max(n1, n2) - 1;
166             nMax = FastMath.min(nMax, maxNumberOfTerms);
167 
168             // Initializing terms used in the equations
169             final double pSquared                  = p * p;
170             final double pCubed                    = pSquared * p;
171             final double pTimesRadiusSquared       = p * radiusSquared;
172             final double radiusFourth              = radiusSquared * radiusSquared;
173             final double radiusSixth               = radiusFourth * radiusSquared;
174             final double phiYSquared               = phiY * phiY;
175             final double pPhiY                     = p * phiY;
176             final double omegaSum                  = omegaX + omegaY;
177             final double pSqTimesHalfPhiYSqPlusOne = pSquared * MathArrays.linearCombination(0.5, phiYSquared, 1., 1.);
178 
179             final double recurrentTerm0 = MathArrays.linearCombination(0.5, phiY, 1., 1.);
180             final double recurrentTerm1 = MathArrays.linearCombination(p, recurrentTerm0, 1., omegaSum);
181             final double recurrentTerm2 = MathArrays.linearCombination(1., pSqTimesHalfPhiYSqPlusOne, 2., pPhiY * omegaY);
182             final double recurrentTerm3 = recurrentTerm1 * recurrentTerm1;
183 
184             final double auxiliaryTerm0 = radiusSixth * pCubed * phiYSquared * omegaX;
185             final double auxiliaryTerm1 = radiusFourth * pSquared * phiY;
186             final double auxiliaryTerm2 = 2. * omegaX * recurrentTerm0;
187 
188             final double auxiliaryTerm3 = phiY * MathArrays.linearCombination(2., omegaX, 1.5, p) + omegaSum;
189             final double auxiliaryTerm4 = pPhiY * recurrentTerm0 * 2.;
190             final double auxiliaryTerm5 = p * (2. * phiY + 1.);
191 
192             double kPlus2 = 2.;
193             double kPlus3 = 3.;
194             double kPlus4 = 4.;
195             double kPlus5 = 5.;
196             double halfY  = 2.5;
197 
198             // Initialize recurrence
199             double c0 = alpha0 * radiusSquared;
200             double c1 = c0 * radiusSquared * 0.5 * recurrentTerm1;
201             double c2 = c0 * (radiusFourth / 12.) * (recurrentTerm3 + recurrentTerm2);
202             double c3 = c0 * (radiusSixth / 144.) * (recurrentTerm1 * (recurrentTerm3 + 3. * recurrentTerm2) +
203                     2. * (pCubed * (1. + phiYSquared * phiY * 0.5) + 3. * pSquared * phiYSquared * omegaY));
204             final double[] initialCoefficients = new double[] { c0, c1, c2, c3 };
205 
206             double sum              = 0.;
207             double rescalingCounter = 0.;
208             for (int i = 0; i < FastMath.min(nMax, 4); i++) {
209                 sum += initialCoefficients[i];
210 
211                 // Rescale quantities if necessary
212                 if (sum > DEFAULT_SCALING_THRESHOLD) {
213                     rescalingCounter += FastMath.log10(DEFAULT_SCALING_THRESHOLD);
214                     c0 /= DEFAULT_SCALING_THRESHOLD;
215                     c1 /= DEFAULT_SCALING_THRESHOLD;
216                     c2 /= DEFAULT_SCALING_THRESHOLD;
217                     c3 /= DEFAULT_SCALING_THRESHOLD;
218                     sum /= DEFAULT_SCALING_THRESHOLD;
219                 }
220 
221             }
222 
223             // Iterate
224             double temp;
225             for (int k = 0; k < nMax - 4; k++) {
226 
227                 // Rescale quantities if necessary
228                 if (sum > DEFAULT_SCALING_THRESHOLD) {
229                     rescalingCounter += FastMath.log10(DEFAULT_SCALING_THRESHOLD);
230                     c0 /= DEFAULT_SCALING_THRESHOLD;
231                     c1 /= DEFAULT_SCALING_THRESHOLD;
232                     c2 /= DEFAULT_SCALING_THRESHOLD;
233                     c3 /= DEFAULT_SCALING_THRESHOLD;
234                     sum /= DEFAULT_SCALING_THRESHOLD;
235                 }
236 
237                 // Recurrence relation
238                 final double denominator = kPlus4 * kPlus3;
239 
240                 temp = c3 * MathArrays.linearCombination(1, recurrentTerm1, kPlus3, auxiliaryTerm5);
241                 temp -= c2 * pTimesRadiusSquared * MathArrays.linearCombination(halfY, auxiliaryTerm4, 1, auxiliaryTerm3) /
242                         kPlus4;
243                 temp += c1 * auxiliaryTerm1 * MathArrays.linearCombination(halfY, pPhiY, 1., auxiliaryTerm2) / denominator;
244                 temp -= c0 * auxiliaryTerm0 / (denominator * kPlus2);
245                 temp *= radiusSquared / (kPlus4 * kPlus5);
246 
247                 c0 = c1;
248                 c1 = c2;
249                 c2 = c3;
250                 c3 = temp;
251 
252                 // Update intermediate variables
253                 kPlus2 = kPlus3;
254                 kPlus3 = kPlus4;
255                 kPlus4 = kPlus5;
256                 kPlus5 = kPlus5 + 1.;
257                 halfY += 1.;
258 
259                 // Update sum
260                 sum += c3;
261 
262             }
263             // CHECKSTYLE: resume Indentation check
264             final double value = sum *
265                     FastMath.exp(MathArrays.linearCombination(FastMath.log(10.), rescalingCounter, -p, radiusSquared));
266 
267             return new ProbabilityOfCollision(value, l0, u0, getName(), isAMaximumProbabilityOfCollisionMethod());
268         }
269     }
270 
271     /** {@inheritDoc} */
272     public final <T extends CalculusFieldElement<T>> FieldProbabilityOfCollision<T> compute(final T xm, final T ym,
273                                                                                             final T sigmaX,
274                                                                                             final T sigmaY,
275                                                                                             final T radius) {
276 
277         // CHECKSTYLE: stop Indentation check
278         // Initializing recurrent terms
279         final Field<T> field = xm.getField();
280         final T        zero  = field.getZero();
281         final T        one   = field.getOne();
282 
283         final T xmSquared     = xm.square();
284         final T ymSquared     = ym.square();
285         final T sigmaXSquared = sigmaX.square();
286         final T sigmaYSquared = sigmaY.square();
287         final T twoSigmaXY    = sigmaX.multiply(sigmaY).multiply(2);
288         final T radiusSquared = radius.square();
289         final T radiusFourth  = radiusSquared.square();
290         final T radiusSixth   = radiusFourth.multiply(radiusSquared);
291 
292         final T p                   = sigmaX.square().reciprocal().multiply(0.5);
293         final T pTimesRadiusSquared = p.multiply(radiusSquared);
294         final T phiY                = sigmaXSquared.divide(sigmaYSquared).negate().add(1.);
295         final T omegaX              = xm.divide(sigmaXSquared.multiply(2.)).pow(2.);
296         final T omegaY              = ym.divide(sigmaYSquared.multiply(2.)).pow(2.);
297         final T omegaSum            = omegaX.add(omegaY);
298         final T bigOmega            = phiY.multiply(0.5).add(omegaX.add(omegaY).divide(p));
299 
300         final T minusP                    = p.negate();
301         final T pSquared                  = p.square();
302         final T pCubed                    = p.multiply(pSquared);
303         final T pPhiY                     = p.multiply(phiY);
304         final T phiYSquared               = phiY.square();
305         final T pRadiusSquaredBigOmega    = p.multiply(radiusSquared).multiply(bigOmega);
306         final T bigOmegaPlusOne           = bigOmega.add(1.);
307         final T pSqTimesHalfPhiYSqPlusOne = pSquared.multiply(phiYSquared.multiply(0.5).add(1.));
308 
309         final T alpha0 = xmSquared.divide(sigmaXSquared).add(ymSquared.divide(sigmaYSquared)).multiply(-0.5).exp()
310                                   .divide(twoSigmaXY);
311 
312         // Lower boundary
313         final T l0 = alpha0.multiply(radiusSquared.multiply(minusP).exp().negate().add(1.)).divide(p);
314 
315         // Upper boundary
316         final T u0Temp = alpha0.multiply(pRadiusSquaredBigOmega.exp().subtract(radiusSquared.multiply(minusP).exp()))
317                                .divide(p.multiply(bigOmegaPlusOne));
318         final T u0 = u0Temp.getReal() > 1 ? one : u0Temp;
319 
320         // If the boundaries are close enough to the actual value according to defined relative accuracy
321         if (u0.getReal() - l0.getReal() <= absoluteAccuracy) {
322             return new FieldProbabilityOfCollision<>(u0.add(l0).multiply(0.5), u0, l0, getName(),
323                                                      isAMaximumProbabilityOfCollisionMethod());
324         }
325         // Otherwise
326         else {
327             final int n1 = (int) (2. *
328                     FastMath.ceil(FastMath.E * p.multiply(radiusSquared).multiply(bigOmegaPlusOne).getReal()));
329             final double n2_inter =
330                     alpha0.getReal() * FastMath.exp(pRadiusSquaredBigOmega.getReal()) /
331                             (absoluteAccuracy * p.getReal() * FastMath.sqrt(MathUtils.TWO_PI * n1) *
332                                     (1. + bigOmega.getReal()));
333             final int n2 = (int) FastMath.ceil(FastMath.log(2., n2_inter));
334 
335             // Number of terms to get the relative accuracy desired
336             int nMax = FastMath.max(n1, n2) - 1;
337             nMax = FastMath.min(nMax, maxNumberOfTerms);
338 
339             // Recurrent term in the equations
340             final T recurrentTerm0 = phiY.multiply(0.5).add(1);
341             final T recurrentTerm1 = p.multiply(recurrentTerm0).add(omegaSum);
342             final T recurrentTerm2 = pSqTimesHalfPhiYSqPlusOne.add(pPhiY.multiply(omegaY).multiply(2.));
343             final T recurrentTerm3 = recurrentTerm1.multiply(recurrentTerm1);
344 
345             final T auxiliaryTerm0 = radiusSixth.multiply(pCubed).multiply(phiYSquared).multiply(omegaX);
346             final T auxiliaryTerm1 = radiusFourth.multiply(pSquared).multiply(phiY);
347             final T auxiliaryTerm2 = omegaX.multiply(recurrentTerm0).multiply(2.);
348             final T auxiliaryTerm3 = phiY.multiply(omegaX.multiply(2.).add(p.multiply(1.5))).add(omegaSum);
349             final T auxiliaryTerm4 = pPhiY.multiply(recurrentTerm0).multiply(2.);
350             final T auxiliaryTerm5 = p.multiply(phiY.multiply(2.).add(1.));
351 
352             T kPlus2 = one.newInstance(2.);
353             T kPlus3 = one.newInstance(3.);
354             T kPlus4 = one.newInstance(4.);
355             T kPlus5 = one.newInstance(5.);
356             T halfY  = one.newInstance(2.5);
357 
358             // Initialize recurrence
359             T c0 = alpha0.multiply(radiusSquared);
360             T c1 = c0.multiply(radiusSquared).multiply(0.5).multiply(recurrentTerm1);
361             T c2 = c0.multiply(radiusFourth.divide(12.)).multiply(recurrentTerm3.add(recurrentTerm2));
362             T c3 = c0.multiply(radiusSixth.divide(144.)).multiply(
363                     recurrentTerm1.multiply(recurrentTerm2.multiply(3.).add(recurrentTerm3))
364                                   .add(pCubed.multiply(2.).multiply(phiYSquared.multiply(phiY).multiply(0.5).add(1.)))
365                                   .add(pSquared.multiply(phiYSquared).multiply(omegaY).multiply(6.)));
366             final FieldVector<T> initialCoefficients = MatrixUtils.createFieldVector(field, 4);
367             initialCoefficients.setEntry(0, c0);
368             initialCoefficients.setEntry(1, c1);
369             initialCoefficients.setEntry(2, c2);
370             initialCoefficients.setEntry(3, c3);
371 
372             T sum              = zero;
373             T rescalingCounter = zero;
374             for (int i = 0; i < FastMath.min(nMax, 4); i++) {
375                 sum = sum.add(initialCoefficients.getEntry(i));
376 
377                 // Rescale quantities if necessary
378                 if (sum.getReal() > DEFAULT_SCALING_THRESHOLD) {
379                     rescalingCounter = rescalingCounter.add(FastMath.log10(DEFAULT_SCALING_THRESHOLD));
380                     c0               = c0.divide(DEFAULT_SCALING_THRESHOLD);
381                     c1               = c1.divide(DEFAULT_SCALING_THRESHOLD);
382                     c2               = c2.divide(DEFAULT_SCALING_THRESHOLD);
383                     c3               = c3.divide(DEFAULT_SCALING_THRESHOLD);
384                     sum              = sum.divide(DEFAULT_SCALING_THRESHOLD);
385                 }
386 
387             }
388 
389             // Iterate
390             T temp;
391             for (int k = 0; k < nMax - 4; k++) {
392 
393                 // Rescale quantities if necessary
394                 if (sum.getReal() > DEFAULT_SCALING_THRESHOLD) {
395                     rescalingCounter = rescalingCounter.add(FastMath.log10(DEFAULT_SCALING_THRESHOLD));
396                     c0               = c0.divide(DEFAULT_SCALING_THRESHOLD);
397                     c1               = c1.divide(DEFAULT_SCALING_THRESHOLD);
398                     c2               = c2.divide(DEFAULT_SCALING_THRESHOLD);
399                     c3               = c3.divide(DEFAULT_SCALING_THRESHOLD);
400                     sum              = sum.divide(DEFAULT_SCALING_THRESHOLD);
401                 }
402 
403                 // Recurrence relation
404                 final T denominator = kPlus4.multiply(kPlus3);
405 
406                 temp = c3.multiply(recurrentTerm1.add(auxiliaryTerm5.multiply(kPlus3)));
407                 temp = temp.subtract(
408                         c2.multiply(pTimesRadiusSquared).multiply(auxiliaryTerm4.multiply(halfY).add(auxiliaryTerm3))
409                           .divide(kPlus4));
410                 temp = temp.add(
411                         c1.multiply(auxiliaryTerm1).multiply(pPhiY.multiply(halfY).add(auxiliaryTerm2)).divide(denominator));
412                 temp = temp.subtract(c0.multiply(auxiliaryTerm0).divide(denominator.multiply(kPlus2)));
413                 temp = temp.multiply(radiusSquared.divide(kPlus4.multiply(kPlus5)));
414 
415                 c0 = c1;
416                 c1 = c2;
417                 c2 = c3;
418                 c3 = temp;
419 
420                 // Update intermediate variables
421                 kPlus2 = kPlus3;
422                 kPlus3 = kPlus4;
423                 kPlus4 = kPlus5;
424                 kPlus5 = kPlus5.add(1.);
425                 halfY  = halfY.add(1.);
426 
427                 // Update sum
428                 sum = sum.add(c3);
429 
430             }
431             final T value =
432                     sum.multiply((rescalingCounter.multiply(FastMath.log(10.)).subtract(pTimesRadiusSquared)).exp());
433 
434             return new FieldProbabilityOfCollision<>(value, l0, u0, getName(), isAMaximumProbabilityOfCollisionMethod());
435             // CHECKSTYLE: resume Indentation check
436         }
437     }
438 
439     /** {@inheritDoc} */
440     @Override
441     public ShortTermEncounter2DPOCMethodType getType() {
442         return ShortTermEncounter2DPOCMethodType.LAAS_2015;
443     }
444 
445 }