Skip to content

Commit

Permalink
Merge pull request #708 from NASA-AMMOS/674-make-simulation-optional-…
Browse files Browse the repository at this point in the history
…for-scheduling-goals

Make simulation optional for scheduling goals
  • Loading branch information
JoelCourtney authored Mar 17, 2023
2 parents 7646622 + 306eda1 commit e3b3578
Show file tree
Hide file tree
Showing 16 changed files with 228 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ public boolean isCollectionSubsetOf(final Spans otherSpans){

public Spans intersectWith(final Windows windows){
final var ret = new Spans();
this.intervals.forEach(x -> windows.iterator().forEachRemaining(y -> ret.add(Interval.intersect(y.interval(), x.interval()), x.value())));
this.intervals.forEach(x -> windows.iterateEqualTo(true).iterator().forEachRemaining(y -> ret.add(Interval.intersect(x.interval(), y), x.value())));
return ret;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,15 @@ public void accumulatedDuration() {

assertIterableEquals(expected, acc);
}

@Test
public void testIntersectWindows() {
final var intersection = new Spans(interval(0, 2, SECONDS)).intersectWith(
new Windows(false).set(interval(1, 10, SECONDS), true)
);

final var expected = new Spans(interval(1,2, SECONDS));

assertIterableEquals(expected, intersection);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
comment on column scheduling_specification_goals.simulate_after is null;
alter table scheduling_specification_goals drop column simulate_after;

call migrations.mark_migration_rolled_back('1');
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
alter table scheduling_specification_goals add column simulate_after boolean not null default true;

comment on column scheduling_specification_goals.simulate_after is e''
'Whether to re-simulate after evaluating this goal and before the next goal.';

call migrations.mark_migration_applied('1');
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ public class Goal {
* state constraints applying to the goal
*/
protected Expression<Windows> resourceConstraints;

/**
* Whether to resimulate after this goal or make the next goal use stale results.
*
* True is the default behavior. If False, the durations of the activities inserted by
* *this* goal will not be simulated and checked, and the *next* goal will not have up-to-date
* sim results if this goal placed any activities.
*/
public boolean simulateAfter;

/** Set to true if partial satisfaction is ok, the scheduler will try to do its best */
private boolean shouldRollbackIfUnsatisfied = false;

Expand Down Expand Up @@ -148,6 +158,13 @@ public T shouldRollbackIfUnsatisfied(final boolean shouldRollbackIfUnsatisfied)

boolean shouldRollbackIfUnsatisfied;

public T simulateAfter(boolean simAfter) {
this.simulateAfter = simAfter;
return getThis();
}

boolean simulateAfter = true;

/**
* uses all pending specifications to construct a matching new goal object
*
Expand Down Expand Up @@ -206,6 +223,8 @@ protected Goal fill(Goal goal) {
goal.temporalContext = new WindowsWrapperExpression(windows);
}

goal.simulateAfter = this.simulateAfter;

return goal;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public class PrioritySolver implements Solver {
private static final Logger logger = LoggerFactory.getLogger(PrioritySolver.class);

boolean checkSimBeforeInsertingActivities;
boolean checkSimBeforeEvaluatingGoal;

/**
* boolean stating whether only conflict analysis should be performed or not
Expand Down Expand Up @@ -88,6 +89,7 @@ public class PrioritySolver implements Solver {
public PrioritySolver(final Problem problem, final boolean analysisOnly) {
checkNotNull(problem, "creating solver with null input problem descriptor");
this.checkSimBeforeInsertingActivities = true;
this.checkSimBeforeEvaluatingGoal = true;
this.problem = problem;
this.simulationFacade = problem.getSimulationFacade();
this.analysisOnly = analysisOnly;
Expand All @@ -97,11 +99,6 @@ public PrioritySolver(final Problem problem) {
this(problem, false);
}

//TODO: should probably be part of sched configuration; maybe even per rule
public void doNotcheckSimBeforeInsertingActInPlan(){
this.checkSimBeforeInsertingActivities = false;
}

/**
* {@inheritDoc}
*
Expand Down Expand Up @@ -150,7 +147,7 @@ private boolean checkAndInsertActs(Collection<SchedulingActivityDirective> acts)
for(var act: acts){
//if some parameters are left uninstantiated, this is the last moment to do it
var duration = act.duration();
if(duration != null && duration.longerThan(this.problem.getPlanningHorizon().getEndAerie())){
if(duration != null && act.startOffset().plus(duration).longerThan(this.problem.getPlanningHorizon().getEndAerie())) {
logger.warn("Activity " + act
+ " is planned to finish after the end of the planning horizon, not simulating. Extend the planning horizon.");
allGood = false;
Expand All @@ -165,7 +162,7 @@ private boolean checkAndInsertActs(Collection<SchedulingActivityDirective> acts)
break;
}
var simDur = simulationFacade.getActivityDuration(act);
if (!simDur.isPresent()) {
if (simDur.isEmpty()) {
logger.error("Activity " + act + " could not be simulated");
allGood = false;
break;
Expand Down Expand Up @@ -352,13 +349,17 @@ private LinkedList<Goal> getGoalQueue() {
}

private void satisfyGoal(Goal goal) {
final boolean checkSimConfig = this.checkSimBeforeInsertingActivities;
this.checkSimBeforeInsertingActivities = goal.simulateAfter;
if (goal instanceof CompositeAndGoal) {
satisfyCompositeGoal((CompositeAndGoal) goal);
} else if (goal instanceof OptionGoal) {
satisfyOptionGoal((OptionGoal) goal);
} else {
satisfyGoalGeneral(goal);
}
this.checkSimBeforeEvaluatingGoal = goal.simulateAfter;
this.checkSimBeforeInsertingActivities = checkSimConfig;
}


Expand Down Expand Up @@ -505,6 +506,8 @@ private void satisfyGoalGeneral(Goal goal) {
evaluation.forGoal(goal).setNbConflictsDetected(missingConflicts.size());
assert missingConflicts != null;
boolean madeProgress = true;


while (!missingConflicts.isEmpty() && madeProgress) {
madeProgress = false;

Expand Down Expand Up @@ -576,8 +579,12 @@ private void satisfyGoalGeneral(Goal goal) {
assert goal != null;
assert plan != null;
//REVIEW: maybe should have way to request only certain kinds of conflicts
this.simulationFacade.computeSimulationResultsUntil(this.problem.getPlanningHorizon().getEndAerie());
final var rawConflicts = goal.getConflicts(plan, this.simulationFacade.getLatestConstraintSimulationResults());
var lastSimResults = this.simulationFacade.getLatestConstraintSimulationResults();
if (lastSimResults == null || this.checkSimBeforeEvaluatingGoal) {
this.simulationFacade.computeSimulationResultsUntil(this.problem.getPlanningHorizon().getEndAerie());
lastSimResults = this.simulationFacade.getLatestConstraintSimulationResults();
}
final var rawConflicts = goal.getConflicts(plan, lastSimResults);
assert rawConflicts != null;
return rawConflicts;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,7 @@ public void testCoexistenceWindowsCutoffMidActivity() {
partialPlan.add(SchedulingActivityDirective.of(actTypeA, planningHorizon.getStartAerie().plus(Duration.of(7, Duration.SECONDS)), Duration.of(4, Duration.SECONDS), null, true)); //create an activity that's 5 seconds long, 11s after start
partialPlan.add(SchedulingActivityDirective.of(actTypeA, planningHorizon.getStartAerie().plus(Duration.of(14, Duration.SECONDS)), Duration.of(4, Duration.SECONDS), null, true)); //create an activity that's 5 seconds long, 16s after start
partialPlan.add(SchedulingActivityDirective.of(actTypeA, planningHorizon.getStartAerie().plus(Duration.of(19, Duration.SECONDS)), Duration.of(4, Duration.SECONDS), null, true)); //create an activity that's 5 seconds long, 16s after start
partialPlan.add(SchedulingActivityDirective.of(actTypeA, planningHorizon.getStartAerie().plus(Duration.of(25, Duration.SECONDS)), Duration.of(4, Duration.SECONDS), null, true)); //create an activity that's 5 seconds long, 16s after start
partialPlan.add(SchedulingActivityDirective.of(actTypeA, planningHorizon.getStartAerie().plus(Duration.of(25, Duration.SECONDS)), Duration.of(2, Duration.SECONDS), null, true)); //create an activity that's 2 seconds long, 25s after start


// pass this plan as initialPlan to Problem object
Expand Down Expand Up @@ -933,7 +933,7 @@ public void testCoexistenceWindowsBisect() { //bad, should fail completely. wort
PlanInMemory partialPlan = new PlanInMemory();
final var actTypeA = problem.getActivityType("ControllableDurationActivity");
partialPlan.add(SchedulingActivityDirective.of(actTypeA, planningHorizon.getStartAerie(), Duration.of(4, Duration.SECONDS), null, true)); //create an activity that's 5 seconds long, start at start
partialPlan.add(SchedulingActivityDirective.of(actTypeA, planningHorizon.getStartAerie().plus(Duration.of(8, Duration.SECONDS)), Duration.of(5, Duration.SECONDS), null, true)); //create an activity that's 5 seconds long, 11s after start
partialPlan.add(SchedulingActivityDirective.of(actTypeA, planningHorizon.getStartAerie().plus(Duration.of(8, Duration.SECONDS)), Duration.of(3, Duration.SECONDS), null, true)); //create an activity that's 5 seconds long, 11s after start

// pass this plan as initialPlan to Problem object
problem.setInitialPlan(partialPlan);
Expand Down
1 change: 1 addition & 0 deletions scheduler-server/sql/scheduler/applied_migrations.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ This file denotes which migrations occur "before" this version of the schema.
*/

call migrations.mark_migration_applied('0');
call migrations.mark_migration_applied('1');
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ create table scheduling_specification_goals (
constraint non_negative_specification_goal_priority check (priority >= 0),
enabled boolean default true,

simulate_after boolean not null default true,

constraint scheduling_specification_goals_primary_key
primary key (specification_id, goal_id),
constraint scheduling_specification_goals_unique_priorities
Expand All @@ -33,6 +35,8 @@ comment on column scheduling_specification_goals.goal_id is e''
comment on column scheduling_specification_goals.priority is e''
'The relative priority of a scheduling goal in relation to other '
'scheduling goals within the same specification.';
comment on column scheduling_specification_goals.simulate_after is e''
'Whether to re-simulate after evaluating this goal and before the next goal.';

create or replace function insert_scheduling_specification_goal_func()
returns trigger as $$begin
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package gov.nasa.jpl.aerie.scheduler.server.models;

public record GoalRecord(GoalId id, GoalSource definition, boolean enabled) {}
public record GoalRecord(GoalId id, GoalSource definition, boolean enabled, boolean simulateAfter) {}
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,17 @@

/*package-local*/ final class GetSpecificationGoalsAction implements AutoCloseable {
private final @Language("SQL") String sql = """
with
goals as
( select
s.specification_id,
s.goal_id,
s.priority,
s.enabled,
g.name,
g.definition,
g.revision
from scheduling_specification_goals as s
left join scheduling_goal as g
on s.goal_id = g.id )
select
g.goal_id,
s.goal_id,
g.name,
g.definition,
g.revision,
g.enabled
from goals as g
where g.specification_id = ?
order by g.priority asc
s.enabled,
s.simulate_after
from scheduling_specification_goals as s
left join scheduling_goal as g on s.goal_id = g.id
where s.specification_id = ?
order by s.priority;
""";

private final PreparedStatement statement;
Expand All @@ -51,7 +40,8 @@ public List<PostgresGoalRecord> get(final long specificationId) throws SQLExcept
final var name = resultSet.getString("name");
final var definition = resultSet.getString("definition");
final var enabled = resultSet.getBoolean("enabled");
goals.add(new PostgresGoalRecord(id, revision, name, definition, enabled));
final var simulateAfter = resultSet.getBoolean("simulate_after");
goals.add(new PostgresGoalRecord(id, revision, name, definition, enabled, simulateAfter));
}

return goals;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public static Goal goalOfGoalSpecifier(
final SchedulingDSL.GoalSpecifier goalSpecifier,
final Timestamp horizonStartTimestamp,
final Timestamp horizonEndTimestamp,
final Function<String, ActivityType> lookupActivityType) {
final Function<String, ActivityType> lookupActivityType,
final boolean simulateAfter) {
final var hor = new PlanningHorizon(
horizonStartTimestamp.toInstant(),
horizonEndTimestamp.toInstant()).getHor();
Expand All @@ -45,13 +46,15 @@ public static Goal goalOfGoalSpecifier(
.repeatingEvery(g.interval())
.shouldRollbackIfUnsatisfied(g.shouldRollbackIfUnsatisfied())
.thereExistsOne(makeActivityTemplate(g.activityTemplate(), lookupActivityType))
.simulateAfter(simulateAfter)
.build();
} else if (goalSpecifier instanceof SchedulingDSL.GoalSpecifier.CoexistenceGoalDefinition g) {
var builder = new CoexistenceGoal.Builder()
.forAllTimeIn(new WindowsWrapperExpression(new Windows(false).set(hor, true)))
.forEach(spansOfConstraintExpression(
g.forEach()))
.thereExistsOne(makeActivityTemplate(g.activityTemplate(), lookupActivityType))
.simulateAfter(simulateAfter)
.shouldRollbackIfUnsatisfied(g.shouldRollbackIfUnsatisfied())
.aliasForAnchors(g.alias());
if (g.startConstraint().isPresent()) {
Expand Down Expand Up @@ -82,8 +85,10 @@ public static Goal goalOfGoalSpecifier(
builder = builder.and(goalOfGoalSpecifier(subGoalSpecifier,
horizonStartTimestamp,
horizonEndTimestamp,
lookupActivityType));
lookupActivityType,
simulateAfter));
}
builder.simulateAfter(simulateAfter);
builder.forAllTimeIn(new WindowsWrapperExpression(new Windows(false).set(hor, true)));
builder.shouldRollbackIfUnsatisfied(g.shouldRollbackIfUnsatisfied());
return builder.build();
Expand All @@ -93,22 +98,25 @@ public static Goal goalOfGoalSpecifier(
builder = builder.or(goalOfGoalSpecifier(subGoalSpecifier,
horizonStartTimestamp,
horizonEndTimestamp,
lookupActivityType));
lookupActivityType,
simulateAfter));
}
builder.simulateAfter(simulateAfter);
builder.forAllTimeIn(new WindowsWrapperExpression(new Windows(false).set(hor, true)));
builder.shouldRollbackIfUnsatisfied(g.shouldRollbackIfUnsatisfied());
return builder.build();
}

else if (goalSpecifier instanceof SchedulingDSL.GoalSpecifier.GoalApplyWhen g) {
var goal = goalOfGoalSpecifier(g.goal(), horizonStartTimestamp, horizonEndTimestamp, lookupActivityType);
var goal = goalOfGoalSpecifier(g.goal(), horizonStartTimestamp, horizonEndTimestamp, lookupActivityType, simulateAfter);
goal.setTemporalContext(g.windows());
return goal;
}

else if(goalSpecifier instanceof SchedulingDSL.GoalSpecifier.CardinalityGoalDefinition g){
final var builder = new CardinalityGoal.Builder()
.thereExistsOne(makeActivityTemplate(g.activityTemplate(), lookupActivityType))
.simulateAfter(simulateAfter)
.forAllTimeIn(new WindowsWrapperExpression(new Windows(false).set(hor, true)))
.shouldRollbackIfUnsatisfied(g.shouldRollbackIfUnsatisfied());
if(g.specification().duration().isPresent()){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ public record PostgresGoalRecord(
long revision,
String name,
String definition,
boolean enabled
boolean enabled,
boolean simulateAfter
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ public Specification getSpecification(final SpecificationId specificationId)
.map((PostgresGoalRecord pgGoal) -> new GoalRecord(
new GoalId(pgGoal.id()),
new GoalSource(pgGoal.definition()),
pgGoal.enabled()
pgGoal.enabled(),
pgGoal.simulateAfter()
))
.toList();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import gov.nasa.jpl.aerie.scheduler.server.exceptions.SpecificationLoadException;
import gov.nasa.jpl.aerie.scheduler.server.http.ResponseSerializers;
import gov.nasa.jpl.aerie.scheduler.server.models.GoalId;
import gov.nasa.jpl.aerie.scheduler.server.models.GoalRecord;
import gov.nasa.jpl.aerie.scheduler.server.models.GoalSource;
import gov.nasa.jpl.aerie.scheduler.server.models.MerlinPlan;
import gov.nasa.jpl.aerie.scheduler.server.models.PlanId;
Expand Down Expand Up @@ -158,7 +159,7 @@ public void schedule(final ScheduleRequest request, final ResultsProtocol.Writer

final var orderedGoals = new ArrayList<Goal>();
final var goals = new HashMap<Goal, GoalId>();
final var compiledGoals = new ArrayList<Pair<GoalId, SchedulingDSL.GoalSpecifier>>();
final var compiledGoals = new ArrayList<Pair<GoalRecord, SchedulingDSL.GoalSpecifier>>();
final var failedGoals = new ArrayList<Pair<GoalId, List<SchedulingCompilationError.UserCodeError>>>();
for (final var goalRecord : specification.goalsByPriority()) {
if (!goalRecord.enabled()) continue;
Expand All @@ -168,7 +169,7 @@ public void schedule(final ScheduleRequest request, final ResultsProtocol.Writer
goalRecord.definition(),
schedulingDSLCompilationService);
if (result instanceof SchedulingDSLCompilationService.SchedulingDSLCompilationResult.Success<SchedulingDSL.GoalSpecifier> r) {
compiledGoals.add(Pair.of(goalRecord.id(), r.value()));
compiledGoals.add(Pair.of(goalRecord, r.value()));
} else if (result instanceof SchedulingDSLCompilationService.SchedulingDSLCompilationResult.Error<SchedulingDSL.GoalSpecifier> r) {
failedGoals.add(Pair.of(goalRecord.id(), r.errors()));
} else {
Expand All @@ -188,9 +189,10 @@ public void schedule(final ScheduleRequest request, final ResultsProtocol.Writer
compiledGoal.getValue(),
specification.horizonStartTimestamp(),
specification.horizonEndTimestamp(),
problem::getActivityType);
problem::getActivityType,
compiledGoal.getKey().simulateAfter());
orderedGoals.add(goal);
goals.put(goal, compiledGoal.getKey());
goals.put(goal, compiledGoal.getKey().id());
}
problem.setGoals(orderedGoals);

Expand Down
Loading

0 comments on commit e3b3578

Please sign in to comment.