Table of contents

1. Introduction

Although it may appear a huge challenge to turn a model engine code (the computational heart of a model) into an OpenMI-compliant linkable component, it may not be as difficult as it seems. The OpenMI Software Development Kit (SDK) provides a large number of software utilities that make migration easier. These utilities can be used by anyone migrating a model but are not required in order to comply with the OpenMI standard. The SDK can be used as a whole or you can select only part of it; alternatively, you can use the SDK as the basis for your own implementations. This article assumes that you will use the OpenMI SDK to the full extent. Step-by-step instructions are given for the whole migration process, from defining the requirements for an OpenMI component, through design and implementation to testing.

This chapter describes the requirements for OpenMI-compliance and introduces the Simple River model, which is used to illustrate the migration process.

1.1. OpenMI compliance

The official requirements for OpenMI compliance are:

There are two variants of OpenMI compliance. Component can be either OpenMI 1.4 .Net compliant or OpenMI 1.4 Java Compliant.

  • OpenMI .Net compliant components must follow the compliance definition given in the comments in the file ILinkableComponent.cs

  • OpenMI Java compliant components must follow the compliance definition given in the comments in the file ILinkableComponent.java

However, when you use the software development kit provided by the OpenMI Association Technical Committee (Oatc.OpenMI.Sdk) most of the requirements for compliance will automatically be taken care of.

1.2. The simple river example

A Simple River model engine was developed as an example of model migration. The model engine is programmed in Fortran and is a very simple conceptual river model. The Simple River consists of nodes and branches, as shown in Figure 1. For each timestep, the inflow to each node is obtained from a boundary-input file. These flow rates are multiplied by the timestep length and added to the storage in each node. Then, starting from the upstream end, the water is moved to downstream nodes and the flow rate in each branch is calculated.

(lightbulb) Source code for the Simple River model (FORTRAN engine and C# wrapper) is included in the OpenMI 1.4.0.0 SDK release, which can be downloaded from:  http://sourceforge.net/projects/openmi/ 

Fig 1. Simple River network

The Simple River engine reads data from three input files, which contain information about the inflow to the river nodes (boundary file), the simulation period and timestep length (simulation file) and the river network (network file) - see Figure 2.


Fig 2. Simple River input and output files

2. Planning the migration

Before you start migrating a model it is important that you have a precise idea about how your model is intended to be used when it is running as an OpenMI component. Think about any situation where it will be useful to run your model linked to other OpenMI components. Such components could be other models, data providers, optimization tools or calibration tools. You may even find it useful to run two instances of your model component in the same configuration.

This chapter suggests ways in which you can plan the migration of a model, including the development of use cases and the definition of exchange items.

2.1 Use cases

Use cases (examples of how software is used) have become very popular in software development. There are no formal requirements for defining a use case. However, what makes a use case different from an example is that a use case is more detailed and well defined. Most importantly, a use case must be formulated in such a way that, after completion of software development, you can unambiguously determine whether the use case is covered or not. The big advantage of use cases is that they are easily understood both by the software developer and the software user. At the beginning of the development process, a number of use cases should be defined. It is important that the repository of use cases at any time, in all areas of the software development, reflects the current target. If a particular use case cannot be fulfilled it should be modified or removed. Two use cases for the migrated Simple River model are given below. The use cases give a step-by-step description of how a user will use the models.

2.1.1 Use case 1: Connecting to other rivers

In the first use case, the Simple River model is connected to another OpenMI-compatible river
model (Figure 3).

Fig 3. Use case 1: Connecting to other rivers

Preconditions:

  • The model user has the OpenMI-compliant Simple River model installed on his PC.
  • The model user has input files for the Simple River model available on his PC.
  • The model user has an OpenMI configuration user interface installed on his PC.
  • The model user has another OpenMI-compliant river model (including required datafiles) available on his PC.

Success guarantee (postconditions):

  • All models in the linked OpenMI configuration have generated correct results.

Main success scenario:

  1. The model user loads the OpenMI Configuration editor on the PC.
  2. The model user uses the configuration editor to browse for available LinkableComponents.
  3. The model user finds the Simple River OMI file and the OMI file for the other river model.
  4. The model user loads the two files (components) into the configuration editor.
  5. The model user creates a unidirectional and ID-based link from the downstream node in the other river model to the upstream node in the Simple River.
  6. The model user selects input and output exchange items for the link (input quantity for the Simple River is 'Inflow').
  7. The model user defines the simulation period.
  8. The model user runs the simulation (runs the OpenMI configuration).

Extensions to the use case provide alternative flows. Here, the flow splits from step 5 into two
alternatives.

First alternative:

  • 5. The model user creates a unidirectional and ID-based link from the downstream branch in the Simple River model to the upstream node in the other river model.
  • 6. The model user selects input and output exchange items for the link (output quantity for the Simple River is 'flow').
  • 7. The model user defines the simulation period.
  • 8. The model user runs the simulation.

Second alternative:

  • 5. The model user creates a unidirectional and ID-based link from the downstream branch in the other river model to an internal node in the Simple River model.
  • 6. The model user selects input and output exchange items for the link (input quantity for the Simple River is 'Inflow').
  • 7. The model user defines the simulation period.
  • 8. The model user runs the simulation (runs the OpenMI configuration).

2.1.2 Use case 2: Inflow from geo-referenced catchment database

In the second use case, the inflow for the Simple River model comes from an OpenMIcompliant
runoff database (Figure 4).


Fig. 4 Inflow from catchments

Preconditions:

  • The model user has the OpenMI-compliant Simple River model installed on his PC.
  • The model user has input files for the Simple River model available on his PC.
  • The model user has an OpenMI configuration editor installed on his PC.
  • The model user has an OpenMI-compliant runoff database (including required data files) available on his PC.

Success guarantee (postconditions):

  • All models have generated correct results.

Main success scenario:

  1. The model user loads the OpenMI Configuration editor on the PC.
  2. The model user uses the configuration editor to browse for available LinkableComponents.
  3. The model user finds the Simple River OMI file.
  4. The model user finds the OMI file for the runoff database.
  5. The model user loads the two files (components) into the configuration editor.
  6. The model user creates a unidirectional and geo-referenced link from the runoff database to 'All Branches' input exchange item in the Simple River model.
  7. The model user selects input and output exchange items for the link (input quantity for the Simple River is 'Inflow').
  8. The model user defines the simulation period.
  9. The model user runs the simulation.

Note that the runoff for a particular polygon is distributed on the river branches depending on how large a portion of a branch is included in each polygon. This type of boundary condition, where water is added to branches, was not possible in the original Simple River engine. The Simple River engine is (as a result of the migration) extended with this feature, simply because such a boundary condition becomes a possibility when running in combination the OpenMI.

2.2 Defining exchange items

Exchange items are combined information about what can be exchanged and where the exchanged item applies. An input exchange item could define that inflow can be accepted on nodes or river branches. An output exchange item could specify that flow can be provided on branches. The Quantity ID identifies what can be exchanged (e.g. 'Flow') and the ElementSet ID identifies where this quantity applies (e.g. 'Node:1').
The next step is to define input and output exchange items. The exchange items that are required in order to run the use cases are listed in Table 1.


Table 1. Required exchange items for use cases 1 and 2

Naturally, the exchange items should not be limited to a particular network, but for the purpose of planning the migration it is easier to start out with a specific case and then generalize this case when it comes to the more detailed design or implementation.

3. Wrapping

The OpenMI standard was designed to allow easy migration of existing model engines. The standard is implemented in C# running under the .NET framework. Almost all existing model engines are implemented in other programming languages, such as Fortran, Pascal, C and C++. In order to bridge the gap between the different technologies and to minimize the amount of changes needed to be made to the engine core a wrapping pattern will be the most attractive choice in most cases.

This chapter describes the process of wrapping and the generic wrapper that is provided by the OpenMI Software Development Kit (SDK).

3.1. A general wrapping pattern

Wrapping basically means that you create a C# class that implements the ILinkableComponent interface. This wrapper will communicate internally with your engine core. The wrapper will appear to the users as a 'black box', which means that all
communication will take place through the ILinkebleComponent interface (Figure 5).


Fig. 5 OpenMI wrapping pattern

One further advantage of using the wrapping pattern is that you can keep the OpenMI specific implementations separated from your engine core. Typically, the engines will also be used as standalone applications where OpenMI is not used and it is naturally an advantage to be able to use the same engine in different contexts. This means that even in situations where new engines are built the wrapping pattern may still be the best choice.

3.2 The LinkableEngine

Model engines that are doing timestep-based computations have many things in common. It is therefore possible to develop a generic wrapper that can be used for these engines. This wrapper is called LinkableEngine and is located in the Oatc.OpenMI.Sdk.Wrapper package. Basically, the LinkableEngine provides a default implementation of the OpenMI.Standard.ILinkableComponent interface. Naturally, the LinkableEngine cannot know the specific behavior of your model engine; this information is obtained though the IEngine interface.
The recommended design pattern for model engine migration when using the LinkableEngine is shown in Figure 6. The design includes the following classes:

  • The MyEngineDLL is the compiled core engine code (compiled dll from e.g. Fortran).

  • The MyEngineDLLAccess class is responsible for translating the Win32Api from MyEngineDLL to .NET (C#).

  • Calling conventions and exception handling are different for .NET and Fortran. The MyEngineDotNetAccess class ensures that these operations follow the .NET conventions.

  • The MyEngineWrapper class implements the IEngine interface, which means that it can be accessed by the LinkableEngine class.

  • The MyLinkableEngine class is responsible for the creation of the MyEngineWrapper class and for assigning a reference to this class to a protected field variable in the LinkableEngine class, thus enabling this class to access the MyEngineWrapper class. The MyLinkableEngine class implement the OpenMI.Standard.IlinkableComponent interface and therefor is the LinkableComponent (the OpenMI compliant component).

More details of these classes are provided in the following sections.
The OpenMI standard puts a lot of responsibilities on the LinkableComponents. The main idea is that when the GetValues method is invoked the providing component must be able to deliver the requested values so that these apply to the requested time and the requested location. To be able to do this the LinkableComponent may have to interpolate, extrapolate or aggregate both in time and space. These and other things are handled by the LinkableEngine.

The LinkableEngine class includes the following features:

  • Buffering: When a model is running as an OpenMI component it may be queried for values that correspond to a time that is before the current time of the model. Most models will only keep values for the current timestep and the previous timestep in memory. It is therefore necessary to store data associated with the OpenMI links in a buffer. The LinkableEngine handles the buffering for you.

  • Temporal interpolation and extrapolation: Most models are only capable of delivering results at times that correspond to their internal timesteps. The LinkableEngine class handles all the temporal operations that are required for LinkableComponents.

  • Spatial operations: The LinkableEngine provides a range of spatial data operations.

  • Link book-keeping: The LinkableEngine handles book-keeping for links added to your component.

  • Event handling: The LinkableEngine sends events that enable an event-listener to monitor the progress of the linked system when running.

More details about how the LinkableEngine works is given in OATC.OpenMI.SDK technical documentation.

4. Migration - step by step

The best strategy when migrating a model is to split the process into a number of steps; at the end of each step you can compile your code and run a small test.

The steps needed for migration are described in this chapter.

4.1 Step 1: Changing your engine core

The aim of the migration is to develop a class that implements the IEngine interface. As shown in Figure 6, the class that implements the IEngine interface is supported by other classes and the engine DLL.


Fig 6. Wrapping classes and engine core DLL

Model engines are typically compiled into an executable file (EXE). Such executable files are not accessible by other components and as such are not very suitable as a basis for OpenMI components. It is therefore necessary for your engine to be compiled into a dynamic link library file (DLL).
Ideally you should make modifications to your engines so that the same engine can be used both when running as an OpenMI component and when running as a standalone application. Having two versions of the same engine leads to unnecessary maintenance work. Therefore you could make a new application (EXE) that calls a function in the engine core DLL which, in turn, makes your engine perform a full simulation.
Figure 7 illustrates the software required to run an engine as a standalone application. The SimpleRiverApplication.EXE file is never used when running in an OpenMI setting.


Fig. 7. Running an engine as a standalone application

The following steps are required in the conversion of the engine core:

  1. Change the engine core so that it can be compiled into a DLL.

  2. Add a function to the engine core that will run a full simulation: logical function RunSimulation()

  3. Create an engine application (EXE) that from its main program calls the RunSimulation function in your engine core DLL.

  4. Run your engine by deploying the engine application and check that the engine is still producing correct results.

When your engine is running in the SDK it must be able to initialize, perform single timesteps, finalize and be disposed as separate operations. This means that your engine core may need to be reorganized. You can do this in any way you like but one logical approach is to create four functions:

logical function Initialize()
(Open files and populate your engine with initial data)

ogical function PerformTimeStep()
(Perform a single timestep)

logical function Finish()
(Close files)

logical function Dispose()
(De-allocate memory)

The RunSimulation function should now be changed so that it calls the Initialize function, then repeatedly calls the PerformTimeStep function until the simulation has completed, and finally calls the Finish and Dispose functions.
At this point you should run your application again and check that the engine is still producing the correct results.
You have now completed the restructuring of the engine. The remaining changes that you need to make to the engine will be much smaller. The nature of the changes will be dependent on the particular engine. For now, you can move on to creating the wrapper code.

4.2 Step 2: Creating the .Net assemblies

Before you can start creating your new projects you need to download the SDK and setup your development environment. Please follow the instruction given here: What should OpenMI 1.4.0.0 developers install

The next step is to create the wrapper classes (Figure 8).


Fig 8. C# wrapping classes

You should create one assembly for your wrapper classes and it is strongly recommended that you also create one assembly for the corresponding test classes.

You can use the following naming conventions for your wrapper assembly:

Assembly name: MyOrganisation.OpenMI.MyModel.Wrapper
Assembly DLL name: MyOrganisation.OpenMI.MyModel.Wrapper.dll
Namespace: MyOrganisation.OpenMI.MyModel.Wrapper
Class names: MyModelEngineWrapper, MyModelEngineDotNetAccess, MyModelEngineDLLAccess, MyModelLinkableComponent

Naming conventions for the test assembly:

Assembly name: MyOrganisation.OpenMI.MyModel.Wrapper.UnitTest
Assembly DLL name: MyOrganisation.OpenMI.MyModel.Wrapper.UnitTest.DLL
Namespace: MyOrganisation.OpenMI.MyModel.Wraper.UnitTest

Class names: MyModelEngineWrapperTest, MyModelEngineDotNetAccessTest, MyModelEngineDLLAccessTest, MyModelLinkableComponentTest

To the wrapper assembly, add the following references:

OpenMI.Standard
Oatc.OpenMI.Sdk.Backbone
Oatc.OpenMI.Sdk.Wrapper

To the test assembly, add the following references:
OpenMI.Standard
Oatc.OpenMI.Sdk.Backbone
Oatc.OpenMI.Sdk.Wrapper
NUnit.framework
MyOrganisation.OpenMI.MyModel.Wrapper

Note that if you have changed the namespace names for the SDK, the references above will be different.

After creating the assemblies and the classes, you can start working on the first class,
MyEngineDLLAccess. Details are given in the next section.

4.3 Step 3: Accessing the functions in the engine core

The third step is to implement the MyEngineDLLAccess class (Figure 9).


Fig 9. MyEngineDllAccess class

Because you are using a C# implementation of OpenMI, your engine needs to be accessible from .NET. In the pattern shown above this is handled in two wrappers, MyEngineDLLAccess and MyEngineDotNetAccess. The MyEngineDLLAccess class will make a one-to-one conversion of all exported functions in the engine core code to public .NET methods. The MyEngineDotNetAccess class will change some of the calling conventions.

The specific implementation of the MyEngineDLLAccess class depends on the compiler you are using. Start by implementing export methods for the Initialize, PerformTimeStep, and Finish functions.

The code listed below shows an example of such an implementation for the Simple River Fortran engine. Note that this implementation corresponds to a particular Fortran compiler; the syntax may vary between compilers.

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace Oatc.OpenMI.Examples.ModelComponents.SimpleRiver.Wrapper
{

	public class SimpleRiverEngineDllAccess
	{

		[DllImport(@"Oatc.OpenMI.Examples.ModelComponents.SimpleRiver.Engine.dll",
			 EntryPoint = "INITIALIZE",
			 SetLastError=true,
			 ExactSpelling = true,
			 CallingConvention=CallingConvention.Cdecl)]
		public static extern bool Initialize(string filePath, uint length);

		[DllImport(@"Oatc.OpenMI.Examples.ModelComponents.SimpleRiver.Engine.dll",
			 EntryPoint = "FINISH",
			 SetLastError=true,
			 ExactSpelling = true,
			 CallingConvention=CallingConvention.Cdecl)]
		public static extern bool Finish();


		[DllImport(@"Oatc.OpenMI.Examples.ModelComponents.SimpleRiver.Engine.dll",
			 EntryPoint = "PERFORMTIMESTEP",
			 SetLastError=true,
			 ExactSpelling = true,
			 CallingConvention=CallingConvention.Cdecl)]
		public static extern bool PerformTimeStep();

	}
}

4.4 Step 4: Implementing the MyEngineDotNetAccess

The fourth step is to implement the MyEngineDotNetAccess class (Figure 10).

Fig 10. MyEngineDotNetAccess class

The MyEngineDotNetAccess has two purposes: to change the calling conventions to C# conventions and to change error messages into .NET exceptions.

The listed code below shows the Simple River example code for a MyEngineDotNetAccess class that implements the Initialize method, the performTimeStep method and the Finish method. In each of these methods the corresponding method in the MyEngineDLLAccess class is called and, if this method returns false, the error message from the engine is queried through the GetMessage method (following which an exception is created and thrown).

Note that the MyEngineDLLAccess class is not instantiated. All the methods in this class are static and can be accessed directly by referencing the class. (Note that to make the example simpler, not all DLL import statements are shown.)

The normal convention for Fortran DLLs is that values are returned through the function (effectively, they are passed by reference); for C# results are passed by value. Therefore there may be a need to state explicitly that C# parameters passed to a Fortran routine are to be passed by reference. In Fortran, arrays normally start from index 1 whereas in C# the convention is to start from zero. These differences can be handled in the MyEngineDotNetAccess class. When you have completed the implementation of the methods shown below you can create and implement the corresponding test class and run the unit test.

using System;
using System.Text;

namespace Oatc.OpenMI.Examples.ModelComponents.SimpleRiver.Wrapper
{

	public class SimpleRiverEngineDotNetAccess
	{
		IntPtr _FortranDllHandle;

		public void Initialize(string filePath)
		{
            _FortranDllHandle = Kernel32Wrapper.LoadLibrary(@"Oatc.OpenMI.Examples.ModelComponents.SimpleRiver.Engine.dll");

            string curDir = System.IO.Directory.GetCurrentDirectory();

			if(!(SimpleRiverEngineDllAccess.Initialize(filePath, ((uint) filePath.Length))))
			{
				CreateAndThrowException();
			}
		}

        public void PerformTimeStep()
        {
            if (!(SimpleRiverEngineDllAccess.PerformTimeStep()))
            {
                CreateAndThrowException();
            }
        }

		public void Finish()
		{
			if(!(SimpleRiverEngineDllAccess.Finish()))
			{
				CreateAndThrowException();
			}
			while(Kernel32Wrapper.FreeLibrary(_FortranDllHandle));
		}
        
		private void CreateAndThrowException()
		{
			int numberOfMessages = 0;
			numberOfMessages = SimpleRiverEngineDllAccess.GetNumberOfMessages();
			string message = "Error Message from SimpleRiver Fortran Core ";

			for (int i = 0; i < numberOfMessages; i++)
			{
				int n = i;
				StringBuilder messageFromCore = new StringBuilder("                                                        ");
				SimpleRiverEngineDllAccess.GetMessage(ref n, messageFromCore, (uint) messageFromCore.Length);
				message +="; ";
				message += messageFromCore.ToString().Trim();
			}
			throw new Exception(message);
		}
	}
}

4.5 Step 5: Implementing the MyEngineWrapper class

The fifth step is to implement the MyEngineWrapper class (Figure 11).


Figure 11

The MyEngineWrapper class must implement the IEngine interface (Oatc.OpenMI.Sdk.Wrapper.IEngine). The easiest way to get started is to make your development environment auto-generate the stub code for this interface.

The first step is to implement the Initialize method and the Finish method.

The MyEngineWrapper has a private field variable (_myEngine) that holds a reference to the MyEngineDotNetAccess class. The Initialize method will instantiate the MyEngineDotNetAcccess object and assign a reference to this object to the _myEngine variable.

Example code for this is shown below.

using System;
using System.Collections;
using OpenMI.Standard;
using Oatc.OpenMI.Sdk.Backbone;
using Oatc.OpenMI.Sdk.Wrapper;


namespace Oatc.OpenMI.Examples.ModelComponents.SimpleRiver.Wrapper
{
	public class SimpleRiverEngineWrapper : Oatc.OpenMI.Sdk.Wrapper.IEngine
	{
		private SimpleRiverEngineDotNetAccess _simpleRiverEngine;

		public void Initialize(System.Collections.Hashtable properties)
		{
            _simpleRiverEngine = new SimpleRiverEngineDotNetAccess();
            _simpleRiverEngine.Initialize(properties["FilePath"].ToString());
		}

        public void Finish()
        {
            _simpleRiverEngine.Finish();
        }
	}
}

4.6 Step 6: Implementing the MyModelLinkablComponent

The sixth step is to implement the MyModeLinkableComponent class (Figure 12).


Figure 12

The MyModelLinkableComponent is the OpenMI-compliant linkable component that is going to be accessed by other models. Implementation of this class is very simple, since it will inherit lots of functionality from the Oatc.OpenMI.Sdk.Wrapper.LinkableEngine class. The example code shown below is the complete implementation for the Simple River model.

using System;

namespace Oatc.OpenMI.Examples.ModelComponents.SimpleRiver.Wrapper
{
	public class SimpleRiverOpenMIComponent : Oatc.OpenMI.Sdk.Wrapper.LinkableEngine
	{
		public SimpleRiverOpenMIComponent()
		{
			_engineApiAccess = new SimpleRiverEngineWrapper();
		}

		protected override void SetEngineApiAccess()
		{
			_engineApiAccess = new SimpleRiverEngineWrapper();
		}
	}
}

This class inherits from the LinkableEngine class. The class creates the EngineWrapper and assigns it to the protected field variable _engineApiAccess.

4.7 Step 7: Implementation of the remaining IEngine methods

The basic structure of your engine and wrapper code is now in place. The task is now to go through the MyEngineWrapper class and complete the implementation of the methods that are currently auto-generated stub code. Some of these methods can be completed only by changing the code in the MyEngineWrapper; for others, changes also need to be made to the other classes and the engine core (MyEngineDLL). After completion of each method you should update the test classes and run the unit test.

For each method you must decide if the bulk of implementation should be located in the MyEngineWrapper class or in the engine core. There is no general answer to this question. Placing the bulk of implementation in the engine core could be advantageous from the perspective of maintenance because you have most things located in one place. On the other hand, you may want to keep the engine core as free as possible of OpenMI-related code and therefore put the bulk of the implementation into the MyEngineWrapper class. Finally, there may also be considerations about the preferred programming language; the engine core may be programmed in Fortran, C or Pascal, whereas the MyEngineWrapper class is programmed in C#.

Implementation of the IEngine interface depends on the engine core, so it is not possible to give a general explanation of how each individual method should be implemented. However, the next chapter gives a description of how the IEngine interface has been implemented for the Simple River model.

The IEngine interface is shown below

5. Migration of the Simple River

The previous chapter described the steps involved in migrating a model to the OpenMI.

This chapter shows how the migrated code is developed for the Simple River example.

5.1 The Simple River Wrapper

The Simple River model uses the migration pattern shown in Figure 12. Figure 13 gives a detailed explanation of how the Simple River wrapper works in terms of the wrapper classes.

Fig. 13 Simple River wrapper classes

5.2 Implementation of the Initialize method

The SimpleRiverEngineWrapper has two private field variables:

ArrayList _inputExchangaItems;

ArrayList _outputExchangeItems;

The _inputExchangeItems is a list of Oatc.OpenMI.Sdk.Backbone.InputExchangeItem objects and the _outputExchangeItems is a list of Oatc.OpenMI.Sdk.Backbone.OutputExchangeItem objects. These arraylists are populated in the Initialize method.

The Simple River wrapper must fulfill the requirements defined by the use cases described in section 2.1. This means that the input and output exchange items are based on the list of items in Table 1.

The source code for implementation of the Initialize method in the SimpleRiverEngineWrapper is shown below.

// From class: Oatc.OpenMI.Examples.ModelComponents.SimpleRiver.Wrapper.SimpleRiverEngineWrapper : Oatc.OpenMI.Sdk.Wrapper.IEngine
	public void Initialize(System.Collections.Hashtable properties)
		{
			
			_inputExchangeItems = new ArrayList();
			_outputExchangeItems = new ArrayList();

			// -- Create and initialize the engine --
			_simpleRiverEngine = new SimpleRiverEngineDotNetAccess();
            if (properties["FilePath"].ToString().Length > 1)
            {
                _simpleRiverEngine.Initialize(properties["FilePath"].ToString());
            }
            else
            {
                string currentDir = System.IO.Directory.GetCurrentDirectory();
                _simpleRiverEngine.Initialize(currentDir);
                //When running from the GUI, the assembly is started with the current dir
                //to the location of the OMI files. It is assumed that the data files are
                //located in the same directory.
            }
			// -- Time horizon --
			char [] delimiter = new char[]{'-',' ',':'};
			string[] strings = _simpleRiverEngine.GetSimulationStartDate().Split(delimiter);
			int StartYear   = Convert.ToInt32(strings[0]);
			int StartMonth  = Convert.ToInt32(strings[1]);
			int StartDay    = Convert.ToInt32(strings[2]);
			int StartHour   = Convert.ToInt32(strings[3]);
			int StartMinute = Convert.ToInt32(strings[4]);
			int StartSecond = Convert.ToInt32(strings[5]);
			DateTime startDate = new DateTime(StartYear,StartMonth,StartDay,StartHour,StartMinute,StartSecond);
			_simulationStartTime = Oatc.OpenMI.Sdk.DevelopmentSupport.CalendarConverter.Gregorian2ModifiedJulian(startDate);

			// -- Build exchange items ---
			Dimension flowDimension = new Dimension();
			Unit flowUnit = new Unit("m3/sec",1,0,"m3/sec");
			
			Quantity flowQuantity   = new Quantity(flowUnit,"description","Flow",global::OpenMI.Standard.ValueType.Scalar,flowDimension);
			Quantity inFlowQuantity = new Quantity(flowUnit,"description","InFlow",global::OpenMI.Standard.ValueType.Scalar,flowDimension);

			int numberOfNodes = _simpleRiverEngine.GetNumberOfNodes();

			for (int i = 0; i < numberOfNodes -1; i++)
			{
				OutputExchangeItem flowFromBrach  = new OutputExchangeItem();
				InputExchangeItem inFlowToBranch = new InputExchangeItem();

				ElementSet branch = new ElementSet("description","Branch:" + i.ToString(),ElementType.XYPolyLine,new SpatialReference("ref"));
                branch.AddElement(new Element("Branch:" + i.ToString()));
				branch.Elements[0].AddVertex(new Vertex(_simpleRiverEngine.GetXCoordinate(i),_simpleRiverEngine.GetYCoordinate(i),0));
				branch.Elements[0].AddVertex(new Vertex(_simpleRiverEngine.GetXCoordinate(i+1),_simpleRiverEngine.GetYCoordinate(i+1),0));
			
				flowFromBrach.ElementSet = branch;
				flowFromBrach.Quantity = flowQuantity;

				inFlowToBranch.ElementSet = branch;
				inFlowToBranch.Quantity = inFlowQuantity;
		
				_outputExchangeItems.Add(flowFromBrach);
				_inputExchangeItems.Add(inFlowToBranch);
		    }
			for (int i = 0; i < numberOfNodes; i++)
			{
				InputExchangeItem inflowToNode = new InputExchangeItem();
				ElementSet node = new ElementSet("description","Node:" + i.ToString(),ElementType.IDBased,new SpatialReference("ref"));
				node.AddElement(new Element("Node:" + i.ToString()));

				inflowToNode.Quantity = inFlowQuantity;
				inflowToNode.ElementSet = node;

				_inputExchangeItems.Add(inflowToNode);
			}

			ElementSet Branches = new ElementSet("description","AllBranches",ElementType.XYPolyLine,new SpatialReference("ref"));
			for (int i = 0; i < numberOfNodes - 1;i++)
			{
				Element branch = new Element("Branch: " + i.ToString());
				branch.AddVertex(new Vertex(_simpleRiverEngine.GetXCoordinate(i),_simpleRiverEngine.GetYCoordinate(i),0));
				branch.AddVertex(new Vertex(_simpleRiverEngine.GetXCoordinate(i+1),_simpleRiverEngine.GetYCoordinate(i+1),0));
				Branches.AddElement(branch);
			}
			InputExchangeItem inFlowToBraches    = new InputExchangeItem();

			inFlowToBraches.ElementSet = Branches;
			inFlowToBraches.Quantity = inFlowQuantity;
			_inputExchangeItems.Add(inFlowToBraches);

		}

As you can see from the implementation of the Initialize method, some methods need to be implemented in the MyEngineDotNetAccess class, the MyEngineDLLAccess class and the engine core.

The sequence diagram in Figure 14 illustrates the communication with the other wrapper classes when the Initialize method is invoked. The EngineDLL is not included in the diagram since there is a one-to-one communication between the EngineDLL and the EngineDLLAccess classes. In other words, each time a method is called in the EngineDLLAccess the corresponding function is called in the EngineDLL.


Fig. 14 Calling sequence for the initialize method

Note that no DataOperations are added to the OutputExchangeItems. The LinkableEngine class will complete the OutputExchangeItems for you by adding spatial and temporal data operations to your OutputExchangeItems. You can still add your own data operations as well.

5 3 Implementing the SetValues method

The calling sequence for the SetValues method is shown on figure 15 below.


Fig. 15. Calling sequence for the SetValues method

The EngineWrapper class decides what has to be done, based on the QuantityID and the ElementSetID. In the Simple River engine core there is only one possible variable that can act as input, which is the storage of water in the nodes. For the Simple River model, inflow is interpreted as additional inflow, which means that the inflow already received from other sources (the boundary inflow) is not overwritten. The inflow is added to the current storage in the nodes. The ElementSetID is parsed and the node number to which the water is going is determined.

If the inflow is going to the branches, the water is added to the downstream node for each branch. If the inflow is going to the nodes, the water is simply added to the storage of the node. Understanding the role of the QuantityID and the ElementSetID is very important when you are migrating your model. For each ExchangeItem you define your ElementSetID and QuantityID. These IDs will be included in the link when a user is configuring a system with your model. When the system is running, the same ElementSetID and QuantityID will get back to the ModelComponent when the GetValues method is invoked. Your component will then use this information to navigate to the correct variables in the engine.

You can use any convention for naming these IDs. In the Simple River ElementSetID, the convention used was Branch:<branch number> (e.g. ´Branch:3' ) or 'AllBranches', and the Quantity IDs were 'Flow' and 'InFlow'.

5.4 Implementing the GetValues method

The source code for the IEngine GetValues implementation is shown below.

//From Class: Oatc.OpenMI.Examples.ModelComponents.SimpleRiver.Wrapper.SimpleRiverEngineWrapper : Oatc.OpenMI.Sdk.Wrapper.IEngine
public IValueSet GetValues(string QuantityID, string ElementSetID)
		{
			double[] returnValues;
			Char[] separator = new char[]{':'};

			if (QuantityID == "Flow")
			{
				int index = Convert.ToInt32((ElementSetID.Split(separator))[1]);
				returnValues = new double[1];
				returnValues[0] = _simpleRiverEngine.GetFlow(index);
			}
			else
			{
				throw new Exception("Illegal QuantityID in GetValues method in SimpleRiverEngine");
			}

			Oatc.OpenMI.Sdk.Backbone.ScalarSet values = new Oatc.OpenMI.Sdk.Backbone.ScalarSet(returnValues);
			return values;
		}

The branch number is extracted from the ElementSetID and used as an index in the GetValues call to the SimpleRiverDotNetAccess class.

The calling sequence for the GetValues method is shown in Figure 16.


Fig. 16. Calling sequence for the GetValues method

5.5 Implementation of the remaining methods

Implementation of the remaining methods in the IEngine interface is not complicated. On the sequence diagram in Figure 17 you can see how each method is accessing the other engine wrapper classes.


Fig. 17. Calling sequence for Simple River

The calling sequence for methods not shown in Figure 17 is given in Figure 14, Figure 15 and Figure 16. Note that for some of the methods the full implementation is done in the SimpleRiverEngineWrapper class. The methods GetCurrentTime, GetInputTime and GetEarliestNeededTime are all invoking the GetCurrentTime method in the SimpleRiverDotNetAccess class. The returned time is the engine local time. This time is converted to the ModifiedJulianTime in the SimpleRiverEngineWrapper (see code below).

public ITime GetCurrentTime()
//From Class: Oatc.OpenMI.Examples.ModelComponents.SimpleRiver.Wrapper.SimpleRiverEngineWrapper : Oatc.OpenMI.Sdk.Wrapper.IEngine		
{
   double time = _simulationStartTime + _simpleRiverEngine.GetCurrentTime() / ((double)(24*3600));
   return new Oatc.OpenMI.Sdk.Backbone.TimeStamp(time);
}

6. Testing the component

It is important to test the component to check that it is working correctly. Traditionally, the procedure has been to complete implementation and then run the engine to see if it produces the correct results. However, in recent years new methodologies have been developed for testing. The dominant testing method for object oriented programs is unit testing. Unit testing is done in parallel with the implementation. This means that you will be able to find errors earlier and thus save time on debugging your code later.

This chapter discusses the testing of migrated components.

6.1 Unit testing

The testing procedure described here assumes you are using the NUnit test tool. You can download the NUnit user interface and libraries fromhttp://www.NUnit.org. This web page also gives more information about NUnit. Basically, you create a test class for each of the wrapper classes; in the test classes you implement a test method for each public method in the class.

This chapter focuses on OpenMI-specific testing. The NUnit home page gives detailed information about Unit testing.

Section 4.2 describes how to create test assemblies. The test classes used for the Simple River example are shown in Figure 18.


Fig. 18 Wrapper and test classes for the Simple river model

There is a one-to-one relation between the wrapper classes and the test classes, with two exceptions. There is no test class for the SimpleRiverEngineDLLAccess class and there is one additional class called UseCaseTests. The test class for the SimpleRiverEngineDLLAccess class was left out because every method in the SimpleRiverEngineDotNetAccess class will invoke the corresponding method in the SimpleRiverEngineDLLAccess class; therefore testing all methods in the SimpleRiverEngineDotNetAccess class will be sufficient.

The main idea of unit testing is to create very simple code that will test each method in the classes. However, it can also be useful to make some more advanced tests that are actually running full simulations. This is done in the UseCaseTests class. This class ensures that the use cases described in section 2.1 run without errors.

As described earlier, the model is migrated by implementing the IEngine interface. For every IEngine method in the MyEngineWrapper class you decide which method needs to be implemented in the remaining wrapper classes. You will implement the methods or functions that are needed in the engine core and then implement the corresponding methods in the MyEngineDLLAccess class and MyEngineDotNetAccess class. Each time you have completed the implementation of a method you can create a test method in the MyEngineDotNetAccessTest class and run the unit test. You can then move on to implement the method in MyEngineAccess and the associated test methods.

Below is the sample test code for the GetModelID method implementation in the Simple River model. Figure 19 shows the NUnit interface.

using System;
using System.Collections;
using System.IO;
using Oatc.OpenMI.Examples.ModelComponents.SimpleRiver.Wrapper;
using NUnit.Framework;

namespace Oatc.OpenMI.Examples.ModelComponents.SimpleRiver.Wrapper.UnitTest
{
	[TestFixture]
	public class SimpleRiverEngineDotNetAccessTest
	{
		SimpleRiverEngineDotNetAccess _simpleRiverEngineDotNetAccess;
		String _filePath;

		[SetUp]
		public void Init()
		{
			_simpleRiverEngineDotNetAccess = new SimpleRiverEngineDotNetAccess();
            _filePath = @"..\..\";
        	_simpleRiverEngineDotNetAccess.Initialize(_filePath);
		}

		[TearDown]
		public void ClearUp()
		{
			
		}

		[Test]
		public void GetModelID()
		{
			try
			{
				_simpleRiverEngineDotNetAccess.Initialize(_filePath);
				Assert.AreEqual("The river Rhine",_simpleRiverEngineDotNetAccess.GetModelID());
				_simpleRiverEngineDotNetAccess.Finish();
			}
			catch(System.Exception e)
			{
				this.WriteException(e.Message);
				throw(e);
			}
		}
	}
}


Fig. 19. NUnit Userinterface with Simple River wrapper classes loaded

7. Implementing IManageState

Implementation of the IManageState interface is not required in order to claim OpenMI compliance. However, if you want to use your model in configurations where iterations are needed or you want to use calibration or optimization controllers, the implementation of the IManageState interface is required. Normally, you should put the bulk of the implementation into the engine core and save the data required in order to restore a state in memory.

7.1 The IManageState interface

Implementation of the IManageState interface is shown in Figure 20.

The LinkableEngine class already implements the required methods for the OpenMI.Standard.ILinkableComponent interface but it is not specified in this class that it implements the OpenMI.Standard.IManageState interface.

To implement the IManageState interface when using the LinkableEngine, the procedure is as follows:

  1. In MyLinkableEngine, specify that it implements the IManageState interface.

  2. In MyEngineWrapper, specify that it implements the IManageState interface.

  3. Implement the IManageState methods in all wrapper classes. The implementation will typically be very simple code that redirects the call to the next wrapper class and finally to the engine core, where the bulk of the implementation is located.


Fig. 20. IManageState implementation

8. The OMI file

The OMI file defines the entry point to a LinkableComponent. It contains information on the software unit to instantiate and the arguments to provide at initialization. This file makes it possible for a user interface to deploy your model.

This chapter describes the OMI file.

The OMI file refers to a specific instance of your LinkableComponent. E.g. if you have an OpenMI compliant river model setup for the River Rhine, the OMI file relates to this specific instance (the River Rhine). OMI files are typically created by the user interfaces of the model. So when the user saves the setup files also the OMI file is saved at the same location as these files.

8.1 Structure of the OMI file

The structure of the OMI file is defined in OpenMI.Standared.LinkableComponent.XSD. (http://www.openmi.org/schemas/LinkableComponent.xsd) Figure 21 provides a visual representation of the schema definition; The XML listing below provides an example of an OMI file.


Fig. 21. Visual representation of the LinkableComponent XML schema definition.

OMI file Example (the simple river):

<?xml version="1.0"?>
<LinkableComponent Type="Oatc.OpenMI.Examples.ModelComponents.SimpleRiver.Wrapper.SimpleRiverOpenMIComponent" Assembly="..\Wrapper\bin\Debug\Oatc.OpenMI.Examples.ModelComponents.SimpleRiver.Wrapper.dll">
  <Arguments>
    <Argument Key="FilePath" ReadOnly="true" Value= "" />
   </Arguments>
</LinkableComponent>

If your OpenMI components are located in the GAC you will need a bit more information in the OMI file. Below you can see an example for the Mike11 model setup for the Karup creek in Denmark.

<?xml version="1.0"?>
<LinkableComponent Type="DHI.OpenMI.Mike11.Mike11LinkableComponent" Assembly="DHI.OpenMI.Mike11, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c513450b5d0bf0bf" xmlns="http://www.openmi.org/LinkableComponent.xsd">
<Arguments>
<Argument Key="SimFile" ReadOnly="true" Value="C:\Users\Gregersen\Documents\OpenMI\Meetings\2008.06.05.Wallingford.CEH_WS\Data\FileMike11\MIKE11\karup.sim11" />
</Arguments>
</LinkableComponent>

See also: HOW TO work with OMI files

9. The OpenMICompliancyInfo file

OpenMI compliant components must be associated with an OpenMI compliance info file. This file provides useful information for people that are planning to use the component in an OpenMI configuration. The OpenMI compliance info file must comply to the schema OpenMICompliancyInfo.xsd ((http://www.openmi.org/schemas/OpenMICompliancyInfo.xsd)).

You can see other examples of OpenMI compliancy info files here.

The openmi compliance xml file for the Simple River is shown below:

<?xml version="1.0" encoding="UTF-8"?>
<!--Sample XML file generated by XMLSpy v2006 rel. 3 sp1 (http://www.altova.com)-->
<OpenMICompliancyInfo xmlns="http://www.openmi.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.openmi.org ..\..\..\..\..\..\OpenMI.Standard\src\OpenMICompliancyInfo.xsd">
	<generalSoftwareInfo>
		<component name="SimpleRiver" version="1.4.0.0">
			<description>Fortran wrapping sample</description>
			<url>public.wldelft.nl/display/OPENMI/Oatc.OpenMI.Examples.ModelComponents.SimpleRiver</url>
		</component>
		<contactInfo>
			<supplierName>OATC (OpenMI Association Technical Committee)</supplierName>
			<contactPerson>Jan Borge Gregersen</contactPerson>
			<supplierEmail>association@openmi.org</supplierEmail>
		</contactInfo>
		<availability component="free" source="available">
			<comment>Available as part of OATC Software Development Kit</comment>
		</availability>
	</generalSoftwareInfo>
	<supportForOpenMI>
		<compliancy>
			<technology>dotNet</technology>
			<openMIStandardVersion>1.4.0.0</openMIStandardVersion>
		</compliancy>
		<exchangeItems>
			<dataOperations setId="spatial/time/buffering">
				<dataOperation>Nearest XYPolyLine to XYPoint</dataOperation>
				<dataOperation>Inverse XYPolyLine to XYPoint</dataOperation>
				<dataOperation>Weighted Mean XYPolyLine to XYPolyGon</dataOperation>
				<dataOperation>Weighted Sum XYPolyLine to XYPolyGon</dataOperation>
				<dataOperation>LinearConversion</dataOperation>
				<dataOperation>Buffering and temporal extrapolation</dataOperation>
			</dataOperations>
			<inputExchangeItem>
				<quantity>
					<description>Flow</description>
					<dimension lenght="3" time="-1"/>
				</quantity>
				<elementSet elementType="IDBased">
					<description>An ElementSet for each branch. Each ElementSet is identified by 'branch-N', where N is the branch number.</description>
				</elementSet>
			</inputExchangeItem>
			<outputExchangeItem>
				<quantity>
					<description>Outflow</description>
					<dimension lenght="3" time="-1"/>
				</quantity>
				<elementSet elementType="XYPolyLine">
					<description>An ElementSet with one element (line segment) for each branch, or an ElementSet with an element for each branch.</description>
				</elementSet>
				<dataOperations>spatial/time/buffering</dataOperations>
			</outputExchangeItem>
			<outputExchangeItem>
				<quantity>
					<description>Outflow</description>
					<dimension lenght="3" time="-1"/>
				</quantity>
				<elementSet elementType="XYPolyLine">
					<description>An ElementSet with one element (line segment) for each branch, or an ElementSet with an element for each branch.</description>
				</elementSet>
				<dataOperation>LinearConversion</dataOperation>
				<dataOperation>Buffering and temporal extrapolation</dataOperation>
			</outputExchangeItem>
			<inputExchangeItem>
				<quantity>
					<description>Flow</description>
					<dimension lenght="3" time="-1"/>
				</quantity>
				<elementSet elementType="XYPolyLine">
					<description>An ElementSet with one element (line segment) for each branch, or an ElementSet with an element for each branch.</description>
				</elementSet>
			</inputExchangeItem>
		</exchangeItems>
		<optionalInterfaces>
			<IManageState isSupported="false">
				<comment>Not implemented for this simple river example</comment>
			</IManageState>
			<IDiscreteTimes isSupported="false"/>
		</optionalInterfaces>
	</supportForOpenMI>
</OpenMICompliancyInfo>

  • No labels