/* Copyright 2002-2017 CS Systèmes d'Information
* Licensed to CS Systèmes d'Information (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.hipparchus.random.RandomGenerator;
import org.hipparchus.random.Well1024a;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.orekit.Utils;
import org.orekit.errors.TimeStampedCacheException;
import org.orekit.time.AbsoluteDate;
public class GenericTimeStampedCacheTest {
@Test
public void testSingleCall() throws TimeStampedCacheException {
GenericTimeStampedCache<AbsoluteDate> cache = createCache(10, 3600.0, 13);
List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
list.add(AbsoluteDate.GALILEO_EPOCH);
Assert.assertEquals(1, checkDatesSingleThread(list, cache));
Assert.assertEquals(1, cache.getGetNeighborsCalls());
Assert.assertEquals(4, cache.getGenerateCalls());
Assert.assertEquals(0, cache.getSlotsEvictions());
Assert.assertEquals(10, cache.getMaxSlots());
Assert.assertEquals(Constants.JULIAN_DAY, cache.getNewSlotQuantumGap(), 1.0e-10);
Assert.assertEquals(Constants.JULIAN_YEAR, cache.getMaxSpan(), 1.0e-10);
}
@Test
public void testPastInfinityRange() throws TimeStampedCacheException {
GenericTimeStampedCache<AbsoluteDate> cache =
new GenericTimeStampedCache<AbsoluteDate>(2, 10, Constants.JULIAN_YEAR, Constants.JULIAN_DAY,
new Generator(AbsoluteDate.PAST_INFINITY,
AbsoluteDate.J2000_EPOCH,
10.0));
List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
list.add(AbsoluteDate.GALILEO_EPOCH);
list.add(AbsoluteDate.MODIFIED_JULIAN_EPOCH);
list.add(AbsoluteDate.JULIAN_EPOCH);
Assert.assertEquals(3, checkDatesSingleThread(list, cache));
Assert.assertEquals(3, cache.getGetNeighborsCalls());
try {
cache.getNeighbors(AbsoluteDate.J2000_EPOCH.shiftedBy(100.0));
Assert.fail("expected TimeStampedCacheException");
} catch (TimeStampedCacheException tce) {
// expected behavior
} catch (Exception e) {
Assert.fail("wrong exception caught");
}
}
@Test
public void testFutureInfinityRange() throws TimeStampedCacheException {
GenericTimeStampedCache<AbsoluteDate> cache =
new GenericTimeStampedCache<AbsoluteDate>(2, 10, Constants.JULIAN_YEAR, Constants.JULIAN_DAY,
new Generator(AbsoluteDate.MODIFIED_JULIAN_EPOCH,
AbsoluteDate.FUTURE_INFINITY, 10.0));
List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
list.add(AbsoluteDate.J2000_EPOCH);
list.add(AbsoluteDate.GALILEO_EPOCH);
Assert.assertEquals(2, checkDatesSingleThread(list, cache));
Assert.assertEquals(2, cache.getGetNeighborsCalls());
try {
cache.getNeighbors(AbsoluteDate.JULIAN_EPOCH);
Assert.fail("expected TimeStampedCacheException");
} catch (TimeStampedCacheException tce) {
// expected behavior
} catch (Exception e) {
Assert.fail("wrong exception caught");
}
}
@Test
public void testInfinityRange() throws TimeStampedCacheException {
GenericTimeStampedCache<AbsoluteDate> cache =
new GenericTimeStampedCache<AbsoluteDate>(2, 10, Constants.JULIAN_YEAR, Constants.JULIAN_DAY,
new Generator(AbsoluteDate.PAST_INFINITY,
AbsoluteDate.FUTURE_INFINITY,
10.0));
List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
list.add(AbsoluteDate.J2000_EPOCH.shiftedBy(+4.6e12));
list.add(AbsoluteDate.J2000_EPOCH.shiftedBy(-4.6e12));
list.add(AbsoluteDate.JULIAN_EPOCH);
list.add(AbsoluteDate.J2000_EPOCH);
list.add(AbsoluteDate.GALILEO_EPOCH);
Assert.assertEquals(5, checkDatesSingleThread(list, cache));
Assert.assertEquals(5, cache.getGetNeighborsCalls());
}
@Test
public void testRegularCalls() throws TimeStampedCacheException {
GenericTimeStampedCache<AbsoluteDate> cache = createCache(2, 3600, 13);
Assert.assertEquals(2000, testMultipleSingleThread(cache, new SequentialMode(), 2));
Assert.assertEquals(2000, cache.getGetNeighborsCalls());
Assert.assertEquals(56, cache.getGenerateCalls());
Assert.assertEquals(0, cache.getSlotsEvictions());
}
@Test
public void testAlternateCallsGoodConfiguration() throws TimeStampedCacheException {
GenericTimeStampedCache<AbsoluteDate> cache = createCache(2, 3600, 13);
Assert.assertEquals(2000, testMultipleSingleThread(cache, new AlternateMode(), 2));
Assert.assertEquals(2000, cache.getGetNeighborsCalls());
Assert.assertEquals(56, cache.getGenerateCalls());
Assert.assertEquals(0, cache.getSlotsEvictions());
}
@Test
public void testAlternateCallsBadConfiguration() throws TimeStampedCacheException {
GenericTimeStampedCache<AbsoluteDate> cache = createCache(1, 3600, 13);
Assert.assertEquals(2000, testMultipleSingleThread(cache, new AlternateMode(), 2));
Assert.assertEquals(2000, cache.getGetNeighborsCalls());
Assert.assertEquals(8000, cache.getGenerateCalls());
Assert.assertEquals(1999, cache.getSlotsEvictions());
}
@Test
public void testRandomCallsGoodConfiguration() throws TimeStampedCacheException {
GenericTimeStampedCache<AbsoluteDate> cache = createCache(30, 3600, 13);
Assert.assertEquals(5000, testMultipleSingleThread(cache, new RandomMode(64394632125212l), 5));
Assert.assertEquals(5000, cache.getGetNeighborsCalls());
Assert.assertTrue(cache.getGenerateCalls() < 250);
Assert.assertEquals(0, cache.getSlotsEvictions());
}
@Test
public void testRandomCallsBadConfiguration() throws TimeStampedCacheException {
GenericTimeStampedCache<AbsoluteDate> cache = createCache(3, 3600, 13);
Assert.assertEquals(5000, testMultipleSingleThread(cache, new RandomMode(64394632125212l), 5));
Assert.assertEquals(5000, cache.getGetNeighborsCalls());
Assert.assertTrue(cache.getGenerateCalls() > 400);
Assert.assertTrue(cache.getSlotsEvictions() > 300);
}
@Test
public void testMultithreadedGoodConfiguration() throws TimeStampedCacheException {
GenericTimeStampedCache<AbsoluteDate> cache = createCache(50, 3600, 13);
int n = testMultipleMultiThread(cache, new AlternateMode(), 50, 30);
Assert.assertEquals(n, cache.getGetNeighborsCalls());
Assert.assertTrue("this test may fail randomly due to multi-threading non-determinism" +
" (n = " + n + ", calls = " + cache.getGenerateCalls() +
", ratio = " + (n / cache.getGenerateCalls()) + ")",
cache.getGenerateCalls() < n / 20);
Assert.assertTrue("this test may fail randomly due to multi-threading non-determinism" +
" (n = " + n + ", evictions = " + cache.getSlotsEvictions() +
(cache.getSlotsEvictions() == 0 ? "" : (", ratio = " + (n / cache.getSlotsEvictions()))) + ")",
cache.getSlotsEvictions() < n / 1000);
}
@Test
public void testMultithreadedBadConfiguration() throws TimeStampedCacheException {
GenericTimeStampedCache<AbsoluteDate> cache = createCache(3, 3600, 13);
int n = testMultipleMultiThread(cache, new AlternateMode(), 50, 100);
Assert.assertEquals(n, cache.getGetNeighborsCalls());
Assert.assertTrue("this test may fail randomly due to multi-threading non-determinism" +
" (n = " + n + ", calls = " + cache.getGenerateCalls() +
", ratio = " + (n / cache.getGenerateCalls()) + ")",
cache.getGenerateCalls() > n / 15);
Assert.assertTrue("this test may fail randomly due to multi-threading non-determinism" +
" (n = " + n + ", evictions = " + cache.getSlotsEvictions() +
", ratio = " + (n / cache.getSlotsEvictions()) + ")",
cache.getSlotsEvictions() > n / 60);
}
@Test
public void testSmallShift() throws TimeStampedCacheException {
double hour = 3600;
GenericTimeStampedCache<AbsoluteDate> cache = createCache(10, hour, 13);
Assert.assertEquals(0, cache.getSlots());
Assert.assertEquals(0, cache.getEntries());
final AbsoluteDate start = AbsoluteDate.GALILEO_EPOCH;
cache.getNeighbors(start);
Assert.assertEquals(1, cache.getGetNeighborsCalls());
Assert.assertEquals(1, cache.getSlots());
Assert.assertEquals(18, cache.getEntries());
Assert.assertEquals(4, cache.getGenerateCalls());
Assert.assertEquals(-11 * hour, cache.getEarliest().durationFrom(start), 1.0e-10);
Assert.assertEquals( +6 * hour, cache.getLatest().durationFrom(start), 1.0e-10);
cache.getNeighbors(start.shiftedBy(-3 * 3600));
Assert.assertEquals(2, cache.getGetNeighborsCalls());
Assert.assertEquals(1, cache.getSlots());
Assert.assertEquals(18, cache.getEntries());
Assert.assertEquals(4, cache.getGenerateCalls());
Assert.assertEquals(-11 * hour, cache.getEarliest().durationFrom(start), 1.0e-10);
Assert.assertEquals( +6 * hour, cache.getLatest().durationFrom(start), 1.0e-10);
cache.getNeighbors(start.shiftedBy(7 * 3600));
Assert.assertEquals(3, cache.getGetNeighborsCalls());
Assert.assertEquals(1, cache.getSlots());
Assert.assertEquals(25, cache.getEntries());
Assert.assertEquals(5, cache.getGenerateCalls());
Assert.assertEquals(-11 * hour, cache.getEarliest().durationFrom(start), 1.0e-10);
Assert.assertEquals(+13 * hour, cache.getLatest().durationFrom(start), 1.0e-10);
}
@Test(expected=IllegalArgumentException.class)
public void testNotEnoughSlots() {
createCache(0, 3600.0, 13);
}
@Test(expected=IllegalArgumentException.class)
public void testNotEnoughNeighbors() {
createCache(10, 3600.0, 1);
}
@Test(expected=IllegalStateException.class)
public void testNoEarliestEntry() {
createCache(10, 3600.0, 3).getEarliest();
}
@Test(expected=IllegalStateException.class)
public void testNoLatestEntry() {
createCache(10, 3600.0, 3).getLatest();
}
@Test(expected=TimeStampedCacheException.class)
public void testNoGeneratedData() throws TimeStampedCacheException {
TimeStampedGenerator<AbsoluteDate> nullGenerator =
new TimeStampedGenerator<AbsoluteDate>() {
public List<AbsoluteDate> generate(AbsoluteDate existingDate,
AbsoluteDate date) {
return new ArrayList<AbsoluteDate>();
}
};
new GenericTimeStampedCache<AbsoluteDate>(2, 10, Constants.JULIAN_YEAR, Constants.JULIAN_DAY,
nullGenerator).getNeighbors(AbsoluteDate.J2000_EPOCH);
}
@Test(expected=TimeStampedCacheException.class)
public void testNoDataBefore() throws TimeStampedCacheException {
TimeStampedGenerator<AbsoluteDate> nullGenerator =
new TimeStampedGenerator<AbsoluteDate>() {
public List<AbsoluteDate> generate(AbsoluteDate existingDate,
AbsoluteDate date) {
return Arrays.asList(AbsoluteDate.J2000_EPOCH);
}
};
new GenericTimeStampedCache<AbsoluteDate>(2, 10, Constants.JULIAN_YEAR, Constants.JULIAN_DAY,
nullGenerator).getNeighbors(AbsoluteDate.J2000_EPOCH.shiftedBy(-10));
}
@Test(expected=TimeStampedCacheException.class)
public void testNoDataAfter() throws TimeStampedCacheException {
TimeStampedGenerator<AbsoluteDate> nullGenerator =
new TimeStampedGenerator<AbsoluteDate>() {
public List<AbsoluteDate> generate(AbsoluteDate existingDate,
AbsoluteDate date) {
return Arrays.asList(AbsoluteDate.J2000_EPOCH);
}
};
new GenericTimeStampedCache<AbsoluteDate>(2, 10, Constants.JULIAN_YEAR, Constants.JULIAN_DAY,
nullGenerator).getNeighbors(AbsoluteDate.J2000_EPOCH.shiftedBy(+10));
}
@Test(expected=TimeStampedCacheException.class)
public void testUnsortedEntries() throws TimeStampedCacheException {
TimeStampedGenerator<AbsoluteDate> reversedGenerator =
new TimeStampedGenerator<AbsoluteDate>() {
/** {@inheritDoc} */
public List<AbsoluteDate> generate(AbsoluteDate existingDate, AbsoluteDate date) {
List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
list.add(date);
list.add(date.shiftedBy(-10.0));
return list;
}
};
new GenericTimeStampedCache<AbsoluteDate>(3, 10, Constants.JULIAN_YEAR, Constants.JULIAN_DAY,
reversedGenerator).getNeighbors(AbsoluteDate.J2000_EPOCH);
}
@Test
public void testDuplicatingGenerator() throws TimeStampedCacheException {
final double step = 3600.0;
TimeStampedGenerator<AbsoluteDate> duplicatingGenerator =
new TimeStampedGenerator<AbsoluteDate>() {
/** {@inheritDoc} */
public List<AbsoluteDate> generate(AbsoluteDate existingDate, AbsoluteDate date) {
List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
if (existingDate == null) {
list.add(date);
} else {
if (date.compareTo(existingDate) > 0) {
AbsoluteDate t = existingDate.shiftedBy(-10 * step);
do {
t = t.shiftedBy(step);
list.add(list.size(), t);
} while (t.compareTo(date) <= 0);
} else {
AbsoluteDate t = existingDate.shiftedBy(10 * step);
do {
t = t.shiftedBy(-step);
list.add(0, t);
} while (t.compareTo(date) >= 0);
}
}
return list;
}
};
final GenericTimeStampedCache<AbsoluteDate> cache =
new GenericTimeStampedCache<AbsoluteDate>(5, 10, Constants.JULIAN_YEAR, Constants.JULIAN_DAY,
duplicatingGenerator);
final AbsoluteDate start = AbsoluteDate.GALILEO_EPOCH;
final List<AbsoluteDate> firstSet = cache.getNeighbors(start).collect(Collectors.toList());
Assert.assertEquals(5, firstSet.size());
Assert.assertEquals(4, cache.getGenerateCalls());
Assert.assertEquals(8, cache.getEntries());
for (int i = 1; i < firstSet.size(); ++i) {
Assert.assertEquals(step, firstSet.get(i).durationFrom(firstSet.get(i - 1)), 1.0e-10);
}
final List<AbsoluteDate> secondSet = cache.getNeighbors(cache.getLatest().shiftedBy(10 * step)).collect(Collectors.toList());
Assert.assertEquals(5, secondSet.size());
Assert.assertEquals(7, cache.getGenerateCalls());
Assert.assertEquals(20, cache.getEntries());
for (int i = 1; i < secondSet.size(); ++i) {
Assert.assertEquals(step, firstSet.get(i).durationFrom(firstSet.get(i - 1)), 1.0e-10);
}
}
private int testMultipleSingleThread(GenericTimeStampedCache<AbsoluteDate> cache, Mode mode, int slots)
throws TimeStampedCacheException {
double step = ((Generator) cache.getGenerator()).getStep();
AbsoluteDate[] base = new AbsoluteDate[slots];
base[0] = AbsoluteDate.GALILEO_EPOCH;
for (int i = 1; i < base.length; ++i) {
base[i] = base[i - 1].shiftedBy(10 * Constants.JULIAN_DAY);
}
return checkDatesSingleThread(mode.generateDates(base, 25 * step, 0.025 * step), cache);
}
private int testMultipleMultiThread(GenericTimeStampedCache<AbsoluteDate> cache, Mode mode,
int slots, int threadPoolSize)
throws TimeStampedCacheException {
double step = ((Generator) cache.getGenerator()).getStep();
AbsoluteDate[] base = new AbsoluteDate[slots];
base[0] = AbsoluteDate.GALILEO_EPOCH;
for (int i = 1; i < base.length; ++i) {
base[i] = base[i - 1].shiftedBy(10 * Constants.JULIAN_DAY);
}
return checkDatesMultiThread(mode.generateDates(base, 25 * step, 0.025 * step), cache, threadPoolSize);
}
private GenericTimeStampedCache<AbsoluteDate> createCache(int maxSlots, double step, int neighborsSize) {
Generator generator =
new Generator(AbsoluteDate.J2000_EPOCH.shiftedBy(-Constants.JULIAN_CENTURY),
AbsoluteDate.J2000_EPOCH.shiftedBy(+Constants.JULIAN_CENTURY),
step);
return new GenericTimeStampedCache<AbsoluteDate>(neighborsSize, maxSlots, Constants.JULIAN_YEAR,
Constants.JULIAN_DAY, generator);
}
private int checkDatesSingleThread(final List<AbsoluteDate> centralDates,
final GenericTimeStampedCache<AbsoluteDate> cache)
throws TimeStampedCacheException {
final int n = cache.getNeighborsSize();
final double step = ((Generator) cache.getGenerator()).getStep();
for (final AbsoluteDate central : centralDates) {
final List<AbsoluteDate> neighbors = cache.getNeighbors(central).collect(Collectors.toList());
Assert.assertEquals(n, neighbors.size());
for (final AbsoluteDate date : neighbors) {
Assert.assertTrue(date.durationFrom(central) >= -(n + 1) * step);
Assert.assertTrue(date.durationFrom(central) <= n * step);
}
}
return centralDates.size();
}
private int checkDatesMultiThread(final List<AbsoluteDate> centralDates,
final GenericTimeStampedCache<AbsoluteDate> cache,
final int threadPoolSize)
throws TimeStampedCacheException {
final int n = cache.getNeighborsSize();
final double step = ((Generator) cache.getGenerator()).getStep();
final AtomicReference<AbsoluteDate[]> failedDates = new AtomicReference<AbsoluteDate[]>();
final AtomicReference<TimeStampedCacheException> caught = new AtomicReference<TimeStampedCacheException>();
ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
for (final AbsoluteDate central : centralDates) {
executorService.execute(new Runnable() {
public void run() {
try {
final List<AbsoluteDate> neighbors = cache.getNeighbors(central).collect(Collectors.toList());
Assert.assertEquals(n, neighbors.size());
for (final AbsoluteDate date : neighbors) {
if (date.durationFrom(central) < -(n + 1) * step ||
date.durationFrom(central) > n * step) {
AbsoluteDate[] dates = new AbsoluteDate[n + 1];
dates[0] = central;
System.arraycopy(neighbors, 0, dates, 1, n);
failedDates.set(dates);
}
}
} catch (TimeStampedCacheException tce) {
caught.set(tce);
}
}
});
}
try {
executorService.shutdown();
Assert.assertTrue(
"Not enough time for all threads to complete, try increasing the timeout",
executorService.awaitTermination(10, TimeUnit.MINUTES));
} catch (InterruptedException ie) {
Assert.fail(ie.getLocalizedMessage());
}
if (caught.get() != null) {
throw caught.get();
}
if (failedDates.get() != null) {
AbsoluteDate[] dates = failedDates.get();
StringBuilder builder = new StringBuilder();
String eol = System.getProperty("line.separator");
builder.append("central = ").append(dates[0]).append(eol);
builder.append("step = ").append(step).append(eol);
builder.append("neighbors =").append(eol);
for (int i = 1; i < dates.length; ++i) {
builder.append(" ").append(dates[i]).append(eol);
}
Assert.fail(builder.toString());
}
return centralDates.size();
}
private static class Generator implements TimeStampedGenerator<AbsoluteDate> {
private final AbsoluteDate earliest;
private final AbsoluteDate latest;
private final double step;
public Generator(final AbsoluteDate earliest, final AbsoluteDate latest, final double step) {
this.earliest = earliest;
this.latest = latest;
this.step = step;
}
public double getStep() {
return step;
}
public List<AbsoluteDate> generate(AbsoluteDate existingDate, AbsoluteDate date) {
List<AbsoluteDate> dates = new ArrayList<AbsoluteDate>();
if (existingDate == null) {
dates.add(date);
} else if (date.compareTo(existingDate) >= 0) {
AbsoluteDate previous = existingDate;
while (date.compareTo(previous) > 0) {
previous = previous.shiftedBy(step);
if (previous.compareTo(earliest) >= 0 && previous.compareTo(latest) <= 0) {
dates.add(dates.size(), previous);
}
}
} else {
AbsoluteDate previous = existingDate;
while (date.compareTo(previous) < 0) {
previous = previous.shiftedBy(-step);
if (previous.compareTo(earliest) >= 0 && previous.compareTo(latest) <= 0) {
dates.add(0, previous);
}
}
}
return dates;
}
}
private interface Mode {
List<AbsoluteDate> generateDates(AbsoluteDate[] base, double duration, double step);
}
private class SequentialMode implements Mode {
public List<AbsoluteDate> generateDates(AbsoluteDate[] base, double duration, double step) {
List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
for (final AbsoluteDate initial : base) {
for (double dt = 0; dt < duration; dt += step) {
list.add(initial.shiftedBy(dt));
}
}
return list;
}
}
private class AlternateMode implements Mode {
public List<AbsoluteDate> generateDates(AbsoluteDate[] base, double duration, double step) {
List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
for (double dt = 0; dt < duration; dt += step) {
for (final AbsoluteDate initial : base) {
list.add(initial.shiftedBy(dt));
}
}
return list;
}
}
private class RandomMode implements Mode {
private RandomGenerator random;
public RandomMode(long seed) {
random = new Well1024a(seed);
}
public List<AbsoluteDate> generateDates(AbsoluteDate[] base, double duration, double step) {
List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
for (int i = 0; i < base.length * duration / step; ++i) {
int j = random.nextInt(base.length);
double dt = random.nextDouble() * duration;
list.add(base[j].shiftedBy(dt));
}
return list;
}
}
@Before
public void setUp() {
Utils.setDataRoot("regular-data");
}
}