Skip to content

Commit

Permalink
Procedural scheduling docs
Browse files Browse the repository at this point in the history
  • Loading branch information
JoelCourtney committed Sep 6, 2024
1 parent 9d88646 commit 67184a2
Show file tree
Hide file tree
Showing 55 changed files with 1,333 additions and 65 deletions.
4 changes: 2 additions & 2 deletions docs/overview/concept-of-operations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ Aerie is designed to address the following capability gaps:
- Allow missions to provide a web deployment of a planning tool such that operators with limited programming skills can create valid activity plans and run simulations
- Support real time collaboration and and parallel hypothesis testing such that many more iterations of an activity plan can be tested within the same time frame
- Allow automation and manual modifications to coexist throughout the planning process
- Provide a low-code constraint checking mechanism to validate simulation outputs which can be largely automated
- Provide a low-code scheduling mechanism that can scaffold parts of or generate complete activity plans according to goal snippets
- Provide a flexible constraint checking mechanism to validate simulation outputs which can be largely automated
- Provide a flexible scheduling mechanism that can scaffold parts of or generate complete activity plans according to goal snippets
- Support an easy-to-use and verified translation from activities to sequences of commands that are recognized by the flight system

Note that none of the capabilities above are completely new. Other software solutions have offered pieces of the listed capabilities in different or more limited flavors. Aerie's mission is to cover as many key steps in the whole activity planning and sequencing workflow in one deployed tool with dedicated components.
Expand Down
4 changes: 2 additions & 2 deletions docs/overview/software-design-document.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ DSL, Merlin has little to no ability to see the actual Java code
comprising a mission model. Merlin must instead make inferences about
the mission model based on its observable behavior.

Merlin is a spiritual successor to the
Merlin is a spiritual successor to the
[Blackbird](https://trs.jpl.nasa.gov/handle/2014/52245) planning
system, which similarly uses Java for activity and resource modeling.
Blackbird's design shed light on the myriad choices made in designing
Expand Down Expand Up @@ -1225,7 +1225,7 @@ constraint expression operators, while the leaf nodes represent the
operands. For example, operator nodes enable expressions such as 'or',
'and', 'less than', 'greater than', while the operands are simulated
resource profiles and activity instances. See our [constraints
documentation](../../constraints/examples) for complete examples.
documentation](../../scheduling-and-constraints/edsl/constraints/examples) for complete examples.

## Meta-Programming (Annotations Processing)

Expand Down
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes.
Empty file.
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Scheduling

This guide explains how to use the scheduling service with the latest version of Aerie.
This guide explains how to use the declarative scheduling service with Aerie.
The scheduling service allows you to add activities to a plan based on goals that you define (this is something called "goal based scheduling").
Goals are defined in [TypeScript](https://www.typescriptlang.org/) using an embedded domain specific language (EDSL) provided by Aerie.
Declarative Goals are defined in [TypeScript](https://www.typescriptlang.org/) using an embedded domain specific language (EDSL) provided by Aerie.

:::note

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,35 +1,11 @@
# Run Scheduling
# Execution

## Scheduling Specification
## Constraints

After you have created one or several goals, you will see them in the scheduling pane of the plan in Aerie UI.
Constraints can be checked inside the main plan view by opening the `Constraints` pane and clicking the checklist icon
in the top right of the pane. Alternatively, you can hover over `Constraints` in the top bar and click "Check Constraints".

The Aerie scheduler accepts a list of goals, and tries to satisfy them one by one by adding activities to your plan. We refer to this list of goals as a **scheduling specification**. Aerie creates one scheduling specification per plan. A goal's priority is a number reflecting that goal's position in the scheduling specification. The first goal will always have priority `0`, and the n-th goal will always have priority `n - 1`. Within a scheduling specification, a scheduling goal can be toggled enabled or disabled. A disabled scheduling goal will be excluded from scheduling execution. The priority ordering of the scheduling goals remains unchanged whether a goal is enabled or disabled.

import schedulingSpecification from './assets/scheduling-specification.png';

<figure>
<img alt="Aerie UI - Scheduling Specification" src={schedulingSpecification} />
<figcaption>Figure 1: Aerie UI Scheduling Specification</figcaption>
</figure>

In this image, you can see a specification with three goals. Goal 1 has priority `0`, and goal 3 has priority `2`. You can modify the priorities directly by typing numbers or use the arrows. You can disable a goal by toggling it off with the checkbox.

:::caution

You must use priorities in the `[0, n-1]` range, `n` being the number of goals.

:::

:::caution

A given goal may be a part of zero or one specification - goals may not be shared between multiple specifications. If you need to do this, make a copy of the goal.

There may be at most one specification at a time associated with a given plan.

:::

## Running the Scheduler
## Scheduling

To run the scheduler, click on the play button:

Expand Down Expand Up @@ -71,7 +47,7 @@ import schedulingSuccess from './assets/scheduling-success.png';
- `174` means that there are `174` activity directives that contribute to the satisfaction of the goal
- `+3` means that 3 new activities have been inserted in the plan to satisfied the goal during the last scheduling run

## Running a Scheduling Analysis
### Running a Scheduling Analysis

The scheduler has an analysis mode that will evaluate the satisfaction of goals but will not place any new activities. To run the scheduler in analysis mode, click on the "analysis" button:

Expand Down
43 changes: 43 additions & 0 deletions docs/scheduling-and-constraints/introduction.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Scheduling & Constraints

Aerie provides related frameworks for defining constraints and scheduling new activities in the plan, and two implementations
of those frameworks: one for arbitrary procedures that run on the JVM, and a legacy system based on a declarative Typescript eDSL
(embedded Domain-Specific Language). Both frameworks are documented here, but new users are encouraged to focus on
creating JVM procedures. The declarative eDSL is significantly less capable, and the difference in capabilities is only expected to
grow.

## Constraints

Constraints represent what is nominal for a plan or mission model, and when executed, the UI will display "violations"
whenever the plan or model is off-nominal. They don't alter the behavior of the simulation engine or scheduler; they
just serve as a warning, indicating that some requirement - perhaps a flight rule - was broken.

## Scheduling

The scheduler allows users to automate the creation of new activities, to remove some cognitive load from planners. A
scheduling specification contains a list of goals and rules with a priority order; during a scheduling run, they are
executed one at a time, starting from a priority of 0 and increasing from there.

### Procedural Goals

Procedural goals directly edit the plan, creating new activities at definite (grounded) times. They
can simulate potential changes to the plan, but aren't required to. In fact, a scheduling specification composed entirely
of procedures might run in its entirety without performing any simulations, potentially at the cost of optimality or even
soundness.

### eDSL Goals

eDSL goals are more declarative, in that they don't allow you to directly create grounded activities; instead they allow
you to describe a pattern of activities that should be present in the plan. If the pattern isn't found, the goal tries to
create it for you. Currently eDSL goals are simpler to write than procedural goals, for patterns that they can represent.
Many goals are more complex than can be represented in the eDSL, and will have to be written as a procedure.

### Global Conditions

Global scheduling conditions (or sometimes just "conditions") are supplemental pieces of code that define when scheduling
goals can and cannot place activities. They are incorporated into the solver when attempting to resolve conflicts as
a substitute for constraints. This is because it is too difficult to respect constraints during scheduling; constraints
only indicate that something went wrong, not what caused it or how to fix it. So in cases when the scheduler keeps violating
constraints, users can create a condition as a heuristic to help it satisfy the constraint.

Conditions will be accessible to scheduling rules, but will be non-binding.
41 changes: 41 additions & 0 deletions docs/scheduling-and-constraints/management.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Management

## Creating and Updating

The recommended setup is to store goals/rules/conditions/constraints (hereafter called "peripheral code")
in one or more repositories outside Aerie, and uploading
them either through the UI or the unofficial [Aerie CLI](https://github.com/NASA-AMMOS/aerie-cli).

For example, after creating a new goal as described in the following pages, you can upload it with the CLI using
`aerie-cli goals new <path/to/MyGoal.jar>`. A new goal will be created in Aerie with a default name of `MyGoal`.
See `aerie-cli goals new --help` for more details, such as automatically
associating it with a model or plan. To update a goal afterward, you can run `aerie-cli goals update <path/to/MyGoal.jar>`,
assuming that the default name was not changed. The same works for Typescript eDSL declarations, and similar workflows
will be implemented for constraints and conditions soon.

## Model and Plan Association

In Aerie, peripherals live independently of plans and models, and can be associated with
any number of plans and models, or none at all. Each model and plan has a scheduling specification and a constraints
specification, which is simply a list of peripherals to run during the scheduling or constraints actions, respectively.

Model specifications are never run directly, and instead populate the default spec for any plans created from that model.
So if a particular constraint is widely applicable to all plans made from a particular model, you can associate it with
the model by navigating to `Models -> <select your model> -> Edit details ... -> Constraints -> Library`. Click the checkbox
on your constraint and select `Save`. Now any new plans made with this model will include your constraint in its specification.

Peripherals that don't apply to all plans for a model can be associated with individual plans too. In the main plan view,
navigate to the `Scheduling Goals` or `Constraints` pane, then click `Manage`. Click the checkbox for your peripheral and
select `Save`.

Additionally, the Aerie CLI provides options when creating a new goal to associate it with a given model ID or plan ID.

## Version Locking

Peripheral associations can be locked to a specific revision, on either the model or plan specification. In the specification,
you can change `Always use latest` to a revision number of your choice.

## Deleting

Peripherals can be deleted in the UI by navigating to the `Scheduling` or `Constraints` view in the top left, and deleting
them there. This action can't be done if the peripheral is being used by a plan or model.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
170 changes: 170 additions & 0 deletions docs/scheduling-and-constraints/procedural/constraints.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Constraints

:::warning

Procedural constraints aren't supported yet, although the interfaces are fully implemented. You should be able to run
a constraint locally using the procedural-remote package, but they can't yet be integrated with Aerie.

:::

It's finally time to write a useful piece of code! Constraints are simple. They take a `Plan` and `SimulationResults`,
and return a `Violations` timeline. Violations are a new type of timeline specific to constraints, that store `Violation`
objects. You won't usually need to perform additional operations after creating a `Violations` timeline; usually you'll
just return it. They can be created with some provided static constructor functions. For example:

<Tabs groupId="lang">
<TabItem value="kt" label="Kotlin">

```kotlin
@ConstraintProcedure
class BatteryAboveZero: Constraint {
override fun run(plan: Plan, simResults: SimulationResults) = Violations.inside(
plan.resource("/battery_soc", Real.deserialize()).lessThan(0).highlightTrue()
)
}
```

</TabItem>
<TabItem value="java" label="Java">

```java
@ConstraintProcedure
public class BatterAboveZero implements Constraint {
@NotNull
@Override
public Violations run(@NotNull Plan plan, @NotNull SimulationResults simResults) {
return Violations.inside(
plan.resource("/battery_soc", Real.deserialize()).lessThan(0).highlightTrue()
);
}
}
```

</TabItem>
</Tabs>

## Generator Constraints

For more complex constraints, it may be tedious to try to represent all the violations in a single `Violations` timeline,
and easier to create violations more iteratively. In this case, you could simply add to a list of violations, then
create a timeline at the end with `new Violations(violationsList)`, or you could use some helper functions provided by
the `GeneratorConstraint` abstract class instead.

:::tip

Because of Kotlin's [extension function concept](https://kotlinlang.org/docs/extensions.html),
the `GeneratorConstraint` class's ergonomics are much more helpful in Kotlin, and only provides a marginal benefit in Java.

:::

For example, to violate whenever `MyActivity` occurs when `/my/resource < 0`, you could do the following:

<Tabs groupId="lang">
<TabItem value="kt" label="Kotlin">

```kotlin
@ConstraintProcedure
class MyConstraint: GeneratorConstraint() {
override fun generate(plan: Plan, simResults: SimulationResults) {
val myResource = simResults.resource("/my/resource", Real.deserialize()).cache()
for (activity in plan.directives("MyActivity")) {
if (myResource.sample(activity.startTime) < 0)
violate(Violation(activity.interval))
}
}
}
```

</TabItem>
<TabItem value="java" label="Java">

```java
@ConstraintProcedure
public class MyConstraint extends GeneratorConstraint {
@Override
public void generate(@NotNull Plan plan, @NotNull SimulationResults simResults) {
final var myResource = simResults.resource("/my/resource", Real.deserialize()).cache();
for (final var activity: plan.directives("MyActivity")) {
if (myResource.sample(activity.startTime) < 0)
violate(Violation(activity.interval));
}
}
}
```

</TabItem>
</Tabs>

Additionally, the `GeneratorConstraint` class provides some nice extension functions (all beginning with `violate...`)
that you can apply to your timelines, which convert them into violations and automatically submit them. This only works
as shown in Kotlin. You can call these functions in Java, but the syntax isn't any more ergonomic than just calling `violate(...)`
normally.

```kotlin
@ConstraintProcedure
class BatteryAboveZero: GeneratorConstraint() {
override fun generate(plan: Plan, simResults: SimulationResults) {
simResults.resource("/battery_soc", Real.deserialize())
.greaterThan(0)

// Only works in a generator constraint!
// Only works in Kotlin!
.violateOn(false)
}
}
```

## Violation Messages

The `Violation` class contains a `message` field, which will display to the user in the UI. It is `null` by default,
but you have two ways to change it.

If you want to set different messages for each violation, you can create the `Violation` objects yourself and then pass
each one individually to `violate(...)` in a generator constraint. Or, if you want to set the same message for all
violations, you can override the `message()` function in the `Constraint` interface. To repeat a previous example:

<Tabs groupId="lang">
<TabItem value="kt" label="Kotlin">

```kotlin
@ConstraintProcedure
class MyConstraint: GeneratorConstraint() {
override fun message() = "MyActivity cannot start when /my/resource < 0"

override fun generate(plan: Plan, simResults: SimulationResults) {
val myResource = simResults.resource("/my/resource", Real.deserialize()).cache()
for (activity in plan.directives("MyActivity")) {
if (myResource.sample(activity.startTime) < 0)
violate(Violation(activity.interval))
}
}
}
```

</TabItem>
<TabItem value="java" label="Java">

```java
@ConstraintProcedure
public class MyConstraint extends GeneratorConstraint {
@Override
public String message() {
return "MyActivity cannot start when /my/resource < 0";
}

@Override
public void generate(@NotNull Plan plan, @NotNull SimulationResults simResults) {
final var myResource = simResults.resource("/my/resource", Real.deserialize()).cache();
for (final var activity: plan.directives("MyActivity")) {
if (myResource.sample(activity.startTime) < 0)
violate(Violation(activity.interval));
}
}
}
```

</TabItem>
</Tabs>
27 changes: 27 additions & 0 deletions docs/scheduling-and-constraints/procedural/getting-started.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Getting Started

Currently only scheduling procedures are supported, not constraint procedures.

## Create a project from the template

We have created a template repository for your mission model and scheduling procedures [here](https://github.com/NASA-AMMOS/aerie-mission-model-template).
If you don't already have a mission model project, you can just copy that template and follow the instructions.
If you do, you can follow these steps to add a place for your procedures:

1. Move your mission model code into a gradle subproject if it isn't already.
2. Create a `scheduling` subproject.
3. Copy the `build.gradle` from the `scheduling` subproject of the [mission model template repo](https://github.com/NASA-AMMOS/aerie-mission-model-template).
4. You can now create procedures in a java package in the `scheduling` subproject, as long as the package path ends in `procedures`.
(i.e. `src/main/java/myorg/mymission/procedures`)

## Compiling

It is a two-part process to build your scheduling jars.
1. Run `./gradlew :scheduling:compileJava` (or any command that delegates to it, such as `:scheduling:build` or a top-level `build`).
2. Run `./gradlew :scheduling:buildAllSchedulingProcedureJars`. This produces the jar artifacts for each procedure.

There should now be one jar for each scheduling procedure, at `scheduling/build/libs/<OriginalSourceCodeFileName>.jar`.

## Creating a Goal

See the examples in the mission model template repo, or see [the scheduling page](../scheduling) in this section.
7 changes: 7 additions & 0 deletions docs/scheduling-and-constraints/procedural/introduction.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Procedural Scheduling & Constraints

Aerie allows users to write custom JVM procedures to check constraints or schedule new activities with complete freedom.
Most users will choose to use Java, but since the libraries are written in Kotlin, they
provide some quality-of-life improvements and syntactic sugar for those using Kotlin. Additionally, Kotlin's more intelligent
type inference, null-safety, and currying syntax make writing peripheral procedures a more seamless experience, although
the API is intended to work well with Java too.
Loading

0 comments on commit 67184a2

Please sign in to comment.