NASA-AMMOS / aerie

A software framework for modeling spacecraft.
https://nasa-ammos.github.io/aerie-docs/
MIT License
68 stars 19 forks source link

CoexistenceGoal based on Resource with negative startsAt condition throws NoSuchElementException when scheduling #1378

Closed pranav-super closed 2 months ago

pranav-super commented 6 months ago

Checked for duplicates

No - I haven't checked

Is this a regression?

No - This is a new bug

Version

2.5.0

Describe the bug

The bug occurs when a CoexistenceGoal with a forEach focused on a resource window, where the activityTemplate uses valueAts has a startsAt with a negative offset. A clearer example follows:

export default (): Goal => {
  var prePostPadding: Temporal.Duration = Temporal.Duration.from({ minutes: 5 });
  return Goal.CoexistenceGoal({ 
    forEach: Discrete.Resource("maneuvers_type").notEqual("NONE"), 
    activityTemplate: interval => {
      // start time
      var sp: Spans = Spans.FromInterval(interval)
      return ActivityTemplates.ManPrePost({
        estimatedDuration: Discrete.Resource("maneuvers_duration").valueAt(sp.starts()), 
        maneuverType: Discrete.Resource("maneuvers_type").valueAt(sp.starts()),
        additionalNotes: Discrete.Resource("maneuvers_additionalNotes").valueAt(sp.starts()), 
        startTime: Discrete.Resource("maneuvers_startTime").valueAt(sp.starts()),
        prePostPadding: prePostPadding 
      })
    },
    startsAt: TimingConstraint.singleton(WindowProperty.START).minus(prePostPadding)
  })
}

This goal works fine if the .minus in the startsAt is not present, or if it is but the forEach depends on an activity, like so:

export default (): Goal => {
  var prePostPadding: Temporal.Duration = Temporal.Duration.from({ minutes: 5 });

  return Goal.CoexistenceGoal({ 
    forEach: ActivityExpression.ofType(ActivityType.ManExec), 
    activityTemplate: activity => {
      return ActivityTemplates.ManPrePost({
        estimatedDuration: activity.parameters.estimatedDuration,
        maneuverType: activity.parameters.maneuverType,
        additionalNotes: activity.parameters.additionalNotes,
        startTime: activity.parameters.startTime,
        prePostPadding: prePostPadding 
      })
    },
    startsAt: TimingConstraint.singleton(WindowProperty.START).minus(prePostPadding)
  })
}

Reproduction

To reproduce this issue:

Logs

The stack trace:
java.util.NoSuchElementException
    at java.base/java.util.ArrayList$Itr.next(Unknown Source)
    at gov.nasa.jpl.aerie.constraints.tree.ValueAt.evaluate(ValueAt.java:31)
    at gov.nasa.jpl.aerie.constraints.tree.ValueAt.evaluate(ValueAt.java:17)
    at gov.nasa.jpl.aerie.constraints.tree.ProfileExpression.evaluate(ProfileExpression.java:20)
    at gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityDirective.lambda$instantiateArguments$1(SchedulingActivityDirective.java:272)
    at java.base/java.util.HashMap.forEach(Unknown Source)
    at gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityDirective.instantiateArguments(SchedulingActivityDirective.java:270)
    at gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityExpression.matches(ActivityExpression.java:342)
    at gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityExpression.matches(ActivityExpression.java:297)
    at gov.nasa.jpl.aerie.scheduler.model.PlanInMemory.find(PlanInMemory.java:177)
    at gov.nasa.jpl.aerie.scheduler.goals.CoexistenceGoal.getConflicts(CoexistenceGoal.java:243)
    at gov.nasa.jpl.aerie.scheduler.solver.PrioritySolver.getConflicts(PrioritySolver.java:709)
    at gov.nasa.jpl.aerie.scheduler.solver.PrioritySolver.satisfyGoalGeneral(PrioritySolver.java:591)
    at gov.nasa.jpl.aerie.scheduler.solver.PrioritySolver.satisfyGoal(PrioritySolver.java:434)
    at gov.nasa.jpl.aerie.scheduler.solver.PrioritySolver.solve(PrioritySolver.java:390)
    at gov.nasa.jpl.aerie.scheduler.solver.PrioritySolver.getNextSolution(PrioritySolver.java:177)
    at gov.nasa.jpl.aerie.scheduler.worker.services.SynchronousSchedulerAgent.schedule(SynchronousSchedulerAgent.java:224)
    at gov.nasa.jpl.aerie.scheduler.worker.SchedulerWorkerAppDriver.main(SchedulerWorkerAppDriver.java:100)

System Info

Firefox Version 115.9.0esr (64-bit)
MacOS Ventura (13.6.5)

Severity

Minor

adrienmaillard commented 3 months ago

edit: code formatting for readability

adrienmaillard commented 3 months ago

reproduced with integration test

@Test
  void testBugValueAt() {
    final var growBananaDuration = Duration.of(1, Duration.HOUR);
    final var results = runScheduler(
        BANANANATION,
        List.of(new ActivityDirective(
                    Duration.HOUR,
                    "GrowBanana",
                    Map.of(
                        "quantity", SerializedValue.of(3),
                        "growingDuration", SerializedValue.of(growBananaDuration.in(Duration.MICROSECONDS))),
                    null,
                    true),
                new ActivityDirective(
                    Duration.MINUTE.times(55),
                    "ChangeProducer",
                    Map.of(
                        "producer", SerializedValue.of("Company")),
                    null,
                    true)),
        List.of(new SchedulingGoal(new GoalId(0L, 0L), """
          export default () => Goal.CoexistenceGoal({
            forEach: Real.Resource("/fruit").greaterThan(4.0),
            activityTemplate: interval => ActivityTemplates.ChangeProducer({producer: Discrete.Resource("/producer").valueAt(Spans.FromInterval(interval).starts())}),
            startsAt: TimingConstraint.singleton(WindowProperty.START).minus(Temporal.Duration.from({ minutes : 5}))
          })
          """, true)),
        PLANNING_HORIZON);
  }