From dc0dbe870094752e417d4aa7fce4d8fd9be6c468 Mon Sep 17 00:00:00 2001 From: psubram3 Date: Thu, 24 Oct 2024 10:22:58 -0700 Subject: [PATCH] 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' ]