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:

Suggestions:

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.: