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 cfb882db49..2d3fd6fff0 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
@@ -74,6 +74,18 @@ static
> JsonParser> assignGapsF(final JsonPa
);
}
+ static > JsonParser> shiftByF(final JsonParser> profileParser) {
+ return productP
+ .field("kind", literalP("ProfileExpressionShiftBy"))
+ .field("expression", profileParser)
+ .field("duration", durationExprP)
+ .map(
+ untuple((kind, expression, duration) -> new ShiftBy<>(expression, duration)),
+ $ -> tuple(Unit.UNIT, $.expression(), $.duration())
+ );
+ }
+
+
static final JsonParser discreteResourceP =
productP
.field("kind", literalP("DiscreteProfileResource"))
@@ -106,6 +118,7 @@ public static JsonParser> discreteProfileExprF(JsonP
discreteValueP,
discreteParameterP,
assignGapsF(selfP),
+ shiftByF(selfP),
valueAtExpressionF(profileExpressionP, spansExpressionP),
listExpressionF(profileExpressionP),
structExpressionF(profileExpressionP)
@@ -206,6 +219,7 @@ private static JsonParser> linearProfileExprF(JsonPars
timesF(selfP),
rateF(selfP),
assignGapsF(selfP),
+ shiftByF(selfP),
accumulatedDurationF(windowsP),
accumulatedDurationF(spansP)
));
@@ -288,14 +302,14 @@ static JsonParser transitionP(JsonParser> profi
$ -> tuple(Unit.UNIT, $.value(), $.interval())
);
- static JsonParser shiftByF(JsonParser> windowsExpressionP) {
+ static JsonParser shiftWindowsEdgesF(JsonParser> windowsExpressionP) {
return productP
.field("kind", literalP("WindowsExpressionShiftBy"))
.field("windowExpression", windowsExpressionP)
.field("fromStart", durationExprP)
.field("fromEnd", durationExprP)
.map(
- untuple((kind, windowsExpression, fromStart, fromEnd) -> new ShiftBy(windowsExpression, fromStart, fromEnd)),
+ untuple((kind, windowsExpression, fromStart, fromEnd) -> new ShiftWindowsEdges(windowsExpression, fromStart, fromEnd)),
$ -> tuple(Unit.UNIT, $.windows, $.fromStart, $.fromEnd));
}
static final JsonParser endOfP =
@@ -515,12 +529,13 @@ private static JsonParser> windowsExpressionF(JsonParserbuilder();
+
+ for (final var segment : this.profilePieces) {
+ final var interval = segment.interval();
+ final var shiftedInterval = interval.shiftBy(duration);
+
+ builder.set(Segment.of(shiftedInterval, segment.value()));
+ }
+ return new DiscreteProfile(builder.build());
+ }
+
@Override
public Optional valueAt(final Duration timepoint) {
final var matchPiece = profilePieces
diff --git a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/model/LinearProfile.java b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/model/LinearProfile.java
index 18325e07d9..e9f9469151 100644
--- a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/model/LinearProfile.java
+++ b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/model/LinearProfile.java
@@ -156,6 +156,25 @@ public Optional valueAt(final Duration timepoint) {
.map(linearEquationSegment -> SerializedValue.of(linearEquationSegment.value().valueAt(timepoint)));
}
+ @Override
+ public LinearProfile shiftBy(final Duration duration) {
+ final var builder = IntervalMap.builder();
+
+ for (final var segment : this.profilePieces) {
+ final var interval = segment.interval();
+ final var shiftedInterval = interval.shiftBy(duration);
+
+ final var shiftedValue = new LinearEquation(
+ segment.value().initialTime.saturatingPlus(duration),
+ segment.value().initialValue,
+ segment.value().rate
+ );
+
+ builder.set(Segment.of(shiftedInterval, shiftedValue));
+ }
+ return new LinearProfile(builder.build());
+ }
+
public static LinearProfile fromSimulatedProfile(final List> simulatedProfile) {
return fromProfileHelper(Duration.ZERO, simulatedProfile, Optional::of);
}
diff --git a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/model/Profile.java b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/model/Profile.java
index 2b34ab28fa..6c64987622 100644
--- a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/model/Profile.java
+++ b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/model/Profile.java
@@ -13,6 +13,7 @@ public interface Profile> {
boolean isConstant();
P assignGaps(P def);
+ P shiftBy(Duration duration);
Optional valueAt(Duration timepoint);
}
diff --git a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/time/Interval.java b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/time/Interval.java
index e215e710ee..0767bd7302 100644
--- a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/time/Interval.java
+++ b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/time/Interval.java
@@ -148,6 +148,15 @@ public boolean isPoint() {
this.start == this.end;
}
+ public Interval shiftBy(final Duration duration) {
+ return Interval.between(
+ this.start.saturatingPlus(duration),
+ this.startInclusivity,
+ this.end.saturatingPlus(duration),
+ this.endInclusivity
+ );
+ }
+
public Duration duration() {
if (this.isEmpty()) return Duration.ZERO;
return this.end.minus(this.start);
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 51aa398733..d7ab1ca3ab 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,7 +342,7 @@ public Windows filterByDuration(Duration minDur, Duration maxDur) {
* @param fromEnd duration to shift true -> false falling edges
* @return a new Windows
*/
- public Windows shiftBy(Duration fromStart, Duration fromEnd) {
+ public Windows shiftEdges(Duration fromStart, Duration fromEnd) {
final var builder = IntervalMap.builder();
for (final var segment : this.segments) {
@@ -363,6 +363,11 @@ public Windows shiftBy(Duration fromStart, Duration fromEnd) {
return new Windows(builder.build());
}
+ @Override
+ public Windows shiftBy(Duration duration) {
+ return this.shiftEdges(duration, duration);
+ }
+
/**
* Converts this into Spans and splits each Span into sub-spans.
*
diff --git a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/ActivityWindow.java b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/ActivityWindow.java
index 9a566eebfa..1ad673d159 100644
--- a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/ActivityWindow.java
+++ b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/ActivityWindow.java
@@ -20,7 +20,7 @@ public ActivityWindow(final String activityAlias) {
public Windows evaluate(final SimulationResults results, final Interval bounds, final EvaluationEnvironment environment) {
final var activity = environment.activityInstances().get(this.activityAlias);
return new Windows(
- Segment.of(Interval.FOREVER, false),
+ Segment.of(bounds, false),
Segment.of(activity.interval, true)
);
}
diff --git a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/DiscreteParameter.java b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/DiscreteParameter.java
index 10409e5a1b..4c336c4219 100644
--- a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/DiscreteParameter.java
+++ b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/DiscreteParameter.java
@@ -22,7 +22,7 @@ public DiscreteParameter(final String activityAlias, final String parameterName)
public DiscreteProfile evaluate(final SimulationResults results, final Interval bounds, final EvaluationEnvironment environment) {
final var activity = environment.activityInstances().get(this.activityAlias);
return new DiscreteProfile(
- Segment.of(Interval.FOREVER, activity.parameters.get(this.parameterName))
+ Segment.of(bounds, activity.parameters.get(this.parameterName))
);
}
diff --git a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/DiscreteProfileFromDuration.java b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/DiscreteProfileFromDuration.java
index dce82b04f9..0409afbc3f 100644
--- a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/DiscreteProfileFromDuration.java
+++ b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/DiscreteProfileFromDuration.java
@@ -18,7 +18,7 @@ public record DiscreteProfileFromDuration(
@Override
public DiscreteProfile evaluate(final SimulationResults results, final Interval bounds, final EvaluationEnvironment environment) {
final Duration duration = this.duration.evaluate(results, bounds, environment);
- return new DiscreteProfile(Segment.of(Interval.FOREVER, SerializedValue.of(duration.in(Duration.MICROSECOND))));
+ return new DiscreteProfile(Segment.of(bounds, SerializedValue.of(duration.in(Duration.MICROSECOND))));
}
@Override
diff --git a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/EndOf.java b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/EndOf.java
index 77654d0967..1fd684bcd2 100644
--- a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/EndOf.java
+++ b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/EndOf.java
@@ -20,7 +20,7 @@ public EndOf(final String activityAlias) {
public Windows evaluate(final SimulationResults results, final Interval bounds, final EvaluationEnvironment environment) {
final var activity = environment.activityInstances().get(this.activityAlias);
return new Windows(
- Segment.of(Interval.FOREVER, false),
+ Segment.of(bounds, false),
Segment.of(Interval.at(activity.interval.end), true)
);
}
diff --git a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/ShiftBy.java b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/ShiftBy.java
index dc62fdd5cb..903e9edf3b 100644
--- a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/ShiftBy.java
+++ b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/ShiftBy.java
@@ -1,59 +1,41 @@
package gov.nasa.jpl.aerie.constraints.tree;
import gov.nasa.jpl.aerie.constraints.model.EvaluationEnvironment;
+import gov.nasa.jpl.aerie.constraints.model.Profile;
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.merlin.protocol.types.Duration;
-import java.util.Objects;
import java.util.Set;
-public final class ShiftBy implements Expression {
- public final Expression windows;
- public final Expression fromStart;
- public final Expression fromEnd;
-
- public ShiftBy(final Expression left, final Expression fromStart, final Expression fromEnd) {
- this.windows = left;
- this.fromStart = fromStart;
- this.fromEnd = fromEnd;
- }
+public record ShiftBy>(
+ Expression
expression,
+ Expression duration) implements Expression {
@Override
- public Windows evaluate(final SimulationResults results, final Interval bounds, final EvaluationEnvironment environment) {
- final var windows = this.windows.evaluate(results, bounds, environment);
- return windows.shiftBy(this.fromStart.evaluate(results, bounds, environment), this.fromEnd.evaluate(results, bounds, environment));
+ public P evaluate(final SimulationResults results, final Interval bounds, final EvaluationEnvironment environment) {
+ // bounds aren't shifted here because duration expressions don't care about them; durations don't exist on the timeline.
+ final var duration = this.duration.evaluate(results, bounds, environment);
+
+ final var shiftedBounds = bounds.shiftBy(Duration.negate(duration));
+ final var originalProfile = this.expression.evaluate(results, shiftedBounds, environment);
+
+ return originalProfile.shiftBy(duration);
}
@Override
public void extractResources(final Set names) {
- this.windows.extractResources(names);
+ this.expression.extractResources(names);
+ this.duration.extractResources(names);
}
@Override
public String prettyPrint(final String prefix) {
return String.format(
- "\n%s(shift %s by %s %s)",
+ "\n%s(shiftBy %s %s)",
prefix,
- this.windows.prettyPrint(prefix + " "),
- this.fromStart.toString(),
- this.fromEnd.toString()
+ this.expression.prettyPrint(prefix + " "),
+ this.duration.prettyPrint(prefix + " ")
);
}
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof ShiftBy)) return false;
- final var o = (ShiftBy)obj;
-
- return Objects.equals(this.windows, o.windows) &&
- 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);
- }
}
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/ShiftWindowsEdges.java
new file mode 100644
index 0000000000..0db61054bc
--- /dev/null
+++ b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/ShiftWindowsEdges.java
@@ -0,0 +1,69 @@
+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.Windows;
+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 Expression fromStart;
+ public final Expression fromEnd;
+
+ public ShiftWindowsEdges(final Expression left, final Expression fromStart, final Expression fromEnd) {
+ this.windows = left;
+ this.fromStart = fromStart;
+ this.fromEnd = fromEnd;
+ }
+
+ @Override
+ public Windows 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);
+
+ final var newBounds = Interval.between(
+ Duration.min(bounds.start.minus(shiftRising), bounds.start.minus(shiftFalling)),
+ bounds.startInclusivity,
+ Duration.max(bounds.end.minus(shiftRising), bounds.end.minus(shiftFalling)),
+ bounds.endInclusivity
+ );
+
+ final var windows = this.windows.evaluate(results, newBounds, environment);
+ return windows.shiftEdges(shiftRising, shiftFalling).select(bounds);
+ }
+
+ @Override
+ public void extractResources(final Set names) {
+ this.windows.extractResources(names);
+ }
+
+ @Override
+ public String prettyPrint(final String prefix) {
+ return String.format(
+ "\n%s(shiftWindowsEdges %s by %s %s)",
+ prefix,
+ this.windows.prettyPrint(prefix + " "),
+ this.fromStart.toString(),
+ this.fromEnd.toString()
+ );
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ShiftWindowsEdges)) return false;
+ final var o = (ShiftWindowsEdges)obj;
+
+ return Objects.equals(this.windows, o.windows) &&
+ 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);
+ }
+}
diff --git a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/StartOf.java b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/StartOf.java
index 35946edfef..7f8a4972a3 100644
--- a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/StartOf.java
+++ b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/StartOf.java
@@ -20,7 +20,7 @@ public StartOf(final String activityAlias) {
public Windows evaluate(final SimulationResults results, final Interval bounds, final EvaluationEnvironment environment) {
final var activity = environment.activityInstances().get(this.activityAlias);
return new Windows(
- Segment.of(Interval.FOREVER, false),
+ Segment.of(bounds, false),
Segment.of(Interval.at(activity.interval.start), true)
);
}
diff --git a/constraints/src/test/java/gov/nasa/jpl/aerie/constraints/time/WindowsTest.java b/constraints/src/test/java/gov/nasa/jpl/aerie/constraints/time/WindowsTest.java
index 3b4c3fe9b0..eebf3e415d 100644
--- a/constraints/src/test/java/gov/nasa/jpl/aerie/constraints/time/WindowsTest.java
+++ b/constraints/src/test/java/gov/nasa/jpl/aerie/constraints/time/WindowsTest.java
@@ -347,7 +347,7 @@ public void shiftByStretch() {
Segment.of(interval(14, Exclusive, 17, Exclusive, SECONDS), false),
Segment.of(at(Duration.MAX_VALUE), true)); //long overflow if at max value
- Windows result = orig.shiftBy(Duration.of(-1, SECONDS), Duration.of(1, SECONDS));
+ Windows result = orig.shiftEdges(Duration.of(-1, SECONDS), Duration.of(1, SECONDS));
Windows expected = new Windows(
@@ -392,7 +392,7 @@ public void shiftByConnectedIntervals() {
.set(interval(0, 2, SECONDS), true)
.set(interval(8, 10, SECONDS), true);
- var fromStartPosFromEndPos = orig.shiftBy(Duration.of(-1, SECONDS), Duration.of(1, SECONDS));
+ var fromStartPosFromEndPos = orig.shiftEdges(Duration.of(-1, SECONDS), Duration.of(1, SECONDS));
assertIterableEquals(
new Windows(interval(0, 10, SECONDS), false)
.set(interval(-1, 3, SECONDS), true)
@@ -400,7 +400,7 @@ public void shiftByConnectedIntervals() {
fromStartPosFromEndPos
);
- var fromStartPosFromEndNeg = orig.shiftBy(Duration.of(1, SECONDS), Duration.of(-1, SECONDS));
+ var fromStartPosFromEndNeg = orig.shiftEdges(Duration.of(1, SECONDS), Duration.of(-1, SECONDS));
assertEquals(
new Windows(interval(1, Exclusive, 9, Exclusive, SECONDS), false)
.set(at(1, SECONDS), true)
@@ -408,7 +408,7 @@ public void shiftByConnectedIntervals() {
fromStartPosFromEndNeg
);
- var fromStartNegFromEndPos = orig.shiftBy(Duration.of(1, SECONDS), Duration.of(1, SECONDS));
+ var fromStartNegFromEndPos = orig.shiftEdges(Duration.of(1, SECONDS), Duration.of(1, SECONDS));
assertEquals(
new Windows(interval(1, 11, SECONDS), false)
.set(interval(1, 3, SECONDS), true)
@@ -416,7 +416,7 @@ public void shiftByConnectedIntervals() {
fromStartNegFromEndPos
);
- var fromStartNegFromEndNeg = orig.shiftBy(Duration.of(-1, SECONDS), Duration.of(-1, SECONDS));
+ var fromStartNegFromEndNeg = orig.shiftEdges(Duration.of(-1, SECONDS), Duration.of(-1, SECONDS));
assertEquals(
new Windows(interval(-1, 9, SECONDS), false)
.set(interval(-1, 1, SECONDS), true)
@@ -424,7 +424,7 @@ public void shiftByConnectedIntervals() {
fromStartNegFromEndNeg
);
- var removal = orig.shiftBy(Duration.of(0, SECONDS), Duration.of(-3, SECONDS));
+ var removal = orig.shiftEdges(Duration.of(0, SECONDS), Duration.of(-3, SECONDS));
assertEquals(
new Windows(interval(-1, Exclusive, 8, Exclusive, SECONDS), false),
removal
@@ -437,19 +437,19 @@ public void shiftByDisconnectedPoints() {
.set(at(0, SECONDS), true)
.set(at(2, SECONDS), false);
- var fromStartPosFromEndPos = orig.shiftBy(Duration.of(-1, SECONDS), Duration.of(1, SECONDS));
+ var fromStartPosFromEndPos = orig.shiftEdges(Duration.of(-1, SECONDS), Duration.of(1, SECONDS));
assertIterableEquals(
new Windows(interval(-1, 1, SECONDS), true),
fromStartPosFromEndPos
);
- var fromStartPosFromEndNeg = orig.shiftBy(Duration.of(1, SECONDS), Duration.of(-1, SECONDS));
+ var fromStartPosFromEndNeg = orig.shiftEdges(Duration.of(1, SECONDS), Duration.of(-1, SECONDS));
assertIterableEquals(
new Windows(interval(1, 3, SECONDS), false),
fromStartPosFromEndNeg
);
- var fromStartNegFromEndPos = orig.shiftBy(Duration.of(1, SECONDS), Duration.of(1, SECONDS));
+ var fromStartNegFromEndPos = orig.shiftEdges(Duration.of(1, SECONDS), Duration.of(1, SECONDS));
assertIterableEquals(
new Windows()
.set(at(1, SECONDS), true)
@@ -457,7 +457,7 @@ public void shiftByDisconnectedPoints() {
fromStartNegFromEndPos
);
- var fromStartNegFromEndNeg = orig.shiftBy(Duration.of(-1, SECONDS), Duration.of(-1, SECONDS));
+ var fromStartNegFromEndNeg = orig.shiftEdges(Duration.of(-1, SECONDS), Duration.of(-1, SECONDS));
assertIterableEquals(
new Windows()
.set(at(-1, SECONDS), true)
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 05c4eaf98d..b5dc5e5bdd 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
@@ -359,7 +359,7 @@ public void testOr() {
@Test
public void testExpandBy() {
final var simResults = new SimulationResults(
- Instant.EPOCH, Interval.between(0, 20, SECONDS),
+ Instant.EPOCH, Interval.between(-100, 200, SECONDS),
List.of(),
Map.of(),
Map.of()
@@ -376,7 +376,7 @@ public void testExpandBy() {
final var expandByFromStart = Duration.of(-1, SECONDS);
final var expandByFromEnd = Duration.of(0, SECONDS);
- final var result = new ShiftBy(Supplier.of(left), Supplier.of(expandByFromStart), Supplier.of(expandByFromEnd)).evaluate(simResults, new EvaluationEnvironment());
+ final var result = new ShiftWindowsEdges(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)
@@ -391,7 +391,7 @@ public void testExpandBy() {
@Test
public void testShrink() {
final var simResults = new SimulationResults(
- Instant.EPOCH, Interval.between(0, 20, SECONDS),
+ Instant.EPOCH, Interval.between(-100, 200, SECONDS),
List.of(),
Map.of(),
Map.of()
@@ -408,7 +408,7 @@ public void testShrink() {
final var clampFromStart = Duration.of(1, SECONDS);
final var clampFromEnd = Duration.negate(Duration.of(1, SECONDS));
- final var result = new ShiftBy(Supplier.of(left), Supplier.of(clampFromStart), Supplier.of(clampFromEnd)).evaluate(simResults, new EvaluationEnvironment());
+ final var result = new ShiftWindowsEdges(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)
@@ -505,6 +505,25 @@ public void testDiscreteResource() {
assertEquivalent(expected, result);
}
+
+ @Test
+ public void testDiscreteShiftBy() {
+ final var simResults = new SimulationResults(
+ Instant.EPOCH, Interval.between(0, 20, SECONDS),
+ List.of(),
+ Map.of(),
+ Map.of(
+ "discrete", new DiscreteProfile(Segment.of(Interval.between(1, 2, SECONDS), SerializedValue.of("much value")))
+ )
+ );
+
+ final var result = new ShiftBy<>(new DiscreteResource("discrete"), new DurationLiteral(Duration.of(1, SECONDS))).evaluate(simResults, new EvaluationEnvironment());
+
+ final var expected = new DiscreteProfile(Segment.of(Interval.between(2, 3, SECONDS), SerializedValue.of("much value")));
+
+ assertEquivalent(expected, result);
+ }
+
@Test
public void testValueAt(){
final var simResults = new SimulationResults(
@@ -621,6 +640,24 @@ public void testRealResourceOnNonexistentResource() {
fail("Expected RealResource node to fail on non-existent resource");
}
+ @Test
+ public void testRealShiftBy() {
+ final var simResults = new SimulationResults(
+ Instant.EPOCH, Interval.between(0, 20, SECONDS),
+ List.of(),
+ Map.of(
+ "real", new LinearProfile(Segment.of(Interval.between(1, 2, SECONDS), new LinearEquation(Duration.of(1, SECONDS), 1, 1)))
+ ),
+ Map.of()
+ );
+
+ final var result = new ShiftBy<>(new RealResource("real"), new DurationLiteral(Duration.of(1, SECONDS))).evaluate(simResults, new EvaluationEnvironment());
+
+ final var expected = new LinearProfile(Segment.of(Interval.between(2, 3, SECONDS), new LinearEquation(Duration.of(2, SECONDS), 1, 1)));
+
+ assertEquivalent(expected, result);
+ }
+
@Test
public void testForEachActivity() {
final var simResults = new SimulationResults(
@@ -807,7 +844,7 @@ public void testStartOf() {
final var result = new StartOf("act").evaluate(simResults, environment);
final var expected = new Windows(
- Segment.of(FOREVER, false),
+ Segment.of(simResults.bounds, false),
Segment.of(at(4, SECONDS), true)
);
@@ -841,7 +878,7 @@ public void testEndOf() {
final var result = new EndOf("act").evaluate(simResults, environment);
final var expected = new Windows(
- Segment.of(FOREVER, false),
+ Segment.of(simResults.bounds, false),
Segment.of(at(8, SECONDS), true)
);
@@ -1017,6 +1054,75 @@ public void testSpansFromInterval() {
assertEquals(expected, result);
}
+ @Test
+ public void testShiftByBoundsAdjustment() {
+ final var simResults = new SimulationResults(
+ Instant.EPOCH, Interval.between(0, 20, SECONDS),
+ List.of(),
+ Map.of(),
+ Map.of()
+ );
+
+ final var expression = new ShiftBy<>(
+ new DiscreteValue(SerializedValue.of("strang")),
+ Supplier.of(Duration.of(10, SECONDS))
+ );
+
+ final var result = expression.evaluate(simResults);
+
+ final var expected = new DiscreteProfile(
+ Segment.of(Interval.between(0, 20, SECONDS), SerializedValue.of("strang"))
+ );
+
+ assertIterableEquals(expected, result);
+ }
+
+ @Test
+ public void testShiftWindowsEdgesBoundsAdjustment() {
+ final var simResults = new SimulationResults(
+ Instant.EPOCH, Interval.between(0, 20, SECONDS),
+ List.of(),
+ Map.of(),
+ Map.of()
+ );
+
+ final var crossingStartOfPlan = new Windows(false).set(Interval.between(-1, 1, SECONDS), true);
+
+ final var result1 = new ShiftWindowsEdges(
+ Supplier.of(crossingStartOfPlan),
+ Supplier.of(Duration.ZERO),
+ Supplier.of(Duration.of(10, SECONDS))
+ ).evaluate(simResults);
+ final var expected1 = new Windows(false).set(Interval.between(-1, 11, SECONDS), true).select(simResults.bounds);
+ assertIterableEquals(expected1, result1);
+
+ final var result2 = new ShiftWindowsEdges(
+ Supplier.of(crossingStartOfPlan),
+ Supplier.of(Duration.of(-10, SECONDS)),
+ Supplier.of(Duration.ZERO)
+ ).evaluate(simResults);
+ final var expected2 = new Windows(false).set(Interval.between(0, 1, SECONDS), true).select(simResults.bounds);
+ assertIterableEquals(expected2, result2);
+
+ final var crossingEndOfPlan = new Windows(false).set(Interval.between(19, 21, SECONDS), true);
+
+ final var result3 = new ShiftWindowsEdges(
+ Supplier.of(crossingEndOfPlan),
+ Supplier.of(Duration.ZERO),
+ Supplier.of(Duration.of(10, SECONDS))
+ ).evaluate(simResults);
+ final var expected3 = new Windows(false).set(Interval.between(19, 20, SECONDS), true).select(simResults.bounds);
+ assertIterableEquals(expected3, result3);
+
+ final var result4 = new ShiftWindowsEdges(
+ Supplier.of(crossingEndOfPlan),
+ Supplier.of(Duration.of(-10, SECONDS)),
+ Supplier.of(Duration.ZERO)
+ ).evaluate(simResults);
+ final var expected4 = new Windows(false).set(Interval.between(9, 21, SECONDS), true).select(simResults.bounds);
+ assertIterableEquals(expected2, result2);
+ }
+
private static final class Supplier implements Expression {
private final T value;
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 ddc771c33b..369aeda9c6 100644
--- a/merlin-server/constraints-dsl-compiler/src/libs/constraints-ast.ts
+++ b/merlin-server/constraints-dsl-compiler/src/libs/constraints-ast.ts
@@ -43,6 +43,7 @@ export enum NodeKind {
ForEachActivitySpans = 'ForEachActivitySpans',
ForEachActivityViolations = 'ForEachActivityViolations',
ProfileChanges = 'ProfileChanges',
+ ProfileExpressionShiftBy = 'ProfileExpressionShiftBy',
ViolationsOf = 'ViolationsOf',
AbsoluteInterval = 'AbsoluteInterval',
IntervalAlias = 'IntervalAlias',
@@ -82,6 +83,7 @@ export type WindowsExpression =
| WindowsExpressionStartOf
| WindowsExpressionEndOf
| ProfileChanges
+ | ProfileExpressionShiftBy
| RealProfileLessThan
| RealProfileLessThanOrEqual
| RealProfileGreaterThan
@@ -120,6 +122,12 @@ export interface ProfileChanges {
expression: ProfileExpression;
}
+export interface ProfileExpressionShiftBy {
+ kind: NodeKind.ProfileExpressionShiftBy,
+ expression: P,
+ duration: Duration
+}
+
export interface WindowsExpressionValue {
kind: NodeKind.WindowsExpressionValue,
value: boolean,
@@ -266,7 +274,8 @@ export type RealProfileExpression =
| RealProfileValue
| RealProfileParameter
| AssignGapsExpression
- | RealProfileAccumulatedDuration;
+ | RealProfileAccumulatedDuration
+ | ProfileExpressionShiftBy;
export interface StructProfileExpression {
kind: NodeKind.StructProfileExpression,
@@ -333,7 +342,8 @@ export type DiscreteProfileExpression =
| StructProfileExpression
| ListProfileExpression
| ValueAtExpression
- | IntervalDuration;
+ | IntervalDuration
+ | ProfileExpressionShiftBy;
export interface DiscreteProfileResource {
kind: NodeKind.DiscreteProfileResource;
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 48fc9c63b9..472c18c09d 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
@@ -220,19 +220,30 @@ export class Windows {
/**
* Shifts the start and end of all true segments by a duration.
*
- * Shifts the start and end of all false segment by the opposite directions (i.e. the start of each false segment
+ * The second argument is optional: if omitted, `shiftBy(dur)` shifts all segments uniformly by `dur`, which
+ * is equivalent to `shiftBy(dur, dur)`.
+ *
+ * Shifts the start and end of all false segment by the reversed directions (i.e. the start of each false segment
* is shifted by `fromEnd`).
*
* @param fromStart duration to add from the start of each true segment
- * @param fromEnd duration to add from the end of each true segment
+ * @param fromEnd duration to add from the end of each true segment. Default is equal to `fromStart` if omitted.
*/
- public shiftBy(fromStart: AST.Duration, fromEnd: AST.Duration) : Windows {
- return new Windows({
- kind: AST.NodeKind.WindowsExpressionShiftBy,
- windowExpression: this.__astNode,
- fromStart,
- fromEnd
- })
+ public shiftBy(fromStart: AST.Duration, fromEnd?: AST.Duration | undefined) : Windows {
+ if (fromEnd === undefined) {
+ return new Windows({
+ kind: AST.NodeKind.ProfileExpressionShiftBy,
+ expression: this.__astNode,
+ duration: fromStart
+ });
+ } else {
+ return new Windows({
+ kind: AST.NodeKind.WindowsExpressionShiftBy,
+ windowExpression: this.__astNode,
+ fromStart,
+ fromEnd
+ })
+ }
}
/**
@@ -707,6 +718,19 @@ export class Real {
timepoint : timepoint.__astNode
});
}
+
+ /**
+ * Shifts the profile forward or backward in time.
+ *
+ * @param duration duration shift each segment (can be negative)
+ */
+ public shiftBy(duration: Temporal.Duration): Real {
+ return new Real({
+ kind: AST.NodeKind.ProfileExpressionShiftBy,
+ expression: this.__astNode,
+ duration
+ })
+ }
}
/**
@@ -868,6 +892,19 @@ export class Discrete {
defaultProfile: defaultProfile.__astNode
});
}
+
+ /**
+ * Shifts the profile forward or backward in time.
+ *
+ * @param duration duration shift each segment (can be negative)
+ */
+ public shiftBy(duration: Temporal.Duration): Discrete {
+ return new Discrete({
+ kind: AST.NodeKind.ProfileExpressionShiftBy,
+ expression: this.__astNode,
+ duration
+ })
+ }
}
/** Represents an instance of an activity in the plan. */
@@ -1065,13 +1102,16 @@ declare global {
/**
* Shifts the start and end of all true segments by a duration.
*
- * Shifts the start and end of all false segment by the opposite directions (i.e. the start of each false segment
+ * The second argument is optional: if omitted, `shiftBy(dur)` shifts all segments uniformly by `dur`, which
+ * is equivalent to `shiftBy(dur, dur)`.
+ *
+ * Shifts the start and end of all false segment by the reversed directions (i.e. the start of each false segment
* is shifted by `fromEnd`).
*
* @param fromStart duration to add from the start of each true segment
- * @param fromEnd duration to add from the end of each true segment
+ * @param fromEnd duration to add from the end of each true segment. Default is equal to `fromStart` if omitted.
*/
- public shiftBy(fromStart: AST.Duration, fromEnd: AST.Duration): Windows;
+ public shiftBy(fromStart: AST.Duration, fromEnd?: AST.Duration | undefined): Windows;
/**
* Returns a new windows object, with all true segments shorter than or equal to the given
@@ -1334,6 +1374,13 @@ declare global {
* @param timepoint the timepoint, represented by a Spans (must be reduced to a single point)
*/
public valueAt(timepoint: Spans): Discrete;
+
+ /**
+ * Shifts the profile forward or backward in time.
+ *
+ * @param duration duration shift each segment (can be negative)
+ */
+ public shiftBy(duration: Temporal.Duration): Real;
}
/**
@@ -1422,6 +1469,13 @@ declare global {
* @param timepoint the timepoint, represented by a Spans (must be reduced to a single point)
*/
public valueAt(timepoint: Spans): Discrete;
+
+ /**
+ * Shifts the profile forward or backward in time.
+ *
+ * @param duration duration shift each segment (can be negative)
+ */
+ public shiftBy(duration: Temporal.Duration): Discrete;
}
/** An enum for whether an interval includes its bounds. */
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 c667ab35c2..1e008e28f1 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
@@ -32,6 +32,7 @@
import gov.nasa.jpl.aerie.constraints.tree.RealResource;
import gov.nasa.jpl.aerie.constraints.tree.RealValue;
import gov.nasa.jpl.aerie.constraints.tree.ShiftBy;
+import gov.nasa.jpl.aerie.constraints.tree.ShiftWindowsEdges;
import gov.nasa.jpl.aerie.constraints.tree.ShorterThan;
import gov.nasa.jpl.aerie.constraints.tree.SpansFromWindows;
import gov.nasa.jpl.aerie.constraints.tree.Split;
@@ -486,7 +487,7 @@ export default() => {
}
@Test
- void testShiftBy() {
+ void testShiftWindowsEdges() {
checkSuccessfulCompilation(
"""
const minute = (m: number) => Temporal.Duration.from({minutes: m});
@@ -494,7 +495,7 @@ export default() => {
return Real.Resource("state of charge").rate().equal(Real.Value(4.0)).shiftBy(minute(2), minute(-20))
}
""",
- new ViolationsOfWindows(new ShiftBy(
+ new ViolationsOfWindows(new ShiftWindowsEdges(
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)))
@@ -1227,4 +1228,32 @@ export default () => {
"TypeError: TS2345 Argument of type 'Discrete' is not assignable to parameter of type '\"Option1\" | \"Option2\" | Discrete<\"Option1\" | \"Option2\">'."
);
}
+
+ @Test
+ void testProfileShiftBy() {
+ checkSuccessfulCompilation(
+ """
+ const minute = (m: number) => Temporal.Duration.from({minutes: m});
+ export default() => {
+ return Real.Resource("state of charge").shiftBy(minute(2)).equal(Real.Value(4.0))
+ }
+ """,
+ new ViolationsOfWindows(
+ new Equal<>(new ShiftBy<>(new RealResource("state of charge"), new DurationLiteral(Duration.of(2, Duration.MINUTE))), new RealValue(4.0))
+ )
+ );
+
+ checkSuccessfulCompilation(
+ """
+ const minute = (m: number) => Temporal.Duration.from({minutes: m});
+ export default() => {
+ return Discrete.Resource("mode").shiftBy(minute(2)).equal("Option1")
+ }
+ """,
+ new ViolationsOfWindows(
+ new Equal<>(new ShiftBy<>(new DiscreteResource("mode"), new DurationLiteral(Duration.of(2, Duration.MINUTE))), new DiscreteValue(SerializedValue.of("Option1")))
+ )
+ );
+ }
+
}
diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/constraints/transformers/TransformerAfterEach.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/constraints/transformers/TransformerAfterEach.java
index 07dad57f5b..2459d7ac53 100644
--- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/constraints/transformers/TransformerAfterEach.java
+++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/constraints/transformers/TransformerAfterEach.java
@@ -19,7 +19,7 @@ public Windows transformWindows(final Plan plan, final Windows windows, final Si
var retWin = windows;
retWin = retWin.not();
retWin = retWin.removeTrueSegment(0);
- retWin = retWin.shiftBy(dur, Duration.ZERO);
+ retWin = retWin.shiftEdges(dur, Duration.ZERO);
return retWin;
}
}
diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/constraints/transformers/TransformerBeforeEach.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/constraints/transformers/TransformerBeforeEach.java
index 493f07f25b..cafd1945a7 100644
--- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/constraints/transformers/TransformerBeforeEach.java
+++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/constraints/transformers/TransformerBeforeEach.java
@@ -19,7 +19,7 @@ public Windows transformWindows(final Plan plan, final Windows windows, final Si
var retWin = windows;
retWin = retWin.not();
retWin = retWin.removeTrueSegment(-1);
- retWin = retWin.shiftBy(Duration.ZERO, Duration.negate(dur));
+ retWin = retWin.shiftEdges(Duration.ZERO, Duration.negate(dur));
return retWin;
}
}