Skip to content

Commit

Permalink
Merge pull request #171 from NASA-AMMOS/prototype/AERIE-1812--cardina…
Browse files Browse the repository at this point in the history
…lity-mutex

[AERIE-1812] Add cardinality goal (with implied mutex) to the scheduling eDSL
  • Loading branch information
adrienmaillard authored May 27, 2022
2 parents c0a2459 + 3e6a6be commit 63efc3f
Show file tree
Hide file tree
Showing 16 changed files with 367 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package gov.nasa.jpl.aerie.constraints.tree;

import gov.nasa.jpl.aerie.constraints.model.ActivityInstance;
import gov.nasa.jpl.aerie.constraints.model.SimulationResults;
import gov.nasa.jpl.aerie.constraints.model.Violation;
import gov.nasa.jpl.aerie.constraints.time.Window;
import gov.nasa.jpl.aerie.constraints.time.Windows;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

public final class WindowsOf implements Expression<Windows> {
public final Expression<List<Violation>> expression;

public WindowsOf(Expression<List<Violation>> expression) {
this.expression = expression;
}

@Override
public Windows evaluate(SimulationResults results, final Window bounds, Map<String, ActivityInstance> environment) {
final var ret = new Windows(bounds);
final var unsatisfiedWindows = this.expression.evaluate(results, bounds, environment);
for(var unsatisfiedWindow : unsatisfiedWindows){
ret.intersectWith(unsatisfiedWindow.violationWindows);
}
return ret;
}

@Override
public void extractResources(final Set<String> names) {
this.expression.extractResources(names);
}

@Override
public String prettyPrint(final String prefix) {
return this.expression.prettyPrint(prefix);
}

@Override
public boolean equals(Object obj) {
if (!(obj instanceof WindowsOf)) return false;
final var o = (WindowsOf)obj;

return Objects.equals(this.expression, o.expression);
}

@Override
public int hashCode() {
return Objects.hash(this.expression);
}
}
2 changes: 1 addition & 1 deletion scheduler-server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ dependencies {
implementation project(':constraints')
implementation project(':scheduler')

runtimeOnly project(':merlin-framework')
implementation project(':merlin-framework')

implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'io.javalin:javalin:4.1.1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,20 @@ export interface ActivityTemplate {
args: {[key: string]: any},
}

export interface ClosedOpenInterval {
start: number
end: number
}

export type CardinalityGoalArguments =
| { duration: number, occurrence: number }
| { duration: number }
| { occurrence: number }

export enum NodeKind {
ActivityRecurrenceGoal = 'ActivityRecurrenceGoal',
ActivityCoexistenceGoal = 'ActivityCoexistenceGoal',
ActivityCardinalityGoal = 'ActivityCardinalityGoal',
GoalAnd = 'GoalAnd',
GoalOr = 'GoalOr'
}
Expand All @@ -22,15 +33,22 @@ export enum NodeKind {
export type Goal =
| ActivityRecurrenceGoal
| ActivityCoexistenceGoal
| ActivityCardinalityGoal
;
// TODO cardinality goal

export interface ActivityRecurrenceGoal {
kind: NodeKind.ActivityRecurrenceGoal,
activityTemplate: ActivityTemplate,
interval: number,
}

export interface ActivityCardinalityGoal {
kind: NodeKind.ActivityCardinalityGoal,
activityTemplate: ActivityTemplate,
specification: CardinalityGoalArguments,
inPeriod: ClosedOpenInterval,
}

export interface ActivityCoexistenceGoal {
kind: NodeKind.ActivityCoexistenceGoal,
activityTemplate: ActivityTemplate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as WindowsEDSL from './windows-edsl-fluent-api'

interface ActivityRecurrenceGoal extends Goal {}
interface ActivityCoexistenceGoal extends Goal {}
interface ActivityCardinalityGoal extends Goal {}

export class Goal {
public readonly __astNode: AST.GoalSpecifier;
Expand Down Expand Up @@ -49,6 +50,14 @@ export class Goal {
forEach: opts.forEach.__astnode,
});
}
public static CardinalityGoal(opts: { activityTemplate: ActivityTemplate, specification: AST.CardinalityGoalArguments, inPeriod: ClosedOpenInterval }): ActivityCardinalityGoal {
return Goal.new({
kind: AST.NodeKind.ActivityCardinalityGoal,
activityTemplate: opts.activityTemplate,
specification: opts.specification,
inPeriod : opts.inPeriod
});
}
}

declare global {
Expand All @@ -61,12 +70,15 @@ declare global {
public static ActivityRecurrenceGoal(opts: { activityTemplate: ActivityTemplate, interval: Duration }): ActivityRecurrenceGoal

public static CoexistenceGoal(opts: { activityTemplate: ActivityTemplate, forEach: WindowsEDSL.WindowSet }): ActivityCoexistenceGoal

public static CardinalityGoal(opts: { activityTemplate: ActivityTemplate, specification: AST.CardinalityGoalArguments, inPeriod: ClosedOpenInterval }): ActivityCardinalityGoal
}
type Duration = number;
type Double = number;
type Integer = number;
}

export interface ClosedOpenInterval extends AST.ClosedOpenInterval {}
export interface ActivityTemplate extends AST.ActivityTemplate {}

// Make Goal available on the global object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

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

import static gov.nasa.jpl.aerie.json.BasicParsers.intP;
import static gov.nasa.jpl.aerie.json.BasicParsers.doubleP;
import static gov.nasa.jpl.aerie.json.BasicParsers.listP;
import static gov.nasa.jpl.aerie.json.BasicParsers.longP;
Expand Down Expand Up @@ -38,6 +40,21 @@ public class SchedulingDSL {
microseconds -> Duration.of(microseconds, Duration.MICROSECONDS),
duration -> duration.in(Duration.MICROSECONDS)));

private static final JsonParser<ClosedOpenInterval> intervalP =
productP
.field("start", durationP)
.field("end", durationP)
.map(Iso.of(
untuple(ClosedOpenInterval::new),
$ -> tuple($.start(), $.end())));

private static final JsonParser<CardinalitySpecification> cardinalitySpecificationJsonParser =
productP
.optionalField("duration", durationP)
.optionalField("occurrence", intP)
.map(Iso.of(
untuple(CardinalitySpecification::new),
$ -> tuple($.duration(), $.occurrence())));
private static final ProductParsers.JsonObjectParser<GoalSpecifier.RecurrenceGoalDefinition> recurrenceGoalDefinitionP =
productP
.field("activityTemplate", activityTemplateP)
Expand Down Expand Up @@ -147,6 +164,18 @@ private static ProductParsers.JsonObjectParser<ConstraintExpression.Or> windowsO
goalDefinition.activityTemplate(),
goalDefinition.forEach())));

private static final ProductParsers.JsonObjectParser<GoalSpecifier.CardinalityGoalDefinition> cardinalityGoalDefinitionP =
productP
.field("activityTemplate", activityTemplateP)
.field("specification", cardinalitySpecificationJsonParser)
.field("inPeriod", intervalP)
.map(Iso.of(
untuple(GoalSpecifier.CardinalityGoalDefinition::new),
goalDefinition -> tuple(
goalDefinition.activityTemplate(),
goalDefinition.specification(),
goalDefinition.inPeriod())));

private static ProductParsers.JsonObjectParser<GoalSpecifier.GoalAnd> goalAndF(final JsonParser<GoalSpecifier> goalSpecifierP) {
return productP
.field("goals", listP(goalSpecifierP))
Expand All @@ -166,6 +195,7 @@ private static ProductParsers.JsonObjectParser<GoalSpecifier.GoalOr> goalOrF(fin
recursiveP(self -> SumParsers.sumP("kind", GoalSpecifier.class, List.of(
SumParsers.variant("ActivityRecurrenceGoal", GoalSpecifier.RecurrenceGoalDefinition.class, recurrenceGoalDefinitionP),
SumParsers.variant("ActivityCoexistenceGoal", GoalSpecifier.CoexistenceGoalDefinition.class, coexistenceGoalDefinitionP),
SumParsers.variant("ActivityCardinalityGoal", GoalSpecifier.CardinalityGoalDefinition.class, cardinalityGoalDefinitionP),
SumParsers.variant("GoalAnd", GoalSpecifier.GoalAnd.class, goalAndF(self)),
SumParsers.variant("GoalOr", GoalSpecifier.GoalOr.class, goalOrF(self))
)));
Expand All @@ -183,12 +213,19 @@ record CoexistenceGoalDefinition(
ActivityTemplate activityTemplate,
ConstraintExpression forEach
) implements GoalSpecifier {}
record CardinalityGoalDefinition(
ActivityTemplate activityTemplate,
CardinalitySpecification specification,
ClosedOpenInterval inPeriod
) implements GoalSpecifier {}
record GoalAnd(List<GoalSpecifier> goals) implements GoalSpecifier {}
record GoalOr(List<GoalSpecifier> goals) implements GoalSpecifier {}
}

public record LinearResource(String name) {}

public record CardinalitySpecification(Optional<Duration> duration, Optional<Integer> occurrence){}
public record ClosedOpenInterval(Duration start, Duration end){}
public record ActivityTemplate(String activityType, Map<String, SerializedValue> arguments) {}

public record ActivityExpression(String type) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,23 @@
import gov.nasa.jpl.aerie.constraints.tree.RealResource;
import gov.nasa.jpl.aerie.constraints.tree.RealValue;
import gov.nasa.jpl.aerie.constraints.tree.Transition;
import gov.nasa.jpl.aerie.constraints.time.Window;
import gov.nasa.jpl.aerie.constraints.time.Windows;
import gov.nasa.jpl.aerie.constraints.tree.During;
import gov.nasa.jpl.aerie.constraints.tree.ForEachActivity;
import gov.nasa.jpl.aerie.constraints.tree.ViolationsOf;
import gov.nasa.jpl.aerie.constraints.tree.WindowsOf;
import gov.nasa.jpl.aerie.contrib.serialization.mappers.DurationValueMapper;
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
import gov.nasa.jpl.aerie.merlin.protocol.types.DurationType;
import gov.nasa.jpl.aerie.scheduler.Range;
import gov.nasa.jpl.aerie.scheduler.constraints.TimeRangeExpression;
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;
import gov.nasa.jpl.aerie.scheduler.constraints.timeexpressions.TimeAnchor;
import gov.nasa.jpl.aerie.scheduler.goals.CardinalityGoal;
import gov.nasa.jpl.aerie.scheduler.goals.CoexistenceGoal;
import gov.nasa.jpl.aerie.scheduler.goals.CompositeAndGoal;
import gov.nasa.jpl.aerie.scheduler.goals.Goal;
Expand Down Expand Up @@ -82,6 +94,20 @@ public static Goal goalOfGoalSpecifier(
lookupActivityType));
}
return builder.build();
} else if(goalSpecifier instanceof SchedulingDSL.GoalSpecifier.CardinalityGoalDefinition g){
final var builder = new CardinalityGoal.Builder()
.thereExistsOne(makeActivityTemplate(g.activityTemplate(), lookupActivityType))
.forAllTimeIn(hor)
.inPeriod(new TimeRangeExpression.Builder()
.from(new Windows(Window.betweenClosedOpen(g.inPeriod().start(), g.inPeriod().end())))
.build());
if(g.specification().duration().isPresent()){
builder.duration(Window.between(g.specification().duration().get(), Duration.MAX_VALUE));
}
if(g.specification().occurrence().isPresent()){
builder.occurences(new Range<>(g.specification().occurrence().get(), Integer.MAX_VALUE));
}
return builder.build();
} else {
throw new Error("Unhandled variant of GoalSpecifier:" + goalSpecifier);
}
Expand Down Expand Up @@ -135,8 +161,16 @@ private static Expression<Windows> expressionOfConstraintExpression(
private static ActivityCreationTemplate makeActivityTemplate(
final SchedulingDSL.ActivityTemplate activityTemplate,
final Function<String, ActivityType> lookupActivityType) {
var builder = new ActivityCreationTemplate.Builder()
.ofType(lookupActivityType.apply(activityTemplate.activityType()));
var builder = new ActivityCreationTemplate.Builder();
final var type = lookupActivityType.apply(activityTemplate.activityType());
if(type.getDurationType() instanceof DurationType.Controllable durationType){
//detect duration parameter
if(activityTemplate.arguments().containsKey(durationType.parameterName())){
builder.duration(new DurationValueMapper().deserializeValue(activityTemplate.arguments().get(durationType.parameterName())).getSuccessOrThrow());
activityTemplate.arguments().remove(durationType.parameterName());
}
}
builder = builder.ofType(type);
for (final var argument : activityTemplate.arguments().entrySet()) {
builder = builder.withArgument(argument.getKey(), argument.getValue());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package gov.nasa.jpl.aerie.scheduler.server.services;

import gov.nasa.jpl.aerie.contrib.serialization.mappers.DurationValueMapper;
import gov.nasa.jpl.aerie.json.BasicParsers;
import gov.nasa.jpl.aerie.merlin.driver.ActivityInstanceId;
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
import gov.nasa.jpl.aerie.merlin.protocol.types.DurationType;
import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue;
import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema;
import gov.nasa.jpl.aerie.scheduler.model.ActivityInstance;
Expand Down Expand Up @@ -318,6 +320,12 @@ public Map<ActivityInstance, ActivityInstanceId> updatePlanActivities(
for (final var activity : plan.getActivities()) {
final var idActFromInitialPlan = idsFromInitialPlan.get(activity.getId());
if (idActFromInitialPlan != null) {
//add duration to parameters if controllable
if(activity.getType().getDurationType() instanceof DurationType.Controllable durationType){
if(!activity.getArguments().containsKey(durationType.parameterName())){
activity.addArgument(durationType.parameterName(), new DurationValueMapper().serializeValue(activity.getDuration()));
}
}
final var actFromInitialPlan = initialPlan.getActivityById(idActFromInitialPlan);
//if act was present in initial plan
final var schedulerActIntoMerlinAct = new MerlinActivityInstance(activity.getType().getName(), activity.getStartTime(), activity.getArguments());
Expand Down Expand Up @@ -462,6 +470,12 @@ private Map<ActivityInstance, ActivityInstanceId> createActivities(final PlanId

for (final var act : orderedActivities) {
requestSB.append(actPre.formatted(planId.id(), act.getType().getName(), act.getStartTime().toString()));
//add duration to parameters if controllable
if(act.getType().getDurationType() instanceof DurationType.Controllable durationType){
if(!act.getArguments().containsKey(durationType.parameterName())){
requestSB.append(argFormat.formatted(durationType.parameterName(), getGraphQLValueString(act.getDuration())));
}
}
for (final var arg : act.getArguments().entrySet()) {
final var name = arg.getKey();
var value = getGraphQLValueString(arg.getValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
import gov.nasa.jpl.aerie.merlin.protocol.types.DurationType;
import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue;
import gov.nasa.jpl.aerie.scheduler.constraints.scheduling.BinaryMutexConstraint;
import gov.nasa.jpl.aerie.scheduler.constraints.scheduling.GlobalConstraint;
import gov.nasa.jpl.aerie.scheduler.goals.Goal;
import gov.nasa.jpl.aerie.scheduler.model.ActivityInstance;
Expand Down Expand Up @@ -112,6 +113,10 @@ public void schedule(final ScheduleRequest request, final ResultsProtocol.Writer

//apply constraints/goals to the problem
loadConstraints(planMetadata, schedulerMissionModel.missionModel()).forEach(problem::add);

//TODO: workaround to get the Cardinality goal working. To remove once we have global constraints in the eDSL
problem.getActivityTypes().forEach(at -> problem.add(BinaryMutexConstraint.buildMutexConstraint(at, at)));

final var orderedGoals = new ArrayList<Goal>();
final var goals = new HashMap<Goal, GoalId>();
for (final var goalRecord : specification.goalsByPriority()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package gov.nasa.jpl.aerie.scheduler.server.services;

import gov.nasa.jpl.aerie.contrib.serialization.mappers.DurationValueMapper;
import gov.nasa.jpl.aerie.merlin.driver.ActivityInstanceId;
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
import gov.nasa.jpl.aerie.merlin.protocol.types.DurationType;
import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue;
import gov.nasa.jpl.aerie.scheduler.TimeUtility;
import gov.nasa.jpl.aerie.scheduler.model.ActivityInstance;
Expand Down Expand Up @@ -163,9 +165,17 @@ record PlannedActivityInstance(String type, Map<String, SerializedValue> args, D
private static Collection<PlannedActivityInstance> extractPlannedActivityInstances(final Plan plan) {
final var plannedActivityInstances = new ArrayList<PlannedActivityInstance>();
for (final var activity : plan.getActivities()) {
final var type = activity.getType();
final var arguments = new HashMap<>(activity.getArguments());
if(type.getDurationType() instanceof DurationType.Controllable durationType){
//detect duration parameter and add it to parameters
if(!arguments.containsKey(durationType.parameterName())){
arguments.put(durationType.parameterName(), new DurationValueMapper().serializeValue(activity.getDuration()));
}
}
plannedActivityInstances.add(new PlannedActivityInstance(
activity.getType().getName(),
activity.getArguments(),
arguments,
activity.getStartTime()));
}
return plannedActivityInstances;
Expand Down
Loading

0 comments on commit 63efc3f

Please sign in to comment.