diff --git a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/json/ConstraintParsers.java b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/json/ConstraintParsers.java index 10017ac713..9397cec074 100644 --- a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/json/ConstraintParsers.java +++ b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/json/ConstraintParsers.java @@ -335,15 +335,15 @@ static JsonParser transitionP(JsonParser> profi $ -> tuple(Unit.UNIT, $.value(), $.interval()) ); - static JsonParser shiftWindowsEdgesF(JsonParser> windowsExpressionP) { + static > JsonParser> shiftEdgesF(final JsonParser> intervalExpressionP) { return productP - .field("kind", literalP("WindowsExpressionShiftBy")) - .field("windowExpression", windowsExpressionP) + .field("kind", literalP("IntervalsExpressionShiftEdges")) + .field("expression", intervalExpressionP) .field("fromStart", durationExprP) .field("fromEnd", durationExprP) .map( - untuple((kind, windowsExpression, fromStart, fromEnd) -> new ShiftWindowsEdges(windowsExpression, fromStart, fromEnd)), - $ -> tuple(Unit.UNIT, $.windows, $.fromStart, $.fromEnd)); + untuple((kind, expr, fromStart, fromEnd) -> new ShiftEdges(expr, fromStart, fromEnd)), + $ -> tuple(Unit.UNIT, $.expression, $.fromStart, $.fromEnd)); } static final JsonParser endOfP = productP @@ -562,7 +562,7 @@ private static JsonParser> windowsExpressionF(JsonParser> spansExpressionF(JsonParser> { LinearProfile accumulatedDuration(final Duration unit); T starts(); T ends(); + T shiftEdges(final Duration fromStart, final Duration fromEnd); + T select(final Interval... intervals); } diff --git a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/time/Spans.java b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/time/Spans.java index 1d3f89c92d..90596d66b9 100644 --- a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/time/Spans.java +++ b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/time/Spans.java @@ -215,6 +215,16 @@ public Spans ends() { return this.map($ -> Interval.at($.end)); } + @Override + public Spans shiftEdges(final Duration fromStart, final Duration fromEnd) { + return this.map($ -> Interval.between($.start.plus(fromStart), $.startInclusivity, $.end.plus(fromEnd), $.endInclusivity)); + } + + @Override + public Spans select(final Interval... intervals) { + return this.flatMap($ -> Arrays.stream(intervals).map(selection -> Interval.intersect($, selection))); + } + @Override public boolean equals(final Object obj) { if (!(obj instanceof final Spans spans)) return false; diff --git a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/time/Windows.java b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/time/Windows.java index 651faafd54..a083641eab 100644 --- a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/time/Windows.java +++ b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/time/Windows.java @@ -342,6 +342,7 @@ public Windows filterByDuration(Duration minDur, Duration maxDur) { * @param fromEnd duration to shift true -> false falling edges * @return a new Windows */ + @Override public Windows shiftEdges(Duration fromStart, Duration fromEnd) { final var builder = IntervalMap.builder(); @@ -622,6 +623,7 @@ public Windows unset(final List intervals) { } /** Delegated to {@link IntervalMap#select(Interval...)} */ + @Override public Windows select(final Interval... intervals) { return new Windows(segments.select(intervals)); } diff --git a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/ShiftWindowsEdges.java b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/ShiftEdges.java similarity index 59% rename from constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/ShiftWindowsEdges.java rename to constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/ShiftEdges.java index 0db61054bc..67f1c1ce99 100644 --- a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/ShiftWindowsEdges.java +++ b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/ShiftEdges.java @@ -3,25 +3,25 @@ import gov.nasa.jpl.aerie.constraints.model.EvaluationEnvironment; import gov.nasa.jpl.aerie.constraints.model.SimulationResults; import gov.nasa.jpl.aerie.constraints.time.Interval; -import gov.nasa.jpl.aerie.constraints.time.Windows; +import gov.nasa.jpl.aerie.constraints.time.IntervalContainer; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import java.util.Objects; import java.util.Set; -public final class ShiftWindowsEdges implements Expression { - public final Expression windows; +public final class ShiftEdges> implements Expression { + public final Expression expression; public final Expression fromStart; public final Expression fromEnd; - public ShiftWindowsEdges(final Expression left, final Expression fromStart, final Expression fromEnd) { - this.windows = left; + public ShiftEdges(final Expression left, final Expression fromStart, final Expression fromEnd) { + this.expression = left; this.fromStart = fromStart; this.fromEnd = fromEnd; } @Override - public Windows evaluate(final SimulationResults results, final Interval bounds, final EvaluationEnvironment environment) { + public I evaluate(final SimulationResults results, final Interval bounds, final EvaluationEnvironment environment) { final var shiftRising = this.fromStart.evaluate(results, bounds, environment); final var shiftFalling = this.fromEnd.evaluate(results, bounds, environment); @@ -32,21 +32,21 @@ public Windows evaluate(final SimulationResults results, final Interval bounds, bounds.endInclusivity ); - final var windows = this.windows.evaluate(results, newBounds, environment); - return windows.shiftEdges(shiftRising, shiftFalling).select(bounds); + final var intervals = this.expression.evaluate(results, newBounds, environment); + return intervals.shiftEdges(shiftRising, shiftFalling).select(bounds); } @Override public void extractResources(final Set names) { - this.windows.extractResources(names); + this.expression.extractResources(names); } @Override public String prettyPrint(final String prefix) { return String.format( - "\n%s(shiftWindowsEdges %s by %s %s)", + "\n%s(shiftEdges %s by %s %s)", prefix, - this.windows.prettyPrint(prefix + " "), + this.expression.prettyPrint(prefix + " "), this.fromStart.toString(), this.fromEnd.toString() ); @@ -54,16 +54,16 @@ public String prettyPrint(final String prefix) { @Override public boolean equals(Object obj) { - if (!(obj instanceof ShiftWindowsEdges)) return false; - final var o = (ShiftWindowsEdges)obj; + if (!(obj instanceof ShiftEdges)) return false; + final var o = (ShiftEdges)obj; - return Objects.equals(this.windows, o.windows) && + return Objects.equals(this.expression, o.expression) && Objects.equals(this.fromStart, o.fromStart) && Objects.equals(this.fromEnd, o.fromEnd); } @Override public int hashCode() { - return Objects.hash(this.windows, this.fromStart, this.fromEnd); + return Objects.hash(this.expression, this.fromStart, this.fromEnd); } } 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 c6ac01d3cf..f51289e90c 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 @@ -378,7 +378,7 @@ public void testExpandBy() { final var expandByFromStart = Duration.of(-1, SECONDS); final var expandByFromEnd = Duration.of(0, SECONDS); - final var result = new ShiftWindowsEdges(Supplier.of(left), Supplier.of(expandByFromStart), Supplier.of(expandByFromEnd)).evaluate(simResults, new EvaluationEnvironment()); + final var result = new ShiftEdges<>(Supplier.of(left), Supplier.of(expandByFromStart), Supplier.of(expandByFromEnd)).evaluate(simResults, new EvaluationEnvironment()); final var expected = new Windows() .set(Interval.between(-1, Inclusive, 7, Inclusive, SECONDS), true) @@ -410,7 +410,7 @@ public void testShrink() { final var clampFromStart = Duration.of(1, SECONDS); final var clampFromEnd = Duration.negate(Duration.of(1, SECONDS)); - final var result = new ShiftWindowsEdges(Supplier.of(left), Supplier.of(clampFromStart), Supplier.of(clampFromEnd)).evaluate(simResults, new EvaluationEnvironment()); + final var result = new ShiftEdges<>(Supplier.of(left), Supplier.of(clampFromStart), Supplier.of(clampFromEnd)).evaluate(simResults, new EvaluationEnvironment()); final var expected = new Windows() .set(Interval.between(1, Inclusive, 4, Exclusive, SECONDS), true) @@ -1092,7 +1092,7 @@ public void testShiftWindowsEdgesBoundsAdjustment() { final var crossingStartOfPlan = new Windows(false).set(Interval.between(-1, 1, SECONDS), true); - final var result1 = new ShiftWindowsEdges( + final var result1 = new ShiftEdges<>( Supplier.of(crossingStartOfPlan), Supplier.of(Duration.ZERO), Supplier.of(Duration.of(10, SECONDS)) @@ -1100,7 +1100,7 @@ public void testShiftWindowsEdgesBoundsAdjustment() { final var expected1 = new Windows(false).set(Interval.between(-1, 11, SECONDS), true).select(simResults.bounds); assertIterableEquals(expected1, result1); - final var result2 = new ShiftWindowsEdges( + final var result2 = new ShiftEdges<>( Supplier.of(crossingStartOfPlan), Supplier.of(Duration.of(-10, SECONDS)), Supplier.of(Duration.ZERO) @@ -1110,7 +1110,7 @@ public void testShiftWindowsEdgesBoundsAdjustment() { final var crossingEndOfPlan = new Windows(false).set(Interval.between(19, 21, SECONDS), true); - final var result3 = new ShiftWindowsEdges( + final var result3 = new ShiftEdges<>( Supplier.of(crossingEndOfPlan), Supplier.of(Duration.ZERO), Supplier.of(Duration.of(10, SECONDS)) @@ -1118,7 +1118,7 @@ public void testShiftWindowsEdgesBoundsAdjustment() { final var expected3 = new Windows(false).set(Interval.between(19, 20, SECONDS), true).select(simResults.bounds); assertIterableEquals(expected3, result3); - final var result4 = new ShiftWindowsEdges( + final var result4 = new ShiftEdges<>( Supplier.of(crossingEndOfPlan), Supplier.of(Duration.of(-10, SECONDS)), Supplier.of(Duration.ZERO) diff --git a/merlin-server/constraints-dsl-compiler/src/libs/constraints-ast.ts b/merlin-server/constraints-dsl-compiler/src/libs/constraints-ast.ts index b00da24a60..740a033614 100644 --- a/merlin-server/constraints-dsl-compiler/src/libs/constraints-ast.ts +++ b/merlin-server/constraints-dsl-compiler/src/libs/constraints-ast.ts @@ -24,7 +24,6 @@ export enum NodeKind { WindowsExpressionEndOf = 'WindowsExpressionEndOf', WindowsExpressionLongerThan = 'WindowsExpressionLongerThan', WindowsExpressionShorterThan = 'WindowsExpressionShorterThan', - WindowsExpressionShiftBy = 'WindowsExpressionShiftBy', WindowsExpressionFromSpans = 'WindowsExpressionFromSpans', SpansExpressionFromWindows = 'SpansExpressionFromWindows', SpansExpressionSplit = 'SpansExpressionSplit', @@ -40,6 +39,7 @@ export enum NodeKind { WindowsExpressionNot = 'WindowsExpressionNot', IntervalsExpressionStarts = 'IntervalsExpressionStarts', IntervalsExpressionEnds = 'IntervalsExpressionEnds', + IntervalsExpressionShiftEdges = 'IntervalsExpressionShiftEdges', ForEachActivitySpans = 'ForEachActivitySpans', ForEachActivityViolations = 'ForEachActivityViolations', ProfileChanges = 'ProfileChanges', @@ -107,7 +107,7 @@ export type WindowsExpression = | WindowsExpressionLongerThan | WindowsExpressionShorterThan | WindowsExpressionNot - | WindowsExpressionShiftBy + | IntervalsExpressionShiftEdges | WindowsExpressionFromSpans | IntervalsExpressionStarts | IntervalsExpressionEnds @@ -118,6 +118,7 @@ export type SpansExpression = | SpansExpressionSplit | IntervalsExpressionStarts | IntervalsExpressionEnds + | IntervalsExpressionShiftEdges | SpansExpressionFromWindows | ForEachActivitySpans | SpansExpressionInterval; @@ -148,9 +149,9 @@ export interface WindowsExpressionNot { expression: WindowsExpression; } -export interface WindowsExpressionShiftBy { - kind: NodeKind.WindowsExpressionShiftBy, - windowExpression: WindowsExpression, +export interface IntervalsExpressionShiftEdges { + kind: NodeKind.IntervalsExpressionShiftEdges, + expression: IntervalsExpression, fromStart: Duration, fromEnd: Duration, } diff --git a/merlin-server/constraints-dsl-compiler/src/libs/constraints-edsl-fluent-api.ts b/merlin-server/constraints-dsl-compiler/src/libs/constraints-edsl-fluent-api.ts index 4c996b643f..727b4fdef7 100644 --- a/merlin-server/constraints-dsl-compiler/src/libs/constraints-edsl-fluent-api.ts +++ b/merlin-server/constraints-dsl-compiler/src/libs/constraints-edsl-fluent-api.ts @@ -280,8 +280,8 @@ export class Windows { }); } else { return new Windows({ - kind: AST.NodeKind.WindowsExpressionShiftBy, - windowExpression: this.__astNode, + kind: AST.NodeKind.IntervalsExpressionShiftEdges, + expression: this.__astNode, fromStart, fromEnd }) @@ -471,6 +471,21 @@ export class Spans { }) } + /** + * Shifts the start and end of each Span by a duration. + * + * @param fromStart duration to shift start by + * @param fromEnd duration to shift end by (defaults is `fromStart` if omitted) + */ + public shiftBy(fromStart: AST.Duration, fromEnd?: AST.Duration | undefined): Spans { + return new Spans({ + kind: AST.NodeKind.IntervalsExpressionShiftEdges, + expression: this.__astNode, + fromStart, + fromEnd: fromEnd !== undefined ? fromEnd : fromStart + }) + } + /** * Convert this into a set of Windows. Each span is a true segment, and everything else is false. * @@ -1314,6 +1329,14 @@ declare global { */ public ends(): Spans; + /** + * Shifts the start and end of each Span by a duration. + * + * @param fromStart duration to shift start by + * @param fromEnd duration to shift end by (defaults is `fromStart` if omitted) + */ + public shiftBy(fromStart: AST.Duration, fromEnd?: AST.Duration | undefined): Spans; + /** * Splits each span into equal sized sub-spans. * 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 345e7a6215..bc5bfc63e6 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 @@ -33,7 +33,7 @@ import gov.nasa.jpl.aerie.constraints.tree.RealValue; import gov.nasa.jpl.aerie.constraints.tree.RollingThreshold; import gov.nasa.jpl.aerie.constraints.tree.ShiftBy; -import gov.nasa.jpl.aerie.constraints.tree.ShiftWindowsEdges; +import gov.nasa.jpl.aerie.constraints.tree.ShiftEdges; import gov.nasa.jpl.aerie.constraints.tree.ShorterThan; import gov.nasa.jpl.aerie.constraints.tree.SpansFromWindows; import gov.nasa.jpl.aerie.constraints.tree.Split; @@ -501,7 +501,7 @@ export default() => { return Real.Resource("state of charge").rate().equal(Real.Value(4.0)).shiftBy(minute(2), minute(-20)) } """, - new ViolationsOfWindows(new ShiftWindowsEdges( + new ViolationsOfWindows(new ShiftEdges( new Equal<>(new Rate(new RealResource("state of charge")), new RealValue(4.0)), new DurationLiteral(Duration.of(2, Duration.MINUTE)), new DurationLiteral(Duration.of(-20, Duration.MINUTE)))