From 6bd6ba282db1afd829ef0a04203428de40fbb25d Mon Sep 17 00:00:00 2001 From: JoelCourtney Date: Fri, 15 Sep 2023 16:10:12 -0700 Subject: [PATCH] Implement connectTo --- .../constraints/json/ConstraintParsers.java | 12 ++++ .../jpl/aerie/constraints/time/Interval.java | 24 +++++++ .../constraints/tree/SpansConnectTo.java | 65 +++++++++++++++++++ .../jpl/aerie/constraints/tree/ASTTests.java | 62 ++++++++++++++++++ .../src/libs/constraints-ast.ts | 12 +++- .../src/libs/constraints-edsl-fluent-api.ts | 38 +++++++++++ ...ConstraintsDSLCompilationServiceTests.java | 22 +++++++ 7 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/SpansConnectTo.java 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 80b944c07a..466b245a76 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 @@ -593,6 +593,17 @@ static JsonParser spansFromWindowsF(JsonParser tuple(Unit.UNIT, $.expression())); } + static JsonParser connectTo(JsonParser> spansExpressionP) { + return productP + .field("kind", literalP("SpansExpressionConnectTo")) + .field("from", spansExpressionP) + .field("to", spansExpressionP) + .map( + untuple((kind, from, to) -> new SpansConnectTo(from, to)), + $ -> tuple(Unit.UNIT, $.from(), $.to()) + ); + } + private static final JsonParser spansIntervalP = productP .field("kind", literalP("SpansExpressionInterval")) @@ -607,6 +618,7 @@ private static JsonParser> spansExpressionF(JsonParser from, Expression to) implements Expression { + + @Override + public Spans evaluate(final SimulationResults results, final Interval bounds, final EvaluationEnvironment environment) { + final var from = this.from.evaluate(results, bounds, environment); + final var sortedFrom = StreamSupport.stream(from.spliterator(), true).sorted((l, r) -> l.interval().compareEnds(r.interval())).toList(); + final var to = this.to.evaluate(results, bounds, environment); + final var sortedTo = StreamSupport.stream(to.spliterator(), true).sorted((l, r) -> l.interval().compareStarts(r.interval())).toList(); + final var result = new Spans(); + var toIndex = 0; + for (final var span: sortedFrom) { + final var startTime = span.interval().end; + while (toIndex < sortedTo.size() && span.interval().compareEndToStart(sortedTo.get(toIndex).interval()) == 1) { + toIndex++; + } + final Duration endTime; + final Interval.Inclusivity endInlusivity; + if (toIndex == sortedTo.size()) { + endTime = bounds.end; + endInlusivity = bounds.endInclusivity; + } + else { + endTime = sortedTo.get(toIndex).interval().start; + endInlusivity = Interval.Inclusivity.Inclusive; + } + result.add( + Interval.between( + startTime, + Interval.Inclusivity.Inclusive, + endTime, + endInlusivity + ), + span.value() + ); + } + return result; + } + + @Override + public void extractResources(final Set names) { + this.from.extractResources(names); + this.to.extractResources(names); + } + + @Override + public String prettyPrint(final String prefix) { + return String.format( + "\n%s(connect from %s to %s)", + prefix, + this.from.prettyPrint(), + this.to.prettyPrint() + ); + } +} 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 80196ce03e..bcd37bc3e2 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 @@ -1127,6 +1127,68 @@ public void testShiftWindowsEdgesBoundsAdjustment() { assertIterableEquals(expected2, result2); } + @Test + void testSpansConnectTo() { + final var simResults = new SimulationResults( + Instant.EPOCH, Interval.between(0, Inclusive, 20, Exclusive, SECONDS), + List.of(), + Map.of(), + Map.of() + ); + + final var result = new SpansConnectTo( + Supplier.of(new Spans( + Interval.between(0, 1, SECONDS), + Interval.between(5, 7, SECONDS), + Interval.between(10, Inclusive, 11, Exclusive, SECONDS), + Interval.between(13, 14, SECONDS) + )), + Supplier.of(new Spans( + Interval.between(2, 3, SECONDS), + Interval.between(4, 6, SECONDS), + Interval.between(8, 9, SECONDS), + Interval.between(11, 12, SECONDS) + )) + ).evaluate(simResults); + + final var expected = new Spans( + Interval.between(1, 2, SECONDS), + Interval.between(7, 8, SECONDS), + Interval.at(11, SECONDS), + Interval.between(14, Inclusive, 20, Exclusive, SECONDS) + ); + + assertIterableEquals(expected, result); + } + + @Test + void testSpansConnectToMetadata() { + final var simResults = new SimulationResults( + Instant.EPOCH, Interval.between(0, Inclusive, 20, Exclusive, SECONDS), + List.of(), + Map.of(), + Map.of() + ); + + final var result = new SpansConnectTo( + Supplier.of(new Spans( + Segment.of(Interval.between(0, 1, SECONDS), Optional.of(new Spans.Metadata(new ActivityInstance(2, "2", Map.of(), FOREVER)))), + Segment.of(Interval.between(5, 7, SECONDS), Optional.empty()) + )), + Supplier.of(new Spans( + Interval.between(2, 3, SECONDS), + Interval.between(8, 9, SECONDS) + )) + ).evaluate(simResults); + + final var expected = new Spans( + Segment.of(Interval.between(1, 2, SECONDS), Optional.of(new Spans.Metadata(new ActivityInstance(2, "2", Map.of(), FOREVER)))), + Segment.of(Interval.between(7, 8, SECONDS), Optional.empty()) + ); + + assertIterableEquals(expected, result); + } + @Test public void testRollingThresholdExcess() { final var simResults = new SimulationResults( 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 edda4420fc..980bdd1008 100644 --- a/merlin-server/constraints-dsl-compiler/src/libs/constraints-ast.ts +++ b/merlin-server/constraints-dsl-compiler/src/libs/constraints-ast.ts @@ -27,6 +27,7 @@ export enum NodeKind { WindowsExpressionFromSpans = 'WindowsExpressionFromSpans', SpansExpressionFromWindows = 'SpansExpressionFromWindows', SpansExpressionSplit = 'SpansExpressionSplit', + SpansExpressionConnectTo = 'SpansExpressionConnectTo', SpansExpressionInterval = 'SpansExpressionInterval', SpansSelectWhenTrue = 'SpansSelectWhenTrue', ExpressionEqual = 'ExpressionEqual', @@ -122,8 +123,9 @@ export type SpansExpression = | IntervalsExpressionShiftEdges | SpansExpressionFromWindows | ForEachActivitySpans - | SpansExpressionInterval - | SpansSelectWhenTrue; + | SpansSelectWhenTrue + | SpansExpressionConnectTo + | SpansExpressionInterval; export interface SpansSelectWhenTrue { kind: NodeKind.SpansSelectWhenTrue, @@ -250,6 +252,12 @@ export interface SpansExpressionSplit { internalEndInclusivity: API.Inclusivity } +export interface SpansExpressionConnectTo { + kind: NodeKind.SpansExpressionConnectTo, + from: SpansExpression, + to: SpansExpression +} + export interface SpansExpressionInterval { kind: NodeKind.SpansExpressionInterval, interval: IntervalExpression 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 449eddce06..7eb85e8600 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 @@ -451,6 +451,28 @@ export class Spans { }) } + /** + * Connects the end of each of these spans to the start of the nearest span in the argument. + * + * This operation creates a new spans object. For each span `s` in `this`, it produces a span from + * the end of `s` to the start of the first span in `other` that occurs after the end of `s`. + * + * If `s` and the nearest subsequent span in `other` meet exactly, with no intersection and no + * space between them, a singleton span (containing exactly one time) is still created at the meeting point. + * + * If there are no spans in `other` that occur after `s`, a span is still created from the end of `s` until the + * end of the plan. + * + * @param other + */ + public connectTo(other: Spans): Spans { + return new Spans({ + kind: AST.NodeKind.SpansExpressionConnectTo, + from: this.__astNode, + to: other.__astNode + }) + } + /** * Replaces each Span with its start point. */ @@ -1335,6 +1357,22 @@ declare global { */ public static FromInterval(interval: Interval): Spans; + /** + * Connects the end of each of these spans to the start of the nearest span in the argument. + * + * This operation creates a new spans object. For each span `s` in `this`, it produces a span from + * the end of `s` to the start of the first span in `other` that occurs after the end of `s`. + * + * If `s` and the nearest subsequent span in `other` meet exactly, with no intersection and no + * space between them, a singleton span (containing exactly one time) is still created at the meeting point. + * + * If there are no spans in `other` that occur after `s`, a span is still created from the end of `s` until the + * end of the plan. + * + * @param other + */ + public connectTo(other: Spans): Spans; + /** * Returns the instantaneous start points of the these 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 f291fe4ce2..a00b71f85d 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 @@ -36,6 +36,7 @@ import gov.nasa.jpl.aerie.constraints.tree.ShiftBy; import gov.nasa.jpl.aerie.constraints.tree.ShiftEdges; import gov.nasa.jpl.aerie.constraints.tree.ShorterThan; +import gov.nasa.jpl.aerie.constraints.tree.SpansConnectTo; import gov.nasa.jpl.aerie.constraints.tree.SpansFromWindows; import gov.nasa.jpl.aerie.constraints.tree.SpansSelectWhenTrue; import gov.nasa.jpl.aerie.constraints.tree.Split; @@ -1378,4 +1379,25 @@ export default() => { ) ); } + @Test + void testSpansConnectTo() { + checkSuccessfulCompilation( + """ + export default () => { + return Windows.Value(true).spans().connectTo( + Windows.Value(false).spans() + ).windows(); + } + """, + new ViolationsOfWindows( + new WindowsFromSpans( + new SpansConnectTo( + new SpansFromWindows(new WindowsValue(true)), + new SpansFromWindows(new WindowsValue(false)) + ) + ) + ) + ); + } + }