Skip to content

Commit

Permalink
Modeling Tutorial Updates (#179)
Browse files Browse the repository at this point in the history
* Minor changes to documentation based on submodule package structure change in model template

* Update docs based on feedback from tutorial tester
  • Loading branch information
ewferg authored Aug 7, 2024
1 parent 422cc41 commit 504f504
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 35 deletions.
2 changes: 1 addition & 1 deletion docs/tutorials/mission-modeling/current-value.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ Looking at our new activity definition, you can see how we use the `increase()`
As a final step, make sure you add this new activity to our `package-info.java` file

```java
@ActivityType("ChangeMagMode")
@WithActivityType("ChangeMagMode.class")
```
13 changes: 12 additions & 1 deletion docs/tutorials/mission-modeling/enum-derived-resource.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,24 @@ Another resource we can add is one to track the numerical value of the data coll
public Resource<Discrete<Double>> MagDataRate; // bps
```

When we go to define this resource in the constructor, we need to tell the resource to get its value by mapping the `MagDataMode` to its corresponding rate. A special static method in the `DiscreteResourceMonad` class called `map()` allows us to define a function that operates on the value of a resource to get a derived resource value. In this case, that function is simply the getter function we added to the `MagDataCollectionMode`. The resulting definition and registration code for `MagDataRate` then becomes
When we go to define this resource in the constructor, we need to tell the resource to get its value by mapping the `MagDataMode` to its corresponding rate.
A special static method in the `DiscreteResourceMonad` class called `map()` allows us to define a function that operates on the value of a resource to get a derived resource value.
In this case, that function is simply the getter function we added to the `MagDataCollectionMode`. The resulting definition and registration code for `MagDataRate` then becomes

```java
MagDataRate = map(MagDataMode, MagDataCollectionMode::getDataRate);
registrar.discrete("MagDataRate", MagDataRate, new DoubleValueMapper());
```

:::tip

If your IDE is struggling to find the `map()` function, the associated import statement is
```java
import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.monads.DiscreteResourceMonad.map;
```

:::

:::info

Instead of deriving a resource value from a function using `map()`, there are a number of static methods in the `DiscreteResources` class, which you can use to `add()`, `multiply()`, `divide()`, etc. resources. For example, you could have a `Total` resource that simple used `add()` to sum some resources together.
Expand Down
13 changes: 9 additions & 4 deletions docs/tutorials/mission-modeling/first-activity.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,19 @@ public boolean validateCollectionRate() {

The `@Validation` annotation specifies the message to present to the operator when the validation fails. The `@Validation.Subject` annotation specifies the parameter(s) with which the validation is associated. Now, as you will see soon, when an operator specifies a data rate above `100.0`, Aerie will show a validation error and message in the UI.

Next, we need to tell our activity how and when to effect change on the `RecordingRate` resource, which is done in an [Activity Effect Model](https://nasa-ammos.github.io/aerie-docs/mission-modeling/activity-types/effect-model/). Just like with validations, an effect model is built by adding a method to our class, but with a different annotation, `@ActivityType.EffectModel`. Unlike validations, there can only be one of these methods per activity and the method should accept the top-level mission model class as a parameter (which in our case is just `Mission`). Conventionally, the method name given to the effect model is `run()`.
Next, we need to tell our activity how and when to effect change on the `RecordingRate` resource, which is done in an [Activity Effect Model](https://nasa-ammos.github.io/aerie-docs/mission-modeling/activity-types/effect-model/).
Just like with validations, an effect model is built by adding a method to our class, but with a different annotation, `@ActivityType.EffectModel`.
Unlike validations, there can only be one of these methods per activity and the method should accept the top-level mission model class as a parameter (which in our case is just `Mission`).
Conventionally, the method name given to the effect model is `run()`.

For our activity, we simply want to model data collection at a fixed rate specified by the `rate` parameter over the full duration of the activity. Within the `run()` method, we can add the follow code to get that behavior:

```java
DiscreteEffects.increase(model.dataModel.RecordingRate, this.rate);
delay(duration);
DiscreteEffects.decrease(model.dataModel.RecordingRate, this.rate);
public void run(Mission model) {
DiscreteEffects.increase(model.dataModel.RecordingRate, this.rate);
delay(duration);
DiscreteEffects.decrease(model.dataModel.RecordingRate, this.rate);
}
```

Effects on resources are accomplished by using one of the many static methods available in the class associated with your resource type. In this case, `RecordingRate` is a discrete resource, and therefore we are using methods from the `DiscreteEffects` class. If you peruse the static methods in `DiscreteEffects`, you'll see methods like `set()`, `increase()`, `decrease()`, `consume()`, `restore()`,`using()`, etc. Since discrete resources can be of many primitive types (e.g. `Double`,`Boolean`), there are specific methods for each type. Most of these effects change the value of the resource at one time point instantaneously, but some, like `using()`, allow you to specify an [action](https://nasa-ammos.github.io/aerie-docs/mission-modeling/activity-types/effect-model/#actions) to run like `delay()`. Prior to executing the action, the resource changes just like other effects, but once the action is complete, the effect on the resource is reversed. These resource effects are sometimes called "renewable" in contrast to the other style of effects, which are often called "consumable".
Expand Down
9 changes: 1 addition & 8 deletions docs/tutorials/mission-modeling/first-build.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,8 @@ Mission models require a couple of standard items for Aerie to process the model
1. A [`package-info.java`](https://nasa-ammos.github.io/aerie-docs/mission-modeling/introduction/#the-package-infojava-file) file containing a reference to the top-level mission model class, annotations referencing any activities defined in the model, an annotation referencing a configuration class that can expose configuration parameters that can be varied prior to simulation, and import statements to the Aerie modeling framework to bridge the framework to the model.
2. The top-level [mission model class](https://ammos.nasa.gov/aerie-docs/mission-modeling/introduction/#the-mission-model-class) that defines or delegates the behavior of the system being described in the model. Any quantity or state that you would like to track over the course of the simulation - which we define as a [**Resource**](https://ammos.nasa.gov/aerie-docs/mission-modeling/resources-and-models/) - should be declared and defined in this class or its delegates. The name of the top-level mission class can be anything as long as it matches the reference in `package-info.java`.

Fortunately, to save you some trouble, we've created a [mission model template repository](https://github.com/NASA-AMMOS/aerie-mission-model-template) that already has these items included for you along with a gradle build setup that takes care of including the right Aerie dependencies to get your mission model `.jar` file built hassle-free. In this repository, if you take a look in [`src/main/java/missionmodel`](https://github.com/NASA-AMMOS/aerie-mission-model-template/tree/main/src/main/java/missionmodel), you'll see the `package-info.java` file along with the top-level `Mission` and `Configuration` classes already defined for you.
Fortunately, to save you some trouble, we've created a [mission model template repository](https://github.com/NASA-AMMOS/aerie-mission-model-template) that already has these items included for you along with a gradle build setup that takes care of including the right Aerie dependencies to get your mission model `.jar` file built hassle-free. In this repository, if you take a look in [`missionmodel/src/main/java/missionmodel`](https://github.com/NASA-AMMOS/aerie-mission-model-template/tree/main/missionmodel/src/main/java/missionmodel), you'll see the `package-info.java` file along with the top-level `Mission` and `Configuration` classes already defined for you.

On the main page for the [mission model template repository](https://github.com/NASA-AMMOS/aerie-mission-model-template), click the "Use this template" button on the top right of the page and select "Create a new repository" to create a new repository for your SSR model. Clone your new repository and follow the instructions in the [`README.md`](https://github.com/NASA-AMMOS/aerie-mission-model-template/blob/main/README.md) to setup your environment and test out building a mission model `.jar` from the model. You'll find the `.jar` you built within a `build/libs` directory generated as part of the gradle build.

At this point, we could pull up the Aerie UI and load the `.jar` file into Aerie as a model, but there is nothing really interesting in the model yet. So before we bring our model into Aerie, let's give it some content.

TODO:

- Update mission model template to align with David's new framework
- Account for his register in the Mission class
- Update dependencies in build.gradle
- Remove activities/resources from the template (If they want content they could use the tutorial repo or other examples we provide)
17 changes: 13 additions & 4 deletions docs/tutorials/mission-modeling/first-model-test.mdx
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
# Model Test Drive

Within your IDE, compile the model (`./gradlew assemble` should do the trick) and make sure it built successfully by checking `build/lib` for a new `missionmodel.jar` file.
Within your IDE, compile the model (`./gradlew :missionmodel:build --refresh-dependencies` should do the trick) and make sure it built successfully by checking `build/libs` for a new `missionmodel.jar` file.

Follow [these instructions](https://ammos.nasa.gov/aerie-docs/planning/upload-mission-model/) to upload your `.jar` file, and give your model a name and version number (e.g. SSR Model version 1.0). Next, you can follow [these instructions](https://ammos.nasa.gov/aerie-docs/planning/create-plan-and-simulate/#instructions) to create a new plan. Pick the model you just compiled to build your plan off of and name your plan `Mission Plan 1` and give it a duration of `1 day`. Click "Create" and click on the newly created plan to open it, which should take you to a view with the plan timeline in the center view panel.
Follow [these instructions](https://ammos.nasa.gov/aerie-docs/planning/upload-mission-model/) to upload your `.jar` file, and give your model a name and version number (e.g. SSR Model version 1.0).
Next, you can follow [these instructions](https://ammos.nasa.gov/aerie-docs/planning/create-plan-and-simulate/#instructions) to create a new plan.
Pick the model you just compiled to build your plan off of and name your plan `Mission Plan 1` and give it an arbitrary start time (for example, today).
The end time will be pre-populated by default to give your plan a duration `1 day`, which we will keep for this test drive.
Click "Create" and click on the newly created plan to open it, which should take you to a view with the plan timeline in the center view panel.

On the left panel, you should see your `CollectData` activity type, which you can drag and drop onto the "Activities" row in the timeline. Your `RecordingRate` resource should also appear as a row in the timeline, but with no values applied yet since we haven't simulated. Put two activities in your plan and click on the second one (the first one we will leave alone and let it use default values). On the right panel, you should now see detailed information about your activity. Look for the "Parameters" section and you will see your rate and duration parameters, which you can modify. Try modifying the rate above `100.0` and you will see a warning icon appear, which you can hover over and see the message we wrote into a Validation earlier. Modify the rate back to `20` and change the default duration to `2 hours`.
On the left panel, you should see your `CollectData` activity type, which you can drag and drop onto the "Activities" row in the timeline.
Your `RecordingRate` resource should also appear as a row in the timeline, but with no values applied yet since we haven't simulated.
Put two instances of the `CollectData` activity type on the plan via drag-and-drop and click on the second one (the first one we will leave alone and let it use default values).
On the right panel, you should now see detailed information about your activity. Look for the "Parameters" section and you will see your rate and duration parameters, which you can modify.
Try modifying the rate above `100.0` and you will see a warning icon appear, which you can hover over and see the message we wrote into a Validation earlier.
Modify the rate back to `20` and change the default duration to `2 hours`.

:::info

When activity types are initially added to the plan, they are shown with a play button icon and don't have a duration. We call these "activity directives", and it is these activities that you are allowed to modify by changing parameters, timing, etc. Once a simulation has been performed, one or more activities will appear below the directive, which are the activity instances. These actually have a duration (based on their effect model) and are the result of the simulation run.
When activity types are initially added to the plan, they are shown as a small rectangle, but they don't yet have a duration. We call these "activity directives", and it is these activities that you are allowed to modify by changing parameters, timing, etc. Once a simulation has been performed, one or more activities will appear, which are the activity instances. These actually have a duration (based on their effect model) and are the result of the simulation run.

:::

Expand Down
25 changes: 19 additions & 6 deletions docs/tutorials/mission-modeling/first-resource.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@

We will begin building our SSR model by creating a single resource, `RecordingRate`, to track the rate at which data is being written to the SSR over time. As a reminder, a **Resource** is any measurable quantity whose behavior we want to track over the course of a plan. Then, we will create a simple activity, `CollectData`, that updates the `RecordingRate` by a user-specified rate for a user-specified duration. This activity is intended to represent an on-board camera taking images and writing data to the spacecraft SSR.

Although we could define the `RecordingRate` resource directly in the pre-provided top-level `Mission` class, we'd like to keep that class as simple as possible and delegate most of model's behavior definition to other, more focused classes. With this in mind, let's create a new class within the `missionmodel` package called `DataModel`, which we will eventually instantiate within the `Mission` class.
Although we could define the `RecordingRate` resource directly in the pre-provided top-level `Mission` class, we'd like to keep that class as simple as possible and delegate most of model's behavior definition to other, more focused classes.
With this in mind, let's create a new file in `missionmodel/main/java/missionmodel` directory called `DataModel.java` with an empty class definition (we will eventually instantiate this new class within the `Mission` class, but that isn't necessary now).

In the `DataModel` class, declare the `RecordingRate` resource with the following line of code:
In the `DataModel` class, declare the `RecordingRate` resource by adding a single line to the `DataModel` class you just made:

```java
public MutableResource<Discrete<Double>> RecordingRate; // Megabits/s
package missionmodel;

import gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.Discrete;

public class DataModel {
public MutableResource<Discrete<Double>> RecordingRate; // Megabits/s
}
```

:::tip
Expand Down Expand Up @@ -38,15 +46,20 @@ Polynomial resources currently cannot be rendered in the Aerie UI and must be tr

Looking back at our resource declaration, you can see that `RecordingRate` is a `MutableResource` (we will emit effects on this resource in our first activity) of the type `Discrete<Double>`, so the value of the resource will stay constant until the next time we compute effects on it.

Next, we must define and initialize our `RecordingRate` resource, which we can do in a class constructor that takes one parameter we'll called `registrar` of type `Registrar`. You can think of the `Registrar` class as your link to what will ultimately get exposed in the UI and in a second we will use this class to register `RecordingRate`. But first, let's add the following line to the constructor we just made to fully define our resource.
Next, we must define and initialize our `RecordingRate` resource, which we can do in a class constructor that takes one parameter we'll called `registrar` of type `Registrar`.
You can think of the `Registrar` class as your link to what will ultimately get exposed in the UI and in a second we will use this class to register `RecordingRate`.
But first, let's add the following to create the constructor and fully define our resource.

```java
RecordingRate = resource(discrete(0.0));
public DataModel(Registrar registrar) {
RecordingRate = resource(discrete(0.0));
}
```

Both the `MutableResource` and `Discrete` classes have static helper functions for initializing resources of their type. If you included those functions via `import static` statements, you get the simple line above. The `discrete()` function expects an initial value for the resource, which we have specified as `0.0`.

The last thing to do is to register `RecordingRate` to the UI so we can view the resource as a timeline along with our activity plan. This is accomplished with the following line of code:
The last thing to do is to register `RecordingRate` to the UI so we can view the resource as a timeline along with our activity plan.
This is accomplished with the following line of code after the resource definition:

```java
registrar.discrete("RecordingRate", RecordingRate, new DoubleValueMapper());
Expand Down
Loading

0 comments on commit 504f504

Please sign in to comment.