Skip to main content

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"

Simulation Config

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.