Model Scaffolding

Last updated on 16th July 2024

This is an experimental feature!

Experimental features are early versions for users to test before final release. We work hard to ensure that every available Simudyne SDK feature is thoroughly tested, but these experimental features may have minor bugs we still need to work on.

If you have any comments or find any bugs, please share with support@simudyne.com.

Overview

In order to create a model with the Simudyne SDK, you must generate the java files yourself, which can be time-consuming and is prone to small errors if you are not familiar with java.

The model scaffolding allows you to avoid having to create those files manually. This plugin can turn correct JSON files into a basic model structure. The simudyne-maven-plugin is the only component you need to make it work. This is currently only supported for models built with maven.

Installation

The plugin should be added to the POM in the build section:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
... Model properties here...
    <dependencies>
        ... dependencies here ...
    </dependencies>
    <build>
        <plugins>
            ... other plugins here ...
            <plugin>
                <groupId>simudyne</groupId>
                <artifactId>simudyne-maven-plugin</artifactId>
                <version>1.0-SNAPSHOT</version>
                <configuration>
                    <overwrite>false</overwrite>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Defining a correct JSON representation

In order to work, a correct JSON representation of a model must be provided to the plugin. Each representation must be held in one JSON file, but the plugin can be called to generate the models from several files at the same time.

The default location the plugin will look for JSON files is in the src/main/json/models (inside the current directory). You can manually set the input directory by passing the input parameter to the plugin.

<configuration>
    <input>my/new/path</input>
</configuration>

Defining a model in JSON

Inside the input directory, put the JSON files you want to use to generate models. Each file will generate a different model and must therefore contain all the necessary information to the creation of a model. The name of the json file will be the package name in which the generated files are put.

General structure

{
    "name": "myModel",
    "variables": {},
    "constants": {},
    "inputs": {},
    "fields": {},
    "longAccumulators": {},
    "doubleAccumulators": {},
    "globals": {},
    "agents": {},
    "messages": {},
    "links": {}
}

A JSON file representing a model must provide the name of the model. It can also define accumulators, model fields, the global states fields, inputs, constants, variables, the agents of the model, the links and the messages that will be used in the model. The name of the JSON file will be the name of the model package.

Variables, constants, inputs and fields structures

{
    "variables": {},
    "constants": {},
    "inputs": {},
    "fields": {}
}

The keys variables, constants, inputs, fields all represent java variables in the model. The only difference is their annotation. A variable defined in inputs will have the annotation @Input, those in variables will have @Variable, those in constants will have @Constant and the ones in fields will have no annotations.

Required fields for a java variable are

  • a name
  • a type
Optional fields for a java variable are
  • a default value
  • a display name
.

{
  "inputs": {
    "fieldWithDefault": "int = 1",
    "basicField": "String",
    "fieldWithDisplayName": "long = 2 , myField"
  }
}

The code above will generate the following java code inside your model:

    @Input
    int fieldWithDefault = 1;

    @Input
    String basicField;

    @Input(name = "myField")
    long fieldWithDisplayName = 2;

The member in the JSON can also be defined in a more explicit way as follow :

{
  "inputs": {
      "fieldName": {
          "type": "int",
          "default": 5,
          "displayName": " this will be displayed"
      }
  }
}

Note that a member of fields does not have any annotation and therefore cannot have a display name.

Globals structure

The globals key in the model allows you to define the variables, constants, inputs and fields that will be in the GlobalState of the model.

{
  "globals" : {
      "variables": {},
      "constants": {},
      "inputs": {},
      "fields": {}
  }
}

The code above will output the correct fields with the right annotations inside the GlobalState object on your model.

Accumulators structure

Accumulators must be defined separately in the JSON file depending on their type (Double or Long accumulator). An accumulator only needs a name, but will also accept a display name if provided. The keys doubleAccumulators and longAccumulators are used to define the accumulators.

To generate accumulators without display names:

{
  "longAccumulators": ["name1", "name2", "name3"],
  "doubleAccumulators": ["name4", "name5"]
}

To use with the display names :

{
  "longAccumulators": {
      "name1": "display name1",
      "name2": "display name2",
      "name3": "display name3"
  },
  "doubleAccumulators": {
      "name4": "display name4",
      "name5": "display name5"
  }
}

The messages and links must be defined on the same JSON file as the model, but they will be output as different files. A message/link only needs a name, but it can also have a type and fields.

There are 3 ways to define a message/link.

  • If the message/link takes fields :

    {
    "messages": {
      "MyMessage": {
            "fields": {}
      }
    },
    "links": {
      "MyLink": {
            "inputs": {}
      }
    }
    }
  • If the link/message is empty :

    {
    "messages": {
      "MyMessage": {}
    },
    "links": {
      "MyLink": {}
    }
    }

    This will create a link/message with the specified name but no other attribute (no fields, no type).

  • Messages can also have a type. A message type is used when the message is used to represent a single data field. For example a message might only have a single Long field, so would be a message of type Long. If a message doesn't have any fields, its good practice to define the message as being of type Empty.

    {
    "messages": {
      "MyMessage": "Empty"
    }
    }

For instance, the following code :

{
 "messages": {
       "Mg1": {
             "fields": {},
             "type": "Empty"
       },
       "Msg2": "Long",
       "Msg3": {}
   },
 "links": {
       "MyLink": {}
   }
}

will output the Messages.java file:

import simudyne.core.graph.Message;

public class Messages {
  public static class Msg1 extends Message.Empty {
  }
  public static class Msg2 extends Message.Long {
  }
  public static class Msg3 extends Message {
  }
}

and the Links.java file:

import simudyne.core.graph.Link;

public class Links {
  public static class MyLink extends Link {
  }
}

Agents structure

Agents are defined with their fields and the names of their actions. The actions generated will not contain any logic, they must be completed before running the code.

{
  "agents": {
      "Agent1": {
          "fields": {},
          "actions": ["action1_name", "action2_name"]
      }
  }
}

The JSON above will for instance output the following java file agent1.java:

package myImports;
import simudyne.core.abm.Action;
import simudyne.core.abm.Agent;

public class Agent1 extends Agent<myModel.Globals> {
  public static Action<agent1> action1_name =
    Action.create(
      Agent1.class,
      agent1 -> {
        // enter the action's body here
    });
  public static Action<agent1> action2_name =
    Action.create(
      Agent1.class,
      agent1 -> {
        // enter the action's body here
    });
}

Generation of the model files

Once the JSON files representing models are placed at the right location, the generation of the java files can begin.

In order to generate the java files from the JSON, run

mvn simudyne:jsonGen

This will fetch the JSON into the specified input directory and extract the information.

The java files will be generated into the directory src/main/java/models inside the Simudyne project.

The files corresponding to a model will be output in a subfolder with the same name as the input JSON file.

Troubleshooting

When running the mvn simudyne:jsonGen, you may encounter issues.

If some fields, actions, classes or files were not generated, please check the syntax and make sure the JSON file does not contain spelling errors.

If the plugin finds duplicate names in your model, an exception will be thrown and the conflicting names will be displayed, as well as their location.

If the plugin tries to create a file that already exists and the overwrite flag is not set on true, the plugin will throw an exception. The overwrite flag must be set to true to enable the old files to be replaced. This can be done in the POM :

<configuration>
    <overwrite>true</overwrite>
</configuration>