Skip to content

Commit

Permalink
Merge pull request #1149 from NASA-AMMOS/1112-add-connectto-operation…
Browse files Browse the repository at this point in the history
…-between-spans

Implement connectTo
  • Loading branch information
JoelCourtney authored Sep 26, 2023
2 parents 2f8c8c3 + 6bd6ba2 commit 50d69e6
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,17 @@ static JsonParser<SpansFromWindows> spansFromWindowsF(JsonParser<Expression<Wind
$ -> tuple(Unit.UNIT, $.expression()));
}

static JsonParser<SpansConnectTo> connectTo(JsonParser<Expression<Spans>> 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<SpansInterval> spansIntervalP =
productP
.field("kind", literalP("SpansExpressionInterval"))
Expand All @@ -607,6 +618,7 @@ private static JsonParser<Expression<Spans>> spansExpressionF(JsonParser<Express
spansIntervalP,
startsF(selfP),
endsF(selfP),
connectTo(selfP),
shiftEdgesF(selfP),
splitF(selfP),
splitF(windowsP),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,30 @@ public boolean isStrictlyBefore(Interval x){
return compareEndToStart(this,x) < 0;
}

public int compareStarts(Interval other) {
final var timeComparison = this.start.compareTo(other.start);
if (timeComparison != 0) return timeComparison;
else if (this.startInclusivity == other.startInclusivity) return 0;
else if (this.startInclusivity == Inclusive) return -1;
else return 1;
}

public int compareEnds(Interval other) {
final var timeComparison = this.end.compareTo(other.end);
if (timeComparison != 0) return timeComparison;
else if (this.startInclusivity == other.startInclusivity) return 0;
else if (this.startInclusivity == Inclusive) return 1;
else return -1;
}

public int compareEndToStart(Interval other) {
final var timeComparison = this.end.compareTo(other.start);
if (timeComparison != 0) return timeComparison;
else if (this.endInclusivity != other.startInclusivity) return 0;
else if (this.endInclusivity == Inclusive) return 1;
else return -1;
}

public boolean contains(Duration d){
return !intersect(this, at(d)).isEmpty();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package gov.nasa.jpl.aerie.constraints.tree;

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.Spans;
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;

import java.util.Set;
import java.util.stream.StreamSupport;

public record SpansConnectTo(Expression<Spans> from, Expression<Spans> to) implements Expression<Spans> {

@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<String> 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()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
12 changes: 10 additions & 2 deletions merlin-server/constraints-dsl-compiler/src/libs/constraints-ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export enum NodeKind {
WindowsExpressionFromSpans = 'WindowsExpressionFromSpans',
SpansExpressionFromWindows = 'SpansExpressionFromWindows',
SpansExpressionSplit = 'SpansExpressionSplit',
SpansExpressionConnectTo = 'SpansExpressionConnectTo',
SpansExpressionInterval = 'SpansExpressionInterval',
SpansSelectWhenTrue = 'SpansSelectWhenTrue',
ExpressionEqual = 'ExpressionEqual',
Expand Down Expand Up @@ -122,8 +123,9 @@ export type SpansExpression =
| IntervalsExpressionShiftEdges
| SpansExpressionFromWindows
| ForEachActivitySpans
| SpansExpressionInterval
| SpansSelectWhenTrue;
| SpansSelectWhenTrue
| SpansExpressionConnectTo
| SpansExpressionInterval;

export interface SpansSelectWhenTrue {
kind: NodeKind.SpansSelectWhenTrue,
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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))
)
)
)
);
}

}

0 comments on commit 50d69e6

Please sign in to comment.