1   /* Copyright 2002-2022 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<TimeStampedSphericalHarmonics>(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     @Deprecated
113     @Override
114     public double getOffset(final AbsoluteDate date) {
115         return rawProvider.getOffset(date);
116     }
117 
118     /** {@inheritDoc} */
119     @Override
120     public TideSystem getTideSystem() {
121         return rawProvider.getTideSystem();
122     }
123 
124     /** {@inheritDoc} */
125     @Override
126     public NormalizedSphericalHarmonics onDate(final AbsoluteDate date) {
127         return TimeStampedSphericalHarmonics.interpolate(date, cache.getNeighbors(date));
128     }
129 
130     /** Generator for time-stamped spherical harmonics. */
131     private class Generator implements TimeStampedGenerator<TimeStampedSphericalHarmonics> {
132 
133         /** Time step between generated sets. */
134         private final double step;
135 
136         /** Simple constructor.
137          * @param step time step between generated sets
138          */
139         Generator(final double step) {
140             this.step = step;
141         }
142 
143         /** {@inheritDoc} */
144         @Override
145         public List<TimeStampedSphericalHarmonics> generate(final AbsoluteDate existingDate,
146                                                             final AbsoluteDate date) {
147             try {
148 
149                 final List<TimeStampedSphericalHarmonics> generated =
150                         new ArrayList<TimeStampedSphericalHarmonics>();
151                 final double[] cnmsnm = new double[2 * size];
152 
153                 if (existingDate == null) {
154 
155                     // no prior existing transforms, just generate a first set
156                     for (int i = 0; i < cache.getNeighborsSize(); ++i) {
157                         final AbsoluteDate t = date.shiftedBy((i - cache.getNeighborsSize() / 2) * step);
158                         fillArray(rawProvider.onDate(t), cnmsnm);
159                         generated.add(new TimeStampedSphericalHarmonics(t, cnmsnm));
160                     }
161 
162                 } else {
163 
164                     // some coefficients have already been generated
165                     // add the missing ones up to specified date
166 
167                     AbsoluteDate t = existingDate;
168                     if (date.compareTo(t) > 0) {
169                         // forward 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                     } else {
176                         // backward generation
177                         do {
178                             t = t.shiftedBy(-step);
179                             fillArray(rawProvider.onDate(t), cnmsnm);
180                             generated.add(new TimeStampedSphericalHarmonics(t, cnmsnm));
181                         } while (t.compareTo(date) >= 0);
182                         // ensure forward chronological order
183                         Collections.reverse(generated);
184                     }
185 
186                 }
187 
188                 // return the generated sample
189                 return generated;
190 
191             } catch (OrekitException oe) {
192                 throw new TimeStampedCacheException(oe);
193             }
194         }
195 
196         /** Fill coefficients array for one entry.
197          * @param raw the un-interpolated spherical harmonics
198          * @param cnmsnm arrays to fill in
199          */
200         private void fillArray(final NormalizedSphericalHarmonics raw,
201                                final double[] cnmsnm) {
202             int index = 0;
203             for (int n = 0; n <= rawProvider.getMaxDegree(); ++n) {
204                 for (int m = 0; m <= n; ++m) {
205                     cnmsnm[index++] = raw.getNormalizedCnm(n, m);
206                 }
207             }
208             for (int n = 0; n <= rawProvider.getMaxDegree(); ++n) {
209                 for (int m = 0; m <= n; ++m) {
210                     cnmsnm[index++] = raw.getNormalizedSnm(n, m);
211                 }
212             }
213         }
214 
215     }
216 
217     /**
218      * Internal class for time-stamped spherical harmonics. Instances are created using
219      * {@link #interpolate(AbsoluteDate, Collection)}
220      */
221     private static class TimeStampedSphericalHarmonics
222             implements TimeStamped, NormalizedSphericalHarmonics {
223 
224         /** Current date. */
225         private final AbsoluteDate date;
226 
227         /** number of C or S coefficients. */
228         private final int size;
229 
230         /** Flattened array for C<sub>n,m</sub> and S<sub>n,m</sub> coefficients. */
231         private final double[] cnmsnm;
232 
233         /** Simple constructor.
234          * @param date current date
235          * @param cnmsnm flattened array for C<sub>n,m</sub> and S<sub>n,m</sub>
236          *               coefficients. It is copied.
237          */
238         private TimeStampedSphericalHarmonics(final AbsoluteDate date,
239                                               final double[] cnmsnm) {
240             this.date   = date;
241             this.cnmsnm = cnmsnm.clone();
242             this.size   = cnmsnm.length / 2;
243         }
244 
245         /** {@inheritDoc} */
246         @Override
247         public AbsoluteDate getDate() {
248             return date;
249         }
250 
251         /** {@inheritDoc} */
252         @Override
253         public double getNormalizedCnm(final int n, final int m) {
254             return cnmsnm[(n * (n + 1)) / 2 + m];
255         }
256 
257         /** {@inheritDoc} */
258         @Override
259         public double getNormalizedSnm(final int n, final int m) {
260             return cnmsnm[(n * (n + 1)) / 2 + m + size];
261         }
262 
263         /** Interpolate spherical harmonics.
264          * <p>
265          * The interpolated instance is created by polynomial Hermite interpolation.
266          * </p>
267          * @param date interpolation date
268          * @param sample sample points on which interpolation should be done
269          * @return a new time-stamped spherical harmonics, interpolated at specified date
270          */
271         public static TimeStampedSphericalHarmonics interpolate(final AbsoluteDate date,
272                                                                 final Stream<TimeStampedSphericalHarmonics> sample) {
273 
274             // set up an interpolator taking derivatives into account
275             final HermiteInterpolator interpolator = new HermiteInterpolator();
276 
277             // add sample points
278             sample.forEach(tssh -> interpolator.addSamplePoint(tssh.date.durationFrom(date), tssh.cnmsnm));
279 
280             // build a new interpolated instance
281             return new TimeStampedSphericalHarmonics(date, interpolator.value(0.0));
282 
283         }
284 
285     }
286 
287 }