Sunteți pe pagina 1din 100

WRC STOAT MIGRATION TO OPENMI

WRc Ref: UC6920


SEPTEMBER 2005

WRC STOAT MIGRATION TO OPENMI

Report No.: UC6920


September 2005
Authors: Jeremy Dudley
Contract Manager: Gordon Devine
Contract No.: 13124-1

RESTRICTION: This report has the following limited distribution:


External: None
Internal: None

Any enquiries relating to this report should be referred to the authors at the following
address:
WRc Swindon, Frankland Road, Blagrove, Swindon, Wiltshire, SN5 8YF.
Telephone: + 44 (0) 1793 865000
Fax: + 44 (0) 1793 865001

The contents of this document are subject to copyright and all rights are reserved. No part of
this document may be reproduced, stored in a retrieval system or transmitted, in any form or
by any means electronic, mechanical, photocopying, recording or otherwise, without the prior
written consent of the copyright owner.
This document has been produced by WRc plc.

CONTENTS
1

INTRODUCTION

PLANNING THE MIGRATION

2.1
2.2
2.3
2.4

Migration team
Harmon-It Use Case
Defining the exchange items
Defining the links

3
3
6
8

WRAPPING

3.1
3.2
3.3
3.4
3.5

Linkable engine
Migration
Migration of your model
The OMI XML file
Support for BOD

9
9
17
22
23

TESTING YOUR COMPONENT

25

COMBINING YOUR COMPONENT WITH OTHER COMPONENTS

27

5.1
5.2
5.3
5.4
5.5
5.6

Linking
Running
Results
Sewer to sewage works (CS to STOAT)
Sewage works to river (STOAT to RS)
Experience

27
29
30
31
35
38

CONCLUSION ON THE MIGRATION

39

6.1

Feed back to OpenMI

39

APPENDIX SOURCE CODE LISTINGS

43

APPENDIX 1

45

A1.1
A1.2

Engine
Main wrapper

APPENDIX 2
A2.1

C# WRAPPER CODE

45
45

VISUAL BASIC CODE

51

Support types

51

APPENDIX 3

OMI CREATOR

58

APPENDIX 4

MAIN CODE

65

APPENDIX 5

GLOBAL CHARACTERISATION PARAMETERS

81

WRc Ref: UC6920/13124-1


September 2005

APPENDIX 6

MAIN INPUT VALUE HANDLING

APPENDIX 7
PAPER SUBMITTED TO MODSIM05 CONFERENCE ON
MODEL USAGE AND INTEGRATION
LIST OF TABLES
Table 2.1
Migration Team
Table 2.2
Parameter exchange between programs
Table 2.3
Available parameters in STOAT

83

87
3
5
6

LIST OF FIGURES
Figure 2.1
Figure 2.2
Figure 5.1
Figure 5.2
Figure 5.3
Figure 5.4
Figure 5.5
Figure 5.6
Figure 5.7
Figure 5.8
Figure 5.9
Figure 5.10
Figure 5.11
Figure 5.12
Figure 5.13
Figure 5.14
Figure 5.15
Figure 5.16
Figure 5.17
Figure 5.18

Schematic connection
Data interpretation
InfoWorks CS city layout
STOAT sewage works model
InfoWorks RS river model
OpenMI Configuration Editor layout
Flow: CS outlet to STOAT
Flow: CS inlet to STOAT
Suspended solids: CS outlet to STOAT
Suspended solids: CS inlet to STOAT
Suspended solids: CS outlet to STOAT, ignoring peak value
Suspended solids: CS inlet to STOAT, ignoring peak value
Ammonia: CS outlet to STOAT
Ammonia: CS inlet to STOAT
Flow: STOAT outlet to RS
Flow: STOAT inlet to RS
Suspended solids: STOAT outlet to RS
Suspended solids: STOAT inlet to RS
Ammonia: STOAT outlet to RS
Ammonia: STOAT inlet to RS

WRc Ref: UC6920/13124-1


September 2005

ii

3
6
27
28
28
29
31
31
32
32
33
33
34
34
35
35
36
36
37
37

1 INTRODUCTION

The HarmonIT project has developed a modelling interface (OMI, the Open Modelling
Interface) for data exchange between programs. The aim of the OMI is to provide a uniform
exchange mechanism for any software that may be applied within the water cycle, and
specifically to facilitate the use of such programs for providing solutions within the Water
Framework Directive.
The core OMI developers have provided the definition of the interface and have demonstrated
that they can successfully apply the OMI to their own programs. Another part of the HarmonIT
consortium comprises the Work Package 8 (WP8) group, software developers and users who
have not participated in the development of the OMI. The WP8 members task is to
demonstrate that the OMI can be understood and applied by people who have not been
intimately involved with its development. WP8 is thus proving that the OMI can successfully
be applied based primarily on the available printed documentation.
WRc is a WP8 member, and has chosen to migrate STOAT. STOAT is a dynamic wastewater
modelling program, and has previously provided data exchange interfaces to river and sewer
models. The data exchange methods have been file based (with Wallingford Softwares
InfoWorks) and both file and direct program access with DHIs MOUSE and MIKE software.
These existing data exchange systems have been used to speed up the addition of the OMI
interface to STOAT.
This report covers the following:
Section 2 describes a use case for STOAT. This is an example of the use of STOAT with the
OMI, the expected results, the data to be exchanged, and a full listing of what data
parameters may be exchanged.
Section 3 is the main part of the report, describing the addition of support for the OMI. All the
code is given in the appendices, with Section 3 using excerpts to discuss the implementation
of the OMI.
Section 4 describes the tests carried out on the OMI-enabled STOAT
Section 5 describes the application of the use-case.
Finally, Section 6 provides conclusions, a discussion of difficulties encountered in
implementing the OMI, and the resolutions adopted.

WRc Ref: UC6920/13124-1


September 2005

WRc Ref: UC6920/13124-1


September 2005

2 PLANNING THE MIGRATION

2.1

Migration team

Table 2.1

Migration Team
Name
Jeremy Dudley

2.2

Role
Lead developer

Harmon-It Use Case

One example of a use case is presented, for the case where STOAT will be run with a sewer
model providing input to STOAT, and a river model receiving the output from STOAT.
2.2.1

Layout

InfoWorks CS

Figure 2.1
2.2.2

STOAT

InfoWorks RS

Schematic connection

Description

InfoWorks CS provides a sewer model, incorporating water quality parameters. InfoWorks CS


has two outlets a CSO spill point to a downstream river, and a sewer discharge to a sewage
works.
STOAT accepts the incoming flow from the sewer, and discharges to a river, downstream of
the CSO.
InfoWorks RS provides a river model that accepts two inputs, one from InfoWorks CS, the
other from STOAT.
2.2.3

Primary actor

Model integrator, using the prepared component models, and then linking them
together to provide the larger-scale system;

WRc Ref: UC6920/13124-1


September 2005

2.2.4

Preconditions

The principal actor (PA) has the OpenMI versions of:


o
o
o
o
o

The sewer model;


The sewage works model;
The sewer model;
The relevant data files for each component;
Documentation for the connection points for each model
Sewer: CSO spill and sewage works inlet
Sewage works: sewage works inlet and outlet;
River: CSO spill and sewage works outlet.
All component models are signed off as being numerically stable and adequately
calibrated for their individual roles.
2.2.5

Usage

Load the OpenMI GUI


Use the GUI to load the sewer model
Use the GUI to load the sewage works model
Use the GUI to load the river model
Use the GUI to define the connections between the sewer model and the river model
(CSO spill)
Use the GUI to define the connections between the sewer model and the sewage
works model (sewage works inlet)
Use the GUI to define the connections between the sewage works model and the river
model (sewage works outfall)
For each connection define the exchange parameters, as indicated in 2.2.6
Define the simulation period
Execute the simulation
2.2.6

Parameter exchange

Table 2.2 lists the parameters that are expected to be exchanged between the sewer model
and STOAT, and again between STOAT and the river model. The parameters fall into the
areas of flow, suspended solids, COD, ammonia, temperature and temperature. Details are
given in the table as to what is expected by STOAT.

WRc Ref: UC6920/13124-1


September 2005

Table 2.2

Parameter exchange between programs

Determinand
Flow
Suspended solids

InfoWorks CS1
m3/s
kg/m3

COD

kg/m3

Ammonia
Temperature
Alkalinity

kg/m3
0
C
Mol

2.2.7

STOAT2
3

m /h
kg/m3, divided into volatile and nonvolatile. If INFOWORKS CS
represents only suspended solids
then assume ratio 75:25 VSS:NVSS
kg/m3, divided into soluble
degradable COD (SS), soluble
nondegradable COD (SI),
particulate degradable COD (X S)
and particulate nondegradable COD
(XI). If INFOWORKS CS represents
COD only as COD (or as dissolved
and attached) then a suitable
partition will be defined in STOAT.
Should be a one-to-one mapping
0
C
Mol; will need to define a different
data source or assumed (constant)
value if not supported in
INFOWORKS CS

InfoWorks RS3
m3/s
kg/m3

kg/m3

kg/m3
0
C
mol

Assessment of success

2.2.7.1 OpenMI interoperability


The layout defined above can be successfully created.
Data mappings between InfoWorks CS, STOAT and InfoWorks RS can be created
within the OpenMI interface editor.
The OpenMI simulation runs with no error messages.
2.2.7.2 InfoWorks CS STOAT communication
The graphs of flow, COD, etc. from InfoWorks CS overlay the corresponding graphs
from STOAT.
The STOAT predictions of effluent quality are comparable to those produced by an
offline simulation. STOAT within OpenMI will adopt a stepwise definition of flow and
water quality, while the data-file driven STOAT uses piecewise linear interpolation. The
difference is indicated in Figure 2.2. Some discrepancies between the two approaches
will therefore be expected.
1
2
3

All exchange items must be available as outputs


All exchange items must be available as input and outputs
All exchange items must be available as inputs

WRc Ref: UC6920/13124-1


September 2005

File-based STOAT interpretation


OpenMI STOAT interpretation
Value

Time

Figure 2.2
2.3
2.3.1

Data interpretation

Defining the exchange items


Processes exposed in stoat

Initially STOAT will expose information for the following:


Streams
Activated sludge models ASM 1, 2d and 3
Activated sludge settling tanks
2.3.2

Parameters exposed in STOAT

Table 2.3
Process
Influent

Available parameters in STOAT


Sub-process

WRc Ref: UC6920/13124-1


September 2005

Parameter
Flow
COD
Volatile fatty acids
Soluble degradable COD
Soluble nondegradable COD
Particulate degradable COD
Particulate nondegradable COD
Suspended solids
Volatile suspended solids
Nonvolatile suspended solids
Ammonia
Nitrate
Soluble organic degradable N
Soluble organic nondegradable

Symbol
Q
COD
SA
SS
SI
XS
XI
TSS
VSS
NVSS
SNH
SNO
SNS
SNI

Input/Output
I
I
I
I
I
I
I
I
I
I
I
I
I
I

Process

Sub-process

Stream

WRc Ref: UC6920/13124-1


September 2005

Parameter
N
Particulate organic degradable
N
Particulate organic
nondegradable N
Phosphate
Soluble organic degradable P
Soluble organic nondegradable
P
Particulate organic degradable
P
Particulate organic
nondegradable P
Dissolved oxygen
Alkalinity
Temperature
pH
Flow
COD
Volatile fatty acids
Soluble degradable COD
Soluble nondegradable COD
Particulate degradable COD
Particulate nondegradable COD
Suspended solids
Volatile suspended solids
Nonvolatile suspended solids
Ammonia
Nitrate
Soluble organic degradable N
Soluble organic nondegradable
N
Particulate organic degradable
N
Particulate organic
nondegradable N
Phosphate
Soluble organic degradable P
Soluble organic nondegradable
P
Particulate organic degradable
P
Particulate organic
nondegradable P
Dissolved oxygen
Alkalinity
Temperature
pH

Symbol

Input/Output

XNS

XNI

SPO4
SPS
SPI

I
I
I

XPS

XPI

SO2
SALK
T
pH
Q
COD
SA
SS
SI
XS
XI
TSS
VSS
NVSS
SNH
SNO
SNS
SNI

I
I
I
I
O
O
O
O
O
O
O
O
O
O
O
O
O
O

XNS

XNI

SPO4
SPS
SPI

O
O
O

XPS

XPI

SO2
SALK
T
pH

O
O
O
O

Process
Activated
sludge

Sub-process
ASM 1

ASM 2

ASM 3

Settling tank
2.4

Parameter
Soluble degradable COD

Symbol
SS

Input/Output
O

Soluble nondegradable COD


Particulate degradable COD
Particulate nondegradable COD
Ammonia
Nitrate
Dissolved oxygen
Heterotrophs
Autotrophs
Volatile fatty acids
Soluble degradable COD
Soluble nondegradable COD
Particulate degradable COD
Particulate nondegradable COD
Suspended solids
Ammonia
Nitrate
Phosphate
Dissolved oxygen
Alkalinity
Temperature
Soluble degradable COD
Soluble nondegradable COD
Particulate degradable COD
Particulate nondegradable COD
Ammonia
Nitrate
Dissolved oxygen
Heterotrophs
Autotrophs
Alkalinity
Suspended solids

SI
XS
XI
SNH
SNO
SO2
XH
XA
SA
SS
SI
XS
XI
TSS
SNH
SNO
SPO4
SO2
SALK
T
SS
SI
XS
XI
SNH
SNO
SO2
XH
XA
SALK
TSS

O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O
O

Defining the links

Link names are exposed using the following schemas:


Streams: These have an ID name, e.g. Stream 1 or Wastewater influent.
Activated sludge: These have an ID name, but comprise multiple stages. The name
will be exposed as, e.g., Aeration tank 1 Stage 1 or Aeration tank 2 Stage 2.
Settling tanks: These have an ID name, but comprise multiple layers. The name will be
exposed as, e.g., FST #1 Layer 1 or FST #1 Layer 2.

WRc Ref: UC6920/13124-1


September 2005

3 WRAPPING
The standard OMI specification is written around Microsofts .NET architecture, with all
provided components written using C#, and all examples provided also using C#. Older
applications, such as STOAT, are not yet ready to be fully migrated to a .NET environment.
Because of this, the OMI system provides for wrappers small pieces of code that make use
of OMI components to handle much of the OMI processing, and are completely hidden from
the developer. All that the developer has to do is to implement the OMI Interfaces to the OMI
environment; additional interfaces to the original application, mapping between .NET and the
main program; and specify any custom behaviour required by the wrapping exercise. This
incurs an additional processing overhead, so that simulations will be slower but how
significant the communication overhead is will be reported upon during the testing phase. The
other implication is that the footprint of the applications is larger. For example, STOAT will
require installing its current run-time support files, for Visual Basic and Visual Fortran, and in
addition the .NET runtime support. However, many of these files are now provided as a
standard part of Microsofts operating systems, as they are shared by any application using
.NET or Visual Basic.
There are two main wrapper components. The first is a relatively trivial piece of code that
creates an object that will implement the main code. The second component, then, is the main
part of the wrapper, and this implements the needed OMI Interfaces.
3.1

Linkable engine

The linkable engine provides a level of indirection to the main code. All that is needed is that a
class member, _engineApiAccess, is set to an instance of the main code in this case,
STOATEngine. The code is listed below, excluding various directives to import namespaces.
namespace WRc.OMI
// All WRc OMI objects will be in this namespace.
{public class STOAT : LinkableEngine
// LinkableEngine hides much of the OMI work
{protected override void SetEngineApiAccess()
// Just this to implement
{_engineApiAccess = new STOATEngine();}
// Create a reference to the
// main work engine
}
}

3.2

Migration

The implicit mental model behind the OMI wrapping system was that the original program
provided a clean separation between the user interface, which would generate any required
data files, and the computational engine. The user interface would be run, which would create
data files. Then an additional data file would be created, which would contain information
about the OMI usage (this data file is discussed further in Section 3.4). The computational
engine would be rewritten as a DLL, and this DLL would be called directly by the wrapper.
STOAT is written with close coupling between the user interface and the computational
engine, where the user interface generates all data needed at each timestep, calls the
computational parts, and the processes the results. There is no provision for file handling
within the computational core. Because of this difference between the expected model and
STOAT the STOAT OMI implementation runs the main program. At present the STOAT user

WRc Ref: UC6920/13124-1


September 2005

interface is also displayed during OMI-based simulations, so that results can be displayed as
they are being calculated.
The wrapper core expects that the following should be implemented:
namespace WRc.OMI
// Must be same namespace as used for ILinkableEngine
{public class STOATEngine: IEngine
// This will be the main work engine
{// Long description of simulation
string IEngine.GetModelDescription()
// Short description of simulation
string IEngine.GetModelID()
// Number of items that can be provided to STOAT
int IEngine.GetInputExchangeItemCount()
// Description of data items that STOAT can use
InputExchangeItem IEngine.GetInputExchangeItem(int exchangeItemIndex)
// Repeat for items that STOAT can provide to other applications
int IEngine.GetOutputExchangeItemCount()
OutputExchangeItem IEngine.GetOutputExchangeItem(int exchangeItemIndex)
// Period for next simulation timestep
ITimeSpan IEngine.GetTimeHorizon()
// Close STOAT completely
void IRunEngine.Dispose()
// Close the current simulation
void IRunEngine.Finish()
// Long description of STOAT
string IRunEngine.GetComponentDescription()
// Short description of STOAT
string IRunEngine.GetComponentID()
// Time at which STOAT will provide next set of data items
ITime IRunEngine.GetCurrentTime()
// If STOAT is likely to interpolate using earlier values that the current,
// this is used to define how much history must be retained. IRunEngine maintains
// a data buffer, and uses the value of GetEarliestNeededTime to prevent the
// buffer from storing all simulation value since the start of the simulation.
ITimeStamp IRunEngine.GetEarliestNeededTime()
// Time at which STOAT will expect next set of data items
ITime IRunEngine.GetInputTime(string QuantityID, string ElementSetID)
// If data is not available, this is the numeric value that will be used
// to signal this. IrunEngine will then make an appropriate interpolation
// or extrapolation for other programs.
double IRunEngine.GetMissingValueDefinition()
// Get values from STOAT for use by other programs
IValueSet IRunEngine.GetValues(string QuantityID, string ElementSetID)
// Provide values to STOAT
void IRunEngine.SetValues(string QuantityID, string ElementSetID,
IValueSet values)
// Initialise STOAT
void IRunEngine.Initialize(System.Collections.Hashtable properties)
// Carry out a single timestep
bool IRunEngine.PerformTimeStep()
}
}

WRc Ref: UC6920/13124-1


September 2005

10

These methods are discussed in more detail in the following sections.


3.2.1

Changing your engine core

No changes were made to the engine core, in the sense of splitting the computational engine
from the user interface. New methods were added to the user interface to support the OMI
interface.
Because STOAT will be controlled directly by another program it must be compiled as a COM
component, in this case as an ActiveX executable. On being instantiated it must detect and
take appropriate action. The main subroutine (the starting point for a Visual Basic application)
contains the following check
If App.StartMode = vbSModeAutomation Then // Controlled by another program
giTVPClass = TVP_L3
// Set a flag that this is the case.

The STOAT Visual Basic OMI interface was written to duplicate, as closely as possible, the
OMI C# methods. To this end various support classes were written, to provide a match on
methods. These methods are:
CEnums: provides enumerated constants for dimensions and elements (only the
IDBased element is used by STOAT at present).
CDimension: class to hold dimensions of unit, e.g. m3/s.
CUnit: Linear conversion to SI units SI equivalent = a + b x value.
CQuantity: amalgamates units (conversion information to SI) and dimensions.
InputExchangeItem: Amalgamates elements and quantities, to provide a basic class
used for exchanging data.
OutputExchangeItem: Amalgamates elements and quantities, to provide a basic class
used for exchanging data.
ITimeSpan: a time period, delimited by a start and end.
IValueSet: An array of double precision values, used to hold the numerical values
being exchanged and described by InputExchangeItem (or OutputExchangeItem).
CElement: not used, but implemented for completeness of the definition of input and
output exchange items.
CSpatialRef: not used, but implemented for completeness of the definition of input and
output exchange items.
Each wrapper method then has a corresponding Visual Basic implementation. These are
described in parallel in the following sections.

WRc Ref: UC6920/13124-1


September 2005

11

3.2.2

Creating your .net assemblies

The C# code was written, using the wrapper, with the details given below. As well as providing
references to the main OMI components, an additional reference was required to STOAT.
Aside from STOAT, the following components had to be referenced:
org.OpenMI.Backbone
org.OpenMI.DevelopmentSupport
org.OpenMI.Standard
org.OpenMI.Utilities.Buffer
org.OpenMI.Utilities.Spatial
org.OpenMI.Utilities.Wrapper
System
System.Data
System.XML
3.2.3

Accessing the functions in your engine core

The functions in STOAT were chosen to map directly onto the functions exposed by the
wrapper. Therefore the STOAT functions are described in parallel with the wrapper functions.
3.2.4

Implementing MyEngineDotNetAccess

Not needed, because of the use of the wrapper.


3.2.5

Implementing the MyEngineWrapper

Not needed, because of the use of the wrapper.


3.2.6

Implementing MyModelOpenMIComponent

Not needed, because of the use of the wrapper.


3.2.7

Implementation of the IEngine methods

Four global objects were defined for the class, to map onto the STOAT classes.
STOAT4.HarmonIT
STOAT4.InputExchangeItem
STOAT4.OutputExchangeItem
STOAT4.IValueSet

WRc Ref: UC6920/13124-1


September 2005

LSTOAT;
STOAT_In;
STOAT_Out;
STOAT_Val;

12

The wrapper code in large part calls the STOAT Visual Basic equivalents. There may be a
need to explicitly assign from STOAT return values to wrapper values, where
structures/classes are used to hold the return values. In the following, the wrapper (using C#)
is written in black while the main STOAT program, written in Visual Basic, is written in red.
string IEngine.GetModelDescription()
{return LSTOAT.GetModelDescription();}

// Pass-through to VB

Public Function GetModelDescription() As String


// Return works + run name
GetModelDescription = gCurrentWorks.sName & ": " & gCurrentRun.sName
End Function
string IEngine.GetModelID()
{return LSTOAT.GetModelID();}

// Pass-through to VB

Public Function GetModelID() As String

// As GetModelDescription, but
// replacing spaces with underscores

Dim s As String
s = "Works " & gCurrentWorks.iID & ": Run " & gCurrentRun.iID
s = Replace(s, " ", "_")
GetModelID = s
End Function
int IEngine.GetInputExchangeItemCount()
{return LSTOAT.GetInputExchangeItemCount();

// Pass-through to VB}

Public Function GetInputExchangeItemCount() As Long


Dim i
As Long
Dim iCount As Long
// This routine is called many times during initialisation by the OpenMI
// configuration editor. Only once is the code acctually required;
// therefore the number of items is set to 1 during STOATs own
// initialisation, and the counting done just once.
If NumberOfInputItems <> -1 then
GetInputExchangeItemCount = NumberOfInputItems
Exit function
End if
iReturn = 0
For i = 1 To giNumberOfProcesses
// Search through all processes
If Not WorksProcess(i).bDeleted Then
// ignore any that have been deleted
Select Case WorksProcess(i).iType
Case STW_INFL, STW_IND, STW_LAND
// At present, only use influents
iCount = iCount + NUMBER_OF_INFLUENT_EXCHANGE_VALUES // Add to count of input
// exchange items
End Select
End If
Next i
GetInputExchangeItemCount = iCount
// Return value
ReDim Preserve Inputs(0 To iCount)
// Dimension an internal array
// that will be used to hold then
// details of the exchange items
NumberofInputItems = iCount
End Function
InputExchangeItem IEngine.GetInputExchangeItem(int exchangeItemIndex)
{ InputExchangeItem item = new InputExchangeItem();
STOAT_In
sDescription
sID
item.ElementSet

LSTOAT.GetInputExchangeItem(exchangeItemIndex);
STOAT_In.Element.get_Description();
// VB copy to C#
STOAT_In.Element.get_ID();
new ElementSet(sDescription, sID, ElementType.IDBased,
new SpatialReference());
// Assemble C# struct
// Repeat for units details not shown
// repeat for quantity details not show
return item;
}

WRc Ref: UC6920/13124-1


September 2005

=
=
=
=

13

Public Function GetInputExchangeItem(ByVal ItemIndex As Long) As InputExchangeItem


// Map from the exchange item index to a process number (iWanted) and the exchange
// item for that process (iItem). Each influent process supports the same number
// ofpossible exchange values, NUMBER_OF_INFLUENT_EXCHANGE_VALUES
iWanted = Int(ItemIndex / NUMBER_OF_INFLUENT_EXCHANGE_VALUES) + 1
iItem = ItemIndex Mod NUMBER_OF_INFLUENT_EXCHANGE_VALUES
For i = 1 To giNumberOfProcesses
If Not WorksProcess(i).bDeleted Then
Select Case WorksProcess(i).iType
Case STW_INFL, STW_IND, STW_LAND
iCount = iCount + 1
If iCount = iWanted Then Exit For
End Select
End If
Next i

// Search over all processes


// Ignore deleted processes
// Only consider influents
// Quit when we have reached the wanted influent

// Now assign dimensions.


fOffset = 0: fMultiplier = 1 // Assume SI units as default
Set tDim = New CDimension
3
tDim.SetAll -3, 1
' Will override where needed
// Assume kg/m as default
sUnit = "kg/m"
Select Case iItem
Case 0: sComp = "Q":
sDescription = "Flow": tDim.SetAll 3, 0, -1: sUnit = "m/s"
Case 1: sComp = "COD":
sDescription = "Total COD"
Case 2: sComp = "SA":
sDescription = "Volatile fatty acid as COD"
Case 3: sComp = "SS":
sDescription = "Soluble degradable COD"
// Other items have been deleted for clarity
End Select
'
' Save the results for local use
'
With Inputs(ItemIndex)
.iStage = 1
.iType = PROCESS
.iUnit = i
.sParameter = sComp
.fSIMult = fMultiplier
.fSIOffset = fOffset
.sLabel = WorksProcess(i).sName
End With

' No stage for influent


' Record location in main array
' Record component

// Copy into the required return class (of type InputExchangeItem)


Set tResult = New InputExchangeItem
With tResult
With .Element
.ID = WorksProcess(i).sName
.Description = WorksProcess(i).sName
End With
With .Quantity
.ID = sComp
.Description = sDescription
.SetDimension tDim
With .Unit
.ID = sUnit
.Description = sUnit
.ConversionFactorToSI = fMultiplier
.OffsetToSI = fOffset
End With
End With
End With
Set GetInputExchangeItem = tResult
End Function
int IEngine.GetOutputExchangeItemCount()

WRc Ref: UC6920/13124-1


September 2005

14

{return LSTOAT.GetOutputExchangeItemCount();

// Pass-through to VB}

// Largely the same as GetInputExchangeItemCount, but because there are various


// processes available streams, activated sludge units, settling tanks then
// number of exchange items for each kind of process is different.
Public Function GetOutputExchangeItemCount() As Long
Dim i
As Long
Dim iCount As Long
Dim iStages
// This routine is called many times during initialisation by the OpenMI
// configuration editor. Only once is the code acctually required;
// therefore the number of items is set to 1 during STOATs own
// initialisation, and the counting done just once.
If NumberOfOutputItems <> -1 then
GetOutputExchangeItemCount = NumberOfOutputItems
Exit function
End if
iCount = 0
For i = 1 To giNumberOfProcesses
// Search over all processs
If Not WorksProcess(i).bDeleted Then
// Ignore deleted processes
iStages = WorksProcess(i).iNumberOfStages
// Record number of stages
Select Case WorksProcess(i).iType
Case STW_ASAL
// Activated sludge
Select Case WorksProcess(i).iModelID
Case MDL_IAWQ1, MDL_IAWQ1_BENCH, MDL_ASM1A, MDL_ASM1B
// ASM1 family
iCount = iCount + NUMBER_OF_ASM1_EXCHANGE_VALUES * iStages
Case MDL_IAWQ2D, MDL_IAWQ2W, MDL_ASM2D, MDL_ASM2W
// ASM2 family
iCount = iCount + NUMBER_OF_ASM2_EXCHANGE_VALUES * iStages
Case MDL_IAWQ3
// ASM3 family
iCount = iCount + NUMBER_OF_ASM3_EXCHANGE_VALUES * iStages
End Select
Case STW_SSED
// Settling tanks
iCount = iCount + NUMBER_OF_FST_EXCHANGE_VALUES * iStages
End Select
End If
Next i
For i = 1 To giNumberOfLinks
If Not WorksLink(i).bDeleted Then
iCount = iCount + NUMBER_OF_STREAM_EXCHANGE_VALUES
End If
Next i

// Also add streams

GetOutputExchangeItemCount = iCount
ReDim Outputs(0 To iCount)
NumberOfOutputItems = iCount
End Function
// A repeat of InputExhange, substituting Output for Input
OutputExchangeItem IEngine.GetOutputExchangeItem(int exchangeItemIndex)
Public Function GetOutputExchangeItem(ByVal ItemIndex As Long) As OutputExchangeItem
iModel = 0
// First, map the exchange item index onto a process or stream, and an exchange item for
// that process or stream.
For i = 1 To giNumberOfProcesses
If Not WorksProcess(i).bDeleted Then
iStages = WorksProcess(i).iNumberOfStages
Select Case WorksProcess(i).iType
Case STW_ASAL
Select Case WorksProcess(i).iModelID
Case MDL_IAWQ1, MDL_IAWQ1_BENCH, MDL_ASM1A, MDL_ASM1B
// Have we reached the relevant process?
If iCount + NUMBER_OF_ASM1_EXCHANGE_VALUES * iStages > ItemIndex Then
iModel = 1
// If yes, record the process type
iType = ItemIndex iCount // Record the process exchange item
iStages = 1
// Map it onto the required stage
Do While iType >= NUMBER_OF_ASM1_EXCHANGE_VALUES
iType = iType - NUMBER_OF_ASM1_EXCHANGE_VALUES

WRc Ref: UC6920/13124-1


September 2005

15

iStages = iStages + 1
Loop
sName = WorksProcess(i).sName & ": Stage " & Trim$(iStages)
Exit For
// And quit
End If
// If this is not the relevant process, increment the number of exchange items for which
// we have searched, and proceed to the next process
iCount = iCount + NUMBER_OF_ASM1_EXCHANGE_VALUES * iStages
Case MDL_IAWQ2D, MDL_IAWQ2W, MDL_ASM2D, MDL_ASM2W
// Similar code for other processes deleted for clarity
End Select
End If
Next i
// If no match found when searching for processes, continue searching for streams
If iModel = 0 Then
For i = 1 To giNumberOfLinks
If Not WorksLink(i).bDeleted Then
If iCount + NUMBER_OF_STREAM_EXCHANGE_VALUES > ItemIndex Then
iModel = -1
iType = ItemIndex - iCount
sName = WorksLink(i).sName
Exit For
End If
iCount = iCount + NUMBER_OF_STREAM_EXCHANGE_VALUES
End If
Next i
End If
// Assign dimensions
fOffset = 0: fMultiplier = 1
Set tDim = New CDimension
tDim.SetAll -3, 1
' Will over ride where needed
sUnit = "kg/m"
Select Case iModel
Case -1
Select Case iType
Case 0: sComp = "Q":
sDescription = "Flow": tDim.SetAll 3, 0, -1: sUnit = "m/s"
Case 1: sComp = "COD":
sDescription = "Total COD"
// Similar code deleted for clarity
End Select
' Record results for local use
With Outputs(ItemIndex)
.iStage = iStages
If iModel > 0 Then
.iType = PROCESS
Else
.iType = STREAM
End If
.iUnit = i
.sParameter = sComp
.sLabel = sName
.fSIMult = fMultiplier
.fSIOffset = fOffset
End With
Set tResult = New OutputExchangeItem
With tResult
With .Element
.ID = sName
.Description = sName
End With
With .Quantity
.SetDimension tDim
.ID = sComp
.Description = sDescription

WRc Ref: UC6920/13124-1


September 2005

16

With .Unit
.ID = sUnit
.Description = sUnit
.ConversionFactorToSI = fMultiplier
.OffsetToSI = fOffset
End With
End With
End With
Set GetOutputExchangeItem = tResult
End Function
ITimeSpan IEngine.GetTimeHorizon()
{ STOAT4.ITimeSpan T = LSTOAT.GetTimeHorizon();
// Pass-through to VB
TimeStamp start = new TimeStamp(T.Start);
// Transfer to C# equivalents
TimeStamp finish = new TimeStamp(T.Finish);
return new org.OpenMI.Backbone.TimeSpan(start,finish);
}
Public Function GetTimeHorizon() As ITimeSpan
Dim cReturn As New ITimeSpan
cReturn.Start = ToModifiedJulian(CDbl(gCurrentRun.varStartTime))
cReturn.Finish = ToModifiedJulian(CDbl(gCurrentRun.varEndTime))
Set GetTimeHorizon = cReturn
End Function

3.2.8

// Simulation start
// Simulation end

Implementing IManageState interface

Not supported in this version of STOAT.


3.3

Migration of your model

3.3.1

Implementation of the Initialise method


void IRunEngine.Initialize(System.Collections.Hashtable properties)
{sDatabase
=
(string) properties["Database"];
iWorksID
= short.Parse((string) properties["WorksID"]);
iRunID
= short.Parse((string) properties["RunID"]);
iOldRunID
= short.Parse((string) properties["OldRunID"]);
iStartCondition
= short.Parse((string) properties["StartCondition"]);
bReuseExistingRun = bool.Parse( (string) properties["ReuseExistingRun"]);
sRunName
=
(string) properties["RunName"];
// Extract model calibration parameters
sa = double.Parse((string) properties["SA"]);

// COD -> SA

// Remainder removed for clarity


LSTOAT
STOAT_In
STOAT_Out
STOAT_Val

=
=
=
=

new
new
new
new

STOAT4.HarmonITClass();
STOAT4.InputExchangeItemClass();
STOAT4.OutputExchangeItemClass();
STOAT4.IValueSetClass();

// Initialise STOAT
bReturn = LSTOAT.Initialize(sDatabase, iWorksID, iRunID, iOldRunID,
iStartCondition, bReuseExistingRun, sRunName);
LSTOAT.SetConstants(ss, si, xs, xi, sa, sol_ss, sol_si, sol_sns, sol_sni,
sol_sps, sol_spi, sol_xs, sol_xi, sol_xns, sol_xni,
sol_xps, sol_xpi);
}
' iWorksID -- the works ID that shall be used. This will be stored within the OMI file
' iRunID -- the run ID that shall be used. This may be modified - see below
' iOldRunID -- if a run is to be created based on a previous run

WRc Ref: UC6920/13124-1


September 2005

17

'
A value of -1 means do not use this
' iStartCondition - 0:: Start from beginning
'
1:: Start from end
'
2:: Start from end, preserve operational settings
' bReuse -- TRUE -- if the run ID has been used, overwrite it
'
FALSE append as the next available run slot
'
Public Function Initialize(ByVal sDatabase As String, _
ByVal iWorksID As Integer, ByVal iRunID As Integer, _
ByVal iOldRunID As Integer, ByVal iStartCondition As Integer,
_
ByVal bReuse As Boolean, ByVal sRunName As String) As Boolean
// Change the database to the one to be used for this simulation
If gdbUser Is Nothing Then
QuietChangeDatabase sDatabase
ElseIf gdbUser.Name <> sDatabase Then
QuietChangeDatabase sDatabase
End If
// Open the works
gOpenBatchWorks iWorksID
// Open the reun
gbCreateOMIWarmStartRun iRunID, iOldRunID, iStartCondition, bReuse, sRunName
// Set various internal parameters to a state that indicates that a simulation may proceed
SetRunMode True
Initialize = True
gbStandardRun = False ' Disable end of run message
NumberOfInputItems = -1
NumberOfOutputItems = -1
frmIconMenu.Hide
End Function

3.3.2

Implementation of the SetValues method


void IRunEngine.SetValues(string QuantityID, string ElementSetID, IValueSet values)
{double f = ((IScalarSet) values).GetScalar(0);
// Extract a double precision number
STOAT_Val.set_Values(0, f);
// Assign it to a STOAT structure
LSTOAT.SetValues(QuantityID,ElementSetID,STOAT_Val); // Call STOAT to set it
}
' set internal value with requested external value
Public Sub SetValues(ByVal QuantityID As String, ByVal ElementSetID As String, _
ByVal Values As IValueSet)
Dim i As Long
// Find the corresponding data element in STOAT
For i = 0 To NumberofInputItems - 1
With Inputs(i)
If .sLabel = ElementSetID And .sParameter = QuantityID Then
.fValue = Values.Values(0)
// Set the value
SetInputValue Inputs(i)
// And then pass it through to the correct process
Exit For
End If
End With
Next i
End Sub
Private Sub SetInputValue(T As RecordData)
' Must be an influent
' First, convert to SI units
T.fValue = ConvertToSI(T.fValue, T.fSIMult, T.fSIOffset)
' Now convert to desired units
Select Case T.sParameter
Case "Q": T.fValue = T.fValue * 3600#
Case "T": T.fValue = T.fValue - 273.15
Case "pH"
Case "SALK"
Case Else: T.fValue = T.fValue * 1000#
End Select

WRc Ref: UC6920/13124-1


September 2005

18

// Always start with SI units

// Then convert to local units

Select Case T.iType


Case 0 ' Influent
INFLUENT.gSetOMIStreamData T.iUnit, T.sParameter, T.fValue

// Assign to the
// relevant influent

End Select
End Sub
Public Sub gSetOMIStreamData(iStream As Integer, sComp As String, fVal As Single)
Dim i
As Integer
Dim j
As Integer
Static bCalled
As Boolean
' Defaults to FALSE
Static dLastTime As Double
// The first time this is used in a simulation the LastTime is set to 1
// There is not always a one-to-one mapping between input exchange items and the STOAT
// usage, so that the start of each timestep needs to be recorded, and then as data is
// exchanged the relevant transformations are carried out.
If Not bCalled Then
bCalled = True
dLastTime = -1
End If

' Simulation time always start at zero

If dLastTime < gCurrentRun.fElapsedTime Then


// If this is the start of a new timestep record this for those determinands that
// may need a data transformation
dLastTime = gCurrentRun.fElapsedTime
For i = 1 To giNumberOfInfluents
mInfluentRun(i).Reset(1) = True
' XS
mInfluentRun(i).Reset(2) = True
' XI
mInfluentRun(i).Reset(3) = True
' XNS
mInfluentRun(i).Reset(4) = True
' XNI
mInfluentRun(i).Reset(5) = True
' XPS
mInfluentRun(i).Reset(6) = True
' XPI
Next i
End If
'
' Set a determinand for a stream
'
For j = 1 To giNumberOfInfluents
// If the stream is to be controlled by OMI, rather than to use a data file,
// record this
If mInfluentRun(j).iOutletLinkID = WorksLink(iStream).iID Then
If mInfluentRun(j).iChannel > 0 Then Close #mInfluentRun(j).iChannel
mInfluentRun(j).iChannel = TVP_DEFINED
Exit For
End If
Next j
For i = 1 To 3 Step 2
With mInfluentRun(j).STREAM(i)
Select Case sComp
Case "Q":
.fFlow = fVal * 3600#
Case "COD":
.fComponent(SOL_BOD) = fVal * omi_ss
.fComponent(SOL_INERT_COD) = fVal * omi_si
.fComponent(PART_BOD) = fVal * omi_xs
.fComponent(PART_INERT_COD) = fVal * omi_xi
.fComponent(VFA) = fVal * omi_sa
Case "SA":
.fComponent(VFA) = fVal
// Example of the transformation issues. Soluble COD in STOAT is defined as material
// that passes through a 0.7 micron filter paper. Soluble COD in many sewer and
// river models is defined as material that is left in the luqid phase after 30 minutes
// settling. Some of this river/sewer soluble COD may be trapped on a filter paper,
// and therefore in STOAT should be assigned to particulate COD and not soluble COD.
Case "SS":
.fComponent(SOL_BOD) = fVal * omi_sol_ss // Assign true
// soluble part
If mInfluentRun(j).Reset(1) Then
// If particulate COD has not yet been assigned,
// then set it to the solublefraction.
.fComponent(PART_BOD) = fVal * (1 - omi_sol_ss)
Else
// If it has already been assigned then add to the

WRc Ref: UC6920/13124-1


September 2005

19

// existing value
.fComponent(PART_BOD) = .fComponent(PART_BOD) _
+ fVal * (1 - omi_sol_ss)
End If
mInfluentRun(j).Reset(1) = False
// Similar code removed for clarity
End Select
End With
Next i
End Sub

3.3.3

Implementing the GetValues method


IValueSet IRunEngine.GetValues(string QuantityID, string ElementSetID)
{
STOAT_Val = LSTOAT.GetValues(QuantityID,ElementSetID);
double [] fResult = new double [] {STOAT_Val.get_Values(0)};
return new ScalarSet(fResult);
}
// Fairly simple mapping between the C# and Visual Basic
' Return internal values
Public Function GetValues(ByVal QuantityID As String, _
ByVal ElementSetID As String) As IValueSet
Dim i As Long
Dim T As IValueSet
Set T = New IValueSet
For i = 0 To NumberOfOutputItems - 1
With Outputs(i)
If .sParameter = QuantityID And .sLabel = ElementSetID Then
GetOutputValue Outputs(i)
T.Values(0) = .fValue
Exit For
End If
End With
Next i
Set GetValues = T
End Function
// Call different routines for streams and processes
Private Sub GetOutputValue(T As RecordData)
Select Case T.iType
Case STREAM
GetStreamOutputValue T
Case PROCESS
GetProcessOutputValue T
End Select
End Sub
// get stream values
Private Sub GetStreamOutputValue(T As RecordData)
Dim f As Double
With WorksLink(T.iUnit).Initial(1)
Select Case T.sParameter
Case "Q":
f = .fFlow
Case "COD":
f = .fComponent(SOL_BOD) + .fComponent(SOL_INERT_COD) _
+ .fComponent(PART_BOD) + .fComponent(PART_INERT_COD)
Case "SA":
f = .fComponent(VFA)
// Do the reverse mapping between soluble COD in STOAT and soluble COD for river/sewer models
Case "SS":
f = .fComponent(SOL_BOD) + omi_sol_xs * .fComponent(PART_BOD)
// Similar code removed for clarity
End Select
End With
' Now convert to SI

WRc Ref: UC6920/13124-1


September 2005

20

Select Case T.sParameter


Case "Q": f = f / 3600#
Case "T": f = f + 273.15
Case "pH"
Case "SALK"
Case Else: f = f / 1000#
End Select
T.fValue = f
End Sub

' m3/h -> m3/s


' Celsius -> Kelvin
' mmol/l -> mol/m3
' mg/l (g/m3) -> kg/m3

// And the same for processes


Private Sub GetProcessOutputValue(T As RecordData)
// Code removed for clarity
End Sub

3.3.4

Implementation of the remaining methods


// The following all pass-through to VB
void IRunEngine.Dispose() {LSTOAT.Dispose();}
void IRunEngine.Finish() {LSTOAT.Finish();}
string IRunEngine.GetComponentDescription() {return LSTOAT.GetComponentDescription();}
string IRunEngine.GetComponentID() {return LSTOAT.GetComponentID();}
double IRunEngine.GetMissingValueDefinition() {return LSTOAT.GetMissingValue();}
bool IRunEngine.PerformTimeStep() {return LSTOAT.PerformTimeStep();}
Public Sub Dispose(); gbCloseRun; gbCloseWorks ForceClose:=True; End;End Sub
Public Sub Finish(); gbSaveRun; End Sub
Public Function GetComponentID() As String; GetComponentID = "WRc_STOAT"; End Function
Public Function GetComponentDescription() As String
GetComponentDescription = "Dynamic sewage treatment modelling system"
End Function
Public Function GetMissingValue() As Double;

GetMissingValue = -999; End Function

Public Function PerformTimeStep() As Boolean


gRunControl RUN_STEP
// Transfer control to the existing STOAT run control
PerformTimeStep = True
End Function
// Or need a minor intermediate step
ITime IRunEngine.GetCurrentTime()
{TimeStamp fTime = new TimeStamp(LSTOAT.GetCurrentTime()); return fTime;}
Public Function GetCurrentTime() As Double
// The current time must be handled to the nearest second; fractional seconds
// result in the OpenMI configuration editor reporting an error and stopping
// a simulation early.
Dim fTime as double
fTime = CLng(3600# * gCurrentRun.fElapsedTime) / 86400#
fTime = DDbl((gCurrentRun.varStartTime) + fTime
GetCurrentTime = ToModifiedJulian(fTime)
End Function
ITimeStamp IRunEngine.GetEarliestNeededTime()
{TimeStamp fTime = new TimeStamp(LSTOAT.GetEarliestNeededTime()); return fTime;}
// Earliest needed time is for the current simulation time
Public Function GetEarliestNeededTime() As Double
// The time must be handled to the nearest second; fractional seconds
// result in the OpenMI configuration editor reporting an error and stopping
// a simulation early.
Dim fTime as double
fTime = CLng(3600# * gCurrentRun.fElapsedTime) / 86400#
fTime = DDbl((gCurrentRun.varStartTime) + fTime
GetEarliestNeededTime = ToModifiedJulian(fTime)
End Function

WRc Ref: UC6920/13124-1


September 2005

21

ITime IRunEngine.GetInputTime(string QuantityID, string ElementSetID)


{double dTime = LSTOAT.GetInputTime(); return new TimeStamp(dTime);}
// On the first call return the start time, to get any available initial data
// from the communicating applications.Thereafter, the required time is for the next
// timestep.
Public Function GetInputTime() As Double
// The current time must be handled to the nearest second; fractional seconds
// result in the OpenMI configuration editor reporting an error and stopping
// a simulation early.
Dim fTime as double
Static bCalled As Boolean
If bCalled Then
fTime = CLng(3600# * (gCurrentRun.fElapsedTime + gCurrentRun.fInputTimestep)) / 86400#
fTime = DDbl((gCurrentRun.varStartTime) + fTime
GetInputTime = ToModifiedJulian(fTime)
Else
bCalled = True
GetInputTime = ToModifiedJulian(CDbl(gCurrentRun.varStartTime))
End If
End Function

3.4

The OMI XML file

The OMI XML file is written through the Visual Basic interface.
The user can access the following screen:

The following information is required:


1. A base run, already created.
2. A name for the new run.
3. The option to overwrite an existing run.
4. The option to start the run from the following points:

WRc Ref: UC6920/13124-1


September 2005

22

a. The start of the base run;


b. The end of the base run, losing any operational settings that have already
expired;
c. The end of the base run, but treating the simulation as starting at time zero and
reusing the complete operational schedule.
5. Calibration data, to map between STOAT (soluble material is defined as passing
through a 0.7 micron filter mesh) and other models (typically, soluble material is
defined as material in suspension after 30 minutes standing.
6. Calibration data, if total COD is to be passed to STOAT and the broken down into the
common 5 COD fractions volatile fatty acids (SA); soluble biodegradable (SS),
soluble nondegradable (SI), particulate biodegradable (XS) and particulate
nondegradable (XI).
These are then written to an OMI XML file, which has the form shown below.
<?xml version="1.0"?>
<LinkableComponent Type="WRc.OMI.STOAT"
Assembly = C:\code\stoat\VB\OMI\OMI_STOAT.dll
xmlns = http://www.openmi.org/LinkableComponents.xsd>
<Arguments>
<Argument Key = "Database" Value = "C:\projects\OMI Test\user.mdb" />
<Argument Key = "WorksID" Value = "1" />
<Argument Key = "RunID" Value = "1" />
<Argument Key = "OldRunID" Value = "1" />
<Argument Key = "StartCondition" Value = "0" />
<Argument Key = "ReuseExistingRun" Value = "False" />
<Argument Key = "RunName" Value = "jd test" />
<Argument Key = "SS" Value = "0.36" />
<Argument Key = "SI" Value = "0.04" />
<Argument Key = "XS" Value = "0.56" />
<Argument Key = "XI" Value = "0.04" />
<Argument Key = "SA" Value = "0" />
<Argument Key = "Sol_SS" Value = "1" />
<Argument Key = "Sol_SI" Value = "1" />
<Argument Key = "Sol_SNS" Value = "1" />
<Argument Key = "Sol_SNI" Value = "1" />
<Argument Key = "Sol_SPS" Value = "1" />
<Argument Key = "Sol_SPI" Value = "1" />
<Argument Key = "Sol_XS" Value = "0" />
<Argument Key = "Sol_XI" Value = "0" />
<Argument Key = "Sol_XNS" Value = "0" />
<Argument Key = "Sol_XNI" Value = "0" />
<Argument Key = "Sol_XPS" Value = "0" />
<Argument Key = "Sol_XPI" Value = "0" />
</Arguments>
</LinkableComponent>

3.5

Support for BOD

Following the development of a larger use case for a paper, there was a need to link STOAT
with Wallingford Softwares SULIS program. SULIS works with BOD, requiring provision of
dissolved and attached BOD. The Wallingford Software river model, InfoWorks RS, could
provide COD as dissolved and attached fractions.
STOAT was modified to provide support for these fractions, both on input and output for
streams. The approach is indicated in the following figures.

WRc Ref: UC6920/13124-1


September 2005

23

BOD
F1

1 - F1

Dissolved BOD

Attached BOD

F2S

F2P

Dissolved
degradable COD

Attached
degradable COD
1 F3P

1 F3S

F3S

F3P

Dissolved
nondegradable
COD

Attached
nondegradable
COD

1 F4S
1 F4S

Soluble
degradable COD

Soluble
nondegradable
COD

Particulate
degradable COD

F4S

F4S

F1: Fraction of BOD that is dissolved


F2S: Ratio of dissolved degradable COD to dissolved BOD
F2P: Ratio of attached degradable COD to attached BOD
F3S: Ratio of dissolved degradable COD to dissolved COD
F3P: Ratio of attached degradable COD to attached COD
F4: Fraction of dissolved material that will not pass through a filter

WRc Ref: UC6920/13124-1


September 2005

24

Particulate
nondegradable
COD

4 TESTING YOUR COMPONENT

STOAT Testing did not use the unit-testing approach, because the bulk of the STOAT code
was in a Visual Basic environment for which a suitable unit-based testing framework was not
available. Testing instead proceeded using primarily the insertion of print statements (in a
Windows environment, MessageBox calls), along with the DataMonitor component in the
OpenMI configuration editor.
The STOAT code was implemented easily, following the guidelines given in the OpenMI
Guidelines publication and also the example framework provided during the December 2004
OpenMI training course. The main bug found was associated with the limited error checking
carried out by the OpenMI configuration editor, when one (large) STOAT flowsheet was
replaced by another (smaller) flowsheet, without updating the composition data file created by
the configuration editor. The substitution of a small flowsheet for a larger one was required
because of the implementation of the configuration editor, which requeried for configuration
data following each mouse click with the large flowsheet this resulted in each mouse click
being followed by a delay of around 15 minutes while the (unchanged) data was being
requeried.
An updated configuration editor was provided, as this problem had been recognised by the
OpenMI development team, but this failed to parse the XML OMI configuration files correctly.
Testing therefore continued using the original configuration editor, pending a release of the
next OpenMI update.
The DataMonitor was used to ensure that the output from STOAT, through the OpenMI
GetValues routine, matched the values being generated in STOAT. At that stage testing was
halted, until the OpenMI update was available.
Further testing of STOAT was done as part of preparation for a paper, submitted to the
ModSim05 conference, discussing experience of using the OpenMI to link different programs
together. This testing made use of the updated configuration editor, where usability for
creating OpenMI compositions had greatly improved.
The testing revealed three main bugs in the STOAT implementation:
1. The OMI file was not properly formed, and the argument parameters Key and Value were
case-sensitive when they had been specified as key and value they had not been
correctly parsed. An earlier version of the configuration editor had not had been case
sensitive, not had it complained about ill-formed XML.
2. The routine GetInputExchangeItemCount is called many times. Each time it is called the
STOAT implementation created local storage space for the exchange items, and cleared
the storage. The local storage values are then assigned during the calls to
GetInputExchangeItem. However, after GetInputExchangeItem had been called there
were additional calls to GetInputExchangeItemCount, and these destroyed the values
assigned to the local copy of the InputExchangeItems. When data was being exchanged
through calls to SetValue STOAT was not able to locate the exchanged data items. The
solution adopted to this problem was to check if GetInputExchangeItemCount had been
previously called, and if so to return the previous count, rather than destroy the local
storage space.

WRc Ref: UC6920/13124-1


September 2005

25

3. Elapsed times are stored as double precision numbers. The OpenMI configuration editor
appears to expect that the smallest unit of time is the second. As long as GetCurrentTime
and similar time routines returned times that could be expressed as an integer number of
seconds the configuration editor would run OpenMI compositions. However, if a rounding
error (as found when using a timestep of 0.01 hours, because 0.01 is not an exact binary
fraction) led to times that had a fractional second, then the configuration editor would halt
a simulation with a failure message. The solution here was to calculate the time and to
ensure that it was always rounded to an integral number of seconds; in Visual Basic this
was implemented as CLng(3600 x Elapsed time in hours). The CLng function in Visual
Basic rounds to the nearest integer, with rounding to even for the special case where the
fractional part is exactly 0.5.

WRc Ref: UC6920/13124-1


September 2005

26

5 COMBINING YOUR COMPONENT WITH OTHER COMPONENTS

5.1

Linking

STOAT was first linked with STOAT, running the output from one STOAT into a second. It
was then run with the test case, InfoWorks CS feeding STOAT, which in turn fed InfoWorks
RS. Finally, two instances of STOAT were used as part of a large configuration, described in
Appendix 7.
This section describes the output of the case where InfoWorks CS provided the input to
STOAT, and STOAT the input to InfoWorks RS.
There were no problems with linking the models. The layouts of the three models are given in
Figure 5.1 (InfoWorks CS), Figure 5.2 (STOAT) and Figure 5.3 (InfoWorks RS). The linking of
these three models was done using the OpenMI configuration editor, and is shown in Figure
5.4.

Figure 5.1

InfoWorks CS city layout

WRc Ref: UC6920/13124-1


September 2005

27

Figure 5.2

STOAT sewage works model

Figure 5.3

WRc Ref: UC6920/13124-1


September 2005

InfoWorks RS river model

28

CS, RS and
STOAT models
described in this
section

Figure 5.4

OpenMI Configuration Editor layout

When loading saved configurations STOAT produces OMI files that by default create a new
instance of a run for each simulation. Because it was new run, with a new ID number, the
configuration editor was unable to recreate the configuration. STOAT also has the option with
the OMI files to use an existing run, so that the run ID will be the same for each usage. With
this option the configuration editor proceeded with no problems. STOAT has been modified to
make this the default behaviour when creating an OMI file.
5.2

Running

There were no problems running STOAT, once the two problems described in Section 4 had
been resolved. These problems were failure of SetValues to locate the passed input items,

WRc Ref: UC6920/13124-1


September 2005

29

and the rounding errors in time calculations resulting in simulations being stopped early with a
failure message because the time was not an integer number of seconds.
5.3

Results

The results were passed correctly between the different programs. There was a need to
ensure that the timesteps used by all three programs were the same, as otherwise peaks in
flow and quality could be missed (if they did not occur on a communication timestep) or
sustained for too long (because interpolation within the programs was on the communicated
values.)
InfoWorks CS programs uses units of m3/s and kg/m3, STOAT m3/h and mg/l (g/m3), and
InfoWorks RS m3/s and mg/l. Therefore the scaling of a factor of 1000 between STOAT and
the InfoWorks results should be used when viewing the following figures. These results show
that while linking is feasible the philosophy behind the OpenMI, the modelling of large
catchments linking different modelling programs, will in future need to provide better means
for aggregating results from the disparate programs for easier comprehension.
Results are shown below for flow, suspended solids and ammonia transmitted between the
three programs, demonstrating that the values are being passed correctly in time and
magnitude. The STOAT implementation of the OpenMI is compatible with that used by
Wallingford Software in InfoWorks RS and InfoWorks CS.

WRc Ref: UC6920/13124-1


September 2005

30

5.4
5.4.1

Sewer to sewage works (CS to STOAT)


Flow

Figure 5.5

Flow: CS outlet to STOAT

Figure 5.6

Flow: CS inlet to STOAT

Peak 3.9 m3/s (14,040 m3/h) correct. Baseline 2.2 m3/s (7,920 m3/h) correct.

WRc Ref: UC6920/13124-1


September 2005

31

5.4.2

Suspended solids

Figure 5.7

Suspended solids: CS outlet to STOAT

Figure 5.8

Suspended solids: CS inlet to STOAT

Peak 0.027 g/l; missed. STOAT peak of 20 mg/l. Most likely reason is one of timestep
communication. The peak from CS is clearly seen in STOAT, so data transmission is taking
place, but a smaller timestep may be needed to fully capture the peak. The timestep used was
60 seconds.

WRc Ref: UC6920/13124-1


September 2005

32

Figure 5.9

Suspended solids: CS outlet to STOAT, ignoring peak value

Figure 5.10

Suspended solids: CS inlet to STOAT, ignoring peak value

Diurnal peaks around 0.0006, 0.0008 and 0.001 g/l. These are captured by STOAT.

WRc Ref: UC6920/13124-1


September 2005

33

5.4.3

Ammonia

Figure 5.11

Ammonia: CS outlet to STOAT

Figure 5.12

Ammonia: CS inlet to STOAT

Peak of 0.0007 g/l; second peak 0.00036; third and fourth peaks 0.0004 g/l. STOAT peaks are
0.7 mg/l (OK), 0.36 (OK), and 0.4 (OK).

WRc Ref: UC6920/13124-1


September 2005

34

5.5
5.5.1

Sewage works to river (STOAT to RS)


Flow

Figure 5.13

Flow: STOAT outlet to RS

Figure 5.14

Flow: STOAT inlet to RS

Peaks and base flows match.

WRc Ref: UC6920/13124-1


September 2005

35

5.5.2

Suspended solids

Figure 5.15

Suspended solids: STOAT outlet to RS

Figure 5.16

Suspended solids: STOAT inlet to RS

Two peaks around 11 mg/l, picked up by RS, and a baseline around 8 mg/l, again picked up
by RS.

WRc Ref: UC6920/13124-1


September 2005

36

Figure 5.17

Ammonia: STOAT outlet to RS

Figure 5.18

Ammonia: STOAT inlet to RS

Ammonia trough around 0.075 mg/l, and rising to 0.11 mg/l. RS values the same.

WRc Ref: UC6920/13124-1


September 2005

37

5.6

Experience

Once the components had been tested and signed off working with OpenMI configurations
proved remarkably straight-forward. Initially there was a view that the compositions were
fragile, because of the poor error handling in the configuration editor. However, as the
programs were modified to correct these errors the system became remarkably robust.
The main operational problem then became, with large compositions, that of ensuring that the
component models were correctly connected. The current configuration editor displays all
components as equal-sized shapes of the same colour, so that the visual representation of
the composition does not make it easy to identify the models and ensure that they are indeed
connected correctly. This, however, is because of the prototypical nature of the configuration
editor, and future editors should better address the need for visual feedback during
composition.
The compute speed when STOAT was run with the second model also as STOAT appeared
to be little reduced against a stand-alone STOAT simulation. However, when adding the RS
component the compute speed slowed down considerably, because of the much greater data
exchange taking place between the hydraulics and water quality components of RS. This
indicates that there is a large overhead in the data exchange mechanism used by OpenMI but
that this overhead is only noticeable when large quantities of data are exchanged frequently.
When the data is exchanged between a small number of points, for a small number of
quantities, then the compute speed of the original programs still dominates the overall
execution speed.
With the STOAT to STOAT test the first STOAT sewage works model was designed to
accommodate a flow of 300 m3/h, while the second model was designed to accommodate a
flow of 8,000 m3/h. Running with a communication timestep of 1 hour produced instability in
the second model, caused by the mismatch in the expected flows. When the communication
timestep was reduced to 0.1 h the instability was avoided, as adequate interpolation of the
influent was achieved. Therefore, when carrying out OpenMI simulations, there may be some
initial settling down of the combined model where appropriate communication intervals are
investigated.
The criteria for acceptance of the use case were all successfully met.

WRc Ref: UC6920/13124-1


September 2005

38

6 CONCLUSION ON THE MIGRATION

6.1

Feed back to OpenMI

The OpenMI has proved remarkably easy to implement in STOAT. There are two
reasons for this.
1. STOAT having a similar architecture to the model data exchange
interface developed from previous work with datasharing between
STOAT and DHIs MOUSE and MIKE products; and
2. The December 2004 training course on the implementation of OMI,
which converted a large, difficult to follow and navigate set of
documents into a short series of easy-to-follow slides. Anyone
planning to implement the OMI would be strongly recommended to
include time and funding to attend a similar conversion course.
6.1.1

Time

Conversion time was not a problem. A total of 11 days was required for the
conversion, with documentation for this report taking another 3 and testing an
additional 2 days, including bug fixes.
6.1.2

Where problems were found

The main problem was the immaturity of the existing user interface provided for
testing OMI components. Problems encountered included:
Having imported a component into the project it was sometimes necessary to
close the project and reopen before it was fully enabled; on some cases it
seemed that a reboot was also needed. This has since been fixed.
Every GUI action interrogated the list of available input and output items.
When that list is several thousand then the delay after each GUI action can
run into tens of minutes using a 1 GHz Pentium IV. This has since been
fixed.
There was no detection for inconsistencies in imported projects having
manually edited a project, the interface did not detect that it was no longer
consistent with the models, and crashed elsewhere with internal problems.
The documentation, while comprehensive, is difficult to navigate and difficult
to understand. Most the documentation for the migration support was taken
from the c. 8 pages of code listing provided during the migration training
course. The rewrite of the guidelines document has removed many of the
problems associated with that document.

WRc Ref: UC6920/13124-1


September 2005

39

ACKNOWLEDGMENTS

The solution to the time calculation problem, where the OpenMI configuration editor
required that the time should be an integral number of seconds, was developed by
Wiktoria Daniels of Wallingford Software and made available to WRc during the final
round of debugging.
The simulation linking InfoWorks CS, STOAT and InfoWorks RS was also carried out
by Wiktoria Daniels, and the graphs presented in this report were generated by her.

WRc Ref: UC6920/13124-1


September 2005

41

APPENDIX SOURCE CODE LISTINGS

WRc Ref: UC6920/13124-1


September 2005

43

WRc Ref: UC6920/13124-1


September 2005

44

Appendix 1
A1.1

C# Wrapper Code

Engine

using System;
using
using
using
using

org.OpenMI.Standard;
org.OpenMI.Backbone;
org.OpenMI.Utilities.Spatial;
org.OpenMI.Utilities.Wrapper;

namespace WRc.OMI
{
public class STOAT : LinkableEngine
{
protected override void SetEngineApiAccess()
{
_engineApiAccess = new STOATEngine();
}
}
}

A1.2

Main wrapper

#region Using directives


using System;
#endregion
using
using
using
using

org.OpenMI.Standard;
org.OpenMI.Backbone;
org.OpenMI.Utilities.Spatial;
org.OpenMI.Utilities.Wrapper;

namespace WRc.OMI
{
public class STOATEngine : IEngine
{
STOAT4.HarmonIT
LSTOAT;
STOAT4.InputExchangeItem STOAT_In;
STOAT4.OutputExchangeItem STOAT_Out;
STOAT4.IValueSet
STOAT_Val;

WRc Ref: UC6920/13124-1


September 2005

45

#region IEngine Members


InputExchangeItem IEngine.GetInputExchangeItem(int exchangeItemIndex)
{ InputExchangeItem item = new InputExchangeItem();
Dimension dim
= new Dimension();
string sDescription;
string sID;
double fFactor;
double fOffset;
STOAT_In
= LSTOAT.GetInputExchangeItem(exchangeItemIndex);
sDescription
= STOAT_In.Element.get_Description();
sID
= STOAT_In.Element.get_ID();
item.ElementSet = new ElementSet(sDescription, sID, ElementType.IDBased,
new SpatialReference());
sID
sDescription
fFactor
fOffset
Unit unit

=
=
=
=
=

STOAT_In.Quantity.Unit.get_ID();
STOAT_In.Quantity.Unit.get_Description();
STOAT_In.Quantity.Unit.ConversionFactorToSI;
STOAT_In.Quantity.Unit.OffsetToSI;
new Unit(sID, fFactor, fOffset, sDescription);

dim.SetPower(DimensionBase.AmountOfSubstance, STOAT_In.Quantity.Dimension.get_Power(STOAT4.EDimensionType.eAmountOfSubstance));
dim.SetPower(DimensionBase.Length, STOAT_In.Quantity.Dimension.get_Power(STOAT4.EDimensionType.eLength));
dim.SetPower(DimensionBase.Mass, STOAT_In.Quantity.Dimension.get_Power(STOAT4.EDimensionType.eMass));
dim.SetPower(DimensionBase.Time, STOAT_In.Quantity.Dimension.get_Power(STOAT4.EDimensionType.eTime));
dim.SetPower(DimensionBase.ElectricCurrent, STOAT_In.Quantity.Dimension.get_Power(STOAT4.EDimensionType.eElectricCurrent));
dim.SetPower(DimensionBase.Temperature, STOAT_In.Quantity.Dimension.get_Power(STOAT4.EDimensionType.eTemperature));
dim.SetPower(DimensionBase.LuminousIntensity, STOAT_In.Quantity.Dimension.get_Power(STOAT4.EDimensionType.eLuminousIntensity));
sDescription = STOAT_In.Quantity.Description;
sID
= STOAT_In.Quantity.ID;
item.Quantity = new Quantity(unit, sDescription, sID,
org.OpenMI.Standard.ValueType.Scalar,
dim);
return item;
}
int IEngine.GetInputExchangeItemCount()
{int iValue;
iValue = LSTOAT.GetInputExchangeItemCount();
return iValue;
}
string IEngine.GetModelDescription()
{return LSTOAT.GetModelDescription();}
string IEngine.GetModelID()
{return LSTOAT.GetModelID();}

WRc Ref: UC6920/13124-1


September 2005

46

OutputExchangeItem IEngine.GetOutputExchangeItem(int exchangeItemIndex)


{ OutputExchangeItem item = new OutputExchangeItem();
Dimension dim
= new Dimension();
string sDescription;
string sID;
double fFactor;
double fOffset;
STOAT_Out
= LSTOAT.GetOutputExchangeItem(exchangeItemIndex);
sDescription
= STOAT_Out.Element.get_Description();
sID
= STOAT_Out.Element.get_ID();
item.ElementSet = new ElementSet(sDescription, sID, ElementType.IDBased,
new SpatialReference());
sID
sDescription
fFactor
fOffset
Unit unit

=
=
=
=
=

STOAT_Out.Quantity.Unit.get_ID();
STOAT_Out.Quantity.Unit.get_Description();
STOAT_Out.Quantity.Unit.ConversionFactorToSI;
STOAT_Out.Quantity.Unit.OffsetToSI;
new Unit(sID, fFactor, fOffset, sDescription);

dim.SetPower(DimensionBase.AmountOfSubstance, STOAT_Out.Quantity.Dimension.get_Power(STOAT4.EDimensionType.eAmountOfSubstance));
dim.SetPower(DimensionBase.Length, STOAT_Out.Quantity.Dimension.get_Power(STOAT4.EDimensionType.eLength));
dim.SetPower(DimensionBase.Mass, STOAT_Out.Quantity.Dimension.get_Power(STOAT4.EDimensionType.eMass));
dim.SetPower(DimensionBase.Time, STOAT_Out.Quantity.Dimension.get_Power(STOAT4.EDimensionType.eTime));
dim.SetPower(DimensionBase.ElectricCurrent, STOAT_Out.Quantity.Dimension.get_Power(STOAT4.EDimensionType.eElectricCurrent));
dim.SetPower(DimensionBase.Temperature, STOAT_Out.Quantity.Dimension.get_Power(STOAT4.EDimensionType.eTemperature));
dim.SetPower(DimensionBase.LuminousIntensity, STOAT_Out.Quantity.Dimension.get_Power(STOAT4.EDimensionType.eLuminousIntensity));
sDescription = STOAT_Out.Quantity.Description;
sID
= STOAT_Out.Quantity.ID;
item.Quantity = new Quantity(unit, sDescription, sID,
org.OpenMI.Standard.ValueType.Scalar,
dim);
return item;
}
int IEngine.GetOutputExchangeItemCount()
{int iValue;
iValue = LSTOAT.GetOutputExchangeItemCount();
return iValue;
}
ITimeSpan IEngine.GetTimeHorizon()
{ STOAT4.ITimeSpan T = LSTOAT.GetTimeHorizon();
TimeStamp start = new TimeStamp(T.Start);
TimeStamp finish = new TimeStamp(T.Finish);
return new org.OpenMI.Backbone.TimeSpan(start,finish);
}
#endregion

WRc Ref: UC6920/13124-1


September 2005

47

#region IRunEngine Members


void IRunEngine.Dispose()
{LSTOAT.Dispose();}
void IRunEngine.Finish()
{LSTOAT.Finish();}
string IRunEngine.GetComponentDescription()
{return LSTOAT.GetComponentDescription();}
string IRunEngine.GetComponentID()
{string sID = LSTOAT.GetComponentID();
return sID;}
ITime IRunEngine.GetCurrentTime()
{TimeStamp fTime = new TimeStamp(LSTOAT.GetCurrentTime());
return fTime;}
ITimeStamp IRunEngine.GetEarliestNeededTime()
{TimeStamp fTime = new TimeStamp(LSTOAT.GetEarliestNeededTime());
return fTime;}
ITime IRunEngine.GetInputTime(string QuantityID, string ElementSetID)
{double dTime = LSTOAT.GetInputTime();
return new TimeStamp(dTime);
}
double IRunEngine.GetMissingValueDefinition()
{return LSTOAT.GetMissingValue();}
IValueSet IRunEngine.GetValues(string QuantityID, string ElementSetID)
{
STOAT_Val = LSTOAT.GetValues(QuantityID,ElementSetID);
double [] fResult = new double [] {STOAT_Val.get_Values(0)};
return new ScalarSet(fResult);
}
void IRunEngine.Initialize(System.Collections.Hashtable properties)
{string sDatabase;
short iWorksID;
short iRunID;
short iOldRunID;
short iStartCondition;
bool bReuseExistingRun;
string sRunName;
bool bReturn;
double sa, ss, si, xs, xi;

WRc Ref: UC6920/13124-1


September 2005

48

double
double
double
double
double

sol_ss, sol_si, sol_xs, sol_xi;


sol_sns, sol_sni, sol_xns, sol_xni;
sol_sps, sol_spi, sol_xps, sol_xpi;
sol_bod, deg_cod, deg_sol, deg_part;
sol_cod_to_bod, part_cod_to_bod;

sDatabase
iWorksID
iRunID
iOldRunID
iStartCondition
bReuseExistingRun
sRunName
sa
ss
si
xs
xi

=
=
=
=
=

=
=
=
=
=
=
=

(string)
short.Parse((string)
short.Parse((string)
short.Parse((string)
short.Parse((string)
bool.Parse( (string)
(string)

double.Parse((string)
double.Parse((string)
double.Parse((string)
double.Parse((string)
double.Parse((string)

properties["Database"];
properties["WorksID"]);
properties["RunID"]);
properties["OldRunID"]);
properties["StartCondition"]);
properties["ReuseExistingRun"]);
properties["RunName"];

properties["SA"]);
properties["SS"]);
properties["SI"]);
properties["XS"]);
properties["XI"]);

// COD -> SA

// For SetValues: true soluble fraction of SS


sol_ss = double.Parse((string) properties["Sol_SS"]);
sol_si = double.Parse((string) properties["Sol_SI"]);
sol_sns = double.Parse((string) properties["Sol_SNS"]);
sol_sni = double.Parse((string) properties["Sol_SNI"]);
sol_sps = double.Parse((string) properties["Sol_SPS"]);
sol_spi = double.Parse((string) properties["Sol_SPI"]);
// For GetValues: 'nonsettleable' fraction of XS to add to SS
sol_xs = double.Parse((string) properties["Sol_XS"]);
sol_xi = double.Parse((string) properties["Sol_XI"]);
sol_xns = double.Parse((string) properties["Sol_XNS"]);
sol_xni = double.Parse((string) properties["Sol_XNI"]);
sol_xps = double.Parse((string) properties["Sol_XPS"]);
sol_xpi = double.Parse((string) properties["Sol_XPI"]);
sol_bod
deg_cod
deg_sol
deg_part
sol_cod_to_bod
part_cod_to_bod
LSTOAT
STOAT_In
STOAT_Out
STOAT_Val

=
=
=
=

new
new
new
new

=
=
=
=
=
=

double.Parse((string)
double.Parse((string)
double.Parse((string)
double.Parse((string)
double.Parse((string)
double.Parse((string)

properties["sol_BOD"]);
properties["deg_COD"]);
properties["sol_deg_COD"]);
properties["par_deg_COD"]);
properties["sBOD_sCOD"]);
properties["pBOD_pCOD"]);

STOAT4.HarmonITClass();
STOAT4.InputExchangeItemClass();
STOAT4.OutputExchangeItemClass();
STOAT4.IValueSetClass();

bReturn = LSTOAT.Initialize(sDatabase, iWorksID, iRunID, iOldRunID,


iStartCondition, bReuseExistingRun, sRunName);

WRc Ref: UC6920/13124-1


September 2005

49

LSTOAT.SetConstants(ss, si, xs, xi, sa, sol_ss, sol_si, sol_sns, sol_sni, sol_sps, sol_spi,
sol_xs, sol_xi, sol_xns, sol_xni, sol_xps, sol_xpi,
sol_bod, deg_cod, deg_sol, deg_part, sol_cod_to_bod, part_cod_to_bod);
}
bool IRunEngine.PerformTimeStep()
{bool bOK = LSTOAT.PerformTimeStep();
return bOK;}
void IRunEngine.SetValues(string QuantityID, string ElementSetID, IValueSet values)
{double f = ((IScalarSet) values).GetScalar(0);
STOAT_Val.set_Values(0, f);
LSTOAT.SetValues(QuantityID,ElementSetID,STOAT_Val);
}
#endregion
}
}

WRc Ref: UC6920/13124-1


September 2005

50

Appendix 2
A2.1

Visual Basic code

Support types

A2.1.1 CDimension
Option Explicit
Dim mPowers(0 To EDimensionType.NUM_BASE_DIMENSIONS)
Public Property Get Power(Quantity As EDimensionType) As Long
Power = mPowers(Quantity)
End Property
Public Property Let Power(Quantity As EDimensionType, iValue As Long)
mPowers(Quantity) = iValue
End Property
Public Sub SetAll(Optional iLength As Long = 0, Optional iMass As Long = 0, _
Optional iTime As Long = 0, Optional iCurrent As Long = 0, _
Optional iTemperature As Long = 0, Optional iMolarity As Long = 0, _
Optional iLuminosity As Long = 0, Optional iCurrency As Long = 0)
mPowers(eAmountOfSubstance) = iMolarity
mPowers(eCurrency) = iCurrency
mPowers(eElectricCurrent) = iCurrent
mPowers(eLength) = iLength
mPowers(eLuminousIntensity) = iLuminosity
mPowers(eMass) = iMass
mPowers(eTemperature) = iTemperature
mPowers(eTime) = iTime
End Sub

A2.1.2 CElement
Dim sDescription
Dim sID
Dim mSpatialRef

As String
As String
As New CSpatialRef

Public Property Get Description() As String

WRc Ref: UC6920/13124-1


September 2005

51

Description = sDescription
End Property
Public Property Let Description(s As String)
sDescription = s
End Property
Public Property Get ID() As String
ID = sID
End Property
Public Property Let ID(s As String)
sID = s
End Property
Public Property Get SpatialRef() As CSpatialRef
SpatialRef = mSpatialRef
End Property
Public Property Let SpatialRef(x As CSpatialRef)
mSpatialRef = x
End Property

WRc Ref: UC6920/13124-1


September 2005

52

A2.1.3 CEnums
Enum EElementType
IDBased
XYLine
XYPoint
XYPolygon
XYPolyLine
XYZLine
XYZPoint
XYZPolygon
XYZPolyhedron
XYZPolyLine
End Enum
Enum EDimensionType
eLength
eMass
eTime
eElectricCurrent
eTemperature
eAmountOfSubstance
eLuminousIntensity
eCurrency
NUM_BASE_DIMENSIONS
End Enum

A2.1.4 CQuantity
Public Unit
Public Dimension
Private sID
Private sDescription

As
As
As
As

New CUnit
New CDimension
String
String

Public Sub SetDimension(tDim As CDimension)


Dimension.Power(eAmountOfSubstance) = tDim.Power(eAmountOfSubstance)
Dimension.Power(eCurrency) = tDim.Power(eCurrency)
Dimension.Power(eElectricCurrent) = tDim.Power(eElectricCurrent)
Dimension.Power(eLength) = tDim.Power(eLength)
Dimension.Power(eLuminousIntensity) = tDim.Power(eLuminousIntensity)
Dimension.Power(eMass) = tDim.Power(eMass)
Dimension.Power(eTemperature) = tDim.Power(eTemperature)
Dimension.Power(eTime) = tDim.Power(eTime)

WRc Ref: UC6920/13124-1


September 2005

53

End Sub
Public Property Get Description() As String
Description = sDescription
End Property
Public Property Let Description(ByVal vNewValue As String)
sDescription = vNewValue
End Property
Public Property Get ID() As String
ID = sID
End Property
Public Property Let ID(ByVal vNewValue As String)
sID = vNewValue
End Property

A2.1.5 CSpatialRef
Dim mX
Dim mY
Dim mZ

As Double
As Double
As Double

Public Property Let Location(x As CSpatialRef)


mX = x.x
mY = x.y
mZ = x.z
End Property
Public Property Get Location() As CSpatialRef
Location = Me
End Property
Public Property Get x() As Double
x = mX
End Property
Public Property Get y() As Double
y = mY
End Property
Public Property Get z() As Double
z = mZ
End Property
Public Property Let x(vx As Double)

WRc Ref: UC6920/13124-1


September 2005

54

mX = vx
End Property
Public Property Let y(vy As Double)
mY = vy
End Property
Public Property Let z(vz As Double)
mZ = vz
End Property
Private Sub Class_Initialize()
mX = 0: mY = 0: mZ = 0
End Sub
Public Sub SpatialRef(x As Double, y As Double, z As Double)
mX = x: mY = y: mZ = z
End Sub

A2.1.6 CUnit
Dim
Dim
Dim
Dim

msID
mfConversionFactor
mfOffset
msDescription

As
As
As
As

String
Double
Double
String

Public Property Get ConversionFactorToSI() As Double


ConversionFactorToSI = mfConversionFactor
End Property
Public Property Let ConversionFactorToSI(ByVal x As Double)
mfConversionFactor = x
End Property
Public Property Get OffsetToSI() As Double
OffsetToSI = mfOffset
End Property
Public Property Let OffsetToSI(ByVal x As Double)
mfOffset = x
End Property
Public Property Let ID(s As String)
msID = s
End Property
Public Property Get ID() As String

WRc Ref: UC6920/13124-1


September 2005

55

ID = msID
End Property
Public Property Let Description(s As String)
msDescription = s
End Property
Public Property Get Description() As String
Description = msDescription
End Property
Public Sub Unit(sID As String, fConversionFactor As Double, fOffset As Double, sDescription As String)
msID = sID
mfConversionFactor = fConversionFactor
mfOffset = fOffset
msDescription = sDescription
End Sub
Private Sub Class_Initialize()
msID = ""
mfConversionFactor = 1#
mfOffset = 0
msDescription = ""
End Sub

A2.1.7 InputExchangeItem
Public Element
Public Quantity

As New CElement
As New Cquantity

A2.1.8 ITimeSpan
Public Start
As Double
Public Finish As Double
Private Sub Class_Initialize()
Start = 0
Finish = 0
End Sub

A2.1.9 IValueSet
Option Explicit

WRc Ref: UC6920/13124-1


September 2005

56

Dim iCount
As Long
Dim fValues() As Double
Public Property Get Count() As Long
Count = iCount
End Property
Public Property Get Values(ByVal i As Long) As Double
Values = fValues(i)
End Property
Public Property Let Values(ByVal i As Long, ByVal a As Double)
If i > iCount Then
iCount = i
ReDim Preserve fValues(0 To iCount)
End If
fValues(i) = a
End Property
Private Sub Class_Initialize()
ReDim fValues(0 To 0)
iCount = -1
End Sub

A2.1.10

OutputExchangeItem

Public Element
Public Quantity

WRc Ref: UC6920/13124-1


September 2005

As New CElement
As New Cquantity

57

Appendix 3

Option Explicit
Dim sCompletedRuns
Dim sStartedRuns
Public bCancel

OMI Creator

As String
As String
As Boolean

Dim iChosenOption As Long


Dim iRunID
As Long
Dim iOldRunID
As Long
Private Sub chkUseExisting_Click()
If Me.chkUseExisting.Value = 0 Then
Me.cboRuns(1).Enabled = False
Else
Me.cboRuns(1).Enabled = True

WRc Ref: UC6920/13124-1


September 2005

58

End If
End Sub
Private Sub cmbButtons_Click(Index As Integer)
Dim i As Long
Select Case Index
Case 0: bCancel = False
' Basic validation checks
' 1. Any cell red? Or does not contain a valid number?
For i = 0 To 4
If txtCOD(i).BackColor = vbRed Then
MsgBox "All COD fractions must be in the range 0 .. 1. Please correct the entries marked in red."
Exit Sub
ElseIf Not IsNumeric(txtCOD(i).Text) Then
txtCOD(i).Text = 0
End If
Next i
For i = 0 To 5
If txtParticulate(i).BackColor = vbRed Then
MsgBox "All 'Apparent soluble' fractions must be in the range 0 .. 1. Please correct the entries marked in red."
Exit Sub
ElseIf Not IsNumeric(txtParticulate(i).Text) Then
txtParticulate(i).Text = 0
End If
Next i
For i = 0 To 5
If txtSoluble(i).BackColor = vbRed Then
MsgBox "All 'true soluble' fractions must be in the range 0 .. 1. Please correct the entries marked in red."
Exit Sub
ElseIf Not IsNumeric(txtSoluble(i).Text) Then
txtSoluble(i).Text = 0
End If
Next i
For i = 0 To 5
If txtBOD(i).BackColor = vbRed Then
MsgBox "BOD cannot exceed COD, nor can the fractions exceed 1."
Exit Sub
End If
Next i
' 2. Check sums
If CDbl(txtCOD(0).Text)
+ CDbl(txtCOD(2).Text)
+ CDbl(txtCOD(4).Text)
MsgBox "COD fractions
Exit Sub
End If

+ CDbl(txtCOD(1).Text) _
+ CDbl(txtCOD(3).Text) _
<> 1 Then
must sum to 1.0"

' 3. Check for missing entries


If Trim$(Me.txtRunName.Text) = "" Then

WRc Ref: UC6920/13124-1


September 2005

59

MsgBox "No value specified for the run name"


Else
WriteOMIFile
Me.Hide
End If
Case 1: bCancel = True: Me.Hide
Case 2: Call GetHelp(Me.hWnd, CLng(Val(Me.Tag))) 'Help
End Select
End Sub
Private Sub Form_Load()
Dim sn As Recordset
Dim i As Long
Set sn = gdbUser.OpenRecordset("Select * From Run Where WorksID = " & gCurrentWorks.iID, dbOpenSnapshot)
sCompletedRuns = ""
sStartedRuns = ""
cboRuns(0).Clear: cboRuns(1).Clear
If sn.EOF Then
MsgBox "no runs are available on which an OpenMI simulation can be based."
Me.Hide
End If
Do While Not sn.EOF
sStartedRuns = sStartedRuns & vbTab & sn("Name") & vbTab & sn("ID")
cboRuns(0).AddItem sn("Name")
cboRuns(0).ItemData(cboRuns(0).NewIndex) = sn("ID")
If sn![Finished] = -1 Then
sCompletedRuns = sCompletedRuns & vbTab & sn("Name") & vbTab & sn("ID")
End If
sn.MoveNext
Loop
sCompletedRuns = sCompletedRuns & vbTab
sStartedRuns = sStartedRuns & vbTab
cboRuns(0).ListIndex = 0
For i = 0 To Me.cboRuns(0).ListCount - 1
cboRuns(1).AddItem cboRuns(0).List(i)
cboRuns(1).ItemData(i) = cboRuns(0).ItemData(i)
Next i
cboRuns(1).ListIndex = 0
cboRuns(1).Enabled = False
End Sub
Private Sub optCreate_Click(Index As Integer)
Dim iCur As Integer
Dim iTab As Integer
iCur = 1
cboRuns(0).Clear
iChosenOption = Index
Select Case Index
Case 0, 3

WRc Ref: UC6920/13124-1


September 2005

60

iCur = InStr(sStartedRuns, vbTab)


' Tab before name
Do While iCur > 0
iTab = InStr(iCur + 1, sStartedRuns, vbTab)
' Tab between name and ID
cboRuns(0).AddItem Mid$(sStartedRuns, iCur + 1, iTab - iCur - 1)
' Name
iCur = iTab
iTab = InStr(iCur + 1, sStartedRuns, vbTab)
cboRuns(0).ItemData(cboRuns(0).NewIndex) = CLng(Mid$(sStartedRuns, iCur + 1, iTab - iCur - 1))
iCur = iTab + 1
If iCur >= Len(sStartedRuns) Then iCur = 0
Loop
Case 1, 2
iCur = InStr(sCompletedRuns, vbTab)
Do While iCur > 0
iTab = InStr(iCur + 1, sCompletedRuns, vbTab)
cboRuns(0).AddItem Mid$(sCompletedRuns, iCur + 1, iTab - iCur - 1)
iCur = iTab
iTab = InStr(iCur + 1, sCompletedRuns, vbTab)
cboRuns(0).ItemData(cboRuns(0).NewIndex) = CLng(Mid$(sCompletedRuns, iCur + 1, iTab - iCur - 1))
iCur = iTab '+ 1
If iCur >= Len(sCompletedRuns) Then iCur = 0
Loop
End Select
Me.cboRuns(0).ListIndex = 0
End Sub
Private Sub WriteOMIFile()
Dim iFile As Long
Dim sName As String
Dim iList As Long
iFile = FreeFile
sName = UTILS.gsGetFilenameToSave("*.omi", "OMI Files", "*.omi")
If LCase$(Right$(sName, 4)) <> ".omi" Then sName = sName & ".omi"
If sName <> "" Then
iList = Me.cboRuns(1).ListIndex
iRunID = Me.cboRuns(1).ItemData(iList)
iList = Me.cboRuns(0).ListIndex
iOldRunID = Me.cboRuns(0).ItemData(iList)
If iChosenOption = 3 Then
iChosenOption = 0
iRunID = iOldRunID
iOldRunID = -1
End If
Open sName For Output As #iFile
Print #iFile, "<?xml version=""1.0""?>"
Print #iFile, "<LinkableComponent Type=""WRc.OMI.STOAT"" "
Print #iFile, " Assembly = " & App.Path & "\OMI\OMI_STOAT.dll"
Print #iFile, " xmlns = ""http://www.openmi.org/LinkableComponents.xsd"" >"
Print #iFile, " <Arguments>"

WRc Ref: UC6920/13124-1


September 2005

61

PrintArg
PrintArg
PrintArg
PrintArg
PrintArg
PrintArg
PrintArg

iFile,
iFile,
iFile,
iFile,
iFile,
iFile,
iFile,

"Database", gdbUser.Name
"WorksID", CStr(gCurrentWorks.iID)
"RunID", CStr(iRunID)
"OldRunID", CStr(iOldRunID)
"StartCondition", CStr(iChosenOption)
"ReuseExistingRun", CStr(CBool(Me.chkUseExisting.Value <> 0))
"RunName", Me.txtRunName.Text

PrintArg
PrintArg
PrintArg
PrintArg
PrintArg

iFile,
iFile,
iFile,
iFile,
iFile,

"SS",
"SI",
"XS",
"XI",
"SA",

PrintArg
PrintArg
PrintArg
PrintArg
PrintArg
PrintArg

iFile,
iFile,
iFile,
iFile,
iFile,
iFile,

"Sol_SS", Me.txtSoluble(0).Text
"Sol_SI", Me.txtSoluble(1).Text
"Sol_SNS", Me.txtSoluble(2).Text
"Sol_SNI", Me.txtSoluble(3).Text
"Sol_SPS", Me.txtSoluble(4).Text
"Sol_SPI", Me.txtSoluble(5).Text

PrintArg
PrintArg
PrintArg
PrintArg
PrintArg
PrintArg

iFile,
iFile,
iFile,
iFile,
iFile,
iFile,

"Sol_XS", Me.txtParticulate(0).Text
"Sol_XI", Me.txtParticulate(1).Text
"Sol_XNS", Me.txtParticulate(2).Text
"Sol_XNI", Me.txtParticulate(3).Text
"Sol_XPS", Me.txtParticulate(4).Text
"Sol_XPI", Me.txtParticulate(5).Text

PrintArg
PrintArg
PrintArg
PrintArg
PrintArg
PrintArg

iFile,
iFile,
iFile,
iFile,
iFile,
iFile,

"sBOD_sCOD", Me.txtBOD(0).Text
"pBOD_pCOD", Me.txtBOD(1).Text
"deg_COD", Me.txtBOD(2).Text
"sol_BOD", Me.txtBOD(3).Text
"sol_deg_COD", Me.txtBOD(4).Text
"par_deg_COD", Me.txtBOD(5).Text

Print
Print
Print
Close
End If
End Sub

Me.txtCOD(0).Text
Me.txtCOD(1).Text
Me.txtCOD(2).Text
Me.txtCOD(3).Text
Me.txtCOD(4).Text

#iFile, " </Arguments>"


#iFile, "</LinkableComponent>"
#iFile,
#iFile

Private Sub PrintArg(iFile As Long, sKey As String, sValue As String)


Print #iFile, "
<Argument Key = """; sKey; """ Value = """; Trim$(sValue); """ />"
End Sub

WRc Ref: UC6920/13124-1


September 2005

62

Private Sub txtBOD_KeyPress(Index As Integer, KeyAscii As Integer)


If KeyAscii > 32 Then
If Not IsPosNum(txtBOD(Index).Text, Chr$(KeyAscii)) Then
KeyAscii = 0
End If
End If
End Sub
Private Sub txtBOD_LostFocus(Index As Integer)
If txtBOD(Index).Text = "" Then txtBOD(Index).Text = 0
If CDbl(txtBOD(Index)) > 1 Then
txtBOD(Index).BackColor = vbRed
Else
txtBOD(Index).BackColor = vbWhite
End If
End Sub
Private Sub txtCOD_KeyPress(Index As Integer, KeyAscii As Integer)
If KeyAscii > 32 Then
If Not IsPosNum(txtCOD(Index).Text, Chr$(KeyAscii)) Then
KeyAscii = 0
End If
End If
End Sub
Private Sub txtCOD_LostFocus(Index As Integer)
If txtCOD(Index).Text = "" Then txtCOD(Index).Text = 0
If CDbl(txtCOD(Index)) > 1 Then
txtCOD(Index).BackColor = vbRed
Else
txtCOD(Index).BackColor = vbWhite
End If
End Sub
Private Sub txtParticulate_KeyPress(Index As Integer, KeyAscii As Integer)
If KeyAscii > 32 Then
If Not IsPosNum(txtParticulate(Index).Text, Chr$(KeyAscii)) Then
KeyAscii = 0
End If
End If
End Sub
Private Sub txtParticulate_LostFocus(Index As Integer)
If txtParticulate(Index).Text = "" Then txtParticulate(Index).Text = 0
If CDbl(txtParticulate(Index)) > 1 Then
txtParticulate(Index).BackColor = vbRed
Else
txtParticulate(Index).BackColor = vbWhite
End If
End Sub

WRc Ref: UC6920/13124-1


September 2005

63

Private Sub txtSoluble_KeyPress(Index As Integer, KeyAscii As Integer)


If KeyAscii > 32 Then
If Not IsPosNum(txtSoluble(Index).Text, Chr$(KeyAscii)) Then
KeyAscii = 0
End If
End If
End Sub
Private Sub txtSoluble_LostFocus(Index As Integer)
If txtSoluble(Index).Text = "" Then txtSoluble(Index).Text = 0
If CDbl(txtSoluble(Index)) > 1 Then
txtSoluble(Index).BackColor = vbRed
Else
txtSoluble(Index).BackColor = vbWhite
End If
End Sub

WRc Ref: UC6920/13124-1


September 2005

64

Appendix 4

Main code

'
' COM interface for OpenMI system
'
' Defined input values
Const NUMBER_OF_INFLUENT_EXCHANGE_VALUES = 29
' Defined output values
Const NUMBER_OF_STREAM_EXCHANGE_VALUES = 29
Const NUMBER_OF_ASM1_EXCHANGE_VALUES = 9
Const NUMBER_OF_ASM2_EXCHANGE_VALUES = 13
Const NUMBER_OF_ASM3_EXCHANGE_VALUES = 11
Const NUMBER_OF_FST_EXCHANGE_VALUES = 1
Dim Inputs() As RecordData
Dim Outputs() As RecordData
Dim NumberofInputItems As Long
Dim NumberOfOutputItems As Long
Const PROCESS = 1
Const STREAM = 2

WRc Ref: UC6920/13124-1


September 2005

65

' -------------- Start of items defined under IEngine ----------------------Public Function GetTimeHorizon() As ITimeSpan
Dim cReturn As New ITimeSpan
cReturn.Start = ToModifiedJulian(CDbl(gCurrentRun.varStartTime))
cReturn.Finish = ToModifiedJulian(CDbl(gCurrentRun.varEndTime))
Set GetTimeHorizon = cReturn
End Function
Public Function GetInputExchangeItemCount() As Long
Dim i
As Long
Dim iCount As Long
If NumberofInputItems <> -1 Then
GetInputExchangeItemCount = NumberofInputItems
Exit Function
End If
iReturn = 0
For i = 1 To giNumberOfProcesses
If Not WorksProcess(i).bDeleted Then
Select Case WorksProcess(i).iType
Case STW_INFL, STW_IND, STW_LAND
iCount = iCount + NUMBER_OF_INFLUENT_EXCHANGE_VALUES
End Select
End If
Next i
GetInputExchangeItemCount = iCount
ReDim Preserve Inputs(0 To iCount)
NumberofInputItems = iCount
End Function
Public Function GetInputExchangeItem(ByVal ItemIndex As Long) As InputExchangeItem
Dim i
As Long
Dim iCount
As Long
Dim iWanted
As Long
Dim iItem
As Long
Dim sComp
As String
Dim sDescription As String
Dim fMultiplier
As Double
Dim fOffset
As Double
Dim tResult
As InputExchangeItem
Dim tDim
As CDimension
Dim sUnit
As String
iWanted = Int(ItemIndex / NUMBER_OF_INFLUENT_EXCHANGE_VALUES) + 1
iItem = ItemIndex Mod NUMBER_OF_INFLUENT_EXCHANGE_VALUES
For i = 1 To giNumberOfProcesses
If Not WorksProcess(i).bDeleted Then

WRc Ref: UC6920/13124-1


September 2005

66

Select Case WorksProcess(i).iType


Case STW_INFL, STW_IND, STW_LAND
iCount = iCount + 1
If iCount = iWanted Then Exit For
End Select
End If
Next i
fOffset = 0: fMultiplier = 1
Set tDim = New CDimension
tDim.SetAll -3, 1
' Will override where needed
sUnit = "kg/m"
Select Case iItem
Case 0: sComp = "Q":
sDescription = "Flow": tDim.SetAll 3, 0, -1: sUnit = "m/s"
Case 1: sComp = "COD":
sDescription = "Total COD"
Case 2: sComp = "SA":
sDescription = "Volatile fatty acid as COD"
Case 3: sComp = "SS":
sDescription = "Soluble degradable COD"
Case 4: sComp = "SI":
sDescription = "Soluble nondegradable COD"
Case 5: sComp = "XS":
sDescription = "Particulate degradable COD"
Case 6: sComp = "XI":
sDescription = "Particulate nondegradable COD"
Case 7: sComp = "sCOD":
sDescription = "Soluble COD"
Case 8: sComp = "pCOD":
sDescription = "Particulate COD"
Case 9: sComp = "BOD":
sDescription = "BOD5"
Case 10: sComp = "sBOD": sDescription = "Soluble BOD"
Case 11: sComp = "pBOD": sDescription = "Particulate BOD"
Case 12: sComp = "TSS":
sDescription = "Suspended solids"
Case 13: sComp = "VSS":
sDescription = "Volatile suspended solids"
Case 14: sComp = "NVSS": sDescription = "Nonvolatile suspended solids"
Case 15: sComp = "SNH":
sDescription = "Ammonia"
Case 16: sComp = "SNO":
sDescription = "Nitrate"
Case 17: sComp = "SNS":
sDescription = "Soluble degradable organic N"
Case 18: sComp = "SNI":
sDescription = "Soluble nondegradable organic N"
Case 19: sComp = "XNS":
sDescription = "Particulate degradable organic N"
Case 20: sComp = "XNI":
sDescription = "Particulate nondegradable organic N"
Case 21: sComp = "SPO4": sDescription = "Phosphate"
Case 22: sComp = "SPS":
sDescription = "Soluble degradable organic P"
Case 23: sComp = "SPI":
sDescription = "Soluble nondegradable organic P"
Case 24: sComp = "XPS":
sDescription = "Particulate degradable organic P"
Case 25: sComp = "XPI":
sDescription = "Particulate nondegradable organic P"
Case 26: sComp = "SO2":
sDescription = "Dissolved oxygen"
Case 27: sComp = "SALK": sDescription = "Alkalinity": tDim.SetAll -3, 0, 0, 0, 0, 1: sUnit = "M/m"
Case 28: sComp = "T":
sDescription = "Temperature": tDim.SetAll 0, 0, 0, 0, 1: sUnit = "K"
Case 29: sComp = "pH":
sDescription = "pH": tDim.SetAll 0, 0: sUnit = "pH"
End Select
'
' Save the results for local use
'
With Inputs(ItemIndex)
.iStage = 1

WRc Ref: UC6920/13124-1


September 2005

' No stage for influent

67

.iType = PROCESS
.iUnit = i
.sParameter = sComp
.fSIMult = fMultiplier
.fSIOffset = fOffset
.sLabel = WorksProcess(i).sName
End With

' Record location in main array


' Record component

Set tResult = New InputExchangeItem


With tResult
With .Element
.ID = WorksProcess(i).sName
.Description = WorksProcess(i).sName
End With
With .Quantity
.ID = sComp
.Description = sDescription
.SetDimension tDim
With .Unit
.ID = sUnit
.Description = sUnit
.ConversionFactorToSI = fMultiplier
.OffsetToSI = fOffset
End With
End With
End With
Set GetInputExchangeItem = tResult
End Function
Public Function GetOutputExchangeItemCount() As Long
Dim i
As Long
Dim iCount As Long
Dim iStages
If NumberOfOutputItems <> -1 Then
GetOutputExchangeItemCount = NumberOfOutputItems
Exit Function
End If
iCount = 0
For i = 1 To giNumberOfProcesses
If Not WorksProcess(i).bDeleted Then
iStages = WorksProcess(i).iNumberOfStages
Select Case WorksProcess(i).iType
Case STW_ASAL
Select Case WorksProcess(i).iModelID
Case MDL_IAWQ1, MDL_IAWQ1_BENCH, MDL_ASM1A, MDL_ASM1B
iCount = iCount + NUMBER_OF_ASM1_EXCHANGE_VALUES * iStages

WRc Ref: UC6920/13124-1


September 2005

68

Case MDL_IAWQ2D, MDL_IAWQ2W, MDL_ASM2D, MDL_ASM2W


iCount = iCount + NUMBER_OF_ASM2_EXCHANGE_VALUES * iStages
Case MDL_IAWQ3
iCount = iCount + NUMBER_OF_ASM3_EXCHANGE_VALUES * iStages
End Select
Case STW_SSED
iCount = iCount + NUMBER_OF_FST_EXCHANGE_VALUES * iStages
End Select
End If
Next i
For i = 1 To giNumberOfLinks
If Not WorksLink(i).bDeleted Then
iCount = iCount + NUMBER_OF_STREAM_EXCHANGE_VALUES
End If
Next i
GetOutputExchangeItemCount = iCount
ReDim Preserve Outputs(0 To iCount)
NumberOfOutputItems = iCount
End Function
Public Function GetOutputExchangeItem(ByVal ItemIndex As Long) As OutputExchangeItem
Dim i
As Long
Dim iCount
As Long
Dim iWanted
As Long
Dim sName
As String
Dim sComp
As String
Dim sDescription As String
Dim fOffset
As Double
Dim fMultiplier
As Double
Dim tResult
As OutputExchangeItem
Dim iModel
As Long
Dim iStages
As Long
Dim tDim
As CDimension
Dim sUnit
As String
iModel = 0
For i = 1 To giNumberOfProcesses
If Not WorksProcess(i).bDeleted Then
iStages = WorksProcess(i).iNumberOfStages
Select Case WorksProcess(i).iType
Case STW_ASAL
Select Case WorksProcess(i).iModelID
Case MDL_IAWQ1, MDL_IAWQ1_BENCH, MDL_ASM1A, MDL_ASM1B
If iCount + NUMBER_OF_ASM1_EXCHANGE_VALUES * iStages > ItemIndex Then
iModel = 1
iType = ItemIndex - iCount
iStages = 1
Do While iType >= NUMBER_OF_ASM1_EXCHANGE_VALUES

WRc Ref: UC6920/13124-1


September 2005

69

iType = iType - NUMBER_OF_ASM1_EXCHANGE_VALUES


iStages = iStages + 1
Loop
sName = WorksProcess(i).sName & ": Stage " & Trim$(iStages)
Exit For
End If
iCount = iCount + NUMBER_OF_ASM1_EXCHANGE_VALUES * iStages
Case MDL_IAWQ2D, MDL_IAWQ2W, MDL_ASM2D, MDL_ASM2W
If iCount + NUMBER_OF_ASM2_EXCHANGE_VALUES * iStages > ItemIndex Then
iModel = 2
iType = ItemIndex - iCount
iStages = 1
Do While iType >= NUMBER_OF_ASM2_EXCHANGE_VALUES
iType = iType - NUMBER_OF_ASM2_EXCHANGE_VALUES
iStages = iStages + 1
Loop
sName = WorksProcess(i).sName & ": Stage " & Trim$(iStages)
Exit For
End If
iCount = iCount + NUMBER_OF_ASM2_EXCHANGE_VALUES * iStages
Case MDL_IAWQ3
If iCount + NUMBER_OF_ASM3_EXCHANGE_VALUES * iStages > ItemIndex Then
iModel = 3
iType = ItemIndex - iCount
iStages = 1
Do While iType >= NUMBER_OF_ASM3_EXCHANGE_VALUES
iType = iType - NUMBER_OF_ASM3_EXCHANGE_VALUES
iStages = iStages + 1
Loop
sName = WorksProcess(i).sName & ": Stage " & Trim$(iStages)
Exit For
End If
iCount = iCount + NUMBER_OF_ASM3_EXCHANGE_VALUES * iStages
End Select
Case STW_SSED
If iCount + NUMBER_OF_FST_EXCHANGE_VALUES * iStages > ItemIndex Then
iModel = 4
iType = ItemIndex - iCount
iStages = 1
Do While iType >= NUMBER_OF_FST_EXCHANGE_VALUES
iType = iType - NUMBER_OF_FST_EXCHANGE_VALUES
iStages = iStages + 1
Loop
sName = WorksProcess(i).sName & ": Stage " & Trim$(iStages)
Exit For
End If
iCount = iCount + NUMBER_OF_FST_EXCHANGE_VALUES * iStages
End Select
End If
Next i

WRc Ref: UC6920/13124-1


September 2005

70

If iModel = 0 Then
For i = 1 To giNumberOfLinks
If Not WorksLink(i).bDeleted Then
If iCount + NUMBER_OF_STREAM_EXCHANGE_VALUES > ItemIndex Then
iModel = -1
iType = ItemIndex - iCount
sName = WorksLink(i).sName
Exit For
End If
iCount = iCount + NUMBER_OF_STREAM_EXCHANGE_VALUES
End If
Next i
End If
fOffset = 0: fMultiplier = 1
Set tDim = New CDimension
tDim.SetAll -3, 1
' Will over ride where needed
sUnit = "kg/m"
Select Case iModel
Case -1
Select Case iType
Case 0: sComp = "Q":
sDescription = "Flow": tDim.SetAll 3, 0, -1: sUnit = "m/s"
Case 1: sComp = "COD":
sDescription = "Total COD"
Case 2: sComp = "SA":
sDescription = "Volatile fatty acid as COD"
Case 3: sComp = "SS":
sDescription = "Soluble degradable COD"
Case 4: sComp = "SI":
sDescription = "Soluble nondegradable COD"
Case 5: sComp = "XS":
sDescription = "Particulate degradable COD"
Case 6: sComp = "XI":
sDescription = "Particulate nondegradable COD"
Case 7: sComp = "sCOD":
sDescription = "Soluble COD"
Case 8: sComp = "pCOD":
sDescription = "Particulate COD"
Case 9: sComp = "BOD":
sDescription = "BOD5"
Case 10: sComp = "sBOD": sDescription = "Soluble BOD"
Case 11: sComp = "pBOD": sDescription = "Particulate BOD"
Case 12: sComp = "TSS":
sDescription = "Suspended solids"
Case 13: sComp = "VSS":
sDescription = "Volatile suspended solids"
Case 14: sComp = "NVSS": sDescription = "Nonvolatile suspended solids"
Case 15: sComp = "SNH":
sDescription = "Ammonia"
Case 16: sComp = "SNO":
sDescription = "Nitrate"
Case 17: sComp = "SNS":
sDescription = "Soluble degradable organic N"
Case 18: sComp = "SNI":
sDescription = "Soluble nondegradable organic N"
Case 19: sComp = "XNS":
sDescription = "Particulate degradable organic N"
Case 20: sComp = "XNI":
sDescription = "Particulate nondegradable organic N"
Case 21: sComp = "SPO4": sDescription = "Phosphate"
Case 22: sComp = "SPS":
sDescription = "Soluble degradable organic P"
Case 23: sComp = "SPI":
sDescription = "Soluble nondegradable organic P"
Case 24: sComp = "XPS":
sDescription = "Particulate degradable organic P"
Case 25: sComp = "XPI":
sDescription = "Particulate nondegradable organic P"
Case 26: sComp = "SO2":
sDescription = "Dissolved oxygen"
Case 27: sComp = "SALK": sDescription = "Alkalinity": tDim.SetAll -3, 0, 0, 0, 0, 1: sUnit = "M/m"

WRc Ref: UC6920/13124-1


September 2005

71

Case 28: sComp = "T":


Case 29: sComp = "pH":
End Select
Case 1
Select Case iType
Case 0: sComp = "SS":
Case 1: sComp = "SI":
Case 2: sComp = "XS":
Case 3: sComp = "XI":
Case 4: sComp = "SNH":
Case 5: sComp = "SNO":
Case 6: sComp = "SO2":
Case 7: sComp = "XH":
Case 8: sComp = "XA":
End Select
Case 2
Select Case iType
Case 0: sComp = "SA":
Case 1: sComp = "SS":
Case 2: sComp = "SI":
Case 3: sComp = "XS":
Case 4: sComp = "XI":
Case 5: sComp = "TSS":
Case 6: sComp = "SNH":
Case 7: sComp = "SNO":
Case 8: sComp = "SPO4":
Case 9: sComp = "SO2":
Case 10: sComp = "ALK":
Case 11: sComp = "XH":
Case 12: sComp = "XA":
End Select
Case 3
Select Case iType
Case 0: sComp = "SS":
Case 1: sComp = "SI":
Case 2: sComp = "XS":
Case 3: sComp = "XI":
Case 4: sComp = "TSS":
Case 5: sComp = "SNH":
Case 6: sComp = "SNO":
Case 7: sComp = "SO2":
Case 8: sComp = "ALK":
Case 9: sComp = "XH":
Case 10: sComp = "XA":
End Select
Case 4
Select Case iType
Case 0: sComp = "TSS":
End Select
End Select

WRc Ref: UC6920/13124-1


September 2005

sDescription = "Temperature": tDim.SetAll 0, 0, 0, 0, 1: sUnit = "K"


sDescription = "pH": tDim.SetAll 0, 0: sUnit = "pH"

sDescription
sDescription
sDescription
sDescription
sDescription
sDescription
sDescription
sDescription
sDescription

=
=
=
=
=
=
=
=
=

"Soluble degradable COD"


"Soluble nondegradable COD"
"Particulate degradable COD"
"Particulate nondegradable COD"
"Ammonia"
"Nitrate"
"Dissolved oxygen"
"Heterotrophs"
"Autotrophs"

sDescription
sDescription
sDescription
sDescription
sDescription
sDescription
sDescription
sDescription
sDescription
sDescription
sDescription
sDescription
sDescription

=
=
=
=
=
=
=
=
=
=
=
=
=

"Volatile fatty acid as COD"


"Soluble degradable COD"
"Soluble nondegradable COD"
"Particulate degradable COD"
"Particulate nondegradable COD"
"Suspended solids"
"Ammonia"
"Nitrate"
"Phosphate"
"Dissolved oxygen"
"Alkalinity": tDim.SetAll -3, 1
"Heterotrophs"
"Autotrophs"

sDescription
sDescription
sDescription
sDescription
sDescription
sDescription
sDescription
sDescription
sDescription
sDescription
sDescription

=
=
=
=
=
=
=
=
=
=
=

"Soluble degradable COD"


"Soluble nondegradable COD"
"Particulate degradable COD"
"Particulate nondegradable COD"
"Suspended solids"
"Ammonia"
"Nitrate"
"Dissolved oxygen"
"Alkalinity": tDim.SetAll -3, 0, 0, 0, 0, 1
"Heterotrophs"
"Autotrophs"

sDescription = "Suspended solids"

72

'
' Record results for local use
'
With Outputs(ItemIndex)
.iStage = iStages
If iModel > 0 Then
.iType = PROCESS
Else
.iType = STREAM
End If
.iUnit = i
.sParameter = sComp
.sLabel = sName
.fSIMult = fMultiplier
.fSIOffset = fOffset
End With
Set tResult = New OutputExchangeItem
With tResult
With .Element
.ID = sName
.Description = sName
End With
With .Quantity
.SetDimension tDim
.ID = sComp
.Description = sDescription
With .Unit
.ID = sUnit
.Description = sUnit
.ConversionFactorToSI = fMultiplier
.OffsetToSI = fOffset
End With
End With
End With
Set GetOutputExchangeItem = tResult
End Function
Public Function GetModelID() As String
Dim s As String
s = "Works " & gCurrentWorks.iID & ": Run " & gCurrentRun.iID
s = Replace(s, " ", "_")
GetModelID = s
End Function

WRc Ref: UC6920/13124-1


September 2005

73

Public Function GetModelDescription() As String


GetModelDescription = gCurrentWorks.sName & ": " & gCurrentRun.sName
End Function
' ------------------------- Start of items defined under IRunEngine -------------Public Function GetComponentID() As String
GetComponentID = "WRc_STOAT"
End Function
Public Function GetComponentDescription() As String
GetComponentDescription = "Dynamic sewage treatment modelling system"
End Function
Public Function PerformTimeStep() As Boolean
gRunControl RUN_STEP
PerformTimeStep = True
End Function
' set internal value with requested external value
Public Sub SetValues(ByVal QuantityID As String, ByVal ElementSetID As String, _
ByVal Values As IValueSet)
Dim i As Long
For i = 0 To NumberofInputItems - 1
With Inputs(i)
If .sLabel = ElementSetID And .sParameter = QuantityID Then
.fValue = Values.Values(0)
SetInputValue Inputs(i)
Exit For
End If
End With
Next i
End Sub
' Return internal values
Public Function GetValues(ByVal QuantityID As String, ByVal ElementSetID As String) As IValueSet
Dim i As Long
Dim T As IValueSet
Set T = New IValueSet
For i = 0 To NumberOfOutputItems - 1
With Outputs(i)
If .sParameter = QuantityID And .sLabel = ElementSetID Then
GetOutputValue Outputs(i)
T.Values(0) = .fValue
Exit For
End If
End With
Next i
Set GetValues = T
End Function

WRc Ref: UC6920/13124-1


September 2005

74

Public Function GetInputTime() As Double


Static bCalled As Boolean
Dim fNextTime As Double
fNextTime = CLng(3600# * (gCurrentRun.fElapsedTime + gCurrentRun.fInputTimestep)) / 86400#
fNextTime = fNextTime + CDbl(gCurrentRun.varStartTime)
If bCalled Then
GetInputTime = ToModifiedJulian(fNextTime)
Else
bCalled = True
GetInputTime = ToModifiedJulian(CDbl(gCurrentRun.varStartTime))
End If
End Function
Public Function GetCurrentTime() As Double
GetCurrentTime = ToModifiedJulian(ElapsedTime())
End Function
Public Function GetEarliestNeededTime() As Double
GetEarliestNeededTime = ToModifiedJulian(ElapsedTime())
End Function
' iWorksID -- the works ID that shall be used. This will be stored within the OMI file
' iRunID
-- the run ID that shall be used. This may be modified - see below
' iOldRunID -- if a run is to be created based on a previous run
'
A value of -1 means do not use this
' iStartCondition - 0:: Start from beginning
'
1:: Start from end
'
2:: Start from end, preserve operational settings
' bReuse -- TRUE -- if the run ID has been used, overwrite it
'
FALSE
append as the next available run slot
'
Public Function Initialize(ByVal sDatabase As String, _
ByVal iWorksID As Integer, ByVal iRunID As Integer, _
ByVal iOldRunID As Integer, ByVal iStartCondition As Integer, _
ByVal bReuse As Boolean, ByVal sRunName As String) As Boolean
Dim iDummy As Long
Dim oDummy1 As InputExchangeItem
Dim oDummy2 As OutputExchangeItem
If gdbUser Is Nothing Then
QuietChangeDatabase sDatabase
ElseIf gdbUser.Name <> sDatabase Then
QuietChangeDatabase sDatabase
End If
gOpenBatchWorks iWorksID
gbCreateOMIWarmStartRun iRunID, iOldRunID, iStartCondition, bReuse, sRunName
giTVPClass = TVP_L3

WRc Ref: UC6920/13124-1


September 2005

75

gbStandardRun = False
SetRunMode True
Initialize = True
gbStandardRun = False ' Disable end of run message
frmIconMenu.Hide
NumberofInputItems = -1
NumberOfOutputItems = -1
End Function
Public Sub SetConstants(ByVal ss As Double, ByVal si As Double, _
ByVal xs As Double, ByVal xi As Double, _
ByVal sa As Double, _
ByVal sol_ss As Double, ByVal sol_si As Double, _
ByVal sol_sns As Double, ByVal sol_sni As Double, _
ByVal sol_sps As Double, ByVal sol_spi As Double, _
ByVal sol_xs As Double, ByVal sol_xi As Double, _
ByVal sol_xns As Double, ByVal sol_xni As Double, _
ByVal sol_xps As Double, ByVal sol_xpi As Double, _
ByVal SOL_BOD As Double, ByVal deg_cod As Double, _
ByVal deg_sol As Double, ByVal deg_part As Double, _
ByVal sol_cod_to_bod As Double, ByVal part_cod_to_bod As Double)
omi_ss = ss: omi_si = si: omi_xs = xs: omi_xi = xi: omi_sa = sa
omi_sol_ss = sol_ss: omi_sol_si = sol_si: omi_sol_xs = sol_xs: omi_sol_xi = sol_xi
omi_sol_sns = sol_sns: omi_sol_sni = sol_sni: omi_sol_xns = sol_xns: omi_sol_xni = sol_xni
omi_sol_sps = sol_sps: omi_sol_spi = sol_spi: omi_sol_xps = sol_xps: omi_sol_xpi = sol_xpi
omi_sol_bod = SOL_BOD
omi_deg_cod = deg_cod
omi_deg_sol = deg_sol
omi_deg_part = deg_part
omi_sol_cod_to_bod = sol_cod_to_bod
omi_part_cod_to_bod = part_cod_to_bod
End Sub

'
'
'
'
'
'

Fraction of BOD that is soluble


Fraction of COD that is degradable
Fraction of soluble COD that is degradable
Fraction of particulate COD that is degradable
Soluble material: deg. COD to BOD
Particulate material: deg Cod to BOD

Public Function GetMissingValue() As Double


GetMissingValue = -999
End Function
Public Sub Finish()
SIMULATE.gRunControl RUN_STOP, True
SIMULATE.StopSimulation
SIMULATE.TidyUpEndSimulation
gbSaveRun
End Sub

WRc Ref: UC6920/13124-1


September 2005

76

Public Sub Dispose()


gbCloseRun
gbCloseWorks ForceClose:=True
End
End Sub
Private Sub SetInputValue(T As RecordData)
' Must be an influent
' First, convert to SI units
T.fValue = ConvertToSI(T.fValue, T.fSIMult, T.fSIOffset)
' Now convert to desired units
Select Case T.sParameter
Case "Q": T.fValue = T.fValue
Case "T": T.fValue = T.fValue - 273.15
Case "pH"
Case "SALK"
Case Else: T.fValue = T.fValue * 1000#
End Select
Select Case T.iType
Case PROCESS ' Influent
INFLUENT.gSetOMIStreamData T.iUnit, T.sParameter, T.fValue
End Select
End Sub
Private Sub GetOutputValue(T As RecordData)
Select Case T.iType
Case STREAM
GetStreamOutputValue T
Case PROCESS
GetProcessOutputValue T
End Select
End Sub
Private Sub GetStreamOutputValue(T As RecordData)
Dim f As Double
With WorksLink(T.iUnit).Initial(1)
Select Case T.sParameter
Case "Q":
f = .fFlow
Case "COD":
f = .fComponent(SOL_BOD) + .fComponent(SOL_INERT_COD) _
+ .fComponent(PART_BOD) + .fComponent(PART_INERT_COD)
Case "SA":
f = .fComponent(VFA)
Case "SS":
f = .fComponent(SOL_BOD) + omi_sol_xs * .fComponent(PART_BOD)
Case "SI":
f = .fComponent(SOL_INERT_COD) + omi_sol_xi * .fComponent(PART_INERT_COD)
Case "XS":
f = .fComponent(PART_BOD) * (1# - omi_sol_xs)
Case "XI":
f = .fComponent(PART_INERT_COD) * (1# - omi_sol_xi)
Case "sCOD": f = .fComponent(SOL_BOD) + .fComponent(SOL_INERT_COD) _
+ (.fComponent(PART_BOD) + .fComponent(PART_INERT_COD)) * omi_sol_cod
Case "pCOD": f = .fComponent(PART_BOD) + .fComponent(PART_INERT_COD) * (1 - omi_sol_cod)

WRc Ref: UC6920/13124-1


September 2005

77

Case "BOD":
Case "sBOD":
Case "pBOD":
Case "TSS":
Case "VSS":
Case "NVSS":
Case "SNH":
Case "SNO":
Case "SNS":
Case "SNI":
Case "XNS":
Case "XNI":
Case "SPO4":
Case "SPS":
Case "SPI":
Case "XPS":
Case "XPI":
Case "SO2":
Case "SALK":
Case "T":
Case "pH":
End Select
End With

f = .fComponent(SOL_BOD) * sol_cod_to_bod _
+ .fComponent(PART_BOD) * part_cod_to_bod
f = .fComponent(SOL_BOD) * sol_cod_to_bod _
+ .fComponent(PART_BOD) * part_cod_to_bod * omi_sol_cod
f = .fComponent(PART_BOD) * part_cod_to_bod * (1 - omi_sol_cod)
f = .fComponent(VOL_S) + .fComponent(NONVOL_S)
f = .fComponent(VOL_S)
f = .fComponent(NONVOL_S)
f = .fComponent(SOL_NH3)
f = .fComponent(SOL_NO3)
f = .fComponent(SOL_ORGN) + omi_sol_xns * .fComponent(PART_ORGN)
f = .fComponent(sndon) + omi_sol_xni * .fComponent(xndon)
f = .fComponent(PART_ORGN) * (1# - omi_sol_xns)
f = .fComponent(xndon) * (1# - omi_sol_xni)
f = .fComponent(SOL_PO4)
f = .fComponent(sorgpo4) + omi_sol_xps * .fComponent(xpo4)
f = .fComponent(sorgndpo4) + omi_sol_xpi * .fComponent(xndpo4)
f = .fComponent(xpo4) * (1# - omi_sol_xps)
f = .fComponent(xndpo4) * (1# - omi_sol_xpi)
f = .fComponent(SOL_O2)
f = .fComponent(Alk)
f = .fTemperature
f = .fpH

' Now convert to SI


Select Case T.sParameter
Case "Q": f = f / 3600#
Case "T": f = f + 273.15
Case "pH"
Case "SALK"
Case Else: f = f / 1000#
End Select
T.fValue = f
End Sub

' m3/h -> m3/s


' Celsius -> Kelvin
' mmol/l -> mol/m3
' mg/l (g/m3) -> kg/m3

Private Sub GetProcessOutputValue(T As RecordData)


Dim iDeterminand As Integer
Dim iWorksID
As Integer
Dim iProcessID
As Integer
Dim iProcessType As Integer
Dim f
As Double
With WorksProcess(T.iUnit)
iWorksID = .iID
iProcessID = .iModelID
iProcessType = .iType
End With

WRc Ref: UC6920/13124-1


September 2005

78

Select Case T.iType


Case 1
Select Case T.iType
Case "SS": iDeterminand = 101
Case "SI": iDeterminand = 102
Case "XS": iDeterminand = 103
Case "XI": iDeterminand = 104
Case "SNH": iDeterminand = 105
Case "SNO": iDeterminand = 106
Case "SO2": iDeterminand = 107
Case "XH": iDeterminand = 108
Case "XA": iDeterminand = 109
End Select
Case 2
Select Case T.iType
Case "SA":
iDeterminand = 101
Case "SS":
iDeterminand = 102
Case "SI":
iDeterminand = 103
Case "XS":
iDeterminand = 104
Case "XI":
iDeterminand = 105
Case "TSS": iDeterminand = 106
Case "SNH": iDeterminand = 107
Case "SNO": iDeterminand = 108
Case "SPO4": iDeterminand = 109
Case "SO2": iDeterminand = 110
Case "ALK": iDeterminand = 111
Case "XH":
iDeterminand = 112
Case "XA":
iDeterminand = 113
End Select
Case 3
Select Case T.iType
Case "SS": iDeterminand = 101
Case "SI": iDeterminand = 102
Case "XS": iDeterminand = 103
Case "XI": iDeterminand = 104
Case "TSS": iDeterminand = 105
Case "SNH": iDeterminand = 106
Case "SNO": iDeterminand = 107
Case "SO2": iDeterminand = 108
Case "ALK": iDeterminand = 109
Case "XH": iDeterminand = 110
Case "XA": iDeterminand = 111
End Select
Case 4
Select Case T.iType
Case "TSS": iDeterminand = 101
End Select
End Select
f = Sensitivity.GetProcessParam(iProcessType, iWorksID, iProcessID, T.iStage, iDeterminand)

WRc Ref: UC6920/13124-1


September 2005

79

' Now convert to SI


Select Case T.sParameter
Case "Q": f = f / 3600#
Case "T": f = f + 273.15
Case "pH"
Case "SALK"
Case Else: f = f / 1000#
End Select
T.fValue = f
End Sub

' m3/h -> m3/s


' Celsius -> Kelvin
' mmol/l -> mol/m3
' mg/l (g/m3) -> kg/m3

Private Function ConvertToSI(x As Single, m As Double, c As Double)


ConvertToSI = m * x + c
End Function
Private Function ToModifiedJulian(ByVal x As Double) As Double
Const BaseDate = -15018 ' Cdbl(Cdate("17/11/1858"))
ToModifiedJulian = x - BaseDate
End Function
Private Function ElapsedTime() As Double
Dim fTemp As Double
fTemp = CLng(3600# * gCurrentRun.fElapsedTime) / 86400#
ElapsedTime = fTemp + CDbl(gCurrentRun.varStartTime)
End Function

WRc Ref: UC6920/13124-1


September 2005

80

Appendix 5

Global characterisation parameters

Option Explicit
Public
Public
Public
Public
Public

omi_sa
omi_ss
omi_si
omi_xs
omi_xi

As
As
As
As
As

Public
Public
Public
Public
Public
Public

omi_sol_ss
omi_sol_si
omi_sol_sns
omi_sol_sni
omi_sol_sps
omi_sol_spi

As
As
As
As
As
As

Double
Double
Double
Double
Double
Double

Public
Public
Public
Public
Public
Public

omi_sol_xs
omi_sol_xi
omi_sol_xns
omi_sol_xni
omi_sol_xps
omi_sol_xpi

As
As
As
As
As
As

Double
Double
Double
Double
Double
Double

Public
Public
Public
Public
Public
Public

omi_sol_bod
omi_deg_cod
omi_deg_sol
omi_deg_part
omi_sol_cod_to_bod
omi_part_cod_to_bod

WRc Ref: UC6920/13124-1


September 2005

Double
Double
Double
Double
Double

As
As
As
As
As
As

Double
Double
Double
Double
Double
Double

'
'
'
'
'
'

Fraction of BOD that is soluble


Fraction of COD that is degradable
Fraction of soluble COD that is degradable
Fraction of particulate COD that is degradable
Soluble material: deg. COD to BOD
Particulate material: deg. COD to BOD

81

WRc Ref: UC6920/13124-1


September 2005

82

Appendix 6

Main input value handling

Public Sub gSetOMIStreamData(iStream As Integer, sComp As String, ByVal fVal As Single)


Dim i
As Integer
Dim j
As Integer
Static bCalled
As Boolean
' Defuats to FALSE
Static dLastTime As Double
If Not bCalled Then
bCalled = True
dLastTime = -1
End If

' Simulation time aslways start at zero

Const FRACT_BCOD = 0.9


Const FRACT_SOL_BOD = 0.4
Const FRACT_VSS = 0.75
If dLastTime < gCurrentRun.fElapsedTime Then
dLastTime = gCurrentRun.fElapsedTime
For i = 1 To giNumberOfInfluents
mInfluentRun(i).Reset(1) = True
' XS
mInfluentRun(i).Reset(2) = True
' XI
mInfluentRun(i).Reset(3) = True
' XNS
mInfluentRun(i).Reset(4) = True
' XNI
mInfluentRun(i).Reset(5) = True
' XPS
mInfluentRun(i).Reset(6) = True
' XPI
Next i
End If
'
' Set a determinand for a TVP stream
'
For j = 1 To giNumberOfInfluents
If mInfluentRun(j).iOutletLinkID = WorksLink(iStream).iID Then
If mInfluentRun(j).iChannel > 0 Then Close #mInfluentRun(j).iChannel
mInfluentRun(j).iChannel = TVP_DEFINED
Exit For
End If
Next j
For i = 1 To 3 Step 2
With mInfluentRun(j).STREAM(i)
Select Case sComp
Case "Q":
.fFlow = fVal * 3600#
Case "COD":
.fComponent(SOL_BOD) = fVal * omi_ss
.fComponent(SOL_INERT_COD) = fVal * omi_si
.fComponent(PART_BOD) = fVal * omi_xs
.fComponent(PART_INERT_COD) = fVal * omi_xi

WRc Ref: UC6920/13124-1


September 2005

83

Case "SA":
Case "SS":

Case "SI":

.fComponent(VFA) = fVal * omi_sa


.fComponent(VFA) = fVal
.fComponent(SOL_BOD) = fVal * omi_sol_ss
If mInfluentRun(j).Reset(1) Then
.fComponent(PART_BOD) = fVal * (1 - omi_sol_ss)
Else
.fComponent(PART_BOD) = .fComponent(PART_BOD) + fVal * (1 - omi_sol_ss)
End If
mInfluentRun(j).Reset(1) = False
.fComponent(SOL_INERT_COD) = fVal * omi_sol_si
If mInfluentRun(j).Reset(2) Then
.fComponent(PART_INERT_COD) = fVal * (1 - omi_sol_si)
Else
.fComponent(PART_INERT_COD) = .fComponent(PART_INERT_COD) + fVal * (1 - omi_sol_si)
End If
mInfluentRun(j).Reset(2) = False

Case "XS":
If mInfluentRun(j).Reset(1) Then
.fComponent(PART_BOD) = fVal
Else
.fComponent(PART_BOD) = .fComponent(PART_BOD) + fVal
End If
mInfluentRun(j).Reset(1) = False
Case "XI":

Case "sCOD":
Case "pCOD":
Case "BOD":
Case "sBOD":
Case "pBOD":
Case "TSS":
Case
Case
Case
Case
Case

"VSS":
"NVSS":
"SNH":
"SNO":
"SNS":

WRc Ref: UC6920/13124-1


September 2005

If mInfluentRun(j).Reset(2) Then
.fComponent(PART_INERT_COD) = fVal
Else
.fComponent(PART_INERT_COD) = .fComponent(PART_INERT_COD) + fVal
End If
mInfluentRun(j).Reset(2) = False
gSetOMIStreamData iStream, "SS", fVal * omi_deg_sol
gSetOMIStreamData iStream, "SI", fVal * (1 - omi_deg_sol)
gSetOMIStreamData iStream, "XS", fVal * omi_deg_part
gSetOMIStreamData iStream, "XI", fVal * (1 - omi_deg_part)
gSetOMIStreamData iStream, "sBOD", fVal * omi_sol_bod
gSetOMIStreamData iStream, "pBOD", fVal * (1 - omi_sol_bod)
fVal = fVal / omi_deg_cod / omi_sol_cod_to_bod
gSetOMIStreamData iStream, "sCOD", fVal
fVal = fVal / omi_deg_cod / omi_part_cod_to_bod
gSetOMIStreamData iStream, "pCOD", fVal
.fComponent(VOL_S) = fVal * FRACT_VSS
.fComponent(NONVOL_S) = fVal * (1 - FRACT_VSS)
.fComponent(VOL_S) = fVal
.fComponent(NONVOL_S) = fVal
.fComponent(SOL_NH3) = fVal
.fComponent(SOL_NO3) = fVal
.fComponent(SOL_ORGN) = fVal
If mInfluentRun(j).Reset(3) Then
.fComponent(PART_ORGN) = fVal * (1 - omi_sol_sns)
Else

84

Case "SNI":

.fComponent(PART_ORGN) = .fComponent(PART_ORGN) + fVal * (1 - omi_sol_sns)


End If
mInfluentRun(j).Reset(3) = False
.fComponent(sndon) = fVal
If mInfluentRun(j).Reset(4) Then
.fComponent(xndon) = fVal * (1 - omi_sol_sni)
Else
.fComponent(xndon) = .fComponent(xndon) + fVal * (1 - omi_sol_sni)
End If
mInfluentRun(j).Reset(4) = False

Case "XNS":
If mInfluentRun(j).Reset(3) Then
.fComponent(PART_ORGN) = fVal
Else
.fComponent(PART_ORGN) = .fComponent(PART_ORGN) + fVal
End If
mInfluentRun(j).Reset(3) = False
Case "XNI":

Case "SPO4":
Case "SPS":

Case "SPI":

If mInfluentRun(j).Reset(4) Then
.fComponent(xndon) = fVal
Else
.fComponent(xndon) = .fComponent(xndon) + fVal
End If
mInfluentRun(j).Reset(4) = False
.fComponent(SOL_PO4) = fVal
.fComponent(sorgpo4) = fVal
If mInfluentRun(j).Reset(5) Then
.fComponent(xpo4) = fVal * (1 - omi_sol_sps)
Else
.fComponent(xpo4) = .fComponent(xpo4) + fVal * (1 - omi_sol_sps)
End If
mInfluentRun(j).Reset(5) = False
.fComponent(sorgndpo4) = fVal
If mInfluentRun(j).Reset(6) Then
.fComponent(xndpo4) = fVal * (1 - omi_sol_spi)
Else
.fComponent(xndpo4) = .fComponent(xndpo4) + fVal * (1 - omi_sol_spi)
End If
mInfluentRun(j).Reset(6) = False

Case "XPS":
If mInfluentRun(j).Reset(5) Then
.fComponent(xpo4) = fVal
Else
.fComponent(xpo4) = .fComponent(xpo4) + fVal
End If
mInfluentRun(j).Reset(5) = False
Case "XPI":
If mInfluentRun(j).Reset(6) Then
.fComponent(xndpo4) = fVal
Else

WRc Ref: UC6920/13124-1


September 2005

85

Case "SO2":
Case "SALK":
Case "T":
Case "pH":
End Select
End With
Next i
End Sub

WRc Ref: UC6920/13124-1


September 2005

.fComponent(xndpo4) = .fComponent(xndpo4) + fVal


End If
mInfluentRun(j).Reset(6) = False
.fComponent(SOL_O2) = fVal
.fComponent(Alk) = fVal
.fTemperature = fVal
.fpH = fVal

86

Appendix 7
Paper submitted to ModSim05
Conference on model usage and integration
Applying the Open Modelling Interface (OpenMI)
Dudley, J1, W Daniels2, PJA Gijsbers3, D Fortune2, S Westen2, and JB Gregersen4
1

WRc plc, UK

2 Wallingford Software Ltd, UK


3 WL/Delft Hydraulics, The Netherlands, peter.gijsbers@wldelft.nl
4 DHI Water and Environment, Denmark
Keywords: OpenMI, model integration, data exchange, interface specification

WRc Ref: UC6920/13124-1


September 2005

87

Extended abstract
Catchment-level modelling has been regarded,
since at least the middle 1980s, as a necessary part
of providing integrated solutions for improving
water quality in receiving waters. However, the
tools that have been available to support this level
of modelling have not made such modelling
convenient. Typically there has been no support
for data exchange between different vendors, and
different classes of model (e.g., rivers, sewers and
sewage works) have used different water quality
parameters in their assessment. The purpose of the
OpenMI has been to provide a first level of greater
ease of data exchange between different programs,
by removing the barrier of exchanging data
between different programs. The OpenMI has also
provided mechanisms to simplify exchanging
quantities between different programs for
example, should one program expect flow in US
units, and another in cumecs, simple
transformations can be defined to ensure that each
program can convert the data from the other to the
required units. A relatively large-scale application
of the OpenMI in a model catchment is described
here, to better illustrate some of the benefits and
modelling issues that are introduced by use of the
OpenMI.
It is concluded that the OpenMI has facilitated
linking many disparate water-cycle programs. The
focus of OpenMI (i.e. data exchange at the engine
/component level) and the current state of adoption
by the software community require the usage of a
straightforward editor to define the linkage outside
the user interface environments of the various
systems. This focus has kept the conceptual
overhead low, as each program in the catchment
model is manipulated individually, so that the
domain experts do not need to learn a new
interface. In a similar way, the results of the
intergrated computation needs to be inspected
through the individual user interfaces of the
various models incorporated.
In addition it is concluded that the OpenMI
provides a feasible for solution for the ITcommunication problem. However, it does not
solve all problems. As each program is required to
exchange data with a program from a different
model developer there has been the need to
communicate to ensure that the semantics are
clear, that the connection points are correct, and
that the data being transferred is mapped correctly
e.g., agreeing on transformation protocols
between COD from the sewage models to BOD in
the lake model. This communication has been a
WRc Ref: UC6920/13124-1
September 2005

88

source of difficulty with previous efforts at


integrated catchment modelling, and while
OpenMI can simplify the technical aspects it does
not address these human issues.
Finally, the analysis of simulation results requires
further communication between the domain
experts. The OpenMI thus allows problems to be
tackled in a more integrated manner, but does not
remove the constraints of ensuring communication
between the different technical work areas in
understanding the total output. As well as needing
the traditional local areas of expertise for
example, river, sewerage and sewage treatment
works modellers there has also been the greater
need for a new area of expertise, providing the
catchment-level understanding of the whole
problem. The OpenMI, by providing a framework
for these different areas to better integrate the
modelling work, has concomitantly provided a
social framework to encourage the individual
teams to work together to provide better modelling
data for the decision makers.

which apparently both use COD as their basis.)


0.

INTRODUCTION
This paper illustrates two points:

The European Union (EU) has introduced the


Water Framework Directive (WFD) as a
legislative driver to encourage catchment-wide
decisions on water quality, covering not only
organisational (e.g. water and sewerage
providers) and local boundaries (e.g. different
towns along a river) but also trans-national
boundaries.

How OpenMI can make integrated


catchment modelling easier, as regards
the mechanics of connecting different
programs together and exchanging data
between these programs; and
Bring out that the technology is not
sufficient by itself; the different skills
available through different modelling
areas (e.g., sewers, rivers) must
cooperate in ensuring that the different
programs connect in a manner that will
solve the problem.

One expectation of the WFD is that there will be


greater use of modelling tools to help provide the
integration of data sources and environmental
effects across these large catchments. Historically
the models have developed as stand-alone tools,
making data exchange, let alone integration, at
best difficult. The EU therefore funded a project
to develop a new interface mechanism, the Open
Modelling Interface (OpenMI) to provide a means
for software to be developed in such a manner as
to support data exchange and communication.
OpenMI-enabled software tools will allow
existing models of parts of a catchment to be
integrated into a larger, more complete, model of
the whole catchment. The technical details of
OpenMI are presented in Gijsbers &
Gregersen,(2005).The OpenMI also provides
mechanisms to address additional problems with
such integration, such as spatial equivalency
(when one model uses, say, a one-dimensional
representation while another uses, perhaps, a twodimensional version), datum levels (while the
water levels at model boundaries should match,
the apparent water levels will be affected by the
choice of the base level) and water quality
parameters (some models use BOD, others COD,
and the fractionation of COD, for example, may
not be defined in the same way between models

Table 4

There is a further level of involvement that has


not been addressed, but which is also important,
namely that of the decision makers in making use
of the output from these programs in reaching
environmental solutions.
1.

A SIMPLE CATCHMENT MODEL

This paper introduces a simple catchment model


constructed through integrating sub-models from
different software providers. While the catchment
is artificial, constructed to demonstrate the
applicability of the OpenMI, it is loosely based on
our collective experience of catchments, and of
issues concerning model integration that we wish
to address within this paper.
2.

MODELS AND CONNECTIONS

The integrated model is represented in Figure 1,


showing a river system with runoff, connected

Programs used in the catchment model

Link

From

To

A
B
C
D
E
F
G
H
I
J
K
L

SOBEK-RR (Rainfall / Runoff)


SOBEK-SF (Sewer Flow)
HYMOS Database (Rainfall)
SOBEK-CF (Channel Flow)
Infoworks CS (Sewer Flow)
STOAT (Sewage treatment works)
Infoworks CS (Sewer Flow)
Infoworks RS (Channel Flow)
Infoworks CS (Sewer Flow)
STOAT (Sewage treatment works)
SOBEK-RR (Rainfall / Runoff)
SULIS (Lake)

SOBEK-CF (Channel Flow)


SOBEK-CF (Channel Flow)
SOBEK-RR (Rainfall / Runoff)
Infoworks RS (Channel Flow)
STOAT (Sewage treatment works)
Infoworks RS (Channel Flow)
Infoworks RS (Channel Flow)
SULIS (Lake)
STOAT (Sewage treatment works)
SULIS (Lake)
SULIS (Lake)
Infoworks RS (Channel Flow)

WRc Ref: UC6920/13124-1


September 2005

89

WQ

sewer systems and sewage works, feeding a lake,


with the lake discharging at one end. Various
water-cycle models are used to represent the
complete catchment, and the details of the models
and their connection points are given in Table 1.

of ensuring that consistent information is passed


from one program to the next. The lake model,
SULIS, uses BOD; the sewage works, sewer and
river models are set to use COD. The sewer and
sewage works do not use the same fractionating
approach for particulate COD, as the sewer model
is concerned with the distinction between COD
that can settle in the sewer, and COD that, while
particulate, will be transported as if soluble. The
sewage works models, on the other hand, are
concerned with the difference between COD that
is immediately taken up by the bacteria, regarded
as soluble, and COD that needs to be broken
down into soluble COD before it can be utilised
by the bacteria, regarded as particulate.

Many of the programs have not been coupled in


the past, and do not have existing interfaces that
would allow them to work together. The previous
solution has been to edit each programs output to
make it compatible with the downstream models.
Such sequential oriented method of file linkage
would not accommodate any feedback processes
that are required to simulate backwater effects
properly without the need to move to small time
steps. In addition to the issues of file formats,
there is, for water quality, the additional problem

OMI Hills
HYMOS Database
Rainfall

C
OMI Valley
SOBEK-RR
Rainfall / Runoff

A
Wallingford
SOBEK-SF
Sewer Flow

B
OMI River
SOBEK-CF
Channel Flow

D
OMI River
Infoworks RS
Channel Flow

F
Delft
Infoworks CS
Sewer Flow

STOAT
Wastewater
treatment works

overflow

I
STOAT
Wastewater treatment
works

Delft Polder
SOBEK-RR
Rainfall / Runoff

Lake Harmoni
SULIS

L
Infoworks RS
Channel Flow

Figure 6.1

Schematic Integrated Model

WRc Ref: UC6920/13124-1


September 2005

90

Swindon
Infoworks CS
Sewer Flow

Previous attempts at creating large integrated models, such as the previous DHI/WRc collaborative project TVP
(Taylor et al., 1999), have created GIS-based interfaces that have attempted to simplify visual representation, but
not the mechanics of connecting the programs and running them. OpenMI focuses on the linkage issue rather than
such software areas as user interfaces. On the contrary, OpenMI does not mandate a user interface and allows
users and their suppliers to develop user interfaces for OpenMI or to embed OpenMI functionality in their own
products. However, to enable working with OpenMI there is a simple linkage editor provided as part of the C#
.Net implementation. This implementation also provides utilities for model wrapping. It is available as open
source on sourceforge.net/projects/openmi/ or through the OpenMI website (www.openmi.org). An example of
the linkage editor is shown in Figure 2. After the programs have been connected the quantities being exchanged
are then defined, as illustrated in Figure 3. In the previous TVP programme a GIS-based user interface was
available, but this made it more difficult to distinguish between the spatial layout of the models and the
boundaries of the programs. The simple approach currently used in the OpenMI demonstrator has been found to
enhance understanding of the inter-relationships between the different programs and the quantities being
exchanged between these programs A GIS-enabled interface that allows switching between the spatial and
software views may help in ensuring that connections are made at the right points, rather than relying upon
descriptions (e.g., Swindon overflow from the sewer model should connect to CSO spill point on the river
model.) However, OpenMI also supports mapping of spatial coordinates, so that future iterations of the program
may automatically identify the connections through their geographical coordinates, and detect if the connections
are not correctly spatially aligned.

Figure 6.2

Linking the programs

WRc Ref: UC6920/13124-1


September 2005

91

Figure 6.3
3.

Setting up data exchange


CONNECTING PROGRAMS

Table 2 provides an overview of the way OpenMI allows model connections to be defined to address the problems
described above. The default wrapper implementation for OpenMI accommodates the specification of data
operations as presented. The bottom pane of Figure 3 shows the feedback available in the current interface to
guide users in ensuring that they are transferring data at the correct location, and mapping quantities correctly.
While OpenMI supports transformations, so that quantities can be manipulated at the OpenMI level, this is not as
readily available where the mapping is not a simple one-to-one relationship (as, for example, different units for
flow), but rather a many-to-one relationship (for example, suspended solids in the target program being the sum of
several solids fractions in the source.) This requires that the programs provide support for such mappings, and can
be done either at the OpenMI level, or, at a further level within the target program.

WRc Ref: UC6920/13124-1


September 2005

92

Table 5 Overview of modelled variables and data operations to match output and
input
Link

Output Quantity

Input Quanity

Data operation

A
B
C
D

Runoff (m3/s)
Runoff (m3/s)
Rainfall (mm/s)
Discharge (m3/s)
Water level (m above OL)
Flow
COD
Suspended solids
.Ammoniacal nitrogen
Flow
COD
Suspended solids
Ammoniacal nitrogen
Temperature
Stage (m above HL)
Flow
COD
Suspended solids
Ammoniacal nitrogen
Temperature
Stage (m above HL)
Flow
COD
Suspended solids
Ammoniacal nitrogen
Temperature
Flow
COD Suspended solids
Ammoniacal nitrogen

Lateral inflow (m3/s)


Lateral inflow (m3/s)
Rainfall (mm/h)
Flow (m3/s)
Stage (m above HL)
Flow
COD
Suspended solids
Ammoniacal nitrogen
Flow
COD
Suspended solids
Ammoniacal nitrogen
Temperature
Stage (m above HL)
Flow
COD
Suspended solids
Ammoniacal nitrogen
Temperature
Stage (m above HL)
Flow
BOD
Suspended solids
Ammoniacal nitrogen
Temperature
Flow
COD
Suspended solids
Ammoniacal nitrogen
Flow
BOD
Suspended solids
Ammoniacal nitrogen
Temperature
Inflow (m3/s)
Stage (m above HL)
Flow
COD
Suspended solids
Ammoniacal nitrogen
Temperature

Unit conversion
datum offset: -0.03 m
-

K
L

Flow
COD
Suspended solids
Ammoniacal nitrogen
Temperature
Runoff (m3/s)
Stage (m above HL)
Flow
BOD
Suspended solids
Ammoniacal nitrogen
Temperature

WRc Ref: UC6920/13124-1


September 2005

93

linear conv. 0.75


-

linear conv. 0.75


-

4.

DISCUSSION AND CONCLUSIONS

The OpenMI has facilitated linking many disparate water-cycle programs. Avoiding regarding the purpose of the
OpenMI as providing an integrated software suite has kept the conceptual overhead low, as each program in the
catchment model is manipulated individually, so that the domain experts do not need to learn a new interface. (Of
course, the chosen implementation may include such a new over-arching interface linking the different models
but this is not a requirement of OpenMI, and is instead a policy decision to be made by users or vendors.)
The integration of the different programs into the larger catchment model has required each contributor to
describe where the data file containing the submodel connection data is stored on the computer system, and what
input and output connections and quantities are involved. As each program is required to exchange data with a
program from a different domain expert there has been the need to communicate to ensure that the connection
points are correct, and that the data being transferred is mapped correctly e.g., agreeing on transformation
protocols between COD from the sewage models to BOD in the lake model. This communication has been a
source of difficulty with previous efforts at integrated catchment modelling, and while OpenMI can simplify the
technical aspects it does not address these human issues.
After the catchment model has been run the results from each program need to be analysed in the originating
program. The currently available OpenMI implementation provides mechanism for examining exchanged data,
but this is not as convenient as the facilities provided in each originating program. Consequently, again, analysing
the results in deciding the overall catchment impact has required further communication between the domain
experts. The OpenMI allows larger problems to be tackled, but does not remove the constraints of ensuring
communication between the different technical work areas in understanding the total output. As well as needing
the traditional local areas of expertise for example, river, sewerage and sewage treatment works modellers
there has also been the greater need for a new area of expertise, providing the catchment-level understanding of
the whole problem. This has normally been addressed at a more political level than has been the case for the
technical problems, with each technical team attempting to provide its solution to the ultimate decision makers
with no interaction, or understanding, of the effects that their solution has on other parts of the complete problem.
The OpenMI, by providing a framework for these different areas to better integrate the modelling work, has
concomitantly provided a social framework to encourage the individual teams to work together to provide better
modelling data for the decision makers.
5.

REFERENCES

Taylor, S, B Tomicic, W Williams and K Murray, 1999, TVP ICS and RTC in Oldham, WaPUG
Autumn Meeting, http://www.wapug.org.uk/past_papers/autumn_1999/paper08.pdf
Gijsbers, P.J.A. and Gregersen, J.B., 2005 OpenMI: A glue for integrated modelling, this conference
6.

ACKNOWLEDGMENTS

The authors would like to thank their respective organisations for permission to publish this paper. We would also
like to thank the EU for funding the project that led to the development of the OpenMI, and the members of the
HarmonIT team for providing tools and support used for this project. The views expressed in this paper are those
of the authors, and are not necessarily the views of WRc, Wallingford Software, WL/Delft Hydraulics, DHI Water
& Environment , the EU Commission or the HarmonIT partners and consortium.

WRc Ref: UC6920/13124-1


September 2005

94

S-ar putea să vă placă și