From 8ff37f331afb8b84d3695a71aa67550ac7e44ba9 Mon Sep 17 00:00:00 2001 From: maillard Date: Tue, 22 Aug 2023 16:48:57 -0700 Subject: [PATCH] Use external dataset in scheduler --- .../tree/SpansWrapperExpression.java | 29 +++++++ .../scheduler/goals/CardinalityGoal.java | 8 +- .../scheduler/goals/CoexistenceGoal.java | 42 ++++++---- .../nasa/jpl/aerie/scheduler/goals/Goal.java | 4 +- .../jpl/aerie/scheduler/goals/OptionGoal.java | 5 +- .../goals/ProceduralCreationGoal.java | 9 ++- .../aerie/scheduler/goals/RecurrenceGoal.java | 13 +-- .../jpl/aerie/scheduler/model/Problem.java | 22 +++++ .../scheduler/solver/PrioritySolver.java | 6 +- .../jpl/aerie/scheduler/TestApplyWhen.java | 80 +++++++++++++++++++ 10 files changed, 183 insertions(+), 35 deletions(-) create mode 100644 constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/SpansWrapperExpression.java diff --git a/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/SpansWrapperExpression.java b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/SpansWrapperExpression.java new file mode 100644 index 0000000000..8d4d7c7977 --- /dev/null +++ b/constraints/src/main/java/gov/nasa/jpl/aerie/constraints/tree/SpansWrapperExpression.java @@ -0,0 +1,29 @@ +package gov.nasa.jpl.aerie.constraints.tree; +import gov.nasa.jpl.aerie.constraints.model.EvaluationEnvironment; +import gov.nasa.jpl.aerie.constraints.model.SimulationResults; +import gov.nasa.jpl.aerie.constraints.time.Interval; +import gov.nasa.jpl.aerie.constraints.time.Spans; + +import java.util.Set; + +public record SpansWrapperExpression(Spans spans) implements Expression { + @Override + public Spans evaluate( + final SimulationResults results, + final Interval bounds, + final EvaluationEnvironment environment) + { + return spans; + } + + @Override + public String prettyPrint(final String prefix) { + return String.format( + "\n%s(spans-wrapper-of %s)", + prefix, + this.spans + ); } + + @Override + public void extractResources(final Set names) { } +} 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 5209da8a0d..d166d0c2b8 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 @@ -121,10 +121,10 @@ protected CardinalityGoal fill(CardinalityGoal goal) { * should probably be created!) */ @Override - public Collection getConflicts(Plan plan, final SimulationResults simulationResults) { + public Collection getConflicts(Plan plan, final SimulationResults simulationResults, final EvaluationEnvironment evaluationEnvironment) { //unwrap temporalContext - final var windows = getTemporalContext().evaluate(simulationResults); + final var windows = getTemporalContext().evaluate(simulationResults, evaluationEnvironment); //make sure it hasn't changed if (this.initiallyEvaluatedTemporalContext != null && !windows.equals(this.initiallyEvaluatedTemporalContext)) { @@ -142,7 +142,7 @@ else if (this.initiallyEvaluatedTemporalContext == null) { final var actTB = new ActivityExpression.Builder().basedOn(this.matchActTemplate).startsOrEndsIn(subIntervalWindows).build(); - final var acts = new LinkedList<>(plan.find(actTB, simulationResults, new EvaluationEnvironment())); + final var acts = new LinkedList<>(plan.find(actTB, simulationResults, evaluationEnvironment)); acts.sort(Comparator.comparing(SchedulingActivityDirective::startOffset)); int nbActs = 0; @@ -201,7 +201,7 @@ else if (this.initiallyEvaluatedTemporalContext == null) { this, subIntervalWindows, this.desiredActTemplate, - new EvaluationEnvironment(), + evaluationEnvironment, nbToSchedule, durToSchedule.isPositive() ? Optional.of(durToSchedule) : Optional.empty())); } 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 ac7b83db11..d3b276ec44 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,7 +19,7 @@ import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityDirective; import java.util.ArrayList; -import java.util.Map; +import java.util.HashMap; import java.util.Optional; /** @@ -172,7 +172,8 @@ protected CoexistenceGoal fill(CoexistenceGoal goal) { * should probably be created!) */ @SuppressWarnings({"unchecked", "rawtypes"}) - public java.util.Collection getConflicts(Plan plan, final SimulationResults simulationResults) { //TODO: check if interval gets split and if so, notify user? + @Override + public java.util.Collection getConflicts(Plan plan, final SimulationResults simulationResults, final EvaluationEnvironment evaluationEnvironment) { //TODO: check if interval gets split and if so, notify user? //NOTE: temporalContext IS A WINDOWS OVER WHICH THE GOAL APPLIES, USUALLY SOMETHING BROAD LIKE A MISSION PHASE //NOTE: expr IS A WINDOWS OVER WHICH A COEXISTENCEGOAL APPLIES, FOR EXAMPLE THE WINDOWS CORRESPONDING TO 5 SECONDS AFTER EVERY BASICACTIVITY IS SCHEDULED @@ -182,7 +183,7 @@ public java.util.Collection getConflicts(Plan plan, final SimulationRe // AN ACTIVITYEXPRESSION AND THEN ANALYZEWHEN WAS A MISSION PHASE, ALTHOUGH IT IS POSSIBLE TO JUST SPECIFY AN EXPRESSION THAT COMBINES THOSE. //unwrap temporalContext - final var windows = getTemporalContext().evaluate(simulationResults); + final var windows = getTemporalContext().evaluate(simulationResults, evaluationEnvironment); //make sure it hasn't changed if (this.initiallyEvaluatedTemporalContext != null && !windows.includes(this.initiallyEvaluatedTemporalContext)) { @@ -192,7 +193,7 @@ else if (this.initiallyEvaluatedTemporalContext == null) { this.initiallyEvaluatedTemporalContext = windows; } - final var anchors = expr.evaluate(simulationResults).intersectWith(windows); + final var anchors = expr.evaluate(simulationResults, evaluationEnvironment).intersectWith(windows); //make sure expr hasn't changed either as that could yield unexpected behavior if (this.evaluatedExpr != null && !anchors.isCollectionSubsetOf(this.evaluatedExpr)) { @@ -242,7 +243,10 @@ else if (this.initiallyEvaluatedTemporalContext == null) { activityCreationTemplate.durationIn(durRange); } - final var existingActs = plan.find(activityFinder.build(), simulationResults, createEvaluationEnvironmentFromAnchor(window)); + final var existingActs = plan.find( + activityFinder.build(), + simulationResults, + createEvaluationEnvironmentFromAnchor(evaluationEnvironment, window)); var missingActAssociations = new ArrayList(); var planEvaluation = plan.getEvaluation(); @@ -272,7 +276,7 @@ else if (this.initiallyEvaluatedTemporalContext == null) { if (!alreadyOneActivityAssociated) { //create conflict if no matching target activity found if (existingActs.isEmpty()) { - conflicts.add(new MissingActivityTemplateConflict(this, this.temporalContext.evaluate(simulationResults), temp, createEvaluationEnvironmentFromAnchor(window), 1, Optional.empty())); + conflicts.add(new MissingActivityTemplateConflict(this, this.temporalContext.evaluate(simulationResults, evaluationEnvironment), temp, createEvaluationEnvironmentFromAnchor(evaluationEnvironment, window), 1, Optional.empty())); } else { conflicts.add(new MissingAssociationConflict(this, missingActAssociations)); } @@ -283,24 +287,28 @@ else if (this.initiallyEvaluatedTemporalContext == null) { return conflicts; } - private EvaluationEnvironment createEvaluationEnvironmentFromAnchor(Segment> span){ + private EvaluationEnvironment createEvaluationEnvironmentFromAnchor(EvaluationEnvironment existingEnvironment, Segment> span){ if(span.value().isPresent()){ final var metadata = span.value().get(); + final var activityInstances = new HashMap<>(existingEnvironment.activityInstances()); + activityInstances.put(this.alias, metadata.activityInstance()); return new EvaluationEnvironment( - Map.of(this.alias, metadata.activityInstance()), - Map.of(), - Map.of(), - Map.of(), - Map.of() + activityInstances, + existingEnvironment.spansInstances(), + existingEnvironment.intervals(), + existingEnvironment.realExternalProfiles(), + existingEnvironment.discreteExternalProfiles() ); } else{ assert this.alias != null; + final var intervals = new HashMap<>(existingEnvironment.intervals()); + intervals.put(this.alias, span.interval()); return new EvaluationEnvironment( - Map.of(), - Map.of(), - Map.of(this.alias, span.interval()), - Map.of(), - Map.of() + existingEnvironment.activityInstances(), + existingEnvironment.spansInstances(), + intervals, + existingEnvironment.realExternalProfiles(), + existingEnvironment.discreteExternalProfiles() ); } } 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 8e8514b699..621d437832 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 @@ -1,5 +1,6 @@ package gov.nasa.jpl.aerie.scheduler.goals; +import gov.nasa.jpl.aerie.constraints.model.EvaluationEnvironment; import gov.nasa.jpl.aerie.constraints.model.SimulationResults; import gov.nasa.jpl.aerie.constraints.time.Interval; import gov.nasa.jpl.aerie.constraints.time.Windows; @@ -283,7 +284,8 @@ public String getName() { * @param simulationResults * @return a list of issues in the plan that diminish goal satisfaction */ - public java.util.Collection getConflicts(Plan plan, final SimulationResults simulationResults) { + public java.util.Collection getConflicts(Plan plan, final SimulationResults simulationResults, final + EvaluationEnvironment evaluationEnvironment) { return java.util.Collections.emptyList(); } 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 7afad635e2..948ae518db 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 @@ -1,5 +1,6 @@ package gov.nasa.jpl.aerie.scheduler.goals; +import gov.nasa.jpl.aerie.constraints.model.EvaluationEnvironment; import gov.nasa.jpl.aerie.constraints.model.SimulationResults; import gov.nasa.jpl.aerie.scheduler.conflicts.Conflict; import gov.nasa.jpl.aerie.scheduler.model.Plan; @@ -28,7 +29,9 @@ public Optimizer getOptimizer(){ } @Override - public java.util.Collection getConflicts(Plan plan, final SimulationResults simulationResults) { + public java.util.Collection getConflicts(Plan plan, + final SimulationResults simulationResults, + final EvaluationEnvironment evaluationEnvironment) { 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 bf6b3ee6e2..d5a88765b9 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 @@ -108,12 +108,13 @@ protected ProceduralCreationGoal fill(ProceduralCreationGoal goal) { * the plan (and should probably be created). The matching is strict: all * arguments must be identical. */ - public Collection getConflicts(Plan plan, final SimulationResults simulationResults) { + @Override + public Collection getConflicts(Plan plan, final SimulationResults simulationResults, final EvaluationEnvironment evaluationEnvironment) { final var conflicts = new java.util.LinkedList(); //run the generator to see what acts are still desired //REVIEW: maybe some caching on plan hash here? - final var requestedActs = getRelevantGeneratedActivities(plan, simulationResults); + final var requestedActs = getRelevantGeneratedActivities(plan, simulationResults, evaluationEnvironment); //walk each requested act and try to find an exact match in the plan for (final var requestedAct : requestedActs) { @@ -202,13 +203,13 @@ protected ProceduralCreationGoal() { } * are deemed relevant to this goal (eg within the temporal context * of this goal) */ - private Collection getRelevantGeneratedActivities(Plan plan, SimulationResults simulationResults) { + private Collection getRelevantGeneratedActivities(Plan plan, SimulationResults simulationResults, EvaluationEnvironment evaluationEnvironment) { //run the generator in the plan context final var allActs = generator.apply(plan); //filter out acts that don't have a start time within the goal purview - final var evaluatedGoalContext = getTemporalContext().evaluate(simulationResults); + final var evaluatedGoalContext = getTemporalContext().evaluate(simulationResults, evaluationEnvironment); final var filteredActs = allActs.stream().filter( act -> ((act.startOffset() != null) && evaluatedGoalContext.includes(Interval.at(0, act.startOffset()))) 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 abb958e08c..7f15465378 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 @@ -104,12 +104,13 @@ else if (every.max.isNegative()) { * exist over a timespan longer than the allowed range (and one should * probably be created!) */ - public java.util.Collection getConflicts(@NotNull Plan plan, final SimulationResults simulationResults) { + @Override + public java.util.Collection getConflicts(@NotNull Plan plan, final SimulationResults simulationResults, final EvaluationEnvironment evaluationEnvironment) { final var conflicts = new java.util.LinkedList(); //unwrap temporalContext final var tempWindowPlanHorizon = new Windows(false).set(List.of(this.planHorizon.getHor()), true); - final var windows = tempWindowPlanHorizon.and(this.getTemporalContext().evaluate(simulationResults)); + final var windows = tempWindowPlanHorizon.and(this.getTemporalContext().evaluate(simulationResults, evaluationEnvironment)); //check repeat is larger than activity duration if(this.getActTemplate().getType().getDurationType() instanceof DurationType.Fixed act){ @@ -153,7 +154,7 @@ else if (this.initiallyEvaluatedTemporalContext == null) { final var strideDur = actStartT.minus(prevStartT); if (strideDur.compareTo(this.recurrenceInterval.max) > 0) { //fill conflicts for all the missing activities in that long span - conflicts.addAll(makeRecurrenceConflicts(prevStartT, actStartT)); + conflicts.addAll(makeRecurrenceConflicts(prevStartT, actStartT, evaluationEnvironment)); } else { /*TODO: right now, we associate with all the activities that are satisfying but we should aim for the minimum @@ -171,7 +172,7 @@ else if (this.initiallyEvaluatedTemporalContext == null) { //fill in conflicts for all missing activities in the last span up to the //goal's own end time (also handles case of no matching acts at all) - conflicts.addAll(makeRecurrenceConflicts(prevStartT, lastStartT)); + conflicts.addAll(makeRecurrenceConflicts(prevStartT, lastStartT, evaluationEnvironment)); } return conflicts; @@ -202,7 +203,7 @@ protected RecurrenceGoal() { } * @param start IN the start time of the span to fill with conflicts (inclusive) * @param end IN the end time of the span to fill with conflicts (exclusive) */ - private java.util.Collection makeRecurrenceConflicts(Duration start, Duration end) + private java.util.Collection makeRecurrenceConflicts(Duration start, Duration end, final EvaluationEnvironment evaluationEnvironment) { final var conflicts = new java.util.LinkedList(); @@ -212,7 +213,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(), new EvaluationEnvironment(), 1, Optional.empty())); + conflicts.add(new MissingActivityTemplateConflict(this, windows, this.getActTemplate(), evaluationEnvironment, 1, Optional.empty())); } else{ System.out.println(); 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 624160e6ec..c2feb377e0 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 @@ -1,5 +1,7 @@ package gov.nasa.jpl.aerie.scheduler.model; +import gov.nasa.jpl.aerie.constraints.model.DiscreteProfile; +import gov.nasa.jpl.aerie.constraints.model.LinearProfile; 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; @@ -12,7 +14,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; /** @@ -40,6 +44,10 @@ public class Problem { */ private final List globalConstraints = new java.util.LinkedList<>(); + + private final Map realExternalProfiles = new HashMap<>(); + private final Map discreteExternalProfiles = new HashMap<>(); + /** * the initial seed plan to start scheduling from */ @@ -148,6 +156,20 @@ public void setInitialPlan(final Plan plan) { public Optional getInitialSimulationResults(){ return initialSimulationResults; } + public void setExternalProfile(final Map realExternalProfiles, + final Map discreteExternalProfiles){ + this.realExternalProfiles.putAll(realExternalProfiles); + this.discreteExternalProfiles.putAll(discreteExternalProfiles); + } + + public Map getRealExternalProfiles(){ + return this.realExternalProfiles; + } + + public Map getDiscreteExternalProfiles(){ + return this.discreteExternalProfiles; + } + public void setGoals(List goals){ goalsOrderedByPriority.clear(); goalsOrderedByPriority.addAll(goals); 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 4daad48a52..e775a5ae4b 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 @@ -613,7 +613,8 @@ else if(!analysisOnly && (missing instanceof MissingActivityTemplateConflict mi assert plan != null; //REVIEW: maybe should have way to request only certain kinds of conflicts final var lastSimulationResults = this.getLatestSimResultsUpTo(this.problem.getPlanningHorizon().getEndAerie()); - final var rawConflicts = goal.getConflicts(plan, lastSimulationResults); + final var evaluationEnvironment = new EvaluationEnvironment(this.problem.getRealExternalProfiles(), this.problem.getDiscreteExternalProfiles()); + final var rawConflicts = goal.getConflicts(plan, lastSimulationResults, evaluationEnvironment); assert rawConflicts != null; return rawConflicts; } @@ -765,9 +766,10 @@ private Windows narrowByResourceConstraints(Windows windows, final var latestSimulationResults = this.getLatestSimResultsUpTo(totalDomain.end); //iteratively narrow the windows from each constraint //REVIEW: could be some optimization in constraint ordering (smallest domain first to fail fast) + final var evaluationEnvironment = new EvaluationEnvironment(this.problem.getRealExternalProfiles(), this.problem.getDiscreteExternalProfiles()); for (final var constraint : constraints) { //REVIEW: loop through windows more efficient than enveloppe(windows) ? - final var validity = constraint.evaluate(latestSimulationResults, totalDomain); + final var validity = constraint.evaluate(latestSimulationResults, totalDomain, evaluationEnvironment); ret = ret.and(validity); //short-circuit if no possible windows left if (ret.stream().noneMatch(Segment::value)) { diff --git a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/TestApplyWhen.java b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/TestApplyWhen.java index 00c3e0dbce..d7663c861c 100644 --- a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/TestApplyWhen.java +++ b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/TestApplyWhen.java @@ -1,13 +1,29 @@ package gov.nasa.jpl.aerie.scheduler; +import gov.nasa.jpl.aerie.constraints.model.DiscreteProfile; +import gov.nasa.jpl.aerie.constraints.model.EvaluationEnvironment; +import gov.nasa.jpl.aerie.constraints.model.LinearEquation; +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; +import gov.nasa.jpl.aerie.constraints.time.Spans; import gov.nasa.jpl.aerie.constraints.time.Windows; import gov.nasa.jpl.aerie.constraints.tree.And; +import gov.nasa.jpl.aerie.constraints.tree.AssignGaps; +import gov.nasa.jpl.aerie.constraints.tree.DiscreteResource; +import gov.nasa.jpl.aerie.constraints.tree.DiscreteValue; +import gov.nasa.jpl.aerie.constraints.tree.Equal; import gov.nasa.jpl.aerie.constraints.tree.Expression; import gov.nasa.jpl.aerie.constraints.tree.GreaterThanOrEqual; +import gov.nasa.jpl.aerie.constraints.tree.ProfileExpression; import gov.nasa.jpl.aerie.constraints.tree.RealResource; import gov.nasa.jpl.aerie.constraints.tree.RealValue; +import gov.nasa.jpl.aerie.constraints.tree.SpansFromWindows; +import gov.nasa.jpl.aerie.constraints.tree.SpansWrapperExpression; +import gov.nasa.jpl.aerie.constraints.tree.ValueAt; import gov.nasa.jpl.aerie.constraints.tree.WindowsWrapperExpression; +import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; import gov.nasa.jpl.aerie.scheduler.constraints.TimeRangeExpression; import gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityCreationTemplate; import gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityExpression; @@ -30,6 +46,8 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -1153,6 +1171,68 @@ public void testCoexistenceUncontrollableJustFits() { assertEquals(3, problem.getSimulationFacade().countSimulationRestarts()); } + + + @Test + public void testCoexistenceExternalResource() { + Interval period = Interval.betweenClosedOpen(Duration.of(0, Duration.SECONDS), Duration.of(25, Duration.SECONDS)); + + final var fooMissionModel = SimulationUtility.getFooMissionModel(); + final var planningHorizon = new PlanningHorizon(TestUtility.timeFromEpochSeconds(0), TestUtility.timeFromEpochSeconds(25)); + Problem problem = new Problem(fooMissionModel, planningHorizon, new SimulationFacade( + planningHorizon, + fooMissionModel), SimulationUtility.getFooSchedulerModel()); + + final var activityDurationInMicroseconds = 6; + final var r1 = new LinearProfile(new Segment<>(Interval.between(Duration.ZERO, Duration.SECONDS.times(5)), new LinearEquation(Duration.ZERO, 5, 1))); + final var r2 = new DiscreteProfile(new Segment<>(Interval.FOREVER, SerializedValue.of(5))); + final var r3 = new DiscreteProfile(new Segment<>(Interval.FOREVER, SerializedValue.of(activityDurationInMicroseconds))); + final var externalRealProfiles = Map.of("/real/R1", r1); + final var externalDiscreteProfiles = Map.of( + "/discrete/R2", r2, + "/discrete/R3", r3 + ); + problem.setExternalProfile( + externalRealProfiles, + externalDiscreteProfiles + ); + + final var profEx = new ProfileExpression<>(new ValueAt<>( + new ProfileExpression<>(new DiscreteResource("/discrete/R3")), + new SpansWrapperExpression(new Spans(Interval.at(Duration.of(0, Duration.MICROSECONDS)))))); + final var cond = new And( + new GreaterThanOrEqual(new RealResource("/real/R1"), new RealValue(6)), + new Equal<>(new DiscreteResource("/discrete/R2"), new DiscreteValue(SerializedValue.of(5)))); + final var actTypeB = problem.getActivityType("ControllableDurationActivity"); + CoexistenceGoal goal = new CoexistenceGoal.Builder() + .forAllTimeIn(new WindowsWrapperExpression(new Windows(period, true).assignGaps(new Windows(Interval.FOREVER, false)))) + .forEach(new SpansFromWindows(new AssignGaps<>(cond, new WindowsWrapperExpression(new Windows(Interval.FOREVER, false))))) + .thereExistsOne(new ActivityCreationTemplate.Builder() + .ofType(actTypeB) + .withArgument("duration", profEx) + .build()) + .startsAt(TimeAnchor.START) + .withinPlanHorizon(planningHorizon) + .aliasForAnchors("a") + .build(); + + problem.setGoals(List.of(goal)); + + final var solver = new PrioritySolver(problem); + var plan = solver.getNextSolution(); + for(SchedulingActivityDirective a : plan.get().getActivitiesByTime()){ + logger.debug(a.startOffset().toString() + ", " + a.duration().toString()); + } + final var emptySimulationResults = new SimulationResults(null, null, List.of(), Map.of(), Map.of()); + final var startOfActivity = cond.evaluate(emptySimulationResults, Interval.FOREVER, new EvaluationEnvironment(externalRealProfiles, externalDiscreteProfiles)).iterateEqualTo(true).iterator().next().start; + assertEquals(1, plan.get().getActivitiesByTime().size()); + final var act = plan.get().getActivitiesByTime().get(0); + assertEquals(act.duration(), Duration.of(activityDurationInMicroseconds, Duration.MICROSECONDS)); + assertEquals(startOfActivity, Duration.of(1, Duration.SECONDS)); + assertEquals(act.startOffset(), startOfActivity); + assertEquals(2, problem.getSimulationFacade().countSimulationRestarts()); + } + @Test public void changingForAllTimeIn() {