Skip to content

Commit

Permalink
Update to latest Aerie and reorganize to add scheduling procedures (#8)
Browse files Browse the repository at this point in the history
* update to gradle 8.7

* update `docker-compose` with latest from `aerie` repo

* update .env and build.gradle for newer aerie versions

* reorg into model and scheduling submodules

* bootstrap `scheduling` submodule

* add utils to mission model, consume in procedures

* add mission model tutorial completion patch

* misc changes

* Update README with scheduling procedure

* update patch to insert new files

* remove `.env`

* update README to reference scheduling procedures

* remove `SimulationDemo` since it references banananation

* link to StayWellFed, as well as update with UI instructions

instead of CLI instructions

* Update to work with type-utils

* use project.aerieVersion

---------

Co-authored-by: dandelany <[email protected]>
Co-authored-by: Matthew Dailis <[email protected]>
  • Loading branch information
3 people authored Sep 10, 2024
1 parent 7966014 commit 343f777
Show file tree
Hide file tree
Showing 8 changed files with 327 additions and 3 deletions.
44 changes: 41 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Aerie Template

This repo provides an Aerie mission model template, which is meant as a starting point for building a new mission model in Aerie.
Included in this repo is all the basic infrastructure required to generate a mission model `.jar` file.
This repo provides a basic template for getting started with mission modeling and scheduling within the Aerie framework.

Included in this repo is all the basic infrastructure and scaffolding required to generate a mission model `.jar` file, as well as scheduling procedure `.jar`s that can be uploaded and run within Aerie.

#### Interested in learning how to develop a model yourself?
Check out the [Aerie Mission Modeling Tutorial](https://nasa-ammos.github.io/aerie-docs/tutorials/mission-modeling/introduction/)
Expand All @@ -12,7 +13,6 @@ Try out the following models:
- [Tutorial Model](https://github.com/NASA-AMMOS/aerie-modeling-tutorial)
- Simple Data Model (coming soon)


## Prerequisites

- Install [OpenJDK Temurin LTS](https://adoptium.net/temurin/releases/?version=21). If you're on macOS, you can install [brew](https://brew.sh/) instead and then use the following command to install JDK 21:
Expand All @@ -35,6 +35,7 @@ Try out the following models:

## Building

### Mission Model
To build a mission model JAR you can do:

```sh
Expand All @@ -45,6 +46,43 @@ This will create the file `'missionmodel/build/libs/missionmodel.jar`, which you

<!-- If you want to just try the model without building it yourself you can [download it here](./missionmodel.jar). -->

### Scheduling Procedures
To build scheduling procedures, first you will need a completed mission model. You can accomplish this by following the [Aerie Mission Modeling Tutorial](https://nasa-ammos.github.io/aerie-docs/tutorials/mission-modeling/introduction/), or by using the included `complete-model-tutorial.patch`:

```sh
git apply complete-model-tutorial.patch
```

This is required since scheduling procedures will reference activity types (e.g. when placing activity directives), and the model in this repo, out of the box, has no registered activities.

Then, copy an example scheduling procedure into the procedures folder.

```sh
cp scheduling/examples/SampleProcedure.java scheduling/src/main/java/scheduling/procedures
```

(For a more involved example procedure, take a look at some [procedures in the Aerie repo](https://github.com/NASA-AMMOS/aerie/blob/develop/procedural/examples/foo-procedures/src/main/java/gov/nasa/ammos/aerie/procedural/examples/fooprocedures/procedures/StayWellFed.java))

The following will be your process every time you iterate on these procedures

```sh
./gradlew scheduling:compileJava
./gradlew scheduling:buildAllSchedulingProcedureJars
```

The first `gradle` command will expand `@SchedulingProcedure` annotations into new, verbose source code files that Aerie can process down the line.
The second `gradle` command then looks for those generated files, creates a task to build a `.jar` for each file, and then runs all those tasks.

Your procedure jars will then be in `scheduling/build/libs/OriginalSourceCodeFileName.jar`, which in this case will be `scheduling/build/libs/SampleProcedure.jar`.

## Running Procedures

Now that you have `.jar`'s, we need to upload them to Aerie so you can run them against plans.

The quickest way to upload a single JAR is to use the `aerie-ui`. On the `/scheduling/goals/new` page, you should now see a new tab option for creating a `jar` procedural goal. Once created, you will need to register the goal with a specific plan, just like you do with EDSL goals.

Then, from the manage goals page on your plan, you can pass arguments to your procedure using the drop down menu, and run your procedures using the schedule button. You can also right click to manage invocations (duplicate, delete, etc)

## Testing

To run unit tests under [./missionmodel/src/test](./missionmodel/src/test) against your mission model you can do:
Expand Down
140 changes: 140 additions & 0 deletions complete-model-tutorial.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
diff --git a/missionmodel/src/main/java/missionmodel/CollectData.java b/missionmodel/src/main/java/missionmodel/CollectData.java
new file mode 100644
index 0000000..4e61911
--- /dev/null
+++ b/missionmodel/src/main/java/missionmodel/CollectData.java
@@ -0,0 +1,45 @@
+package missionmodel;
+
+import gov.nasa.jpl.aerie.contrib.metadata.Unit;
+import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.DiscreteEffects;
+import gov.nasa.jpl.aerie.merlin.framework.annotations.ActivityType;
+import gov.nasa.jpl.aerie.merlin.framework.annotations.Export.Parameter;
+import gov.nasa.jpl.aerie.merlin.framework.annotations.Export.Validation;
+
+
+import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
+
+import static gov.nasa.jpl.aerie.merlin.framework.ModelActions.delay;
+
+/* Example Activity Type Definition
+ If this activity is moved over to main/java/missionmodel along with the DataModel class and the example model
+ declaration in the Mission class is uncommented, the model should compile.
+ */
+@ActivityType("CollectData")
+public class CollectData {
+
+ @Parameter
+ @Unit("Mbps")
+ public double rate = 10.0; // Mbps
+
+ @Parameter
+ public Duration duration = Duration.duration(1, Duration.HOURS);
+
+ @Validation("Collection rate is beyond buffer limit of 100.0 Mbps")
+ @Validation.Subject("rate")
+ public boolean validateCollectionRate() {
+ return rate <= 100.0;
+ }
+
+ @ActivityType.EffectModel
+ public void run(Mission model) {
+
+ /*
+ Collect data at fixed rate over duration of activity
+ */
+ DiscreteEffects.increase(model.dataModel.RecordingRate, this.rate);
+ delay(duration);
+ DiscreteEffects.decrease(model.dataModel.RecordingRate, this.rate);
+
+ }
+}
diff --git a/missionmodel/src/main/java/missionmodel/DataModel.java b/missionmodel/src/main/java/missionmodel/DataModel.java
new file mode 100644
index 0000000..b0df0d4
--- /dev/null
+++ b/missionmodel/src/main/java/missionmodel/DataModel.java
@@ -0,0 +1,42 @@
+package missionmodel;
+
+import gov.nasa.jpl.aerie.contrib.serialization.mappers.DoubleValueMapper;
+import gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource;
+import gov.nasa.jpl.aerie.contrib.streamline.modeling.Registrar;
+import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.Discrete;
+import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.DiscreteEffects;
+import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
+
+import static gov.nasa.jpl.aerie.contrib.metadata.UnitRegistrar.withUnit;
+import static gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource.resource;
+import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.currentValue;
+import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.Discrete.discrete;
+import static gov.nasa.jpl.aerie.merlin.framework.ModelActions.delay;
+
+/* Example Mission Model delegate class
+ This class includes two resource declarations and a method that can be spawned via a daemon task
+ If this activity is moved over to main/java/missionmodel and the example model declaration in the Mission class
+ is uncommented, the model should compile.
+ */
+public class DataModel {
+
+ public MutableResource<Discrete<Double>> RecordingRate; // Megabits/s
+
+ public MutableResource<Discrete<Double>> SSR_Volume_Sampled; // Gigabits
+
+ public DataModel(Registrar registrar, Configuration config) {
+ RecordingRate = resource(discrete(0.0));
+ registrar.discrete("RecordingRate", RecordingRate, withUnit("Mbps", new DoubleValueMapper()));
+ }
+
+ public void integrateDataRate() {
+ Duration INTEGRATION_SAMPLE_INTERVAL = Duration.duration(60, Duration.SECONDS);
+ while(true) {
+ delay(INTEGRATION_SAMPLE_INTERVAL);
+ Double currentRecordingRate = currentValue(RecordingRate);
+ DiscreteEffects.increase(SSR_Volume_Sampled, currentRecordingRate *
+ INTEGRATION_SAMPLE_INTERVAL.ratioOver(Duration.SECONDS) / 1000.0); // Mbit -> Gbit
+ }
+ }
+
+}
diff --git a/missionmodel/src/main/java/missionmodel/Mission.java b/missionmodel/src/main/java/missionmodel/Mission.java
index d7eb103..e2b3da5 100644
--- a/missionmodel/src/main/java/missionmodel/Mission.java
+++ b/missionmodel/src/main/java/missionmodel/Mission.java
@@ -25,7 +25,7 @@ public final class Mission {
// public MutableResource<Discrete<Double>> ExampleResource;

// Example model declaration
- //public final DataModel dataModel;
+ public final DataModel dataModel;

public Mission(final gov.nasa.jpl.aerie.merlin.framework.Registrar registrar, final Configuration config) {
this.errorRegistrar = new Registrar(registrar, Registrar.ErrorBehavior.Log);
@@ -35,10 +35,10 @@ public final class Mission {
// errorRegistrar.discrete("ExampleResource", ExampleResource, new DoubleValueMapper());

// Example model instantiation
- //this.dataModel = new DataModel(this.errorRegistrar, config);
+ this.dataModel = new DataModel(this.errorRegistrar, config);

// Example daemon task call
- // spawn(dataModel::integrateDataRate);
+ spawn(dataModel::integrateDataRate);

}
}
diff --git a/missionmodel/src/main/java/missionmodel/package-info.java b/missionmodel/src/main/java/missionmodel/package-info.java
index 314ae30..f0d67da 100644
--- a/missionmodel/src/main/java/missionmodel/package-info.java
+++ b/missionmodel/src/main/java/missionmodel/package-info.java
@@ -1,8 +1,8 @@
@MissionModel(model = Mission.class)
@WithMappers(BasicValueMappers.class)
@WithConfiguration(Configuration.class)
-// @WithActivityType(ActivityType.class) // for new activity type
-// @WithMetadata(name = "unit", annotation = gov.nasa.jpl.aerie.contrib.metadata.Unit.class) // for unit support
+@WithActivityType(CollectData.class) // for new activity type
+@WithMetadata(name = "unit", annotation = gov.nasa.jpl.aerie.contrib.metadata.Unit.class) // for unit support
package missionmodel;

import gov.nasa.jpl.aerie.contrib.serialization.rulesets.BasicValueMappers;
7 changes: 7 additions & 0 deletions missionmodel/src/main/java/missionmodel/Utils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package missionmodel;

public class Utils {
public static String getCollectDataActivityName() {
return "CollectData";
}
}
100 changes: 100 additions & 0 deletions scheduling/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

plugins {
id 'java'
id 'io.github.goooler.shadow' version '8.1.7'
}

java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}

dependencies {
// pull in any helper / utils from the mission model
implementation project(":missionmodel")

// procedural scheduling libraries
annotationProcessor "gov.nasa.ammos.aerie.procedural:processor:" + project.aerieVersion
implementation "gov.nasa.ammos.aerie.procedural:scheduling:" + project.aerieVersion
implementation "gov.nasa.ammos.aerie.procedural:timeline:" + project.aerieVersion
implementation "gov.nasa.ammos.aerie.procedural:constraints:" + project.aerieVersion

// standard aerie deps
implementation 'gov.nasa.jpl.aerie:merlin-framework:' + project.aerieVersion
implementation 'gov.nasa.jpl.aerie:contrib:' + project.aerieVersion
implementation 'gov.nasa.jpl.aerie:type-utils:' + project.aerieVersion

testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
}

test {
useJUnitPlatform()
}

tasks.register('buildAllSchedulingProcedureJars') {
group = 'SchedulingProcedureJars'

dependsOn "generateSchedulingProcedureJarTasks"
dependsOn {
tasks.findAll { task -> task.name.startsWith('buildSchedulingProcedureJar_') }
}
}

tasks.create("generateSchedulingProcedureJarTasks") {
group = 'SchedulingProcedureJars'

final proceduresDir = findFirstMatchingBuildDir("generated/procedures")

if (proceduresDir == null) {
println "No generated procedures found yet, make sure you have procedure source files in src/.../procedures"
return
}
println "Generating jar tasks for the following procedures directory: ${proceduresDir}"

final files = file(proceduresDir).listFiles()
if (files.length == 0) {
println "No procedures available within folder ${proceduresDir}"
return
}

files.toList().each { file ->
final nameWithoutExtension = file.name.replace(".java", "")
final taskName = "buildSchedulingProcedureJar_${nameWithoutExtension}"

println "Generating ${taskName} task, which will build ${nameWithoutExtension}.jar"

tasks.create(taskName, ShadowJar) {
group = 'SchedulingProcedureJars'
configurations = [project.configurations.compileClasspath]
from sourceSets.main.output
archiveBaseName = "" // clear
archiveClassifier.set(nameWithoutExtension) // set output jar name
manifest {
attributes 'Main-Class': getMainClassFromGeneratedFile(file)
}
minimize()
dependencies {
// exclude project(':procedural:timeline')
// exclude dependency(":kotlin.*")
}
}
}
}

private String findFirstMatchingBuildDir(String pattern) {
String found = null
final generatedDir = file("build/generated/sources")
generatedDir.mkdirs()
generatedDir.eachDirRecurse { dir -> if (dir.path.contains(pattern)) found = dir.path }
return found
}

private static String getMainClassFromGeneratedFile(File file) {
final fileString = file.toString()
final prefix = "build/generated/sources/annotationProcessor/java/main/"
final index = fileString.indexOf(prefix) + prefix.length()
final trimmed = fileString.substring(index).replace(".java", "")
return trimmed.replace("/", ".")
}
33 changes: 33 additions & 0 deletions scheduling/examples/SampleProcedure.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package scheduling.procedures;

import gov.nasa.ammos.aerie.procedural.scheduling.Goal;
import gov.nasa.ammos.aerie.procedural.scheduling.plan.EditablePlan;
import gov.nasa.ammos.aerie.procedural.scheduling.annotations.SchedulingProcedure;
import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart;
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;

import missionmodel.Utils;

import java.util.Map;

@SchedulingProcedure
public record SampleProcedure(int quantity) implements Goal {
@Override
public void run(EditablePlan plan) {
final var firstTime = Duration.hours(0);
final var step = Duration.hours(6);

var currentTime = firstTime;
for (var i = 0; i < quantity; i++) {
plan.create(
Utils.getCollectDataActivityName(),
new DirectiveStart.Absolute(currentTime),
Map.of()
);
currentTime = currentTime.plus(step);
}
plan.commit();
// var results = plan.simulate(new SimulateOptions());
// var size = results.instances().collect().size();
}
}
5 changes: 5 additions & 0 deletions scheduling/src/main/java/scheduling/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@WithMappers(BasicValueMappers.class)
package scheduling;

import gov.nasa.jpl.aerie.contrib.serialization.rulesets.BasicValueMappers;
import gov.nasa.ammos.aerie.procedural.scheduling.annotations.WithMappers;
Empty file.
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
rootProject.name = 'aerie-template'
include('missionmodel')
include('scheduling')

0 comments on commit 343f777

Please sign in to comment.