Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migration of unmigrated content due to installation of a new plugin
scrollbar

Table of contents

Table of Contents
Include Page
OPENMI:1 IntroductionOPENMI:
1 Introduction
Include Page
OPENMI:2 Planning the migrationOPENMI:
2 Planning the migration
OPENMI:3 Wrapping
Include Page
OPENMI:3 Wrapping
3

...

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.

3.1. A general wrapping pattern

Wrapping

...

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 org.OpenMI.Utilities.Wrapper package. Basically, the LinkableEngine provides a default implementation of the ILinkableComponent interface. Naturally, the LinkableEngine cannot know the specific behaviour 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:
 

...

...

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.

Include Page
4.1 Step 1

...

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.
Image Removed
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.
 
Image Removed
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 OpenMI Software Development Kit 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)
logical 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

The next step is to create the wrapper classes (Figure 8). For this stage, make sure that the OpenMI Software Development Kit is installed on your PC.
Image Removed
Fig 8. C# wrapping classes
 

Load the .NET development environment. 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 should use the following naming conventions for your wrapper assembly:

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

Naming conventions for the test assembly:

Assembly name: MyOrganisation.OpenMITest.MyModel
Assembly DLL name: MyOrganisation.OpenMITest.MyModel.DLL
Namespace: MyOrganisation.OpenMI.MyModel

Class names: MyModelEngineWrapperTest, MyModelEngineDotNetAccessTest, MyModelEngineDLLAccessTest, MyModelLinkableComponentTest

Now install the NUnit test software (see Chapter 6)

To the wrapper assembly, add the following references:

Org.OpenMI.Standard
Org.OpenMI.Backbone
Org.OpenMI.Utilities.Wrapper

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

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 th engine core

 

 The third step is to implement the MyEngineDLLAccess class (Figure 9).
Image Removed
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.

Code Block

using System; using System.Run-time.InteropServices; using System.Text; namespace MyOrganisation.OpenMI.MyModel {     public class MyEngineDLLAccess     {          \[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 = "PERFORMTIMESTEP", 		SetLastError=true, 		ExactSpelling = true, 		CallingConvention=CallingConvention.Cdecl)\] 		public static extern bool PerformTimeStep();            \[DllImport(@"Oatc.OpenMI.Examples.ModelComponents.SimpleRiver.Engine.dll", 	       EntryPoint = "FINISH", 	       SetLastError=true, 	       ExactSpelling = true, 	       CallingConvention=CallingConvention.Cdecl)\] 	       public static extern bool Finish();     } }

4.4 Step 4: Implementing the MyEngineDotNetAccess

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

Image Removed
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).

...

Code Block

using System; using System.Text; namespace MyOrganisation.OpenMI.MyModel {    public class MyEngineDotNetAccess    {       public void Initialize(string filePath)       {         if(\!(MyModelDLL.Initialize(filePath, ((uint) filePath.Length))))         {           CreateAndThrowException();         }       }       public void PerformTimeStep()       {         if(\!(MyModelDLL.PerformTimeStep()))         {            CreateAndThrowException();         }       }       public void Finish()       {          if(\!(MyModel.Finish()))          {              CreateAndThrowException();          }       }       private void CreateAndThrowException()       {          int numberOfMessages = 0;          numberOfMessages = MyModelDLL.GetNumberOfMessages();          string message = 'Error Message from MyModel ';          for (int i = 0; i < numberOfMessages; i++)          {             int n = i;             StringBuilder messageFromCore = new StringBuilder(' ');             MyModelDLL.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).

Image Removed

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.

...

Code Block

using System; using System.Collections; namespace MyOrganisation.OpenMI.MyModel {     public class MyEngineWrapper : Oatc.OpenMI.Sdk.Wrapper.IEngine     {            private MyEngineDotNetAccess \_myEngine;         public void Initialize(Hashtable properties)         {              \_myEngine = new MyEngineDotNetAccess();             \_myEngine.Initialize((string)properties\["FilePath"\]);         }         public void Finish()                 {             \_simpleRiverEngine.Finish();         }     } }

4.6 Step 6: Implementing the MyModelLinkablComponent

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

Image Removed

The MyModelLinkableComponent is the OpenMI-compliant linkable component that is going to be accessed by other models. Implementation of this class is very simple. The example code shown below is the complete implementation for the Simple River model.

Code Block

using System; namespace MyOrganisation.OpenMI.MyModel {    public class MyModelOpenMIComponent :    org.OpenMI.Utilities.Wrapper.LinkableEngine    {       protected override void SetEngineApiAccess()       {          \_engineApiAccess = new MyEngineWrapper();       }    } }

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.

...

- Changing your engine core
4.1 Step 1 - Changing your engine core
Include Page
4.2 Step 2 - Creating the .Net assemblies
4.2 Step 2 - Creating the .Net assemblies
Include Page
4.3 Step 3 - Accessing the functions in the engine core
4.3 Step 3 - Accessing the functions in the engine core
Include Page
4.4 Step 4 - Implementing the MyEngineDotNetAccess
4.4 Step 4 - Implementing the MyEngineDotNetAccess
Include Page
4.5 Step 5 - Implementing the MyEngineWrapper class
4.5 Step 5 - Implementing the MyEngineWrapper class
Include Page
4.6 Step 6 - Implementing the MyModelLinkablComponent
4.6 Step 6 - Implementing the MyModelLinkablComponent
Include Page
4.7 Step 7 - Implementation of the remaining IEngine methods
4.7 Step 7 - Implementation of the remaining IEngine methods

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 4-912. Figure 4-21 13 gives a detailed explanation of how the Simple River wrapper works in terms of the wrapper classes.

...

Fig. 13 Simple River wrapper classes

Include Page

...

5.2 Implementation of the Initialize method

The SimpleRiverEngineWrapper has two private field variables:

...

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

...

Code Block

public void Initialize(System.Collections.Hashtable properties) {    \_inputExchangeItems = new ArrayList(); //ArrayList of    Oatc.OpenMI.Sdk.Backbone.InputExchangeItem objects    \_outputExchangeItems = new ArrayList(); //ArrayList of    Oatc.OpenMI.Sdk.Backbone.OutputExchangeItem objects    // \-\\- Create and initialize the engine \-\\-    \_simpleRiverEngine = new SimpleRiverEngineDotNetAccess();    \_simpleRiverEngine.Initialize((string)properties\['FilePath'\]);    // \-\\- Simulation start time -    // The start time is obtained from the engine core as a string. This string is    // passed and converted to a System.DateTime. Then the    // Oatc.OpenMI.Sdk.DevelopmentSupport.CalendarConverter class is used to convert    // this time into the ModifiedJulianDay (this is the OpenMI standard time)    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'); //The Simple River only uses    // quantities with the unit m3/sec.    Quantity flowQuantity = new Quantity(flowUnit,'description','Flow',    OpenMI.Standard.ValueType.Scalar,flowDimension);    Quantity inFlowQuantity = new Quantity(flowUnit,'description','InFlow',    OpenMI.Standard.ValueType.Scalar,flowDimension);    int numberOfNodes = \_simpleRiverEngine.GetNumberOfNodes();    for (int i = 0; i < numberOfNodes \-1; i++) //For each branch    {       OutputExchangeItem flowFromBranch = new OutputExchangeItem();       InputExchangeItem inFlowToBranch = new InputExchangeItem();       // One ElementSet is created for each branch. The ElementID's are       // Branch:<Branch number>. E.g. 'Branch:3'       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.       GetXCo-ordinate(i),_simpleRiverEngine.GetYCo-ordinate(i),0));       branch.Elements\[0\].AddVertex(new Vertex(_simpleRiverEngine.       GetXCo-ordinate(i+1),_simpleRiverEngine.GetYCo-ordinate(i+1),0));       flowFromBranch.ElementSet = branch;       flowFromBranch.Quantity = flowQuantity;       inFlowToBranch.ElementSet = branch;       inFlowToBranch.Quantity = inFlowQuantity;       \_outputExchangeItems.Add(flowFromBranch);       \_inputExchangeItems.Add(inFlowToBranch);    }    for (int i = 0; i < numberOfNodes; i++) //For all nodes    {       InputExchangeItem inflowToNode = new InputExchangeItem();       // Each node is a ID-based ElementSet. The ElementSet ID are       // Node:<node number>. E.g. 'Node:3'       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++) //Create an InputExchangeItem that    // has all branches in one ElementSet    {       Element branch = new Element('Branch: ' + i.ToString());       branch.AddVertex(new Vertex(_simpleRiverEngine.       GetXCo-ordinate(i),_simpleRiverEngine.GetYCo-ordinate(i),0));       branch.AddVertex(new Vertex(_simpleRiverEngine.       GetXCo-ordinate(i+1),_simpleRiverEngine.GetYCo-ordinate(i+1),0));       Branches.AddElement(branch);    }    InputExchangeItem inFlowToBranches = new InputExchangeItem();    inFlowToBranches.ElementSet = Branches;    inFlowToBranches.Quantity = inFlowQuantity;    \_inputExchangeItems.Add(inFlowToBranches); }

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.

...

Image Removed
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.

4.5 3 Implementing the SetValues method

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

Image Removed
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.

...

5.2 Implementation of the Initialize method
Include Page
5.3 Implementing the SetValues method
5.3 Implementing the SetValues method
Include Page
5.4 Implementing the GetValues method
5.4 Implementing the GetValues method

4.5.4 Implementing the GetValues method

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

Code Block

public org.OpenMI.Standard.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');    } }

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

...

Image Removed
Fig. 16. Calling sequence for the GetValues method

...

Include Page
5.5 Implementation of the remaining methods
5.5 Implementation of the remaining methods

...

Image Removed
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 15. 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).

...

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.

Include Page
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.

...

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

...

Code Block

using System; using Oatc.OpenMI.Examples.ModelComponents.SimpleRiver.Wrapper; using NUnit.Framework; namespace org.OpenMITest.Examples.ModelComponents.SimpleRiver.Wrapper {    \[TestFixture\]    public class SimpleRiverEngineDotNetAccessTest    {       \[Test\]       public void GetModelID()       {          SimpleRiverEngineDotNetAccess \_simpleRiverEngineDotNetAccess;          String \_filePath;         \_simpleRiverEngineDotNetAccess = new SimpleRiverEngineDotNetAccess();         \_filePath = 'C:\\SimpleRiver\\UnitTest\\Data\\Rhine';         \_simpleRiverEngineDotNetAccess.Initialize(_filePath);         Assert.AreEqual('The river Rhine',         \_simpleRiverEngineDotNetAccess.GetModelID());         \_simpleRiverEngineDotNetAccess.Finish();       }    } }

Image Removed
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.

...

  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.

Image Removed
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.

...

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.

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

OMI file Example:

Code Block

<?XML version='1.0'?> <LinkableComponent Type='org.OpenMI.Examples.MC.SimpleRain' Assembly='org.OpenMI.Examples.MC, Version=1.4.0.0, Culture=neutral, PublicKeyToken= 8384b9b46466c568' XMLns='http://www.openmi.org/LinkableComponent.xsd'> <Arguments>    <Argument Key='Data' ReadOnly='true' Value='c:\OpenMI\Examples\Data\SimpleRain.txt' /> </Arguments> </LinkableComponent>

9. The OpenMICompliancyInfo file

OpenMI compliant components must be associated with an OpenMI compliance info file. This file 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)).

...

6.1 Unit testing
Include Page
7. Implementing IManageState
7. Implementing IManageState
Include Page
8. The OMI file
8. The OMI file
Include Page
9. The OpenMICompliancyInfo file
9. The OpenMICompliancyInfo file