Skip to content

Commit

Permalink
Add justAfter and justBefore timing constraints in scheduling edsl
Browse files Browse the repository at this point in the history
  • Loading branch information
adrienmaillard committed Sep 28, 2023
1 parent baf34d4 commit fd6e586
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,9 @@ export class ActivityExpression<T extends WindowsEDSL.Gen.ActivityType> {
}

export class TimingConstraint {

public static defaultPadding: Temporal.Duration = Temporal.Duration.from({microseconds:1});

/** @internal **/
private constructor() {}
/**
Expand Down Expand Up @@ -633,6 +636,23 @@ export class TimingConstraint {
singleton: false
})
}

/**
* Represents a precise time point at a defined offset (default: 1 microseconds, can be modified) from the start or end of a window.
* Equivalent to TimingConstraint.singleton(windowProperty).plus(TimingConstraint.defaultPadding)
* @param windowProperty either WindowProperty.START or WindowProperty.END
*/
public static justAfter(windowProperty: WindowProperty): SingletonTimingConstraint {
return this.singleton(windowProperty).plus(this.defaultPadding);
}
/**
* Represents a precise time point at a defined offset (default: -1 microseconds, can be modified) from the start or end of a window.
* Equivalent to TimingConstraint.singleton(windowProperty).minus(TimingConstraint.defaultPadding)
* @param windowProperty either WindowProperty.START or WindowProperty.END
*/
public static justBefore(windowProperty: WindowProperty): SingletonTimingConstraint {
return this.singleton(windowProperty).minus(this.defaultPadding);
}
}

/**
Expand Down Expand Up @@ -837,6 +857,7 @@ declare global {
matchingArgs: WindowsEDSL.Gen.ActivityTypeParameterMapWithUndefined[T]): ActivityExpression<T>
}
class TimingConstraint {
public static defaultPadding: Temporal.Duration;
/**
* The singleton timing constraint represents a precise time point
* at some offset from either the start or end of a window.
Expand All @@ -862,6 +883,18 @@ declare global {
* @param upperBound represents the (inclusive) upper bound of the time interval
*/
public static bounds(lowerBound: SingletonTimingConstraint, upperBound: SingletonTimingConstraint): FlexibleRangeTimingConstraint
/**
* Represents a precise time point at a defined offset (default: 1 microseconds, can be modified) from the start or end of a window.
* Equivalent to TimingConstraint.singleton(windowProperty).plus(TimingConstraint.defaultPadding)
* @param windowProperty either WindowProperty.START or WindowProperty.END
*/
public static justAfter(windowProperty: WindowProperty): SingletonTimingConstraint
/**
* Represents a precise time point at a defined offset (default: -1 microseconds, can be modified) from the start or end of a window.
* Equivalent to TimingConstraint.singleton(windowProperty).minus(TimingConstraint.defaultPadding)
* @param windowProperty either WindowProperty.START or WindowProperty.END
*/
public static justBefore(windowProperty: WindowProperty): SingletonTimingConstraint
}
var WindowProperty: typeof AST.WindowProperty
var Operator: typeof AST.TimingConstraintOperator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.HOUR;
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.HOURS;
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.MICROSECOND;
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.MICROSECONDS;
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.MILLISECONDS;
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.MINUTE;
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.MINUTES;
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.SECOND;
Expand Down Expand Up @@ -2744,7 +2746,128 @@ export default () => Goal.CoexistenceGoal({
assertEquals(SerializedValue.of("fromStem"), peelBanana.serializedActivity().getArguments().get("peelDirection"));
}

/**
@Test
void testJustAfter() {
/*
Start with a plan with B anchored to A
Goal: for each B, place a C
And make sure that C ends up in the right place
*/
final var activityDuration = Duration.of(1, Duration.HOUR);
final var tenMinutes = Duration.of(10, MINUTES);
final var results = runScheduler(
BANANANATION,
Map.of(
new ActivityDirectiveId(2L),
new ActivityDirective(
tenMinutes,
"GrowBanana",
Map.of(
"quantity", SerializedValue.of(1),
"growingDuration", SerializedValue.of(activityDuration.in(Duration.MICROSECONDS))),
new ActivityDirectiveId(1L),
true)),
List.of(new SchedulingGoal(new GoalId(0L), """
export default function(){
TimingConstraint.defaultPadding = Temporal.Duration.from({milliseconds:1})
return Goal.CoexistenceGoal({
forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana),
activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}),
startsAt: TimingConstraint.justAfter(WindowProperty.START)
})
}
""", true)),
PLANNING_HORIZON);

assertEquals(1, results.scheduleResults.goalResults().size());
final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L));

assertTrue(goalResult.satisfied());
assertEquals(1, goalResult.createdActivities().size());
for (final var activity : goalResult.createdActivities()) {
assertNotNull(activity);
}
for (final var activity : goalResult.satisfyingActivities()) {
assertNotNull(activity);
}

final var planByActivityType = partitionByActivityType(results.updatedPlan());
final var peelBananas = planByActivityType.get("PeelBanana");
final var growBananas = planByActivityType.get("GrowBanana");

assertEquals(1, peelBananas.size());
assertEquals(1, growBananas.size());
final var peelBanana = peelBananas.iterator().next();
final var growBanana = growBananas.iterator().next();


assertEquals(tenMinutes, growBanana.startOffset());
assertEquals(SerializedValue.of(1), growBanana.serializedActivity().getArguments().get("quantity"));

assertEquals(Duration.of(10, Duration.MINUTES).plus(Duration.of(1, MILLISECONDS)), peelBanana.startOffset());
assertEquals(SerializedValue.of("fromStem"), peelBanana.serializedActivity().getArguments().get("peelDirection"));
}

@Test
void testJustBefore() {
/*
Start with a plan with B anchored to A
Goal: for each B, place a C
And make sure that C ends up in the right place
*/
final var activityDuration = Duration.of(1, Duration.HOUR);
final var tenMinutes = Duration.of(10, MINUTES);
final var results = runScheduler(
BANANANATION,
Map.of(
new ActivityDirectiveId(2L),
new ActivityDirective(
tenMinutes,
"GrowBanana",
Map.of(
"quantity", SerializedValue.of(1),
"growingDuration", SerializedValue.of(activityDuration.in(Duration.MICROSECONDS))),
new ActivityDirectiveId(1L),
true)),
List.of(new SchedulingGoal(new GoalId(0L), """
export default () => Goal.CoexistenceGoal({
forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana),
activityTemplate: ActivityTemplates.PeelBanana({peelDirection: "fromStem"}),
startsAt: TimingConstraint.justBefore(WindowProperty.START)
})
""", true)),
PLANNING_HORIZON);

assertEquals(1, results.scheduleResults.goalResults().size());
final var goalResult = results.scheduleResults.goalResults().get(new GoalId(0L));

assertTrue(goalResult.satisfied());
assertEquals(1, goalResult.createdActivities().size());
for (final var activity : goalResult.createdActivities()) {
assertNotNull(activity);
}
for (final var activity : goalResult.satisfyingActivities()) {
assertNotNull(activity);
}

final var planByActivityType = partitionByActivityType(results.updatedPlan());
final var peelBananas = planByActivityType.get("PeelBanana");
final var growBananas = planByActivityType.get("GrowBanana");

assertEquals(1, peelBananas.size());
assertEquals(1, growBananas.size());
final var peelBanana = peelBananas.iterator().next();
final var growBanana = growBananas.iterator().next();


assertEquals(tenMinutes, growBanana.startOffset());
assertEquals(SerializedValue.of(1), growBanana.serializedActivity().getArguments().get("quantity"));

assertEquals(Duration.of(10, Duration.MINUTES).minus(Duration.of(1, MICROSECONDS)), peelBanana.startOffset());
assertEquals(SerializedValue.of("fromStem"), peelBanana.serializedActivity().getArguments().get("peelDirection"));
}

/**
* Test that the scheduler can correctly place activities off of activities anchored to start after the start
* of another activity.
*/
Expand Down

0 comments on commit fd6e586

Please sign in to comment.