Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add InstantClock and VariableInstantClock support #1589

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
* </p>
*/
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> CLOCK = resource(clock(ZERO));
private static Resource<Clock> CLOCK;
private static Resource<InstantClock> ABSOLUTE_CLOCK;
public static Duration currentTime() {
try {
return currentValue(CLOCK);
} catch (Scoped.EmptyDynamicCellException | IllegalArgumentException e) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice to see this exception handler go away. Not entirely sure I follow what the paradigm shift was that made it obsolete

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In hindsight, it was obvious. I just had to put a call to Resources.init() (or build a streamline registrar) in the constructor for my unit tests, so the unit tests would re-init the system at the correct time. The unit test is just a model, so it's exactly the same paradigm that we use in production. I don't know why I didn't realize I could do that before. I think maybe I was originally trying to avoid the need for an init method altogether?

// 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<Clock> simulationClock() {
return CLOCK;
}
public static Resource<InstantClock> absoluteClock() {
return ABSOLUTE_CLOCK;
}

public static <D> D currentData(Resource<D> resource) {
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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 Clock} that represents an absolute {@link Instant}
* instead of a relative {@link Duration}.
*/
public record InstantClock(Instant extract) implements Dynamics<Instant, InstantClock> {
@Override
public InstantClock step(Duration t) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just musing - would it make any sense for InstantClock to be derived from the Duration Clock? Or do you find that separating them is cleaner?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java won't let us derive one from the other, because they both implement Dynamics but with different type arguments. I also kind of like that an Instant clock doesn't necessarily have a preferred "zero" / "epoch" time, and you can just create a Duration clock by giving a zero time that makes sense in context.

return new InstantClock(addToInstant(extract, t));
}
}
Original file line number Diff line number Diff line change
@@ -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<InstantClock> absoluteClock(Instant startTime) {
return resource(new InstantClock(startTime));
}

public static Resource<InstantClock> addToInstant(Instant zeroTime, Resource<Clock> relativeClock) {
return addToInstant(constant(zeroTime), relativeClock);
}

public static Resource<InstantClock> addToInstant(Resource<Discrete<Instant>> zeroTime, Resource<Clock> relativeClock) {
return name(
map(zeroTime, relativeClock, (zero, clock) ->
new InstantClock(Duration.addToInstant(zero.extract(), clock.extract()))),
"%s + %s",
zeroTime,
relativeClock);
}

public static Resource<Clock> relativeTo(Resource<InstantClock> clock, Resource<Discrete<Instant>> zeroTime) {
return name(ResourceMonad.map(clock, zeroTime, (c, t) -> new Clock(Duration.between(t.extract(), c.extract()))),
"%s relative to %s", clock, zeroTime);
}

public static Resource<Discrete<Boolean>> lessThan(Resource<InstantClock> clock, Resource<Discrete<Instant>> threshold) {
return ClockResources.lessThan(relativeTo(clock, threshold), constant(Duration.ZERO));
}

public static Resource<Discrete<Boolean>> lessThanOrEquals(Resource<InstantClock> clock, Resource<Discrete<Instant>> threshold) {
return ClockResources.lessThanOrEquals(relativeTo(clock, threshold), constant(Duration.ZERO));
}

public static Resource<Discrete<Boolean>> greaterThan(Resource<InstantClock> clock, Resource<Discrete<Instant>> threshold) {
return ClockResources.greaterThan(relativeTo(clock, threshold), constant(Duration.ZERO));
}

public static Resource<Discrete<Boolean>> greaterThanOrEquals(Resource<InstantClock> clock, Resource<Discrete<Instant>> threshold) {
return ClockResources.greaterThanOrEquals(relativeTo(clock, threshold), constant(Duration.ZERO));
}
}
Original file line number Diff line number Diff line change
@@ -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<Instant, VariableInstantClock> {
@Override
public VariableInstantClock step(Duration t) {
return new VariableInstantClock(addToInstant(extract, t.times(multiplier)), multiplier);
}
}
Original file line number Diff line number Diff line change
@@ -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<VariableInstantClock> clock) {
clock.emit("Pause", effect($ -> new VariableInstantClock($.extract(), 0)));
}

/**
* Start the clock without affecting the current time.
*/
public static void start(MutableResource<VariableInstantClock> 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<VariableInstantClock> clock, Instant newTime) {
clock.emit(name(effect($ -> new VariableInstantClock(newTime, $.multiplier())), "Reset to %s", newTime));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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.discrete.DiscreteResources.constant;

public final class VariableInstantClockResources {
private VariableInstantClockResources() {}

public static Resource<VariableClock> relativeTo(Resource<VariableInstantClock> clock, Resource<Discrete<Instant>> zeroTime) {
return name(map(clock, zeroTime, (c, t) ->
new VariableClock(Duration.between(c.extract(), t.extract()), c.multiplier())),
"%s relative to %s", clock, zeroTime);
}

public static Resource<Discrete<Boolean>> lessThan(Resource<VariableInstantClock> clock, Resource<Discrete<Instant>> threshold) {
return VariableClockResources.lessThan(relativeTo(clock, threshold), constant(Duration.ZERO));
}

public static Resource<Discrete<Boolean>> lessThanOrEquals(Resource<VariableInstantClock> clock, Resource<Discrete<Instant>> threshold) {
return VariableClockResources.lessThanOrEquals(relativeTo(clock, threshold), constant(Duration.ZERO));
}

public static Resource<Discrete<Boolean>> greaterThan(Resource<VariableInstantClock> clock, Resource<Discrete<Instant>> threshold) {
return VariableClockResources.greaterThan(relativeTo(clock, threshold), constant(Duration.ZERO));
}

public static Resource<Discrete<Boolean>> greaterThanOrEquals(Resource<VariableInstantClock> clock, Resource<Discrete<Instant>> threshold) {
return VariableClockResources.greaterThanOrEquals(relativeTo(clock, threshold), constant(Duration.ZERO));
}

public static Resource<VariableClock> between(Resource<VariableInstantClock> start, Resource<VariableInstantClock> end) {
return map(start, end, (s, e) -> new VariableClock(Duration.between(s.extract(), e.extract()), e.multiplier() - s.multiplier()));
}
}
Original file line number Diff line number Diff line change
@@ -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<Discrete<Integer>> cell = MutableResource.resource(discrete(42), noncommutingEffects());
private final MutableResource<Discrete<Integer>> 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<Discrete<Integer>> cell = MutableResource.resource(discrete(42), commutingEffects());
private final MutableResource<Discrete<Integer>> 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<Discrete<Integer>> cell = MutableResource.resource(discrete(42), autoEffects());
private final MutableResource<Discrete<Integer>> cell;

@Test
void gets_initial_value_if_no_effects_are_emitted() {
Original file line number Diff line number Diff line change
@@ -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<Discrete<Boolean>> constantTrue = DiscreteResources.constant(true);
Resource<Polynomial> constant1234 = constant(1234);
Resource<Polynomial> constant5678 = constant(5678);
Resource<Polynomial> polynomialCell = resource(polynomial(1));
Resource<Polynomial> 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<Discrete<Boolean>> constantTrue;
Resource<Polynomial> constant1234;
Resource<Polynomial> constant5678;
Resource<Polynomial> polynomialCell;
Resource<Polynomial> derived;

@Test
void constants_are_named_by_their_value() {
Original file line number Diff line number Diff line change
@@ -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()));
}
}
Original file line number Diff line number Diff line change
@@ -5,14 +5,15 @@
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;
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 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<Discrete<Integer>> settable = resource(discrete(42));
Original file line number Diff line number Diff line change
@@ -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<Discrete<Integer>> precomputedAsAConstant =
Original file line number Diff line number Diff line change
@@ -5,14 +5,15 @@
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;
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 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<Polynomial> p = resource(polynomial(0));
private final MutableResource<Polynomial> q = resource(polynomial(0));
p = resource(polynomial(0));
q = resource(polynomial(0));

private final Resource<Discrete<Boolean>> p_lt_q = lessThan(p, q);
private final Resource<Discrete<Boolean>> p_lte_q = lessThanOrEquals(p, q);
private final Resource<Discrete<Boolean>> p_gt_q = greaterThan(p, q);
private final Resource<Discrete<Boolean>> 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<Polynomial> min_p_q = min(p, q);
private final Resource<Polynomial> min_q_p = min(q, p);
private final Resource<Polynomial> max_p_q = max(p, q);
private final Resource<Polynomial> max_q_p = max(q, p);
private final MutableResource<Polynomial> p, q;
private final Resource<Discrete<Boolean>> p_lt_q, p_lte_q, p_gt_q, p_gte_q;
private final Resource<Polynomial> min_p_q, min_q_p, max_p_q, max_q_p;

@Test
void comparing_distinct_constants() {
Original file line number Diff line number Diff line change
@@ -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<Polynomial> driver = resource(polynomial(10));
MutableResource<Polynomial> driver;
Resource<Polynomial> 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<Polynomial> lowerBound1 = resource(polynomial(10));
MutableResource<Polynomial> lowerBound2 = resource(polynomial(20));
MutableResource<Polynomial> upperBound = resource(polynomial(30));
MutableResource<Polynomial> lowerBound1, lowerBound2, upperBound;
Resource<Polynomial> 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<Polynomial> driver = resource(polynomial(10));
MutableResource<Polynomial> driver;
Resource<Polynomial> 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<Polynomial> upperBound = resource(polynomial(10));
MutableResource<Polynomial> upperBoundOnC = resource(polynomial(5));
MutableResource<Polynomial> upperBound, upperBoundOnC;
Resource<Polynomial> 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);
Original file line number Diff line number Diff line change
@@ -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<Polynomial> precomputedAsConstantInPast =
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package gov.nasa.jpl.aerie.merlin.protocol.types;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.List;
@@ -455,6 +456,11 @@ public static java.time.Instant addToInstant(final java.time.Instant instant, fi
.plusNanos(1000 * duration.remainderOf(Duration.MILLISECONDS).dividedBy(Duration.MICROSECONDS));
}

/** Compute the duration between two {@link Instant}s. */
public static Duration between(Instant start, Instant end) {
return Duration.of(ChronoUnit.MICROS.between(start, end), Duration.MICROSECONDS);
}

/** @see Duration#add(Duration, Duration) */
public Duration plus(final Duration other) throws ArithmeticException {
return Duration.add(this, other);