Sim Configuration
There is often a need for certain aspects of a model to be exposed to the planner to provide flexibility to tweak and configure the model prior to a simulation run.
The Aerie modeling framework provides a simulation configuration interface to satisfy this need.
In our SSR model, we will expose a couple variables that already exist in our code: the sample interval for our SSR_Volume_Sampled
resource and the SSR max capacity defined as part of the SSR_Volume_Polynomial
resource definition.
We will also create a new model configuration for setting the initial state of the MagDataMode
.
Back when we initially grabbed the mission model template to give us a jumping off point for our model, you may recall that the template provided a Configuration
class, and that class is already passed into the top-level Mission
class as a parameter.
Taking a look at the Configuration
class (which is actually a java record), you'll see there is already a static method there called defaultConfiguration()
that uses the @Template
annotation.
This type of annotation assumes every variable within the parent class should be exposed as simulation configuration (or a parameter if you use this within activities) with a default value.
So, in our case, we will declare three member variables and give them all default values that match the values we have for them in the DataModel
class, which we will soon replace with references to this configuration.
public static final Double SSR_MAX_CAPACITY = 250.0;
public static final long INTEGRATION_SAMPLE_INTERVAL = 60;
public static final MagDataCollectionMode STARTING_MAG_MODE = MagDataCollectionMode.OFF;
In order to hook up these member variables to our record, we need to add three constructor parameters and then update the defaultConfiguration()
method to pass in these default values to construct a record with default values. Once we do this, we get a Configuration
record that looks like this:
package missionmodel;
import static gov.nasa.jpl.aerie.merlin.framework.annotations.Export.Template;
public record Configuration(Double ssrMaxCapacity,
long integrationSampleInterval,
MagDataCollectionMode startingMagMode) {
public static final Double SSR_MAX_CAPACITY = 250.0;
public static final long INTEGRATION_SAMPLE_INTERVAL = 60;
public static final MagDataCollectionMode STARTING_MAG_MODE = MagDataCollectionMode.OFF;
public static @Template Configuration defaultConfiguration() {
return new Configuration(SSR_MAX_CAPACITY,
INTEGRATION_SAMPLE_INTERVAL,
STARTING_MAG_MODE);
}
}
Now, when Aerie loads in our model, the member variables above will be exposed as simulation configuration with defaults set to the defaults defined in this record. However, at the moment, changing the values from their defaults won't actually change the behavior of the simulation because our DataModel
doesn't yet know about this configuration. Within our top-level Mission
class, we need to pass our configuration into DataModel
via its constructor
this.dataModel = new DataModel(this.errorRegistrar, config);
and then update the DataModel
class constructor to include Configuration
as an argument:
public DataModel(Registrar registrar, Configuration config) { ... }
Now we must find references to our original, hard-coded values for our configuration and replace them with references to our config
object.
Here is what this looks like for ssrMaxCapacity
var clampedIntegrate = PolynomialResources.clampedIntegrate( scale(
asPolynomial(this.RecordingRate), 1e-3),
PolynomialResources.constant(0.0),
PolynomialResources.constant(config.ssrMaxCapacity()),
0.0);
and integrationSampleInterval
INTEGRATION_SAMPLE_INTERVAL = Duration.duration(config.integrationSampleInterval(), Duration.SECONDS);
Note that for the sample interval, we had to move from a hardcoded definition as part of the variable declaration and move the definition to the constructor, which you could put on the line following the registration of the SSR_Volume_Sampled
resource.
Our final configuration parameter, startingMagMode
, is not quite as straightforward as the other two because in addition to ensuring that the initial value of MagDataMode
is set correctly, we need to make sure that the initial RecordingRate
also takes into account the MagDataRate
associated with the initial MagDataMode
. We can achieve this by switching around the order of construction so that the RecordingRate
is defined after the mag mode and rate. We also need to make sure the previousRecordingRate
used to compute our SSR_Volume_UponRateChange
resource is set to the initial value of RecordingRate
. The resulting code will look like this
MagDataMode = resource(discrete(config.startingMagMode()));
registrar.discrete("MagDataMode",MagDataMode, new EnumValueMapper<>(MagDataCollectionMode.class));
MagDataRate = map(MagDataMode, MagDataCollectionMode::getDataRate);
registrar.discrete("MagDataRate", MagDataRate, new DoubleValueMapper());
RecordingRate = resource(discrete(currentValue(MagDataRate)/1e3));
registrar.discrete("RecordingRate", RecordingRate, new DoubleValueMapper());
previousRecordingRate = currentValue(RecordingRate);
Now you should be ready to try this out in the Aerie UI. Go ahead and compile your model with simulation configuration and upload it to Aerie. Build whatever plan you'd like and then before you simulate, in the left panel view, select "Simulation" in the dropdown menu. You should now see your three configuration variables appear under "Arguments"
Aerie is smart enough to look at the types of the configuration variables and generate a input field in the UI that best matches that type. So, for example, the startingMagMode
is a simple drop down menu with the only options available being members of the MagDataCollectionMode
enumeration.