-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Procedural scheduling with events docs
- Loading branch information
1 parent
3fe252a
commit dc0dbe8
Showing
8 changed files
with
295 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 3 additions & 0 deletions
3
docs/scheduling-and-constraints/procedural/scheduling/assets/example-events.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions
3
docs/scheduling-and-constraints/procedural/scheduling/assets/results.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
186 changes: 186 additions & 0 deletions
186
docs/scheduling-and-constraints/procedural/scheduling/examples.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'; | ||
|
||
<figure> | ||
<img alt="A drawing of a timeline depicting two sources in different derivation groups, each with 3 non-overlapping events inside of them." src={exampleEvents} /> | ||
<figcaption>Figure 1: The events that we will write a procedure to schedule off of.</figcaption> | ||
</figure> | ||
|
||
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'; | ||
|
||
<figure> | ||
<img alt="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`." src={results} /> | ||
<figcaption>Figure 2: The results of the described procedure.</figcaption> | ||
</figure> | ||
|
||
As expected, there is a single activity, scheduled off of an `Event_01` from the second source. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
docs/scheduling-and-constraints/procedural/timelines/basics/external-events.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
|
||
<Tabs groupId="lang"> | ||
<TabItem value="kt" label="Kotlin"> | ||
|
||
```kotlin | ||
// These are equivalent | ||
plan.events().filter { it.type.equals("MyEventType") } | ||
plan.events().filterByType("MyEventType") | ||
``` | ||
|
||
</TabItem> | ||
<TabItem value="java" label="Java"> | ||
|
||
```java | ||
// These are equivalent | ||
plan.events().filter(false, $ -> $.type.equals("MyEventType")); | ||
plan.events().filterByType("MyEventType"); | ||
``` | ||
|
||
</TabItem> | ||
</Tabs> | ||
|
||
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: | ||
|
||
<Tabs groupId="lang"> | ||
<TabItem value="kt" label="Kotlin"> | ||
|
||
```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) | ||
``` | ||
|
||
</TabItem> | ||
<TabItem value="java" label="Java"> | ||
|
||
```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); | ||
``` | ||
|
||
</TabItem> | ||
</Tabs> | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters