1   /* Copyright 2002-2026 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.utils;
18  
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.io.Serial;
24  import java.io.Serializable;
25  import java.nio.charset.StandardCharsets;
26  import java.util.List;
27  import java.util.function.Function;
28  import java.util.regex.Pattern;
29  
30  import org.hipparchus.CalculusFieldElement;
31  import org.hipparchus.analysis.interpolation.FieldHermiteInterpolator;
32  import org.hipparchus.analysis.interpolation.HermiteInterpolator;
33  import org.hipparchus.util.FastMath;
34  import org.hipparchus.util.FieldSinCos;
35  import org.hipparchus.util.MathArrays;
36  import org.hipparchus.util.MathUtils;
37  import org.hipparchus.util.SinCos;
38  import org.orekit.annotation.DefaultDataContext;
39  import org.orekit.data.BodiesElements;
40  import org.orekit.data.DataContext;
41  import org.orekit.data.DelaunayArguments;
42  import org.orekit.data.FieldBodiesElements;
43  import org.orekit.data.FieldDelaunayArguments;
44  import org.orekit.data.FundamentalNutationArguments;
45  import org.orekit.data.PoissonSeries;
46  import org.orekit.data.PoissonSeries.CompiledSeries;
47  import org.orekit.data.PoissonSeriesParser;
48  import org.orekit.data.PolynomialNutation;
49  import org.orekit.data.PolynomialParser;
50  import org.orekit.data.PolynomialParser.Unit;
51  import org.orekit.data.SimpleTimeStampedTableParser;
52  import org.orekit.errors.OrekitException;
53  import org.orekit.errors.OrekitInternalError;
54  import org.orekit.errors.OrekitMessages;
55  import org.orekit.errors.TimeStampedCacheException;
56  import org.orekit.frames.EOPHistory;
57  import org.orekit.frames.FieldPoleCorrection;
58  import org.orekit.frames.PoleCorrection;
59  import org.orekit.time.AbsoluteDate;
60  import org.orekit.time.DateComponents;
61  import org.orekit.time.FieldAbsoluteDate;
62  import org.orekit.time.TimeComponents;
63  import org.orekit.time.TimeScalarFunction;
64  import org.orekit.time.TimeScale;
65  import org.orekit.time.TimeScales;
66  import org.orekit.time.TimeStamped;
67  import org.orekit.time.TimeVectorFunction;
68  
69  
70  /** Supported IERS conventions.
71   * @since 6.0
72   * @author Luc Maisonobe
73   */
74  public enum IERSConventions {
75  
76      /** Constant for IERS 1996 conventions. */
77      IERS_1996 {
78  
79          /** Nutation arguments resources. */
80          private static final String NUTATION_ARGUMENTS = IERS_BASE + "1996/nutation-arguments.txt";
81  
82          /** X series resources. */
83          private static final String X_Y_SERIES         = IERS_BASE + "1996/tab5.4.txt";
84  
85          /** Psi series resources. */
86          private static final String PSI_EPSILON_SERIES = IERS_BASE + "1996/tab5.1.txt";
87  
88          /** Tidal correction for xp, yp series resources. */
89          private static final String TIDAL_CORRECTION_XP_YP_SERIES = IERS_BASE + "1996/tab8.4.txt";
90  
91          /** Tidal correction for UT1 resources. */
92          private static final String TIDAL_CORRECTION_UT1_SERIES = IERS_BASE + "1996/tab8.3.txt";
93  
94          /** Love numbers resources. */
95          private static final String LOVE_NUMBERS = IERS_BASE + "1996/tab6.1.txt";
96  
97          /** Frequency dependence model for k₂₀. */
98          private static final String K20_FREQUENCY_DEPENDENCE = IERS_BASE + "1996/tab6.2b.txt";
99  
100         /** Frequency dependence model for k₂₁. */
101         private static final String K21_FREQUENCY_DEPENDENCE = IERS_BASE + "1996/tab6.2a.txt";
102 
103         /** Frequency dependence model for k₂₂. */
104         private static final String K22_FREQUENCY_DEPENDENCE = IERS_BASE + "1996/tab6.2c.txt";
105 
106         /** Tidal displacement frequency correction for diurnal tides. */
107         private static final String TIDAL_DISPLACEMENT_CORRECTION_DIURNAL = IERS_BASE + "1996/tab7.3a.txt";
108 
109         /** Tidal displacement frequency correction for zonal tides. */
110         private static final String TIDAL_DISPLACEMENT_CORRECTION_ZONAL = IERS_BASE + "1996/tab7.3b.txt";
111 
112         /** {@inheritDoc} */
113         @Override
114         public FundamentalNutationArguments getNutationArguments(final TimeScale timeScale,
115                                                                  final TimeScales timeScales) {
116             return load(NUTATION_ARGUMENTS, in -> new FundamentalNutationArguments(this, timeScale,
117                                                                                             in, NUTATION_ARGUMENTS,
118                                                                                             timeScales));
119         }
120 
121         /** {@inheritDoc} */
122         @Override
123         public TimeScalarFunction getMeanObliquityFunction(final TimeScales timeScales) {
124 
125             // value from chapter 5, page 22
126             final PolynomialNutation epsilonA =
127                     new PolynomialNutation(84381.448    * Constants.ARC_SECONDS_TO_RADIANS,
128                                              -46.8150   * Constants.ARC_SECONDS_TO_RADIANS,
129                                               -0.00059  * Constants.ARC_SECONDS_TO_RADIANS,
130                                                0.001813 * Constants.ARC_SECONDS_TO_RADIANS);
131 
132             return new TimeScalarFunction() {
133 
134                 /** {@inheritDoc} */
135                 @Override
136                 public double value(final AbsoluteDate date) {
137                     return epsilonA.value(evaluateTC(date, timeScales));
138                 }
139 
140                 /** {@inheritDoc} */
141                 @Override
142                 public <T extends CalculusFieldElement<T>> T value(final FieldAbsoluteDate<T> date) {
143                     return epsilonA.value(evaluateTC(date, timeScales));
144                 }
145 
146             };
147 
148         }
149 
150         /** {@inheritDoc} */
151         @Override
152         public TimeVectorFunction getXYSpXY2Function(final TimeScales timeScales) {
153 
154             // set up nutation arguments
155             final FundamentalNutationArguments arguments =
156                     getNutationArguments(timeScales);
157 
158             // X = 2004.3109″t - 0.42665″t² - 0.198656″t³ + 0.0000140″t⁴
159             //     + 0.00006″t² cos Ω + sin ε0 { Σ [(Ai + Ai' t) sin(ARGUMENT) + Ai'' t cos(ARGUMENT)]}
160             //     + 0.00204″t² sin Ω + 0.00016″t² sin 2(F - D + Ω),
161             final PolynomialNutation xPolynomial =
162                     new PolynomialNutation(0,
163                                            2004.3109 * Constants.ARC_SECONDS_TO_RADIANS,
164                                            -0.42665  * Constants.ARC_SECONDS_TO_RADIANS,
165                                            -0.198656 * Constants.ARC_SECONDS_TO_RADIANS,
166                                            0.0000140 * Constants.ARC_SECONDS_TO_RADIANS);
167 
168             final double fXCosOm    = 0.00006 * Constants.ARC_SECONDS_TO_RADIANS;
169             final double fXSinOm    = 0.00204 * Constants.ARC_SECONDS_TO_RADIANS;
170             final double fXSin2FDOm = 0.00016 * Constants.ARC_SECONDS_TO_RADIANS;
171             final double sinEps0   = FastMath.sin(getMeanObliquityFunction(timeScales)
172                     .value(getNutationReferenceEpoch(timeScales)));
173 
174             final double deciMilliAS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-4;
175             final PoissonSeriesParser baseParser =
176                     new PoissonSeriesParser(12).withFirstDelaunay(1);
177 
178             final PoissonSeriesParser xParser =
179                     baseParser.
180                     withSinCos(0, 7, deciMilliAS, -1, deciMilliAS).
181                     withSinCos(1, 8, deciMilliAS,  9, deciMilliAS);
182             final PoissonSeries xSum = load(X_Y_SERIES, in -> xParser.parse(in, X_Y_SERIES));
183 
184             // Y = -0.00013″ - 22.40992″t² + 0.001836″t³ + 0.0011130″t⁴
185             //     + Σ [(Bi + Bi' t) cos(ARGUMENT) + Bi'' t sin(ARGUMENT)]
186             //    - 0.00231″t² cos Ω − 0.00014″t² cos 2(F - D + Ω)
187             final PolynomialNutation yPolynomial =
188                     new PolynomialNutation(-0.00013  * Constants.ARC_SECONDS_TO_RADIANS,
189                                            0.0,
190                                            -22.40992 * Constants.ARC_SECONDS_TO_RADIANS,
191                                            0.001836  * Constants.ARC_SECONDS_TO_RADIANS,
192                                            0.0011130 * Constants.ARC_SECONDS_TO_RADIANS);
193 
194             final double fYCosOm    = -0.00231 * Constants.ARC_SECONDS_TO_RADIANS;
195             final double fYCos2FDOm = -0.00014 * Constants.ARC_SECONDS_TO_RADIANS;
196 
197             final PoissonSeriesParser yParser =
198                     baseParser.
199                     withSinCos(0, -1, deciMilliAS, 10, deciMilliAS).
200                     withSinCos(1, 12, deciMilliAS, 11, deciMilliAS);
201             final PoissonSeries ySum = load(X_Y_SERIES, in -> yParser.parse(in, X_Y_SERIES));
202 
203             final PoissonSeries.CompiledSeries xySum =
204                     PoissonSeries.compile(xSum, ySum);
205 
206             // s = -XY/2 + 0.00385″t - 0.07259″t³ - 0.00264″ sin Ω - 0.00006″ sin 2Ω
207             //     + 0.00074″t² sin Ω + 0.00006″t² sin 2(F - D + Ω)
208             final double fST          =  0.00385 * Constants.ARC_SECONDS_TO_RADIANS;
209             final double fST3         = -0.07259 * Constants.ARC_SECONDS_TO_RADIANS;
210             final double fSSinOm      = -0.00264 * Constants.ARC_SECONDS_TO_RADIANS;
211             final double fSSin2Om     = -0.00006 * Constants.ARC_SECONDS_TO_RADIANS;
212             final double fST2SinOm    =  0.00074 * Constants.ARC_SECONDS_TO_RADIANS;
213             final double fST2Sin2FDOm =  0.00006 * Constants.ARC_SECONDS_TO_RADIANS;
214 
215             return new TimeVectorFunction() {
216 
217                 /** {@inheritDoc} */
218                 @Override
219                 public double[] value(final AbsoluteDate date) {
220 
221                     final BodiesElements elements = arguments.evaluateAll(date);
222                     final double[] xy             = xySum.value(elements);
223 
224                     final double omega     = elements.getOmega();
225                     final double f         = elements.getF();
226                     final double d         = elements.getD();
227                     final double t         = elements.getTC();
228 
229                     final SinCos scOmega   = FastMath.sinCos(omega);
230                     final SinCos sc2omega  = SinCos.sum(scOmega, scOmega);
231                     final SinCos sc2FD0m   = FastMath.sinCos(2 * (f - d + omega));
232                     final double cosOmega  = scOmega.cos();
233                     final double sinOmega  = scOmega.sin();
234                     final double sin2Omega = sc2omega.sin();
235                     final double cos2FDOm  = sc2FD0m.cos();
236                     final double sin2FDOm  = sc2FD0m.sin();
237 
238                     final double x = xPolynomial.value(t) + sinEps0 * xy[0] +
239                             t * t * (fXCosOm * cosOmega + fXSinOm * sinOmega + fXSin2FDOm * cos2FDOm);
240                     final double y = yPolynomial.value(t) + xy[1] +
241                             t * t * (fYCosOm * cosOmega + fYCos2FDOm * cos2FDOm);
242                     final double sPxy2 = fSSinOm * sinOmega + fSSin2Om * sin2Omega +
243                             t * (fST + t * (fST2SinOm * sinOmega + fST2Sin2FDOm * sin2FDOm + t * fST3));
244 
245                     return new double[] {
246                         x, y, sPxy2
247                     };
248 
249                 }
250 
251                 /** {@inheritDoc} */
252                 @Override
253                 public <T extends CalculusFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
254 
255                     final FieldBodiesElements<T> elements = arguments.evaluateAll(date);
256                     final T[] xy             = xySum.value(elements);
257 
258                     final T omega     = elements.getOmega();
259                     final T f         = elements.getF();
260                     final T d         = elements.getD();
261                     final T t         = elements.getTC();
262                     final T t2        = t.square();
263 
264                     final FieldSinCos<T> scOmega  = FastMath.sinCos(omega);
265                     final FieldSinCos<T> sc2omega = FieldSinCos.sum(scOmega, scOmega);
266                     final FieldSinCos<T> sc2FD0m  = FastMath.sinCos(f.subtract(d).add(omega).multiply(2));
267                     final T cosOmega  = scOmega.cos();
268                     final T sinOmega  = scOmega.sin();
269                     final T sin2Omega = sc2omega.sin();
270                     final T cos2FDOm  = sc2FD0m.cos();
271                     final T sin2FDOm  = sc2FD0m.sin();
272 
273                     final T x = xPolynomial.value(t).
274                                 add(xy[0].multiply(sinEps0)).
275                                 add(t2.multiply(cosOmega.multiply(fXCosOm).add(sinOmega.multiply(fXSinOm)).add(cos2FDOm.multiply(fXSin2FDOm))));
276                     final T y = yPolynomial.value(t).
277                                 add(xy[1]).
278                                 add(t2.multiply(cosOmega.multiply(fYCosOm).add(cos2FDOm.multiply(fYCos2FDOm))));
279                     final T sPxy2 = sinOmega.multiply(fSSinOm).
280                                     add(sin2Omega.multiply(fSSin2Om)).
281                                     add(t.multiply(fST3).add(sinOmega.multiply(fST2SinOm)).add(sin2FDOm.multiply(fST2Sin2FDOm)).multiply(t).add(fST).multiply(t));
282 
283                     final T[] a = MathArrays.buildArray(date.getField(), 3);
284                     a[0] = x;
285                     a[1] = y;
286                     a[2] = sPxy2;
287                     return a;
288 
289                 }
290 
291             };
292 
293         }
294 
295         /** {@inheritDoc} */
296         @Override
297         public TimeVectorFunction getPrecessionFunction(final TimeScales timeScales) {
298 
299             // set up the conventional polynomials
300             // the following values are from Lieske et al. paper:
301             // Expressions for the precession quantities based upon the IAU(1976) system of astronomical constants
302             // http://articles.adsabs.harvard.edu/full/1977A%26A....58....1L
303             // also available as equation 30 in IERS 2003 conventions
304             final PolynomialNutation psiA =
305                     new PolynomialNutation(   0.0,
306                                            5038.7784   * Constants.ARC_SECONDS_TO_RADIANS,
307                                              -1.07259  * Constants.ARC_SECONDS_TO_RADIANS,
308                                              -0.001147 * Constants.ARC_SECONDS_TO_RADIANS);
309             final PolynomialNutation omegaA =
310                     new PolynomialNutation(getMeanObliquityFunction(timeScales)
311                             .value(getNutationReferenceEpoch(timeScales)),
312                                             0.0,
313                                             0.05127   * Constants.ARC_SECONDS_TO_RADIANS,
314                                            -0.007726  * Constants.ARC_SECONDS_TO_RADIANS);
315             final PolynomialNutation chiA =
316                     new PolynomialNutation( 0.0,
317                                            10.5526   * Constants.ARC_SECONDS_TO_RADIANS,
318                                            -2.38064  * Constants.ARC_SECONDS_TO_RADIANS,
319                                            -0.001125 * Constants.ARC_SECONDS_TO_RADIANS);
320 
321             return new PrecessionFunction(psiA, omegaA, chiA, timeScales);
322 
323         }
324 
325         /** {@inheritDoc} */
326         @Override
327         public TimeVectorFunction getNutationFunction(final TimeScales timeScales) {
328 
329             // set up nutation arguments
330             final FundamentalNutationArguments arguments =
331                     getNutationArguments(timeScales);
332 
333             // set up Poisson series
334             final double deciMilliAS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-4;
335             final PoissonSeriesParser baseParser =
336                     new PoissonSeriesParser(10).withFirstDelaunay(1);
337 
338             final PoissonSeriesParser psiParser =
339                     baseParser.
340                     withSinCos(0, 7, deciMilliAS, -1, deciMilliAS).
341                     withSinCos(1, 8, deciMilliAS, -1, deciMilliAS);
342             final PoissonSeries psiSeries = load(PSI_EPSILON_SERIES, in -> psiParser.parse(in, PSI_EPSILON_SERIES));
343 
344             final PoissonSeriesParser epsilonParser =
345                     baseParser.
346                     withSinCos(0, -1, deciMilliAS, 9, deciMilliAS).
347                     withSinCos(1, -1, deciMilliAS, 10, deciMilliAS);
348             final PoissonSeries epsilonSeries = load(PSI_EPSILON_SERIES, in -> epsilonParser.parse(in, PSI_EPSILON_SERIES));
349 
350             final PoissonSeries.CompiledSeries psiEpsilonSeries =
351                     PoissonSeries.compile(psiSeries, epsilonSeries);
352 
353             return new TimeVectorFunction() {
354 
355                 /** {@inheritDoc} */
356                 @Override
357                 public double[] value(final AbsoluteDate date) {
358                     final BodiesElements elements = arguments.evaluateAll(date);
359                     final double[] psiEpsilon = psiEpsilonSeries.value(elements);
360                     return new double[] {
361                         psiEpsilon[0], psiEpsilon[1], IAU1994ResolutionC7.value(elements, timeScales.getTAI())
362                     };
363                 }
364 
365                 /** {@inheritDoc} */
366                 @Override
367                 public <T extends CalculusFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
368                     final FieldBodiesElements<T> elements = arguments.evaluateAll(date);
369                     final T[] psiEpsilon = psiEpsilonSeries.value(elements);
370                     final T[] result = MathArrays.buildArray(date.getField(), 3);
371                     result[0] = psiEpsilon[0];
372                     result[1] = psiEpsilon[1];
373                     result[2] = IAU1994ResolutionC7.value(elements, timeScales.getTAI());
374                     return result;
375                 }
376 
377             };
378 
379         }
380 
381         /** {@inheritDoc} */
382         @Override
383         public TimeScalarFunction getGMSTFunction(final TimeScale ut1,
384                                                   final TimeScales timeScales) {
385 
386             // Radians per second of time
387             final double radiansPerSecond = MathUtils.TWO_PI / Constants.JULIAN_DAY;
388 
389             // constants from IERS 1996 page 21
390             // the underlying model is IAU 1982 GMST-UT1
391             final AbsoluteDate gmstReference = new AbsoluteDate(
392                     DateComponents.J2000_EPOCH, TimeComponents.H12, timeScales.getTAI());
393             final double gmst0 = 24110.54841;
394             final double gmst1 = 8640184.812866;
395             final double gmst2 = 0.093104;
396             final double gmst3 = -6.2e-6;
397 
398             return new TimeScalarFunction() {
399 
400                 /** {@inheritDoc} */
401                 @Override
402                 public double value(final AbsoluteDate date) {
403 
404                     // offset in Julian centuries from J2000 epoch (UT1 scale)
405                     final double dtai = date.durationFrom(gmstReference);
406                     final double tut1 = dtai + ut1.offsetFromTAI(date).toDouble();
407                     final double tt   = tut1 / Constants.JULIAN_CENTURY;
408 
409                     // Seconds in the day, adjusted by 12 hours because the
410                     // UT1 is supplied as a Julian date beginning at noon.
411                     final double sd = FastMath.IEEEremainder(tut1 + Constants.JULIAN_DAY / 2, Constants.JULIAN_DAY);
412 
413                     // compute Greenwich mean sidereal time, in radians
414                     return ((((((tt * gmst3 + gmst2) * tt) + gmst1) * tt) + gmst0) + sd) * radiansPerSecond;
415 
416                 }
417 
418                 /** {@inheritDoc} */
419                 @Override
420                 public <T extends CalculusFieldElement<T>> T value(final FieldAbsoluteDate<T> date) {
421 
422                     // offset in Julian centuries from J2000 epoch (UT1 scale)
423                     final T dtai = date.durationFrom(gmstReference);
424                     final T tut1 = dtai.add(ut1.offsetFromTAI(date.toAbsoluteDate()).toDouble());
425                     final T tt   = tut1.divide(Constants.JULIAN_CENTURY);
426 
427                     // Seconds in the day, adjusted by 12 hours because the
428                     // UT1 is supplied as a Julian date beginning at noon.
429                     final T sd = tut1.add(Constants.JULIAN_DAY / 2).remainder(Constants.JULIAN_DAY);
430 
431                     // compute Greenwich mean sidereal time, in radians
432                     return tt.multiply(gmst3).add(gmst2).multiply(tt).add(gmst1).multiply(tt).add(gmst0).add(sd).multiply(radiansPerSecond);
433 
434                 }
435 
436             };
437 
438         }
439 
440         /** {@inheritDoc} */
441         @Override
442         public TimeScalarFunction getGMSTRateFunction(final TimeScale ut1,
443                                                       final TimeScales timeScales) {
444 
445             // Radians per second of time
446             final double radiansPerSecond = MathUtils.TWO_PI / Constants.JULIAN_DAY;
447 
448             // constants from IERS 1996 page 21
449             // the underlying model is IAU 1982 GMST-UT1
450             final AbsoluteDate gmstReference = new AbsoluteDate(
451                     DateComponents.J2000_EPOCH, TimeComponents.H12, timeScales.getTAI());
452             final double gmst1 = 8640184.812866;
453             final double gmst2 = 0.093104;
454             final double gmst3 = -6.2e-6;
455 
456             return new TimeScalarFunction() {
457 
458                 /** {@inheritDoc} */
459                 @Override
460                 public double value(final AbsoluteDate date) {
461 
462                     // offset in Julian centuries from J2000 epoch (UT1 scale)
463                     final double dtai = date.durationFrom(gmstReference);
464                     final double tut1 = dtai + ut1.offsetFromTAI(date).toDouble();
465                     final double tt   = tut1 / Constants.JULIAN_CENTURY;
466 
467                     // compute Greenwich mean sidereal time rate, in radians per second
468                     return ((((tt * 3 * gmst3 + 2 * gmst2) * tt) + gmst1) / Constants.JULIAN_CENTURY + 1) * radiansPerSecond;
469 
470                 }
471 
472                 /** {@inheritDoc} */
473                 @Override
474                 public <T extends CalculusFieldElement<T>> T value(final FieldAbsoluteDate<T> date) {
475 
476                     // offset in Julian centuries from J2000 epoch (UT1 scale)
477                     final T dtai = date.durationFrom(gmstReference);
478                     final T tut1 = dtai.add(ut1.offsetFromTAI(date.toAbsoluteDate()).toDouble());
479                     final T tt   = tut1.divide(Constants.JULIAN_CENTURY);
480 
481                     // compute Greenwich mean sidereal time, in radians
482                     return tt.multiply(3 * gmst3).add(2 * gmst2).multiply(tt).add(gmst1).divide(Constants.JULIAN_CENTURY).add(1).multiply(radiansPerSecond);
483 
484                 }
485 
486             };
487 
488         }
489 
490         /** {@inheritDoc} */
491         @Override
492         public TimeScalarFunction getGASTFunction(final TimeScale ut1,
493                                                   final EOPHistory eopHistory,
494                                                   final TimeScales timeScales) {
495 
496             // obliquity
497             final TimeScalarFunction epsilonA = getMeanObliquityFunction(timeScales);
498 
499             // GMST function
500             final TimeScalarFunction gmst = getGMSTFunction(ut1, timeScales);
501 
502             // nutation function
503             final TimeVectorFunction nutation = getNutationFunction(timeScales);
504 
505             return new TimeScalarFunction() {
506 
507                 /** {@inheritDoc} */
508                 @Override
509                 public double value(final AbsoluteDate date) {
510 
511                     // compute equation of equinoxes
512                     final double[] angles = nutation.value(date);
513                     double deltaPsi = angles[0];
514                     if (eopHistory != null) {
515                         deltaPsi += eopHistory.getEquinoxNutationCorrection(date)[0];
516                     }
517                     final double eqe = deltaPsi  * FastMath.cos(epsilonA.value(date)) + angles[2];
518 
519                     // add mean sidereal time and equation of equinoxes
520                     return gmst.value(date) + eqe;
521 
522                 }
523 
524                 /** {@inheritDoc} */
525                 @Override
526                 public <T extends CalculusFieldElement<T>> T value(final FieldAbsoluteDate<T> date) {
527 
528                     // compute equation of equinoxes
529                     final T[] angles = nutation.value(date);
530                     T deltaPsi = angles[0];
531                     if (eopHistory != null) {
532                         deltaPsi = deltaPsi.add(eopHistory.getEquinoxNutationCorrection(date)[0]);
533                     }
534                     final T eqe = deltaPsi.multiply(epsilonA.value(date).cos()).add(angles[2]);
535 
536                     // add mean sidereal time and equation of equinoxes
537                     return gmst.value(date).add(eqe);
538 
539                 }
540 
541             };
542 
543         }
544 
545         /** {@inheritDoc} */
546         @Override
547         public TimeVectorFunction getEOPTidalCorrection(final TimeScales timeScales) {
548 
549             // set up nutation arguments
550             // BEWARE! Using TT as the time scale here and not UT1 is intentional!
551             // as this correction is used to compute UT1 itself, it is not surprising we cannot use UT1 yet,
552             // however, using the close UTC as would seem logical make the comparison with interp.f from IERS fail
553             // looking in the interp.f code, the same TT scale is used for both Delaunay and gamma argument
554             final FundamentalNutationArguments arguments =
555                     getNutationArguments(timeScales.getTT(), timeScales);
556 
557             // set up Poisson series
558             final double milliAS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-3;
559             final PoissonSeriesParser xyParser = new PoissonSeriesParser(17).
560                     withOptionalColumn(1).
561                     withGamma(7).
562                     withFirstDelaunay(2);
563             final PoissonSeries xSeries =
564                             load(TIDAL_CORRECTION_XP_YP_SERIES, in -> xyParser.
565                                                                       withSinCos(0, 14, milliAS, 15, milliAS).
566                                                                       parse(in, TIDAL_CORRECTION_XP_YP_SERIES));
567             final PoissonSeries ySeries =
568                             load(TIDAL_CORRECTION_XP_YP_SERIES, in -> xyParser.
569                                                                       withSinCos(0, 16, milliAS, 17, milliAS).
570                                                                       parse(in, TIDAL_CORRECTION_XP_YP_SERIES));
571 
572             final double deciMilliS = 1.0e-4;
573             final PoissonSeriesParser ut1Parser = new PoissonSeriesParser(17).
574                     withOptionalColumn(1).
575                     withGamma(7).
576                     withFirstDelaunay(2);
577             final PoissonSeries ut1Series =
578                             load(TIDAL_CORRECTION_UT1_SERIES, in -> ut1Parser.
579                                                                     withSinCos(0, 16, deciMilliS, 17, deciMilliS).
580                                                                     parse(in, TIDAL_CORRECTION_UT1_SERIES));
581 
582             return new EOPTidalCorrection(arguments, xSeries, ySeries, ut1Series);
583 
584         }
585 
586         /** {@inheritDoc} */
587         public LoveNumbers getLoveNumbers() {
588             return loadLoveNumbers(LOVE_NUMBERS);
589         }
590 
591         /** {@inheritDoc} */
592         public TimeVectorFunction getTideFrequencyDependenceFunction(final TimeScale ut1,
593                                                                      final TimeScales timeScales) {
594 
595             // set up nutation arguments
596             final FundamentalNutationArguments arguments = getNutationArguments(ut1, timeScales);
597 
598             // set up Poisson series
599             final PoissonSeriesParser k20Parser =
600                     new PoissonSeriesParser(18).
601                         withOptionalColumn(1).
602                         withDoodson(4, 2).
603                         withFirstDelaunay(10);
604             final PoissonSeriesParser k21Parser =
605                     new PoissonSeriesParser(18).
606                         withOptionalColumn(1).
607                         withDoodson(4, 3).
608                         withFirstDelaunay(10);
609             final PoissonSeriesParser k22Parser =
610                     new PoissonSeriesParser(16).
611                         withOptionalColumn(1).
612                         withDoodson(4, 2).
613                         withFirstDelaunay(10);
614 
615             final double pico = 1.0e-12;
616             final PoissonSeries c20Series =
617                             load(K20_FREQUENCY_DEPENDENCE, in -> k20Parser.
618                                                                  withSinCos(0, 18, -pico, 16, pico).
619                                                                  parse(in, K20_FREQUENCY_DEPENDENCE));
620             final PoissonSeries c21Series =
621                             load(K21_FREQUENCY_DEPENDENCE, in -> k21Parser.
622                                                                  withSinCos(0, 17, pico, 18, pico).
623                                                                  parse(in, K21_FREQUENCY_DEPENDENCE));
624             final PoissonSeries s21Series =
625                             load(K21_FREQUENCY_DEPENDENCE, in -> k21Parser.
626                                                                  withSinCos(0, 18, -pico, 17, pico).
627                                                                  parse(in, K21_FREQUENCY_DEPENDENCE));
628             final PoissonSeries c22Series =
629                             load(K22_FREQUENCY_DEPENDENCE, in -> k22Parser.
630                                                                  withSinCos(0, -1, pico, 16, pico).
631                                                                  parse(in, K22_FREQUENCY_DEPENDENCE));
632             final PoissonSeries s22Series =
633                             load(K22_FREQUENCY_DEPENDENCE, in -> k22Parser.
634                                                                  withSinCos(0, 16, -pico, -1, pico).
635                                                                  parse(in, K22_FREQUENCY_DEPENDENCE));
636 
637             return new TideFrequencyDependenceFunction(arguments,
638                                                        c20Series,
639                                                        c21Series, s21Series,
640                                                        c22Series, s22Series);
641 
642         }
643 
644         /** {@inheritDoc} */
645         @Override
646         public double getPermanentTide() {
647             return 4.4228e-8 * -0.31460 * getLoveNumbers().getReal(2, 0);
648         }
649 
650         /** {@inheritDoc} */
651         @Override
652         public TimeVectorFunction getSolidPoleTide(final EOPHistory eopHistory) {
653 
654             // constants from IERS 1996 page 47
655             final double globalFactor = -1.348e-9 / Constants.ARC_SECONDS_TO_RADIANS;
656             final double coupling     =  0.0112;
657 
658             return new TimeVectorFunction() {
659 
660                 /** {@inheritDoc} */
661                 @Override
662                 public double[] value(final AbsoluteDate date) {
663                     final PoleCorrection pole = eopHistory.getPoleCorrection(date);
664                     return new double[] {
665                         globalFactor * (pole.getXp() + coupling * pole.getYp()),
666                         globalFactor * (coupling * pole.getXp() - pole.getYp()),
667                     };
668                 }
669 
670                 /** {@inheritDoc} */
671                 @Override
672                 public <T extends CalculusFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
673                     final FieldPoleCorrection<T> pole = eopHistory.getPoleCorrection(date);
674                     final T[] a = MathArrays.buildArray(date.getField(), 2);
675                     a[0] = pole.getXp().add(pole.getYp().multiply(coupling)).multiply(globalFactor);
676                     a[1] = pole.getXp().multiply(coupling).subtract(pole.getYp()).multiply(globalFactor);
677                     return a;
678                 }
679 
680             };
681         }
682 
683         /** {@inheritDoc} */
684         @Override
685         public TimeVectorFunction getOceanPoleTide(final EOPHistory eopHistory) {
686 
687             return new TimeVectorFunction() {
688 
689                 /** {@inheritDoc} */
690                 @Override
691                 public double[] value(final AbsoluteDate date) {
692                     // there are no model for ocean pole tide prior to conventions 2010
693                     return new double[] {
694                         0.0, 0.0
695                     };
696                 }
697 
698                 /** {@inheritDoc} */
699                 @Override
700                 public <T extends CalculusFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
701                     // there are no model for ocean pole tide prior to conventions 2010
702                     return MathArrays.buildArray(date.getField(), 2);
703                 }
704 
705             };
706         }
707 
708         /** {@inheritDoc} */
709         @Override
710         public double[] getNominalTidalDisplacement() {
711 
712             //  // elastic Earth values
713             //  return new double[] {
714             //      // h⁽⁰⁾,                                  h⁽²⁾,   h₃,     hI diurnal, hI semi-diurnal,
715             //      0.6026,                                  -0.0006, 0.292, -0.0025, -0.0022,
716             //      // l⁽⁰⁾, l⁽¹⁾ diurnal, l⁽¹⁾ semi-diurnal, l⁽²⁾,   l₃,     lI diurnal, lI semi-diurnal
717             //      0.0831,  0.0012,       0.0024,            0.0002, 0.015, -0.0007,    -0.0007,
718             //      // H₀
719             //      -0.31460
720             //  };
721 
722             // anelastic Earth values
723             return new double[] {
724                 // h⁽⁰⁾,                                  h⁽²⁾,   h₃,     hI diurnal, hI semi-diurnal,
725                 0.6078,                                  -0.0006, 0.292, -0.0025,    -0.0022,
726                 // l⁽⁰⁾, l⁽¹⁾ diurnal, l⁽¹⁾ semi-diurnal, l⁽²⁾,   l₃,     lI diurnal, lI semi-diurnal
727                 0.0847,  0.0012,       0.0024,            0.0002, 0.015, -0.0007,    -0.0007,
728                 // H₀
729                 -0.31460
730             };
731 
732         }
733 
734         /** {@inheritDoc} */
735         @Override
736         public CompiledSeries getTidalDisplacementFrequencyCorrectionDiurnal() {
737             return getTidalDisplacementFrequencyCorrectionDiurnal(TIDAL_DISPLACEMENT_CORRECTION_DIURNAL,
738                                                                   18, 17, -1, 18, -1);
739         }
740 
741         /** {@inheritDoc} */
742         @Override
743         public CompiledSeries getTidalDisplacementFrequencyCorrectionZonal() {
744             return getTidalDisplacementFrequencyCorrectionZonal(TIDAL_DISPLACEMENT_CORRECTION_ZONAL,
745                                                                 20, 17, 19, 18, 20);
746         }
747 
748     },
749 
750     /** Constant for IERS 2003 conventions. */
751     IERS_2003 {
752 
753         /** Nutation arguments resources. */
754         private static final String NUTATION_ARGUMENTS = IERS_BASE + "2003/nutation-arguments.txt";
755 
756         /** X series resources. */
757         private static final String X_SERIES           = IERS_BASE + "2003/tab5.2a.txt";
758 
759         /** Y series resources. */
760         private static final String Y_SERIES           = IERS_BASE + "2003/tab5.2b.txt";
761 
762         /** S series resources. */
763         private static final String S_SERIES           = IERS_BASE + "2003/tab5.2c.txt";
764 
765         /** Luni-solar series resources. */
766         private static final String LUNI_SOLAR_SERIES  = IERS_BASE + "2003/tab5.3a-first-table.txt";
767 
768         /** Planetary series resources. */
769         private static final String PLANETARY_SERIES   = IERS_BASE + "2003/tab5.3b.txt";
770 
771         /** Greenwhich sidereal time series resources. */
772         private static final String GST_SERIES         = IERS_BASE + "2003/tab5.4.txt";
773 
774         /** Tidal correction for xp, yp series resources. */
775         private static final String TIDAL_CORRECTION_XP_YP_SERIES = IERS_BASE + "2003/tab8.2ab.txt";
776 
777         /** Tidal correction for UT1 resources. */
778         private static final String TIDAL_CORRECTION_UT1_SERIES = IERS_BASE + "2003/tab8.3ab.txt";
779 
780         /** Love numbers resources. */
781         private static final String LOVE_NUMBERS = IERS_BASE + "2003/tab6.1.txt";
782 
783         /** Frequency dependence model for k₂₀. */
784         private static final String K20_FREQUENCY_DEPENDENCE = IERS_BASE + "2003/tab6.3b.txt";
785 
786         /** Frequency dependence model for k₂₁. */
787         private static final String K21_FREQUENCY_DEPENDENCE = IERS_BASE + "2003/tab6.3a.txt";
788 
789         /** Frequency dependence model for k₂₂. */
790         private static final String K22_FREQUENCY_DEPENDENCE = IERS_BASE + "2003/tab6.3c.txt";
791 
792         /** Annual pole table. */
793         private static final String ANNUAL_POLE = IERS_BASE + "2003/annual.pole";
794 
795         /** Tidal displacement frequency correction for diurnal tides. */
796         private static final String TIDAL_DISPLACEMENT_CORRECTION_DIURNAL = IERS_BASE + "2003/tab7.5a.txt";
797 
798         /** Tidal displacement frequency correction for zonal tides. */
799         private static final String TIDAL_DISPLACEMENT_CORRECTION_ZONAL = IERS_BASE + "2003/tab7.5b.txt";
800 
801         /** {@inheritDoc} */
802         public FundamentalNutationArguments getNutationArguments(final TimeScale timeScale,
803                                                                  final TimeScales timeScales) {
804             return load(NUTATION_ARGUMENTS, in -> new FundamentalNutationArguments(this, timeScale,
805                                                                                    in, NUTATION_ARGUMENTS,
806                                                                                    timeScales));
807         }
808 
809         /** {@inheritDoc} */
810         @Override
811         public TimeScalarFunction getMeanObliquityFunction(final TimeScales timeScales) {
812 
813             // epsilon 0 value from chapter 5, page 41, other terms from equation 32 page 45
814             final PolynomialNutation epsilonA =
815                     new PolynomialNutation(84381.448    * Constants.ARC_SECONDS_TO_RADIANS,
816                                              -46.84024  * Constants.ARC_SECONDS_TO_RADIANS,
817                                               -0.00059  * Constants.ARC_SECONDS_TO_RADIANS,
818                                                0.001813 * Constants.ARC_SECONDS_TO_RADIANS);
819 
820             return new TimeScalarFunction() {
821 
822                 /** {@inheritDoc} */
823                 @Override
824                 public double value(final AbsoluteDate date) {
825                     return epsilonA.value(evaluateTC(date, timeScales));
826                 }
827 
828                 /** {@inheritDoc} */
829                 @Override
830                 public <T extends CalculusFieldElement<T>> T value(final FieldAbsoluteDate<T> date) {
831                     return epsilonA.value(evaluateTC(date, timeScales));
832                 }
833 
834             };
835 
836         }
837 
838         /** {@inheritDoc} */
839         @Override
840         public TimeVectorFunction getXYSpXY2Function(final TimeScales timeScales) {
841 
842             // set up nutation arguments
843             final FundamentalNutationArguments arguments =
844                     getNutationArguments(timeScales);
845 
846             // set up Poisson series
847             final double microAS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-6;
848             final PoissonSeriesParser parser =
849                     new PoissonSeriesParser(17).
850                         withPolynomialPart('t', PolynomialParser.Unit.MICRO_ARC_SECONDS).
851                         withFirstDelaunay(4).
852                         withFirstPlanetary(9).
853                         withSinCos(0, 2, microAS, 3, microAS);
854 
855             final PoissonSeries xSeries = load(X_SERIES, in -> parser.parse(in, X_SERIES));
856             final PoissonSeries ySeries = load(Y_SERIES, in -> parser.parse(in, Y_SERIES));
857             final PoissonSeries sSeries = load(S_SERIES, in -> parser.parse(in, S_SERIES));
858             final PoissonSeries.CompiledSeries xys = PoissonSeries.compile(xSeries, ySeries, sSeries);
859 
860             // create a function evaluating the series
861             return new TimeVectorFunction() {
862 
863                 /** {@inheritDoc} */
864                 @Override
865                 public double[] value(final AbsoluteDate date) {
866                     return xys.value(arguments.evaluateAll(date));
867                 }
868 
869                 /** {@inheritDoc} */
870                 @Override
871                 public <T extends CalculusFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
872                     return xys.value(arguments.evaluateAll(date));
873                 }
874 
875             };
876 
877         }
878 
879 
880         /** {@inheritDoc} */
881         @Override
882         public TimeVectorFunction getPrecessionFunction(final TimeScales timeScales) {
883 
884             // set up the conventional polynomials
885             // the following values are from equation 32 in IERS 2003 conventions
886             final PolynomialNutation psiA =
887                     new PolynomialNutation(    0.0,
888                                             5038.47875   * Constants.ARC_SECONDS_TO_RADIANS,
889                                               -1.07259   * Constants.ARC_SECONDS_TO_RADIANS,
890                                               -0.001147  * Constants.ARC_SECONDS_TO_RADIANS);
891             final PolynomialNutation omegaA =
892                     new PolynomialNutation(getMeanObliquityFunction(timeScales)
893                             .value(getNutationReferenceEpoch(timeScales)),
894                                            -0.02524   * Constants.ARC_SECONDS_TO_RADIANS,
895                                             0.05127   * Constants.ARC_SECONDS_TO_RADIANS,
896                                            -0.007726  * Constants.ARC_SECONDS_TO_RADIANS);
897             final PolynomialNutation chiA =
898                     new PolynomialNutation( 0.0,
899                                            10.5526   * Constants.ARC_SECONDS_TO_RADIANS,
900                                            -2.38064  * Constants.ARC_SECONDS_TO_RADIANS,
901                                            -0.001125 * Constants.ARC_SECONDS_TO_RADIANS);
902 
903             return new PrecessionFunction(psiA, omegaA, chiA, timeScales);
904 
905         }
906 
907         /** {@inheritDoc} */
908         @Override
909         public TimeVectorFunction getNutationFunction(final TimeScales timeScales) {
910 
911             // set up nutation arguments
912             final FundamentalNutationArguments arguments =
913                     getNutationArguments(timeScales);
914 
915             // set up Poisson series
916             final double milliAS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-3;
917             final PoissonSeriesParser luniSolarParser =
918                     new PoissonSeriesParser(14).withFirstDelaunay(1);
919             final PoissonSeriesParser luniSolarPsiParser =
920                     luniSolarParser.
921                     withSinCos(0, 7, milliAS, 11, milliAS).
922                     withSinCos(1, 8, milliAS, 12, milliAS);
923             final PoissonSeries psiLuniSolarSeries =
924                             load(LUNI_SOLAR_SERIES, in -> luniSolarPsiParser.parse(in, LUNI_SOLAR_SERIES));
925             final PoissonSeriesParser luniSolarEpsilonParser = luniSolarParser.
926                                                                withSinCos(0, 13, milliAS, 9, milliAS).
927                                                                withSinCos(1, 14, milliAS, 10, milliAS);
928             final PoissonSeries epsilonLuniSolarSeries =
929                             load(LUNI_SOLAR_SERIES, in -> luniSolarEpsilonParser.parse(in, LUNI_SOLAR_SERIES));
930 
931             final PoissonSeriesParser planetaryParser =
932                     new PoissonSeriesParser(21).
933                         withFirstDelaunay(2).
934                         withFirstPlanetary(7);
935             final PoissonSeriesParser planetaryPsiParser =
936                     planetaryParser.withSinCos(0, 17, milliAS, 18, milliAS);
937             final PoissonSeries psiPlanetarySeries =
938                             load(PLANETARY_SERIES, in -> planetaryPsiParser.parse(in, PLANETARY_SERIES));
939             final PoissonSeriesParser planetaryEpsilonParser =
940                     planetaryParser.withSinCos(0, 19, milliAS, 20, milliAS);
941             final PoissonSeries epsilonPlanetarySeries =
942                             load(PLANETARY_SERIES, in -> planetaryEpsilonParser.parse(in, PLANETARY_SERIES));
943 
944             final PoissonSeries.CompiledSeries luniSolarSeries =
945                     PoissonSeries.compile(psiLuniSolarSeries, epsilonLuniSolarSeries);
946             final PoissonSeries.CompiledSeries planetarySeries =
947                     PoissonSeries.compile(psiPlanetarySeries, epsilonPlanetarySeries);
948 
949             return new TimeVectorFunction() {
950 
951                 /** {@inheritDoc} */
952                 @Override
953                 public double[] value(final AbsoluteDate date) {
954                     final BodiesElements elements = arguments.evaluateAll(date);
955                     final double[] luniSolar = luniSolarSeries.value(elements);
956                     final double[] planetary = planetarySeries.value(elements);
957                     return new double[] {
958                         luniSolar[0] + planetary[0], luniSolar[1] + planetary[1],
959                         IAU1994ResolutionC7.value(elements, timeScales.getTAI())
960                     };
961                 }
962 
963                 /** {@inheritDoc} */
964                 @Override
965                 public <T extends CalculusFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
966                     final FieldBodiesElements<T> elements = arguments.evaluateAll(date);
967                     final T[] luniSolar = luniSolarSeries.value(elements);
968                     final T[] planetary = planetarySeries.value(elements);
969                     final T[] result = MathArrays.buildArray(date.getField(), 3);
970                     result[0] = luniSolar[0].add(planetary[0]);
971                     result[1] = luniSolar[1].add(planetary[1]);
972                     result[2] = IAU1994ResolutionC7.value(elements, timeScales.getTAI());
973                     return result;
974                 }
975 
976             };
977 
978         }
979 
980         /** {@inheritDoc} */
981         @Override
982         public TimeScalarFunction getGMSTFunction(final TimeScale ut1,
983                                                   final TimeScales timeScales) {
984 
985             // Earth Rotation Angle
986             final StellarAngleCapitaine era =
987                     new StellarAngleCapitaine(ut1, timeScales.getTAI());
988 
989             // Polynomial part of the apparent sidereal time series
990             // which is the opposite of Equation of Origins (EO)
991             final double microAS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-6;
992             final PoissonSeriesParser parser =
993                     new PoissonSeriesParser(17).
994                         withFirstDelaunay(4).
995                         withFirstPlanetary(9).
996                         withSinCos(0, 2, microAS, 3, microAS).
997                         withPolynomialPart('t', Unit.ARC_SECONDS);
998             final PolynomialNutation minusEO = load(GST_SERIES, in -> parser.parse(in, GST_SERIES).getPolynomial());
999 
1000             // create a function evaluating the series
1001             return new TimeScalarFunction() {
1002 
1003                 /** {@inheritDoc} */
1004                 @Override
1005                 public double value(final AbsoluteDate date) {
1006                     return era.value(date) + minusEO.value(evaluateTC(date, timeScales));
1007                 }
1008 
1009                 /** {@inheritDoc} */
1010                 @Override
1011                 public <T extends CalculusFieldElement<T>> T value(final FieldAbsoluteDate<T> date) {
1012                     return era.value(date).add(minusEO.value(evaluateTC(date, timeScales)));
1013                 }
1014 
1015             };
1016 
1017         }
1018 
1019         /** {@inheritDoc} */
1020         @Override
1021         public TimeScalarFunction getGMSTRateFunction(final TimeScale ut1,
1022                                                       final TimeScales timeScales) {
1023 
1024             // Earth Rotation Angle
1025             final StellarAngleCapitaine era =
1026                     new StellarAngleCapitaine(ut1, timeScales.getTAI());
1027 
1028             // Polynomial part of the apparent sidereal time series
1029             // which is the opposite of Equation of Origins (EO)
1030             final double microAS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-6;
1031             final PoissonSeriesParser parser =
1032                     new PoissonSeriesParser(17).
1033                         withFirstDelaunay(4).
1034                         withFirstPlanetary(9).
1035                         withSinCos(0, 2, microAS, 3, microAS).
1036                         withPolynomialPart('t', Unit.ARC_SECONDS);
1037             final PolynomialNutation minusEO = load(GST_SERIES, in -> parser.parse(in, GST_SERIES).getPolynomial());
1038 
1039             // create a function evaluating the series
1040             return new TimeScalarFunction() {
1041 
1042                 /** {@inheritDoc} */
1043                 @Override
1044                 public double value(final AbsoluteDate date) {
1045                     return era.getRate() + minusEO.derivative(evaluateTC(date, timeScales));
1046                 }
1047 
1048                 /** {@inheritDoc} */
1049                 @Override
1050                 public <T extends CalculusFieldElement<T>> T value(final FieldAbsoluteDate<T> date) {
1051                     return minusEO.derivative(evaluateTC(date, timeScales)).add(era.getRate());
1052                 }
1053 
1054             };
1055 
1056         }
1057 
1058         /** {@inheritDoc} */
1059         @Override
1060         public TimeScalarFunction getGASTFunction(final TimeScale ut1,
1061                                                   final EOPHistory eopHistory,
1062                                                   final TimeScales timeScales) {
1063 
1064             // set up nutation arguments
1065             final FundamentalNutationArguments arguments =
1066                     getNutationArguments(timeScales);
1067 
1068             // mean obliquity function
1069             final TimeScalarFunction epsilon = getMeanObliquityFunction(timeScales);
1070 
1071             // set up Poisson series
1072             final double milliAS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-3;
1073             final PoissonSeriesParser luniSolarPsiParser =
1074                     new PoissonSeriesParser(14).
1075                         withFirstDelaunay(1).
1076                         withSinCos(0, 7, milliAS, 11, milliAS).
1077                         withSinCos(1, 8, milliAS, 12, milliAS);
1078             final PoissonSeries psiLuniSolarSeries =
1079                             load(LUNI_SOLAR_SERIES, in -> luniSolarPsiParser.parse(in, LUNI_SOLAR_SERIES));
1080 
1081             final PoissonSeriesParser planetaryPsiParser =
1082                     new PoissonSeriesParser(21).
1083                         withFirstDelaunay(2).
1084                         withFirstPlanetary(7).
1085                         withSinCos(0, 17, milliAS, 18, milliAS);
1086             final PoissonSeries psiPlanetarySeries =
1087                             load(PLANETARY_SERIES, in -> planetaryPsiParser.parse(in, PLANETARY_SERIES));
1088 
1089 
1090             final double microAS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-6;
1091             final PoissonSeriesParser gstParser =
1092                     new PoissonSeriesParser(17).
1093                         withFirstDelaunay(4).
1094                         withFirstPlanetary(9).
1095                         withSinCos(0, 2, microAS, 3, microAS).
1096                         withPolynomialPart('t', Unit.ARC_SECONDS);
1097             final PoissonSeries gstSeries = load(GST_SERIES, in -> gstParser.parse(in, GST_SERIES));
1098             final PoissonSeries.CompiledSeries psiGstSeries =
1099                     PoissonSeries.compile(psiLuniSolarSeries, psiPlanetarySeries, gstSeries);
1100 
1101             // ERA function
1102             final TimeScalarFunction era =
1103                     getEarthOrientationAngleFunction(ut1, timeScales.getTAI());
1104 
1105             return new TimeScalarFunction() {
1106 
1107                 /** {@inheritDoc} */
1108                 @Override
1109                 public double value(final AbsoluteDate date) {
1110 
1111                     // evaluate equation of origins
1112                     final BodiesElements elements = arguments.evaluateAll(date);
1113                     final double[] angles = psiGstSeries.value(elements);
1114                     final double ddPsi    = (eopHistory == null) ? 0 : eopHistory.getEquinoxNutationCorrection(date)[0];
1115                     final double deltaPsi = angles[0] + angles[1] + ddPsi;
1116                     final double epsilonA = epsilon.value(date);
1117 
1118                     // subtract equation of origin from EA
1119                     // (hence add the series above which have the sign included)
1120                     return era.value(date) + deltaPsi * FastMath.cos(epsilonA) + angles[2];
1121 
1122                 }
1123 
1124                 /** {@inheritDoc} */
1125                 @Override
1126                 public <T extends CalculusFieldElement<T>> T value(final FieldAbsoluteDate<T> date) {
1127 
1128                     // evaluate equation of origins
1129                     final FieldBodiesElements<T> elements = arguments.evaluateAll(date);
1130                     final T[] angles = psiGstSeries.value(elements);
1131                     final T ddPsi    = (eopHistory == null) ? date.getField().getZero() : eopHistory.getEquinoxNutationCorrection(date)[0];
1132                     final T deltaPsi = angles[0].add(angles[1]).add(ddPsi);
1133                     final T epsilonA = epsilon.value(date);
1134 
1135                     // subtract equation of origin from EA
1136                     // (hence add the series above which have the sign included)
1137                     return era.value(date).add(deltaPsi.multiply(epsilonA.cos())).add(angles[2]);
1138 
1139                 }
1140 
1141             };
1142 
1143         }
1144 
1145         /** {@inheritDoc} */
1146         @Override
1147         public TimeVectorFunction getEOPTidalCorrection(final TimeScales timeScales) {
1148 
1149             // set up nutation arguments
1150             // BEWARE! Using TT as the time scale here and not UT1 is intentional!
1151             // as this correction is used to compute UT1 itself, it is not surprising we cannot use UT1 yet,
1152             // however, using the close UTC as would seem logical make the comparison with interp.f from IERS fail
1153             // looking in the interp.f code, the same TT scale is used for both Delaunay and gamma argument
1154             final FundamentalNutationArguments arguments =
1155                     getNutationArguments(timeScales.getTT(), timeScales);
1156 
1157             // set up Poisson series
1158             final double microAS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-6;
1159             final PoissonSeriesParser xyParser = new PoissonSeriesParser(13).
1160                     withOptionalColumn(1).
1161                     withGamma(2).
1162                     withFirstDelaunay(3);
1163             final double microS = 1.0e-6;
1164             final PoissonSeriesParser ut1Parser = new PoissonSeriesParser(11).
1165                             withOptionalColumn(1).
1166                             withGamma(2).
1167                             withFirstDelaunay(3);
1168             final PoissonSeries xSeries =
1169                             load(TIDAL_CORRECTION_XP_YP_SERIES, in -> xyParser.
1170                                                                       withSinCos(0, 10, microAS, 11, microAS).
1171                                                                       parse(in, TIDAL_CORRECTION_XP_YP_SERIES));
1172             final PoissonSeries ySeries =
1173                             load(TIDAL_CORRECTION_XP_YP_SERIES, in -> xyParser.
1174                                                                       withSinCos(0, 12, microAS, 13, microAS).
1175                                                                       parse(in, TIDAL_CORRECTION_XP_YP_SERIES));
1176 
1177             final PoissonSeries ut1Series =
1178                             load(TIDAL_CORRECTION_UT1_SERIES, in -> ut1Parser.
1179                                                                     withSinCos(0, 10, microS, 11, microS).
1180                                                                     parse(in, TIDAL_CORRECTION_UT1_SERIES));
1181 
1182             return new EOPTidalCorrection(arguments, xSeries, ySeries, ut1Series);
1183 
1184         }
1185 
1186         /** {@inheritDoc} */
1187         public LoveNumbers getLoveNumbers() {
1188             return loadLoveNumbers(LOVE_NUMBERS);
1189         }
1190 
1191         /** {@inheritDoc} */
1192         public TimeVectorFunction getTideFrequencyDependenceFunction(final TimeScale ut1,
1193                                                                      final TimeScales timeScales) {
1194 
1195             // set up nutation arguments
1196             final FundamentalNutationArguments arguments = getNutationArguments(ut1, timeScales);
1197 
1198             // set up Poisson series
1199             final PoissonSeriesParser k20Parser =
1200                     new PoissonSeriesParser(18).
1201                         withOptionalColumn(1).
1202                         withDoodson(4, 2).
1203                         withFirstDelaunay(10);
1204             final PoissonSeriesParser k21Parser =
1205                     new PoissonSeriesParser(18).
1206                         withOptionalColumn(1).
1207                         withDoodson(4, 3).
1208                         withFirstDelaunay(10);
1209             final PoissonSeriesParser k22Parser =
1210                     new PoissonSeriesParser(16).
1211                         withOptionalColumn(1).
1212                         withDoodson(4, 2).
1213                         withFirstDelaunay(10);
1214 
1215             final double pico = 1.0e-12;
1216             final PoissonSeries c20Series =
1217                             load(K20_FREQUENCY_DEPENDENCE, in -> k20Parser.
1218                                                                  withSinCos(0, 18, -pico, 16, pico).
1219                                                                  parse(in, K20_FREQUENCY_DEPENDENCE));
1220             final PoissonSeries c21Series =
1221                             load(K21_FREQUENCY_DEPENDENCE, in -> k21Parser.
1222                                                                  withSinCos(0, 17, pico, 18, pico).
1223                                                                  parse(in, K21_FREQUENCY_DEPENDENCE));
1224             final PoissonSeries s21Series =
1225                             load(K21_FREQUENCY_DEPENDENCE, in -> k21Parser.
1226                                                                  withSinCos(0, 18, -pico, 17, pico).
1227                                                                  parse(in, K21_FREQUENCY_DEPENDENCE));
1228             final PoissonSeries c22Series =
1229                             load(K22_FREQUENCY_DEPENDENCE, in -> k22Parser.
1230                                                                  withSinCos(0, -1, pico, 16, pico).
1231                                                                  parse(in, K22_FREQUENCY_DEPENDENCE));
1232             final PoissonSeries s22Series =
1233                             load(K22_FREQUENCY_DEPENDENCE, in -> k22Parser.
1234                                                                  withSinCos(0, 16, -pico, -1, pico).
1235                                                                  parse(in, K22_FREQUENCY_DEPENDENCE));
1236 
1237             return new TideFrequencyDependenceFunction(arguments,
1238                                                        c20Series,
1239                                                        c21Series, s21Series,
1240                                                        c22Series, s22Series);
1241 
1242         }
1243 
1244         /** {@inheritDoc} */
1245         @Override
1246         public double getPermanentTide() {
1247             return 4.4228e-8 * -0.31460 * getLoveNumbers().getReal(2, 0);
1248         }
1249 
1250         /** {@inheritDoc} */
1251         @Override
1252         public TimeVectorFunction getSolidPoleTide(final EOPHistory eopHistory) {
1253 
1254             // annual pole from ftp://tai.bipm.org/iers/conv2003/chapter7/annual.pole
1255             final TimeScale utc = eopHistory.getTimeScales().getUTC();
1256             final SimpleTimeStampedTableParser.RowConverter<MeanPole> converter =
1257                 new SimpleTimeStampedTableParser.RowConverter<MeanPole>() {
1258                     /** {@inheritDoc} */
1259                     @Override
1260                     public MeanPole convert(final double[] rawFields) {
1261                         return new MeanPole(new AbsoluteDate((int) rawFields[0], 1, 1, utc),
1262                                             rawFields[1] * Constants.ARC_SECONDS_TO_RADIANS,
1263                                             rawFields[2] * Constants.ARC_SECONDS_TO_RADIANS);
1264                     }
1265                 };
1266             final SimpleTimeStampedTableParser<MeanPole> parser =
1267                     new SimpleTimeStampedTableParser<>(3, converter);
1268             final List<MeanPole> annualPoleList = load(ANNUAL_POLE, in -> parser.parse(in, ANNUAL_POLE));
1269             final AbsoluteDate firstAnnualPoleDate = annualPoleList.getFirst().getDate();
1270             final AbsoluteDate lastAnnualPoleDate  = annualPoleList.getLast().getDate();
1271             final ImmutableTimeStampedCache<MeanPole> annualCache =
1272                     new ImmutableTimeStampedCache<>(2, annualPoleList);
1273 
1274             // polynomial extension from IERS 2003, section 7.1.4, equations 23a and 23b
1275             final double xp0    = 0.054   * Constants.ARC_SECONDS_TO_RADIANS;
1276             final double xp0Dot = 0.00083 * Constants.ARC_SECONDS_TO_RADIANS / Constants.JULIAN_YEAR;
1277             final double yp0    = 0.357   * Constants.ARC_SECONDS_TO_RADIANS;
1278             final double yp0Dot = 0.00395 * Constants.ARC_SECONDS_TO_RADIANS / Constants.JULIAN_YEAR;
1279 
1280             // constants from IERS 2003, section 6.2
1281             final double globalFactor = -1.333e-9 / Constants.ARC_SECONDS_TO_RADIANS;
1282             final double ratio        =  0.0115;
1283 
1284             return new TimeVectorFunction() {
1285 
1286                 /** {@inheritDoc} */
1287                 @Override
1288                 public double[] value(final AbsoluteDate date) {
1289 
1290                     // we can't compute anything before the range covered by the annual pole file
1291                     if (date.compareTo(firstAnnualPoleDate) <= 0) {
1292                         return new double[] {
1293                             0.0, 0.0
1294                         };
1295                     }
1296 
1297                     // evaluate mean pole
1298                     double meanPoleX = 0;
1299                     double meanPoleY = 0;
1300                     if (date.compareTo(lastAnnualPoleDate) <= 0) {
1301                         // we are within the range covered by the annual pole file,
1302                         // we interpolate within it
1303                         try {
1304                             final HermiteInterpolator interpolator = new HermiteInterpolator();
1305                             annualCache.getNeighbors(date).forEach(neighbor ->
1306                                 interpolator.addSamplePoint(neighbor.getDate().durationFrom(date),
1307                                                             new double[] {
1308                                                                 neighbor.getX(), neighbor.getY()
1309                                                             }));
1310                             final double[] interpolated = interpolator.value(0);
1311                             meanPoleX = interpolated[0];
1312                             meanPoleY = interpolated[1];
1313                         } catch (TimeStampedCacheException tsce) {
1314                             // this should never happen
1315                             throw new OrekitInternalError(tsce);
1316                         }
1317                     } else {
1318 
1319                         // we are after the range covered by the annual pole file,
1320                         // we use the polynomial extension
1321                         final double t = date.durationFrom(
1322                                 eopHistory.getTimeScales().getJ2000Epoch());
1323                         meanPoleX = xp0 + t * xp0Dot;
1324                         meanPoleY = yp0 + t * yp0Dot;
1325 
1326                     }
1327 
1328                     // evaluate wobble variables
1329                     final PoleCorrection correction = eopHistory.getPoleCorrection(date);
1330                     final double m1 = correction.getXp() - meanPoleX;
1331                     final double m2 = meanPoleY - correction.getYp();
1332 
1333                     return new double[] {
1334                         // the following correspond to the equations published in IERS 2003 conventions,
1335                         // section 6.2 page 65. In the publication, the equations read:
1336                         // ∆C₂₁ = −1.333 × 10⁻⁹ (m₁ − 0.0115m₂)
1337                         // ∆S₂₁ = −1.333 × 10⁻⁹ (m₂ + 0.0115m₁)
1338                         // However, it seems there are sign errors in these equations, which have
1339                         // been fixed in IERS 2010 conventions, section 6.4 page 94. In these newer
1340                         // publication, the equations read:
1341                         // ∆C₂₁ = −1.333 × 10⁻⁹ (m₁ + 0.0115m₂)
1342                         // ∆S₂₁ = −1.333 × 10⁻⁹ (m₂ − 0.0115m₁)
1343                         // the newer equations seem more consistent with the premises as the
1344                         // deformation due to the centrifugal potential has the form:
1345                         // −Ω²r²/2 sin 2θ Re [k₂(m₁ − im₂) exp(iλ)] where k₂ is the complex
1346                         // number 0.3077 + 0.0036i, so the real part in the previous equation is:
1347                         // A[Re(k₂) m₁ + Im(k₂) m₂)] cos λ + A[Re(k₂) m₂ - Im(k₂) m₁] sin λ
1348                         // identifying this with ∆C₂₁ cos λ + ∆S₂₁ sin λ we get:
1349                         // ∆C₂₁ = A Re(k₂) [m₁ + Im(k₂)/Re(k₂) m₂)]
1350                         // ∆S₂₁ = A Re(k₂) [m₂ - Im(k₂)/Re(k₂) m₁)]
1351                         // and Im(k₂)/Re(k₂) is very close to +0.0115
1352                         // As the equation as written in the IERS 2003 conventions are used in
1353                         // legacy systems, we have reproduced this alleged error here (and fixed it in
1354                         // the IERS 2010 conventions below) for validation purposes. We don't recommend
1355                         // using the IERS 2003 conventions for solid pole tide computation other than
1356                         // for validation or reproducibility of legacy applications behavior.
1357                         // As solid pole tide is small and as the sign change is on the smallest coefficient,
1358                         // the effect is quite small. A test case on a propagated orbit showed a position change
1359                         // slightly below 0.4m after a 30 days propagation on a Low Earth Orbit
1360                         globalFactor * (m1 - ratio * m2),
1361                         globalFactor * (m2 + ratio * m1),
1362                     };
1363 
1364                 }
1365 
1366                 /** {@inheritDoc} */
1367                 @Override
1368                 public <T extends CalculusFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
1369 
1370                     final AbsoluteDate aDate = date.toAbsoluteDate();
1371 
1372                     // we can't compute anything before the range covered by the annual pole file
1373                     if (aDate.compareTo(firstAnnualPoleDate) <= 0) {
1374                         return MathArrays.buildArray(date.getField(), 2);
1375                     }
1376 
1377                     // evaluate mean pole
1378                     T meanPoleX = date.getField().getZero();
1379                     T meanPoleY = date.getField().getZero();
1380                     if (aDate.compareTo(lastAnnualPoleDate) <= 0) {
1381                         // we are within the range covered by the annual pole file,
1382                         // we interpolate within it
1383                         try {
1384                             final FieldHermiteInterpolator<T> interpolator = new FieldHermiteInterpolator<>();
1385                             final T[] y = MathArrays.buildArray(date.getField(), 2);
1386                             final T zero = date.getField().getZero();
1387                             final FieldAbsoluteDate<T> central = new FieldAbsoluteDate<>(aDate, zero); // here, we attempt to get a constant date,
1388                                                                                                        // for example removing derivatives
1389                                                                                                        // if T was DerivativeStructure
1390                             annualCache.getNeighbors(aDate).forEach(neighbor -> {
1391                                 y[0] = zero.newInstance(neighbor.getX());
1392                                 y[1] = zero.newInstance(neighbor.getY());
1393                                 interpolator.addSamplePoint(central.durationFrom(neighbor.getDate()).negate(), y);
1394                             });
1395                             final T[] interpolated = interpolator.value(date.durationFrom(central)); // here, we introduce derivatives again (in DerivativeStructure case)
1396                             meanPoleX = interpolated[0];
1397                             meanPoleY = interpolated[1];
1398                         } catch (TimeStampedCacheException tsce) {
1399                             // this should never happen
1400                             throw new OrekitInternalError(tsce);
1401                         }
1402                     } else {
1403 
1404                         // we are after the range covered by the annual pole file,
1405                         // we use the polynomial extension
1406                         final T t = date.durationFrom(
1407                                 eopHistory.getTimeScales().getJ2000Epoch());
1408                         meanPoleX = t.multiply(xp0Dot).add(xp0);
1409                         meanPoleY = t.multiply(yp0Dot).add(yp0);
1410 
1411                     }
1412 
1413                     // evaluate wobble variables
1414                     final FieldPoleCorrection<T> correction = eopHistory.getPoleCorrection(date);
1415                     final T m1 = correction.getXp().subtract(meanPoleX);
1416                     final T m2 = meanPoleY.subtract(correction.getYp());
1417 
1418                     final T[] a = MathArrays.buildArray(date.getField(), 2);
1419 
1420                     // the following correspond to the equations published in IERS 2003 conventions,
1421                     // section 6.2 page 65. In the publication, the equations read:
1422                     // ∆C₂₁ = −1.333 × 10⁻⁹ (m₁ − 0.0115m₂)
1423                     // ∆S₂₁ = −1.333 × 10⁻⁹ (m₂ + 0.0115m₁)
1424                     // However, it seems there are sign errors in these equations, which have
1425                     // been fixed in IERS 2010 conventions, section 6.4 page 94. In these newer
1426                     // publication, the equations read:
1427                     // ∆C₂₁ = −1.333 × 10⁻⁹ (m₁ + 0.0115m₂)
1428                     // ∆S₂₁ = −1.333 × 10⁻⁹ (m₂ − 0.0115m₁)
1429                     // the newer equations seem more consistent with the premises as the
1430                     // deformation due to the centrifugal potential has the form:
1431                     // −Ω²r²/2 sin 2θ Re [k₂(m₁ − im₂) exp(iλ)] where k₂ is the complex
1432                     // number 0.3077 + 0.0036i, so the real part in the previous equation is:
1433                     // A[Re(k₂) m₁ + Im(k₂) m₂)] cos λ + A[Re(k₂) m₂ - Im(k₂) m₁] sin λ
1434                     // identifying this with ∆C₂₁ cos λ + ∆S₂₁ sin λ we get:
1435                     // ∆C₂₁ = A Re(k₂) [m₁ + Im(k₂)/Re(k₂) m₂)]
1436                     // ∆S₂₁ = A Re(k₂) [m₂ - Im(k₂)/Re(k₂) m₁)]
1437                     // and Im(k₂)/Re(k₂) is very close to +0.0115
1438                     // As the equation as written in the IERS 2003 conventions are used in
1439                     // legacy systems, we have reproduced this alleged error here (and fixed it in
1440                     // the IERS 2010 conventions below) for validation purposes. We don't recommend
1441                     // using the IERS 2003 conventions for solid pole tide computation other than
1442                     // for validation or reproducibility of legacy applications behavior.
1443                     // As solid pole tide is small and as the sign change is on the smallest coefficient,
1444                     // the effect is quite small. A test case on a propagated orbit showed a position change
1445                     // slightly below 0.4m after a 30 days propagation on a Low Earth Orbit
1446                     a[0] = m1.add(m2.multiply(-ratio)).multiply(globalFactor);
1447                     a[1] = m2.add(m1.multiply( ratio)).multiply(globalFactor);
1448 
1449                     return a;
1450 
1451                 }
1452 
1453             };
1454 
1455         }
1456 
1457         /** {@inheritDoc} */
1458         @Override
1459         public TimeVectorFunction getOceanPoleTide(final EOPHistory eopHistory) {
1460 
1461             return new TimeVectorFunction() {
1462 
1463                 /** {@inheritDoc} */
1464                 @Override
1465                 public double[] value(final AbsoluteDate date) {
1466                     // there are no model for ocean pole tide prior to conventions 2010
1467                     return new double[] {
1468                         0.0, 0.0
1469                     };
1470                 }
1471 
1472                 /** {@inheritDoc} */
1473                 @Override
1474                 public <T extends CalculusFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
1475                     // there are no model for ocean pole tide prior to conventions 2010
1476                     return MathArrays.buildArray(date.getField(), 2);
1477                 }
1478 
1479             };
1480         }
1481 
1482         /** {@inheritDoc} */
1483         @Override
1484         public double[] getNominalTidalDisplacement() {
1485             return new double[] {
1486                 // h⁽⁰⁾,                                  h⁽²⁾,   h₃,     hI diurnal, hI semi-diurnal,
1487                 0.6078,                                  -0.0006, 0.292, -0.0025,    -0.0022,
1488                 // l⁽⁰⁾, l⁽¹⁾ diurnal, l⁽¹⁾ semi-diurnal, l⁽²⁾,   l₃,     lI diurnal, lI semi-diurnal
1489                 0.0847,  0.0012,       0.0024,            0.0002, 0.015, -0.0007,    -0.0007,
1490                 // H₀
1491                 -0.31460
1492             };
1493         }
1494 
1495         /** {@inheritDoc} */
1496         @Override
1497         public CompiledSeries getTidalDisplacementFrequencyCorrectionDiurnal() {
1498             return getTidalDisplacementFrequencyCorrectionDiurnal(TIDAL_DISPLACEMENT_CORRECTION_DIURNAL,
1499                                                                   18, 15, 16, 17, 18);
1500         }
1501 
1502         /** {@inheritDoc} */
1503         @Override
1504         public CompiledSeries getTidalDisplacementFrequencyCorrectionZonal() {
1505             return getTidalDisplacementFrequencyCorrectionZonal(TIDAL_DISPLACEMENT_CORRECTION_ZONAL,
1506                                                                 18, 15, 16, 17, 18);
1507         }
1508 
1509     },
1510 
1511     /** Constant for IERS 2010 conventions. */
1512     IERS_2010 {
1513 
1514         /** Nutation arguments resources. */
1515         private static final String NUTATION_ARGUMENTS = IERS_BASE + "2010/nutation-arguments.txt";
1516 
1517         /** X series resources. */
1518         private static final String X_SERIES           = IERS_BASE + "2010/tab5.2a.txt";
1519 
1520         /** Y series resources. */
1521         private static final String Y_SERIES           = IERS_BASE + "2010/tab5.2b.txt";
1522 
1523         /** S series resources. */
1524         private static final String S_SERIES           = IERS_BASE + "2010/tab5.2d.txt";
1525 
1526         /** Psi series resources. */
1527         private static final String PSI_SERIES         = IERS_BASE + "2010/tab5.3a.txt";
1528 
1529         /** Epsilon series resources. */
1530         private static final String EPSILON_SERIES     = IERS_BASE + "2010/tab5.3b.txt";
1531 
1532         /** Greenwhich sidereal time series resources. */
1533         private static final String GST_SERIES         = IERS_BASE + "2010/tab5.2e.txt";
1534 
1535         /** Tidal correction for xp, yp series resources. */
1536         private static final String TIDAL_CORRECTION_XP_YP_SERIES = IERS_BASE + "2010/tab8.2ab.txt";
1537 
1538         /** Tidal correction for UT1 resources. */
1539         private static final String TIDAL_CORRECTION_UT1_SERIES = IERS_BASE + "2010/tab8.3ab.txt";
1540 
1541         /** Love numbers resources. */
1542         private static final String LOVE_NUMBERS = IERS_BASE + "2010/tab6.3.txt";
1543 
1544         /** Frequency dependence model for k₂₀. */
1545         private static final String K20_FREQUENCY_DEPENDENCE = IERS_BASE + "2010/tab6.5b.txt";
1546 
1547         /** Frequency dependence model for k₂₁. */
1548         private static final String K21_FREQUENCY_DEPENDENCE = IERS_BASE + "2010/tab6.5a.txt";
1549 
1550         /** Frequency dependence model for k₂₂. */
1551         private static final String K22_FREQUENCY_DEPENDENCE = IERS_BASE + "2010/tab6.5c.txt";
1552 
1553         /** Tidal displacement frequency correction for diurnal tides. */
1554         private static final String TIDAL_DISPLACEMENT_CORRECTION_DIURNAL = IERS_BASE + "2010/tab7.3a.txt";
1555 
1556         /** Tidal displacement frequency correction for zonal tides. */
1557         private static final String TIDAL_DISPLACEMENT_CORRECTION_ZONAL = IERS_BASE + "2010/tab7.3b.txt";
1558 
1559         /** {@inheritDoc} */
1560         public FundamentalNutationArguments getNutationArguments(final TimeScale timeScale,
1561                                                                  final TimeScales timeScales) {
1562             return load(NUTATION_ARGUMENTS, in -> new FundamentalNutationArguments(this, timeScale,
1563                                                                                             in, NUTATION_ARGUMENTS,
1564                                                                                             timeScales));
1565         }
1566 
1567         /** {@inheritDoc} */
1568         @Override
1569         public TimeScalarFunction getMeanObliquityFunction(final TimeScales timeScales) {
1570 
1571             // epsilon 0 value from chapter 5, page 56, other terms from equation 5.40 page 65
1572             final PolynomialNutation epsilonA =
1573                     new PolynomialNutation(84381.406        * Constants.ARC_SECONDS_TO_RADIANS,
1574                                              -46.836769     * Constants.ARC_SECONDS_TO_RADIANS,
1575                                               -0.0001831    * Constants.ARC_SECONDS_TO_RADIANS,
1576                                                0.00200340   * Constants.ARC_SECONDS_TO_RADIANS,
1577                                               -0.000000576  * Constants.ARC_SECONDS_TO_RADIANS,
1578                                               -0.0000000434 * Constants.ARC_SECONDS_TO_RADIANS);
1579 
1580             return new TimeScalarFunction() {
1581 
1582                 /** {@inheritDoc} */
1583                 @Override
1584                 public double value(final AbsoluteDate date) {
1585                     return epsilonA.value(evaluateTC(date, timeScales));
1586                 }
1587 
1588                 /** {@inheritDoc} */
1589                 @Override
1590                 public <T extends CalculusFieldElement<T>> T value(final FieldAbsoluteDate<T> date) {
1591                     return epsilonA.value(evaluateTC(date, timeScales));
1592                 }
1593 
1594             };
1595 
1596         }
1597 
1598         /** {@inheritDoc} */
1599         @Override
1600         public TimeVectorFunction getXYSpXY2Function(final TimeScales timeScales) {
1601 
1602             // set up nutation arguments
1603             final FundamentalNutationArguments arguments =
1604                     getNutationArguments(timeScales);
1605 
1606             // set up Poisson series
1607             final double microAS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-6;
1608             final PoissonSeriesParser parser =
1609                     new PoissonSeriesParser(17).
1610                         withPolynomialPart('t', PolynomialParser.Unit.MICRO_ARC_SECONDS).
1611                         withFirstDelaunay(4).
1612                         withFirstPlanetary(9).
1613                         withSinCos(0, 2, microAS, 3, microAS);
1614             final PoissonSeries                xSeries = load(X_SERIES, in -> parser.parse(in, X_SERIES));
1615             final PoissonSeries                ySeries = load(Y_SERIES, in -> parser.parse(in, Y_SERIES));
1616             final PoissonSeries                sSeries = load(S_SERIES, in -> parser.parse(in, S_SERIES));
1617             final PoissonSeries.CompiledSeries xys     = PoissonSeries.compile(xSeries, ySeries, sSeries);
1618 
1619             // create a function evaluating the series
1620             return new TimeVectorFunction() {
1621 
1622                 /** {@inheritDoc} */
1623                 @Override
1624                 public double[] value(final AbsoluteDate date) {
1625                     return xys.value(arguments.evaluateAll(date));
1626                 }
1627 
1628                 /** {@inheritDoc} */
1629                 @Override
1630                 public <T extends CalculusFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
1631                     return xys.value(arguments.evaluateAll(date));
1632                 }
1633 
1634             };
1635 
1636         }
1637 
1638         /** {@inheritDoc} */
1639         public LoveNumbers getLoveNumbers() {
1640             return loadLoveNumbers(LOVE_NUMBERS);
1641         }
1642 
1643         /** {@inheritDoc} */
1644         public TimeVectorFunction getTideFrequencyDependenceFunction(final TimeScale ut1,
1645                                                                      final TimeScales timeScales) {
1646 
1647             // set up nutation arguments
1648             final FundamentalNutationArguments arguments = getNutationArguments(ut1, timeScales);
1649 
1650             // set up Poisson series
1651             final PoissonSeriesParser k20Parser =
1652                     new PoissonSeriesParser(18).
1653                         withOptionalColumn(1).
1654                         withDoodson(4, 2).
1655                         withFirstDelaunay(10);
1656             final PoissonSeriesParser k21Parser =
1657                     new PoissonSeriesParser(18).
1658                         withOptionalColumn(1).
1659                         withDoodson(4, 3).
1660                         withFirstDelaunay(10);
1661             final PoissonSeriesParser k22Parser =
1662                     new PoissonSeriesParser(16).
1663                         withOptionalColumn(1).
1664                         withDoodson(4, 2).
1665                         withFirstDelaunay(10);
1666 
1667             final double pico = 1.0e-12;
1668             final PoissonSeries c20Series = load(K20_FREQUENCY_DEPENDENCE, in -> k20Parser.
1669                                                                                  withSinCos(0, 18, -pico, 16, pico).
1670                                                                                  parse(in, K20_FREQUENCY_DEPENDENCE));
1671             final PoissonSeries c21Series = load(K21_FREQUENCY_DEPENDENCE, in -> k21Parser.
1672                                                                                  withSinCos(0, 17, pico, 18, pico).
1673                                                                                  parse(in, K21_FREQUENCY_DEPENDENCE));
1674             final PoissonSeries s21Series = load(K21_FREQUENCY_DEPENDENCE, in -> k21Parser.
1675                                                                                  withSinCos(0, 18, -pico, 17, pico).
1676                                                                                  parse(in, K21_FREQUENCY_DEPENDENCE));
1677             final PoissonSeries c22Series = load(K22_FREQUENCY_DEPENDENCE, in -> k22Parser.
1678                                                                                  withSinCos(0, -1, pico, 16, pico).
1679                                                                                  parse(in, K22_FREQUENCY_DEPENDENCE));
1680             final PoissonSeries s22Series = load(K22_FREQUENCY_DEPENDENCE, in -> k22Parser.
1681                                                                                  withSinCos(0, 16, -pico, -1, pico).
1682                                                                                  parse(in, K22_FREQUENCY_DEPENDENCE));
1683 
1684             return new TideFrequencyDependenceFunction(arguments,
1685                                                        c20Series,
1686                                                        c21Series, s21Series,
1687                                                        c22Series, s22Series);
1688 
1689         }
1690 
1691         /** {@inheritDoc} */
1692         @Override
1693         public double getPermanentTide() {
1694             return 4.4228e-8 * -0.31460 * getLoveNumbers().getReal(2, 0);
1695         }
1696 
1697         /** Compute pole wobble variables m₁ and m₂.
1698          * @param date current date
1699          * @param eopHistory EOP history
1700          * @return array containing m₁ and m₂
1701          */
1702         private double[] computePoleWobble(final AbsoluteDate date, final EOPHistory eopHistory) {
1703 
1704             // polynomial model from IERS 2010, table 7.7
1705             final double f0 = Constants.ARC_SECONDS_TO_RADIANS / 1000.0;
1706             final double f1 = f0 / Constants.JULIAN_YEAR;
1707             final double f2 = f1 / Constants.JULIAN_YEAR;
1708             final double f3 = f2 / Constants.JULIAN_YEAR;
1709             final AbsoluteDate changeDate =
1710                     new AbsoluteDate(2010, 1, 1, eopHistory.getTimeScales().getTT());
1711 
1712             // evaluate mean pole
1713             final double[] xPolynomial;
1714             final double[] yPolynomial;
1715             if (date.compareTo(changeDate) <= 0) {
1716                 xPolynomial = new double[] {
1717                     55.974 * f0, 1.8243 * f1, 0.18413 * f2, 0.007024 * f3
1718                 };
1719                 yPolynomial = new double[] {
1720                     346.346 * f0, 1.7896 * f1, -0.10729 * f2, -0.000908 * f3
1721                 };
1722             } else {
1723                 xPolynomial = new double[] {
1724                     23.513 * f0, 7.6141 * f1
1725                 };
1726                 yPolynomial = new double[] {
1727                     358.891 * f0,  -0.6287 * f1
1728                 };
1729             }
1730             double meanPoleX = 0;
1731             double meanPoleY = 0;
1732             final double t = date.durationFrom(
1733                     eopHistory.getTimeScales().getJ2000Epoch());
1734             for (int i = xPolynomial.length - 1; i >= 0; --i) {
1735                 meanPoleX = meanPoleX * t + xPolynomial[i];
1736             }
1737             for (int i = yPolynomial.length - 1; i >= 0; --i) {
1738                 meanPoleY = meanPoleY * t + yPolynomial[i];
1739             }
1740 
1741             // evaluate wobble variables
1742             final PoleCorrection correction = eopHistory.getPoleCorrection(date);
1743             final double m1 = correction.getXp() - meanPoleX;
1744             final double m2 = meanPoleY - correction.getYp();
1745 
1746             return new double[] {
1747                 m1, m2
1748             };
1749 
1750         }
1751 
1752         /** Compute pole wobble variables m₁ and m₂.
1753          * @param date current date
1754          * @param <T> type of the field elements
1755          * @param eopHistory EOP history
1756          * @return array containing m₁ and m₂
1757          */
1758         private <T extends CalculusFieldElement<T>> T[] computePoleWobble(final FieldAbsoluteDate<T> date, final EOPHistory eopHistory) {
1759 
1760             // polynomial model from IERS 2010, table 7.7
1761             final double f0 = Constants.ARC_SECONDS_TO_RADIANS / 1000.0;
1762             final double f1 = f0 / Constants.JULIAN_YEAR;
1763             final double f2 = f1 / Constants.JULIAN_YEAR;
1764             final double f3 = f2 / Constants.JULIAN_YEAR;
1765             final AbsoluteDate changeDate =
1766                     new AbsoluteDate(2010, 1, 1, eopHistory.getTimeScales().getTT());
1767 
1768             // evaluate mean pole
1769             final double[] xPolynomial;
1770             final double[] yPolynomial;
1771             if (date.toAbsoluteDate().compareTo(changeDate) <= 0) {
1772                 xPolynomial = new double[] {
1773                     55.974 * f0, 1.8243 * f1, 0.18413 * f2, 0.007024 * f3
1774                 };
1775                 yPolynomial = new double[] {
1776                     346.346 * f0, 1.7896 * f1, -0.10729 * f2, -0.000908 * f3
1777                 };
1778             } else {
1779                 xPolynomial = new double[] {
1780                     23.513 * f0, 7.6141 * f1
1781                 };
1782                 yPolynomial = new double[] {
1783                     358.891 * f0,  -0.6287 * f1
1784                 };
1785             }
1786             T meanPoleX = date.getField().getZero();
1787             T meanPoleY = date.getField().getZero();
1788             final T t = date.durationFrom(
1789                     eopHistory.getTimeScales().getJ2000Epoch());
1790             for (int i = xPolynomial.length - 1; i >= 0; --i) {
1791                 meanPoleX = meanPoleX.multiply(t).add(xPolynomial[i]);
1792             }
1793             for (int i = yPolynomial.length - 1; i >= 0; --i) {
1794                 meanPoleY = meanPoleY.multiply(t).add(yPolynomial[i]);
1795             }
1796 
1797             // evaluate wobble variables
1798             final FieldPoleCorrection<T> correction = eopHistory.getPoleCorrection(date);
1799             final T[] m = MathArrays.buildArray(date.getField(), 2);
1800             m[0] = correction.getXp().subtract(meanPoleX);
1801             m[1] = meanPoleY.subtract(correction.getYp());
1802 
1803             return m;
1804 
1805         }
1806 
1807         /** {@inheritDoc} */
1808         @Override
1809         public TimeVectorFunction getSolidPoleTide(final EOPHistory eopHistory) {
1810 
1811             // constants from IERS 2010, section 6.4
1812             final double globalFactor = -1.333e-9 / Constants.ARC_SECONDS_TO_RADIANS;
1813             final double ratio        =  0.0115;
1814 
1815             return new TimeVectorFunction() {
1816 
1817                 /** {@inheritDoc} */
1818                 @Override
1819                 public double[] value(final AbsoluteDate date) {
1820 
1821                     // evaluate wobble variables
1822                     final double[] wobbleM = computePoleWobble(date, eopHistory);
1823 
1824                     return new double[] {
1825                         // the following correspond to the equations published in IERS 2010 conventions,
1826                         // section 6.4 page 94. The equations read:
1827                         // ∆C₂₁ = −1.333 × 10⁻⁹ (m₁ + 0.0115m₂)
1828                         // ∆S₂₁ = −1.333 × 10⁻⁹ (m₂ − 0.0115m₁)
1829                         // These equations seem to fix what was probably a sign error in IERS 2003
1830                         // conventions section 6.2 page 65. In this older publication, the equations read:
1831                         // ∆C₂₁ = −1.333 × 10⁻⁹ (m₁ − 0.0115m₂)
1832                         // ∆S₂₁ = −1.333 × 10⁻⁹ (m₂ + 0.0115m₁)
1833                         globalFactor * (wobbleM[0] + ratio * wobbleM[1]),
1834                         globalFactor * (wobbleM[1] - ratio * wobbleM[0])
1835                     };
1836 
1837                 }
1838 
1839                 /** {@inheritDoc} */
1840                 @Override
1841                 public <T extends CalculusFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
1842 
1843                     // evaluate wobble variables
1844                     final T[] wobbleM = computePoleWobble(date, eopHistory);
1845 
1846                     final T[] a = MathArrays.buildArray(date.getField(), 2);
1847 
1848                     // the following correspond to the equations published in IERS 2010 conventions,
1849                     // section 6.4 page 94. The equations read:
1850                     // ∆C₂₁ = −1.333 × 10⁻⁹ (m₁ + 0.0115m₂)
1851                     // ∆S₂₁ = −1.333 × 10⁻⁹ (m₂ − 0.0115m₁)
1852                     // These equations seem to fix what was probably a sign error in IERS 2003
1853                     // conventions section 6.2 page 65. In this older publication, the equations read:
1854                     // ∆C₂₁ = −1.333 × 10⁻⁹ (m₁ − 0.0115m₂)
1855                     // ∆S₂₁ = −1.333 × 10⁻⁹ (m₂ + 0.0115m₁)
1856                     a[0] = wobbleM[0].add(wobbleM[1].multiply( ratio)).multiply(globalFactor);
1857                     a[1] = wobbleM[1].add(wobbleM[0].multiply(-ratio)).multiply(globalFactor);
1858 
1859                     return a;
1860 
1861                 }
1862 
1863             };
1864 
1865         }
1866 
1867         /** {@inheritDoc} */
1868         @Override
1869         public TimeVectorFunction getOceanPoleTide(final EOPHistory eopHistory) {
1870 
1871             return new TimeVectorFunction() {
1872 
1873                 /** {@inheritDoc} */
1874                 @Override
1875                 public double[] value(final AbsoluteDate date) {
1876 
1877                     // evaluate wobble variables
1878                     final double[] wobbleM = computePoleWobble(date, eopHistory);
1879 
1880                     return new double[] {
1881                         // the following correspond to the equations published in IERS 2010 conventions,
1882                         // section 6.4 page 94 equation 6.24:
1883                         // ∆C₂₁ = −2.1778 × 10⁻¹⁰ (m₁ − 0.01724m₂)
1884                         // ∆S₂₁ = −1.7232 × 10⁻¹⁰ (m₂ − 0.03365m₁)
1885                         -2.1778e-10 * (wobbleM[0] - 0.01724 * wobbleM[1]) / Constants.ARC_SECONDS_TO_RADIANS,
1886                         -1.7232e-10 * (wobbleM[1] - 0.03365 * wobbleM[0]) / Constants.ARC_SECONDS_TO_RADIANS
1887                     };
1888 
1889                 }
1890 
1891                 /** {@inheritDoc} */
1892                 @Override
1893                 public <T extends CalculusFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
1894 
1895                     final T[] v = MathArrays.buildArray(date.getField(), 2);
1896 
1897                     // evaluate wobble variables
1898                     final T[] wobbleM = computePoleWobble(date, eopHistory);
1899 
1900                     // the following correspond to the equations published in IERS 2010 conventions,
1901                     // section 6.4 page 94 equation 6.24:
1902                     // ∆C₂₁ = −2.1778 × 10⁻¹⁰ (m₁ − 0.01724m₂)
1903                     // ∆S₂₁ = −1.7232 × 10⁻¹⁰ (m₂ − 0.03365m₁)
1904                     v[0] = wobbleM[0].subtract(wobbleM[1].multiply(0.01724)).multiply(-2.1778e-10 / Constants.ARC_SECONDS_TO_RADIANS);
1905                     v[1] = wobbleM[1].subtract(wobbleM[0].multiply(0.03365)).multiply(-1.7232e-10 / Constants.ARC_SECONDS_TO_RADIANS);
1906 
1907                     return v;
1908 
1909                 }
1910 
1911             };
1912 
1913         }
1914 
1915         /** {@inheritDoc} */
1916         @Override
1917         public TimeVectorFunction getPrecessionFunction(final TimeScales timeScales) {
1918 
1919             // set up the conventional polynomials
1920             // the following values are from equation 5.40 in IERS 2010 conventions
1921             final PolynomialNutation psiA =
1922                     new PolynomialNutation(   0.0,
1923                                            5038.481507     * Constants.ARC_SECONDS_TO_RADIANS,
1924                                              -1.0790069    * Constants.ARC_SECONDS_TO_RADIANS,
1925                                              -0.00114045   * Constants.ARC_SECONDS_TO_RADIANS,
1926                                               0.000132851  * Constants.ARC_SECONDS_TO_RADIANS,
1927                                              -0.0000000951 * Constants.ARC_SECONDS_TO_RADIANS);
1928             final PolynomialNutation omegaA =
1929                     new PolynomialNutation(getMeanObliquityFunction(timeScales)
1930                             .value(getNutationReferenceEpoch(timeScales)),
1931                                            -0.025754     * Constants.ARC_SECONDS_TO_RADIANS,
1932                                             0.0512623    * Constants.ARC_SECONDS_TO_RADIANS,
1933                                            -0.00772503   * Constants.ARC_SECONDS_TO_RADIANS,
1934                                            -0.000000467  * Constants.ARC_SECONDS_TO_RADIANS,
1935                                             0.0000003337 * Constants.ARC_SECONDS_TO_RADIANS);
1936             final PolynomialNutation chiA =
1937                     new PolynomialNutation( 0.0,
1938                                            10.556403     * Constants.ARC_SECONDS_TO_RADIANS,
1939                                            -2.3814292    * Constants.ARC_SECONDS_TO_RADIANS,
1940                                            -0.00121197   * Constants.ARC_SECONDS_TO_RADIANS,
1941                                             0.000170663  * Constants.ARC_SECONDS_TO_RADIANS,
1942                                            -0.0000000560 * Constants.ARC_SECONDS_TO_RADIANS);
1943 
1944             return new PrecessionFunction(psiA, omegaA, chiA, timeScales);
1945 
1946         }
1947 
1948         /** {@inheritDoc} */
1949         @Override
1950         public TimeVectorFunction getNutationFunction(final TimeScales timeScales) {
1951 
1952             // set up nutation arguments
1953             final FundamentalNutationArguments arguments =
1954                     getNutationArguments(timeScales);
1955 
1956             // set up Poisson series
1957             final double microAS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-6;
1958             final PoissonSeriesParser parser =
1959                     new PoissonSeriesParser(17).
1960                         withFirstDelaunay(4).
1961                         withFirstPlanetary(9).
1962                         withSinCos(0, 2, microAS, 3, microAS);
1963             final PoissonSeries psiSeries     = load(PSI_SERIES,     in -> parser.parse(in, PSI_SERIES));
1964             final PoissonSeries epsilonSeries = load(EPSILON_SERIES, in -> parser.parse(in, EPSILON_SERIES));
1965             final PoissonSeries.CompiledSeries psiEpsilonSeries = PoissonSeries.compile(psiSeries, epsilonSeries);
1966 
1967             return new TimeVectorFunction() {
1968 
1969                 /** {@inheritDoc} */
1970                 @Override
1971                 public double[] value(final AbsoluteDate date) {
1972                     final BodiesElements elements = arguments.evaluateAll(date);
1973                     final double[] psiEpsilon = psiEpsilonSeries.value(elements);
1974                     return new double[] {
1975                         psiEpsilon[0], psiEpsilon[1],
1976                         IAU1994ResolutionC7.value(elements, timeScales.getTAI())
1977                     };
1978                 }
1979 
1980                 /** {@inheritDoc} */
1981                 @Override
1982                 public <T extends CalculusFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
1983                     final FieldBodiesElements<T> elements = arguments.evaluateAll(date);
1984                     final T[] psiEpsilon = psiEpsilonSeries.value(elements);
1985                     final T[] result = MathArrays.buildArray(date.getField(), 3);
1986                     result[0] = psiEpsilon[0];
1987                     result[1] = psiEpsilon[1];
1988                     result[2] = IAU1994ResolutionC7.value(elements, timeScales.getTAI());
1989                     return result;
1990                 }
1991 
1992             };
1993 
1994         }
1995 
1996         /** {@inheritDoc} */
1997         @Override
1998         public TimeScalarFunction getGMSTFunction(final TimeScale ut1,
1999                                                   final TimeScales timeScales) {
2000 
2001             // Earth Rotation Angle
2002             final StellarAngleCapitaine era =
2003                     new StellarAngleCapitaine(ut1, timeScales.getTAI());
2004 
2005             // Polynomial part of the apparent sidereal time series
2006             // which is the opposite of Equation of Origins (EO)
2007             final double microAS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-6;
2008             final PoissonSeriesParser parser =
2009                     new PoissonSeriesParser(17).
2010                         withFirstDelaunay(4).
2011                         withFirstPlanetary(9).
2012                         withSinCos(0, 2, microAS, 3, microAS).
2013                         withPolynomialPart('t', Unit.ARC_SECONDS);
2014             final PolynomialNutation minusEO = load(GST_SERIES, in ->  parser.parse(in, GST_SERIES).getPolynomial());
2015 
2016             // create a function evaluating the series
2017             return new TimeScalarFunction() {
2018 
2019                 /** {@inheritDoc} */
2020                 @Override
2021                 public double value(final AbsoluteDate date) {
2022                     return era.value(date) + minusEO.value(evaluateTC(date, timeScales));
2023                 }
2024 
2025                 /** {@inheritDoc} */
2026                 @Override
2027                 public <T extends CalculusFieldElement<T>> T value(final FieldAbsoluteDate<T> date) {
2028                     return era.value(date).add(minusEO.value(evaluateTC(date, timeScales)));
2029                 }
2030 
2031             };
2032 
2033         }
2034 
2035         /** {@inheritDoc} */
2036         @Override
2037         public TimeScalarFunction getGMSTRateFunction(final TimeScale ut1,
2038                                                       final TimeScales timeScales) {
2039 
2040             // Earth Rotation Angle
2041             final StellarAngleCapitaine era =
2042                     new StellarAngleCapitaine(ut1, timeScales.getTAI());
2043 
2044             // Polynomial part of the apparent sidereal time series
2045             // which is the opposite of Equation of Origins (EO)
2046             final double microAS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-6;
2047             final PoissonSeriesParser parser =
2048                     new PoissonSeriesParser(17).
2049                         withFirstDelaunay(4).
2050                         withFirstPlanetary(9).
2051                         withSinCos(0, 2, microAS, 3, microAS).
2052                         withPolynomialPart('t', Unit.ARC_SECONDS);
2053             final PolynomialNutation minusEO = load(GST_SERIES, in -> parser.parse(in, GST_SERIES).getPolynomial());
2054 
2055             // create a function evaluating the series
2056             return new TimeScalarFunction() {
2057 
2058                 /** {@inheritDoc} */
2059                 @Override
2060                 public double value(final AbsoluteDate date) {
2061                     return era.getRate() + minusEO.derivative(evaluateTC(date, timeScales));
2062                 }
2063 
2064                 /** {@inheritDoc} */
2065                 @Override
2066                 public <T extends CalculusFieldElement<T>> T value(final FieldAbsoluteDate<T> date) {
2067                     return minusEO.derivative(evaluateTC(date, timeScales)).add(era.getRate());
2068                 }
2069 
2070             };
2071 
2072         }
2073 
2074         /** {@inheritDoc} */
2075         @Override
2076         public TimeScalarFunction getGASTFunction(final TimeScale ut1,
2077                                                   final EOPHistory eopHistory,
2078                                                   final TimeScales timeScales) {
2079 
2080             // set up nutation arguments
2081             final FundamentalNutationArguments arguments =
2082                     getNutationArguments(timeScales);
2083 
2084             // mean obliquity function
2085             final TimeScalarFunction epsilon = getMeanObliquityFunction(timeScales);
2086 
2087             // set up Poisson series
2088             final double microAS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-6;
2089             final PoissonSeriesParser baseParser =
2090                     new PoissonSeriesParser(17).
2091                         withFirstDelaunay(4).
2092                         withFirstPlanetary(9).
2093                         withSinCos(0, 2, microAS, 3, microAS);
2094             final PoissonSeriesParser          gstParser    = baseParser.withPolynomialPart('t', Unit.ARC_SECONDS);
2095             final PoissonSeries                psiSeries    = load(PSI_SERIES, in -> baseParser.parse(in, PSI_SERIES));
2096             final PoissonSeries                gstSeries    = load(GST_SERIES, in -> gstParser.parse(in, GST_SERIES));
2097             final PoissonSeries.CompiledSeries psiGstSeries = PoissonSeries.compile(psiSeries, gstSeries);
2098 
2099             // ERA function
2100             final TimeScalarFunction era =
2101                     getEarthOrientationAngleFunction(ut1, timeScales.getTAI());
2102 
2103             return new TimeScalarFunction() {
2104 
2105                 /** {@inheritDoc} */
2106                 @Override
2107                 public double value(final AbsoluteDate date) {
2108 
2109                     // evaluate equation of origins
2110                     final BodiesElements elements = arguments.evaluateAll(date);
2111                     final double[] angles = psiGstSeries.value(elements);
2112                     final double ddPsi    = (eopHistory == null) ? 0 : eopHistory.getEquinoxNutationCorrection(date)[0];
2113                     final double deltaPsi = angles[0] + ddPsi;
2114                     final double epsilonA = epsilon.value(date);
2115 
2116                     // subtract equation of origin from EA
2117                     // (hence add the series above which have the sign included)
2118                     return era.value(date) + deltaPsi * FastMath.cos(epsilonA) + angles[1];
2119 
2120                 }
2121 
2122                 /** {@inheritDoc} */
2123                 @Override
2124                 public <T extends CalculusFieldElement<T>> T value(final FieldAbsoluteDate<T> date) {
2125 
2126                     // evaluate equation of origins
2127                     final FieldBodiesElements<T> elements = arguments.evaluateAll(date);
2128                     final T[] angles = psiGstSeries.value(elements);
2129                     final T ddPsi    = (eopHistory == null) ? date.getField().getZero() : eopHistory.getEquinoxNutationCorrection(date)[0];
2130                     final T deltaPsi = angles[0].add(ddPsi);
2131                     final T epsilonA = epsilon.value(date);
2132 
2133                     // subtract equation of origin from EA
2134                     // (hence add the series above which have the sign included)
2135                     return era.value(date).add(deltaPsi.multiply(epsilonA.cos())).add(angles[1]);
2136 
2137                 }
2138 
2139             };
2140 
2141         }
2142 
2143         /** {@inheritDoc} */
2144         @Override
2145         public TimeVectorFunction getEOPTidalCorrection(final TimeScales timeScales) {
2146 
2147             // set up nutation arguments
2148             // BEWARE! Using TT as the time scale here and not UT1 is intentional!
2149             // as this correction is used to compute UT1 itself, it is not surprising we cannot use UT1 yet,
2150             // however, using the close UTC as would seem logical make the comparison with interp.f from IERS fail
2151             // looking in the interp.f code, the same TT scale is used for both Delaunay and gamma argument
2152             final FundamentalNutationArguments arguments =
2153                     getNutationArguments(timeScales.getTT(), timeScales);
2154 
2155             // set up Poisson series
2156             final double microAS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-6;
2157             final PoissonSeriesParser xyParser  = new PoissonSeriesParser(13).
2158                                                   withOptionalColumn(1).
2159                                                   withGamma(2).
2160                                                   withFirstDelaunay(3);
2161             final double microS = 1.0e-6;
2162             final PoissonSeriesParser ut1Parser = new PoissonSeriesParser(11).
2163                                                   withOptionalColumn(1).
2164                                                   withGamma(2).
2165                                                   withFirstDelaunay(3);
2166             final PoissonSeries xSeries =
2167                             load(TIDAL_CORRECTION_XP_YP_SERIES, xpIn -> xyParser.
2168                                                                         withSinCos(0, 10, microAS, 11, microAS).
2169                                                                         parse(xpIn, TIDAL_CORRECTION_XP_YP_SERIES));
2170             final PoissonSeries ySeries =
2171                             load(TIDAL_CORRECTION_XP_YP_SERIES, ypIn -> xyParser.
2172                                                                         withSinCos(0, 12, microAS, 13, microAS).
2173                                                                         parse(ypIn, TIDAL_CORRECTION_XP_YP_SERIES));
2174             final PoissonSeries ut1Series =
2175                             load(TIDAL_CORRECTION_UT1_SERIES, ut1In -> ut1Parser.
2176                                                                        withSinCos(0, 10, microS, 11, microS).
2177                                                                        parse(ut1In, TIDAL_CORRECTION_UT1_SERIES));
2178 
2179             return new EOPTidalCorrection(arguments, xSeries, ySeries, ut1Series);
2180 
2181         }
2182 
2183         /** {@inheritDoc} */
2184         @Override
2185         public double[] getNominalTidalDisplacement() {
2186             return new double[] {
2187                 // h⁽⁰⁾,                                  h⁽²⁾,   h₃,     hI diurnal, hI semi-diurnal,
2188                 0.6078,                                  -0.0006, 0.292, -0.0025,    -0.0022,
2189                 // l⁽⁰⁾, l⁽¹⁾ diurnal, l⁽¹⁾ semi-diurnal, l⁽²⁾,   l₃,     lI diurnal, lI semi-diurnal
2190                 0.0847,  0.0012,       0.0024,            0.0002, 0.015, -0.0007,    -0.0007,
2191                 // H₀
2192                 -0.31460
2193             };
2194         }
2195 
2196         /** {@inheritDoc} */
2197         @Override
2198         public CompiledSeries getTidalDisplacementFrequencyCorrectionDiurnal() {
2199             return getTidalDisplacementFrequencyCorrectionDiurnal(TIDAL_DISPLACEMENT_CORRECTION_DIURNAL,
2200                                                                   18, 15, 16, 17, 18);
2201         }
2202 
2203         /** {@inheritDoc} */
2204         @Override
2205         public CompiledSeries getTidalDisplacementFrequencyCorrectionZonal() {
2206             return getTidalDisplacementFrequencyCorrectionZonal(TIDAL_DISPLACEMENT_CORRECTION_ZONAL,
2207                                                                 18, 15, 16, 17, 18);
2208         }
2209 
2210     };
2211 
2212     /** Pattern for delimiting regular expressions. */
2213     private static final Pattern SEPARATOR = Pattern.compile("\\p{Space}+");
2214 
2215     /** IERS conventions resources base directory. */
2216     private static final String IERS_BASE = "/assets/org/orekit/IERS-conventions/";
2217 
2218     /** Get the reference epoch for fundamental nutation arguments.
2219      *
2220      * <p>This method uses the {@link DataContext#getDefault() default data context}.
2221      *
2222      * @return reference epoch for fundamental nutation arguments
2223      * @since 6.1
2224      * @see #getNutationReferenceEpoch(TimeScales)
2225      */
2226     @DefaultDataContext
2227     public AbsoluteDate getNutationReferenceEpoch() {
2228         return getNutationReferenceEpoch(DataContext.getDefault().getTimeScales());
2229     }
2230 
2231     /**
2232      * Get the reference epoch for fundamental nutation arguments.
2233      *
2234      * @param timeScales to use for the reference epoch.
2235      * @return reference epoch for fundamental nutation arguments
2236      * @since 10.1
2237      */
2238     public AbsoluteDate getNutationReferenceEpoch(final TimeScales timeScales) {
2239         // IERS 1996, IERS 2003 and IERS 2010 use the same J2000.0 reference date
2240         return timeScales.getJ2000Epoch();
2241     }
2242 
2243     /** Evaluate the date offset between the current date and the {@link #getNutationReferenceEpoch() reference date}.
2244      *
2245      * <p>This method uses the {@link DataContext#getDefault() default data context}.
2246      *
2247      * @param date current date
2248      * @return date offset in Julian centuries
2249      * @since 6.1
2250      * @see #evaluateTC(AbsoluteDate, TimeScales)
2251      */
2252     @DefaultDataContext
2253     public double evaluateTC(final AbsoluteDate date) {
2254         return evaluateTC(date, DataContext.getDefault().getTimeScales());
2255     }
2256 
2257     /**
2258      * Evaluate the date offset between the current date and the {@link
2259      * #getNutationReferenceEpoch() reference date}.
2260      *
2261      * @param date       current date
2262      * @param timeScales used in the evaluation.
2263      * @return date offset in Julian centuries
2264      * @since 10.1
2265      */
2266     public double evaluateTC(final AbsoluteDate date, final TimeScales timeScales) {
2267         return date.durationFrom(getNutationReferenceEpoch(timeScales)) /
2268                 Constants.JULIAN_CENTURY;
2269     }
2270 
2271     /** Evaluate the date offset between the current date and the {@link #getNutationReferenceEpoch() reference date}.
2272      *
2273      * <p>This method uses the {@link DataContext#getDefault() default data context}.
2274      *
2275      * @param date current date
2276      * @param <T> type of the field elements
2277      * @return date offset in Julian centuries
2278      * @since 9.0
2279      * @see #evaluateTC(FieldAbsoluteDate, TimeScales)
2280      */
2281     @DefaultDataContext
2282     public <T extends CalculusFieldElement<T>> T evaluateTC(final FieldAbsoluteDate<T> date) {
2283         return evaluateTC(date, DataContext.getDefault().getTimeScales());
2284     }
2285 
2286     /** Evaluate the date offset between the current date and the {@link #getNutationReferenceEpoch() reference date}.
2287      * @param <T> type of the field elements
2288      * @param date current date
2289      * @param timeScales used in the evaluation.
2290      * @return date offset in Julian centuries
2291      * @since 10.1
2292      */
2293     public <T extends CalculusFieldElement<T>> T evaluateTC(final FieldAbsoluteDate<T> date,
2294                                                         final TimeScales timeScales) {
2295         return date.durationFrom(getNutationReferenceEpoch(timeScales))
2296                 .divide(Constants.JULIAN_CENTURY);
2297     }
2298 
2299     /**
2300      * Get the fundamental nutation arguments. Does not compute GMST based values: gamma,
2301      * gammaDot.
2302      *
2303      * @param timeScales other time scales used in the computation including TAI and TT.
2304      * @return fundamental nutation arguments
2305      * @see #getNutationArguments(TimeScale, TimeScales)
2306      * @since 10.1
2307      */
2308     protected FundamentalNutationArguments getNutationArguments(
2309             final TimeScales timeScales) {
2310 
2311         return getNutationArguments(null, timeScales);
2312     }
2313 
2314     /** Get the fundamental nutation arguments.
2315      *
2316      * <p>This method uses the {@link DataContext#getDefault() default data context}.
2317      *
2318      * @param timeScale time scale for computing Greenwich Mean Sidereal Time
2319      * (typically {@link TimeScales#getUT1(IERSConventions, boolean) UT1})
2320      * @return fundamental nutation arguments
2321      * @since 6.1
2322      * @see #getNutationArguments(TimeScale, TimeScales)
2323      * @see #getNutationArguments(TimeScales)
2324      */
2325     @DefaultDataContext
2326     public FundamentalNutationArguments getNutationArguments(final TimeScale timeScale) {
2327         return getNutationArguments(timeScale, DataContext.getDefault().getTimeScales());
2328     }
2329 
2330     /**
2331      * Get the fundamental nutation arguments.
2332      *
2333      * @param timeScale time scale for computing Greenwich Mean Sidereal Time (typically
2334      *                  {@link TimeScales#getUT1(IERSConventions, boolean) UT1})
2335      * @param timeScales other time scales used in the computation including TAI and TT.
2336      * @return fundamental nutation arguments
2337      * @since 10.1
2338      */
2339     public abstract FundamentalNutationArguments getNutationArguments(
2340             TimeScale timeScale,
2341             TimeScales timeScales);
2342 
2343     /** Get the function computing mean obliquity of the ecliptic.
2344      *
2345      * <p>This method uses the {@link DataContext#getDefault() default data context}.
2346      *
2347      * @return function computing mean obliquity of the ecliptic
2348      * @since 6.1
2349      * @see #getMeanObliquityFunction(TimeScales)
2350      */
2351     @DefaultDataContext
2352     public TimeScalarFunction getMeanObliquityFunction() {
2353         return getMeanObliquityFunction(DataContext.getDefault().getTimeScales());
2354     }
2355 
2356     /**
2357      * Get the function computing mean obliquity of the ecliptic.
2358      *
2359      * @param timeScales used in computing the function.
2360      * @return function computing mean obliquity of the ecliptic
2361      * @since 10.1
2362      */
2363     public abstract TimeScalarFunction getMeanObliquityFunction(TimeScales timeScales);
2364 
2365     /** Get the function computing the Celestial Intermediate Pole and Celestial Intermediate Origin components.
2366      * <p>
2367      * The returned function computes the two X, Y components of CIP and the S+XY/2 component of the non-rotating CIO.
2368      * </p>
2369      *
2370      * <p>This method uses the {@link DataContext#getDefault() default data context}.
2371      *
2372      * @return function computing the Celestial Intermediate Pole and Celestial Intermediate Origin components
2373      * @since 6.1
2374      * @see #getXYSpXY2Function(TimeScales)
2375      */
2376     @DefaultDataContext
2377     public TimeVectorFunction getXYSpXY2Function() {
2378         return getXYSpXY2Function(DataContext.getDefault().getTimeScales());
2379     }
2380 
2381     /**
2382      * Get the function computing the Celestial Intermediate Pole and Celestial
2383      * Intermediate Origin components.
2384      * <p>
2385      * The returned function computes the two X, Y components of CIP and the S+XY/2
2386      * component of the non-rotating CIO.
2387      * </p>
2388      *
2389      * @param timeScales used to define the function.
2390      * @return function computing the Celestial Intermediate Pole and Celestial
2391      * Intermediate Origin components
2392      * @since 10.1
2393      */
2394     public abstract TimeVectorFunction getXYSpXY2Function(TimeScales timeScales);
2395 
2396     /** Get the function computing the raw Earth Orientation Angle.
2397      *
2398      * <p>This method uses the {@link DataContext#getDefault() default data context}.
2399      *
2400      * <p>
2401      * The raw angle does not contain any correction. If for example dTU1 correction
2402      * due to tidal effect is desired, it must be added afterward by the caller.
2403      * The returned value contain the angle as the value and the angular rate as
2404      * the first derivative.
2405      * </p>
2406      * @param ut1 UT1 time scale
2407      * @return function computing the rawEarth Orientation Angle, in the non-rotating origin paradigm
2408      * @since 6.1
2409      * @see #getEarthOrientationAngleFunction(TimeScale, TimeScale)
2410      */
2411     @DefaultDataContext
2412     public TimeScalarFunction getEarthOrientationAngleFunction(final TimeScale ut1) {
2413         return getEarthOrientationAngleFunction(ut1,
2414                 DataContext.getDefault().getTimeScales().getTAI());
2415     }
2416 
2417     /** Get the function computing the raw Earth Orientation Angle.
2418      * <p>
2419      * The raw angle does not contain any correction. If for example dTU1 correction
2420      * due to tidal effect is desired, it must be added afterward by the caller.
2421      * The returned value contain the angle as the value and the angular rate as
2422      * the first derivative.
2423      * </p>
2424      * @param ut1 UT1 time scale
2425      * @param tai TAI time scale
2426      * @return function computing the rawEarth Orientation Angle, in the non-rotating origin paradigm
2427      * @since 10.1
2428      */
2429     public TimeScalarFunction getEarthOrientationAngleFunction(final TimeScale ut1,
2430                                                                final TimeScale tai) {
2431         return new StellarAngleCapitaine(ut1, tai);
2432     }
2433 
2434     /** Get the function computing the precession angles.
2435      * <p>
2436      * The function returned computes the three precession angles
2437      * ψ<sub>A</sub> (around Z axis), ω<sub>A</sub> (around X axis)
2438      * and χ<sub>A</sub> (around Z axis). The constant angle ε₀
2439      * for the fourth rotation (around X axis) can be retrieved by evaluating the
2440      * function returned by {@link #getMeanObliquityFunction()} at {@link
2441      * #getNutationReferenceEpoch() nutation reference epoch}.
2442      * </p>
2443      *
2444      * <p>This method uses the {@link DataContext#getDefault() default data context}.
2445      *
2446      * @return function computing the precession angle
2447      * @since 6.1
2448      * @see #getPrecessionFunction(TimeScales)
2449      */
2450     @DefaultDataContext
2451     public TimeVectorFunction getPrecessionFunction()
2452     {
2453         return getPrecessionFunction(DataContext.getDefault().getTimeScales());
2454     }
2455 
2456     /** Get the function computing the precession angles.
2457      * <p>
2458      * The function returned computes the three precession angles
2459      * ψ<sub>A</sub> (around Z axis), ω<sub>A</sub> (around X axis)
2460      * and χ<sub>A</sub> (around Z axis). The constant angle ε₀
2461      * for the fourth rotation (around X axis) can be retrieved by evaluating the
2462      * function returned by {@link #getMeanObliquityFunction()} at {@link
2463      * #getNutationReferenceEpoch() nutation reference epoch}.
2464      * </p>
2465      * @return function computing the precession angle
2466      * @since 10.1
2467      * @param timeScales used to define the function.
2468      */
2469     public abstract TimeVectorFunction getPrecessionFunction(TimeScales timeScales);
2470 
2471     /** Get the function computing the nutation angles.
2472      *
2473      * <p>This method uses the {@link DataContext#getDefault() default data context}.
2474      *
2475      * <p>
2476      * The function returned computes the two classical angles ΔΨ and Δε,
2477      * and the correction to the equation of equinoxes introduced since 1997-02-27 by IAU 1994
2478      * resolution C7 (the correction is forced to 0 before this date)
2479      * </p>
2480      * @return function computing the nutation in longitude ΔΨ and Δε
2481      * and the correction of equation of equinoxes
2482      * @since 6.1
2483      */
2484     @DefaultDataContext
2485     public TimeVectorFunction getNutationFunction() {
2486         return getNutationFunction(DataContext.getDefault().getTimeScales());
2487     }
2488 
2489     /** Get the function computing the nutation angles.
2490      * <p>
2491      * The function returned computes the two classical angles ΔΨ and Δε,
2492      * and the correction to the equation of equinoxes introduced since 1997-02-27 by IAU 1994
2493      * resolution C7 (the correction is forced to 0 before this date)
2494      * </p>
2495      * @return function computing the nutation in longitude ΔΨ and Δε
2496      * and the correction of equation of equinoxes
2497      * @param timeScales used in the computation including TAI and TT.
2498      * @since 10.1
2499      */
2500     public abstract TimeVectorFunction getNutationFunction(TimeScales timeScales);
2501 
2502     /** Get the function computing Greenwich mean sidereal time, in radians.
2503      *
2504      * <p>This method uses the {@link DataContext#getDefault() default data context}.
2505      *
2506      * @param ut1 UT1 time scale
2507      * @return function computing Greenwich mean sidereal time
2508      * @since 6.1
2509      * @see #getGMSTFunction(TimeScale, TimeScales)
2510      */
2511     @DefaultDataContext
2512     public TimeScalarFunction getGMSTFunction(final TimeScale ut1) {
2513         return getGMSTFunction(ut1, DataContext.getDefault().getTimeScales());
2514     }
2515 
2516     /**
2517      * Get the function computing Greenwich mean sidereal time, in radians.
2518      *
2519      * @param ut1 UT1 time scale
2520      * @param timeScales other time scales used in the computation including TAI and TT.
2521      * @return function computing Greenwich mean sidereal time
2522      * @since 10.1
2523      */
2524     public abstract TimeScalarFunction getGMSTFunction(TimeScale ut1,
2525                                                        TimeScales timeScales);
2526 
2527     /** Get the function computing Greenwich mean sidereal time rate, in radians per second.
2528      *
2529      * <p>This method uses the {@link DataContext#getDefault() default data context}.
2530      *
2531      * @param ut1 UT1 time scale
2532      * @return function computing Greenwich mean sidereal time rate
2533      * @since 9.0
2534      * @see #getGMSTRateFunction(TimeScale, TimeScales)
2535      */
2536     @DefaultDataContext
2537     public TimeScalarFunction getGMSTRateFunction(final TimeScale ut1) {
2538         return getGMSTRateFunction(ut1,
2539                 DataContext.getDefault().getTimeScales());
2540     }
2541 
2542     /**
2543      * Get the function computing Greenwich mean sidereal time rate, in radians per
2544      * second.
2545      *
2546      * @param ut1 UT1 time scale
2547      * @param timeScales other time scales used in the computation including TAI and TT.
2548      * @return function computing Greenwich mean sidereal time rate
2549      * @since 10.1
2550      */
2551     public abstract TimeScalarFunction getGMSTRateFunction(TimeScale ut1,
2552                                                            TimeScales timeScales);
2553 
2554     /**
2555      * Get the function computing Greenwich apparent sidereal time, in radians.
2556      *
2557      * <p>This method uses the {@link DataContext#getDefault() default data context} if
2558      * {@code eopHistory == null}.
2559      *
2560      * @param ut1        UT1 time scale
2561      * @param eopHistory EOP history. If {@code null} then no nutation correction is
2562      *                   applied for EOP.
2563      * @return function computing Greenwich apparent sidereal time
2564      * @since 6.1
2565      * @see #getGASTFunction(TimeScale, EOPHistory, TimeScales)
2566      */
2567     @DefaultDataContext
2568     public TimeScalarFunction getGASTFunction(final TimeScale ut1,
2569                                               final EOPHistory eopHistory) {
2570         final TimeScales timeScales = eopHistory != null ?
2571                 eopHistory.getTimeScales() :
2572                 DataContext.getDefault().getTimeScales();
2573         return getGASTFunction(ut1, eopHistory, timeScales);
2574     }
2575 
2576     /**
2577      * Get the function computing Greenwich apparent sidereal time, in radians.
2578      *
2579      * @param ut1        UT1 time scale
2580      * @param eopHistory EOP history. If {@code null} then no nutation correction is
2581      *                   applied for EOP.
2582      * @param timeScales        TAI time scale.
2583      * @return function computing Greenwich apparent sidereal time
2584      * @since 10.1
2585      */
2586     public abstract TimeScalarFunction getGASTFunction(TimeScale ut1,
2587                                                        EOPHistory eopHistory,
2588                                                        TimeScales timeScales);
2589 
2590     /** Get the function computing tidal corrections for Earth Orientation Parameters.
2591      *
2592      * <p>This method uses the {@link DataContext#getDefault() default data context}.
2593      *
2594      * @return function computing tidal corrections for Earth Orientation Parameters,
2595      * for xp, yp, ut1 and lod respectively
2596      * @since 6.1
2597      * @see #getEOPTidalCorrection(TimeScales)
2598      */
2599     @DefaultDataContext
2600     public TimeVectorFunction getEOPTidalCorrection() {
2601         return getEOPTidalCorrection(DataContext.getDefault().getTimeScales());
2602     }
2603 
2604     /**
2605      * Get the function computing tidal corrections for Earth Orientation Parameters.
2606      *
2607      * @param timeScales used in the computation. The TT and TAI scales are used.
2608      * @return function computing tidal corrections for Earth Orientation Parameters, for
2609      * xp, yp, ut1 and lod respectively
2610      * @since 10.1
2611      */
2612     public abstract TimeVectorFunction getEOPTidalCorrection(TimeScales timeScales);
2613 
2614     /** Get the Love numbers.
2615      * @return Love numbers
2616           * @since 6.1
2617      */
2618     public abstract LoveNumbers getLoveNumbers();
2619 
2620     /** Get the function computing frequency dependent terms (ΔC₂₀, ΔC₂₁, ΔS₂₁, ΔC₂₂, ΔS₂₂).
2621      *
2622      * <p>This method uses the {@link DataContext#getDefault() default data context}.
2623      *
2624      * @param ut1 UT1 time scale
2625      * @return frequency dependence model for tides computation (ΔC₂₀, ΔC₂₁, ΔS₂₁, ΔC₂₂, ΔS₂₂).
2626      * @since 6.1
2627      * @see #getTideFrequencyDependenceFunction(TimeScale, TimeScales)
2628      */
2629     @DefaultDataContext
2630     public TimeVectorFunction getTideFrequencyDependenceFunction(final TimeScale ut1) {
2631         return getTideFrequencyDependenceFunction(ut1,
2632                 DataContext.getDefault().getTimeScales());
2633     }
2634 
2635     /**
2636      * Get the function computing frequency dependent terms (ΔC₂₀, ΔC₂₁, ΔS₂₁, ΔC₂₂,
2637      * ΔS₂₂).
2638      *
2639      * @param ut1 UT1 time scale
2640      * @param timeScales other time scales used in the computation including TAI and TT.
2641      * @return frequency dependence model for tides computation (ΔC₂₀, ΔC₂₁, ΔS₂₁, ΔC₂₂,
2642      * ΔS₂₂).
2643      * @since 10.1
2644      */
2645     public abstract TimeVectorFunction getTideFrequencyDependenceFunction(
2646             TimeScale ut1,
2647             TimeScales timeScales);
2648 
2649     /** Get the permanent tide to be <em>removed</em> from ΔC₂₀ when zero-tide potentials are used.
2650      * @return permanent tide to remove
2651      */
2652     public abstract double getPermanentTide();
2653 
2654     /** Get the function computing solid pole tide (ΔC₂₁, ΔS₂₁).
2655      * @param eopHistory EOP history
2656      * @return model for solid pole tide (ΔC₂₀, ΔC₂₁, ΔS₂₁, ΔC₂₂, ΔS₂₂).
2657           * @since 6.1
2658      */
2659     public abstract TimeVectorFunction getSolidPoleTide(EOPHistory eopHistory);
2660 
2661     /** Get the function computing ocean pole tide (ΔC₂₁, ΔS₂₁).
2662      * @param eopHistory EOP history
2663      * @return model for ocean pole tide (ΔC₂₀, ΔC₂₁, ΔS₂₁, ΔC₂₂, ΔS₂₂).
2664           * @since 6.1
2665      */
2666     public abstract TimeVectorFunction getOceanPoleTide(EOPHistory eopHistory);
2667 
2668     /** Get the nominal values of the displacement numbers.
2669      * @return an array containing h⁽⁰⁾, h⁽²⁾, h₃, hI diurnal, hI semi-diurnal,
2670      * l⁽⁰⁾, l⁽¹⁾ diurnal, l⁽¹⁾ semi-diurnal, l⁽²⁾, l₃, lI diurnal, lI semi-diurnal,
2671      * H₀ permanent deformation amplitude
2672      * @since 9.1
2673      */
2674     public abstract double[] getNominalTidalDisplacement();
2675 
2676     /** Get the correction function for tidal displacement for diurnal tides.
2677      * <ul>
2678      *  <li>f[0]: radial correction, longitude cosine part</li>
2679      *  <li>f[1]: radial correction, longitude sine part</li>
2680      *  <li>f[2]: North correction, longitude cosine part</li>
2681      *  <li>f[3]: North correction, longitude sine part</li>
2682      *  <li>f[4]: East correction, longitude cosine part</li>
2683      *  <li>f[5]: East correction, longitude sine part</li>
2684      * </ul>
2685      * @return correction function for tidal displacement
2686      * @since 9.1
2687      */
2688     public abstract CompiledSeries getTidalDisplacementFrequencyCorrectionDiurnal();
2689 
2690     /** Get the correction function for tidal displacement for diurnal tides.
2691      * <ul>
2692      *  <li>f[0]: radial correction, longitude cosine part</li>
2693      *  <li>f[1]: radial correction, longitude sine part</li>
2694      *  <li>f[2]: North correction, longitude cosine part</li>
2695      *  <li>f[3]: North correction, longitude sine part</li>
2696      *  <li>f[4]: East correction, longitude cosine part</li>
2697      *  <li>f[5]: East correction, longitude sine part</li>
2698      * </ul>
2699      * @param tableName name for the diurnal tides table
2700      * @param cols total number of columns of the diurnal tides table
2701      * @param rIp column holding ∆Rf(ip) in the diurnal tides table, counting from 1
2702      * @param rOp column holding ∆Rf(op) in the diurnal tides table, counting from 1
2703      * @param tIp column holding ∆Tf(ip) in the diurnal tides table, counting from 1
2704      * @param tOp column holding ∆Tf(op) in the diurnal tides table, counting from 1
2705      * @return correction function for tidal displacement for diurnal tides
2706           * @since 9.1
2707      */
2708     protected static CompiledSeries getTidalDisplacementFrequencyCorrectionDiurnal(final String tableName, final int cols,
2709                                                                                    final int rIp, final int rOp,
2710                                                                                    final int tIp, final int tOp) {
2711 
2712         // radial component, missing the sin 2φ factor; this corresponds to:
2713         //  - equation 15a in IERS conventions 1996, chapter 7
2714         //  - equation 16a in IERS conventions 2003, chapter 7
2715         //  - equation 7.12a in IERS conventions 2010, chapter 7
2716         final PoissonSeries drCos = load(tableName, in -> new PoissonSeriesParser(cols).
2717                                                           withOptionalColumn(1).
2718                                                           withDoodson(4, 3).
2719                                                           withFirstDelaunay(10).
2720                                                           withSinCos(0, rIp, +1.0e-3, rOp, +1.0e-3).
2721                                                           parse(in, tableName));
2722         final PoissonSeries drSin = load(tableName, in -> new PoissonSeriesParser(cols).
2723                                                           withOptionalColumn(1).
2724                                                           withDoodson(4, 3).
2725                                                           withFirstDelaunay(10).
2726                                                           withSinCos(0, rOp, -1.0e-3, rIp, +1.0e-3).
2727                                                           parse(in, tableName));
2728 
2729         // North component, missing the cos 2φ factor; this corresponds to:
2730         //  - equation 15b in IERS conventions 1996, chapter 7
2731         //  - equation 16b in IERS conventions 2003, chapter 7
2732         //  - equation 7.12b in IERS conventions 2010, chapter 7
2733         final PoissonSeries dnCos = load(tableName, in -> new PoissonSeriesParser(cols).
2734                                                           withOptionalColumn(1).
2735                                                           withDoodson(4, 3).
2736                                                           withFirstDelaunay(10).
2737                                                           withSinCos(0, tIp, +1.0e-3, tOp, +1.0e-3).
2738                                                           parse(in, tableName));
2739         final PoissonSeries dnSin = load(tableName, in -> new PoissonSeriesParser(cols).
2740                                                           withOptionalColumn(1).
2741                                                           withDoodson(4, 3).
2742                                                           withFirstDelaunay(10).
2743                                                           withSinCos(0, tOp, -1.0e-3, tIp, +1.0e-3).
2744                                                           parse(in, tableName));
2745 
2746         // East component, missing the sin φ factor; this corresponds to:
2747         //  - equation 15b in IERS conventions 1996, chapter 7
2748         //  - equation 16b in IERS conventions 2003, chapter 7
2749         //  - equation 7.12b in IERS conventions 2010, chapter 7
2750         final PoissonSeries deCos = load(tableName, in -> new PoissonSeriesParser(cols).
2751                                                           withOptionalColumn(1).
2752                                                           withDoodson(4, 3).
2753                                                           withFirstDelaunay(10).
2754                                                           withSinCos(0, tOp, -1.0e-3, tIp, +1.0e-3).
2755                                                           parse(in, tableName));
2756         final PoissonSeries deSin = load(tableName, in -> new PoissonSeriesParser(cols).
2757                                                           withOptionalColumn(1).
2758                                                           withDoodson(4, 3).
2759                                                           withFirstDelaunay(10).
2760                                                           withSinCos(0, tIp, -1.0e-3, tOp, -1.0e-3).
2761                                                           parse(in, tableName));
2762 
2763         return PoissonSeries.compile(drCos, drSin, dnCos, dnSin, deCos, deSin);
2764 
2765     }
2766 
2767     /** Get the correction function for tidal displacement for zonal tides.
2768      * <ul>
2769      *  <li>f[0]: radial correction</li>
2770      *  <li>f[1]: North correction</li>
2771      * </ul>
2772      * @return correction function for tidal displacement
2773      * @since 9.1
2774      */
2775     public abstract CompiledSeries getTidalDisplacementFrequencyCorrectionZonal();
2776 
2777     /** Get the correction function for tidal displacement for zonal tides.
2778      * <ul>
2779      *  <li>f[0]: radial correction</li>
2780      *  <li>f[1]: North correction</li>
2781      * </ul>
2782      * @param tableName name for the zonal tides table
2783      * @param cols total number of columns of the table
2784      * @param rIp column holding ∆Rf(ip) in the table, counting from 1
2785      * @param rOp column holding ∆Rf(op) in the table, counting from 1
2786      * @param tIp column holding ∆Tf(ip) in the table, counting from 1
2787      * @param tOp column holding ∆Tf(op) in the table, counting from 1
2788      * @return correction function for tidal displacement for zonal tides
2789           * @since 9.1
2790      */
2791     protected static CompiledSeries getTidalDisplacementFrequencyCorrectionZonal(final String tableName, final int cols,
2792                                                                                  final int rIp, final int rOp,
2793                                                                                  final int tIp, final int tOp) {
2794 
2795         // radial component, missing the 3⁄2 sin² φ - 1⁄2 factor; this corresponds to:
2796         //  - equation 16a in IERS conventions 1996, chapter 7
2797         //  - equation 17a in IERS conventions 2003, chapter 7
2798         //  - equation 7.13a in IERS conventions 2010, chapter 7
2799         final PoissonSeries dr = load(tableName, in -> new PoissonSeriesParser(cols).
2800                                                        withOptionalColumn(1).
2801                                                        withDoodson(4, 3).
2802                                                        withFirstDelaunay(10).
2803                                                        withSinCos(0, rOp, +1.0e-3, rIp, +1.0e-3).
2804                                                        parse(in, tableName));
2805 
2806         // North component, missing the sin 2φ factor; this corresponds to:
2807         //  - equation 16b in IERS conventions 1996, chapter 7
2808         //  - equation 17b in IERS conventions 2003, chapter 7
2809         //  - equation 7.13b in IERS conventions 2010, chapter 7
2810         final PoissonSeries dn = load(tableName, in -> new PoissonSeriesParser(cols).
2811                                                        withOptionalColumn(1).
2812                                                        withDoodson(4, 3).
2813                                                        withFirstDelaunay(10).
2814                                                        withSinCos(0, tOp, +1.0e-3, tIp, +1.0e-3).
2815                                                        parse(in, tableName));
2816 
2817         return PoissonSeries.compile(dr, dn);
2818 
2819     }
2820 
2821     /** Interface for functions converting nutation corrections between
2822      * δΔψ/δΔε to δX/δY.
2823      * <ul>
2824      * <li>δΔψ/δΔε nutation corrections are used with the equinox-based paradigm.</li>
2825      * <li>δX/δY nutation corrections are used with the Non-Rotating Origin paradigm.</li>
2826      * </ul>
2827      * @since 6.1
2828      */
2829     public interface NutationCorrectionConverter {
2830 
2831         /** Convert nutation corrections.
2832          * @param date current date
2833          * @param ddPsi δΔψ part of the nutation correction
2834          * @param ddEpsilon δΔε part of the nutation correction
2835          * @return array containing δX and δY
2836          */
2837         double[] toNonRotating(AbsoluteDate date, double ddPsi, double ddEpsilon);
2838 
2839         /** Convert nutation corrections.
2840          * @param date current date
2841          * @param dX δX part of the nutation correction
2842          * @param dY δY part of the nutation correction
2843          * @return array containing δΔψ and δΔε
2844          */
2845         double[] toEquinox(AbsoluteDate date, double dX, double dY);
2846 
2847     }
2848 
2849     /** Create a function converting nutation corrections between
2850      * δX/δY and δΔψ/δΔε.
2851      * <ul>
2852      * <li>δX/δY nutation corrections are used with the Non-Rotating Origin paradigm.</li>
2853      * <li>δΔψ/δΔε nutation corrections are used with the equinox-based paradigm.</li>
2854      * </ul>
2855      *
2856      * <p>This method uses the {@link DataContext#getDefault() default data context}.
2857      *
2858      * @return a new converter
2859      * @since 6.1
2860      * @see #getNutationCorrectionConverter(TimeScales)
2861      */
2862     @DefaultDataContext
2863     public NutationCorrectionConverter getNutationCorrectionConverter() {
2864         return getNutationCorrectionConverter(DataContext.getDefault().getTimeScales());
2865     }
2866 
2867     /** Create a function converting nutation corrections between
2868      * δX/δY and δΔψ/δΔε.
2869      * <ul>
2870      * <li>δX/δY nutation corrections are used with the Non-Rotating Origin paradigm.</li>
2871      * <li>δΔψ/δΔε nutation corrections are used with the equinox-based paradigm.</li>
2872      * </ul>
2873      * @return a new converter
2874      * @since 10.1
2875      * @param timeScales used to define the conversion.
2876      */
2877     public NutationCorrectionConverter getNutationCorrectionConverter(
2878             final TimeScales timeScales) {
2879 
2880         // get models parameters
2881         final TimeVectorFunction precessionFunction = getPrecessionFunction(timeScales);
2882         final TimeScalarFunction epsilonAFunction = getMeanObliquityFunction(timeScales);
2883         final AbsoluteDate date0 = getNutationReferenceEpoch(timeScales);
2884         final double cosE0 = FastMath.cos(epsilonAFunction.value(date0));
2885 
2886         return new NutationCorrectionConverter() {
2887 
2888             /** {@inheritDoc} */
2889             @Override
2890             public double[] toNonRotating(final AbsoluteDate date,
2891                                           final double ddPsi, final double ddEpsilon) {
2892                 // compute precession angles psiA, omegaA and chiA
2893                 final double[] angles = precessionFunction.value(date);
2894 
2895                 // conversion coefficients
2896                 final double sinEA = FastMath.sin(epsilonAFunction.value(date));
2897                 final double c     = angles[0] * cosE0 - angles[2];
2898 
2899                 // convert nutation corrections (equation 23/IERS-2003 or 5.25/IERS-2010)
2900                 return new double[] {
2901                     sinEA * ddPsi + c * ddEpsilon,
2902                     ddEpsilon - c * sinEA * ddPsi
2903                 };
2904 
2905             }
2906 
2907             /** {@inheritDoc} */
2908             @Override
2909             public double[] toEquinox(final AbsoluteDate date,
2910                                       final double dX, final double dY) {
2911                 // compute precession angles psiA, omegaA and chiA
2912                 final double[] angles   = precessionFunction.value(date);
2913 
2914                 // conversion coefficients
2915                 final double sinEA = FastMath.sin(epsilonAFunction.value(date));
2916                 final double c     = angles[0] * cosE0 - angles[2];
2917                 final double opC2  = 1 + c * c;
2918 
2919                 // convert nutation corrections (inverse of equation 23/IERS-2003 or 5.25/IERS-2010)
2920                 return new double[] {
2921                     (dX - c * dY) / (sinEA * opC2),
2922                     (dY + c * dX) / opC2
2923                 };
2924             }
2925 
2926         };
2927 
2928     }
2929 
2930     /** Load the Love numbers.
2931      * @param nameLove name of the Love number resource
2932      * @return Love numbers
2933      */
2934     protected LoveNumbers loadLoveNumbers(final String nameLove) {
2935         try {
2936 
2937             // allocate the three triangular arrays for real, imaginary and time-dependent numbers
2938             final double[][] real      = new double[4][];
2939             final double[][] imaginary = new double[4][];
2940             final double[][] plus      = new double[4][];
2941             for (int i = 0; i < real.length; ++i) {
2942                 real[i]      = new double[i + 1];
2943                 imaginary[i] = new double[i + 1];
2944                 plus[i]      = new double[i + 1];
2945             }
2946 
2947             try (InputStream stream = IERSConventions.class.getResourceAsStream(nameLove)) {
2948 
2949                 if (stream == null) {
2950                     // this should never happen with files embedded within Orekit
2951                     throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, nameLove);
2952                 }
2953 
2954                 int lineNumber = 1;
2955                 String line = null;
2956                 // setup the reader
2957                 try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
2958 
2959                     line = reader.readLine();
2960 
2961                     // look for the Love numbers
2962                     while (line != null) {
2963 
2964                         line = line.trim();
2965                         if (!(line.isEmpty() || line.startsWith("#"))) {
2966                             final String[] fields = SEPARATOR.split(line);
2967                             if (fields.length != 5) {
2968                                 // this should never happen with files embedded within Orekit
2969                                 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
2970                                                           lineNumber, nameLove, line);
2971                             }
2972                             final int n = Integer.parseInt(fields[0]);
2973                             final int m = Integer.parseInt(fields[1]);
2974                             if (n < 2 || n > 3 || m < 0 || m > n) {
2975                                 // this should never happen with files embedded within Orekit
2976                                 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
2977                                                           lineNumber, nameLove, line);
2978 
2979                             }
2980                             real[n][m]      = Double.parseDouble(fields[2]);
2981                             imaginary[n][m] = Double.parseDouble(fields[3]);
2982                             plus[n][m]      = Double.parseDouble(fields[4]);
2983                         }
2984 
2985                         // next line
2986                         lineNumber++;
2987                         line = reader.readLine();
2988 
2989                     }
2990                 } catch (NumberFormatException nfe) {
2991                     // this should never happen with files embedded within Orekit
2992                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
2993                                               lineNumber, nameLove, line);
2994                 }
2995             }
2996 
2997             return new LoveNumbers(real, imaginary, plus);
2998 
2999         } catch (IOException ioe) {
3000             // this should never happen with files embedded within Orekit
3001             throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_IERS_DATA_FILE, nameLove);
3002         }
3003     }
3004 
3005     /** Load resources.
3006      * @param name name of the resource
3007      * @param loader loaader for the resource
3008      * @param <T> type of the processed data
3009      * @return processed data
3010      */
3011     private static <T> T load(final String name, final Function<InputStream, T> loader) {
3012         try (InputStream is = IERSConventions.class.getResourceAsStream(name)) {
3013             return loader.apply(is);
3014         } catch (IOException ioe) {
3015             // this should never happen with internal streams
3016             throw new OrekitException(OrekitMessages.INTERNAL_ERROR, ioe);
3017         }
3018     }
3019 
3020     /** Correction to equation of equinoxes.
3021      * <p>IAU 1994 resolution C7 added two terms to the equation of equinoxes
3022      * taking effect since 1997-02-27 for continuity
3023      * </p>
3024      */
3025     private static class IAU1994ResolutionC7 {
3026 
3027         /** First Moon correction term for the Equation of the Equinoxes. */
3028         private static final double EQE1 =     0.00264  * Constants.ARC_SECONDS_TO_RADIANS;
3029 
3030         /** Second Moon correction term for the Equation of the Equinoxes. */
3031         private static final double EQE2 =     0.000063 * Constants.ARC_SECONDS_TO_RADIANS;
3032 
3033 
3034         /** Evaluate the correction.
3035          * @param arguments Delaunay for nutation
3036          * @param tai TAI time scale.
3037          * @return correction value (0 before 1997-02-27)
3038          */
3039         public static double value(final DelaunayArguments arguments,
3040                                    final TimeScale tai) {
3041             /* Start date for applying Moon corrections to the equation of the equinoxes.
3042              * This date corresponds to 1997-02-27T00:00:00 UTC, hence the 30s offset from TAI.
3043              */
3044             final AbsoluteDate modelStart = new AbsoluteDate(1997, 2, 27, 0, 0, 30, tai);
3045             if (arguments.getDate().compareTo(modelStart) >= 0) {
3046 
3047                 // IAU 1994 resolution C7 added two terms to the equation of equinoxes
3048                 // taking effect since 1997-02-27 for continuity
3049 
3050                 // Mean longitude of the ascending node of the Moon
3051                 final double om = arguments.getOmega();
3052 
3053                 // add the two correction terms
3054                 return EQE1 * FastMath.sin(om) + EQE2 * FastMath.sin(om + om);
3055 
3056             } else {
3057                 return 0.0;
3058             }
3059         }
3060 
3061         /** Evaluate the correction.
3062          * @param arguments Delaunay for nutation
3063          * @param tai TAI time scale.
3064          * @param <T> type of the field elements
3065          * @return correction value (0 before 1997-02-27)
3066          */
3067         public static <T extends CalculusFieldElement<T>> T value(
3068                 final FieldDelaunayArguments<T> arguments,
3069                 final TimeScale tai) {
3070             /* Start date for applying Moon corrections to the equation of the equinoxes.
3071              * This date corresponds to 1997-02-27T00:00:00 UTC, hence the 30s offset from TAI.
3072              */
3073             final AbsoluteDate modelStart = new AbsoluteDate(1997, 2, 27, 0, 0, 30, tai);
3074             if (arguments.getDate().toAbsoluteDate().compareTo(modelStart) >= 0) {
3075 
3076                 // IAU 1994 resolution C7 added two terms to the equation of equinoxes
3077                 // taking effect since 1997-02-27 for continuity
3078 
3079                 // Mean longitude of the ascending node of the Moon
3080                 final T om = arguments.getOmega();
3081 
3082                 // add the two correction terms
3083                 return om.sin().multiply(EQE1).add(om.add(om).sin().multiply(EQE2));
3084 
3085             } else {
3086                 return arguments.getDate().getField().getZero();
3087             }
3088         }
3089 
3090     }
3091 
3092     /** Stellar angle model.
3093      * <p>
3094      * The stellar angle computed here has been defined in the paper "A non-rotating origin on the
3095      * instantaneous equator: Definition, properties and use", N. Capitaine, Guinot B., and Souchay J.,
3096      * Celestial Mechanics, Volume 39, Issue 3, pp 283-307. It has been proposed as a conventional
3097      * conventional relationship between UT1 and Earth rotation in the paper "Definition of the Celestial
3098      * Ephemeris origin and of UT1 in the International Celestial Reference Frame", Capitaine, N.,
3099      * Guinot, B., and McCarthy, D. D., 2000, Astronomy and Astrophysics, 355(1), pp. 398–405.
3100      * </p>
3101      * <p>
3102      * It is presented simply as stellar angle in IERS conventions 1996 but as since been adopted as
3103      * the conventional relationship defining UT1 from ICRF and is called Earth Rotation Angle in
3104      * IERS conventions 2003 and 2010.
3105      * </p>
3106      */
3107     private static class StellarAngleCapitaine implements TimeScalarFunction {
3108 
3109         /** Constant term of Capitaine's Earth Rotation Angle model. */
3110         private static final double ERA_0   = MathUtils.TWO_PI * 0.7790572732640;
3111 
3112         /** Rate term of Capitaine's Earth Rotation Angle model.
3113          * (radians per day, main part) */
3114         private static final double ERA_1A  = MathUtils.TWO_PI / Constants.JULIAN_DAY;
3115 
3116         /** Rate term of Capitaine's Earth Rotation Angle model.
3117          * (radians per day, fractional part) */
3118         private static final double ERA_1B  = ERA_1A * 0.00273781191135448;
3119 
3120         /** UT1 time scale. */
3121         private final TimeScale ut1;
3122 
3123         /** Reference date of Capitaine's Earth Rotation Angle model. */
3124         private final AbsoluteDate referenceDate;
3125 
3126         /** Simple constructor.
3127          * @param ut1 UT1 time scale
3128          * @param tai TAI time scale
3129          */
3130         StellarAngleCapitaine(final TimeScale ut1, final TimeScale tai) {
3131             this.ut1 = ut1;
3132             referenceDate = new AbsoluteDate(
3133                     DateComponents.J2000_EPOCH,
3134                     TimeComponents.H12,
3135                     tai);
3136         }
3137 
3138         /** Get the rotation rate.
3139          * @return rotation rate
3140          */
3141         public double getRate() {
3142             return ERA_1A + ERA_1B;
3143         }
3144 
3145         /** {@inheritDoc} */
3146         @Override
3147         public double value(final AbsoluteDate date) {
3148 
3149             // split the date offset as a full number of days plus a smaller part
3150             final int secondsInDay = 86400;
3151             final double dt  = date.durationFrom(referenceDate);
3152             final long days  = ((long) dt) / secondsInDay;
3153             final double dtA = secondsInDay * days;
3154             final double dtB = (dt - dtA) + ut1.offsetFromTAI(date).toDouble();
3155 
3156             return ERA_0 + ERA_1A * dtB + ERA_1B * (dtA + dtB);
3157 
3158         }
3159 
3160         /** {@inheritDoc} */
3161         @Override
3162         public <T extends CalculusFieldElement<T>> T value(final FieldAbsoluteDate<T> date) {
3163 
3164             // split the date offset as a full number of days plus a smaller part
3165             final int secondsInDay = 86400;
3166             final T dt  = date.durationFrom(referenceDate);
3167             final long days  = ((long) dt.getReal()) / secondsInDay;
3168             final double dtA = secondsInDay * days;
3169             final T dtB = dt.subtract(dtA).add(ut1.offsetFromTAI(date.toAbsoluteDate()).toDouble());
3170 
3171             return dtB.add(dtA).multiply(ERA_1B).add(dtB.multiply(ERA_1A)).add(ERA_0);
3172 
3173         }
3174 
3175     }
3176 
3177     /** Mean pole. */
3178     private static class MeanPole implements TimeStamped, Serializable {
3179 
3180         /** Serializable UID. */
3181         @Serial
3182         private static final long serialVersionUID = 20131028L;
3183 
3184         /** Date. */
3185         private final AbsoluteDate date;
3186 
3187         /** X coordinate. */
3188         private final double x;
3189 
3190         /** Y coordinate. */
3191         private final double y;
3192 
3193         /** Simple constructor.
3194          * @param date date
3195          * @param x x coordinate
3196          * @param y y coordinate
3197          */
3198         MeanPole(final AbsoluteDate date, final double x, final double y) {
3199             this.date = date;
3200             this.x    = x;
3201             this.y    = y;
3202         }
3203 
3204         /** {@inheritDoc} */
3205         @Override
3206         public AbsoluteDate getDate() {
3207             return date;
3208         }
3209 
3210         /** Get x coordinate.
3211          * @return x coordinate
3212          */
3213         public double getX() {
3214             return x;
3215         }
3216 
3217         /** Get y coordinate.
3218          * @return y coordinate
3219          */
3220         public double getY() {
3221             return y;
3222         }
3223 
3224     }
3225 
3226     /** Local class for precession function. */
3227     private class PrecessionFunction implements TimeVectorFunction {
3228 
3229         /** Polynomial nutation for psiA. */
3230         private final PolynomialNutation psiA;
3231 
3232         /** Polynomial nutation for omegaA. */
3233         private final PolynomialNutation omegaA;
3234 
3235         /** Polynomial nutation for chiA. */
3236         private final PolynomialNutation chiA;
3237 
3238         /** Time scales to use. */
3239         private final TimeScales timeScales;
3240 
3241         /** Simple constructor.
3242          * @param psiA polynomial nutation for psiA
3243          * @param omegaA polynomial nutation for omegaA
3244          * @param chiA polynomial nutation for chiA
3245          * @param timeScales used in the computation.
3246          */
3247         PrecessionFunction(final PolynomialNutation psiA,
3248                            final PolynomialNutation omegaA,
3249                            final PolynomialNutation chiA,
3250                            final TimeScales timeScales) {
3251             this.psiA   = psiA;
3252             this.omegaA = omegaA;
3253             this.chiA   = chiA;
3254             this.timeScales = timeScales;
3255         }
3256 
3257 
3258         /** {@inheritDoc} */
3259         @Override
3260         public double[] value(final AbsoluteDate date) {
3261             final double tc = evaluateTC(date, timeScales);
3262             return new double[] {
3263                 psiA.value(tc), omegaA.value(tc), chiA.value(tc)
3264             };
3265         }
3266 
3267         /** {@inheritDoc} */
3268         @Override
3269         public <T extends CalculusFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
3270             final T[] a = MathArrays.buildArray(date.getField(), 3);
3271             final T tc = evaluateTC(date, timeScales);
3272             a[0] = psiA.value(tc);
3273             a[1] = omegaA.value(tc);
3274             a[2] = chiA.value(tc);
3275             return a;
3276         }
3277 
3278     }
3279 
3280     /** Local class for tides frequency function. */
3281     private static class TideFrequencyDependenceFunction implements TimeVectorFunction {
3282 
3283         /** Nutation arguments. */
3284         private final FundamentalNutationArguments arguments;
3285 
3286         /** Correction series. */
3287         private final PoissonSeries.CompiledSeries kSeries;
3288 
3289         /** Simple constructor.
3290          * @param arguments nutation arguments
3291          * @param c20Series correction series for the C20 term
3292          * @param c21Series correction series for the C21 term
3293          * @param s21Series correction series for the S21 term
3294          * @param c22Series correction series for the C22 term
3295          * @param s22Series correction series for the S22 term
3296          */
3297         TideFrequencyDependenceFunction(final FundamentalNutationArguments arguments,
3298                                         final PoissonSeries c20Series,
3299                                         final PoissonSeries c21Series,
3300                                         final PoissonSeries s21Series,
3301                                         final PoissonSeries c22Series,
3302                                         final PoissonSeries s22Series) {
3303             this.arguments = arguments;
3304             this.kSeries   = PoissonSeries.compile(c20Series, c21Series, s21Series, c22Series, s22Series);
3305         }
3306 
3307 
3308         /** {@inheritDoc} */
3309         @Override
3310         public double[] value(final AbsoluteDate date) {
3311             return kSeries.value(arguments.evaluateAll(date));
3312         }
3313 
3314         /** {@inheritDoc} */
3315         @Override
3316         public <T extends CalculusFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
3317             return kSeries.value(arguments.evaluateAll(date));
3318         }
3319 
3320     }
3321 
3322     /** Local class for EOP tidal corrections. */
3323     private static class EOPTidalCorrection implements TimeVectorFunction {
3324 
3325         /** Nutation arguments. */
3326         private final FundamentalNutationArguments arguments;
3327 
3328         /** Correction series. */
3329         private final PoissonSeries.CompiledSeries correctionSeries;
3330 
3331         /** Simple constructor.
3332          * @param arguments nutation arguments
3333          * @param xSeries correction series for the x coordinate
3334          * @param ySeries correction series for the y coordinate
3335          * @param ut1Series correction series for the UT1
3336          */
3337         EOPTidalCorrection(final FundamentalNutationArguments arguments,
3338                            final PoissonSeries xSeries,
3339                            final PoissonSeries ySeries,
3340                            final PoissonSeries ut1Series) {
3341             this.arguments        = arguments;
3342             this.correctionSeries = PoissonSeries.compile(xSeries, ySeries, ut1Series);
3343         }
3344 
3345         /** {@inheritDoc} */
3346         @Override
3347         public double[] value(final AbsoluteDate date) {
3348             final BodiesElements elements = arguments.evaluateAll(date);
3349             final double[] correction    = correctionSeries.value(elements);
3350             final double[] correctionDot = correctionSeries.derivative(elements);
3351             return new double[] {
3352                 correction[0],
3353                 correction[1],
3354                 correction[2],
3355                 correctionDot[2] * (-Constants.JULIAN_DAY)
3356             };
3357         }
3358 
3359         /** {@inheritDoc} */
3360         @Override
3361         public <T extends CalculusFieldElement<T>> T[] value(final FieldAbsoluteDate<T> date) {
3362 
3363             final FieldBodiesElements<T> elements = arguments.evaluateAll(date);
3364             final T[] correction    = correctionSeries.value(elements);
3365             final T[] correctionDot = correctionSeries.derivative(elements);
3366             final T[] a = MathArrays.buildArray(date.getField(), 4);
3367             a[0] = correction[0];
3368             a[1] = correction[1];
3369             a[2] = correction[2];
3370             a[3] = correctionDot[2].multiply(-Constants.JULIAN_DAY);
3371             return a;
3372         }
3373 
3374     }
3375 
3376 }