1   /* Copyright 2022-2026 Thales Alenia Space
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.time.clocks;
18  
19  import java.util.Map;
20  
21  import org.hipparchus.CalculusFieldElement;
22  import org.hipparchus.analysis.differentiation.Gradient;
23  import org.hipparchus.util.FastMath;
24  import org.orekit.errors.OrekitException;
25  import org.orekit.errors.OrekitMessages;
26  import org.orekit.time.AbsoluteDate;
27  import org.orekit.time.FieldAbsoluteDate;
28  import org.orekit.utils.ParameterDriver;
29  
30  /** Quadratic clock model.
31   *
32   * @author Luc Maisonobe
33   * @since 12.1
34   *
35   */
36  public class QuadraticClockModel implements ClockModel {
37  
38      /** Clock offset scaling factor.
39       * <p>
40       * We use a power of 2 to avoid numeric noise introduction
41       * in the multiplications/divisions sequences.
42       * </p>
43       */
44      private static final double CLOCK_OFFSET_SCALE = FastMath.scalb(1.0, -10);
45  
46      /** Constant term. */
47      private final ParameterDriver a0;
48  
49      /** Linear term. */
50      private final ParameterDriver a1;
51  
52      /** Quadratic term. */
53      private final ParameterDriver a2;
54  
55      /** Simple constructor.
56       * @param referenceDate reference date
57       * @param a0 constant term
58       * @param a1 linear term
59       * @param a2 quadratic term
60       */
61      public QuadraticClockModel(final AbsoluteDate referenceDate,
62                                 final double a0, final double a1, final double a2) {
63          this(new ParameterDriver("a0",
64                                   0.0, CLOCK_OFFSET_SCALE,
65                                   Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY),
66               new ParameterDriver("a1",
67                                   0.0, CLOCK_OFFSET_SCALE,
68                                   Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY),
69               new ParameterDriver("a2",
70                                   0.0, CLOCK_OFFSET_SCALE,
71                                   Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
72          this.a0.setValue(a0);
73          this.a0.setReferenceDate(referenceDate);
74          this.a1.setValue(a1);
75          this.a1.setReferenceDate(referenceDate);
76          this.a2.setValue(a2);
77          this.a2.setReferenceDate(referenceDate);
78      }
79  
80      /** Simple constructor.
81       * @param a0 constant term
82       * @param a1 linear term
83       * @param a2 quadratic term
84       */
85      public QuadraticClockModel(final ParameterDriver a0, final ParameterDriver a1, final ParameterDriver a2) {
86          this.a0 = a0;
87          this.a1 = a1;
88          this.a2 = a2;
89      }
90  
91      /** {@inheritDoc} */
92      @Override
93      public AbsoluteDate getValidityStart() {
94          return AbsoluteDate.PAST_INFINITY;
95      }
96  
97      /** {@inheritDoc} */
98      @Override
99      public AbsoluteDate getValidityEnd() {
100         return AbsoluteDate.FUTURE_INFINITY;
101     }
102 
103     /** {@inheritDoc} */
104     @Override
105     public ClockOffset getOffset(final AbsoluteDate date) {
106         final double dt = date.durationFrom(getSafeReference(date));
107         final double c0 = a0.getValue(date);
108         final double c1 = a1.getValue(date);
109         final double c2 = a2.getValue(date);
110         return new ClockOffset(date,
111                                (c2 * dt + c1) * dt + c0,
112                                2 * c2 * dt + c1,
113                                2 * c2);
114     }
115 
116     /** Get clock bias driver.
117      * @return bias parameter driver (constant)
118      */
119     public ParameterDriver getClockBiasDriver() {
120         return a0;
121     }
122 
123     /** Get clock drift driver.
124      * @return drift parameter driver (linear)
125      */
126     public ParameterDriver getClockDriftDriver() {
127         return a1;
128     }
129 
130     /** Get clock acceleration driver.
131      * @return acceleration parameter driver (quadratic)
132      */
133     public ParameterDriver getClockAccelerationDriver() {
134         return a2;
135     }
136 
137     /** {@inheritDoc} */
138     @Override
139     public <T extends CalculusFieldElement<T>> FieldClockOffset<T> getOffset(final FieldAbsoluteDate<T> date) {
140         final AbsoluteDate aDate = date.toAbsoluteDate();
141         final T dt = date.durationFrom(getSafeReference(aDate));
142         final double c0 = a0.getValue(aDate);
143         final double c1 = a1.getValue(aDate);
144         final double c2 = a2.getValue(aDate);
145         return new FieldClockOffset<>(date,
146                                       dt.multiply(dt.multiply(c2).add(c1)).add(c0),
147                                       dt.multiply(2 * c2).add(c1),
148                                       dt.newInstance(2 * c2));
149     }
150 
151     /** Get a safe reference date.
152      * <p>
153      * This method deals with parameters drivers for which no reference
154      * date has been set, which is acceptable if the model is not
155      * time-dependent.
156      * </p>
157      * @param date date at which values are requested
158      * @return safe reference date
159      */
160     private AbsoluteDate getSafeReference(final AbsoluteDate date) {
161         if (a0.getReferenceDate() == null) {
162             if (a1.getValue(date) == 0 && a2.getValue(date) == 0) {
163                 // it is OK to not have a reference date is clock offset is constant
164                 return date;
165             } else {
166                 throw new OrekitException(OrekitMessages.NO_REFERENCE_DATE_FOR_PARAMETER,
167                                           a0.getName());
168             }
169         } else {
170             return a0.getReferenceDate();
171         }
172     }
173 
174     /** Convert to gradient model.
175      * @param freeParameters total number of free parameters in the gradient
176      * @param indices indices of the differentiation parameters in derivatives computations,
177      * must be span name and not driver name
178      * @param date date at which model must be valid
179      * @return converted clock model
180      */
181     public QuadraticFieldClockModel<Gradient> toGradientModel(final int freeParameters,
182                                                               final Map<String, Integer> indices,
183                                                               final AbsoluteDate date) {
184         final Gradient g0 = a0.getValue(freeParameters, indices, date);
185         final Gradient g1 = a1.getValue(freeParameters, indices, date);
186         final Gradient g2 = a2.getValue(freeParameters, indices, date);
187         final FieldAbsoluteDate<Gradient> referenceDate =
188             new FieldAbsoluteDate<>(g0.getField(), getSafeReference(date));
189         return new QuadraticFieldClockModel<>(referenceDate, g0, g1, g2);
190     }
191 
192 }