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 streamline resource framework #1198

Closed
Closed
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
38699ef
Add streamline resource framework
Oct 12, 2023
d14c005
Additions and bug fixes to support Clipper
Oct 17, 2023
cd8a39b
Profiling and performance improvements
Oct 23, 2023
9eb55f9
Add toggle for error behavior
Oct 24, 2023
c1e1cc8
Fill out consumable/nonconsumable effects
Oct 24, 2023
d237869
Documentation and clean-up
Oct 25, 2023
da5913f
Generalize "or" and "and" functions
Oct 26, 2023
4720079
Promote unit_aware module one level
Oct 26, 2023
94ed959
Move unit_aware tests to mirror src package structure
Oct 26, 2023
9c76027
Add Differentiable dynamics and initial secant approximations
Oct 27, 2023
018ff08
WIP: Filling out unstructured resources and general purpose secant ap…
Oct 27, 2023
e1aa3ce
WIP: Bug fixes and performance improvements to secant approximations
Oct 28, 2023
af91b44
WIP: Add discrete approximations for unstructured resources
Oct 30, 2023
6256dd0
WIP: refactoring approximation code
Oct 30, 2023
308356d
WIP: debugging and demo work
Nov 8, 2023
fd2db29
WIP: debugging quadratic approximation
Nov 9, 2023
4d09c0a
WIP: Add VariableClockResource.toLinear
Nov 9, 2023
c1ed0e3
WIP: Add ClockResources.asLinear
Nov 9, 2023
73505ab
Add value/data extraction methods for wrapped dynamics
Nov 9, 2023
08b3598
Add increase/decrease effects
Nov 10, 2023
f960825
Add discrete double arithmetic functions
Nov 10, 2023
b086a19
Fix clampedIntegrate
Nov 15, 2023
41fc01e
Fix approximation and handling of double precision edge cases
Nov 22, 2023
5960ae7
Merge branch 'contrib-streamline-framework--applicatives-2' into cont…
Nov 28, 2023
a3347d0
Add debugging utilities for naming and dependency tracing.
Dec 5, 2023
2b628c0
Add pre-built polynomial->linear conversions, refactor reductions
Dec 5, 2023
73d03ca
Debug DependenciesTest
Dec 5, 2023
94bb39c
Spawn task for duration-based using effects
Dec 5, 2023
d4efb02
Add Unstructured monads
Dec 6, 2023
401b34f
Regenerate supplemental monad methods, including Unstructured monads
Dec 6, 2023
c728c2a
Shorten default approximation window to fix stability issues
Dec 6, 2023
8fe2cb2
Demo using unstructured + default approximations to represent complic…
Dec 6, 2023
711a240
Change default error tolerance to 1%
Dec 8, 2023
47c7539
Trace effects on traced CellResources
Dec 8, 2023
c720ad9
Add binned function for polynomials
Dec 11, 2023
980b862
Debug discrete precomputed resources
Dec 11, 2023
7d1bb01
Test precomputed linear resources
Dec 12, 2023
69ddc6c
Use discrete resource thresholds and signalling for clock comparisons.
Dec 12, 2023
6e28259
Add scale function for polynomial resources.
Dec 12, 2023
465130c
Rename CellResource -> MutableResource
Dec 12, 2023
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
Prev Previous commit
Next Next commit
Debug discrete precomputed resources
  • Loading branch information
David Legg committed Dec 11, 2023
commit 980b86296e0525d675160557bf858e0a01679770
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
import gov.nasa.jpl.aerie.contrib.streamline.core.*;
import gov.nasa.jpl.aerie.contrib.streamline.core.CellRefV2.CommutativityTestInput;
import gov.nasa.jpl.aerie.contrib.streamline.core.monads.ResourceMonad;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks.Clock;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.monads.DiscreteDynamicsMonad;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.monads.DiscreteMonad;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.monads.DiscreteResourceMonad;
@@ -27,6 +28,8 @@
import static gov.nasa.jpl.aerie.contrib.streamline.core.Reactions.every;
import static gov.nasa.jpl.aerie.contrib.streamline.core.Reactions.whenever;
import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.*;
import static gov.nasa.jpl.aerie.contrib.streamline.core.monads.ResourceMonad.bind;
import static gov.nasa.jpl.aerie.contrib.streamline.core.monads.ResourceMonad.pure;
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.ClockResources.clock;
@@ -122,13 +125,13 @@ public static <V, T extends Dynamics<Duration, T>> Resource<Discrete<V>> sampled
public static <V> Resource<Discrete<V>> precomputed(
final V valueBeforeFirstEntry, final NavigableMap<Duration, V> segments) {
var clock = clock();
return ResourceMonad.bind(clock, clock$ -> {
return signalling(bind(clock, (Clock clock$) -> {
var t = clock$.extract();
var entry = segments.floorEntry(t);
var value = entry == null ? valueBeforeFirstEntry : entry.getValue();
var nextTime = expiry(Optional.ofNullable(segments.higherKey(t)));
return ResourceMonad.pure(expiring(discrete(value), nextTime.minus(t)));
});
return pure(expiring(discrete(value), nextTime.minus(t)));
}));
}

/**
@@ -246,7 +249,7 @@ public static Resource<Discrete<Boolean>> not(Resource<Discrete<Boolean>> operan
* Resource-level if-then-else logic.
*/
public static <D> Resource<D> choose(Resource<Discrete<Boolean>> condition, Resource<D> thenCase, Resource<D> elseCase) {
var result = ResourceMonad.bind(condition, c -> c.extract() ? thenCase : elseCase);
var result = bind(condition, c -> c.extract() ? thenCase : elseCase);
// Manually add dependencies, since short-circuiting will break automatic dependency tracking.
addDependency(result, thenCase);
addDependency(result, elseCase);
Original file line number Diff line number Diff line change
@@ -486,7 +486,7 @@ public static Resource<Discrete<Boolean>> lessThanOrEquals(Resource<Polynomial>
* The next entry in the map is the exclusive upper bound of that range.
*/
public static <A> Resource<Discrete<A>> binned(Resource<Polynomial> p, Resource<Discrete<NavigableMap<Double, A>>> bins) {
return signalling(ResourceMonad.bind(p, bins, (p$, bins$) -> {
return signalling(bind(p, bins, (Polynomial p$, Discrete<NavigableMap<Double, A>> bins$) -> {
var entry = bins$.extract().floorEntry(p$.extract());
if (entry == null) {
throw new IllegalStateException(
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete;

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;
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;

import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.currentValue;
import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.DiscreteResources.precomputed;
import static gov.nasa.jpl.aerie.merlin.framework.ModelActions.delay;
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.*;
import static org.junit.jupiter.api.Assertions.*;

/**
* Tests for {@link DiscreteResources#precomputed}
*/
@ExtendWith(MerlinExtension.class)
@TestInstance(Lifecycle.PER_CLASS)
public class PrecomputedTest {
public PrecomputedTest(final Registrar registrar) {
Resources.init();
}

final Resource<Discrete<Integer>> precomputedAsAConstant =
precomputed(4, new TreeMap<>());
@Test
void precomputed_with_no_transitions_uses_default_value_forever() {
assertEquals(4, currentValue(precomputedAsAConstant));
delay(HOUR);
assertEquals(4, currentValue(precomputedAsAConstant));
delay(HOUR);
assertEquals(4, currentValue(precomputedAsAConstant));
}

final Resource<Discrete<Integer>> precomputedWithOneTransitionInFuture =
precomputed(0, new TreeMap<>(Map.of(MINUTE, 10)));
@Test
void precomputed_with_transition_in_future_changes_at_that_time() {
assertEquals(0, currentValue(precomputedWithOneTransitionInFuture));
assertTransition(precomputedWithOneTransitionInFuture, MINUTE, 10);
delay(HOUR);
assertEquals(10, currentValue(precomputedWithOneTransitionInFuture));
delay(HOUR);
assertEquals(10, currentValue(precomputedWithOneTransitionInFuture));
}

final Resource<Discrete<Integer>> precomputedWithOneTransitionInPast =
precomputed(0, new TreeMap<>(Map.of(duration(-1, MINUTE), 10)));
@Test
void precomputed_with_transition_in_past_uses_that_value_forever() {
assertEquals(10, currentValue(precomputedWithOneTransitionInPast));
delay(HOUR);
assertEquals(10, currentValue(precomputedWithOneTransitionInPast));
delay(HOUR);
assertEquals(10, currentValue(precomputedWithOneTransitionInPast));
}

final Resource<Discrete<Integer>> precomputedWithMultipleTransitionsInFuture =
precomputed(0, new TreeMap<>(Map.of(
duration(2, MINUTE), 5,
duration(5, MINUTE), 10,
duration(6, MINUTE), 15)));
@Test
void precomputed_with_multiple_transitions_in_future_goes_through_each_in_turn() {
assertEquals(0, currentValue(precomputedWithMultipleTransitionsInFuture));
assertTransition(precomputedWithMultipleTransitionsInFuture, duration(2, MINUTE), 5);
assertTransition(precomputedWithMultipleTransitionsInFuture, duration(3, MINUTE), 10);
assertTransition(precomputedWithMultipleTransitionsInFuture, duration(1, MINUTE), 15);
delay(HOUR);
assertEquals(15, currentValue(precomputedWithMultipleTransitionsInFuture));
delay(HOUR);
assertEquals(15, currentValue(precomputedWithMultipleTransitionsInFuture));
}

final Resource<Discrete<Integer>> precomputedWithMultipleTransitionsInPast =
precomputed(0, new TreeMap<>(Map.of(
duration(-2, MINUTE), 5,
duration(-5, MINUTE), 10,
duration(-6, MINUTE), 15)));
@Test
void precomputed_with_multiple_transition_in_past_uses_last_value_forever() {
assertEquals(5, currentValue(precomputedWithMultipleTransitionsInPast));
delay(HOUR);
assertEquals(5, currentValue(precomputedWithMultipleTransitionsInPast));
delay(HOUR);
assertEquals(5, currentValue(precomputedWithMultipleTransitionsInPast));
}

final Resource<Discrete<Integer>> precomputedWithTransitionsInPastAndFuture =
precomputed(0, new TreeMap<>(Map.of(
duration(-5, MINUTE), 25,
duration(-2, MINUTE), 5,
duration(5, MINUTE), 10,
duration(6, MINUTE), 15)));
@Test
void precomputed_with_transitions_in_past_and_future_chooses_starting_value_and_changes_later() {
assertEquals(5, currentValue(precomputedWithTransitionsInPastAndFuture));
assertTransition(precomputedWithTransitionsInPastAndFuture, duration(5, MINUTE), 10);
assertTransition(precomputedWithTransitionsInPastAndFuture, duration(1, MINUTE), 15);
delay(HOUR);
assertEquals(15, currentValue(precomputedWithTransitionsInPastAndFuture));
delay(HOUR);
assertEquals(15, currentValue(precomputedWithTransitionsInPastAndFuture));
}

final Resource<Discrete<Integer>> precomputedWithInstantKeys =
precomputed(0, new TreeMap<>(Map.of(
Instant.parse("2023-10-17T23:55:00Z"), 25,
Instant.parse("2023-10-17T23:58:00Z"), 5,
Instant.parse("2023-10-18T00:05:00Z"), 10,
Instant.parse("2023-10-18T00:06:00Z"), 15)),
Instant.parse("2023-10-18T00:00:00Z"));
@Test
void precomputed_with_instant_keys_behaves_identically_to_equivalent_duration_offsets() {
assertEquals(5, currentValue(precomputedWithInstantKeys));
assertTransition(precomputedWithInstantKeys, duration(5, MINUTE), 10);
assertTransition(precomputedWithInstantKeys, duration(1, MINUTE), 15);
delay(HOUR);
assertEquals(15, currentValue(precomputedWithInstantKeys));
delay(HOUR);
assertEquals(15, currentValue(precomputedWithInstantKeys));
}

private <A> void assertTransition(Resource<Discrete<A>> resource, Duration transitionDelay, A expectedValue) {
A startValue = currentValue(resource);
delay(transitionDelay.minus(EPSILON));
assertEquals(startValue, currentValue(resource));
delay(EPSILON);
assertEquals(expectedValue, currentValue(resource));
}
}