Summary

To extend types of values that can be exchanged using OpenMI.

Feedback from Steve (HEC):

All of the exchange information in OpenMI looks like it time based. That is, the information is exchanged over the entire simulation. However, I can think of lots of situations where there is some exchange information that is only needed for the initial (time = 0.0) conditions. For example, someone might have an OpenMI program that controls the discharge from a dam/lake and they want to use RAS to compute the lake level and also route the flow upstream and downstream of the lake. When the two programs start running, they would want to specify the initial lake level at the dam and the initial discharge (or gate settings) from the lake. However, from that point on, their program would only specify the discharge (or gate settings), allowing RAS to compute the lake level as it performs the stream routing. The initial lake level can, of course, be specified by using the RAS GUI, but then there would be the issue of how to automate this process. Alternately, I could structure the OpenMI RAS program so that it used the lake level exchange data at time zero, but ignored it after that. This would work, but it would be awkward and confusing for the user. You might consider adding exchange items to OpenMI that are only intended to be used for the initial conditions.

I have been experimenting with a RAS model connected to a RAS model and I have additional concerns about how the initial conditions are handled. If the upstream RAS model is a single reach, or a dendritic system (there is
only one location that has a downstream outflow), then the upstream model can compute the outgoing flow (by adding up all the incoming flows) even without knowing the downstream water surface. This flow can be passed to the downstream model and the downstream model can then compute the flows and water surfaces for all the nodes at time zero (by assuming a steady state solution). I would like to have the downstream model (before it performs any
timesteps) then pass the water surface back to the upstream model, and have the upstream model also compute all the flows and water surfaces at time zero
(by assuming a steady state solution). I have not been able to figure out how to do this. I'm not sure whether it is because OpenMI is not structured to be able to do this, or if it is because I haven't figured out how to correctly modify the engine wrapper. In either case, I would like to see the OpenMI/engine wrapper changed so that this is a standard feature in the generic engine wrapper.

This use case should be posed on the WIKI with a description for how it can be solved with version 1.4 and some suggestions for improvement in version 2.0 that will make thing easier. (TASK: Gena / Stef).

How to address in Version 1

Leave it.

How to address in Version 2

Make time dependent types of values as optional feature. See ideas below.

Current implementation:

interface ILinkableComponent
{
   int InputExchangeItemCount {get;}
   IInputExchangeItem GetInputExchangeItem(int inputExchangeItemIndex);

   int OutputExchangeItemCount {get;}
   IOutputExchangeItem GetOutputExchangeItem(int outputExchangeItemIndex);

   void AddLink(ILink link);

   ...
   IValueSet GetValues(ITime time, string linkID);
   ...
}

public interface IExchangeItem // extended by IOutputExchangeItem and IInputExchangeItem
{
   IQuantity Quantity {get;}
   IElementSet ElementSet {get;}
}

interface ILink
{
   string ID { get; }
   IQuantity Quantity { get; }
   IElementSet ElementSet { get; }
}

public interface IValueSet
{
   int Count {get;}
   bool IsValid(int elementIndex);
}

public interface IScalarSet : IValueSet
{
   double GetScalar(int elementIndex);
}

// how it is used
void ConnectComponentsAndAskValues()
{
  ILinkableComponent sourceComponent = ...;
  ILinkableComponent targetComponent = ...;

  OutputExchangeItem oututExchangeItem = sourceComponent.GetOutputExchangeItem(0); // assume that it is at index 0
  InputExchangeItem inputExchangeItem = targetComponent.GetInputExchangeItem(0); // assume that it is at index 0

  LinkImpl link1 = new LinkImpl();
  link1.ID ="Link1";
  link1.SourceComponent = sourceComponent;
  link1.TargetComponent = targetComponent;
  link1.SourceElementSet = oututExchangeItem.ElementSet;
  link1.SourceQuantity = oututExchangeItem.Quantity;
  link1.TargetElementSet = inputExchangeItem.ElementSet;
  link1.TargetQuantity = inputExchangeItem.Quantity;

  srcComponent.AddLink(link1);
  dstComponent.AddLink(link1);

  // start asking values
  ITime time = srcComponent.TimeHorizon.Start;
  IValueSet values = dstComponent.GetValues(time, link1.ID);
}

Disadvantages of the current approach:

  • Information that is exchanged is always related to time and link id
  • Type of information that is exchanged should implement IValueSet
  • IValueSet interface is not self-descriptive: you can ask number of values in a set but in order to ask real values you have to check if object is of type IScalarSet or IVectorSet, derive it to that type and then ask for real values.
  • Values can be asked only from component and not from exchange item: ILinkableComponent.GetValues().
  • Values can be asked for a given links assuming that it should search for corresponding exchange item and return values for output exchange item related to that link - very implicit. What if there are 2 exchange items with the same quantity and element set but with different name.
  • It is not possible to connect 2 components without having some Backbone implementation of ILink. In other words you have to implement something in order to use OpenMI components in your system instead of simple: link to OpenMI.Standard.dll and run OpenMI-compliant components.

Suggestions:

  • Separate time-dependency concept from value
  • Move link-related functionality into exchange item instead of component
  • Add additional ValueType property to exchange item returning type of value being exchanges.
  • Make it possible for developers to extend types of exchangeable value objects by use of object based values instead of IValueSet-based.
  • Use enumeration instead of polymorphisms to identify if exchange item is input or output - will simplify implementation of backbones.
  • Make time-dependent runnable components as one of the component types
  • Use IDiscreteTimes-like approach in order to identify time-dependency of exchange items.
  • Separate concepts of getting values and running / updating component. Currently GetValues() implicitly triggers run of component. Actually in backbone methods used to run component are defined using IRunEngine - this interface can be pulled up into OpenMI interface and defined as extension of ILinkableComponent (see below). See also use case 4. Value as a property in IExchangeItem, TimeRunnableComponent.
    • ILinkableComponent - only properties / methods related to linkage (exchange items, meta info)
    • IRunEngineComponent - time-dependent runs
interface ILinkableComponent
{
   IList<IExchangeItem> InputExchangeItems { get; }
   IList<IExchangeItem> OutputExchangeItems { get; }
}

enum ExchangeItemRole { Input, Output };

public interface IExchangeItem
{
   ExchangeItemRole Role { get; }

   object Value { get; set; }
   Type ValueType { get; }

   // allows to link current exchange item to any destination exchange item
   // sourceItem -> targetItem
   void LinkTo(IExchangeItem sourceExchangeItem);

   // list of exchange items where current exchange item is used (linked to)
   IList<IExchangeItem> LinkedTo { get; }

   // source exchange item used as a source
   IExchangeItem LinkedFrom { get; set; }
}

// how it can be used
void ConnectComponentsAndAskValues()
{
  ILinkableComponent sourceComponent = ...;
  ILinkableComponent targetComponent = ...;

  IExchangeItem sourceExchangeItem = sourceComponent.OutputExchangeItems[0];
  IExchangeItem targetExchangeItem = targetComponent.InputExchangeItems[0];

  sourceExchangeItem.LinkTo(targetExchangeItem);
  
  // start asking for values
  object value = targetExchangeItem.Value;

  // show list of exchange items where our item is used:
  foreach(IExchangeItem item in sourceExchangeItem.LinkedTo)
  {
      Debug.Write(sourceExchangeItem.ID + " -> " + item.ID);
  }
}

void BackwardCompatibilityWithScalarValueSet()
{
  ILinkableComponent targetComponent = ...;

  IExchangeItem sourceExchangeItem = sourceComponent.OutputExchangeItems[0];

  Assert.AreEqual(sourceExchangeItem.ValueType, typeof(IScalarSet));

  IScalarSet scalarSetValue = (IScalarSet)sourceExchangeItem.Value;
}

How to address time-dependency of exchange items

interface IRunEngineComponent : ILinkableComponent
{
   void Run(TimeSpan duration);
   ...
}

interface IExchangeItem
{
   // returns current value of exchange item (for current exchange item/component state defined by time, element set, substance, etc.)
   object Value { get; set; }

   // returns value for specific time, element, ...
   object GetFilteredValue(IValueFilter valueFilter);
}

void GetValueFromExchangeItemForSpecificTimeAndElement()
{
    IExchangeItem exchangeItem = ...; // construct/get it somewhere

    // get current value (which may change after component will progress / be updated / run / ...)
    object currentValue = exchangeItem.Value;

    // for example IScalarSet for component current time
    IScalarSet scalarSetValue = (IScalarSet)currentValue;
    Assert.AreEqual("number of values should be equal to number of elements", elementSet.Count, scalarSetValue.Count);

    // get value only for specific time and element
    DateTime time0 = ...; // for example runnableComponent.CurrentTime
    IElement element1 = ...;
    IValueFilter valueFilter = new TimeAndLocationValueFilter(time0, element1);

    scalarSetValue = (IScalarSet)exchangeItem.GetFilteredValue(valueFilter);

    Assert.AreEqual("number of values should be equal to 1 (element)", 1, scalarSetValue.Count);
}

Thoughts:

Object type for exchange item values sounds quite generic, however providing a little of meta info may help. A solution may be also to define a set of exchangeable value objects mostly-used in OpenMI community and include them into Standard, e.g.:

  • One-dimensional element-based IScalarSet containing array of (Double) values and reference to IElementSet.
  • Time-dependent series ITimeSeries containing pairs of (DateTime, ValueObject) where ValueObject can be primitive type of above mentioned IScalarSet
  • Elements or ElementSets can be also exchangeable values.
  • Primitive values like String, Double, Integer, Enumerations ...
  • ...
  • No labels

1 Comment

  1. Perhaps we could introduce a flexible attributed object design, like illustrated in the code below. Some of it could have standard implementations in the SDK, without loosing extensibility for third parties. E.g. there could be a IOMIValueObject (maybe also just as marker interface) and a OMIScalarValueObject implementation. However e.g. the scalar value could also be an attribute of a OMIValueObject implementation. At the moment I am not sure what would be the best (smile)

    // A marker interface
    public interface IOMIAttribute {}
    
    public interface IOMIObject
    {
    	HashMap getAttributeGroup(Class[] attrClasses);
    	Collection getAttributeCollection(Class attrClass);
    	IOMIAttribute getAttribute(Class attrClass);
    	Collection getAttributeGroupCollection(Class[] attrClasses);
    }
    
    // sample attributes
    
    public interface IQuantityAttribute extends IOMIAttribute
    {
    	IQuantity getQuantity();
    }
    
    public interface ISemanticAttribute extends IOMIAttribute
    {
    	String getConceptURI();
    }
    
    public interface IQualityAttribute extends IOMIAttribute
    {
    	IQuality getQuality();
    }
    
    public interface IElementAttribute extends IOMIAttribute
    {
    	int getVertexCount();
            double getXCoordinate(int vertexIndex);
            double getYCoordinate(int vertexIndex);
            double getZCoordinate(int vertexIndex);
    	...
    }