Skip to content

Commit

Permalink
Mock simulation results if no resources are needed
Browse files Browse the repository at this point in the history
  • Loading branch information
adrienmaillard committed Feb 16, 2024
1 parent 4318315 commit 6957ada
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package gov.nasa.jpl.aerie.scheduler.model;

import gov.nasa.jpl.aerie.constraints.model.ActivityInstance;
import gov.nasa.jpl.aerie.constraints.time.Interval;
import gov.nasa.jpl.aerie.merlin.driver.ActivityDirective;
import gov.nasa.jpl.aerie.merlin.driver.ActivityDirectiveId;
import gov.nasa.jpl.aerie.merlin.driver.StartOffsetReducer;
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
import org.apache.commons.lang3.tuple.Pair;

import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class SchedulePlanGrounder {
public static Optional<List<ActivityInstance>> groundSchedule(
final List<SchedulingActivityDirective> schedulingActivityDirectiveList,
final Duration planDuration){
final var grounded = new HashMap<ActivityDirectiveId, ActivityInstance>();
final var idMap = schedulingActivityDirectiveList.stream().map(a -> Pair.of(new ActivityDirectiveId(a.getId().id()), a)).collect(Collectors.toMap(
Pair::getLeft, Pair::getRight));
final var converted = schedulingActivityDirectiveList.stream().map(a ->
Pair.of(
new ActivityDirectiveId(a.id().id()),
new ActivityDirective(
a.startOffset(),
a.type().getName(),
a.arguments(),
(a.anchorId() == null) ? null : new ActivityDirectiveId(a.anchorId().id()),
a.anchoredToStart()
))).collect(Collectors.toMap(Pair::getLeft,
Pair::getRight));
final var converter = new StartOffsetReducer(planDuration, converted);
var computed = converter.compute();
computed = StartOffsetReducer.filterOutNegativeStartOffset(computed);

for(final var entry: computed.entrySet()){
Duration offset = Duration.ZERO;
final var idActivity = entry.getKey();
if(idActivity != null){
if(grounded.get(idActivity) == null){
return Optional.empty();
} else {
final var alreadyGroundedAct = grounded.get(idActivity);
offset = alreadyGroundedAct.interval.end;
}
}
for(final var activityAndDependents : entry.getValue()) {
final var dependentId = activityAndDependents.getKey();
final var dependentOriginalActivity = idMap.get(dependentId);
final var startTime = offset.plus(activityAndDependents.getRight());
//happens only in tests
if(dependentOriginalActivity.duration() == null){
return Optional.empty();
}
grounded.put(dependentId, new ActivityInstance(
dependentId.id(),
dependentOriginalActivity.type().getName(),
dependentOriginalActivity.arguments(),
Interval.between(startTime, startTime.plus(dependentOriginalActivity.duration()))));
}
}
return Optional.of(grounded.values().stream().toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@ public static SchedulingActivityDirective of(ActivityType type, Duration startOf
Map.of(), null, anchorId, anchoredToStart);

}
public static SchedulingActivityDirective of(SchedulingActivityDirectiveId id, ActivityType type, Duration startOffset, Duration duration, SchedulingActivityDirectiveId anchorId, boolean anchoredToStart) {
return new SchedulingActivityDirective(
id,
type,
startOffset,
duration,
Map.of(),
null,
anchorId,
anchoredToStart);
}

public static SchedulingActivityDirective of(ActivityType type, Duration startOffset, Duration duration, Map<String, SerializedValue> parameters, SchedulingActivityDirectiveId anchorId, boolean anchoredToStart) {
return new SchedulingActivityDirective(new SchedulingActivityDirectiveId(uniqueId.getAndIncrement()), type,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package gov.nasa.jpl.aerie.scheduler.solver;

import gov.nasa.jpl.aerie.constraints.model.ActivityInstance;
import gov.nasa.jpl.aerie.constraints.model.DiscreteProfile;
import gov.nasa.jpl.aerie.constraints.model.EvaluationEnvironment;
import gov.nasa.jpl.aerie.constraints.model.LinearProfile;
import gov.nasa.jpl.aerie.constraints.model.SimulationResults;
import gov.nasa.jpl.aerie.constraints.time.Interval;
import gov.nasa.jpl.aerie.constraints.time.Segment;
Expand All @@ -26,20 +29,23 @@
import gov.nasa.jpl.aerie.scheduler.model.Plan;
import gov.nasa.jpl.aerie.scheduler.model.PlanInMemory;
import gov.nasa.jpl.aerie.scheduler.model.Problem;
import gov.nasa.jpl.aerie.scheduler.model.SchedulePlanGrounder;
import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityDirective;
import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityDirectiveId;
import gov.nasa.jpl.aerie.scheduler.simulation.SimulationFacade;
import gov.nasa.jpl.aerie.scheduler.solver.stn.TaskNetwork;
import gov.nasa.jpl.aerie.scheduler.solver.stn.TaskNetworkAdapter;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
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.stream.Collectors;
Expand All @@ -61,7 +67,9 @@ public class PrioritySolver implements Solver {

private boolean checkSimBeforeEvaluatingGoal;

private SimulationResults lastSimulationResults;
private boolean atLeastOneSimulateAfter;

private SimulationResults cachedSimulationResultsBeforeGoalEvaluation;

/**
* boolean stating whether only conflict analysis should be performed or not
Expand Down Expand Up @@ -135,6 +143,7 @@ public PrioritySolver(final Problem problem, final boolean analysisOnly) {
checkNotNull(problem, "creating solver with null input problem descriptor");
this.checkSimBeforeInsertingActivities = true;
this.checkSimBeforeEvaluatingGoal = true;
this.atLeastOneSimulateAfter = false;
this.problem = problem;
this.simulationFacade = problem.getSimulationFacade();
this.analysisOnly = analysisOnly;
Expand Down Expand Up @@ -283,6 +292,8 @@ private LinkedList<Goal> getGoalQueue() {
final var rawGoals = problem.getGoals();
assert rawGoals != null;

this.atLeastOneSimulateAfter = rawGoals.stream().filter(g -> g.simulateAfter).findFirst().isPresent();

//create queue container using comparator and pre-sized for all goals
final var capacity = rawGoals.size();
assert capacity >= 0;
Expand Down Expand Up @@ -579,9 +590,9 @@ private Collection<Conflict> getConflicts(Goal goal) throws SchedulingInterrupte
logger.debug("Computing simulation results until "+ this.problem.getPlanningHorizon().getEndAerie() + " (planning horizon end) in order to compute conflicts");
final var resources = new HashSet<String>();
goal.extractResources(resources);
this.getLatestSimResultsUpTo(this.problem.getPlanningHorizon().getEndAerie(), resources);
final var simulationResults = this.getLatestSimResultsUpTo(this.problem.getPlanningHorizon().getEndAerie(), resources);
final var evaluationEnvironment = new EvaluationEnvironment(this.problem.getRealExternalProfiles(), this.problem.getDiscreteExternalProfiles());
final var rawConflicts = goal.getConflicts(plan, lastSimulationResults, evaluationEnvironment);
final var rawConflicts = goal.getConflicts(plan, simulationResults, evaluationEnvironment);
assert rawConflicts != null;
return rawConflicts;
}
Expand Down Expand Up @@ -748,14 +759,42 @@ private Windows narrowByResourceConstraints(
return ret;
}


private SimulationResults getLatestSimResultsUpTo(final Duration time, final Set<String> resourceNames) throws SchedulingInterruptedException {
//if no resource is needed, build the results from the current plan
if(resourceNames.isEmpty()) {
final var groundedPlan = SchedulePlanGrounder.groundSchedule(
this.plan.getActivities().stream().toList(),
this.problem.getPlanningHorizon().getEndAerie());
if (groundedPlan.isPresent()) {
return new SimulationResults(
problem.getPlanningHorizon().getStartInstant(),
problem.getPlanningHorizon().getHor(),
groundedPlan.get(),
Map.of(),
Map.of());
} else {
logger.debug(
"Tried mocking simulation results with a grounded plan but could not because of the activity cannot be grounded.");
}
}
try {
if(checkSimBeforeEvaluatingGoal || lastSimulationResults == null)
lastSimulationResults = simulationFacade
.simulateWithResults(plan, time.plus(Duration.of(1, Duration.MICROSECONDS)), resourceNames)
var resources = new HashSet<String>(resourceNames);
var resourcesAreMissing = false;
if(cachedSimulationResultsBeforeGoalEvaluation != null){
final var allResources = new HashSet<>(cachedSimulationResultsBeforeGoalEvaluation.realProfiles.keySet());
allResources.addAll(cachedSimulationResultsBeforeGoalEvaluation.discreteProfiles.keySet());
resourcesAreMissing = !allResources.containsAll(resourceNames);
}
//if at least one goal needs the simulateAfter, we can't compute partial resources to avoid future recomputations
if(atLeastOneSimulateAfter){
resources.clear();
resources.addAll(this.problem.getMissionModel().getResources().keySet());
}
if(checkSimBeforeEvaluatingGoal || cachedSimulationResultsBeforeGoalEvaluation == null || cachedSimulationResultsBeforeGoalEvaluation.bounds.end.shorterThan(time) || resourcesAreMissing)
cachedSimulationResultsBeforeGoalEvaluation = simulationFacade
.simulateWithResults(plan, time, resources)
.constraintsResults();
return lastSimulationResults;
return cachedSimulationResultsBeforeGoalEvaluation;
} catch (SimulationFacade.SimulationException e) {
throw new RuntimeException("Exception while running simulation before evaluating conflicts", e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package gov.nasa.jpl.aerie.scheduler;

import com.google.common.testing.NullPointerTester;
import gov.nasa.jpl.aerie.constraints.model.ActivityInstance;
import gov.nasa.jpl.aerie.constraints.time.Interval;
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
import gov.nasa.jpl.aerie.scheduler.model.ActivityType;
import gov.nasa.jpl.aerie.scheduler.model.PlanningHorizon;
import gov.nasa.jpl.aerie.scheduler.model.Problem;
import gov.nasa.jpl.aerie.scheduler.model.SchedulePlanGrounder;
import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityDirective;
import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityDirectiveId;
import org.junit.jupiter.api.Test;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class SchedulingGrounderTest {
private final static PlanningHorizon h = new PlanningHorizon(TimeUtility.fromDOY("2025-001T01:01:01.001"), TimeUtility.fromDOY("2025-005T01:01:01.001"));
private final static Duration t0 = h.getStartAerie();
private final static Duration d1min = Duration.of(1, Duration.MINUTE);
private final static Duration d1hr = Duration.of(1, Duration.HOUR);
private final static Duration t1hr = t0.plus(d1hr);
private final static Duration t2hr = t0.plus(d1hr.times(2));

@Test
public void test(){
final var at = new ActivityType("at");
final var id1 = new SchedulingActivityDirectiveId(1);
final var id2 = new SchedulingActivityDirectiveId(2);
final var id3 = new SchedulingActivityDirectiveId(3);
final var act1 = SchedulingActivityDirective.of(id1, at, t0, d1min, null, true);
final var act2 = SchedulingActivityDirective.of(id2, at, t1hr, d1min, act1.id(), false);
final var act3 = SchedulingActivityDirective.of(id3, at, t2hr, d1min, act2.id(), false);
final var acts = List.of(act1, act3, act2);
final var result = SchedulePlanGrounder.groundSchedule(acts, h.getEndAerie());
//act 2 should be [t1hr+d1min, t1hr + d1min + d1min]
//act 3 should be [t1hr + d1min + d1min, t1hr + d1min + d1min + d1min]
final var act1expt = new ActivityInstance(id1.id(), at.getName(), Map.of(), Interval.between(t0, t0.plus(d1min)));
final var act2expt = new ActivityInstance(id2.id(), at.getName(), Map.of(), Interval.between(t1hr.plus(t0).plus(d1min), t1hr.plus(t0).plus(d1min).plus(d1min)));
final var act3expt = new ActivityInstance(id3.id(), at.getName(), Map.of(), Interval.between(t2hr.plus(t1hr).plus(t0).plus(d1min).plus(d1min), t2hr.plus(t1hr).plus(t0).plus(d1min).plus(d1min).plus(d1min)));
final var asSet = new HashSet<>(result.get());
assertTrue(asSet.contains(act1expt));
assertTrue(asSet.contains(act2expt));
assertTrue(asSet.contains(act3expt));
}

@Test
public void testEmptyDueToEmptyDuration(){
final var at = new ActivityType("at");
final var id1 = new SchedulingActivityDirectiveId(1);
final var id2 = new SchedulingActivityDirectiveId(2);
final var id3 = new SchedulingActivityDirectiveId(3);
final var act1 = SchedulingActivityDirective.of(id1, at, t0, d1min, null, true);
final var act2 = SchedulingActivityDirective.of(id2, at, t1hr, null, act1.id(), false);
final var act3 = SchedulingActivityDirective.of(id3, at, t2hr, d1min, act2.id(), false);
final var acts = List.of(act1, act3, act2);
final var result = SchedulePlanGrounder.groundSchedule(acts, h.getEndAerie());
assertEquals(Optional.empty(), result);
}
}

0 comments on commit 6957ada

Please sign in to comment.