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  package org.orekit.forces.gravity.potential;
18  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.List;
23  import java.util.stream.Stream;
24  
25  import org.hipparchus.analysis.interpolation.HermiteInterpolator;
26  import org.orekit.errors.OrekitException;
27  import org.orekit.errors.TimeStampedCacheException;
28  import org.orekit.time.AbsoluteDate;
29  import org.orekit.time.TimeStamped;
30  import org.orekit.utils.GenericTimeStampedCache;
31  import org.orekit.utils.TimeStampedCache;
32  import org.orekit.utils.TimeStampedGenerator;
33  
34  /** Caching wrapper for {@link NormalizedSphericalHarmonicsProvider}.
35   * <p>
36   * This wrapper improves efficiency of {@link NormalizedSphericalHarmonicsProvider}
37   * by sampling the values at a user defined rate and using interpolation
38   * between samples. This is important with providers that have sub-daily
39   * frequencies and are computing intensive, such as tides fields.
40   * </p>
41   * @see NormalizedSphericalHarmonicsProvider
42   * @see org.orekit.forces.gravity.SolidTides
43   * @see TimeStampedCache
44   * @author Luc Maisonobe
45   * @since 6.1
46   */
47  public class CachedNormalizedSphericalHarmonicsProvider implements NormalizedSphericalHarmonicsProvider {
48  
49      /** Underlying raw provider. */
50      private final NormalizedSphericalHarmonicsProvider rawProvider;
51  
52      /** Number of coefficients in C<sub>n, m</sub> and S<sub>n, m</sub> arrays (counted separately). */
53      private final int size;
54  
55      /** Cache. */
56      private final TimeStampedCache<TimeStampedSphericalHarmonics> cache;
57  
58      /** Simple constructor.
59       * @param rawProvider underlying raw provider
60       * @param step time step between sample points for interpolation
61       * @param nbPoints number of points to use for interpolation, must be at least 2
62       * @param maxSlots maximum number of independent cached time slots
63       * @param maxSpan maximum duration span in seconds of one slot
64       * (can be set to {@code Double.POSITIVE_INFINITY} if desired)
65       * @param newSlotInterval time interval above which a new slot is created
66       * instead of extending an existing one
67       */
68      public CachedNormalizedSphericalHarmonicsProvider(final NormalizedSphericalHarmonicsProvider rawProvider,
69                                                        final double step, final int nbPoints,
70                                                        final int maxSlots, final double maxSpan,
71                                                        final double newSlotInterval) {
72  
73          this.rawProvider  = rawProvider;
74          final int k       = rawProvider.getMaxDegree() + 1;
75          this.size         = (k * (k + 1)) / 2;
76  
77          cache = new GenericTimeStampedCache<>(nbPoints, maxSlots, maxSpan,
78                  newSlotInterval, new Generator(step));
79      }
80  
81      /** {@inheritDoc} */
82      @Override
83      public int getMaxDegree() {
84          return rawProvider.getMaxDegree();
85      }
86  
87      /** {@inheritDoc} */
88      @Override
89      public int getMaxOrder() {
90          return rawProvider.getMaxOrder();
91      }
92  
93      /** {@inheritDoc} */
94      @Override
95      public double getMu() {
96          return rawProvider.getMu();
97      }
98  
99      /** {@inheritDoc} */
100     @Override
101     public double getAe() {
102         return rawProvider.getAe();
103     }
104 
105     /** {@inheritDoc} */
106     @Override
107     public AbsoluteDate getReferenceDate() {
108         return rawProvider.getReferenceDate();
109     }
110 
111     /** {@inheritDoc} */
112     @Override
113     public TideSystem getTideSystem() {
114         return rawProvider.getTideSystem();
115     }
116 
117     /** {@inheritDoc} */
118     @Override
119     public NormalizedSphericalHarmonics onDate(final AbsoluteDate date) {
120         return TimeStampedSphericalHarmonics.interpolate(date, cache.getNeighbors(date));
121     }
122 
123     /** Generator for time-stamped spherical harmonics. */
124     private class Generator implements TimeStampedGenerator<TimeStampedSphericalHarmonics> {
125 
126         /** Time step between generated sets. */
127         private final double step;
128 
129         /** Simple constructor.
130          * @param step time step between generated sets
131          */
132         Generator(final double step) {
133             this.step = step;
134         }
135 
136         /** {@inheritDoc} */
137         @Override
138         public List<TimeStampedSphericalHarmonics> generate(final AbsoluteDate existingDate,
139                                                             final AbsoluteDate date) {
140             try {
141 
142                 final List<TimeStampedSphericalHarmonics> generated =
143                         new ArrayList<>();
144                 final double[] cnmsnm = new double[2 * size];
145 
146                 if (existingDate == null) {
147 
148                     // no prior existing transforms, just generate a first set
149                     for (int i = 0; i < cache.getMaxNeighborsSize(); ++i) {
150                         final AbsoluteDate t = date.shiftedBy((i - cache.getMaxNeighborsSize() / 2) * step);
151                         fillArray(rawProvider.onDate(t), cnmsnm);
152                         generated.add(new TimeStampedSphericalHarmonics(t, cnmsnm));
153                     }
154 
155                 } else {
156 
157                     // some coefficients have already been generated
158                     // add the missing ones up to specified date
159 
160                     AbsoluteDate t = existingDate;
161                     if (date.compareTo(t) > 0) {
162                         // forward generation
163                         do {
164                             t = t.shiftedBy(step);
165                             fillArray(rawProvider.onDate(t), cnmsnm);
166                             generated.add(new TimeStampedSphericalHarmonics(t, cnmsnm));
167                         } while (t.compareTo(date) <= 0);
168                     } else {
169                         // backward generation
170                         do {
171                             t = t.shiftedBy(-step);
172                             fillArray(rawProvider.onDate(t), cnmsnm);
173                             generated.add(new TimeStampedSphericalHarmonics(t, cnmsnm));
174                         } while (t.compareTo(date) >= 0);
175                         // ensure forward chronological order
176                         Collections.reverse(generated);
177                     }
178 
179                 }
180 
181                 // return the generated sample
182                 return generated;
183 
184             } catch (OrekitException oe) {
185                 throw new TimeStampedCacheException(oe);
186             }
187         }
188 
189         /** Fill coefficients array for one entry.
190          * @param raw the un-interpolated spherical harmonics
191          * @param cnmsnm arrays to fill in
192          */
193         private void fillArray(final NormalizedSphericalHarmonics raw,
194                                final double[] cnmsnm) {
195             int index = 0;
196             for (int n = 0; n <= rawProvider.getMaxDegree(); ++n) {
197                 for (int m = 0; m <= n; ++m) {
198                     cnmsnm[index++] = raw.getNormalizedCnm(n, m);
199                 }
200             }
201             for (int n = 0; n <= rawProvider.getMaxDegree(); ++n) {
202                 for (int m = 0; m <= n; ++m) {
203                     cnmsnm[index++] = raw.getNormalizedSnm(n, m);
204                 }
205             }
206         }
207 
208     }
209 
210     /**
211      * Internal class for time-stamped spherical harmonics. Instances are created using
212      * {@link #interpolate(AbsoluteDate, Collection)}
213      */
214     private static class TimeStampedSphericalHarmonics
215             implements TimeStamped, NormalizedSphericalHarmonics {
216 
217         /** Current date. */
218         private final AbsoluteDate date;
219 
220         /** number of C or S coefficients. */
221         private final int size;
222 
223         /** Flattened array for C<sub>n,m</sub> and S<sub>n,m</sub> coefficients. */
224         private final double[] cnmsnm;
225 
226         /** Simple constructor.
227          * @param date current date
228          * @param cnmsnm flattened array for C<sub>n,m</sub> and S<sub>n,m</sub>
229          *               coefficients. It is copied.
230          */
231         private TimeStampedSphericalHarmonics(final AbsoluteDate date,
232                                               final double[] cnmsnm) {
233             this.date   = date;
234             this.cnmsnm = cnmsnm.clone();
235             this.size   = cnmsnm.length / 2;
236         }
237 
238         /** {@inheritDoc} */
239         @Override
240         public AbsoluteDate getDate() {
241             return date;
242         }
243 
244         /** {@inheritDoc} */
245         @Override
246         public double getNormalizedCnm(final int n, final int m) {
247             return cnmsnm[(n * (n + 1)) / 2 + m];
248         }
249 
250         /** {@inheritDoc} */
251         @Override
252         public double getNormalizedSnm(final int n, final int m) {
253             return cnmsnm[(n * (n + 1)) / 2 + m + size];
254         }
255 
256         /** Interpolate spherical harmonics.
257          * <p>
258          * The interpolated instance is created by polynomial Hermite interpolation.
259          * </p>
260          * @param date interpolation date
261          * @param sample sample points on which interpolation should be done
262          * @return a new time-stamped spherical harmonics, interpolated at specified date
263          */
264         public static TimeStampedSphericalHarmonics interpolate(final AbsoluteDate date,
265                                                                 final Stream<TimeStampedSphericalHarmonics> sample) {
266 
267             // set up an interpolator taking derivatives into account
268             final HermiteInterpolator interpolator = new HermiteInterpolator();
269 
270             // add sample points
271             sample.forEach(tssh -> interpolator.addSamplePoint(tssh.date.durationFrom(date), tssh.cnmsnm));
272 
273             // build a new interpolated instance
274             return new TimeStampedSphericalHarmonics(date, interpolator.value(0.0));
275 
276         }
277 
278     }
279 
280 }