Advanced - Incons
It is possible to initialize the internal state of your model via an initial conditions (incons) file. This page will go over one way to do so, using a simplified version of the Banananation model as a reference. Note that a different implementation may suit your model better.
Configuration
Before we can use an incons file in our Mission Model's constructor, we need to be able to specify what that file is in the Configuration.
Fields
We first add a parameter, inconsFile
, to specify the path to the file to be used.
Additionally, since this model will still allow initialPlantCount
and initialProducer
to possibly be specified by a Planner rather than inconsFile
,
we include a boolean, useInconsFile
, to determine whether we should use the default initialization or initialize from the incons file.
public record Configuration(int initialPlantCount, String initialProducer, boolean useInconsFile, String inconsFile)
Validation
It is recommended to include a validation of the path to the incons file, should one be in use.
@Export.Validation("incons file must exist")
public boolean validateInconsFilePath() {
if(useInconsFile)
return Path.of(inconsFile.trim()).toFile().exists();
return true;
}
Default
With @Template
public static @Template Configuration defaultConfiguration() {
return new Configuration(DEFAULT_PLANT_COUNT, DEFAULT_PRODUCER, false, "");
}
With @WithDefaults
@WithDefaults
public static final class Defaults {
public static Boolean useInconsFile = false;
public static String inconsFile = "";
}
Changes in the Mission Model Class
Now that we are able to specify an incons file to use, we need to load it.
Parsing Incons
In this example, we expect the incons file to be a .JSON file of the format described below, although additional fields on the resource objects are acceptable.
[
{
"dynamics": 12,
"name": "/resource_1"
},
{
"dynamics": {
"doubleField": 1.1,
"stringField": "JPL"
},
"name": "/resource_2"
}
]
Below is a parser for Banananation:
private void parseInconsFile(Configuration config){
try(final JsonReader reader = Json.createReader(new FileReader(config.inconsFile().trim()))) {
JsonArray resources = reader.readArray();
for(int i = 0; i < resources.size(); i++){
final JsonObject resource = resources.getJsonObject(i);
switch (resource.getString("name")) {
case "/fruit" -> {
final JsonObject initialFruit = resource.getJsonObject("dynamics");
final double initialValue = initialFruit.getJsonNumber("initial").doubleValue();
final double rate = initialFruit.getJsonNumber("rate").doubleValue();
this.fruit = new Accumulator(initialValue, rate);
}
case "/flag" -> {
final String initialFlag = resource.getString("dynamics");
this.flag = Register.forImmutable(Flag.valueOf(initialFlag));
this.flag.isConflicted();
}
case "/producer" -> {
final String initialProducer = resource.getString("dynamics");
this.producer = Register.forImmutable(initialProducer);
}
case "/peel" -> {
final double initialPeel = resource.getJsonNumber("dynamics").doubleValue();
this.peel = AdditiveRegister.create(initialPeel);
}
case "/plant" -> {
final int initialPlantCount = resource.getInt("dynamics");
this.plant = Counter.ofInteger(initialPlantCount);
}
}
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
You may want to handle a missing incons file in a more graceful way, such as loading a default file or falling back on the default initialization.
Default Initialization
In your Mission Model's constructor, extract your current initialization into a method like so:
private void defaultInitialization(Configuration config){
this.fruit = new Accumulator(4.0, 0.0);
this.peel = AdditiveRegister.create(4.0);
this.flag = Register.forImmutable(Flag.A);
this.lineCount = Register.forImmutable(0);
this.plant = Counter.ofInteger(config.initialPlantCount());
this.producer = Register.forImmutable(config.initialProducer());
}
Updating the Mission Model Constructor
Replace that initialization was with the following if statement:
if(config.useInconsFile()){
parseInconsFile(config);
} else {
defaultInitialization(config);
}
This gives a final constructor of:
public Mission(final Registrar registrar, final Configuration config) {
if(config.useInconsFile()){
parseInconsFile(config);
} else {
defaultInitialization(config);
}
registrar.discrete("/flag", this.flag, new EnumValueMapper<>(Flag.class));
registrar.discrete("/flag/conflicted", this.flag::isConflicted, new BooleanValueMapper());
registrar.discrete("/peel", this.peel, new DoubleValueMapper());
registrar.real("/fruit", this.fruit);
registrar.discrete("/plant", this.plant, new IntegerValueMapper());
registrar.discrete("/producer", this.producer, new StringValueMapper());
registrar.topic("/producer", this.producer.ref, new StringValueMapper());
}