diff --git a/deployment/hasura/migrations/Aerie/12_activity_goal_invocation_id/down.sql b/deployment/hasura/migrations/Aerie/12_activity_goal_invocation_id/down.sql new file mode 100644 index 0000000000..b3f97f5c99 --- /dev/null +++ b/deployment/hasura/migrations/Aerie/12_activity_goal_invocation_id/down.sql @@ -0,0 +1,6 @@ +alter table merlin.activity_directive + drop constraint activity_directive_source_invocation_id_exists, + + drop column source_scheduling_goal_invocation_id; + +call migrations.mark_migration_rolled_back('12'); diff --git a/deployment/hasura/migrations/Aerie/12_activity_goal_invocation_id/up.sql b/deployment/hasura/migrations/Aerie/12_activity_goal_invocation_id/up.sql new file mode 100644 index 0000000000..9739953df7 --- /dev/null +++ b/deployment/hasura/migrations/Aerie/12_activity_goal_invocation_id/up.sql @@ -0,0 +1,13 @@ +alter table merlin.activity_directive + add column source_scheduling_goal_invocation_id integer default null, + + add constraint activity_directive_source_invocation_id_exists + foreign key (source_scheduling_goal_invocation_id) + references scheduler.scheduling_specification_goals + on update cascade + on delete set null; + +comment on column merlin.activity_directive.source_scheduling_goal_invocation_id is e'' + 'The scheduling goal invocation that this activity_directive was generated by.'; + +call migrations.mark_migration_applied('12'); diff --git a/deployment/postgres-init-db/sql/tables/merlin/activity_directive/activity_directive.sql b/deployment/postgres-init-db/sql/tables/merlin/activity_directive/activity_directive.sql index 9509621387..172b658fdd 100644 --- a/deployment/postgres-init-db/sql/tables/merlin/activity_directive/activity_directive.sql +++ b/deployment/postgres-init-db/sql/tables/merlin/activity_directive/activity_directive.sql @@ -4,6 +4,7 @@ create table merlin.activity_directive ( name text, source_scheduling_goal_id integer, + source_scheduling_goal_invocation_id integer default null, created_at timestamptz not null default now(), created_by text, last_modified_at timestamptz not null default now(), @@ -38,6 +39,11 @@ create table merlin.activity_directive ( foreign key (created_by) references permissions.users on update cascade + on delete set null, + constraint activity_directive_source_invocation_id_exists + foreign key (source_scheduling_goal_invocation_id) + references scheduler.scheduling_specification_goals + on update cascade on delete set null ); @@ -56,6 +62,8 @@ comment on column merlin.activity_directive.name is e'' 'The name of this activity_directive.'; comment on column merlin.activity_directive.source_scheduling_goal_id is e'' 'The scheduling goal that this activity_directive was generated by.'; +comment on column merlin.activity_directive.source_scheduling_goal_invocation_id is e'' + 'The scheduling goal invocation that this activity_directive was generated by.'; comment on column merlin.activity_directive.created_at is e'' 'The time at which this activity_directive was created.'; comment on column merlin.activity_directive.created_by is e'' diff --git a/e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ActivityAutoDeletionGoal.java b/e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ActivityAutoDeletionGoal.java new file mode 100644 index 0000000000..d8f736d8e2 --- /dev/null +++ b/e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ActivityAutoDeletionGoal.java @@ -0,0 +1,43 @@ +package gov.nasa.jpl.aerie.e2e.procedural.scheduling.procedures; + +import gov.nasa.ammos.aerie.procedural.scheduling.ActivityAutoDelete; +import gov.nasa.ammos.aerie.procedural.scheduling.Goal; +import gov.nasa.ammos.aerie.procedural.scheduling.annotations.SchedulingProcedure; +import gov.nasa.ammos.aerie.procedural.scheduling.plan.DeletedAnchorStrategy; +import gov.nasa.ammos.aerie.procedural.scheduling.plan.EditablePlan; +import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart; +import gov.nasa.ammos.aerie.procedural.timeline.plan.Plan; +import gov.nasa.ammos.aerie.procedural.timeline.plan.SimulationResults; +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; +import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +/** + * Creates one activity, and deletes it automatically on subsequent runs. + */ +@SchedulingProcedure +public record ActivityAutoDeletionGoal(boolean deleteAtBeginning) implements Goal { + @NotNull + @Override + public ActivityAutoDelete shouldDeletePastCreations( + @NotNull final Plan plan, + @Nullable final SimulationResults simResults) + { + if (deleteAtBeginning) return new ActivityAutoDelete.AtBeginning(DeletedAnchorStrategy.Error, false); + else return new ActivityAutoDelete.JustBefore(DeletedAnchorStrategy.Error); + } + + @Override + public void run(@NotNull final EditablePlan plan) { + plan.create( + "BiteBanana", + new DirectiveStart.Absolute(Duration.MINUTE), + Map.of("biteSize", SerializedValue.of(1)) + ); + + plan.commit(); + } +} diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/AutoDeletionTests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/AutoDeletionTests.java new file mode 100644 index 0000000000..e3e7fbb3a6 --- /dev/null +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/AutoDeletionTests.java @@ -0,0 +1,135 @@ +package gov.nasa.jpl.aerie.e2e.procedural.scheduling; + +import gov.nasa.jpl.aerie.e2e.types.GoalInvocationId; +import gov.nasa.jpl.aerie.e2e.utils.GatewayRequests; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.json.Json; +import java.io.IOException; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AutoDeletionTests extends ProceduralSchedulingSetup { + private GoalInvocationId edslId; + private GoalInvocationId procedureId; + + @BeforeEach + void localBeforeEach() throws IOException { + try (final var gateway = new GatewayRequests(playwright)) { + final String coexGoalDefinition = + """ + export default function myGoal() { + return Goal.CoexistenceGoal({ + forEach: ActivityExpression.ofType(ActivityTypes.BiteBanana), + activityTemplate: ActivityTemplates.GrowBanana({quantity: 1, growingDuration: Temporal.Duration.from({minutes:1})}), + startsAt:TimingConstraint.singleton(WindowProperty.START).plus(Temporal.Duration.from({ minutes : 5})) + }) + }"""; + + edslId = hasura.createSchedulingSpecGoal( + "Coexistence Scheduling Test Goal", + coexGoalDefinition, + "", + specId, + 0, + false + ); + + int procedureJarId = gateway.uploadJarFile("build/libs/ActivityAutoDeletionGoal.jar"); + // Add Scheduling Procedure + procedureId = hasura.createSchedulingSpecProcedure( + "Test Scheduling Procedure", + procedureJarId, + specId, + 1, + false + ); + } + } + + @AfterEach + void localAfterEach() throws IOException { + hasura.deleteSchedulingGoal(procedureId.goalId()); + hasura.deleteSchedulingGoal(edslId.goalId()); + } + + @Test + void createsOneActivityIfRunOnce() throws IOException { + final var args = Json + .createObjectBuilder() + .add("deleteAtBeginning", false) + .build(); + + hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args); + + hasura.awaitScheduling(specId); + + final var plan = hasura.getPlan(planId); + final var activities = plan.activityDirectives(); + + assertEquals(1, activities.size()); + + assertTrue(activities.stream().anyMatch( + it -> Objects.equals(it.type(), "BiteBanana") + )); + } + + @Test + void createsTwoActivitiesSteadyState_JustBefore() throws IOException { + final var args = Json + .createObjectBuilder() + .add("deleteAtBeginning", false) + .build(); + + hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args); + + hasura.awaitScheduling(specId); + + for (int i = 0; i < 3; i++) { + hasura.awaitScheduling(specId); + + final var plan = hasura.getPlan(planId); + final var activities = plan.activityDirectives(); + + assertEquals(2, activities.size()); + + assertTrue(activities.stream().anyMatch( + it -> Objects.equals(it.type(), "BiteBanana") + )); + + assertTrue(activities.stream().anyMatch( + it -> Objects.equals(it.type(), "GrowBanana") + )); + } + } + + @Test + void createsOneActivitySteadyState_AtBeginning() throws IOException { + final var args = Json + .createObjectBuilder() + .add("deleteAtBeginning", true) + .build(); + + hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args); + + hasura.awaitScheduling(specId); + + for (int i = 0; i < 3; i++) { + hasura.awaitScheduling(specId); + + final var plan = hasura.getPlan(planId); + final var activities = plan.activityDirectives(); + + assertEquals(1, activities.size()); + + assertTrue(activities.stream().anyMatch( + it -> Objects.equals(it.type(), "BiteBanana") + )); + } + } +} diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/HasuraRequests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/HasuraRequests.java index 71b5dff661..e11241b0ec 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/HasuraRequests.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/HasuraRequests.java @@ -710,12 +710,22 @@ public void updatePlanRevisionSchedulingSpec(int planId) throws IOException { makeRequest(GQL.UPDATE_SCHEDULING_SPECIFICATION_PLAN_REVISION, variables); } - public GoalInvocationId createSchedulingSpecProcedure( String name, int jarId, int specificationId, int priority + ) throws IOException { + return createSchedulingSpecProcedure(name, jarId, specificationId, priority, true); + } + + + public GoalInvocationId createSchedulingSpecProcedure( + String name, + int jarId, + int specificationId, + int priority, + boolean simulateAfter ) throws IOException { final var specGoalBuilder = Json.createObjectBuilder() .add("goal_metadata", @@ -733,7 +743,8 @@ public GoalInvocationId createSchedulingSpecProcedure( .add("uploaded_jar_id", jarId) ))))) .add("specification_id", specificationId) - .add("priority", priority); + .add("priority", priority) + .add("simulate_after", simulateAfter); final var variables = Json.createObjectBuilder().add("spec_goal", specGoalBuilder).build(); final var resp = makeRequest(GQL.CREATE_SCHEDULING_SPEC_GOAL, variables) .getJsonObject("insert_scheduling_specification_goals_one"); @@ -768,6 +779,18 @@ public GoalInvocationId createSchedulingSpecGoal( String description, int specificationId, int priority + ) throws IOException + { + return createSchedulingSpecGoal(name, definition, description, specificationId, priority, true); + } + + public GoalInvocationId createSchedulingSpecGoal( + String name, + String definition, + String description, + int specificationId, + int priority, + boolean simulateAfter ) throws IOException { final var specGoalBuilder = Json.createObjectBuilder() .add("goal_metadata", @@ -783,6 +806,7 @@ public GoalInvocationId createSchedulingSpecGoal( .add(Json.createObjectBuilder() .add("definition", definition)))))) .add("specification_id", specificationId) + .add("simulate_after", simulateAfter) .add("priority", priority); final var variables = Json.createObjectBuilder().add("spec_goal", specGoalBuilder).build(); final var resp = makeRequest(GQL.CREATE_SCHEDULING_SPEC_GOAL, variables) diff --git a/procedural/scheduling/src/main/kotlin/gov/nasa/ammos/aerie/procedural/scheduling/ActivityAutoDelete.kt b/procedural/scheduling/src/main/kotlin/gov/nasa/ammos/aerie/procedural/scheduling/ActivityAutoDelete.kt new file mode 100644 index 0000000000..9ea4f79d9a --- /dev/null +++ b/procedural/scheduling/src/main/kotlin/gov/nasa/ammos/aerie/procedural/scheduling/ActivityAutoDelete.kt @@ -0,0 +1,9 @@ +package gov.nasa.ammos.aerie.procedural.scheduling + +import gov.nasa.ammos.aerie.procedural.scheduling.plan.DeletedAnchorStrategy + +sealed interface ActivityAutoDelete { + data class AtBeginning(val anchorStrategy: DeletedAnchorStrategy, val simulateAfter: Boolean): ActivityAutoDelete + data class JustBefore(val anchorStrategy: DeletedAnchorStrategy): ActivityAutoDelete + data object No: ActivityAutoDelete +} diff --git a/procedural/scheduling/src/main/kotlin/gov/nasa/ammos/aerie/procedural/scheduling/Goal.kt b/procedural/scheduling/src/main/kotlin/gov/nasa/ammos/aerie/procedural/scheduling/Goal.kt index f4014d2cf0..4d6a0c39f1 100644 --- a/procedural/scheduling/src/main/kotlin/gov/nasa/ammos/aerie/procedural/scheduling/Goal.kt +++ b/procedural/scheduling/src/main/kotlin/gov/nasa/ammos/aerie/procedural/scheduling/Goal.kt @@ -1,9 +1,23 @@ package gov.nasa.ammos.aerie.procedural.scheduling import gov.nasa.ammos.aerie.procedural.scheduling.plan.EditablePlan +import gov.nasa.ammos.aerie.procedural.timeline.plan.Plan +import gov.nasa.ammos.aerie.procedural.timeline.plan.SimulationResults /** The interface that all scheduling rules must satisfy. */ interface Goal { + /** + * Whether the scheduler should delete this goal's past created activities. + * + * Default implementation returns [ActivityAutoDelete.No]. Override this method + * to specify otherwise and choose a strategy for deleted anchors. + * + * This method may be called multiple times during the scheduling run, and must return the + * same result every time. All calls to this method and [run] during a scheduling run + * will be performed on the same object instance. + */ + fun shouldDeletePastCreations(plan: Plan, simResults: SimulationResults?): ActivityAutoDelete = ActivityAutoDelete.No + /** * Run the rule. * diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/Procedure.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/Procedure.java index 91e68cffe6..88293e1e9f 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/Procedure.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/Procedure.java @@ -1,5 +1,7 @@ package gov.nasa.jpl.aerie.scheduler.goals; +import gov.nasa.ammos.aerie.procedural.scheduling.ActivityAutoDelete; +import gov.nasa.ammos.aerie.procedural.scheduling.plan.DeletedAnchorStrategy; import gov.nasa.ammos.aerie.procedural.timeline.payloads.ExternalEvent; import gov.nasa.jpl.aerie.merlin.driver.MissionModel; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; @@ -8,6 +10,7 @@ import gov.nasa.jpl.aerie.scheduler.DirectiveIdGenerator; import gov.nasa.jpl.aerie.scheduler.ProcedureLoader; import gov.nasa.jpl.aerie.scheduler.model.ActivityType; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.scheduler.model.Plan; import gov.nasa.jpl.aerie.scheduler.model.PlanningHorizon; import gov.nasa.jpl.aerie.scheduler.model.Problem; @@ -17,11 +20,13 @@ import gov.nasa.jpl.aerie.scheduler.simulation.SimulationFacade; import gov.nasa.jpl.aerie.scheduler.solver.ConflictSatisfaction; import gov.nasa.jpl.aerie.scheduler.solver.Evaluation; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Function; import static gov.nasa.jpl.aerie.scheduler.plan.InMemoryEditablePlan.toSchedulingActivity; @@ -30,16 +35,37 @@ public class Procedure extends Goal { private final Path jarPath; private final Map args; - public Procedure(final PlanningHorizon planningHorizon, Path jarPath, Map args, boolean simulateAfter) { + private gov.nasa.ammos.aerie.procedural.scheduling.Goal goal; + + private ActivityAutoDelete shouldDelete; + private final GoalId goalId; + + public Procedure( + final PlanningHorizon planningHorizon, + Path jarPath, + Map args, + boolean simulateAfter, + GoalId goalId + ) { this.simulateAfter = simulateAfter; this.planHorizon = planningHorizon; this.jarPath = jarPath; this.args = args; + this.goalId = goalId; } - public void run( + public void prepare() { + final ProcedureMapper procedureMapper; + try { + procedureMapper = ProcedureLoader.loadProcedure(jarPath); + } catch (ProcedureLoader.ProcedureLoadException e) { + throw new RuntimeException(e); + } + this.goal = procedureMapper.deserialize(SerializedValue.of(this.args)); + } + + public boolean deleteAtBeginning( final Problem problem, - final Evaluation eval, final Plan plan, final MissionModel missionModel, final Function lookupActivityType, @@ -47,13 +73,43 @@ public void run( final DirectiveIdGenerator idGenerator, Map> eventsByDerivationGroup ) { - final ProcedureMapper procedureMapper; - try { - procedureMapper = ProcedureLoader.loadProcedure(jarPath); - } catch (ProcedureLoader.ProcedureLoadException e) { - throw new RuntimeException(e); + final var planAdapter = new SchedulerToProcedurePlanAdapter( + plan, + planHorizon, + eventsByDerivationGroup, + problem.getDiscreteExternalProfiles(), + problem.getRealExternalProfiles() + ); + + final var editablePlan = new InMemoryEditablePlan( + missionModel, + idGenerator, + planAdapter, + simulationFacade, + lookupActivityType::apply + ); + + final var simResults = editablePlan.latestResults(); + + this.shouldDelete = this.goal.shouldDeletePastCreations(editablePlan, simResults); + + if (shouldDelete instanceof ActivityAutoDelete.AtBeginning ab) { + deletePastCreations(editablePlan, ab.getAnchorStrategy(), problem.sourceSchedulingGoals); + return ab.getSimulateAfter(); } + return false; + } + public void run( + final Problem problem, + final Evaluation eval, + final Plan plan, + final MissionModel missionModel, + final Function lookupActivityType, + final SimulationFacade simulationFacade, + final DirectiveIdGenerator idGenerator, + Map> eventsByDerivationGroup + ) { List newActivities = new ArrayList<>(); final var planAdapter = new SchedulerToProcedurePlanAdapter( @@ -72,7 +128,11 @@ public void run( lookupActivityType::apply ); - procedureMapper.deserialize(SerializedValue.of(this.args)).run(editablePlan); + if (shouldDelete instanceof ActivityAutoDelete.JustBefore jb) { + deletePastCreations(editablePlan, jb.getAnchorStrategy(), problem.sourceSchedulingGoals); + } + + this.goal.run(editablePlan); if (editablePlan.isDirty()) { throw new IllegalStateException("procedural goal %s had changes that were not committed or rolled back".formatted(jarPath.getFileName())); @@ -89,4 +149,17 @@ public void run( } evaluation.setConflictSatisfaction(null, ConflictSatisfaction.SAT); } + + private void deletePastCreations( + final InMemoryEditablePlan plan, + final DeletedAnchorStrategy strategy, + final Map sourceSchedulingGoals + ) { + for (final var activity: plan.getAdapter().getActivities()) { + final var goalId = sourceSchedulingGoals.getOrDefault(activity.id(), null); + if (goalId != null && goalId.goalInvocationId().equals(this.goalId.goalInvocationId())) { + plan.delete(activity.id(), strategy); + } + } + } } diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GoalId.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/GoalId.java similarity index 86% rename from scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GoalId.java rename to scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/GoalId.java index ec4ab2b8a6..79d404e34a 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GoalId.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/GoalId.java @@ -1,4 +1,4 @@ -package gov.nasa.jpl.aerie.scheduler.server.models; +package gov.nasa.jpl.aerie.scheduler.model; import java.util.Optional; 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 ae70e94449..0cd07c93b0 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 @@ -11,6 +11,7 @@ import gov.nasa.jpl.aerie.scheduler.simulation.SimulationFacade; import gov.nasa.jpl.aerie.scheduler.simulation.SimulationData; import gov.nasa.jpl.aerie.scheduler.simulation.SimulationResultsConverter; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import java.util.ArrayList; import java.util.Collection; @@ -60,6 +61,8 @@ public class Problem { */ private Optional initialSimulationResults; + public final Map sourceSchedulingGoals; + /** * container of all goals in the problem, indexed by name */ @@ -80,7 +83,9 @@ public Problem( MissionModel mission, PlanningHorizon planningHorizon, SimulationFacade simulationFacade, - SchedulerModel schedulerModel) { + SchedulerModel schedulerModel, + Map sourceSchedulingGoals + ) { this.missionModel = mission; this.schedulerModel = schedulerModel; this.initialPlan = new PlanInMemory(); @@ -96,6 +101,16 @@ public Problem( this.simulationFacade.addActivityTypes(this.getActivityTypes()); } this.initialSimulationResults = Optional.empty(); + this.sourceSchedulingGoals = sourceSchedulingGoals; + } + + public Problem( + MissionModel mission, + PlanningHorizon planningHorizon, + SimulationFacade simulationFacade, + SchedulerModel schedulerModel + ) { + this(mission, planningHorizon, simulationFacade, schedulerModel, new HashMap<>()); } public SimulationFacade getSimulationFacade(){ 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 a6629d3182..3dc5d15364 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 @@ -265,11 +265,35 @@ public void initializePlan() throws SimulationFacade.SimulationException, Schedu * * the output plan member is updated directly with the devised solution */ - private void solve() throws SchedulingInterruptedException{ + private void solve() throws SchedulingInterruptedException { //construct a priority sorted goal container final var goalQ = getGoalQueue(); assert goalQ != null; + // perform at-beginning auto deletions first + boolean simulateAfter = false; + for (final var goal: goalQ) { + if (goal instanceof Procedure p) { + simulateAfter = simulateAfter || p.deleteAtBeginning( + problem, + plan, + problem.getMissionModel(), + this.problem::getActivityType, + this.simulationFacade, + this.idGenerator, + this.problem.getEventsByDerivationGroup() + ); + } + } + + if (simulateAfter) { + try { + simulationFacade.simulateNoResults(plan, problem.getPlanningHorizon().getAerieHorizonDuration()); + } catch (SimulationFacade.SimulationException e) { + logger.error("Simulation error after auto deleting activities: ", e); + } + } + //process each goal independently in that order while (!goalQ.isEmpty()) { var goal = goalQ.remove(); @@ -299,7 +323,7 @@ private LinkedList getGoalQueue() { final var rawGoals = problem.getGoals(); assert rawGoals != null; - this.atLeastOneSimulateAfter = rawGoals.stream().filter(g -> g.simulateAfter).findFirst().isPresent(); + this.atLeastOneSimulateAfter = rawGoals.stream().anyMatch(g -> g.simulateAfter); //create queue container using comparator and pre-sized for all goals final var capacity = rawGoals.size(); diff --git a/scheduler-driver/src/main/kotlin/gov/nasa/jpl/aerie/scheduler/plan/InMemoryEditablePlan.kt b/scheduler-driver/src/main/kotlin/gov/nasa/jpl/aerie/scheduler/plan/InMemoryEditablePlan.kt index 49ca1dbaeb..d45d119bb6 100644 --- a/scheduler-driver/src/main/kotlin/gov/nasa/jpl/aerie/scheduler/plan/InMemoryEditablePlan.kt +++ b/scheduler-driver/src/main/kotlin/gov/nasa/jpl/aerie/scheduler/plan/InMemoryEditablePlan.kt @@ -13,38 +13,35 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.DurationType import gov.nasa.jpl.aerie.scheduler.DirectiveIdGenerator import gov.nasa.jpl.aerie.scheduler.model.* import gov.nasa.jpl.aerie.types.ActivityDirectiveId -import java.time.Instant import kotlin.jvm.optionals.getOrNull /* * An implementation of [EditablePlan] that stores the plan in memory for use in the internal scheduler. - * - */ data class InMemoryEditablePlan( private val missionModel: MissionModel<*>, private var idGenerator: DirectiveIdGenerator, - private val plan: SchedulerToProcedurePlanAdapter, + val adapter: SchedulerToProcedurePlanAdapter, private val simulationFacade: SimulationFacade, private val lookupActivityType: (String) -> ActivityType -) : EasyEditablePlanDriver(plan) { +) : EasyEditablePlanDriver(adapter) { override fun generateDirectiveId(): ActivityDirectiveId = idGenerator.next() override fun latestResultsInternal(): PerishableSimulationResults? { val merlinResults = simulationFacade.latestSimulationData.getOrNull() ?: return null - return MerlinToProcedureSimulationResultsAdapter(merlinResults.driverResults, plan.copy(schedulerPlan = plan.duplicate())) + return MerlinToProcedureSimulationResultsAdapter(merlinResults.driverResults, adapter.copy(schedulerPlan = adapter.duplicate())) } override fun createInternal(directive: Directive) { - plan.add(directive.toSchedulingActivity(lookupActivityType, true)) + adapter.add(directive.toSchedulingActivity(lookupActivityType, true)) } override fun deleteInternal(id: ActivityDirectiveId) { - plan.remove(plan.activitiesById[id]) + adapter.remove(adapter.activitiesById[id]) } override fun simulateInternal(options: SimulateOptions) { - simulationFacade.simulateWithResults(plan, options.pause.resolve(this)) + simulationFacade.simulateWithResults(adapter, options.pause.resolve(this)) } override fun validate(directive: Directive) { diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/exceptions/NoSuchSchedulingGoalException.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/exceptions/NoSuchSchedulingGoalException.java index 421ae3705e..0e8b3b4ba5 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/exceptions/NoSuchSchedulingGoalException.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/exceptions/NoSuchSchedulingGoalException.java @@ -1,6 +1,6 @@ package gov.nasa.jpl.aerie.scheduler.server.exceptions; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; public final class NoSuchSchedulingGoalException extends Exception { public final GoalId goalId; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/http/ResponseSerializers.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/http/ResponseSerializers.java index 44302a40c2..af99dbd96c 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/http/ResponseSerializers.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/http/ResponseSerializers.java @@ -11,7 +11,7 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchPlanException; import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchSpecificationException; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.scheduler.server.models.SchedulingCompilationError; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleAction; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleResults; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GoalInvocationRecord.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GoalInvocationRecord.java index bb048ddbf8..026219b4dc 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GoalInvocationRecord.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/models/GoalInvocationRecord.java @@ -1,6 +1,7 @@ package gov.nasa.jpl.aerie.scheduler.server.models; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import java.util.Map; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/SpecificationRepository.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/SpecificationRepository.java index 8c3d192eea..91b7da7cf0 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/SpecificationRepository.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/SpecificationRepository.java @@ -4,7 +4,7 @@ import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchSchedulingGoalException; import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchSpecificationException; import gov.nasa.jpl.aerie.scheduler.server.exceptions.SpecificationLoadException; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.scheduler.server.models.GoalType; import gov.nasa.jpl.aerie.scheduler.server.models.Specification; import gov.nasa.jpl.aerie.scheduler.server.models.SpecificationId; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetCreatedActivitiesAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetCreatedActivitiesAction.java index e675a6c73a..823587b5a0 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetCreatedActivitiesAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetCreatedActivitiesAction.java @@ -1,6 +1,6 @@ package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import org.intellij.lang.annotations.Language; @@ -11,7 +11,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; /*package-local*/ final class GetCreatedActivitiesAction implements AutoCloseable { private static final @Language("SQL") String sql = """ diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetGoalSatisfactionAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetGoalSatisfactionAction.java index 8624da2af2..aba6e9b2fc 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetGoalSatisfactionAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetGoalSatisfactionAction.java @@ -5,7 +5,7 @@ import java.sql.SQLException; import java.util.HashMap; import java.util.Map; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import org.intellij.lang.annotations.Language; /*package-local*/ final class GetGoalSatisfactionAction implements AutoCloseable { diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSatisfyingActivitiesAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSatisfyingActivitiesAction.java index d52dfbdf47..407833c36f 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSatisfyingActivitiesAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSatisfyingActivitiesAction.java @@ -1,6 +1,6 @@ package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import org.intellij.lang.annotations.Language; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSchedulingGoalAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSchedulingGoalAction.java index 40c8b1ee0b..ac7868ffb0 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSchedulingGoalAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSchedulingGoalAction.java @@ -1,6 +1,6 @@ package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.scheduler.server.models.GoalSource; import gov.nasa.jpl.aerie.scheduler.server.models.GoalType; import org.intellij.lang.annotations.Language; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSpecificationGoalsAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSpecificationGoalsAction.java index e74941e538..557774fb94 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSpecificationGoalsAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GetSpecificationGoalsAction.java @@ -3,7 +3,7 @@ import gov.nasa.jpl.aerie.merlin.driver.json.SerializedValueJsonParser; import gov.nasa.jpl.aerie.scheduler.server.http.InvalidEntityException; import gov.nasa.jpl.aerie.scheduler.server.http.InvalidJsonException; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.scheduler.server.models.GoalInvocationRecord; import gov.nasa.jpl.aerie.scheduler.server.models.GoalSource; import gov.nasa.jpl.aerie.scheduler.server.models.GoalType; @@ -13,10 +13,8 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.text.ParseException; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import static gov.nasa.jpl.aerie.scheduler.server.http.SchedulerParsers.parseJson; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GoalBuilder.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GoalBuilder.java index 5a9b6b6569..f8756e2c3b 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GoalBuilder.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/GoalBuilder.java @@ -25,6 +25,7 @@ import gov.nasa.jpl.aerie.scheduler.goals.Procedure; import gov.nasa.jpl.aerie.scheduler.goals.RecurrenceGoal; import gov.nasa.jpl.aerie.scheduler.model.ActivityType; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.scheduler.model.PersistentTimeAnchor; import gov.nasa.jpl.aerie.scheduler.model.PlanningHorizon; import gov.nasa.jpl.aerie.scheduler.server.models.SchedulingDSL; @@ -43,7 +44,9 @@ public static Goal goalOfGoalSpecifier( final Timestamp horizonStartTimestamp, final Timestamp horizonEndTimestamp, final Function lookupActivityType, - final boolean simulateAfter) { + final boolean simulateAfter, + final GoalId goalId + ) { final var planningHorizon = new PlanningHorizon( horizonStartTimestamp.toInstant(), horizonEndTimestamp.toInstant()); @@ -110,11 +113,16 @@ public static Goal goalOfGoalSpecifier( case SchedulingDSL.GoalSpecifier.GoalAnd g -> { var builder = new CompositeAndGoal.Builder(); for (final var subGoalSpecifier : g.goals()) { - builder = builder.and(goalOfGoalSpecifier(subGoalSpecifier, - horizonStartTimestamp, - horizonEndTimestamp, - lookupActivityType, - simulateAfter)); + builder = builder.and( + goalOfGoalSpecifier( + subGoalSpecifier, + horizonStartTimestamp, + horizonEndTimestamp, + lookupActivityType, + simulateAfter, + goalId + ) + ); } builder.simulateAfter(simulateAfter); builder.withinPlanHorizon(planningHorizon); @@ -126,11 +134,16 @@ public static Goal goalOfGoalSpecifier( case SchedulingDSL.GoalSpecifier.GoalOr g -> { var builder = new OptionGoal.Builder(); for (final var subGoalSpecifier : g.goals()) { - builder = builder.or(goalOfGoalSpecifier(subGoalSpecifier, - horizonStartTimestamp, - horizonEndTimestamp, - lookupActivityType, - simulateAfter)); + builder = builder.or( + goalOfGoalSpecifier( + subGoalSpecifier, + horizonStartTimestamp, + horizonEndTimestamp, + lookupActivityType, + simulateAfter, + goalId + ) + ); } builder.simulateAfter(simulateAfter); builder.withinPlanHorizon(planningHorizon); @@ -140,7 +153,7 @@ public static Goal goalOfGoalSpecifier( } case SchedulingDSL.GoalSpecifier.GoalApplyWhen g -> { - var goal = goalOfGoalSpecifier(g.goal(), horizonStartTimestamp, horizonEndTimestamp, lookupActivityType, simulateAfter); + var goal = goalOfGoalSpecifier(g.goal(), horizonStartTimestamp, horizonEndTimestamp, lookupActivityType, simulateAfter, goalId); goal.setTemporalContext(g.windows()); return goal; } @@ -165,7 +178,7 @@ public static Goal goalOfGoalSpecifier( } case SchedulingDSL.GoalSpecifier.Procedure g -> { - return new Procedure(planningHorizon, g.jarPath(), g.arguments(), simulateAfter); + return new Procedure(planningHorizon, g.jarPath(), g.arguments(), simulateAfter, goalId); } } } diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertCreatedActivitiesAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertCreatedActivitiesAction.java index 87e5205d95..aedd1ae523 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertCreatedActivitiesAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertCreatedActivitiesAction.java @@ -1,6 +1,6 @@ package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import org.intellij.lang.annotations.Language; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertGoalSatisfactionAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertGoalSatisfactionAction.java index 0a3083ea54..958ae9f8b3 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertGoalSatisfactionAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertGoalSatisfactionAction.java @@ -1,6 +1,6 @@ package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import org.intellij.lang.annotations.Language; import java.sql.Connection; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertSatisfyingActivitiesAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertSatisfyingActivitiesAction.java index 3fa7556f48..a00535839f 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertSatisfyingActivitiesAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/InsertSatisfyingActivitiesAction.java @@ -1,6 +1,6 @@ package gov.nasa.jpl.aerie.scheduler.server.remotes.postgres; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import org.intellij.lang.annotations.Language; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java index 87bd7daaf0..d92b39868e 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresResultsCellRepository.java @@ -13,7 +13,7 @@ import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchRequestException; import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchSpecificationException; import gov.nasa.jpl.aerie.scheduler.server.models.DatasetId; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.scheduler.server.models.SpecificationId; import gov.nasa.jpl.aerie.scheduler.server.remotes.ResultsCellRepository; import gov.nasa.jpl.aerie.scheduler.server.services.ScheduleFailure; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresSpecificationRepository.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresSpecificationRepository.java index 3338460c64..c43224a777 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresSpecificationRepository.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/PostgresSpecificationRepository.java @@ -3,7 +3,7 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchSchedulingGoalException; import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchSpecificationException; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.scheduler.server.models.GoalType; import gov.nasa.jpl.aerie.scheduler.server.models.SchedulingConditionRecord; import gov.nasa.jpl.aerie.scheduler.server.models.GoalInvocationRecord; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/UpdateSchedulingGoalParameterSchemaAction.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/UpdateSchedulingGoalParameterSchemaAction.java index 27dce53309..93039d3f9a 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/UpdateSchedulingGoalParameterSchemaAction.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/remotes/postgres/UpdateSchedulingGoalParameterSchemaAction.java @@ -2,7 +2,7 @@ import gov.nasa.jpl.aerie.merlin.driver.json.ValueSchemaJsonParser; import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import org.intellij.lang.annotations.Language; import java.sql.Connection; diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinDatabaseService.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinDatabaseService.java index 5c31c7eee9..306d708901 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinDatabaseService.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinDatabaseService.java @@ -36,7 +36,7 @@ import gov.nasa.jpl.aerie.scheduler.server.models.ActivityType; import gov.nasa.jpl.aerie.scheduler.server.models.DatasetId; import gov.nasa.jpl.aerie.scheduler.server.models.ExternalProfiles; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.scheduler.server.models.MerlinPlan; import gov.nasa.jpl.aerie.scheduler.server.models.PlanId; import gov.nasa.jpl.aerie.scheduler.server.models.PlanMetadata; @@ -88,6 +88,7 @@ import static gov.nasa.jpl.aerie.scheduler.server.graphql.GraphQLParsers.simulationArgumentsP; import static gov.nasa.jpl.aerie.scheduler.server.graphql.ProfileParsers.discreteValueSchemaTypeP; import static gov.nasa.jpl.aerie.scheduler.server.graphql.ProfileParsers.realValueSchemaTypeP; +import static java.util.Map.entry; /** * {@inheritDoc} @@ -582,14 +583,14 @@ mutation createAllPlanActivityDirectives($activities: [activity_directive_insert final var insertionObjects = Json.createArrayBuilder(); for (final var act : orderedActivities) { - var insertionObject = Json + final var insertionObject = Json .createObjectBuilder() .add("plan_id", planId.id()) .add("type", act.getType().getName()) .add("start_offset", act.startOffset().toString()) .add("anchored_to_start", act.anchoredToStart()); - if (act.name() != null) insertionObject = insertionObject.add("name", act.name()); + if (act.name() != null) insertionObject.add("name", act.name()); //add duration to parameters if controllable final var insertionObjectArguments = Json.createObjectBuilder(); @@ -602,6 +603,7 @@ mutation createAllPlanActivityDirectives($activities: [activity_directive_insert final var goalId = activityToGoalId.get(act); if (goalId != null) { insertionObject.add("source_scheduling_goal_id", goalId.id()); + goalId.goalInvocationId().ifPresent($ -> insertionObject.add("source_scheduling_goal_invocation_id", $)); } for (final var arg : act.arguments().entrySet()) { @@ -858,6 +860,35 @@ public Collection getResourceTypes(final PlanId planId) return allResourceTypes; } + @Override + @SuppressWarnings("unchecked") + public Map getActivityIdToGoalIdMap(final PlanId planId) + throws MerlinServiceException, IOException + { + final var request = """ + query { + activity_directive(where: {plan_id: {_eq: %d}}) { + id + source_scheduling_goal_id + source_scheduling_goal_invocation_id + } + """.formatted(planId.id()); + final JsonObject response = postRequest(request).get(); + final var data = response.getJsonObject("data"); + final List> results = data.getJsonArray("activity_directive").getValuesAs( + $ -> { + final var obj = $.asJsonObject(); + final var id = new ActivityDirectiveId(obj.getInt("id")); + if (obj.isNull("source_scheduling_goal_id")) return entry(id, null); + final var source_goal = obj.getInt("source_scheduling_goal_id"); + if (obj.isNull("source_scheduling_goal_invocation_id")) return entry(id, new GoalId(source_goal, -1, Optional.empty())); + final Long source_invocation = (long) obj.getInt("source_scheduling_goal_invocation_id"); + return entry(id, new GoalId(source_goal, -1, Optional.of(source_invocation))); + } + ); + return Map.ofEntries(results.toArray(new Map.Entry[0])); + } + public SimulationId getSimulationId(PlanId planId) throws MerlinServiceException, IOException { final var request = """ query { diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/MerlinDatabaseService.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/MerlinDatabaseService.java index 18939f8d1f..d046427db8 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/MerlinDatabaseService.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/MerlinDatabaseService.java @@ -15,7 +15,7 @@ import gov.nasa.jpl.aerie.scheduler.server.models.ActivityType; import gov.nasa.jpl.aerie.scheduler.server.models.DatasetId; import gov.nasa.jpl.aerie.scheduler.server.models.ExternalProfiles; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.scheduler.server.models.MerlinPlan; import gov.nasa.jpl.aerie.scheduler.server.models.PlanId; import gov.nasa.jpl.aerie.scheduler.server.models.PlanMetadata; @@ -114,6 +114,9 @@ Map> getExternalEvents(final PlanId planId, final In */ Collection getResourceTypes(final PlanId planId) throws IOException, MerlinServiceException, NoSuchPlanException; + + Map getActivityIdToGoalIdMap(final PlanId planId) + throws MerlinServiceException, IOException; } interface WriterRole { diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/ScheduleResults.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/ScheduleResults.java index 755ca3c313..b9bcd3344f 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/ScheduleResults.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/ScheduleResults.java @@ -3,7 +3,7 @@ import java.util.Collection; import java.util.Map; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.types.ActivityDirectiveId; /** diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/SpecificationService.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/SpecificationService.java index 9fe6fad93e..d0a6dc27a4 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/SpecificationService.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/SpecificationService.java @@ -5,7 +5,7 @@ import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchSchedulingGoalException; import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchSpecificationException; import gov.nasa.jpl.aerie.scheduler.server.exceptions.SpecificationLoadException; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.scheduler.server.models.GoalType; import gov.nasa.jpl.aerie.scheduler.server.models.Specification; import gov.nasa.jpl.aerie.scheduler.server.models.SpecificationId; 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 1347e2afac..ddcb1a01a5 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 @@ -50,7 +50,7 @@ import gov.nasa.jpl.aerie.scheduler.server.http.ResponseSerializers; import gov.nasa.jpl.aerie.scheduler.server.models.DatasetId; import gov.nasa.jpl.aerie.scheduler.server.models.ExternalProfiles; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.scheduler.server.models.GoalInvocationRecord; import gov.nasa.jpl.aerie.scheduler.server.models.GoalSource; import gov.nasa.jpl.aerie.scheduler.server.models.GoalType; @@ -141,12 +141,15 @@ public void schedule( planMetadata.modelConfiguration(), planMetadata.horizon().getStartInstant(), new MissionModelId(planMetadata.modelId())), - canceledListener); + canceledListener + ); + final var oldActivityIdToGoalId = merlinDatabaseService.getActivityIdToGoalIdMap(specification.planId()); final var problem = new Problem( schedulerMissionModel.missionModel(), planningHorizon, simulationFacade, - schedulerMissionModel.schedulerModel() + schedulerMissionModel.schedulerModel(), + oldActivityIdToGoalId ); final var externalProfiles = loadExternalProfiles(planMetadata.planId()); final var externalEventsByDerivationGroup = loadExternalEvents(planMetadata.planId(), planMetadata.horizon().getStartInstant()); @@ -191,49 +194,51 @@ public void schedule( final var orderedGoals = new ArrayList(); final var goals = new HashMap(); - final var compiledGoals = new ArrayList>(); - final var failedGoals = new ArrayList>>(); - for (final var goalRecord : specification.goalsByPriority()) { - switch (goalRecord.type()) { - case GoalType.EDSL edsl -> { - final var result = compileGoalDefinition( - merlinDatabaseService, - planMetadata.planId(), - edsl.source(), - schedulingDSLCompilationService, - externalProfiles.resourceTypes()); - if (result instanceof SchedulingDSLCompilationService.SchedulingDSLCompilationResult.Success r) { - compiledGoals.add(Pair.of(goalRecord, r.value())); - } else if (result instanceof SchedulingDSLCompilationService.SchedulingDSLCompilationResult.Error r) { - failedGoals.add(Pair.of(goalRecord.id(), r.errors())); - } else { - throw new Error("Unhandled variant of %s: %s".formatted( - SchedulingDSLCompilationService.SchedulingDSLCompilationResult.class.getSimpleName(), - result)); - } - } - case GoalType.JAR jar -> { - compiledGoals.add(Pair.of(goalRecord, new SchedulingDSL.GoalSpecifier.Procedure(modelJarsDir.resolve(jar.path()), goalRecord.args()))); + final var compiledGoals = new ArrayList>(); + final var failedGoals = new ArrayList>>(); + for (final var goalRecord : specification.goalsByPriority()) { + switch (goalRecord.type()) { + case GoalType.EDSL edsl -> { + final var result = compileGoalDefinition( + merlinDatabaseService, + planMetadata.planId(), + edsl.source(), + schedulingDSLCompilationService, + externalProfiles.resourceTypes()); + if (result instanceof SchedulingDSLCompilationService.SchedulingDSLCompilationResult.Success r) { + compiledGoals.add(Pair.of(goalRecord, r.value())); + } else if (result instanceof SchedulingDSLCompilationService.SchedulingDSLCompilationResult.Error r) { + failedGoals.add(Pair.of(goalRecord.id(), r.errors())); + } else { + throw new Error("Unhandled variant of %s: %s".formatted( + SchedulingDSLCompilationService.SchedulingDSLCompilationResult.class.getSimpleName(), + result)); } } + case GoalType.JAR jar -> { + compiledGoals.add(Pair.of(goalRecord, new SchedulingDSL.GoalSpecifier.Procedure(modelJarsDir.resolve(jar.path()), goalRecord.args()))); + } } - if (!failedGoals.isEmpty()) { - writer.failWith(b -> b - .type("SCHEDULING_GOALS_FAILED") - .message("Scheduling goal%s failed".formatted(failedGoals.size() > 1 ? "s" : "")) - .data(ResponseSerializers.serializeFailedGoals(failedGoals))); - return; - } - for (final var compiledGoal : compiledGoals) { - final var goal = GoalBuilder - .goalOfGoalSpecifier( - compiledGoal.getValue(), - specification.horizonStartTimestamp(), - specification.horizonEndTimestamp(), - problem::getActivityType, - compiledGoal.getKey().simulateAfter()); - orderedGoals.add(goal); - goals.put(goal, compiledGoal.getKey().id()); + } + if (!failedGoals.isEmpty()) { + writer.failWith(b -> b + .type("SCHEDULING_GOALS_FAILED") + .message("Scheduling goal%s failed".formatted(failedGoals.size() > 1 ? "s" : "")) + .data(ResponseSerializers.serializeFailedGoals(failedGoals))); + return; + } + for (final var compiledGoal : compiledGoals) { + final var goal = GoalBuilder + .goalOfGoalSpecifier( + compiledGoal.getValue(), + specification.horizonStartTimestamp(), + specification.horizonEndTimestamp(), + problem::getActivityType, + compiledGoal.getKey().simulateAfter(), + compiledGoal.getKey().id() + ); + orderedGoals.add(goal); + goals.put(goal, compiledGoal.getKey().id()); } problem.setGoals(orderedGoals); @@ -242,10 +247,10 @@ public void schedule( final var solutionPlan = scheduler.getNextSolution().orElseThrow( () -> new ResultsProtocolFailure("scheduler returned no solution")); - final var activityToGoalId = new HashMap(); + final var newActivityToGoalId = new HashMap(); for (final var entry : solutionPlan.getEvaluation().getGoalEvaluations().entrySet()) { for (final var activity : entry.getValue().getInsertedActivities()) { - activityToGoalId.put(activity, goals.get(entry.getKey())); + newActivityToGoalId.put(activity, goals.get(entry.getKey())); } } //store the solution plan back into merlin (and reconfirm no intervening mods!) @@ -255,7 +260,7 @@ public void schedule( planMetadata, loadedPlanComponents.merlinPlan(), solutionPlan, - activityToGoalId, + newActivityToGoalId, schedulerMissionModel.schedulerModel() ); diff --git a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockMerlinDatabaseService.java b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockMerlinDatabaseService.java index f79c1acf1d..d9dd3e9954 100644 --- a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockMerlinDatabaseService.java +++ b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockMerlinDatabaseService.java @@ -13,7 +13,7 @@ import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivity; import gov.nasa.jpl.aerie.scheduler.server.models.DatasetId; import gov.nasa.jpl.aerie.scheduler.server.models.ExternalProfiles; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.scheduler.server.models.MerlinPlan; import gov.nasa.jpl.aerie.scheduler.server.models.PlanId; import gov.nasa.jpl.aerie.scheduler.server.models.PlanMetadata; @@ -173,6 +173,13 @@ public Collection getResourceTypes(final PlanId planId) return null; } + @Override + public Map getActivityIdToGoalIdMap(final PlanId planId) + throws MerlinServiceException, IOException + { + return Map.of(); + } + @Override public void clearPlanActivityDirectives(final PlanId planId) { diff --git a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockSpecificationRepository.java b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockSpecificationRepository.java index 9547dbc82a..1834ab16a2 100644 --- a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockSpecificationRepository.java +++ b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockSpecificationRepository.java @@ -5,7 +5,7 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchSpecificationException; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.scheduler.server.models.GoalType; import gov.nasa.jpl.aerie.scheduler.server.models.Specification; import gov.nasa.jpl.aerie.scheduler.server.models.SpecificationId; diff --git a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingDSLCompilationServiceTests.java b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingDSLCompilationServiceTests.java index efd274cb7a..8198d1058d 100644 --- a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingDSLCompilationServiceTests.java +++ b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingDSLCompilationServiceTests.java @@ -29,6 +29,7 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; import gov.nasa.jpl.aerie.scheduler.TimeUtility; import gov.nasa.jpl.aerie.scheduler.constraints.timeexpressions.TimeAnchor; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.scheduler.model.PersistentTimeAnchor; import gov.nasa.jpl.aerie.scheduler.model.Problem; import gov.nasa.jpl.aerie.scheduler.server.exceptions.NoSuchPlanException; @@ -42,6 +43,7 @@ import gov.nasa.jpl.aerie.scheduler.server.models.SchedulingDSL; import gov.nasa.jpl.aerie.scheduler.server.services.MerlinDatabaseService; import gov.nasa.jpl.aerie.scheduler.server.services.MerlinServiceException; +import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import gov.nasa.jpl.aerie.types.MissionModelId; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.AfterAll; @@ -122,6 +124,13 @@ public Map> getExternalEvents(final PlanId planId, f public Collection getResourceTypes(final PlanId planId) { return null; } + + @Override + public Map getActivityIdToGoalIdMap(final PlanId planId) + throws MerlinServiceException, IOException + { + return Map.of(); + } }; SchedulingDSLCompilationService schedulingDSLCompilationService; diff --git a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingEdslIntegrationTests.java b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingEdslIntegrationTests.java index b7dda0af9a..51eebe46a4 100644 --- a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingEdslIntegrationTests.java +++ b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingEdslIntegrationTests.java @@ -42,7 +42,7 @@ import gov.nasa.jpl.aerie.scheduler.server.models.SchedulingConditionId; import gov.nasa.jpl.aerie.scheduler.server.models.SchedulingConditionRecord; import gov.nasa.jpl.aerie.scheduler.server.models.SchedulingConditionSource; -import gov.nasa.jpl.aerie.scheduler.server.models.GoalId; +import gov.nasa.jpl.aerie.scheduler.model.GoalId; import gov.nasa.jpl.aerie.scheduler.server.models.GoalInvocationRecord; import gov.nasa.jpl.aerie.scheduler.server.models.GoalSource; import gov.nasa.jpl.aerie.scheduler.server.models.PlanId;