Tutorial Model Developers
JAMS Components-API
JAMS Data Types
The following JAMS data types are available:
- Attribute.Boolean
- Attribute.BooleanArray
- Attribute.Calendar
- Attribute.DirName
- Attribute.Document
- Attribute.Double
- Attribute.DoubleArray
- Attribute.Entity
- Attribute.EntityCollection
- Attribute.FileName
- Attribute.Float
- Attribute.FloatArray
- Attribute.Geometry
- Attribute.Interger
- Attribute.IntegerArray
- Attribute.Long
- Attribute.LongArray
- Attribute.Object
- Attribute.String
- Attribute.StringArray
- Attribute.TimeIntervall
JAMS Components
The main building block of any JAMS model is named component. A component is a JAVA class that implements three different methods (i.e. init, run and cleanup) that are triggered at corresponding runtime stages that a JAMS model iterates through. As indicated by their names, these methods are activated at the beginning, during and at the end of a component's lifetime. While the init and clean methods are executed only once, the run method can be activated several times, depending on the component's purpose and configuration.
Communication with the framework and other components is handled by arbitrary public attributes that fulfil two conditions: (i) they are of a valid JAMS data type and (ii) they are marked by special JAVA annotations, i.e. syntactic meta-information. These attribute annotations are used to define their I/O type (read, write), their default values, physical unit and boundaries (if numeric), and a short text describing the attribute's purpose. This information is used both for the runtime system be able to setup and interlink attributes and for the GUIs to provide support during the model design.
JAMS Context Components
A JAMS context is a special, compound-like component that can nest other components and contexts, named children. A context serves two main purposes: (i) it controls the execution of its children, and (ii) serves as a data store for its children, allowing for flexible data exchange between them. Depending on the purpose of a given context, it might invoke its children multiple times over a number of iterations (Figure left), only once if some predefined condition is satisfied (Figure center) or only once in a sequence (Figure right). As contexts are specialized components, they can be nested in other contexts, allowing the creation of complex component hierarchies and execution control structures. The following figure shows context examples.
Input/Output of JAMS Data
Data I/O in JAMS can be handled in two different ways. As a first option, a component can be used to read or write data from any data source, e.g. files or databases. In this case, the component developer has to take care of requesting all necessary information via the component's input data (e.g. file names), accessing the data source, and provide potential results (e.g. data read from a file) as output data. An advantage of this approach is a high flexibility in accessing external information. On the other hand, such components usually need to be adapted to certain models and data formats which limits their re-usability.
The second option to handle data exchange in JAMS is the usage of the DataStore software interface. This interface can be used to read/write data from/to an external data repository, which might be a file, a database system or a network resource. It provides functions to read and write datasets, each represented by an ordered list of data objects. The data objects are described by metadata, containing e.g. their type, boundaries, and meaning. The following figure gives an overview of the JAMS DataStore architecture, showing two DataStore types for reading (InputDataStore) and writing (OutputDataStore) data. In the case of InputDataStore, methods can be used to ask for (hasNext) and retrieve (getNext) available data (DataSet). Accordingly, OutputDataStore provides methods to write data to the underlying storage device.
The supported data types reflect the available JAMS data types and include numerical types, string and calendar types, and spatial geometries. In order to represent the latter, JAMS makes use of the Simple Feature Access standard as defined by the Open Geospatial Consortium.
To access data from a DataStore, its ultimate data source and software to access it needs to be identified and parameterized. This is done by special XML documents. Each of them defines one DataStore and serves the following purposes:
(1) identify and parameterize an I/O software component that can ultimately access a certain type of data source,
(2) define one or more data sources to be accessed by that component, and in the case of an InputDataStore,
(3) define how retrieved data will be formatted and presented.
The I/O software components can be provided by the user and are made available to JAMS via a plug-in mechanism. As an example, this could be a JAVA class which is able to read ASCII-formatted files containing time series information. Parameters could be the file name and the read buffer size.
Development of JAMS Components
Installing a Development Environment
JAMS comes with preconfigured NetBeans projects for easy setup of a development environment. To make use of these projects, please download the current version of NetBeans from http://netbeans.org for free. Please choose at least the JAVA SE package. In addition, the Java Development Kit (JDK) is required which can be downloaded at http://www.oracle.com/technetwork/java/javase/downloads/index.html.
Your NetBeans developement environment looks like this after starting it:
Setting up JAMS in NetBeans
To open the JAMS projects in NetBeans, please choose File -> Open Project, navigate to your JAMS installation folder and select the Components project from the dev sub-folder. Repeat the steps for the JAMS project. Your development environment has now been set up.
The Components project serves as a starting point for your own component development. A sample component (ExampleComponent) is already provided in the src folder of the project and can be used as a basis for component development. In order to compile the project, right-click on the Components project node and select the item Build . As a result, a component library (Components.jar) is being generated and copied to the JAMS library folder. When starting the JAMS Builder (or pressing the Reload button in JAMS Builder), the new component library is immediately shown in the component repository. The respository tree will show the new library containing ExampleComponent.
The JAMS project can be used to launch the various JAMS user interfaces (Builder, Launcher, CmdLine) directly from NetBeans. To set the interface, please right click the JAMS project node, select Set Configuration and choose your preferred interface. Alternatively, select the configuration from the NetBeans toolbar. To start JAMS, please right click the JAMS project node and choose Run. Alternatively, press the green start button from the NetBeans toolbar.
Debugging
The JAMS project serves not only as a software launcher, but also offers the option to easily debug your simulation component using the NetBeans debugger. Debugging can be used to reconstruct the functioning of existing components or to facilitate error search in the process of development. You can select positions in the source code by using breakpoints where the compiler is supposed to stop the model execution. Hence it is possible to view the values of the designated variables at this time. Moreover, the functioning of existing or developed component can be reconstructed line by line. Before starting the debug mode of the model, you have to set breakpoints in the component's source code at the positions which are of interest for you. To do so, open your component in NetBeans and click on the line number you would like to start debugging. In the following image, two breakpoints were set:
To start debugging, right-click your JAMS project and select Debug.
The model run will be stopped at the selected position. In the bottom window you can see the current allocation of the model's variables. In top left window there is the position in the source code.
Now you can reconstruct the functioning of the source code step by step. For this purpose, the following functions which can be be found in the Netbeans menu bar under the item Debug or directly in the menu bar are available:
- Finish Debug Session - ends the debug mode
- Continue - goes to the next breakpoint
- Step Over - goes to the next statement
- Step Into - goes into the statement, if possible. otherwise to the next statement
- Step Out
The functions Step Over and Step Into can be used to reconstruct the functioning of the source code step by step. The line in green shade marks the current position which is being analysed.
By clicking on the button Continue you can go to the next breakpoint (red-shaded line).
Integrating the J2000 Project
In order to integrate the existing J2000 components in NetBeans, you need the J2000 source files which come as part of the JAMS sources available in the Downloads section at http://jams.uni-jena.de. After unpacking the JAMS-X_XX-src.tgz archive to your favourite disc location you will find a number of folders containg JAVA source files and libraries needed to run JAMS.
The J2K folder contains the current set of J2000 component sources. To integrate them into a NetBeans project, please apply the following steps:
- In NetBeans: File -> New Project -> Java Project with Existing Sources
- Indicate project name (e.g. J2000) and directory (e.g. c:\nbprojects\J2000) (the compiled classes and the JAR file are stored here) -> Next
- Source Package Folder -> Add Folder -> c:\JAMS-X_XX-src\J2000\src -> OK
- Finish import
- Include libraries: right click on J2000/Libraries -> Add JAR/Folder... and select the JAMS libraries jams-api.jar and jams-main.jar from the subdirectory lib in your JAMS installation path.
- Right click on J2000 -> Clean and Build (Compile project and create J2000.jar in c:\nbprojects\J2000\dist\)
To use the compiled library in your model, please add the compiled J2000.jar to your JAMS library path or copy J2000.jar to the lib sub-folder of your JAMS installation.
Integrating the JAMSComponents Project
According to the steps described above, plaease create a new NetBeans project (Java Project with Existing Sources), this time adding the src folder from the JAMSComponents sub-folder of the JAMS source tree.
Development of New Process Components in JAVA
Preconfigured Netbeans project
In order to develop new simulation components without setting up the JAMS/J2000 development environment, a preconfigured project can be used. The project is part of the standard JAMS download and can be seperately downloaded here: File:ExampleProject.zip. (When downloading the project seperately, the extracted "ExampleProject" directory must be copied into <JAMS installation>/data directory.
In order to use the project, the File->Open Project command must be selected from the NetBeans menu and the project folder must be selected from the <JAMS installation>/data directory.
Structure of a Component
Every JAMS process component is structured like this:
1. package name. It is automatically generated by NetBeans.
package org.unijena.j2k.interception;
2. Import of libraries/classes which are required for the process components.
import jams.data.*; import jams.model.*; ...
3. Starting the process component. Every process component starts with a component description. In this way, new metadata of the component can be stored in the source code. It should include information on the component title, the author, version and modification date. In addition, a short description of the component is required.
@JAMSComponentDescription( title="...", author="...", description="...", version="...", date="..." )
public class NewClass {
4. Set of variables with public and private variables. The public variables can be accessed from other classes within a model. For every public variable remarks in the form of Annotations are included in the source code.
The access type (AccessType) JAMSVarDescription.AccessType.READ (variables are only read), JAMSVarDescription.AccessType.WRITE (variables are only written) or JAMSVarDescription.AccessType.READWRITE (variables are read and written) can be chosen.
In addition, a short description as well as a measurement unit (if possible) and an upper and lower boundary of the variable is required.
@JAMSVarDescription( access =... , description = "...", unit = "...", lowerBound= ..., upperBound =... ) public JAMSDouble var1; @JAMSVarDescription( access =... , description = "...", unit = "...", lowerBound= ..., upperBound =... ) public JAMSInteger var2; @JAMSVarDescription( access =... , description = "...", unit = "...", lowerBound= ..., upperBound =... ) public JAMSString var3; ... private JAMSDoublr var4; ...
Every JAMS- or J2000-class has the procedures init(), run() and cleanup() available.
5. The init procedure is passed during the initialization of the model.
public void init() throws JAMSEntity.NoSuchAttributeException{ ... }
6. The run procedure is passed during the model run.
public void run() throws JAMSEntity.NoSuchAttributeException{ ... }
7. The cleanup procedure is passed at the end of a model run.
public void cleanup() throws JAMSEntity.NoSuchAttributeException{ ... }
8. Private procedures and functions. These are only available within the process component.
private double funktion1(double var){ ... return ...; }
9. End of process component.
}
Example of a Complete Process Component
1. Package name
package org.unijena.j2k.potET;
2. Imported libraries
import java.io.*; import jams.data.*; import jams.model.*;
3. Starting the process component. Every process component starts with a component description. In this way, new metadata of the component can be stored in the source code. It should include information on the component title, the author, version and modification date. In addition, a short description of the component is required.
@JAMSComponentDescription( title=""CalcDailyETP_PenmanMonteith"", author="Peter Krause", description="Calculates potential ETP according Penman-Monteith", version="1.0_0", date="2011-04-11" )
public class Penman extends JAMSComponent {
4. Set of variables with public and private variables. The public variables can be accessed from other classes within a model. For every public variable remarks in the form of Annotations are included in the source code.
The access type (AccessType) JAMSVarDescription.AccessType.READ (variables are only read), JAMSVarDescription.AccessType.WRITE (variablen are only written) or JAMSVarDescription.AccessType.READWRITE (variables are read and written) can be chosen.
In addition, a short description as well as a measurement unit (if possible) and an upper and lower boundary of the variable is required.
@JAMSVarDescription(access = JAMSVarDescription.AccessType.READ, description = "Current time") public Attribute.Calendar time; @JAMSVarDescription(access = JAMSVarDescription.AccessType.READ, description = "temporal resolution [d | h | m]") public Attribute.String tempRes; @JAMSVarDescription(access = JAMSVarDescription.AccessType.READ, description = "state variable wind") public Attribute.Double wind; @JAMSVarDescription(access = JAMSVarDescription.AccessType.READ, description = "state variable mean temperature") public Attribute.Double tmean; @JAMSVarDescription(access = JAMSVarDescription.AccessType.READ, description = "state variable relative humidity") public Attribute.Double rhum; @JAMSVarDescription(access = JAMSVarDescription.AccessType.READ, description = "state variable net radiation") public Attribute.Double netRad; @JAMSVarDescription(access = JAMSVarDescription.AccessType.READ, description = "attribute elevation") public Attribute.Double elevation; @JAMSVarDescription(access = JAMSVarDescription.AccessType.READ, description = "attribute area") public Attribute.Double area; @JAMSVarDescription(access = JAMSVarDescription.AccessType.WRITE, description = "potential ET [mm/ timeUnit]") public Attribute.Double potET; @JAMSVarDescription(access = JAMSVarDescription.AccessType.WRITE, description = "actual ET [mm/ timeUnit]") public Attribute.Double actET; @JAMSVarDescription(access = JAMSVarDescription.AccessType.READ, description = "et calibration parameter") public Attribute.Double et_cal; @JAMSVarDescription(access = JAMSVarDescription.AccessType.READ, defaultValue = "0") public Attribute.Integer dataCaching; private File cacheFile; transient private ObjectOutputStream writer; transient private ObjectInputStream reader; public final double CP = 1.031E-3; //konstanter Parameter public final double RSS = 150; //konstanter Parameter
5. The init procedure is passed during the initialization of the model.
public void init() throws JAMSEntity.NoSuchAttributeException, IOException { cacheFile = new File(getModel().getWorkspace().getTempDirectory(), this.getInstanceName() + ".cache"); if (!cacheFile.exists() && (dataCaching.getValue() == 1)) { getModel().getRuntime().sendHalt(this.getInstanceName() + ": dataCaching is true but no cache file available!"); } if (dataCaching.getValue() == 1) { reader = new ObjectInputStream(new BufferedInputStream(new FileInputStream(cacheFile)));//new FileInputStream(cacheFile)); } else if (dataCaching.getValue() == 0) { writer = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(cacheFile))); } }
6. The run procedure is passed during the model run.
public void run() throws JAMSEntity.NoSuchAttributeException, IOException { if (dataCaching.getValue() == 1) { this.potET.setValue(reader.readDouble()); this.actET.setValue(0.0); } else { double netRad = this.netRad.getValue(); double temperature = this.tmean.getValue(); double rhum = this.rhum.getValue(); double wind = this.wind.getValue(); double elevation = this.elevation.getValue(); double area = this.area.getValue(); double abs_temp = org.unijena.j2k.physicalCalculations.ClimatologicalVariables.calc_absTemp(temperature, "degC"); double delta_s = org.unijena.j2k.physicalCalculations.ClimatologicalVariables.calc_slopeOfSaturationPressureCurve(temperature); double pz = org.unijena.j2k.physicalCalculations.ClimatologicalVariables.calc_atmosphericPressure(elevation, abs_temp); double est = org.unijena.j2k.physicalCalculations.ClimatologicalVariables.calc_saturationVapourPressure(temperature); double ea = org.unijena.j2k.physicalCalculations.ClimatologicalVariables.calc_vapourPressure(rhum, est); double latH = org.unijena.j2k.physicalCalculations.ClimatologicalVariables.calc_latentHeatOfVaporization(temperature); double psy = org.unijena.j2k.physicalCalculations.ClimatologicalVariables.calc_psyConst(pz, latH); double G = this.calc_groundHeatFlux(netRad); double vT = org.unijena.j2k.physicalCalculations.ClimatologicalVariables.calc_VirtualTemperature(abs_temp, pz, ea); double pa = org.unijena.j2k.physicalCalculations.ClimatologicalVariables.calc_AirDensityAtConstantPressure(vT, pz); double tempFactor = 0; double pET = 0; double aET = 0; if (this.tempRes.getValue().equals("d")) { tempFactor = 86400; } else if (this.tempRes.getValue().equals("h")) { tempFactor = 3600; } else if (this.tempRes.getValue().equals("m")) { tempFactor = 86400; } double Letp = 0; Letp = this.calcPM(delta_s, netRad, G, pa, CP, est, ea, psy, tempFactor, wind); pET = Letp / latH; aET = 0; //converting mm to litres pET = pET * area; //aggregation to monthly values if (this.time != null) { if (this.tempRes.getValue().equals("m")) { int daysInMonth = this.time.getActualMaximum(time.DATE); pET = pET * daysInMonth; } } //avoiding negative potETPs if (pET < 0) { pET = 0; } this.potET.setValue(pET*et_cal.getValue()); this.actET.setValue(aET); if (dataCaching.getValue() == 0) { writer.writeDouble(pET*et_cal.getValue()); } } }
7. The cleanup procedure is passed at the end of a model run.
public void cleanup() throws IOException { if (dataCaching.getValue() == 0) { writer.flush(); writer.close(); } else if (dataCaching.getValue() == 1) { reader.close(); } }
8. Private procedures and functions. These are only available within the process component.
private double calcPM(double ds, double netRad, double G, double pa, double CP, double est, double ea, double psy, double tempFactor, double wind){ double fu = (0.27 + 0.2333 * wind); double Letp = (ds * (netRad - G) + (pa * CP * (est - ea) * fu)) / (ds + psy); return Letp; } private double calc_groundHeatFlux(double netRad) { double g = 0.1 * netRad; return g; }
9. End of process component.
}
Development and Implementation of JAMS-Context Components
Integrating New Process Components into JAMS or J2000 Libraries
In order to add your generated process component to the existing JAMS or J2000 components, generate a new *.jar of the project where the component is located. Right-click on the project and select the items Build and Clean. Now integrate this *.jar as a library, where you will find your new component.
Integrating and Testing New (Context) Components
In order to integrate new (context) components into existing models or to use them for new models, the tool JUICE is available for designing models. In section 1.2 Application of JUICE for Model Design and Model Configuration in the Tutorial Advanced Users all tasks for the integration of components are explained. By using the JUICE the model can be started and new components can be tested.
Debugging and Profiling of Models in JAMS
Debugging
In order to reconstruct the functioning of existing (JAMS/J2000) components and those which have been developed or to facilitate error search in the process of development, you have the debugging function at your disposal. You can select positions in the source code by using breakpoints where the compiler is supposed to stop the model run. Hence it is possible to view the values of the designated variables at this time. Moreover, the functioning of existing or developed component can be reconstructed line by line. Before starting the debug mode of the model, you have to set breakpoints at the positions which are interesting for you. Therefore, click on the red point in the menu bar and afterwards on the position in the source code. In the following image, two breakpoints were set:
If your component has a main procedure, you can start the debugging directly. If your component is part of a complex model, you have to debug the entire model. In this case, right-click on the project which is supposed to start (the project which contains the component) and select debug.
The model run will be stopped at the selected position. In the bottom window you can see the current allocation of the model's variables. In top left window there is the position in the source code.
Now you can reconstruct the functioning of the source code step by step. For this purpose you have the following functions, which can be be found in the Netbeans menu bar under the item Debug or directly in the menu bar, are available:
- Finish Debug Session - ends the debug mode
- Continue - goes to the next breakpoint
- Step Over - goes to the next statement
- Step Into - goes into the statement, if possible. otherwise to the next statement
- Step Out
The functions Step Over and Step Into can be used to reconstruct the functioning of the source code step by step. The line in green shade marks the current position which is being analysed.
By clicking on the button Continue you can go to the next breakpoint (red-shaded line).
Profiling in Netbeans
Netbeans offers the possibility of reconstructing the models' runtimes, i.e. which part of the model requires how much time. In this way, time-consuming implementations can be identified.
For this purpose, right-click on the project which is supposed to be analysed. Select the main class under item Run in Properties where the profiling should take place.
Now select the item Profiling.
The following window opens. Now you can choose between analysing the complete source code or parts of the source code. The following options are available for filtering:
- Profile all classes
- Profile only project classes
- Exclude Java core classes
- Quick Filter
Click on run.
The runtime analysis is being carried out. The following window appears.
The result of the analysis is shown in Netbeans. In the left window general information on your profiling is displayed. In the top right window the runtimes of the individual functions within the selected main classes are displayed.