diff --git a/scheduler-driver/build.gradle b/scheduler-driver/build.gradle index a3f808ceb1..c62f98bc9b 100644 --- a/scheduler-driver/build.gradle +++ b/scheduler-driver/build.gradle @@ -33,7 +33,7 @@ dependencies { implementation 'com.google.guava:guava:32.1.2-jre' implementation 'org.jgrapht:jgrapht-core:1.5.2' implementation 'org.slf4j:slf4j-simple:2.0.7' - + implementation 'org.apache.commons:commons-collections4:4.4' testImplementation project(':merlin-framework-junit') testImplementation project(':constraints') testImplementation project(':examples:banananation') 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 3cdba1bce4..5a16b0edc1 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 @@ -4,19 +4,21 @@ 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.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerModel; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; +import gov.nasa.jpl.aerie.scheduler.Range; +import gov.nasa.jpl.aerie.scheduler.conflicts.Conflict; +import gov.nasa.jpl.aerie.scheduler.conflicts.MissingActivityTemplateConflict; +import gov.nasa.jpl.aerie.scheduler.conflicts.MissingAssociationConflict; import gov.nasa.jpl.aerie.scheduler.conflicts.UnsatisfiableGoalConflict; import gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityExpression; +import gov.nasa.jpl.aerie.scheduler.model.Plan; +import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityDirective; import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityDirectiveId; +import org.apache.commons.collections4.BidiMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityDirective; -import gov.nasa.jpl.aerie.scheduler.conflicts.Conflict; -import gov.nasa.jpl.aerie.scheduler.model.Plan; -import gov.nasa.jpl.aerie.scheduler.Range; -import gov.nasa.jpl.aerie.scheduler.conflicts.MissingActivityTemplateConflict; -import gov.nasa.jpl.aerie.scheduler.conflicts.MissingAssociationConflict; import java.util.Collection; import java.util.Comparator; @@ -127,6 +129,7 @@ protected CardinalityGoal fill(CardinalityGoal goal) { public Collection getConflicts( final Plan plan, final SimulationResults simulationResults, + final Optional> mapSchedulingIdsToActivityIds, final EvaluationEnvironment evaluationEnvironment, final SchedulerModel schedulerModel) { @@ -200,7 +203,7 @@ else if (this.initiallyEvaluatedTemporalContext == null) { for (final var act : acts) { if (!associatedActivitiesToThisGoal.contains(act) && planEvaluation.canAssociateMoreToCreatorOf(act)) { //they ALL have to be associated - conflicts.add(new MissingAssociationConflict(this, List.of(act))); + conflicts.add(new MissingAssociationConflict(this, List.of(act), Optional.empty(), Optional.empty())); } } @@ -211,6 +214,8 @@ else if (this.initiallyEvaluatedTemporalContext == null) { this.desiredActTemplate, evaluationEnvironment, nbToSchedule, + Optional.empty(), + Optional.empty(), durToSchedule.isPositive() ? Optional.of(durToSchedule) : Optional.empty())); } } diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/Goal.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/Goal.java index 2fd21d76ef..ac375ec1a0 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/Goal.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/Goal.java @@ -7,14 +7,18 @@ import gov.nasa.jpl.aerie.constraints.tree.And; import gov.nasa.jpl.aerie.constraints.tree.Expression; import gov.nasa.jpl.aerie.constraints.tree.WindowsWrapperExpression; +import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerModel; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.scheduler.conflicts.Conflict; import gov.nasa.jpl.aerie.scheduler.model.Plan; import gov.nasa.jpl.aerie.scheduler.model.PlanningHorizon; +import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityDirectiveId; +import org.apache.commons.collections4.BidiMap; import java.util.LinkedList; import java.util.List; +import java.util.Optional; /** * describes some criteria that is desired in the solution plans @@ -288,6 +292,7 @@ public String getName() { public java.util.Collection getConflicts( Plan plan, final SimulationResults simulationResults, + final Optional> mapSchedulingIdsToActivityIds, final EvaluationEnvironment evaluationEnvironment, final SchedulerModel schedulerModel ) { diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/OptionGoal.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/OptionGoal.java index fdfc792a27..4cac7cca10 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/OptionGoal.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/OptionGoal.java @@ -2,14 +2,18 @@ import gov.nasa.jpl.aerie.constraints.model.EvaluationEnvironment; import gov.nasa.jpl.aerie.constraints.model.SimulationResults; +import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerModel; import gov.nasa.jpl.aerie.scheduler.conflicts.Conflict; import gov.nasa.jpl.aerie.scheduler.model.Plan; +import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityDirectiveId; import gov.nasa.jpl.aerie.scheduler.solver.optimizers.Optimizer; +import org.apache.commons.collections4.BidiMap; import org.apache.commons.lang3.NotImplementedException; import java.util.ArrayList; import java.util.List; +import java.util.Optional; public class OptionGoal extends Goal { @@ -33,6 +37,7 @@ public Optimizer getOptimizer(){ public java.util.Collection getConflicts( final Plan plan, final SimulationResults simulationResults, + final Optional> mapSchedulingIdsToActivityIds, final EvaluationEnvironment evaluationEnvironment, final SchedulerModel schedulerModel) { throw new NotImplementedException("Conflict detection is performed at solver level"); diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/ProceduralCreationGoal.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/ProceduralCreationGoal.java index d92cdd9ea4..981f40a972 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/ProceduralCreationGoal.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/ProceduralCreationGoal.java @@ -4,15 +4,19 @@ import gov.nasa.jpl.aerie.constraints.model.SimulationResults; import gov.nasa.jpl.aerie.constraints.time.Interval; import gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerModel; +import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityExpression; import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityDirective; import gov.nasa.jpl.aerie.scheduler.conflicts.Conflict; import gov.nasa.jpl.aerie.scheduler.model.Plan; import gov.nasa.jpl.aerie.scheduler.conflicts.MissingActivityInstanceConflict; import gov.nasa.jpl.aerie.scheduler.conflicts.MissingAssociationConflict; +import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityDirectiveId; +import org.apache.commons.collections4.BidiMap; import java.util.ArrayList; import java.util.Collection; +import java.util.Optional; import java.util.function.Function; /** @@ -113,6 +117,7 @@ protected ProceduralCreationGoal fill(ProceduralCreationGoal goal) { public Collection getConflicts( final Plan plan, final SimulationResults simulationResults, + final Optional> mapSchedulingIdsToActivityIds, final EvaluationEnvironment evaluationEnvironment, final SchedulerModel schedulerModel) { final var conflicts = new java.util.LinkedList(); @@ -160,7 +165,11 @@ public Collection getConflicts( //REVIEW: pass the requested instance to conflict or otherwise cache it // for the imminent request to create it in the plan } else { - conflicts.add(new MissingAssociationConflict(this, missingActAssociations)); + conflicts.add(new MissingAssociationConflict( + this, + missingActAssociations, + Optional.empty(), + Optional.empty())); } } }//for(requestedAct) diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/RecurrenceGoal.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/RecurrenceGoal.java index 5130000230..c5168ad05f 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/RecurrenceGoal.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/RecurrenceGoal.java @@ -4,6 +4,7 @@ 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.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerModel; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.DurationType; @@ -14,6 +15,8 @@ import gov.nasa.jpl.aerie.scheduler.model.Plan; import gov.nasa.jpl.aerie.scheduler.conflicts.MissingActivityTemplateConflict; import gov.nasa.jpl.aerie.scheduler.conflicts.MissingAssociationConflict; +import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityDirectiveId; +import org.apache.commons.collections4.BidiMap; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -113,6 +116,7 @@ else if (every.max.isNegative()) { public java.util.Collection getConflicts( @NotNull final Plan plan, final SimulationResults simulationResults, + final Optional> mapSchedulingIdsToActivityIds, final EvaluationEnvironment evaluationEnvironment, final SchedulerModel schedulerModel) { final var conflicts = new java.util.LinkedList(); @@ -171,7 +175,7 @@ else if (this.initiallyEvaluatedTemporalContext == null) { var planEval = plan.getEvaluation(); if (!planEval.forGoal(this).getAssociatedActivities().contains(act) && planEval.canAssociateMoreToCreatorOf( act)) { - conflicts.add(new MissingAssociationConflict(this, List.of(act))); + conflicts.add(new MissingAssociationConflict(this, List.of(act), Optional.empty(), Optional.empty())); } } @@ -222,7 +226,7 @@ private java.util.Collection makeRecurrenceConflicts(Du ) { final var windows = new Windows(false).set(Interval.betweenClosedOpen(intervalT.minus(recurrenceInterval.max), Duration.min(intervalT, end)), true); if(windows.iterateEqualTo(true).iterator().hasNext()){ - conflicts.add(new MissingActivityTemplateConflict(this, windows, this.getActTemplate(), evaluationEnvironment, 1, Optional.empty())); + conflicts.add(new MissingActivityTemplateConflict(this, windows, this.getActTemplate(), evaluationEnvironment, 1,Optional.empty(), Optional.empty(), Optional.empty())); } if(intervalT.compareTo(end) >= 0){ break; diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/Problem.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/Problem.java index 01aa3d3f8f..8b9f39bf61 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/Problem.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/Problem.java @@ -2,6 +2,7 @@ import gov.nasa.jpl.aerie.constraints.model.DiscreteProfile; import gov.nasa.jpl.aerie.constraints.model.LinearProfile; +import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.MissionModel; import gov.nasa.jpl.aerie.merlin.driver.SimulationResults; import gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerModel; @@ -10,6 +11,7 @@ import gov.nasa.jpl.aerie.scheduler.simulation.SimulationData; import gov.nasa.jpl.aerie.scheduler.simulation.SimulationFacade; import gov.nasa.jpl.aerie.scheduler.simulation.SimulationResultsConverter; +import org.apache.commons.collections4.BidiMap; import java.util.ArrayList; import java.util.Collection; @@ -138,12 +140,12 @@ public Plan getInitialPlan() { * @param initialSimulationResults optional initial simulation results associated to the initial plan * @param plan the initial seed plan that schedulers may start from */ - public void setInitialPlan(final Plan plan, final Optional initialSimulationResults) { + public void setInitialPlan(final Plan plan, final Optional initialSimulationResults, final BidiMap mapSchedulingIdsToActivityIds) { initialPlan = plan; this.initialSimulationResults = initialSimulationResults.map(simulationResults -> new SimulationData( simulationResults, SimulationResultsConverter.convertToConstraintModelResults( - simulationResults))); + simulationResults), Optional.ofNullable(mapSchedulingIdsToActivityIds))); } /** @@ -152,7 +154,7 @@ public void setInitialPlan(final Plan plan, final Optional in * @param plan the initial seed plan that schedulers may start from */ public void setInitialPlan(final Plan plan) { - setInitialPlan(plan, Optional.empty()); + setInitialPlan(plan, Optional.empty(), null); } public Optional getInitialSimulationResults(){ return initialSimulationResults; } diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/SimulationData.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/SimulationData.java index c3cb890607..09684c8ff8 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/SimulationData.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/SimulationData.java @@ -1,11 +1,15 @@ package gov.nasa.jpl.aerie.scheduler.simulation; +import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId; import gov.nasa.jpl.aerie.merlin.driver.SimulationResults; -import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityDirective; +import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityDirectiveId; +import org.apache.commons.collections4.BidiMap; import java.util.Collection; +import java.util.Optional; public record SimulationData( SimulationResults driverResults, - gov.nasa.jpl.aerie.constraints.model.SimulationResults constraintsResults + gov.nasa.jpl.aerie.constraints.model.SimulationResults constraintsResults, + Optional> mapSchedulingIdsToActivityIds ){} diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/SimulationFacade.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/SimulationFacade.java index 9d64fc19f8..d550fc9be9 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/SimulationFacade.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/simulation/SimulationFacade.java @@ -26,6 +26,9 @@ import java.util.Optional; import java.util.function.Supplier; +import org.apache.commons.collections4.BidiMap; +import org.apache.commons.collections4.bidimap.DualHashBidiMap; + /** * A facade for simulating plans and processing simulation results. */ @@ -42,9 +45,8 @@ public class SimulationFacade implements AutoCloseable{ private Map activityTypes; private ResumableSimulationDriver driver; private int itSimActivityId; + private final BidiMap mapSchedulingIdsToActivityIds; - private final Map - planActDirectiveIdToSimulationActivityDirectiveId = new HashMap<>(); private final Map insertedActivities; //counts the total number of simulation restarts, used as performance metric in the scheduler private int pastSimulationRestarts; @@ -117,6 +119,7 @@ public SimulationFacade( this.driver = new ResumableSimulationDriver<>(missionModel, planningHorizon.getAerieHorizonDuration(), canceledListener); this.itSimActivityId = 0; this.insertedActivities = new HashMap<>(); + this.mapSchedulingIdsToActivityIds = new DualHashBidiMap<>(); this.activityTypes = new HashMap<>(); this.pastSimulationRestarts = 0; this.initialPlan = new ArrayList<>(); @@ -146,9 +149,20 @@ public void setActivityTypes(final Collection activityTypes){ } public Map getActivityIdCorrespondence(){ - return planActDirectiveIdToSimulationActivityDirectiveId; + return new HashMap<>(mapSchedulingIdsToActivityIds); + } + + public Optional> getBidiActivityIdCorrespondence(){ + if(initialSimulationResults.isEmpty() || initialPlanHasBeenModified) + return Optional.ofNullable(mapSchedulingIdsToActivityIds); + else if(initialSimulationResults.isPresent()) + return initialSimulationResults.get().mapSchedulingIdsToActivityIds(); + else + return Optional.empty(); } + + /** * Fetches activity instance durations from last simulation * @@ -156,10 +170,10 @@ public Map getActivityIdCorr * @return the duration if found in the last simulation, null otherwise */ public Optional getActivityDuration(final SchedulingActivityDirective schedulingActivityDirective) { - if(!planActDirectiveIdToSimulationActivityDirectiveId.containsKey(schedulingActivityDirective.getId())){ + if(!mapSchedulingIdsToActivityIds.containsKey(schedulingActivityDirective.getId())){ return Optional.empty(); } - final var duration = driver.getActivityDuration(planActDirectiveIdToSimulationActivityDirectiveId.get( + final var duration = driver.getActivityDuration(mapSchedulingIdsToActivityIds.get( schedulingActivityDirective.getId())); return duration; } @@ -190,9 +204,7 @@ public Map getAllChi latestSimulationData.get().simulatedActivities.forEach( (activityInstanceId, activity) -> { if (activity.parentId() == null) return; final var rootParent = getIdOfRootParent(this.lastSimulationData.driverResults(), activityInstanceId); - final var schedulingActId = planActDirectiveIdToSimulationActivityDirectiveId.entrySet().stream().filter( - entry -> entry.getValue().equals(rootParent) - ).findFirst().get().getKey(); + final var schedulingActId = mapSchedulingIdsToActivityIds.inverseBidiMap().get(rootParent); final var activityInstance = SchedulingActivityDirective.of( activityTypes.get(activity.type()), this.planningHorizon.toDur(activity.start()), @@ -234,7 +246,7 @@ public void removeAndInsertActivitiesFromSimulation( if(atLeastOneActualRemoval || earliestActStartTime.noLongerThan(this.driver.getCurrentSimulationEndTime())){ allActivitiesToSimulate.addAll(insertedActivities.keySet()); insertedActivities.clear(); - planActDirectiveIdToSimulationActivityDirectiveId.clear(); + mapSchedulingIdsToActivityIds.clear(); logger.info("(Re)creating simulation driver because at least one removal("+atLeastOneActualRemoval+") or insertion in the past ("+earliestActStartTime+")"); if (driver != null) { this.pastSimulationRestarts += driver.getCountSimulationRestarts(); @@ -272,8 +284,8 @@ public void insertActivitiesIntoSimulation(final Collection activities) @@ -298,13 +310,12 @@ private void simulateActivities(final Collection ac for(final var activity : activitiesSortedByStartTime){ final var activityIdSim = new ActivityDirectiveId(itSimActivityId++); - planActDirectiveIdToSimulationActivityDirectiveId.put(activity.getId(), activityIdSim); + mapSchedulingIdsToActivityIds.put(activity.getId(), activityIdSim); } for(final var activity : activitiesSortedByStartTime) { final var activityDirective = schedulingActToActivityDir(activity); - directivesToSimulate.put( - planActDirectiveIdToSimulationActivityDirectiveId.get(activity.getId()), + directivesToSimulate.put(mapSchedulingIdsToActivityIds.get(activity.getId()), activityDirective); insertedActivities.put(activity, activityDirective); } @@ -336,7 +347,7 @@ public void computeSimulationResultsUntil(final Duration endTime) //compare references if(lastSimulationData == null || results != lastSimulationData.driverResults()) { //simulation results from the last simulation, as converted for use by the constraint evaluation engine - this.lastSimulationData = new SimulationData(results, SimulationResultsConverter.convertToConstraintModelResults(results)); + this.lastSimulationData = new SimulationData(results, SimulationResultsConverter.convertToConstraintModelResults(results), Optional.ofNullable(mapSchedulingIdsToActivityIds)); } } catch (SchedulingInterruptedException e){ throw e; //pass interruption up @@ -369,13 +380,13 @@ private ActivityDirective schedulingActToActivityDir(SchedulingActivityDirective } } final var serializedActivity = new SerializedActivity(activity.getType().getName(), arguments); - if(activity.anchorId()!= null && !planActDirectiveIdToSimulationActivityDirectiveId.containsKey(activity.anchorId())){ + if(activity.anchorId()!= null && !mapSchedulingIdsToActivityIds.containsKey(activity.anchorId())){ throw new RuntimeException("Activity with id "+ activity.anchorId() + " referenced as an anchor by activity " + activity.toString() + " is not present in the plan"); } return new ActivityDirective( activity.startOffset(), serializedActivity, - planActDirectiveIdToSimulationActivityDirectiveId.get(activity.anchorId()), + mapSchedulingIdsToActivityIds.get(activity.anchorId()), activity.anchoredToStart()); } } diff --git a/scheduler-worker/build.gradle b/scheduler-worker/build.gradle index a695eafbf5..ccd1ccaebf 100644 --- a/scheduler-worker/build.gradle +++ b/scheduler-worker/build.gradle @@ -117,6 +117,7 @@ dependencies { implementation 'org.slf4j:slf4j-simple:2.0.7' implementation 'org.eclipse:yasson:3.0.3' implementation 'org.postgresql:postgresql:42.6.1' + implementation 'org.apache.commons:commons-collections4:4.4' implementation 'com.zaxxer:HikariCP:5.0.1' testImplementation project(':examples:foo-missionmodel') diff --git a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java index 072667c14a..6a021e19a1 100644 --- a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java +++ b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java @@ -68,8 +68,10 @@ import gov.nasa.jpl.aerie.scheduler.server.services.SpecificationService; import gov.nasa.jpl.aerie.scheduler.simulation.SimulationFacade; import gov.nasa.jpl.aerie.scheduler.solver.PrioritySolver; +import org.apache.commons.collections4.BidiMap; +import org.apache.commons.collections4.bidimap.DualHashBidiMap; import org.apache.commons.lang3.tuple.Pair; - +import java.util.HashSet; /** * agent that handles posed scheduling requests by blocking the requester thread until scheduling is complete * @@ -140,7 +142,7 @@ public void schedule( final var initialSimulationResults = loadSimulationResults(planMetadata); //seed the problem with the initial plan contents final var loadedPlanComponents = loadInitialPlan(planMetadata, problem, initialSimulationResults); - problem.setInitialPlan(loadedPlanComponents.schedulerPlan(), initialSimulationResults); + problem.setInitialPlan(loadedPlanComponents.schedulerPlan(), initialSimulationResults, loadedPlanComponents.mapSchedulingIdsToActivityIds); problem.setExternalProfile(externalProfiles.realProfiles(), externalProfiles.discreteProfiles()); //apply constraints/goals to the problem final var compiledGlobalSchedulingConditions = new ArrayList(); @@ -237,6 +239,9 @@ public void schedule( activityToGoalId, schedulerMissionModel.schedulerModel() ); + List updatedActs = updateEverythingWithNewAnchorIds(solutionPlan, instancesToIds); + merlinService.updatePlanActivityDirectiveAnchors(specification.planId(), updatedActs, instancesToIds); + final var planMetadataAfterChanges = merlinService.getPlanMetadata(specification.planId()); final var datasetId = storeSimulationResults(planningHorizon, simulationFacade, planMetadataAfterChanges, instancesToIds); //collect results and notify subscribers of success @@ -281,6 +286,24 @@ public void schedule( } } + public List updateEverythingWithNewAnchorIds(Plan solutionPlan, Map instancesToIds){ + ArrayList updatedActs = new ArrayList(); + var planActs = new HashSet<>(solutionPlan.getActivities()); + for (SchedulingActivityDirective act : planActs) { + if (act.anchorId() != null) { + SchedulingActivityDirective actAnchored = solutionPlan.getActivitiesById().get(act.anchorId()); + SchedulingActivityDirective updatedAct = SchedulingActivityDirective.copyOf(act, new SchedulingActivityDirectiveId(instancesToIds.get(actAnchored).id()), act.anchoredToStart(), act.startOffset()); + updatedActs.add(updatedAct); + solutionPlan.replaceActivity(act, updatedAct); + ActivityDirectiveId value = instancesToIds.get(act); + instancesToIds.remove(act); + instancesToIds.put(updatedAct, value); + } + } + return updatedActs; + } + + private Optional loadSimulationResults(final PlanMetadata planMetadata){ try { return merlinService.getSimulationResults(planMetadata); @@ -390,6 +413,7 @@ private PlanComponents loadInitialPlan( final Optional initialSimulationResults) { //TODO: maybe paranoid check if plan rev has changed since original metadata? try { + BidiMap mapSchedulingIdsToActivityIds = new DualHashBidiMap(); final var merlinPlan = merlinService.getPlanActivityDirectives(planMetadata, problem); final Map schedulingIdToDirectiveId = new HashMap<>(); final var plan = new PlanInMemory(); @@ -426,17 +450,24 @@ private PlanComponents loadInitialPlan( throw new Error("Unhandled variant of DurationType:" + schedulerActType.getDurationType()); } final var act = SchedulingActivityDirective.fromActivityDirective(elem.getKey(), activity, schedulerActType, actDuration); - schedulingIdToDirectiveId.put(act.getId(), elem.getKey()); plan.add(act); + if(initialSimulationResults.isPresent()){ + for(final var simAct: initialSimulationResults.get().simulatedActivities.entrySet()){ + if(simAct.getValue().directiveId().isPresent() && + simAct.getValue().directiveId().get().equals(elem.getKey())){ + mapSchedulingIdsToActivityIds.put(act.getId(), new ActivityDirectiveId(simAct.getKey().id())); + } + } + } } - return new PlanComponents(plan, merlinPlan, schedulingIdToDirectiveId); + return new PlanComponents(plan, mapSchedulingIdsToActivityIds, merlinPlan, schedulingIdToDirectiveId); } catch (Exception e) { throw new ResultsProtocolFailure(e); } } - record PlanComponents(Plan schedulerPlan, MerlinPlan merlinPlan, Map idMap) {} + record PlanComponents(Plan schedulerPlan, BidiMap mapSchedulingIdsToActivityIds, MerlinPlan merlinPlan, Map idMap) {} record SchedulerMissionModel(MissionModel missionModel, SchedulerModel schedulerModel) {} /**