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