diff --git a/constraints/src/test/java/gov/nasa/jpl/aerie/constraints/json/ConstraintParsersTest.java b/constraints/src/test/java/gov/nasa/jpl/aerie/constraints/json/ConstraintParsersTest.java index 087a2ba975..cf652941c9 100644 --- a/constraints/src/test/java/gov/nasa/jpl/aerie/constraints/json/ConstraintParsersTest.java +++ b/constraints/src/test/java/gov/nasa/jpl/aerie/constraints/json/ConstraintParsersTest.java @@ -1,6 +1,5 @@ package gov.nasa.jpl.aerie.constraints.json; -import gov.nasa.jpl.aerie.constraints.time.Spans; import gov.nasa.jpl.aerie.constraints.tree.All; import gov.nasa.jpl.aerie.constraints.tree.Changes; import gov.nasa.jpl.aerie.constraints.tree.DiscreteParameter; @@ -23,6 +22,7 @@ import gov.nasa.jpl.aerie.constraints.tree.RealParameter; import gov.nasa.jpl.aerie.constraints.tree.RealResource; import gov.nasa.jpl.aerie.constraints.tree.RealValue; +import gov.nasa.jpl.aerie.constraints.tree.Split; import gov.nasa.jpl.aerie.constraints.tree.SpansFromWindows; import gov.nasa.jpl.aerie.constraints.tree.StartOf; import gov.nasa.jpl.aerie.constraints.tree.Times; @@ -493,6 +493,57 @@ public void testParseInvert() { assertEquivalent(expected, result); } + @Test + public void testParseSplitWindows() { + final var json = Json + .createObjectBuilder() + .add("kind", "IntervalsExpressionSplit") + .add("intervals", Json + .createObjectBuilder() + .add("kind", "WindowsExpressionActivityWindow") + .add("alias", "A")) + .add("numberOfSubIntervals", 3) + .build(); + + final var result = windowsExpressionP.parse(json).getSuccessOrThrow(); + + final var expected = + new Split<>( + new ActivityWindow("A"), + 3 + ); + + assertEquivalent(expected, result); + } + + @Test + public void testParseSplitSpans() { + final var json = Json + .createObjectBuilder() + .add("kind", "IntervalsExpressionSplit") + .add("intervals", Json.createObjectBuilder() + .add("kind", "SpansExpressionFromWindows") + .add("windowsExpression", Json + .createObjectBuilder() + .add("kind", "WindowsExpressionActivityWindow") + .add("alias", "A")) + ) + .add("numberOfSubIntervals", 3) + .build(); + + final var result = spansExpressionP.parse(json).getSuccessOrThrow(); + + final var expected = + new Split<>( + new SpansFromWindows( + new ActivityWindow("A") + ), + 3 + ); + + assertEquivalent(expected, result); + } + @Test public void testForEachActivity() { final var json = Json diff --git a/constraints/src/test/java/gov/nasa/jpl/aerie/constraints/tree/ASTTests.java b/constraints/src/test/java/gov/nasa/jpl/aerie/constraints/tree/ASTTests.java index 51e27c72ff..4f40fe38e3 100644 --- a/constraints/src/test/java/gov/nasa/jpl/aerie/constraints/tree/ASTTests.java +++ b/constraints/src/test/java/gov/nasa/jpl/aerie/constraints/tree/ASTTests.java @@ -1,6 +1,7 @@ package gov.nasa.jpl.aerie.constraints.tree; import gov.nasa.jpl.aerie.constraints.InputMismatchException; +import gov.nasa.jpl.aerie.constraints.UnsplittableIntervalException; import gov.nasa.jpl.aerie.constraints.model.ActivityInstance; import gov.nasa.jpl.aerie.constraints.model.DiscreteProfile; import gov.nasa.jpl.aerie.constraints.model.DiscreteProfilePiece; @@ -8,6 +9,7 @@ import gov.nasa.jpl.aerie.constraints.model.LinearProfilePiece; import gov.nasa.jpl.aerie.constraints.model.SimulationResults; import gov.nasa.jpl.aerie.constraints.model.Violation; +import gov.nasa.jpl.aerie.constraints.time.Spans; import gov.nasa.jpl.aerie.constraints.time.Window; import gov.nasa.jpl.aerie.constraints.time.Windows; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; @@ -21,8 +23,9 @@ import static gov.nasa.jpl.aerie.constraints.Assertions.assertEquivalent; import static gov.nasa.jpl.aerie.constraints.time.Window.Inclusivity.Exclusive; import static gov.nasa.jpl.aerie.constraints.time.Window.Inclusivity.Inclusive; -import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.MICROSECOND; +import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.MICROSECONDS; import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.SECONDS; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; public class ASTTests { @@ -50,6 +53,76 @@ public void testNot() { assertEquivalent(expected, result); } + @Test + public void testSplitWindows() { + final var simResults = new SimulationResults( + Window.between(0, 20, SECONDS), + List.of(), + Map.of(), + Map.of() + ); + + final var windows = new Windows(); + windows.add(Window.between(0, Inclusive, 5, Exclusive, SECONDS)); + windows.add(Window.between(10000000, Exclusive, 15000001, Exclusive, MICROSECONDS)); + + final var result = new Split(Supplier.of(windows), 3).evaluate(simResults, Map.of()); + + final var expected = new Windows(); + expected.add(Window.between(0, Inclusive, 1666666, Exclusive, MICROSECONDS)); + expected.add(Window.between(1666666, Exclusive, 3333332, Exclusive, MICROSECONDS)); + expected.add(Window.between(3333332, Exclusive, 5000000, Exclusive, MICROSECONDS)); + + expected.add(Window.between(10000000, Exclusive, 11666667, Exclusive, MICROSECONDS)); + expected.add(Window.between(11666667, Exclusive, 13333334, Exclusive, MICROSECONDS)); + expected.add(Window.between(13333334, Exclusive, 15000001, Exclusive, MICROSECONDS)); + + assertEquivalent(expected, result); + } + + @Test + public void testSplitSpans() { + final var simResults = new SimulationResults( + Window.between(0, 20, SECONDS), + List.of(), + Map.of(), + Map.of() + ); + + final var spans = new Spans(); + spans.add(Window.between(0, Inclusive, 5, Exclusive, SECONDS)); + spans.add(Window.between(0, Exclusive, 5000001, Exclusive, MICROSECONDS)); + + final var result = new Split(Supplier.of(spans), 3).evaluate(simResults, Map.of()); + + final var expected = new Spans(); + expected.add(Window.between(0, Inclusive, 1666666, Exclusive, MICROSECONDS)); + expected.add(Window.between(1666666, Exclusive, 3333332, Exclusive, MICROSECONDS)); + expected.add(Window.between(3333332, Exclusive, 5000000, Exclusive, MICROSECONDS)); + + expected.add(Window.between(0, Exclusive, 1666667, Exclusive, MICROSECONDS)); + expected.add(Window.between(1666667, Exclusive, 3333334, Exclusive, MICROSECONDS)); + expected.add(Window.between(3333334, Exclusive, 5000001, Exclusive, MICROSECONDS)); + + assertEquivalent(expected, result); + } + + @Test + public void testUnsplittableInterval() { + final var simResults = new SimulationResults( + Window.between(0, 20, SECONDS), + List.of(), + Map.of(), + Map.of() + ); + + final var spans = new Spans( + Window.at(5, SECONDS) + ); + + assertThrows(UnsplittableIntervalException.class, () -> new Split(Supplier.of(spans), 3).evaluate(simResults, Map.of())); + } + @Test public void testAnd() { final var simResults = new SimulationResults( diff --git a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintsDSLCompilationServiceTests.java b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintsDSLCompilationServiceTests.java index 0658102284..293ed5f141 100644 --- a/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintsDSLCompilationServiceTests.java +++ b/merlin-server/src/test/java/gov/nasa/jpl/aerie/merlin/server/services/ConstraintsDSLCompilationServiceTests.java @@ -559,6 +559,78 @@ export default () => { ); } + @Test + void testSplit() { + checkSuccessfulCompilation( + """ + export default () => { + return Real.Resource("state of charge").lessThan(0.3).split(4) + } + """, + new ViolationsOf( + new Split<>( + new LessThan(new RealResource("state of charge"), new RealValue(0.3)), + 4 + ) + ) + ); + + checkSuccessfulCompilation( + """ + export default () => { + return Real.Resource("state of charge").lessThan(0.3).spans().split(4).windows() + } + """, + new ViolationsOf( + new WindowsFromSpans( + new Split<>( + new SpansFromWindows(new LessThan(new RealResource("state of charge"), new RealValue(0.3))), + 4 + ) + ) + ) + ); + } + + @Test + void testSplitArgumentError() { + checkFailedCompilation( + """ + export default () => { + return Real.Resource("state of charge").lessThan(0.3).split(0) + } + """, + ".split numberOfSubWindows cannot be less than 1, but was: 0" + ); + + checkFailedCompilation( + """ + export default () => { + return Real.Resource("state of charge").lessThan(0.3).split(-2) + } + """, + ".split numberOfSubWindows cannot be less than 1, but was: -2" + ); + + checkFailedCompilation( + """ + export default () => { + return Real.Resource("state of charge").lessThan(0.3).spans().split(0).windows() + } + """, + ".split numberOfSubSpans cannot be less than 1, but was: 0" + ); + + checkFailedCompilation( + """ + export default () => { + return Real.Resource("state of charge").lessThan(0.3).spans().split(-2).windows() + } + """, + ".split numberOfSubSpans cannot be less than 1, but was: -2" + ); + } + @Test void testViolations() { checkSuccessfulCompilation(