From 7e85f3076a9a3670317cc5e6cf9050a54762acb3 Mon Sep 17 00:00:00 2001
From: David Legg
Date: Fri, 12 Jul 2024 17:45:37 -0700
Subject: [PATCH 1/2] Add InstantClock and VariableInstantClock support
Adds "Instant" counterparts to Clock and VariableClock, which report absolute times as Java instants, but still step time according to an Aerie duration.
Also adds some minimal interoperability between absolute and relative clocks, and some useful derivations including comparisons.
Using these, also adds a global Instant-based clock to Resources, along with exposing both the duration- and instant-based clocks as resources.
In doing so, we add a new parameter for the plan's start time to Resources.init().
This in turn required refactoring some unit tests, and I took the opportunity to clean up the test construction a little bit as well.
This revealed a way to correctly initialize Resources, i.e., to call Resources.init() exactly once per initialization of each test model.
As a result, I refactored the clock handling to remove the awkward reinitialization pattern, since that pattern was added to handle test code.
---
.../contrib/streamline/core/Resources.java | 31 ++++++----
.../streamline/modeling/Registrar.java | 6 +-
.../modeling/clocks/InstantClock.java | 24 ++++++++
.../clocks/InstantClockResources.java | 56 +++++++++++++++++++
.../modeling/clocks/VariableInstantClock.java | 19 +++++++
.../clocks/VariableInstantClockEffects.java | 33 +++++++++++
.../clocks/VariableInstantClockResources.java | 42 ++++++++++++++
.../streamline/core/CellRefV2Test.java | 20 ++++---
.../debugging/DependenciesTest.java | 26 ++++++---
.../discrete/DiscreteEffectsTest.java | 9 ++-
.../modeling/discrete/PrecomputedTest.java | 7 ++-
.../modeling/polynomial/ComparisonsTest.java | 33 ++++++-----
.../LinearBoundaryConsistencySolverTest.java | 40 ++++++++-----
.../modeling/polynomial/PrecomputedTest.java | 8 ++-
.../jpl/aerie/streamline_demo/Mission.java | 6 +-
15 files changed, 291 insertions(+), 69 deletions(-)
create mode 100644 contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/InstantClock.java
create mode 100644 contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/InstantClockResources.java
create mode 100644 contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/VariableInstantClock.java
create mode 100644 contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/VariableInstantClockEffects.java
create mode 100644 contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/VariableInstantClockResources.java
diff --git a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/core/Resources.java b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/core/Resources.java
index 087815118a..c0e72fcea0 100644
--- a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/core/Resources.java
+++ b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/core/Resources.java
@@ -1,11 +1,13 @@
package gov.nasa.jpl.aerie.contrib.streamline.core;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks.Clock;
+import gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks.InstantClock;
import gov.nasa.jpl.aerie.merlin.framework.Condition;
import gov.nasa.jpl.aerie.merlin.framework.Scoped;
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
import gov.nasa.jpl.aerie.merlin.protocol.types.Unit;
+import java.time.Instant;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
@@ -20,6 +22,7 @@
import static gov.nasa.jpl.aerie.contrib.streamline.debugging.Dependencies.addDependency;
import static gov.nasa.jpl.aerie.contrib.streamline.debugging.Naming.*;
import static gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks.Clock.clock;
+import static gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks.InstantClockResources.addToInstant;
import static gov.nasa.jpl.aerie.merlin.framework.ModelActions.*;
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.ZERO;
import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.Discrete.discrete;
@@ -38,22 +41,26 @@ private Resources() {}
* This method is idempotent; calling it multiple times is the same as calling it once.
*
*/
- public static void init() {
- currentTime();
+ public static void init(Instant planStart) {
+ CLOCK = resource(clock(ZERO));
+ ABSOLUTE_CLOCK = name(addToInstant(planStart, CLOCK), "Global Absolute Simulation Clock");
}
// TODO if Aerie provides either a `getElapsedTime` method or dynamic allocation of Cells, we can avoid this mutable static variable
- private static Resource CLOCK = resource(clock(ZERO));
+ private static Resource CLOCK;
+ private static Resource ABSOLUTE_CLOCK;
public static Duration currentTime() {
- try {
- return currentValue(CLOCK);
- } catch (Scoped.EmptyDynamicCellException | IllegalArgumentException e) {
- // If we're running unit tests, several simulations can happen without reloading the Resources class.
- // In that case, we'll have discarded the clock resource we were using, and get the above exception.
- // REVIEW: Is there a cleaner way to make sure this resource gets (re-)initialized?
- CLOCK = resource(clock(ZERO));
- return currentValue(CLOCK);
- }
+ return currentValue(CLOCK);
+ }
+ public static Instant currentInstant() {
+ return currentValue(ABSOLUTE_CLOCK);
+ }
+
+ public static Resource simulationClock() {
+ return CLOCK;
+ }
+ public static Resource absoluteClock() {
+ return ABSOLUTE_CLOCK;
}
public static D currentData(Resource resource) {
diff --git a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/Registrar.java b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/Registrar.java
index a8ab5ecce3..77545fc939 100644
--- a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/Registrar.java
+++ b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/Registrar.java
@@ -15,6 +15,8 @@
import gov.nasa.jpl.aerie.merlin.protocol.types.RealDynamics;
import gov.nasa.jpl.aerie.merlin.protocol.types.Unit;
+import java.time.Instant;
+
import static gov.nasa.jpl.aerie.contrib.streamline.core.Reactions.whenever;
import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.currentData;
import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.currentValue;
@@ -54,8 +56,8 @@ public enum ErrorBehavior {
Throw
}
- public Registrar(final gov.nasa.jpl.aerie.merlin.framework.Registrar baseRegistrar, final ErrorBehavior errorBehavior) {
- Resources.init();
+ public Registrar(final gov.nasa.jpl.aerie.merlin.framework.Registrar baseRegistrar, final Instant planStart, final ErrorBehavior errorBehavior) {
+ Resources.init(planStart);
Logging.init(baseRegistrar);
this.baseRegistrar = baseRegistrar;
this.errorBehavior = errorBehavior;
diff --git a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/InstantClock.java b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/InstantClock.java
new file mode 100644
index 0000000000..e6c866fd23
--- /dev/null
+++ b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/InstantClock.java
@@ -0,0 +1,24 @@
+package gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks;
+
+import gov.nasa.jpl.aerie.contrib.streamline.core.Dynamics;
+import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.addToInstant;
+
+/**
+ * A variation on {@link Clock} that represents an absolute {@link Instant}
+ * instead of a relative {@link Duration}.
+ */
+public record InstantClock(Instant extract) implements Dynamics {
+ @Override
+ public InstantClock step(Duration t) {
+ return new InstantClock(addToInstant(extract, t));
+ }
+
+ static Duration durationBetween(Instant start, Instant end) {
+ return Duration.of(ChronoUnit.MICROS.between(start, end), Duration.MICROSECONDS);
+ }
+}
diff --git a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/InstantClockResources.java b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/InstantClockResources.java
new file mode 100644
index 0000000000..204ef2c3c2
--- /dev/null
+++ b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/InstantClockResources.java
@@ -0,0 +1,56 @@
+package gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks;
+
+import gov.nasa.jpl.aerie.contrib.streamline.core.*;
+import gov.nasa.jpl.aerie.contrib.streamline.core.monads.ResourceMonad;
+import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.Discrete;
+import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
+
+import java.time.Instant;
+
+import static gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource.resource;
+import static gov.nasa.jpl.aerie.contrib.streamline.core.monads.ResourceMonad.map;
+import static gov.nasa.jpl.aerie.contrib.streamline.debugging.Naming.name;
+import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.DiscreteResources.constant;
+
+public class InstantClockResources {
+ /**
+ * Create an absolute clock that starts now at the given start time.
+ */
+ public static MutableResource absoluteClock(Instant startTime) {
+ return resource(new InstantClock(startTime));
+ }
+
+ public static Resource addToInstant(Instant zeroTime, Resource relativeClock) {
+ return addToInstant(constant(zeroTime), relativeClock);
+ }
+
+ public static Resource addToInstant(Resource> zeroTime, Resource relativeClock) {
+ return name(
+ map(zeroTime, relativeClock, (zero, clock) ->
+ new InstantClock(Duration.addToInstant(zero.extract(), clock.extract()))),
+ "%s + %s",
+ zeroTime,
+ relativeClock);
+ }
+
+ public static Resource relativeTo(Resource clock, Resource> zeroTime) {
+ return name(ResourceMonad.map(clock, zeroTime, (c, t) -> new Clock(InstantClock.durationBetween(t.extract(), c.extract()))),
+ "%s relative to %s", clock, zeroTime);
+ }
+
+ public static Resource> lessThan(Resource clock, Resource> threshold) {
+ return ClockResources.lessThan(relativeTo(clock, threshold), constant(Duration.ZERO));
+ }
+
+ public static Resource> lessThanOrEquals(Resource clock, Resource> threshold) {
+ return ClockResources.lessThanOrEquals(relativeTo(clock, threshold), constant(Duration.ZERO));
+ }
+
+ public static Resource> greaterThan(Resource clock, Resource> threshold) {
+ return ClockResources.greaterThan(relativeTo(clock, threshold), constant(Duration.ZERO));
+ }
+
+ public static Resource> greaterThanOrEquals(Resource clock, Resource> threshold) {
+ return ClockResources.greaterThanOrEquals(relativeTo(clock, threshold), constant(Duration.ZERO));
+ }
+}
diff --git a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/VariableInstantClock.java b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/VariableInstantClock.java
new file mode 100644
index 0000000000..7915396af3
--- /dev/null
+++ b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/VariableInstantClock.java
@@ -0,0 +1,19 @@
+package gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks;
+
+import gov.nasa.jpl.aerie.contrib.streamline.core.Dynamics;
+import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
+
+import java.time.Instant;
+
+import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.addToInstant;
+
+/**
+ * A variation on {@link VariableClock} that represents an absolute {@link Instant}
+ * instead of a relative {@link Duration}.
+ */
+public record VariableInstantClock(Instant extract, int multiplier) implements Dynamics {
+ @Override
+ public VariableInstantClock step(Duration t) {
+ return new VariableInstantClock(addToInstant(extract, t.times(multiplier)), multiplier);
+ }
+}
diff --git a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/VariableInstantClockEffects.java b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/VariableInstantClockEffects.java
new file mode 100644
index 0000000000..019a31ee4c
--- /dev/null
+++ b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/VariableInstantClockEffects.java
@@ -0,0 +1,33 @@
+package gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks;
+
+import gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource;
+
+import java.time.Instant;
+
+import static gov.nasa.jpl.aerie.contrib.streamline.core.monads.DynamicsMonad.effect;
+import static gov.nasa.jpl.aerie.contrib.streamline.debugging.Naming.name;
+
+public final class VariableInstantClockEffects {
+ private VariableInstantClockEffects() {}
+
+ /**
+ * Stop the clock without affecting the current time.
+ */
+ public static void pause(MutableResource clock) {
+ clock.emit("Pause", effect($ -> new VariableInstantClock($.extract(), 0)));
+ }
+
+ /**
+ * Start the clock without affecting the current time.
+ */
+ public static void start(MutableResource clock) {
+ clock.emit("Start", effect($ -> new VariableInstantClock($.extract(), 1)));
+ }
+
+ /**
+ * Reset the clock to the given time, without affecting how fast it's running.
+ */
+ public static void reset(MutableResource clock, Instant newTime) {
+ clock.emit(name(effect($ -> new VariableInstantClock(newTime, $.multiplier())), "Reset to %s", newTime));
+ }
+}
diff --git a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/VariableInstantClockResources.java b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/VariableInstantClockResources.java
new file mode 100644
index 0000000000..83df2de990
--- /dev/null
+++ b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/VariableInstantClockResources.java
@@ -0,0 +1,42 @@
+package gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks;
+
+import gov.nasa.jpl.aerie.contrib.streamline.core.Resource;
+import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.Discrete;
+import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
+
+import java.time.Instant;
+
+import static gov.nasa.jpl.aerie.contrib.streamline.core.monads.ResourceMonad.map;
+import static gov.nasa.jpl.aerie.contrib.streamline.debugging.Naming.name;
+import static gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks.InstantClock.durationBetween;
+import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.DiscreteResources.constant;
+
+public final class VariableInstantClockResources {
+ private VariableInstantClockResources() {}
+
+ public static Resource relativeTo(Resource clock, Resource> zeroTime) {
+ return name(map(clock, zeroTime, (c, t) ->
+ new VariableClock(durationBetween(c.extract(), t.extract()), c.multiplier())),
+ "%s relative to %s", clock, zeroTime);
+ }
+
+ public static Resource> lessThan(Resource clock, Resource> threshold) {
+ return VariableClockResources.lessThan(relativeTo(clock, threshold), constant(Duration.ZERO));
+ }
+
+ public static Resource> lessThanOrEquals(Resource clock, Resource> threshold) {
+ return VariableClockResources.lessThanOrEquals(relativeTo(clock, threshold), constant(Duration.ZERO));
+ }
+
+ public static Resource> greaterThan(Resource clock, Resource> threshold) {
+ return VariableClockResources.greaterThan(relativeTo(clock, threshold), constant(Duration.ZERO));
+ }
+
+ public static Resource> greaterThanOrEquals(Resource clock, Resource> threshold) {
+ return VariableClockResources.greaterThanOrEquals(relativeTo(clock, threshold), constant(Duration.ZERO));
+ }
+
+ public static Resource between(Resource start, Resource end) {
+ return map(start, end, (s, e) -> new VariableClock(durationBetween(s.extract(), e.extract()), e.multiplier() - s.multiplier()));
+ }
+}
diff --git a/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/core/CellRefV2Test.java b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/core/CellRefV2Test.java
index c77bb601c8..1a626b6fda 100644
--- a/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/core/CellRefV2Test.java
+++ b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/core/CellRefV2Test.java
@@ -9,10 +9,11 @@
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.ExtendWith;
+import java.time.Instant;
+
import static gov.nasa.jpl.aerie.contrib.streamline.core.CellRefV2.autoEffects;
import static gov.nasa.jpl.aerie.contrib.streamline.core.CellRefV2.commutingEffects;
import static gov.nasa.jpl.aerie.contrib.streamline.core.CellRefV2.noncommutingEffects;
-import static gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource.resource;
import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.currentValue;
import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.Discrete.discrete;
import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.monads.DiscreteDynamicsMonad.effect;
@@ -27,10 +28,11 @@ class MutableResourceTest {
@TestInstance(Lifecycle.PER_CLASS)
class NonCommutingEffects {
public NonCommutingEffects(final Registrar registrar) {
- Resources.init();
+ Resources.init(Instant.EPOCH);
+ cell = MutableResource.resource(discrete(42), noncommutingEffects());
}
- private final MutableResource> cell = MutableResource.resource(discrete(42), noncommutingEffects());
+ private final MutableResource> cell;
@Test
void gets_initial_value_if_no_effects_are_emitted() {
@@ -66,10 +68,11 @@ void throws_exception_when_concurrent_effects_are_applied() {
@TestInstance(Lifecycle.PER_CLASS)
class CommutingEffects {
public CommutingEffects(final Registrar registrar) {
- Resources.init();
+ Resources.init(Instant.EPOCH);
+ cell = MutableResource.resource(discrete(42), commutingEffects());
}
- private final MutableResource> cell = MutableResource.resource(discrete(42), commutingEffects());
+ private final MutableResource> cell;
@Test
void gets_initial_value_if_no_effects_are_emitted() {
@@ -108,11 +111,12 @@ void applies_concurrent_effects_in_any_order() {
@ExtendWith(MerlinExtension.class)
@TestInstance(Lifecycle.PER_CLASS)
class AutoEffects {
- public AutoEffects(final Registrar registrar) {
- Resources.init();
+ public AutoEffects() {
+ Resources.init(Instant.EPOCH);
+ cell = MutableResource.resource(discrete(42), autoEffects());
}
- private final MutableResource> cell = MutableResource.resource(discrete(42), autoEffects());
+ private final MutableResource> cell;
@Test
void gets_initial_value_if_no_effects_are_emitted() {
diff --git a/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/debugging/DependenciesTest.java b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/debugging/DependenciesTest.java
index 8014216f63..75ea39427a 100644
--- a/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/debugging/DependenciesTest.java
+++ b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/debugging/DependenciesTest.java
@@ -1,7 +1,7 @@
package gov.nasa.jpl.aerie.contrib.streamline.debugging;
-import gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource;
import gov.nasa.jpl.aerie.contrib.streamline.core.Resource;
+import gov.nasa.jpl.aerie.contrib.streamline.core.Resources;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.Discrete;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.DiscreteResources;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.Polynomial;
@@ -11,6 +11,8 @@
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
+import java.time.Instant;
+
import static gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource.resource;
import static gov.nasa.jpl.aerie.contrib.streamline.core.monads.ResourceMonad.*;
import static gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.Polynomial.polynomial;
@@ -21,12 +23,22 @@
@ExtendWith(MerlinExtension.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DependenciesTest {
- Resource> constantTrue = DiscreteResources.constant(true);
- Resource constant1234 = constant(1234);
- Resource constant5678 = constant(5678);
- Resource polynomialCell = resource(polynomial(1));
- Resource derived = map(constantTrue, constant1234, constant5678,
- (b, x, y) -> b.extract() ? x : y);
+ public DependenciesTest() {
+ Resources.init(Instant.EPOCH);
+
+ constantTrue = DiscreteResources.constant(true);
+ constant1234 = constant(1234);
+ constant5678 = constant(5678);
+ polynomialCell = resource(polynomial(1));
+ derived = map(constantTrue, constant1234, constant5678,
+ (b, x, y) -> b.extract() ? x : y);
+ }
+
+ Resource> constantTrue;
+ Resource constant1234;
+ Resource constant5678;
+ Resource polynomialCell;
+ Resource derived;
@Test
void constants_are_named_by_their_value() {
diff --git a/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/discrete/DiscreteEffectsTest.java b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/discrete/DiscreteEffectsTest.java
index 49d4a953e8..ea30dd871a 100644
--- a/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/discrete/DiscreteEffectsTest.java
+++ b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/discrete/DiscreteEffectsTest.java
@@ -5,7 +5,6 @@
import gov.nasa.jpl.aerie.contrib.streamline.core.Resources;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks.Clock;
import gov.nasa.jpl.aerie.contrib.streamline.unit_aware.UnitAware;
-import gov.nasa.jpl.aerie.merlin.framework.Registrar;
import gov.nasa.jpl.aerie.merlin.framework.junit.MerlinExtension;
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
import org.junit.jupiter.api.Test;
@@ -13,6 +12,8 @@
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.ExtendWith;
+import java.time.Instant;
+
import static gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource.resource;
import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.currentTime;
import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.currentValue;
@@ -40,8 +41,10 @@
@ExtendWith(MerlinExtension.class)
@TestInstance(Lifecycle.PER_CLASS)
class DiscreteEffectsTest {
- public DiscreteEffectsTest(final Registrar registrar) {
- Resources.init();
+ {
+ // We need to initialize this up front, so we can use in-line initializers for other resources after.
+ // I think in-line initializers for the other resources make the tests easier to read.
+ Resources.init(Instant.EPOCH);
}
private final MutableResource> settable = resource(discrete(42));
diff --git a/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/discrete/PrecomputedTest.java b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/discrete/PrecomputedTest.java
index 3849203bf5..d0d8bd733b 100644
--- a/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/discrete/PrecomputedTest.java
+++ b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/discrete/PrecomputedTest.java
@@ -2,7 +2,6 @@
import gov.nasa.jpl.aerie.contrib.streamline.core.Resource;
import gov.nasa.jpl.aerie.contrib.streamline.core.Resources;
-import gov.nasa.jpl.aerie.merlin.framework.Registrar;
import gov.nasa.jpl.aerie.merlin.framework.junit.MerlinExtension;
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
import org.junit.jupiter.api.Test;
@@ -26,8 +25,10 @@
@ExtendWith(MerlinExtension.class)
@TestInstance(Lifecycle.PER_CLASS)
public class PrecomputedTest {
- public PrecomputedTest(final Registrar registrar) {
- Resources.init();
+ {
+ // We need to initialize this up front, so we can use in-line initializers for other resources after.
+ // I think in-line initializers for the other resources make the tests easier to read.
+ Resources.init(Instant.EPOCH);
}
final Resource> precomputedAsAConstant =
diff --git a/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/polynomial/ComparisonsTest.java b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/polynomial/ComparisonsTest.java
index a7cad57998..67a3e109e9 100644
--- a/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/polynomial/ComparisonsTest.java
+++ b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/polynomial/ComparisonsTest.java
@@ -5,7 +5,6 @@
import gov.nasa.jpl.aerie.contrib.streamline.core.Resource;
import gov.nasa.jpl.aerie.contrib.streamline.core.Resources;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.Discrete;
-import gov.nasa.jpl.aerie.merlin.framework.Registrar;
import gov.nasa.jpl.aerie.merlin.framework.junit.MerlinExtension;
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
import org.junit.jupiter.api.Test;
@@ -13,6 +12,8 @@
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.ExtendWith;
+import java.time.Instant;
+
import static gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource.resource;
import static gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource.set;
import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.currentData;
@@ -28,22 +29,26 @@
@ExtendWith(MerlinExtension.class)
@TestInstance(Lifecycle.PER_CLASS)
public class ComparisonsTest {
- public ComparisonsTest(final Registrar registrar) {
- Resources.init();
- }
+ public ComparisonsTest() {
+ Resources.init(Instant.EPOCH);
- private final MutableResource p = resource(polynomial(0));
- private final MutableResource q = resource(polynomial(0));
+ p = resource(polynomial(0));
+ q = resource(polynomial(0));
- private final Resource> p_lt_q = lessThan(p, q);
- private final Resource> p_lte_q = lessThanOrEquals(p, q);
- private final Resource> p_gt_q = greaterThan(p, q);
- private final Resource> p_gte_q = greaterThanOrEquals(p, q);
+ p_lt_q = lessThan(p, q);
+ p_lte_q = lessThanOrEquals(p, q);
+ p_gt_q = greaterThan(p, q);
+ p_gte_q = greaterThanOrEquals(p, q);
+
+ min_p_q = min(p, q);
+ min_q_p = min(q, p);
+ max_p_q = max(p, q);
+ max_q_p = max(q, p);
+ }
- private final Resource min_p_q = min(p, q);
- private final Resource min_q_p = min(q, p);
- private final Resource max_p_q = max(p, q);
- private final Resource max_q_p = max(q, p);
+ private final MutableResource p, q;
+ private final Resource> p_lt_q, p_lte_q, p_gt_q, p_gte_q;
+ private final Resource min_p_q, min_q_p, max_p_q, max_q_p;
@Test
void comparing_distinct_constants() {
diff --git a/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/polynomial/LinearBoundaryConsistencySolverTest.java b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/polynomial/LinearBoundaryConsistencySolverTest.java
index eceec5b70e..254fa93ff7 100644
--- a/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/polynomial/LinearBoundaryConsistencySolverTest.java
+++ b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/polynomial/LinearBoundaryConsistencySolverTest.java
@@ -10,6 +10,8 @@
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.ExtendWith;
+import java.time.Instant;
+
import static gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource.*;
import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.currentData;
import static gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.LinearBoundaryConsistencySolver.Comparison.*;
@@ -25,11 +27,13 @@ class LinearBoundaryConsistencySolverTest {
@ExtendWith(MerlinExtension.class)
@TestInstance(Lifecycle.PER_CLASS)
class SingleVariableSingleConstraint {
- MutableResource driver = resource(polynomial(10));
+ MutableResource driver;
Resource result;
- public SingleVariableSingleConstraint() {
- Resources.init();
+ SingleVariableSingleConstraint() {
+ Resources.init(Instant.EPOCH);
+
+ driver = resource(polynomial(10));
var solver = new LinearBoundaryConsistencySolver("SingleVariableSingleConstraint");
var v = solver.variable("v", Domain::upperBound);
@@ -65,13 +69,15 @@ void results_evolve_with_time() {
@ExtendWith(MerlinExtension.class)
@TestInstance(Lifecycle.PER_CLASS)
class SingleVariableMultipleConstraint {
- MutableResource lowerBound1 = resource(polynomial(10));
- MutableResource lowerBound2 = resource(polynomial(20));
- MutableResource upperBound = resource(polynomial(30));
+ MutableResource lowerBound1, lowerBound2, upperBound;
Resource result;
- public SingleVariableMultipleConstraint() {
- Resources.init();
+ SingleVariableMultipleConstraint() {
+ Resources.init(Instant.EPOCH);
+
+ lowerBound1 = resource(polynomial(10));
+ lowerBound2 = resource(polynomial(20));
+ upperBound = resource(polynomial(30));
var solver = new LinearBoundaryConsistencySolver("SingleVariableMultipleConstraint");
var v = solver.variable("v", Domain::lowerBound);
@@ -141,11 +147,13 @@ void failures_are_cleared_if_problem_becomes_feasible_again() {
@ExtendWith(MerlinExtension.class)
@TestInstance(Lifecycle.PER_CLASS)
class ScalingConstraint {
- MutableResource driver = resource(polynomial(10));
+ MutableResource driver;
Resource result;
- public ScalingConstraint() {
- Resources.init();
+ ScalingConstraint() {
+ Resources.init(Instant.EPOCH);
+
+ driver = resource(polynomial(10));
var solver = new LinearBoundaryConsistencySolver("ScalingConstraint");
var v = solver.variable("v", Domain::upperBound);
@@ -171,12 +179,14 @@ void scaling_is_respected_for_later_solutions() {
@ExtendWith(MerlinExtension.class)
@TestInstance(Lifecycle.PER_CLASS)
class MultipleVariables {
- MutableResource upperBound = resource(polynomial(10));
- MutableResource upperBoundOnC = resource(polynomial(5));
+ MutableResource upperBound, upperBoundOnC;
Resource a, b, c;
- public MultipleVariables() {
- Resources.init();
+ MultipleVariables() {
+ Resources.init(Instant.EPOCH);
+
+ upperBound = resource(polynomial(10));
+ upperBoundOnC = resource(polynomial(5));
var solver = new LinearBoundaryConsistencySolver("MultipleVariablesSingleConstraint");
var a = solver.variable("a", Domain::upperBound);
diff --git a/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/polynomial/PrecomputedTest.java b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/polynomial/PrecomputedTest.java
index 47427baf17..54122c6581 100644
--- a/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/polynomial/PrecomputedTest.java
+++ b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/polynomial/PrecomputedTest.java
@@ -2,13 +2,13 @@
import gov.nasa.jpl.aerie.contrib.streamline.core.Resource;
import gov.nasa.jpl.aerie.contrib.streamline.core.Resources;
-import gov.nasa.jpl.aerie.merlin.framework.Registrar;
import gov.nasa.jpl.aerie.merlin.framework.junit.MerlinExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.ExtendWith;
+import java.time.Instant;
import java.util.Map;
import java.util.TreeMap;
@@ -24,8 +24,10 @@
@ExtendWith(MerlinExtension.class)
@TestInstance(Lifecycle.PER_CLASS)
public class PrecomputedTest {
- public PrecomputedTest(final Registrar registrar) {
- Resources.init();
+ {
+ // We need to initialize this up front, so we can use in-line initializers for other resources after.
+ // I think in-line initializers for the other resources make the tests easier to read.
+ Resources.init(Instant.EPOCH);
}
final Resource precomputedAsConstantInPast =
diff --git a/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/Mission.java b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/Mission.java
index ece739efba..a1d32e971a 100644
--- a/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/Mission.java
+++ b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/Mission.java
@@ -5,13 +5,15 @@
import gov.nasa.jpl.aerie.contrib.streamline.modeling.Registrar;
import gov.nasa.jpl.aerie.merlin.framework.ModelActions;
+import java.time.Instant;
+
public final class Mission {
public final DataModel dataModel;
public final ErrorTestingModel errorTestingModel;
public final ApproximationModel approximationModel;
- public Mission(final gov.nasa.jpl.aerie.merlin.framework.Registrar registrar$, final Configuration config) {
- var registrar = new Registrar(registrar$, Registrar.ErrorBehavior.Log);
+ public Mission(final gov.nasa.jpl.aerie.merlin.framework.Registrar registrar$, Instant planStart, final Configuration config) {
+ var registrar = new Registrar(registrar$, planStart, Registrar.ErrorBehavior.Log);
if (config.traceResources) registrar.setTrace();
if (config.profileResources) Resource.profileAllResources();
dataModel = new DataModel(registrar, config);
From 6d30dd1a117cb1a067049a1e1c92741fa8600a79 Mon Sep 17 00:00:00 2001
From: David Legg
Date: Tue, 29 Oct 2024 18:22:10 -0700
Subject: [PATCH 2/2] Add SimulationClockTest
---
.../modeling/clocks/InstantClock.java | 2 +
.../modeling/clocks/SimulationClockTest.java | 66 +++++++++++++++++++
2 files changed, 68 insertions(+)
create mode 100644 contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/SimulationClockTest.java
diff --git a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/InstantClock.java b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/InstantClock.java
index e6c866fd23..e00bb5b323 100644
--- a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/InstantClock.java
+++ b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/InstantClock.java
@@ -18,6 +18,8 @@ public InstantClock step(Duration t) {
return new InstantClock(addToInstant(extract, t));
}
+ // TODO - this method belongs somewhere else...
+ // Making it package-private at least lets us move it later without dependency issues outside the library.
static Duration durationBetween(Instant start, Instant end) {
return Duration.of(ChronoUnit.MICROS.between(start, end), Duration.MICROSECONDS);
}
diff --git a/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/SimulationClockTest.java b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/SimulationClockTest.java
new file mode 100644
index 0000000000..6efac343e7
--- /dev/null
+++ b/contrib/src/test/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/clocks/SimulationClockTest.java
@@ -0,0 +1,66 @@
+package gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks;
+
+import gov.nasa.jpl.aerie.contrib.streamline.core.Resources;
+import gov.nasa.jpl.aerie.merlin.framework.junit.MerlinExtension;
+import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.*;
+import static gov.nasa.jpl.aerie.merlin.framework.ModelActions.delay;
+import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.HOUR;
+import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.ZERO;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@ExtendWith(MerlinExtension.class)
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public class SimulationClockTest {
+ // This time is unlikely to be a hard-coded default anywhere
+ public static final Instant PLAN_START = Instant.parse("2020-01-02T03:04:05Z");
+
+ public SimulationClockTest() {
+ Resources.init(PLAN_START);
+ }
+
+ @Test
+ public void relative_clock_starts_at_zero() {
+ assertEquals(ZERO, currentTime());
+ assertEquals(ZERO, currentValue(simulationClock()));
+ }
+
+ @Test
+ public void absolute_clock_starts_at_plan_start() {
+ assertEquals(PLAN_START, currentInstant());
+ assertEquals(PLAN_START, currentValue(absoluteClock()));
+ }
+
+ @Test
+ public void relative_clock_advances_at_unit_rate() {
+ delay(1, HOUR);
+ assertEquals(Duration.of(1, HOUR), currentTime());
+ assertEquals(Duration.of(1, HOUR), currentValue(simulationClock()));
+ delay(1, HOUR);
+ assertEquals(Duration.of(2, HOUR), currentTime());
+ assertEquals(Duration.of(2, HOUR), currentValue(simulationClock()));
+ delay(1, HOUR);
+ assertEquals(Duration.of(3, HOUR), currentTime());
+ assertEquals(Duration.of(3, HOUR), currentValue(simulationClock()));
+ }
+
+ @Test
+ public void absolute_clock_advances_at_unit_rate() {
+ delay(1, HOUR);
+ assertEquals(PLAN_START.plus(1, ChronoUnit.HOURS), currentInstant());
+ assertEquals(PLAN_START.plus(1, ChronoUnit.HOURS), currentValue(absoluteClock()));
+ delay(1, HOUR);
+ assertEquals(PLAN_START.plus(2, ChronoUnit.HOURS), currentInstant());
+ assertEquals(PLAN_START.plus(2, ChronoUnit.HOURS), currentValue(absoluteClock()));
+ delay(1, HOUR);
+ assertEquals(PLAN_START.plus(3, ChronoUnit.HOURS), currentInstant());
+ assertEquals(PLAN_START.plus(3, ChronoUnit.HOURS), currentValue(absoluteClock()));
+ }
+}