-
Notifications
You must be signed in to change notification settings - Fork 71
AgentSpring Coding Instructions & Conventions
On this page, an overview is provided of the most important modelling conventions for EMLab. These provide a basis for understanding existing models, and for making choices regarding extending those models and creating new models. The conventions originate from 1) choices regarding java technology, 2) object-oriented programming practices, and 3) experience with ABM. The conventions aim to provide some means to develop well-structured, easy to understand, and modular/maintainable models.
The modelled system is reflected in a so-called graph database, i.e. a set of vertices (nodes) and edges (links) between those nodes. The database can save vertices with various types of parameters, i.e. all primitives (String, int, long, double, boolean) and arrays of primitives (e.g. double[]). Links between objects are stored as edges. Repositories and queries Accessing the database for retrieving data is by a query. Queries are stored in java classes and interfaces in the repository package. There are various languages possible:
-
Repository interfaces contain queries in two common query languages: Gremlin and Cypher. Cypher are often easier to read then Gremlin queries. However, Gremlin is good for more advanced queries and it is also the language used in the web interface for making graphs, although the format is slightly different.
-
The query is entered as a string in the annotation of a method. To execute the query you call the method. AgentSpring will read the query annotated and execute it with the parameters supplied.
-
An example query that finds the ClearingPoint (the price an volume) on a certain market for a certain time is this:
@Query(value = "g.v(market).in('MARKET_POINT').filter{it.time==tick}.next", type = QueryType.Gremlin)ClearingPoint findClearingPointForMarketAndTime(@Param("market") DecarbonizationMarket market, @Param("tick") long tick); -
The query starts with g, that is the graph (the system), it finds vertex market, that is the first parameter of DecarbonizationMarket, it finds the incoming edges labeled with MARKET_POINT, that is the name of the label to the ClearingPoint object, and it filters on time equal to tick, which is the second parameter given to the method.
-
A third query language, used not in java interfaces but java classes is making us of Java pipes and filters. Although is makes use of only Java as a language, it is less straightforward to develop queries than in the two languages described above. Some older queries are still in this format though.
-
Making queries is a matter of a lot of trying and smart copy pasting.
Creating new objects that need to be put in the database, other than the ones created at the start of the simulation, need to be persisted.
-
The code that changes an object that is already existing in the database needs to be marked, in order to make sure the change is reflected in the database. Marking such parts of code is done by the annotation @Transactional, explained below.
-
In order to make sure that new objects are reflected in the database requires an additional step. Create an object and add .persist() when constructed. Persisting is not necessary at the constructor, it can be done later (see below). Make sure that you can only use persist, when it is in a transaction, so the piece of code (thee method) should be annotated @Transactional, as already mentioned above. This would persist a new energy producer:
PowerPlant plant = new PowerPlant().persist(); -
Although the method above works, we adopted an adjusted convention to streamline this process and make it more failsave. The convention is to use methods in the domain classes themselves (in this case in the PowerPlant class), and label them according to the following convention:
- Somewhere in the code, where you want to create a power plant, one just creates a non-persistent power plant:
PowerPlant plant = new PowerPlant(); - A transactional method called specifyAndPersist exists in the PowerPlant class, which takes in this new PowerPlant plant, persists it, and fills in all the properties.
- A non-transactional method called specifyNotPersist exists in the PowerPlant class, which can be used instead of the previous one when one does not want to persist the new object.
- Somewhere in the code, where you want to create a power plant, one just creates a non-persistent power plant:
-
This convention allows to choose where to open and close the transactions (see the discussion on the efficiently using transactions).
Things in the model – agents, power plants, bids, markets – are reflected in classes in the domain package.
- Each type of thing is a java class, containing the properties that such things can have and the links it can have with other types of things.
- Links between classes can be of two types:
-
Classes can be linked as a property of one of the classes. Such links need to be annotated with @RelatedTo (see below). An example is the relation between agents and contracts. It is not always clear which direction the link should be; the the convention is to be consistent throughout the model. Here we chose to make the link in the Contract class, to the selling Agent from the DecarbonizationAgent class:
@RelatedTo(type = "CONTRACT_FROM", elementClass = DecarbonizationAgent.class, direction = Direction.OUTGOING)private DecarbonizationAgent from; -
Classes can also be linked by inheritance, i.e. more specific classes with more properties can extend less specific classes. An example is that a ElectricitySpotMarket extends the more generic DecarbonizationMarket. All markets have properties so there can be traded, but the ElectricitySpotMarket has some properties that are unique to the functioning of such markets.
-
@NodeEntity
public class EnergyProducer extends DecarbonizationAgent implements Agent {}
- All domain classes need to be annotated with @NodeEntity (see the example above and the description below)
- Agent classes need to extend the AbstractAgent class and need to implement the Agent interface, in order to inherit the method that allows to execute behavior (the act method, see the example above and the description below).
- Make sure that for all properties and links, there are so-called getter and setter methods. Do not include methods other than those that deal with the object itself. So use getters, setters, specifyAndPersist, specifyNotPersist, updateProperties. But do not include methods on the interaction of methods.
- Do not include methods with logic. Logic should be connected to behavior, which is captured in Role classes (see below).
- Domain classes and properties may have long names. The convention is to be rather precise and specific in the naming of classes and properties, in order to improve their readability and intuitive undestanding.
Use Javadoc conventions for making in-code comments and remarks:
\\TODOfor unfinished code,\** *\on top of every method, describing what it does and what parameters it uses\\lines of comments that explain specific small pieces code.
Behavior of agents is not contained in agent classes, but in Role classes. Each Role class contains a behavioral module, which can be executed by a specific types of agents.
-
Role classes need to end with Role.
-
Role classes must have the act method, which executes the behavior. The agent type needs to be the single parameter of the model: The act method typically retrieves data from the database, determines some action and then saves the result of the action to the database.
public void act(DecarbonizationModel model) {} -
Links between role classes can be of two types:
-
Role classes can be linked by initiating others. The initiating role class has a property named after the role-to-be-initated. In its act method, the property can be used. An example is below the link to:
@Autowiredprivate DetermineFuelMixRole determineFuelMixRole; -
And in the act method:
otherAgent.act(determineFuelMixRole);
-
-
Role classes can be linked by inheritance, in order to share methods containing logic. Role classes need to be annotated with either @ScriptComponent or @RoleComponent (see also below)
- @ScriptComponent is used for role classes that need to be scheduled to drive the simulation, and are not necessarily initiated by other role classes.
- @RoleComponent is used for role classes that are initiated by others.
-
Role classes need to be able to access the database by the reps property, which contains again links to all repository classes:
@Autowiredprivate Reps reps; -
Role classes and their internal properties may have long names. The convention is to be rather precise and specific, in order to improve their readability and intuitive understanding.
-
Use Javadoc conventions as explained above.
All data, parameters and objects at the start of a simulation are contained in one or more scenarios. For instance, which agents are in the model and how much money they have, but also how fuel prices may develop.
- Scenarios are contained in the resources/scenarios folder.
- At the start of a simulation, a scenario can be selected through the web interface.
- All the objects described in the xml file are automatically created before the start of the simulation.
- Optional: some values can be put in a parameters.properties file. The link is created at the bottom of the xml file. Parameters formatted like this: ${parameter1} are replaced with the value found in the parameters.properties file on the line formatted like this: parameter1=5. In this case, it is replaced by 5.
- One set of units is used in the d13n model for each quantity. The units in use are MWh for energy, MW for energy flow, ton for mass, m3 for volume, euro for money. Other units in place are a combination of these.
Annotation is a special form of syntactic metadata that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. An annotation start with @.
The annotations used specifically for AgentSpring are:
- @NodeEntity for classes that are nodes in the system that need to be/could be persisted in the database. Therefore, any domain class needs to be annotated with @NodeEntity.
- @RelatedTo for parameters that refer to a link to another object. It will be reflected as an edge in the database. The direction of the edge, and class of the object on the end of the edge need to be specified.
-
@Transactional for methods that make a change in the database. When the method starts, a transaction to the database is opened. Any changes in objects that are reflected in the database, will be persisted when the method is done. The transaction is then closed. It is important to be aware when transactions are used:
- Opening a lot of transactions at the same time will slow the simulation down.
- Opening a lot of transactions after each other is also inefficient.
- Not closing the transaction before using a change is dangerous, because it is not yet persisted.
- Changing the objects the database (with a transaction) that you are currently looping over with a query is unsafe, you may not get all objects.
- @Query for methods that contain queries in either Gremlin or Cypher. The actual query is added as a string to the annotation (an example query is found below).
- @SimulationParameter for parameters that are put on the parameter panel in the web interface.
- @Autowired for a link to an object of a class that you do not create manually in the code, but that are created automatically. The most prominent example are the references to the Role classes. You need only one unique of those classes and somewhere it will be created automatically when needed.
- @ScriptComponent for classes that contain behavior that needs to be scheduled to drive the simulation.
- @RoleComponent for classes that contain behavior initiated by classes.