1   /* Copyright 2013-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  package org.orekit.rugged.los;
18  
19  import java.util.stream.Stream;
20  
21  import org.hipparchus.Field;
22  import org.hipparchus.analysis.differentiation.Derivative;
23  import org.hipparchus.analysis.polynomials.PolynomialFunction;
24  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
25  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
26  import org.hipparchus.geometry.euclidean.threed.Rotation;
27  import org.hipparchus.geometry.euclidean.threed.RotationConvention;
28  import org.hipparchus.geometry.euclidean.threed.Vector3D;
29  import org.hipparchus.util.FastMath;
30  import org.hipparchus.util.MathArrays;
31  import org.orekit.rugged.utils.DerivativeGenerator;
32  import org.orekit.time.AbsoluteDate;
33  import org.orekit.utils.ParameterDriver;
34  import org.orekit.utils.ParameterObserver;
35  import org.orekit.utils.TimeSpanMap;
36  
37  /** {@link LOSTransform LOS transform} based on a rotation with polynomial angle.
38   * @author Luc Maisonobe
39   * @see LOSBuilder
40   */
41  public class PolynomialRotation implements LOSTransform {
42  
43      /** Parameters scaling factor.
44       * <p>
45       * We use a power of 2 to avoid numeric noise introduction
46       * in the multiplications/divisions sequences.
47       * </p>
48       */
49      private final double SCALE = FastMath.scalb(1.0, -20);
50  
51      /** Rotation axis. */
52      private final Vector3D axis;
53  
54      /** Rotation angle polynomial. */
55      private PolynomialFunction angle;
56  
57      /** Rotation axis and derivatives. */
58      private FieldVector3D<?> axisDS;
59  
60      /** Rotation angle polynomial and derivatives. */
61      private Derivative<?>[] angleDS;
62  
63      /** Reference date for polynomial evaluation. */
64      private final AbsoluteDate referenceDate;
65  
66      /** Drivers for rotation angle polynomial coefficients. */
67      private final ParameterDriver[] coefficientsDrivers;
68  
69      /** Simple constructor.
70       * <p>
71       * The angle of the rotation is evaluated as a polynomial in t,
72       * where t is the duration in seconds between evaluation date and
73       * reference date. The parameters are the polynomial coefficients,
74       * with the constant term at index 0.
75       * </p>
76       * @param name name of the rotation (used for estimated parameters identification)
77       * @param axis rotation axis
78       * @param referenceDate reference date for the polynomial angle
79       * @param angleCoeffs polynomial coefficients of the polynomial angle,
80       * with the constant term at index 0
81       */
82      public PolynomialRotation(final String name,
83                                final Vector3D axis,
84                                final AbsoluteDate referenceDate,
85                                final double... angleCoeffs) {
86          this.axis                = axis;
87          this.referenceDate       = referenceDate;
88          this.coefficientsDrivers = new ParameterDriver[angleCoeffs.length];
89          final ParameterObserver resettingObserver = new ParameterObserver() {
90              @Override
91              public void valueChanged(final double previousValue, final ParameterDriver driver, final AbsoluteDate date) {
92                  // reset rotations to null, they will be evaluated lazily if needed
93                  angle   = null;
94                  axisDS  = null;
95                  angleDS = null;
96              }
97  
98              @Override
99              public void valueSpanMapChanged(final TimeSpanMap<Double> previousValueSpanMap, final ParameterDriver driver) {
100                 // reset rotations to null, they will be evaluated lazily if needed
101                 angle   = null;
102                 axisDS  = null;
103                 angleDS = null;
104             }
105         };
106         for (int i = 0; i < angleCoeffs.length; ++i) {
107             coefficientsDrivers[i] = new ParameterDriver(name + "[" + i + "]", angleCoeffs[i], SCALE,
108                     Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
109             coefficientsDrivers[i].addObserver(resettingObserver);
110         }
111     }
112 
113     /** Simple constructor.
114      * <p>
115      * The angle of the rotation is evaluated as a polynomial in t,
116      * where t is the duration in seconds between evaluation date and
117      * reference date. The parameters are the polynomial coefficients,
118      * with the constant term at index 0.
119      * </p>
120      * @param name name of the rotation (used for estimated parameters identification)
121      * @param axis rotation axis
122      * @param referenceDate reference date for the polynomial angle
123      * @param angle polynomial angle
124      */
125     public PolynomialRotation(final String name,
126                               final Vector3D axis,
127                               final AbsoluteDate referenceDate,
128                               final PolynomialFunction angle) {
129         this(name, axis, referenceDate, angle.getCoefficients());
130     }
131 
132     /** {@inheritDoc}
133      * @since 2.0
134      */
135     @Override
136     public Stream<ParameterDriver> getParametersDrivers() {
137         return Stream.of(coefficientsDrivers);
138     }
139 
140     /** {@inheritDoc} */
141     @Override
142     public Vector3D transformLOS(final int i, final Vector3D los, final AbsoluteDate date) {
143         if (angle == null) {
144             // lazy evaluation of the rotation
145             final double[] coefficients = new double[coefficientsDrivers.length];
146             for (int k = 0; k < coefficients.length; ++k) {
147                 coefficients[k] = coefficientsDrivers[k].getValue();
148             }
149             angle = new PolynomialFunction(coefficients);
150         }
151         return new Rotation(axis,
152                             angle.value(date.durationFrom(referenceDate)),
153                             RotationConvention.VECTOR_OPERATOR).applyTo(los);
154     }
155 
156     /** {@inheritDoc} */
157     @SuppressWarnings("unchecked")
158     @Override
159     public <T extends Derivative<T>> FieldVector3D<T> transformLOS(final int i, final FieldVector3D<T> los,
160                                                                    final AbsoluteDate date,
161                                                                    final DerivativeGenerator<T> generator) {
162 
163         final Field<T> field = generator.getField();
164         final FieldVector3D<T> axisD;
165         final T[] angleD;
166         if (axisDS == null || !axisDS.getX().getField().equals(field)) {
167 
168             // lazy evaluation of the rotation
169             axisD = new FieldVector3D<>(generator.constant(axis.getX()),
170                                         generator.constant(axis.getY()),
171                                         generator.constant(axis.getZ()));
172             angleD = MathArrays.buildArray(field, coefficientsDrivers.length);
173             for (int k = 0; k < angleD.length; ++k) {
174                 angleD[k] = generator.variable(coefficientsDrivers[k]);
175             }
176 
177             // cache evaluated rotation parameters
178             axisDS  = axisD;
179             angleDS = angleD;
180 
181         } else {
182             // reuse cached values
183             axisD  = (FieldVector3D<T>) axisDS;
184             angleD = (T[]) angleDS;
185         }
186 
187         // evaluate polynomial, with all its partial derivatives
188         final double t = date.durationFrom(referenceDate);
189         T alpha = field.getZero();
190         for (int k = angleDS.length - 1; k >= 0; --k) {
191             alpha = alpha.multiply(t).add(angleD[k]);
192         }
193 
194         return new FieldRotation<>(axisD, alpha, RotationConvention.VECTOR_OPERATOR).applyTo(los);
195 
196     }
197 
198 }