Repository: nasa-ammos/aerie
Issue: Cardinality goal does not respect applyWhen

I want to write a cardinality goal that is only able to satisfy itself with / create activity instances within a given time bounds specified in an `applyWhen` clause. Currently, this goal finds all of activity instances in the plan even if they are very far from the specified window.

```ts
export default (): Goal => {
  return Goal.CardinalityGoal({
    activityTemplate: ActivityTemplates.ResumeBG(),
    specification: { occurrence: 1 },
  }).applyWhen(
    Windows.All(
      Real.Resource('/time/clock.seconds').greaterThan(0),
      Real.Resource('/time/clock.seconds').lessThan(3600),
    ),
  );
};
```

Internal ticket: https://jira.jpl.nasa.gov/browse/AERIEQS-355

Changed files:
scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/constraints/activities/ActivityExpression.java
scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/PrioritySolverTest.java
scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/CardinalityGoal.java

diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/constraints/activities/ActivityExpression.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/constraints/activities/ActivityExpression.java
index b98ca05fd..ed2ef2564 100644
--- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/constraints/activities/ActivityExpression.java
+++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/constraints/activities/ActivityExpression.java
@@ -334,6 +334,7 @@ public class ActivityExpression implements Expression<Spans> {
       endsIn = template.endRange;
       durationIn = template.durationRange;
       startsOrEndsIn = template.startOrEndRange;
+      startsOrEndsInW = template.startOrEndRangeW;
       arguments = template.arguments;
       return getThis();
     }
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 5c26028c3..e8d2decc3 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
@@ -6,6 +6,7 @@ import gov.nasa.jpl.aerie.constraints.time.Interval;
 import gov.nasa.jpl.aerie.constraints.time.Windows;
 import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
 import gov.nasa.jpl.aerie.scheduler.conflicts.UnsatisfiableGoalConflict;
+import gov.nasa.jpl.aerie.scheduler.constraints.activities.ActivityExpression;
 import gov.nasa.jpl.aerie.scheduler.model.SchedulingActivityInstanceId;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -161,8 +162,8 @@ public class CardinalityGoal extends ActivityTemplateGoal {
 
     for(Interval subInterval : windows.iterateEqualTo(true)) {
       final var subIntervalWindows = new Windows(false).set(subInterval, true);
-      ActivityCreationTemplate actTB =
-          new ActivityCreationTemplate.Builder().basedOn(this.desiredActTemplate).startsOrEndsIn(subIntervalWindows).build();
+      final var actTB =
+          new ActivityExpression.Builder().basedOn(this.desiredActTemplate).startsOrEndsIn(subIntervalWindows).build();
 
       final var acts = new LinkedList<>(plan.find(actTB, simulationResults, new EvaluationEnvironment()));
       acts.sort(Comparator.comparing(ActivityInstance::startTime));
diff --git a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/PrioritySolverTest.java b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/PrioritySolverTest.java
index 00f76bd15..f5c484874 100644
--- a/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/PrioritySolverTest.java
+++ b/scheduler-driver/src/test/java/gov/nasa/jpl/aerie/scheduler/PrioritySolverTest.java
@@ -2,12 +2,15 @@ package gov.nasa.jpl.aerie.scheduler;
 
 import com.google.common.testing.NullPointerTester;
 import com.google.common.truth.Correspondence;
+import gov.nasa.jpl.aerie.constraints.time.Interval;
 import gov.nasa.jpl.aerie.constraints.time.Windows;
 import gov.nasa.jpl.aerie.constraints.tree.WindowsWrapperExpression;
 import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
 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.ChildCustody;
 import gov.nasa.jpl.aerie.scheduler.goals.CoexistenceGoal;
 import gov.nasa.jpl.aerie.scheduler.goals.ProceduralCreationGoal;
 import gov.nasa.jpl.aerie.scheduler.goals.RecurrenceGoal;
@@ -228,4 +231,46 @@ public class PrioritySolverTest {
         .containsAtLeastElementsIn(expectedPlan.getActivitiesByTime());
   }
 
+
+  @Test
+  public void testCardGoalWithApplyWhen(){
+    var planningHorizon = h;
+
+    final var fooMissionModel = SimulationUtility.getFooMissionModel();
+    Problem problem = new Problem(fooMissionModel, planningHorizon, new SimulationFacade(
+        planningHorizon,
+        fooMissionModel), SimulationUtility.getFooSchedulerModel());
+    final var activityType = problem.getActivityType("ControllableDurationActivity");
+
+    //act at t=1hr and at t=2hrs
+    problem.setInitialPlan(makePlanA12(problem));
+
+    //and the goal window in between [0, 50 min[ so the activities already present in the plan can't count towards satisfying the goal
+    final var goalWindow = new Windows(false).set(List.of(
+        Interval.between(Duration.of(0, Duration.SECONDS), Duration.of(50, Duration.MINUTE))
+    ), true);
+
+    CardinalityGoal cardGoal = new CardinalityGoal.Builder()
+        .duration(Interval.between(Duration.of(2, Duration.SECONDS), Duration.of(65, Duration.HOUR)))
+        .occurences(new Range<>(1, 3))
+        .thereExistsOne(new ActivityCreationTemplate.Builder()
+                            .ofType(problem.getActivityType("ControllableDurationActivity"))
+                            .duration(d1min)
+                            .build())
+        .named("TestCardGoal")
+        .forAllTimeIn(new WindowsWrapperExpression(goalWindow))
+        .owned(ChildCustody.Jointly)
+        .build();
+
+    TestUtility.createAutoMutexGlobalSchedulingCondition(activityType).forEach(problem::add);
+    problem.setGoals(List.of(cardGoal));
+    final var solver = new PrioritySolver(problem);
+
+    var plan = solver.getNextSolution().orElseThrow();
+    //will insert an activity at the beginning of the plan in addition of the two already-present activities
+    assertThat(plan.getActivities().size()).isEqualTo(3);
+  }
+
+
+
 }