Introduction

I have looked carefully at the current OpenMI development version 2 (the chained approach). I believe that it works, but somehow I also find it a bit too complicated. The whole idea of creating a chain of data operations may provide possibilities, but I have a hard time finding examples where this is really needed.

In my approach below I have tried to simplify things so there is only one data operation (I called this interface a bridge, just to confuse everyone). Maybe the bridge actually is a cover up for a link, honestly I do not know. Please forgive me if I am moving in circles (smile). Please take a look at the relevant interfaces in the figure below.

A linkable component has a list of input items (IInputExchangeItems) and output items (IOutputExchangeItems). Data can be accessed directly by GetValues() and SetValues(Object) through the output and input items, respectively. In this case it is the raw data, no conversions is done, so what you set or get is data at whatever time the component it at.

In order to make conversions a bridge is required. You can request the output item to create a bridge, which will take care of any temporal, spatial, unit, and other conversion that is needed in order to make the data fit with the target exchange item (the CreateBridge(IExchangeItem)). A bridge must be activated before the GetValues method on this bridge can be invoked. This is needed because the bridge may need to buffer time dependent data. If e.g. an output item has data represented on a polyline and the input item needs data on a polygon, a bridge that makes this conversion is created. You can still set additional options by changing arguments (e.g. inversDistance, search radius, temporal relaxation factor etc.).

Please see examples of how the linkable components can be used for different purposes in the unit test examples below:
The full source code is located at source forge in the branch called "OpenMI-2.0.0-Gregersen02", use the SimpleComponent solution under MyOpenMISource/LicTek/OpenMIVersion2Tests/SimpleComponent.

Simple Getvalues

 [Test]
        public void GetValues()
        {
            ILinkableComponent myLinkableComponent = new MyLinkableComponent();
            myLinkableComponent.Initialize(new Argument[0]);
            double x = (double)myLinkableComponent.OutputItems[0].GetValues();
            Assert.AreEqual(7.3, x);
        }

Simple SetValues

[Test]
        public void SetValues()
        {
            ILinkableComponent myLinkableComponent = new MyLinkableComponent();
            myLinkableComponent.Initialize(new Argument[0]);

            double x = 6.4;
            myLinkableComponent.InputItems[0].SetValues(x);
                        
            Assert.AreEqual(x, ((MyLinkableComponent) myLinkableComponent).WaterLevel);
        }

Configuration test

[Test]
        public void ConfigurationTest()
        {
            ILinkableComponent upperRiver = new MyLinkableComponent();
            ILinkableComponent lowerRiver = new MyLinkableComponent();
            upperRiver.Initialize(new Argument[0]);
            lowerRiver.Initialize(new Argument[0]);

            // Connect the two models
            // The bridge object will take care of temporal, spatial, and unit conversion.
            IBridge bridge = upperRiver.OutputItems[0].CreateBridge(lowerRiver.InputItems[0]);
            lowerRiver.InputItems[0].AddBridge(bridge);
            bridge.IsActivated = true;

            // prepare, but this may not be needed since the IsActivated is swithed on

            //Run
            lowerRiver.Update(new TimeStamp(100));

            //close down
            upperRiver.Finish();
            upperRiver.Dispose();
            lowerRiver.Finish();
            lowerRiver.Dispose();

        }

Calibration test

[Test]
        public void CalibrationTest()
        {
            ILinkableComponent upperRiver = new MyLinkableComponent();
            upperRiver.Initialize(new Argument[0]);

            double observedWaterLevel = 2.3;
            double roughness = 0.03;
            double simulatedWaterLevel = 0;

            
            Object state = ((IManageState)upperRiver).KeepCurrentState();

            do
            {
                upperRiver.InputItems[17].SetValues(roughness);
                upperRiver.Update(new TimeStamp(232));
                simulatedWaterLevel = (double) upperRiver.OutputItems[5].GetValues();

                roughness = VeryAdvancedCalibrationAlgorithm(roughness, observedWaterLevel, simulatedWaterLevel);
                ((IManageState)upperRiver).RestoreState(state);
            } 
            while (Math.Abs(observedWaterLevel - simulatedWaterLevel) < 0.01);

            upperRiver.Finish();
            upperRiver.Dispose();
        }

Suggestions for how to handle time

The way time is represented in the 1.4 standard has for a long time been bothering me. If you want to make a very simple component that does not care about time, you still have to implement TimeHorizon, GetEarliestInputTime, and the GetValues method will have a time argument. Examples of such non time components could be a GIS geo-references soil conductivity or soil type (when we get the quality data type included). It could also be a very simple component that provides a single value e.g. if you want to make a component for setting the roughness coefficient from outside via OpenMI.

I have changed the interfaces as shown on the figure below:

  • I made a new interface called IIdentifier. This has nothing to do with the time issues and I should probably not have made this change since it can confuse the issue. Anyway the IIdentifier is simply used to group the caption, ID and description that we have in some of our interfaces.
  • A new interface called ITemporal (could not at the moment come up with at better name (sad)) has been added. The idea is that if time is relevant for an exchange item (input or output) then such exchange items may implement this interface in addition to IInputExchangeItem or IOutputExchangeItem.
  • The TimeHorizon property is moved from the ILinkableComponet to the ITemporal interface, so a time horizon relates to at specific exchange item rather than the whole linkable component.
  • The property exchangeTime should be regarded exactly as the elementSet in the exchangeItem. So, the elemetSet defines the locations for where data can be provided or accepted and likewise does the exchangeTime define when data is available or expected. This also means that the GetValues method in the IBridge interface does not need at time argument. The bridge will convert data from being represented on the output elementset and the output exchange time to be presented on the input elementset at the input exchange time. (I do like symmetry (smile))
  • The AlwaysForwardInTime property is used to tell the other component that the exchange time will never be moved backwards thus allowing the delivering component to clear it's buffer.

  • No labels