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