From dc0dbe870094752e417d4aa7fce4d8fd9be6c468 Mon Sep 17 00:00:00 2001 From: psubram3 Date: Thu, 24 Oct 2024 10:22:58 -0700 Subject: [PATCH 1/4] Procedural scheduling with events docs --- .../procedural/getting-started.mdx | 2 +- .../procedural/parameters-and-invocations.mdx | 2 +- .../scheduling/assets/example-events.png | 3 + .../procedural/scheduling/assets/results.png | 3 + .../procedural/scheduling/examples.mdx | 186 ++++++++++++++++++ .../introduction.mdx} | 2 +- .../timelines/basics/external-events.mdx | 88 +++++++++ sidebars.js | 13 +- 8 files changed, 295 insertions(+), 4 deletions(-) create mode 100644 docs/scheduling-and-constraints/procedural/scheduling/assets/example-events.png create mode 100644 docs/scheduling-and-constraints/procedural/scheduling/assets/results.png create mode 100644 docs/scheduling-and-constraints/procedural/scheduling/examples.mdx rename docs/scheduling-and-constraints/procedural/{scheduling.mdx => scheduling/introduction.mdx} (98%) create mode 100644 docs/scheduling-and-constraints/procedural/timelines/basics/external-events.mdx diff --git a/docs/scheduling-and-constraints/procedural/getting-started.mdx b/docs/scheduling-and-constraints/procedural/getting-started.mdx index 4cae359..3fd3e16 100644 --- a/docs/scheduling-and-constraints/procedural/getting-started.mdx +++ b/docs/scheduling-and-constraints/procedural/getting-started.mdx @@ -24,4 +24,4 @@ There should now be one jar for each scheduling procedure, at `scheduling/build/ ## Creating a Goal -See the examples in the mission model template repo, or see [the scheduling page](../scheduling) in this section. +See the examples in the mission model template repo, or see [the scheduling page](../scheduling/introduction/) in this section. diff --git a/docs/scheduling-and-constraints/procedural/parameters-and-invocations.mdx b/docs/scheduling-and-constraints/procedural/parameters-and-invocations.mdx index 002180e..284d047 100644 --- a/docs/scheduling-and-constraints/procedural/parameters-and-invocations.mdx +++ b/docs/scheduling-and-constraints/procedural/parameters-and-invocations.mdx @@ -15,7 +15,7 @@ For now, only Java records are supported for parameterization. ::: -For example, we can take the example from the [scheduling](../scheduling) page, and parameterize it so that it doesn't +For example, we can take the example from the [scheduling](../scheduling/introduction/) page, and parameterize it so that it doesn't unconditionally recur every hour, and instead takes the period as input: diff --git a/docs/scheduling-and-constraints/procedural/scheduling/assets/example-events.png b/docs/scheduling-and-constraints/procedural/scheduling/assets/example-events.png new file mode 100644 index 0000000..8cbd137 --- /dev/null +++ b/docs/scheduling-and-constraints/procedural/scheduling/assets/example-events.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c2fb8d792249da565420fe9403fcd329acf62b0399ab8f1082d4c0f03f48a81 +size 138619 diff --git a/docs/scheduling-and-constraints/procedural/scheduling/assets/results.png b/docs/scheduling-and-constraints/procedural/scheduling/assets/results.png new file mode 100644 index 0000000..158a8e6 --- /dev/null +++ b/docs/scheduling-and-constraints/procedural/scheduling/assets/results.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c945e3697b04bb029573f5f8c0ad8464acf1ea2ae1544d68a2e1ac4cdbc6735c +size 87408 diff --git a/docs/scheduling-and-constraints/procedural/scheduling/examples.mdx b/docs/scheduling-and-constraints/procedural/scheduling/examples.mdx new file mode 100644 index 0000000..866dd9c --- /dev/null +++ b/docs/scheduling-and-constraints/procedural/scheduling/examples.mdx @@ -0,0 +1,186 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Examples + +This page details various examples of scheduling procedures to further illustrate how the aforementioned concepts +can be used. + +## Scheduling a Recurring Activity + +A recurrence goal specifies that a certain activity should occur repeatedly throughout the plan at some given interval. +An example of this can be seen in the description of [Activity Recurrence Goal](../../../declarative/scheduling/goals/#activity-recurrence-goal). + +We will replicate the second, more intricate goal. This places an activity of type `GrowBanana` every two hours, +unless an equivalent activity is already happening at the same time. + +```java +@SchedulingProcedure +public record ActivityRecurrenceGoal() implements Goal { + @Override + public void run(@NotNull final EditablePlan plan) { + // grab the current set of activities, and filter by those of type GrowBanana and duration 1 hour + var existingActivities = plan.directives("GrowBanana") + .filter(false, a -> a.inner.arguments.get("growingDuration").asInt().get() == 1) + .active().cache(); + + int count = 1; + for (final var time: plan.totalBounds().step(Duration.hours(2))) { + if (!existingActivities.sample(time)) { + plan.create( + new NewDirective( + // parameters + new AnyDirective(Map.of( + "growingDuration", BasicValueMappers.duration().serializeValue(Duration.of(1, Duration.HOUR)), + "quantity", SerializedValue.of(1) + ) + ), + // name + "GrowBanana" + count++, + // type + "GrowBanana", + // start time + new DirectiveStart.Absolute(time) + ) + ); + } + } + + plan.commit(); + } +} +``` + +## Scheduling From Profiles + +Next we'll show how to schedule from resource profile conditions, much like a [Coexistence Goal](../../../declarative/scheduling/goals/#coexistence-goal) +from the declarative scheduler. This goal places an activity 5 minutes after the end of each where `/fruit` is equal to `4.0`. + +```java +@SchedulingProcedure +public record ResourceCoexistenceGoal() implements Goal { + @Override + public void run(@NotNull final EditablePlan plan) { + // Get the latest results, or simulate if they don't exist. + var simResults = plan.latestResults(); + if (simResults == null) simResults = plan.simulate(); + + // access the resource + final var fruitResource = simResults.resource("/fruit", Numbers.deserializer()); + for (final var interval: fruitResource.equalTo(4.0).highlightTrue()) { + // place the activity off of the window that the resource access created + plan.create( + "PeelBanana", + new DirectiveStart.Absolute(interval.start.plus(Duration.minutes(5))), + Map.of("peelDirection", SerializedValue.of("fromStem")) + ); + } + plan.commit(); + } +} +``` + +An implementation of a procedure that schedules based off of an external profile is almost exactly the same, except +the resource is queried using `plan.resource("/my_resource")` instead of `simResults.resource("/my_resource")`. +In those cases, if you don't use any simulated resources, there's no need to get a sim results object. + +## Scheduling From Activities + +We can also schedule relative to other activities, and place new activities that are anchored to them. This is a slightly +more complex example to show how to create activities to fix a problematic resource condition caused by other activities. +We look for `BiteBanana` activities, and check if those activities lower the `/fruit` resource below zero. If so, +we create a `GrowBanana` activity that replenishes the resource back up to zero. Lastly, we mock the effect the new +activity would have on the `/fruit` resource, rather than resimulating a potentially expensive mission model. + +```java +@SchedulingProcedure +public record ActivityCoexistenceGoal() implements Goal { + @Override + public void run(@NotNull final EditablePlan plan) { + // make sure we have up-to-date results to start with + final var simResults = plan.simulate(); + + var mockedFruit = simResults.resource("/fruit", Real.deserializer()).cache(); + + for (final var biteInstance: simResults.instances("BiteBanana")) { + final var biteInterval = biteInstance.getInterval(); + final var fruitLevel = mockedFruit.sample(biteInterval.end); + if (fruitLevel < 0.0) { + final var growQuantity = Math.ceil(-fruitLevel); + plan.create( + "GrowBanana", + new DirectiveStart.Anchor(biteInstance.directiveId, Duration.HOUR.negate(), DirectiveStart.Anchor.AnchorPoint.Start), + Map.of( + "growingDuration", SerializedValue.of(Duration.HOUR.micros()), + "quantity", SerializedValue.of(growQuantity) + ) + ); + mockedFruit = mockedFruit.plus(Real.step(biteInterval.start, growQuantity)); + } + } + + plan.commit(); + } +} +``` + +## Scheduling Based off of External Events + +Consider this set of events: + +import exampleEvents from './assets/example-events.png'; + +
+ A drawing of a timeline depicting two sources in different derivation groups, each with 3 non-overlapping events inside of them. +
Figure 1: The events that we will write a procedure to schedule off of.
+
+ +Consider scheduling the Banananation activity `BiteBanana` off of these events. +This can mean a variety of different things, depending on the exact behavior we desire: +- schedule the activity coincident with all events +- schedule the activity coincident with events belonging to derivation group `"TestGroup"` +- schedule the activity coincident with events belonging to the second source +- schedule the activity based on events of type `"Test_Type"` +- schedule the activity based on events with the substring `"01"` in their key. + +We will just show one case - events belonging to the second source with the substring `"01"` in their key. + +```java +@SchedulingProcedure +public record ExternalEventsSourceQueryGoal() implements Goal { + @Override + public void run(@NotNull final EditablePlan plan) { + + // extract events belonging to the second source + EventQuery eventQuery = new EventQuery( + null, + null, + new ExternalSource("NewTest.json", "TestGroup_2") + ); + + final var events = plan.events(eventQuery).filter(false, $ -> $.key.contains("01")); + + for (final var e: events) { + plan.create( + "BiteBanana", + // place the directive such that it is coincident with the event's start + new DirectiveStart.Absolute(e.getInterval().start), + Map.of("biteSize", SerializedValue.of(1)) + ); + } + + plan.commit(); + } +} +``` + +After running it, we get the following result in AERIE: + +import results from './assets/results.png'; + +
+ The results of scheduling off of external events. One should be scheduled off of an event belonging to the second source, of event type `TestType`. +
Figure 2: The results of the described procedure.
+
+ +As expected, there is a single activity, scheduled off of an `Event_01` from the second source. diff --git a/docs/scheduling-and-constraints/procedural/scheduling.mdx b/docs/scheduling-and-constraints/procedural/scheduling/introduction.mdx similarity index 98% rename from docs/scheduling-and-constraints/procedural/scheduling.mdx rename to docs/scheduling-and-constraints/procedural/scheduling/introduction.mdx index f4a9990..16fc4d0 100644 --- a/docs/scheduling-and-constraints/procedural/scheduling.mdx +++ b/docs/scheduling-and-constraints/procedural/scheduling/introduction.mdx @@ -8,7 +8,7 @@ goals fix them. You do that by implement the `Goal` interface and interacting wi ## `EditablePlan` -The `EditablePlan` gives you the same interface as [`Plan`](../plan-and-sim-results), but also allows you to add new +The `EditablePlan` gives you the same interface as [`Plan`](../../plan-and-sim-results), but also allows you to add new activities. You do this with `plan.create(...)`. You can either provide the minimal amount of information (type, arguments, and start time), or provide a `NewDirective` object that is a little more configurable. diff --git a/docs/scheduling-and-constraints/procedural/timelines/basics/external-events.mdx b/docs/scheduling-and-constraints/procedural/timelines/basics/external-events.mdx new file mode 100644 index 0000000..0cc368e --- /dev/null +++ b/docs/scheduling-and-constraints/procedural/timelines/basics/external-events.mdx @@ -0,0 +1,88 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# External Events + +External Events timelines are analogous to activity `Instances` timelines. They represent intervals with various properties that +are allowed to overlap in time, even if of the same type. It is possible to access the events associated with +a plan (specifically, the events that are members of derivation groups that are associated with a plan - see +[Associating Derivation Groups](../../../../../tutorials/external-events/associating-derivation-groups)). + +A timeline of External Events is represented by the `ExternalEvents` class, which contains objects of the `ExternalEvent` +type. `ExternalEvent` objects have a `type`, a `source`, and an event `key` (which is unique within the `type` and `source`). +The `source` contains its own key and also refers to the derivation group that it is a part of. + +Unlike `Instances`, external events currently don't have arguments or properties, so filtering by any other +attributes is not yet possible. + +You can access external events using `plan.events()`. This will return all events associated with the plan; you can filter +the result afterward or you can provide a more specific query to the events function. + +## Filtering Events + +You can filter events either with the general-purpose `filter` method, or with the specific `filterBySource`, `filterByType`, +and `filterByDerivationGroup` methods. + + + + +```kotlin +// These are equivalent +plan.events().filter { it.type.equals("MyEventType") } +plan.events().filterByType("MyEventType") +``` + + + + +```java +// These are equivalent +plan.events().filter(false, $ -> $.type.equals("MyEventType")); +plan.events().filterByType("MyEventType"); +``` + + + + +Similar operations exist for filtering by derivation group and source (which is a combination of source name and derivation group). +All of them accept varargs arguments (i.e. `.filterByType("TypeA", "TypeB")`) so you can filter for the union of multiple +types/sources/etc. + +Lastly, you can filter by derivation group using the shorthand `plan.events("my derivation group")`. + +## `EventQuery` + +The `EventQuery` type is a more flexible way to express multiple filters, and the union of filters, at the same time. +For example if you wanted the events of a specific type, but from multiple derivation groups, you could express it like +this: + + + + +```kotlin +val query = EventQuery( + listOf("DerivationGroupA", "DerivationGroupB"), // both derivation groups are accepted + listOf("MyType"), // just one type is accepted + null // null indicates all sources are accepted +) +plan.events(query) +``` + + + + +```java +final var query = new EventQuery( + List.of("DerivationGroupA", "DerivationGroupB"), // both derivation groups are accepted + List.of("MyType"), // just one type is accepted + null // null indicates all sources are accepted +); +plan.events(query); +``` + + + + +Passing `null` means that all are accepted. Some, but not all, implementations of the `Plan` interface may use the query +object to selectively load only the events you want, improving performance. You could achieve with `plan.events().filterBy...`, +but since the chain started with an unfiltered query, all events will be loaded and then filtered out. diff --git a/sidebars.js b/sidebars.js index d337ef4..ece7eb7 100644 --- a/sidebars.js +++ b/sidebars.js @@ -221,6 +221,7 @@ const sidebars = { 'scheduling-and-constraints/procedural/timelines/basics/profiles', 'scheduling-and-constraints/procedural/timelines/basics/sampling-and-caching', 'scheduling-and-constraints/procedural/timelines/basics/activities', + 'scheduling-and-constraints/procedural/timelines/basics/external-events', 'scheduling-and-constraints/procedural/timelines/basics/windows', 'scheduling-and-constraints/procedural/timelines/basics/common-operations' ] @@ -242,7 +243,17 @@ const sidebars = { }, 'scheduling-and-constraints/procedural/plan-and-sim-results', 'scheduling-and-constraints/procedural/constraints', - 'scheduling-and-constraints/procedural/scheduling', + { + type: 'category', + label: 'Scheduling', + link: { + id: 'scheduling-and-constraints/procedural/scheduling/introduction', + type: 'doc' + }, + items: [ + 'scheduling-and-constraints/procedural/scheduling/examples' + ] + }, 'scheduling-and-constraints/procedural/parameters-and-invocations', // 'scheduling-and-constraints/procedural/running-externally' ] From 1a6b2e3f0e42d56728471454fb236e919a57f623 Mon Sep 17 00:00:00 2001 From: dandelany Date: Tue, 5 Nov 2024 16:29:58 -0800 Subject: [PATCH 2/4] small edits to events docs: comments on examples, update External Events page to link to scheduling docs and remove from 'coming soon' section --- docs/planning/external-events.mdx | 21 +++++++------------ .../procedural/scheduling/examples.mdx | 6 ++++-- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/docs/planning/external-events.mdx b/docs/planning/external-events.mdx index 4a3871c..52b3df9 100644 --- a/docs/planning/external-events.mdx +++ b/docs/planning/external-events.mdx @@ -531,6 +531,13 @@ As a final note on the frontend, it is worth briefly detailing what parts of the - _External Event Type filters_ - the External Event Type filters selected in the timeline editor, - _External Event options_ - the options in the timeline editor filter. +## Scheduling Activities from Events +External events associated with a plan are accessible from the [Procedural Scheduling](../../scheduling-and-constraints/procedural/introduction/) API, just like resources and activities. This allows users to write scheduling goals which create activities based on the presence (or absence) of external events - for example, "create a downlink activity 5 minutes after the start of every 'DSN Contact' type event". + +To access events in a procedural goal, you must create an External Events timeline object - see [Timelines: External Events](../../scheduling-and-constraints/procedural/timelines/basics/external-events/). A full example of a scheduling goal using events can be found on the [Procedural Scheduling Examples](../../scheduling-and-constraints/procedural/scheduling/examples/#scheduling-based-off-of-external-events) page. + +Currently, external events are **not accessible from the [Declarative Scheduling EDSL](http://localhost:3000/aerie-docs/scheduling-and-constraints/declarative/scheduling/introduction/)** - but we may implement support for this in the future. + ## What Remains Here, we discuss briefly everything that is not currently implemented but that we do plan to in the future. @@ -554,17 +561,3 @@ It might make sense to add ownership, i.e. a given user owns an External Source That being said, as we move forward with more features relating to External Events, more nuance may come to the problem of ownership and roles. Once plans start actually depending on these plans, the rules surrounding ownership and permissions may need to change or be entirely rethought. Ultimately, we need something that justifies the implementation of such a scheme in that it provides more than the status quo. -### Scheduling/Constraints - -This is the biggest "coming-next" feature, but also the least explored. It is planned to be part of the next batch of work relating to External Events. We would like External Events to get the same visibility that activities and resources get in the scheduling and constraints EDSLs and engines. Ideally, we could define a scheduling goal (or constraint), like the following: - -``` -export default () => - Goal.CoexistenceGoal({ - forEach: ExternalEventExpression.ofType(ExternalEventTypes.Eclipse), - activityTemplate: ActivityTemplates.SolarDisable({ rate: 0.5 }), - startsAt: TimingConstraint.singleton(WindowProperty.START) - }); -``` - -which might be how one could write a scheduling goal to place an activity disabling a solar panel whenever an eclipse occurred. diff --git a/docs/scheduling-and-constraints/procedural/scheduling/examples.mdx b/docs/scheduling-and-constraints/procedural/scheduling/examples.mdx index 866dd9c..53afd76 100644 --- a/docs/scheduling-and-constraints/procedural/scheduling/examples.mdx +++ b/docs/scheduling-and-constraints/procedural/scheduling/examples.mdx @@ -28,6 +28,7 @@ public record ActivityRecurrenceGoal() implements Goal { for (final var time: plan.totalBounds().step(Duration.hours(2))) { if (!existingActivities.sample(time)) { plan.create( + // use NewDirective() to specify names/arguments for activities new NewDirective( // parameters new AnyDirective(Map.of( @@ -115,6 +116,7 @@ public record ActivityCoexistenceGoal() implements Goal { "quantity", SerializedValue.of(growQuantity) ) ); + // mock the effect of new activity on fruit resource, so we don't need to resimulate mockedFruit = mockedFruit.plus(Real.step(biteInterval.start, growQuantity)); } } @@ -124,7 +126,7 @@ public record ActivityCoexistenceGoal() implements Goal { } ``` -## Scheduling Based off of External Events +## Scheduling From External Events Consider this set of events: @@ -157,7 +159,7 @@ public record ExternalEventsSourceQueryGoal() implements Goal { null, new ExternalSource("NewTest.json", "TestGroup_2") ); - + // look for events where key contains "01" final var events = plan.events(eventQuery).filter(false, $ -> $.key.contains("01")); for (final var e: events) { From 63231678913f0e66b1820708890a0e6881754648 Mon Sep 17 00:00:00 2001 From: dandelany Date: Tue, 5 Nov 2024 16:34:05 -0800 Subject: [PATCH 3/4] link to External Events Timelines from scheduling example --- .../procedural/scheduling/examples.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/scheduling-and-constraints/procedural/scheduling/examples.mdx b/docs/scheduling-and-constraints/procedural/scheduling/examples.mdx index 53afd76..2724bd0 100644 --- a/docs/scheduling-and-constraints/procedural/scheduling/examples.mdx +++ b/docs/scheduling-and-constraints/procedural/scheduling/examples.mdx @@ -145,7 +145,7 @@ This can mean a variety of different things, depending on the exact behavior we - schedule the activity based on events of type `"Test_Type"` - schedule the activity based on events with the substring `"01"` in their key. -We will just show one case - events belonging to the second source with the substring `"01"` in their key. +We will just show one case - events belonging to the second source with the substring `"01"` in their key. The event query and subsequent call to `plan.events()` in this example creates an `ExternalEvents` timeline object - see [Timelines: External Events](../../scheduling-and-constraints/procedural/timelines/basics/external-events/) for more information. ```java @SchedulingProcedure From df393bd8bc56288fa0f553225effb9210fa9c875 Mon Sep 17 00:00:00 2001 From: dandelany Date: Tue, 5 Nov 2024 16:39:29 -0800 Subject: [PATCH 4/4] fix broken links --- .../procedural/scheduling/examples.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/scheduling-and-constraints/procedural/scheduling/examples.mdx b/docs/scheduling-and-constraints/procedural/scheduling/examples.mdx index 2724bd0..7fa99cb 100644 --- a/docs/scheduling-and-constraints/procedural/scheduling/examples.mdx +++ b/docs/scheduling-and-constraints/procedural/scheduling/examples.mdx @@ -145,7 +145,7 @@ This can mean a variety of different things, depending on the exact behavior we - schedule the activity based on events of type `"Test_Type"` - schedule the activity based on events with the substring `"01"` in their key. -We will just show one case - events belonging to the second source with the substring `"01"` in their key. The event query and subsequent call to `plan.events()` in this example creates an `ExternalEvents` timeline object - see [Timelines: External Events](../../scheduling-and-constraints/procedural/timelines/basics/external-events/) for more information. +We will just show one case - events belonging to the second source with the substring `"01"` in their key. The event query and subsequent call to `plan.events()` in this example creates an `ExternalEvents` timeline object - see [Timelines: External Events](../../timelines/basics/external-events/) for more information. ```java @SchedulingProcedure