1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.orekit.time;
19  
20  import org.junit.jupiter.api.Assertions;
21  import org.junit.jupiter.api.DisplayName;
22  import org.junit.jupiter.api.RepeatedTest;
23  import org.junit.jupiter.api.Test;
24  
25  import java.util.ArrayList;
26  import java.util.List;
27  import java.util.concurrent.Callable;
28  import java.util.concurrent.ExecutorService;
29  import java.util.concurrent.Executors;
30  import java.util.concurrent.TimeUnit;
31  import java.util.concurrent.atomic.DoubleAccumulator;
32  
33  class TimeStampedDoubleAndDerivativeHermiteInterpolatorTest {
34  
35      @Test
36      @DisplayName("Test default constructor")
37      void testDefaultConstructor() {
38          // When
39          final TimeStampedDoubleAndDerivativeHermiteInterpolator interpolator = new TimeStampedDoubleAndDerivativeHermiteInterpolator();
40  
41          // Then
42          Assertions.assertEquals(AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
43                                  interpolator.getNbInterpolationPoints());
44          Assertions.assertEquals(AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC,
45                                  interpolator.getExtrapolationThreshold());
46      }
47  
48      @RepeatedTest(10)
49      @DisplayName("test interpolator in multi-threaded environment")
50      void testIssue1164() throws InterruptedException {
51          // GIVEN
52          // Create interpolator
53          final TimeInterpolator<TimeStampedDoubleAndDerivative> interpolator = new TimeStampedDoubleAndDerivativeHermiteInterpolator();
54  
55          // Create sample and interpolation dates
56          final int                                  sampleSize  = 100;
57          final AbsoluteDate                         initialDate = new AbsoluteDate();
58          final List<TimeStampedDoubleAndDerivative> sample      = new ArrayList<>();
59          final List<AbsoluteDate>                   dates       = new ArrayList<>();
60          for (int i = 1; i < sampleSize + 1; i++) {
61              sample.add(new TimeStampedDoubleAndDerivative(i * i, i / 30.0,
62                                                            initialDate.shiftedBy(i * 60)));
63              dates.add(initialDate.shiftedBy(i * 60));
64          }
65  
66          // Create multithreading environment
67          ExecutorService service = Executors.newFixedThreadPool(sampleSize);
68  
69          final DoubleAccumulator sum0  = new DoubleAccumulator(Double::sum, 0.0);
70          final DoubleAccumulator sum1  = new DoubleAccumulator(Double::sum, 0.0);
71          final List<Callable<Integer>> tasks = new ArrayList<>();
72          for (final AbsoluteDate date : dates) {
73              tasks.add(new ParallelTask(interpolator, sum0, sum1, sample, date));
74          }
75  
76          // WHEN
77          service.invokeAll(tasks);
78  
79          // THEN
80          // Sum of 1*1 + 2*2 + 3*3 + ...
81          // Sum of 1 / 30 + 2 / 30 + ...
82          final double expectedSum0 = sampleSize * (sampleSize + 1) * (2 * sampleSize + 1) / 6.0;
83          final double expectedSum1 = sampleSize * (sampleSize + 1)  / 60.0;
84          try {
85              // wait for proper ending
86              service.shutdown();
87              Assertions.assertTrue(service.awaitTermination(5, TimeUnit.SECONDS));
88          } catch (InterruptedException ie) {
89              // Restore interrupted state...
90              Thread.currentThread().interrupt();
91          }
92          Assertions.assertEquals(expectedSum0, sum0.get(), 1.0e-12);
93          Assertions.assertEquals(expectedSum1, sum1.get(), 1.0e-12);
94      }
95  
96      /** Custom class for multi threading testing purpose */
97      private static class ParallelTask implements Callable<Integer> {
98  
99          private final TimeInterpolator<TimeStampedDoubleAndDerivative> interpolator;
100 
101         private final List<TimeStampedDoubleAndDerivative> sample;
102 
103         private final DoubleAccumulator sum0;
104 
105         private final DoubleAccumulator sum1;
106 
107         private final AbsoluteDate interpolationDate;
108 
109         private ParallelTask(final TimeInterpolator<TimeStampedDoubleAndDerivative> interpolator,
110                              final DoubleAccumulator sum0, final DoubleAccumulator sum1,
111                              final List<TimeStampedDoubleAndDerivative> sample,
112                              final AbsoluteDate interpolationDate) {
113             // Store interpolator
114             this.interpolator      = interpolator;
115             this.sum0              = sum0;
116             this.sum1              = sum1;
117             this.interpolationDate = interpolationDate;
118             this.sample            = sample;
119         }
120 
121         @Override
122         public Integer call() {
123             // Add result to sums
124             final TimeStampedDoubleAndDerivative interpolated = interpolator.interpolate(interpolationDate, sample);
125             sum0.accumulate(interpolated.getValue());
126             sum1.accumulate(interpolated.getDerivative());
127             return 1;
128         }
129     }
130 }