Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add allen relations #1069

Merged
merged 3 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package gov.nasa.jpl.aerie.scheduler.constraints.timeexpressions;

import gov.nasa.jpl.aerie.constraints.model.SimulationResults;
import gov.nasa.jpl.aerie.constraints.time.Interval;
import gov.nasa.jpl.aerie.scheduler.model.Plan;

public class TimeExpressionBetween extends TimeExpression {

private final TimeExpressionRelativeFixed lowerBound;
private final TimeExpressionRelativeFixed upperBound;

public TimeExpressionBetween(TimeExpressionRelativeFixed lowerBound, TimeExpressionRelativeFixed upperBound) {
this.lowerBound = lowerBound;
this.upperBound = upperBound;
}

@Override
public Interval computeTime(final SimulationResults simulationResults, final Plan plan, final Interval interval) {
final var interval1 = lowerBound.computeTime(simulationResults, plan, interval);
final var interval2 = upperBound.computeTime(simulationResults, plan, interval);
return Interval.between(interval1.start, interval2.end);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import gov.nasa.jpl.aerie.constraints.time.Windows;
import gov.nasa.jpl.aerie.constraints.tree.Expression;
import gov.nasa.jpl.aerie.constraints.tree.StructExpressionAt;
import gov.nasa.jpl.aerie.json.Convert;
import gov.nasa.jpl.aerie.json.JsonObjectParser;
import gov.nasa.jpl.aerie.json.JsonParser;
import gov.nasa.jpl.aerie.json.SumParsers;
Expand All @@ -12,6 +13,7 @@
import gov.nasa.jpl.aerie.scheduler.constraints.timeexpressions.TimeAnchor;
import gov.nasa.jpl.aerie.scheduler.server.http.ActivityTemplateJsonParser;
import gov.nasa.jpl.aerie.scheduler.server.services.MissionModelService;
import org.apache.commons.lang3.tuple.Pair;

import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -90,16 +92,25 @@ private static JsonObjectParser<GoalSpecifier.RecurrenceGoalDefinition> recurren
ConstraintExpression.WindowsExpression::new,
ConstraintExpression.WindowsExpression::expression));

public static final JsonParser<ActivityTimingConstraint> activityTimingConstraintP =
public static final JsonParser<TimingConstraint.ActivityTimingConstraint> activityTimingConstraintP =
productP
.field("windowProperty", enumP(TimeAnchor.class, Enum::name))
.field("operator", enumP(TimeUtility.Operator.class, Enum::name))
.field("operand", durationP)
.field("singleton", boolP)
.map(
untuple(ActivityTimingConstraint::new),
untuple(TimingConstraint.ActivityTimingConstraint::new),
$ -> tuple($.windowProperty(), $.operator(), $.operand(), $.singleton()));

public static final JsonParser<TimingConstraint.ActivityTimingConstraintFlexibleRange> activityTimingConstraintFlexibleRangeP =
productP
.field("lowerBound", activityTimingConstraintP)
.field("upperBound", activityTimingConstraintP)
.field("singleton", boolP)
.map(
untuple(TimingConstraint.ActivityTimingConstraintFlexibleRange::new),
(TimingConstraint.ActivityTimingConstraintFlexibleRange $) -> tuple($.lowerBound(), $.upperBound(), $.singleton()));

private static final JsonObjectParser<GoalSpecifier.CoexistenceGoalDefinition> coexistenceGoalDefinitionP(
MissionModelService.MissionModelTypes activityTypes)
{
Expand All @@ -109,20 +120,31 @@ private static final JsonObjectParser<GoalSpecifier.CoexistenceGoalDefinition> c
.optionalField("activityFinder", activityExpressionP)
.field("alias", stringP)
.field("forEach", constraintExpressionP)
.optionalField("startConstraint", activityTimingConstraintP)
.optionalField("endConstraint", activityTimingConstraintP)
.optionalField("startConstraint", (JsonParser<? extends TimingConstraint>) chooseP(activityTimingConstraintP, activityTimingConstraintFlexibleRangeP))
.optionalField("endConstraint", (JsonParser<? extends TimingConstraint>) chooseP(activityTimingConstraintP, activityTimingConstraintFlexibleRangeP))
.field("shouldRollbackIfUnsatisfied", boolP)
.map(
untuple(GoalSpecifier.CoexistenceGoalDefinition::new),
goalDefinition -> tuple(
goalDefinition.activityTemplate(),
goalDefinition.activityFinder(),
goalDefinition.alias(),
goalDefinition.forEach(),
goalDefinition.startConstraint(),
goalDefinition.endConstraint(),
goalDefinition.shouldRollbackIfUnsatisfied()));
.map(coexistenceGoalTransform());
}

/**
* This convert is in a helper function in order to define the generic variables T1 and T2
*/
private static <T1 extends TimingConstraint, T2 extends TimingConstraint>
Convert<
Pair<Pair<Pair<Pair<Pair<Pair<ActivityTemplate, Optional<ConstraintExpression.ActivityExpression>>, String>, ConstraintExpression>, Optional<T1>>, Optional<T2>>, Boolean>,
GoalSpecifier.CoexistenceGoalDefinition>
coexistenceGoalTransform() {
return Convert.between(untuple(GoalSpecifier.CoexistenceGoalDefinition::new), (GoalSpecifier.CoexistenceGoalDefinition $) -> tuple(
$.activityTemplate(),
$.activityFinder(),
$.alias,
$.forEach,
(Optional<T1>) $.startConstraint,
(Optional<T2>) $.endConstraint,
$.shouldRollbackIfUnsatisfied
));
adrienmaillard marked this conversation as resolved.
Show resolved Hide resolved
}

private static final JsonObjectParser<GoalSpecifier.CardinalityGoalDefinition> cardinalityGoalDefinitionP(
MissionModelService.MissionModelTypes activityTypes) {
return
Expand Down Expand Up @@ -248,8 +270,8 @@ record CoexistenceGoalDefinition(
Optional<ConstraintExpression.ActivityExpression> activityFinder,
String alias,
ConstraintExpression forEach,
Optional<ActivityTimingConstraint> startConstraint,
Optional<ActivityTimingConstraint> endConstraint,
Optional<? extends TimingConstraint> startConstraint,
Optional<? extends TimingConstraint> endConstraint,
boolean shouldRollbackIfUnsatisfied
) implements GoalSpecifier {}
record CardinalityGoalDefinition(
Expand Down Expand Up @@ -277,5 +299,19 @@ public sealed interface ConstraintExpression {
record ActivityExpression(String type, Optional<StructExpressionAt> arguments) implements ConstraintExpression {}
record WindowsExpression(Expression<Windows> expression) implements ConstraintExpression {}
}
public record ActivityTimingConstraint(TimeAnchor windowProperty, TimeUtility.Operator operator, Duration operand, boolean singleton) {}

public sealed interface TimingConstraint {
record ActivityTimingConstraint(
TimeAnchor windowProperty,
TimeUtility.Operator operator,
Duration operand,
boolean singleton
) implements TimingConstraint {}

record ActivityTimingConstraintFlexibleRange(
ActivityTimingConstraint lowerBound,
ActivityTimingConstraint upperBound,
boolean singleton
) implements TimingConstraint {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import gov.nasa.jpl.aerie.scheduler.Range;
import gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityCreationTemplate;
import gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityExpression;
import gov.nasa.jpl.aerie.scheduler.constraints.timeexpressions.TimeExpressionBetween;
import gov.nasa.jpl.aerie.scheduler.constraints.timeexpressions.TimeExpressionRelativeFixed;
import gov.nasa.jpl.aerie.scheduler.goals.CardinalityGoal;
import gov.nasa.jpl.aerie.scheduler.goals.CoexistenceGoal;
Expand All @@ -25,6 +26,7 @@
import gov.nasa.jpl.aerie.scheduler.server.models.SchedulingDSL;
import gov.nasa.jpl.aerie.scheduler.server.models.Timestamp;
import gov.nasa.jpl.aerie.scheduler.server.services.UnexpectedSubtypeError;
import org.jetbrains.annotations.NotNull;

import java.util.Map;
import java.util.function.Function;
Expand Down Expand Up @@ -65,21 +67,23 @@ public static Goal goalOfGoalSpecifier(
.aliasForAnchors(g.alias());
if (g.startConstraint().isPresent()) {
final var startConstraint = g.startConstraint().get();
final var timeExpression = new TimeExpressionRelativeFixed(
startConstraint.windowProperty(),
startConstraint.singleton()
);
timeExpression.addOperation(startConstraint.operator(), startConstraint.operand());
builder.startsAt(timeExpression);
if (startConstraint instanceof SchedulingDSL.TimingConstraint.ActivityTimingConstraint s) {
builder.startsAt(makeTimeExpressionRelativeFixed(s));
} else if (startConstraint instanceof SchedulingDSL.TimingConstraint.ActivityTimingConstraintFlexibleRange s) {
builder.startsAt(new TimeExpressionBetween(makeTimeExpressionRelativeFixed(s.lowerBound()), makeTimeExpressionRelativeFixed(s.upperBound())));
} else {
throw new UnexpectedSubtypeError(SchedulingDSL.TimingConstraint.class, startConstraint);
}
}
if (g.endConstraint().isPresent()) {
final var startConstraint = g.endConstraint().get();
final var timeExpression = new TimeExpressionRelativeFixed(
startConstraint.windowProperty(),
startConstraint.singleton()
);
timeExpression.addOperation(startConstraint.operator(), startConstraint.operand());
builder.endsAt(timeExpression);
final var endConstraint = g.endConstraint().get();
if (endConstraint instanceof SchedulingDSL.TimingConstraint.ActivityTimingConstraint e) {
builder.endsAt(makeTimeExpressionRelativeFixed(e));
} else if (endConstraint instanceof SchedulingDSL.TimingConstraint.ActivityTimingConstraintFlexibleRange e) {
builder.endsAt(new TimeExpressionBetween(makeTimeExpressionRelativeFixed(e.lowerBound()), makeTimeExpressionRelativeFixed(e.upperBound())));
} else {
throw new UnexpectedSubtypeError(SchedulingDSL.TimingConstraint.class, endConstraint);
}
}
if (g.startConstraint().isEmpty() && g.endConstraint().isEmpty()) {
throw new Error("Both start and end constraints were empty. This should have been disallowed at the type level.");
Expand Down Expand Up @@ -146,6 +150,16 @@ else if(goalSpecifier instanceof SchedulingDSL.GoalSpecifier.CardinalityGoalDefi
}
}

@NotNull
private static TimeExpressionRelativeFixed makeTimeExpressionRelativeFixed(final SchedulingDSL.TimingConstraint.ActivityTimingConstraint s) {
final var timeExpression = new TimeExpressionRelativeFixed(
s.windowProperty(),
s.singleton()
);
timeExpression.addOperation(s.operator(), s.operand());
return timeExpression;
}

private static ActivityExpression buildActivityExpression(SchedulingDSL.ConstraintExpression.ActivityExpression activityExpr,
final Function<String, ActivityType> lookupActivityType){
final var builder = new ActivityExpression.Builder().ofType(lookupActivityType.apply(activityExpr.type()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ export interface ActivityCoexistenceGoal {
activityFinder: ActivityExpression<any> | undefined,
alias: string,
forEach: WindowsExpressions.WindowsExpression | ActivityExpression<any>,
startConstraint: ActivityTimingConstraintSingleton | ActivityTimingConstraintRange | undefined,
endConstraint: ActivityTimingConstraintSingleton | ActivityTimingConstraintRange | undefined,
startConstraint: ActivityTimingConstraintSingleton | ActivityTimingConstraintRange | ActivityTimingConstraintInterval | undefined,
endConstraint: ActivityTimingConstraintSingleton | ActivityTimingConstraintRange | ActivityTimingConstraintInterval | undefined,
shouldRollbackIfUnsatisfied: boolean
}

Expand Down Expand Up @@ -138,6 +138,11 @@ export interface ActivityTimingConstraintRange {
singleton: false
}

export interface ActivityTimingConstraintInterval {
lowerBound: ActivityTimingConstraintSingleton;
upperBound: ActivityTimingConstraintSingleton;
singleton: false;
}
export type GoalSpecifier =
| Goal
| GoalComposition
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -528,11 +528,11 @@ export class Goal {
/**
* An StartTimingConstraint is a constraint applying on the start time of an activity template.
*/
export type StartTimingConstraint = { startsAt: SingletonTimingConstraint | SingletonTimingConstraintNoOperator } | { startsWithin: RangeTimingConstraint }
export type StartTimingConstraint = { startsAt: SingletonTimingConstraint | SingletonTimingConstraintNoOperator } | { startsWithin: RangeTimingConstraint | FlexibleRangeTimingConstraint}
/**
* An EndTimingConstraint is a constraint applying on the end time of an activity template.
*/
export type EndTimingConstraint = { endsAt: SingletonTimingConstraint | SingletonTimingConstraintNoOperator } | {endsWithin: RangeTimingConstraint }
export type EndTimingConstraint = { endsAt: SingletonTimingConstraint | SingletonTimingConstraintNoOperator } | { endsWithin: RangeTimingConstraint | FlexibleRangeTimingConstraint }
/**
* An CoexistenceGoalTimingConstraints is a constraint that can be used to constrain the start or end times of activities in coexistence goals.
*/
Expand Down Expand Up @@ -619,6 +619,20 @@ export class TimingConstraint {
singleton: false
})
}

/**
* The bound timing constraint is used to represent time intervals used to define temporal constraints between goals (e.g. A before[lowerbound, upperbound] B)
* .
* @param lowerBound represents the (inclusive) lower bound of the time interval
* @param upperBound represents the (inclusive) upper bound of the time interval
*/
public static bounds(lowerBound: SingletonTimingConstraint, upperBound: SingletonTimingConstraint): FlexibleRangeTimingConstraint {
return FlexibleRangeTimingConstraint.new({
lowerBound: lowerBound.__astNode,
upperBound: upperBound.__astNode,
singleton: false
})
}
}

/**
Expand Down Expand Up @@ -697,6 +711,22 @@ export class RangeTimingConstraint {
}
}

/**
* A FlexibleRange timing constraint specifies that the start or the end time of an activity must be within certain bounds. Use {@link TimingConstraint.bounds} to specify the bounds.
*/
export class FlexibleRangeTimingConstraint {
/** @internal **/
public readonly __astNode: AST.ActivityTimingConstraintInterval
/** @internal **/
private constructor(__astNode: AST.ActivityTimingConstraintInterval) {
this.__astNode = __astNode;
}
/** @internal **/
public static new(__astNode: AST.ActivityTimingConstraintInterval): FlexibleRangeTimingConstraint {
return new FlexibleRangeTimingConstraint(__astNode);
}
}

declare global {
class GlobalSchedulingCondition {

Expand Down Expand Up @@ -824,6 +854,14 @@ declare global {
* @param operand the duration offset
*/
public static range(windowProperty: WindowProperty, operator: TimingConstraintOperator, operand: Temporal.Duration): RangeTimingConstraint

/**
* The bound timing constraint is used to represent time intervals used to define temporal constraints between goals (e.g. A before[lowerbound, upperbound] B)
* .
* @param lowerBound represents the (inclusive) lower bound of the time interval
* @param upperBound represents the (inclusive) upper bound of the time interval
*/
public static bounds(lowerBound: SingletonTimingConstraint, upperBound: SingletonTimingConstraint): FlexibleRangeTimingConstraint
}
var WindowProperty: typeof AST.WindowProperty
var Operator: typeof AST.TimingConstraintOperator
Expand All @@ -847,4 +885,14 @@ export interface ClosedOpenInterval extends AST.ClosedOpenInterval {}
export interface ActivityTemplate<A extends WindowsEDSL.Gen.ActivityType> extends AST.ActivityTemplate<A> {}

// Make Goal available on the global object
Object.assign(globalThis, { GlobalSchedulingCondition, Goal, ActivityExpression, TimingConstraint: TimingConstraint, WindowProperty: AST.WindowProperty, Operator: AST.TimingConstraintOperator, ActivityTypes: WindowsEDSL.Gen.ActivityType});
Object.assign(globalThis,
{
GlobalSchedulingCondition,
Goal,
ActivityExpression,
TimingConstraint: TimingConstraint,
WindowProperty: AST.WindowProperty,
Operator: AST.TimingConstraintOperator,
ActivityTypes: WindowsEDSL.Gen.ActivityType,
FlexibleRangeTimingConstraint
});
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ record MissionModelInfo(Path libPath, Path modelPath, String modelName, MissionM
private Optional<MissionModelInfo> missionModelInfo = Optional.empty();
private MerlinPlan initialPlan;
Collection<ActivityDirective> updatedPlan;
Plan plan;

MockMerlinService() {
this.initialPlan = new MerlinPlan();
Expand Down Expand Up @@ -123,6 +124,7 @@ public Map<SchedulingActivityDirective, ActivityDirectiveId> updatePlanActivityD
)
{
this.updatedPlan = extractActivityDirectives(plan);
this.plan = plan;
final var res = new HashMap<SchedulingActivityDirective, ActivityDirectiveId>();
for (final var activity : plan.getActivities()) {
res.put(activity, new ActivityDirectiveId(activity.id().id()));
Expand Down
Loading