diff --git a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/time/IntervalMap.java b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/time/IntervalMap.java index 3934ef93a5..2450304594 100644 --- a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/time/IntervalMap.java +++ b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/time/IntervalMap.java @@ -447,6 +447,10 @@ public Iterator> iterator() { return this.segments.iterator(); } + public Iterator> reverseIterator() { + return this.segments.descendingIterator(); + } + /** Creates an iterable over the Intervals where this map is equal to a value */ public Iterable iterateEqualTo(final V value) { return () -> this.segments 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 e2cab009e3..2cebddf2ec 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 @@ -657,6 +657,10 @@ public Iterable iterateEqualTo(final boolean value) { return segments.iterateEqualTo(value); } + public Iterator> reverseIterator() { + return segments.reverseIterator(); + } + /** Delegated to {@link IntervalMap#stream} */ public Stream> stream() { return segments.stream(); diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingActivityConflict.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingActivityConflict.java index 545965d515..c13c322d06 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingActivityConflict.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingActivityConflict.java @@ -3,6 +3,7 @@ import gov.nasa.jpl.aerie.constraints.model.EvaluationEnvironment; import gov.nasa.jpl.aerie.constraints.time.Windows; import gov.nasa.jpl.aerie.scheduler.goals.Goal; +import gov.nasa.jpl.aerie.scheduler.solver.ScheduleAt; /** * describes an issue in a plan whereby a desired activity instance is absent @@ -23,6 +24,8 @@ public MissingActivityConflict( super(goal, environment); } + public abstract ScheduleAt scheduleAt(); + /** * {@inheritDoc} * diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingActivityInstanceConflict.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingActivityInstanceConflict.java index 68b8f32416..c07b0752a4 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingActivityInstanceConflict.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingActivityInstanceConflict.java @@ -5,6 +5,7 @@ import gov.nasa.jpl.aerie.constraints.time.Windows; import gov.nasa.jpl.aerie.scheduler.goals.ActivityExistentialGoal; import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivity; +import gov.nasa.jpl.aerie.scheduler.solver.ScheduleAt; /** * describes an issue in a plan caused by a specific activity instance missing @@ -44,6 +45,11 @@ public String toString() { return "Empty conflict"; } + @Override + public ScheduleAt scheduleAt() { + return ScheduleAt.EARLIEST; + } + /** * {@inheritDoc} * diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingActivityTemplateConflict.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingActivityTemplateConflict.java index 91762fc7d0..823eb7f87b 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingActivityTemplateConflict.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/conflicts/MissingActivityTemplateConflict.java @@ -7,6 +7,7 @@ import gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityExpression; import gov.nasa.jpl.aerie.scheduler.goals.ActivityTemplateGoal; import gov.nasa.jpl.aerie.scheduler.goals.Goal; +import gov.nasa.jpl.aerie.scheduler.solver.ScheduleAt; import java.util.Optional; @@ -36,7 +37,8 @@ public MissingActivityTemplateConflict( int cardinality, Optional anchorIdTo, Optional anchorToStart, - Optional totalDuration) + Optional totalDuration, + ScheduleAt scheduleAtEarliest) { super(goal, evaluationEnvironment); @@ -50,6 +52,7 @@ public MissingActivityTemplateConflict( this.anchorIdTo = anchorIdTo; this.anchorToStart = anchorToStart; this.totalDuration = totalDuration; + this.scheduleAt = scheduleAtEarliest; } //the number of times the activity needs to be inserted @@ -67,6 +70,8 @@ public Optional getAnchorToStart() { //the desired total duration over the number of activities needed Optional totalDuration; + ScheduleAt scheduleAt; + public int getCardinality(){ return cardinality; } @@ -75,6 +80,11 @@ public Optional getTotalDuration(){ return totalDuration; } + @Override + public ScheduleAt scheduleAt() { + return scheduleAt; + } + /** * {@inheritDoc} * diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/CardinalityGoal.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/CardinalityGoal.java index 880bdb5297..6363bf4c9e 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/CardinalityGoal.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/CardinalityGoal.java @@ -15,6 +15,7 @@ import gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityExpression; import gov.nasa.jpl.aerie.scheduler.model.Plan; import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivity; +import gov.nasa.jpl.aerie.scheduler.solver.ScheduleAt; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -205,7 +206,8 @@ public Collection getConflicts( nbToSchedule, Optional.empty(), Optional.empty(), - durToSchedule.isPositive() ? Optional.of(durToSchedule) : Optional.empty())); + durToSchedule.isPositive() ? Optional.of(durToSchedule) : Optional.empty(), + ScheduleAt.EARLIEST)); } } diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/CoexistenceGoal.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/CoexistenceGoal.java index 920a654627..18fc3f95f7 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/CoexistenceGoal.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/CoexistenceGoal.java @@ -19,6 +19,7 @@ import gov.nasa.jpl.aerie.scheduler.model.PersistentTimeAnchor; import gov.nasa.jpl.aerie.scheduler.model.Plan; import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivity; +import gov.nasa.jpl.aerie.scheduler.solver.ScheduleAt; import java.util.ArrayList; import java.util.HashMap; @@ -305,7 +306,8 @@ public java.util.Collection getConflicts( 1, anchorValue, Optional.of(this.persistentAnchor.equals(PersistentTimeAnchor.START)), - Optional.empty() + Optional.empty(), + ScheduleAt.EARLIEST )); } else { final var actsToAssociate = missingActAssociationsWithAnchor.isEmpty() ? missingActAssociationsWithoutAnchor : missingActAssociationsWithAnchor; diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/solver/PrioritySolver.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/solver/PrioritySolver.java index d18e988c16..d5040493d0 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/solver/PrioritySolver.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/solver/PrioritySolver.java @@ -39,14 +39,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkNotNull; @@ -503,7 +496,7 @@ private void satisfyGoalGeneral(Goal goal) throws SchedulingInterruptedException } else if (missing instanceof MissingAssociationConflict missingAssociationConflict) { conflictSolverReturn = solveMissingAssociationConflict(missingAssociationConflict, goal); } else if(!analysisOnly && missing instanceof MissingActivityNetworkConflict missingActivityNetworkConflict){ - conflictSolverReturn = solveActivityNetworkConflict(missingActivityNetworkConflict, goal); + conflictSolverReturn = solveActivityNetworkConflict(missingActivityNetworkConflict, goal, ScheduleAt.EARLIEST); } else if(!analysisOnly && missing instanceof MissingRecurrenceConflict missingRecurrenceConflict){ conflictSolverReturn = solveMissingRecurrenceConflict(missingRecurrenceConflict, goal); } @@ -661,7 +654,8 @@ private Optional makeActivityNetworkConflict( private ConflictSolverResult solveActivityNetworkConflict( final MissingActivityNetworkConflict missingActivityNetworkConflict, - final Goal goal + final Goal goal, + final ScheduleAt scheduleAt ) throws SchedulingInterruptedException { var satisfaction = ConflictSatisfaction.NOT_SAT; @@ -694,7 +688,8 @@ private ConflictSolverResult solveActivityNetworkConflict( 1, Optional.empty(), Optional.of(true), - Optional.empty()); + Optional.empty(), + scheduleAt); final var satisfactionOfThisSubConflict = solveActivityTemplateConflict(derivedActivityTemplateConflict, goal, true); if(satisfactionOfThisSubConflict.satisfaction() == ConflictSatisfaction.SAT){ //apply constraints to network @@ -996,7 +991,8 @@ private Optional getBestNewActivity(MissingActivityConflict missingTemplate, goal.getName() + "_" + java.util.UUID.randomUUID(), startWindows, - missing.getEvaluationEnvironment()); + missing.getEvaluationEnvironment(), + missing.scheduleAt()); if(act.isPresent()){ if (missingTemplate.getAnchorId().isPresent()) { final SchedulingActivity predecessor = plan.getActivitiesById().get(missingTemplate.getAnchorId().get()); @@ -1158,17 +1154,27 @@ private Windows narrowGlobalConstraints( final MissingActivityTemplateConflict missingConflict, final String name, final Windows windows, - final EvaluationEnvironment evaluationEnvironment) throws SchedulingInterruptedException + final EvaluationEnvironment evaluationEnvironment, + final ScheduleAt scheduleAt) throws SchedulingInterruptedException { //REVIEW: how to properly export any flexibility to instance? logger.info("Trying to create one activity, will loop through possible windows"); - for (var window : windows.iterateEqualTo(true)) { - logger.info("Trying in window " + window); - var activity = instantiateActivity(missingConflict.getActTemplate(), name, window, missingConflict.getEvaluationEnvironment()); - if (activity.isPresent()) { - return activity; + var iterator = scheduleAt == ScheduleAt.EARLIEST ? windows.iterator() : windows.reverseIterator(); + while(iterator.hasNext()) { + final var segment = iterator.next(); + if(segment.value()) { + logger.info("Trying in window " + segment.interval()); + var activity = instantiateActivity( + missingConflict.getActTemplate(), + name, + segment.interval(), + missingConflict.getEvaluationEnvironment(), + scheduleAt); + if (activity.isPresent()) { + return activity; + } + } } - } return Optional.empty(); } @@ -1176,7 +1182,8 @@ private Optional instantiateActivity( final ActivityExpression activityExpression, final String name, final Interval interval, - final EvaluationEnvironment evaluationEnvironment + final EvaluationEnvironment evaluationEnvironment, + final ScheduleAt scheduleAt ) throws SchedulingInterruptedException { final var planningHorizon = this.problem.getPlanningHorizon(); final var envelopes = new ArrayList(); @@ -1242,15 +1249,15 @@ public Duration valueAt(Duration start, final EquationSolvingAlgorithms.History< } }; - return rootFindingHelper(f, history, solved); + return rootFindingHelper(f, history, solved, scheduleAt); //CASE 2: activity has a controllable duration } else if (activityExpression.type().getDurationType() instanceof DurationType.Controllable dt) { //select earliest start time, STN guarantees satisfiability - final var earliestStart = solved.start().start; + final var start = scheduleAt == ScheduleAt.EARLIEST ? solved.start().start : solved.start().end; final var instantiatedArguments = SchedulingActivity.instantiateArguments( activityExpression.arguments(), - earliestStart, - getLatestSimResultsUpTo(earliestStart, resourceNames).constraintsResults(), + start, + getLatestSimResultsUpTo(start, resourceNames).constraintsResults(), evaluationEnvironment, activityExpression.type()); @@ -1274,7 +1281,7 @@ public Duration valueAt(Duration start, final EquationSolvingAlgorithms.History< return Optional.of(SchedulingActivity.of( idGenerator.next(), activityExpression.type(), - earliestStart, + start, setActivityDuration, instantiatedArguments, null, @@ -1288,17 +1295,17 @@ public Duration valueAt(Duration start, final EquationSolvingAlgorithms.History< return Optional.empty(); } - final var earliestStart = solved.start().start; + final var start = scheduleAt == ScheduleAt.EARLIEST ? solved.start().start : solved.start().end; // TODO: When scheduling is allowed to create activities with anchors, this constructor should pull from an expanded creation template return Optional.of(SchedulingActivity.of( idGenerator.next(), activityExpression.type(), - earliestStart, + start, dt.duration(), SchedulingActivity.instantiateArguments( activityExpression.arguments(), - earliestStart, - getLatestSimResultsUpTo(earliestStart, resourceNames).constraintsResults(), + start, + getLatestSimResultsUpTo(start, resourceNames).constraintsResults(), evaluationEnvironment, activityExpression.type()), null, @@ -1341,7 +1348,7 @@ public Duration valueAt(final Duration start, final EquationSolvingAlgorithms.Hi } }; - return rootFindingHelper(f, history, solved); + return rootFindingHelper(f, history, solved, scheduleAt); } else { throw new UnsupportedOperationException("Unsupported duration type found: " + activityExpression.type().getDurationType()); } @@ -1350,7 +1357,8 @@ public Duration valueAt(final Duration start, final EquationSolvingAlgorithms.Hi private Optional rootFindingHelper( final EquationSolvingAlgorithms.Function f, final HistoryWithActivity history, - final TaskNetworkAdapter.TNActData solved + final TaskNetworkAdapter.TNActData solved, + final ScheduleAt scheduleAt ) throws SchedulingInterruptedException { try { var endInterval = solved.end(); @@ -1363,7 +1371,7 @@ private Optional rootFindingHelper( .findRoot( f, history, - startInterval.start, + scheduleAt == ScheduleAt.EARLIEST ? startInterval.start : startInterval.end, endInterval.start.plus(durationHalfEndInterval), durationHalfEndInterval, durationHalfEndInterval, diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/solver/ScheduleAt.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/solver/ScheduleAt.java new file mode 100644 index 0000000000..a17ffa3bfa --- /dev/null +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/solver/ScheduleAt.java @@ -0,0 +1,6 @@ +package gov.nasa.jpl.aerie.scheduler.solver; + +public enum ScheduleAt { + EARLIEST, + LATEST +}