Feature wrapper (#136)

this quite sizable commit does several things, but is primarily
focussed on building a toolkit that can run simultaneous
simulations/analyses within a shared memory space. Versions <=2.1 use a
long list of global variables that prevent multiple instantiations on
linux systems without resorting to compilation tricks (like duplicate
binaries or similar via static linkage). This version uses a single "Project" pointer to encapsulate
the network and analysis data. There are no changes to existing algo
implementations other than to accomodate dereferencing of the passed-in
pointers. A more detailed list of major changes below:

- mirrors all “ENxxxx” function calls with “EN_xxxx” versions (note the
underscore) that take an extra first parameter: a pointer to an
EN_Project struct, which contains all network, hydraulic, quality, and
associated data.
- tweaks some code formatting to make it more readable
- removes some deprecated/commented code that was sufficiently old
- fixes implicit type-cast warnings

* Added ENaddnode and ENaddlink functions

* More memory reallocations

* Added ENInit, ENsetheadcurveindex

* Added ENdeletelink and ENdeletenode

* restored default behavior for float types

* fixed type

* Added docstrings for ENinit

* cleanup change

* moves global rule variables to vars.h

* migrates rule structs to typedefs

for better readability

* char types to proper enums

fixes #93

* Change some variable declarations for compatibility

Changes to keep compatibility with C89 compilers: variables must be
declared at the top of the functions. Remove the use of EN_LinkType in
function call as it is not compatible with ENgetnodetype.

* Moved declaration of idstodelete to top of function

* Updated ENinit function and headers

Updated header files with new functions
Updated def file with new functions
For ENinit changes names of parameters #98
Added enum for headloss formula

* Missed these files in 1a033fc

* migrates char types to enums fixes #93,

supports unified link/node type enums, rather than public/private
redefinitions

* removing links in reverse-index order maintains proper indexing fixes #96

* style

* clarifies curve getter units issue (dox)

closes #95

* fixes link/node confusion in ENsetlinktype

partially reverts a3bce95dc330a5a297634a303d438e2e1bc41cc9

* partial compilation fix

* fixes dox issue

* fixes allocation issues with enums

- updates style in various places
- introduces FlowDirection enum
- use snprintf to prevent overflow

* fixes enum type cast

* updated mac project settings

* Use of _snprintf on Windows and remove DLLEXPORT from mempool.h

snprintf it not compatible on Windows so we use _snprintf
mempool gave starnge compilation errors while removing DLLEXPORT worked.
Not sure why these functions needed to be exposed in the DLL?

* Revert "Use of _snprintf on Windows and remove DLLEXPORT from mempool.h"

This reverts commit 6238f77d47fa0feaabe5836043c006937de433a2.

* use of _snprintf instead of snprintf on Windows and removed DLLEXPORT from mempool.h

Had compilation errors on mempool.h. Removed DLLEXPORT so solve it. Not
sure why there was a need to expose these functions?

* Shift indices for Links in ENaddnode

Need to shift indices for Links not just Pipes since a pump could be
connected directly to a reservoir. Also set the defult base demand to
zero (was 5).

* Set defualts for madatory link properties in ENaddlink and small fix for ENsetheadcurveindex

Relates to #102 and closes #103

* wraps globals into structs, duplicates api functions with objective versions



* parse and serialize Comment field for network elements

related to #47

* adds getter for head/efficiency curve

in EN_getlinkvalue

* adds getter for event node index

… to return the index of the junction (tank) that triggered the event.

* fixes edge case in parsing

… where inp files without demands in [JUNCTIONS] and without any
[DEMAND] categories will fail.

* adds freeing function for project pointer

* removes redundant string literals, fixes overrun issue in error message getter function

* check for hydraulics already closed

* moving error definitions to data file

* deprecates ENR err message getter (unused)

* updates location of errors data file

also begins to expose blind structs to curves and patterns,
anticipating buildout of APIs for those.

* updates CLI output to reflect executable name as invoked

relates to #109

* Feature nrtest (#131)

* Initial commit EPANET testing tools.

* Initial commit for epanet-nrtestsuite

* SWIG wrapper for EPANET outputapi (#118)

* Removed pervious version of outputapi and wrapper

* SWIG wrapper for EPANET outputapi

* Patching cmake build script fixed target for outputapi

* Build failing on deprecated test script

* Minor changes. Responding to review comments.

* Feature nrtest (#121)

* Configured python setup to automatically build nrtest tools.

* Working on build / testing automation

* Adding EPANET 2.0.12 benchmark

* Updated Travis yml to run nrtest

* Fixing InsecurePlatformWarning

* Fixing InsecurePlatformWarning again

* Fixing InsecurePlatformWarning

* Fixing InsecurePlatformWarning

* Fixing InsecurePlatformWarning

* Update .travis.yml

* Update .travis.yml

* Update .travis.yml

* Update .travis.yml

* Working on configuring python environment and building test tools under Travis CI.

* Making gen-config.sh and run-nrtest.sh executable

* Debugging .travis.yml

* Debugging .travis.yml

* Debugging .travis.yml again

* Debugging .travis.yml again

* debugging travis setup

* debugging Travis setup

* debugging Travis setup

* debugging Travis setup

* debugging Travis setup

* debugging Travis setup

* debugging Travis setup

* debugging Travis setup

* debugging Travis setup

* debugging Travis setup

* debugging Travis setup

* Fixing bug with __strncpy_chk destlen < len

* nrtesting clean up

* re-implements fixes from:

5eead5ae40
3c788567a4

* removes extraneous build files, moves cmake and updates travis

* mirror of 9b37035560f9683f1514b439f7586a5c17bca5bf

* Move some variable declarations

* More variable declarations

* Fix TmpDir

* Allocate _defaultModel

* Fix EN_addcurve funcrion

* Fix for inpfile

* Fix writeRuleinInp call

* Set MAXMSG to 79 chars

* Fix for flow direction

* Refactoring testing related python packages and SWIG wrapper bug fix (#139)

* Eliminated epanet-reader package. Removed numpy dependency from epanet-output. Fixed reference counting bug in SWIG wrapper. Added error checking to run_nrtest.sh. Added nrtest package to requirements file.

* changing buildhome directory

* Fixing bug related to preprocessor definition of PI
This commit is contained in:
Sam Hatchett
2018-01-09 16:56:42 -05:00
committed by GitHub
parent 5eead5ae40
commit f97d837231
77 changed files with 37683 additions and 20504 deletions

27
tools/.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
# Python compiler files
*.py[cd]
# Python distribution and packaging
build/
dist/
temp/
*.cfg
*.egg-info/
*.whl
# SWIG generated files
epanet_output_wrap.c
epanet_output.py
# C compiler
*.o
*.dll
*.exe
# Eclipse project files and directories
.metadata/
.settings/
Release/
.project
.cproject
.pydevproject

View File

@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
#
# setup.py
#
# Created: 9/20/2017
# Author: Michael E. Tryby
# US EPA - ORD/NRMRL
#
# Setup up script for en_outputapi python extension
#
# Requires:
# Platform C language compiler
# Python packages: numpy
#
try:
from setuptools import setup, Extension
from setuptools.command.build_ext import build_ext
except ImportError:
from distutils.core import setup, Extension
from distutils.command.build_ext import build_ext
setup(
name = "epanet-output",
version = "1.0",
ext_modules = [
Extension("_epanet_output",
sources = ['src/epanet_output.i', 'src/epanet_output.c', 'src/errormanager.c'],
swig_opts=['-modern'],
language = 'C'
)
],
package_dir = {'':'src'},
py_modules = ['epanet_output'],
install_requires = [
'enum34'
]
)

View File

@@ -0,0 +1,969 @@
//-----------------------------------------------------------------------------
//
// epanet_output.c -- API for reading results from EPANET binary output file
//
// Version: 0.30
// Date 09/06/2017
// 06/17/2016
// 08/05/2014
// 05/21/2014
//
// Author: Michael E. Tryby
// US EPA - ORD/NRMRL
//
// Modified: Maurizio Cingi
// University of Modena
//
// Purpose: Output API provides an interface for retrieving results from an
// EPANET binary output file.
//
// Output data in the binary file are aligned on a 4 byte word size.
// Therefore all values both integers and reals are 32 bits in length.
//
// All values returned by the output API are indexed from 0 to n-1. This
// differs from how node and link elements are indexed by the binary file
// writer found in EPANET. Times correspond to reporting periods are indexed
// from 0 to number of reporting periods minus one. Node and link elements
// are indexed from 0 to nodeCount minus one and 0 to linkCount minus one
// respectively.
//
// The Output API functions provide a convenient way to select "slices" of
// data from the output file. As such they return arrays of data. The API
// functions automatically allocate memory for the array to be returned. The
// caller is responsible for deallocating memory. The function ENR_free() is
// provided to deallocate memory.
//
//-----------------------------------------------------------------------------
#include "epanet_output.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "errormanager.h"
#include "messages.h"
// NOTE: These depend on machine data model and may change when porting
// F_OFF Must be a 8 byte / 64 bit integer for large file support
#ifdef _WIN32 // Windows (32-bit and 64-bit)
#define F_OFF __int64
#else // Other platforms
#define F_OFF off_t
#endif
#define INT4 int // Must be a 4 byte / 32 bit integer type
#define REAL4 float // Must be a 4 byte / 32 bit real type
#define WORDSIZE 4 // Memory alignment 4 byte word size for both int and real
#define MINNREC 14 // Minimum allowable number of records
#define PROLOGUE 884 // Preliminary fixed length section of header
#define MAXID_P1 32 // Max. # characters in ID name
#define NELEMENTTYPES 5 // Number of element types
#define NENERGYRESULTS 6 // Number of energy results
#define NNODERESULTS 4 // number of result fields for nodes
#define NLINKRESULTS 8 // number of result fields for links
#define NREACTRESULTS 4 // number of net reaction results
#define MEMCHECK(x) (((x) == NULL) ? 411 : 0 )
// Typedefs for opaque pointer
typedef struct data_s {
char name[MAXFNAME+1]; // file path/name
FILE* file; // FILE structure pointer
INT4 nodeCount, tankCount, linkCount, pumpCount, valveCount, nPeriods;
F_OFF outputStartPos; // starting file position of output data
F_OFF bytesPerPeriod; // bytes saved per simulation time period
error_handle_t* error_handle;
} data_t;
//-----------------------------------------------------------------------------
// Local functions
//-----------------------------------------------------------------------------
void errorLookup(int errcode, char* errmsg, int length);
int validateFile(ENR_Handle);
float getNodeValue(ENR_Handle, int, int, int);
float getLinkValue(ENR_Handle, int, int, int);
int _fopen(FILE **f, const char *name, const char *mode);
int _fseek(FILE* stream, F_OFF offset, int whence);
F_OFF _ftell(FILE* stream);
float* newFloatArray(int n);
int* newIntArray(int n);
char* newCharArray(int n);
int DLLEXPORT ENR_init(ENR_Handle* dp_handle)
// Purpose: Initialized pointer for the opaque ENR_Handle.
//
// Returns: Error code 0 on success, -1 on failure
//
// Note: The existence of this function has been carefully considered.
// Don't change it.
//
{
int errorcode = 0;
data_t* p_data;
// Allocate memory for private data
p_data = (data_t*)calloc(1, sizeof(data_t));
if (p_data != NULL){
p_data->error_handle = new_errormanager(&errorLookup);
*dp_handle = p_data;
}
else
errorcode = -1;
// TODO: Need to handle errors during initialization better.
return errorcode;
}
int DLLEXPORT ENR_close(ENR_Handle* p_handle)
/*------------------------------------------------------------------------
** Input: *p_handle = pointer to ENR_Handle struct
**
** Returns: Error code 0 on success, -1 on failure
**
** Purpose: Close the output binary file, dellocate ENR_Handle struc
** and nullify pointer to ENR_Handle struct
**
** NOTE: ENR_close must be called before program end
** after calling ENR_close data in ENR_Handle struct are no more
** accessible
**-------------------------------------------------------------------------
*/
{
data_t* p_data;
int errorcode = 0;
p_data = (data_t*)(*p_handle);
if (p_data == NULL || p_data->file == NULL)
errorcode = -1;
else
{
dst_errormanager(p_data->error_handle);
fclose(p_data->file);
free(p_data);
*p_handle = NULL;
}
return errorcode;
}
int DLLEXPORT ENR_open(ENR_Handle p_handle, const char* path)
/*------------------------------------------------------------------------
** Input: path
** Output: p_handle = pointer to ENR_Handle struct
** Returns: warning / error code
** Purpose: Opens the output binary file and reads prologue and epilogue
**
** NOTE: ENR_init must be called before anyother ENR_* functions
**-------------------------------------------------------------------------
*/
{
int err, errorcode = 0;
F_OFF bytecount;
data_t* p_data;
p_data = (data_t*)p_handle;
if (p_data == NULL) return -1;
else
{
strncpy(p_data->name, path, MAXFNAME);
// Attempt to open binary output file for reading only
if ((_fopen(&(p_data->file), path, "rb")) != 0) errorcode = 434;
// Perform checks to insure the file is valid
else if ((err = validateFile(p_data)) != 0) errorcode = err;
// If a warning is encountered read file header
if (errorcode < 400 ) {
// read network size
fseek(p_data->file, 2*WORDSIZE, SEEK_SET);
fread(&(p_data->nodeCount), WORDSIZE, 1, p_data->file);
fread(&(p_data->tankCount), WORDSIZE, 1, p_data->file);
fread(&(p_data->linkCount), WORDSIZE, 1, p_data->file);
fread(&(p_data->pumpCount), WORDSIZE, 1, p_data->file);
fread(&(p_data->valveCount), WORDSIZE, 1, p_data->file);
// Compute positions and offsets for retrieving data
// fixed portion of header + title section + filenames + chem names
bytecount = PROLOGUE;
// node names + link names
bytecount += MAXID_P1*p_data->nodeCount + MAXID_P1*p_data->linkCount;
// network connectivity + tank nodes + tank areas
bytecount += 3*WORDSIZE*p_data->linkCount + 2*WORDSIZE*p_data->tankCount;
// node elevations + link lengths and link diameters
bytecount += WORDSIZE*p_data->nodeCount + 2*WORDSIZE*p_data->linkCount;
// pump energy summary
bytecount += 7*WORDSIZE*p_data->pumpCount + WORDSIZE;
p_data->outputStartPos= bytecount;
p_data->bytesPerPeriod = NNODERESULTS*WORDSIZE*p_data->nodeCount +
NLINKRESULTS*WORDSIZE*p_data->linkCount;
}
}
// If error close the binary file
if (errorcode > 400) {
set_error(p_data->error_handle, errorcode);
ENR_close(&p_handle);
}
return errorcode;
}
int DLLEXPORT ENR_getVersion(ENR_Handle p_handle, int* version)
/*------------------------------------------------------------------------
** Input: p_handle = pointer to ENR_Handle struct
** Output: version Epanet version
** Returns: error code
**
** Purpose: Returns Epanet version that wrote EBOFile
**--------------element codes-------------------------------------------
*/
{
int errorcode = 0;
data_t* p_data;
p_data = (data_t*)p_handle;
if (p_data == NULL) return -1;
else
{
fseek(p_data->file, 1*WORDSIZE, SEEK_SET);
if (fread(version, WORDSIZE, 1, p_data->file) != 1)
errorcode = 436;
}
return set_error(p_data->error_handle, errorcode);
}
int DLLEXPORT ENR_getNetSize(ENR_Handle p_handle, int** elementCount, int* length)
/*------------------------------------------------------------------------
** Input: p_handle = pointer to ENR_Handle struct
** Output: array of element counts (nodes, tanks, links, pumps, valves)
** Returns: error code
** Purpose: Returns an array of count values
**-------------------------------------------------------------------------
*/
{
int errorcode = 0;
int* temp = newIntArray(NELEMENTTYPES);
data_t* p_data;
p_data = (data_t*)p_handle;
if (p_data == NULL) return -1;
else
{
temp[0] = p_data->nodeCount;
temp[1] = p_data->tankCount;
temp[2] = p_data->linkCount;
temp[3] = p_data->pumpCount;
temp[4] = p_data->valveCount;
*elementCount = temp;
*length = NELEMENTTYPES;
}
return set_error(p_data->error_handle, errorcode);
}
int DLLEXPORT ENR_getUnits(ENR_Handle p_handle, ENR_Units code, int* unitFlag)
/*------------------------------------------------------------------------
** Input: p_handle = pointer to ENR_Handle struct
** code
** Output: count
** Returns: unitFlag
** Purpose: Returns pressure or flow unit flag
**--------------pressure unit flags----------------------------------------
** 0 = psi
** 1 = meters
** 2 = kPa
**------------------flow unit flags----------------------------------------
** 0 = cubic feet/second
** 1 = gallons/minute
** 2 = million gallons/day
** 3 = Imperial million gallons/day
** 4 = acre-ft/day
** 5 = liters/second
** 6 = liters/minute
** 7 = megaliters/day
** 8 = cubic meters/hour
** 9 = cubic meters/day
**-------------------------------------------------------------------------
*/
{
int errorcode = 0;
data_t* p_data;
*unitFlag = -1;
p_data = (data_t*)p_handle;
if (p_data == NULL) return -1;
else
{
switch (code)
{
case ENR_flowUnits:
fseek(p_data->file, 9*WORDSIZE, SEEK_SET);
fread(unitFlag, WORDSIZE, 1, p_data->file);
break;
case ENR_pressUnits:
fseek(p_data->file, 10*WORDSIZE, SEEK_SET);
fread(unitFlag, WORDSIZE, 1, p_data->file);
break;
default: errorcode = 421;
}
}
return set_error(p_data->error_handle, errorcode);
}
int DLLEXPORT ENR_getTimes(ENR_Handle p_handle, ENR_Time code, int* time)
/*------------------------------------------------------------------------
** Input: p_handle = pointer to ENR_Handle struct
** code = element code
** Output: time
** Returns: error code
** Purpose: Returns report and simulation time related parameters.
**-------------------------------------------------------------------------
*/
{
int errorcode = 0;
data_t* p_data;
*time = -1;
p_data = (data_t*)p_handle;
if (p_data == NULL) return -1;
else
{
switch (code)
{
case ENR_reportStart:
fseek(p_data->file, 12*WORDSIZE, SEEK_SET);
fread(time, WORDSIZE, 1, p_data->file);
break;
case ENR_reportStep:
fseek(p_data->file, 13*WORDSIZE, SEEK_SET);
fread(time, WORDSIZE, 1, p_data->file);
break;
case ENR_simDuration:
fseek(p_data->file, 14*WORDSIZE, SEEK_SET);
fread(time, WORDSIZE, 1, p_data->file);
break;
case ENR_numPeriods:
*time = p_data->nPeriods;
break;
default:
errorcode = 421;
}
}
return set_error(p_data->error_handle, errorcode);
}
int DLLEXPORT ENR_getElementName(ENR_Handle p_handle, ENR_ElementType type,
int elementIndex, char** name, int* length)
/*------------------------------------------------------------------------
** Input: p_handle = pointer to ENR_Handle struct
** type = ENR_node or ENR_link
** elementIndex from 1 to nodeCount or 1 to linkCount
** Output: name = elementName
** Returns: error code
** Purpose: Retrieves Name of a specified node or link element
** NOTE: 'name' must be able to hold MAXID characters
** TODO: Takes EPANET indexing from 1 to n not 0 to n-1
**-------------------------------------------------------------------------
*/
{
F_OFF offset;
int errorcode = 0;
char* temp;
data_t* p_data;
p_data = (data_t*)p_handle;
if (p_data == NULL) return -1;
/* Allocate memory for name */
else if MEMCHECK(temp = newCharArray(MAXID_P1)) errorcode = 411;
else
{
switch (type)
{
case ENR_node:
if (elementIndex < 1 || elementIndex > p_data->nodeCount)
errorcode = 423;
else offset = PROLOGUE + (elementIndex - 1)*MAXID_P1;
break;
case ENR_link:
if (elementIndex < 1 || elementIndex > p_data->linkCount)
errorcode = 423;
else
offset = PROLOGUE + p_data->nodeCount*MAXID_P1 +
(elementIndex - 1)*MAXID_P1;
break;
default:
errorcode = 421;
}
if (!errorcode)
{
_fseek(p_data->file, offset, SEEK_SET);
fread(temp, 1, MAXID_P1, p_data->file);
*name = temp;
*length = MAXID_P1;
}
}
return set_error(p_data->error_handle, errorcode);
}
int DLLEXPORT ENR_getEnergyUsage(ENR_Handle p_handle, int pumpIndex,
int* linkIndex, float** outValues, int* length)
/*
* Purpose: Returns pump energy usage statistics.
*
* Energy usage statistics:
* 0 = pump utilization
* 1 = avg. efficiency
* 2 = avg. kW/flow
* 3 = avg. kwatts
* 4 = peak kwatts
* 5 = cost/day
*/
{
F_OFF offset;
int errorcode = 0;
float* temp;
data_t* p_data;
p_data = (data_t*)p_handle;
if (p_data == NULL) return -1;
// Check for valid pump index
else if (pumpIndex < 1 || pumpIndex > p_data->pumpCount) errorcode = 423;
// Check memory for outValues
else if MEMCHECK(temp = newFloatArray(NENERGYRESULTS)) errorcode = 411;
else
{
// Position offset to start of pump energy summary
offset = p_data->outputStartPos - (p_data->pumpCount*(WORDSIZE + 6*WORDSIZE) + WORDSIZE);
// Adjust offset by pump index
offset += (pumpIndex - 1)*(WORDSIZE + 6*WORDSIZE);
// Power summary is 1 int and 6 floats for each pump
_fseek(p_data->file, offset, SEEK_SET);
fread(linkIndex, WORDSIZE, 1, p_data->file);
fread(temp, WORDSIZE, 6, p_data->file);
*outValues = temp;
*length = NENERGYRESULTS;
}
return set_error(p_data->error_handle, errorcode);
}
int DLLEXPORT ENR_getNetReacts(ENR_Handle p_handle, float** outValues, int* length)
/*
* Purpose: Returns network wide average reaction rates and average
* source mass inflow:
* 0 = bulk
* 1 = wall
* 2 = tank
* 3 = source
*/
{
F_OFF offset;
int errorcode = 0;
float* temp;
data_t* p_data;
p_data = (data_t*)p_handle;
if (p_data == NULL) return -1;
// Check memory for outValues
else if MEMCHECK(temp = newFloatArray(NREACTRESULTS)) errorcode = 411;
else
{
// Reaction summary is 4 floats located right before epilogue.
// This offset is relative to the end of the file.
offset = - 3*WORDSIZE - 4*WORDSIZE;
_fseek(p_data->file, offset, SEEK_END);
fread(temp, WORDSIZE, 4, p_data->file);
*outValues = temp;
*length = NREACTRESULTS;
}
return set_error(p_data->error_handle, errorcode);
}
void DLLEXPORT ENR_free(void** array)
//
// Purpose: Frees memory allocated by API calls
//
{
if (array != NULL) {
free(*array);
*array = NULL;
}
}
int DLLEXPORT ENR_getNodeSeries(ENR_Handle p_handle, int nodeIndex, ENR_NodeAttribute attr,
int startPeriod, int endPeriod, float** outValueSeries, int* dim)
//
// Purpose: Get time series results for particular attribute. Specify series
// start and length using seriesStart and seriesLength respectively.
//
// NOTE: The node index argument corresponds to the EPANET node index from 1 to
// nnodes. The series returned is indexed from 0 to nperiods - 1.
//
{
int k, length, errorcode = 0;
float* temp;
data_t* p_data;
p_data = (data_t*)p_handle;
if (p_data == NULL) return -1;
else if (nodeIndex < 1 || nodeIndex > p_data->nodeCount) errorcode = 423;
else if (startPeriod < 0 || endPeriod >= p_data->nPeriods ||
endPeriod <= startPeriod) errorcode = 422;
// Check memory for outValues
else if MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) errorcode = 411;
else
{
// loop over and build time series
for (k = 0; k < length; k++)
temp[k] = getNodeValue(p_handle, startPeriod + k,
nodeIndex, attr);
*outValueSeries = temp;
*dim = length;
}
return set_error(p_data->error_handle, errorcode);
}
int DLLEXPORT ENR_getLinkSeries(ENR_Handle p_handle, int linkIndex, ENR_LinkAttribute attr,
int startPeriod, int endPeriod, float** outValueSeries, int* dim)
//
// Purpose: Get time series results for particular attribute. Specify series
// start and length using seriesStart and seriesLength respectively.
//
// NOTE:
// The link index argument corresponds to the EPANET link index from 1 to
// nlinks. The series returned is indexed from 0 to nperiods - 1.
//
{
int k, length, errorcode = 0;
float* temp;
data_t* p_data;
p_data = (data_t*)p_handle;
if (p_data == NULL) return -1;
else if (linkIndex < 1 || linkIndex > p_data->linkCount) errorcode = 423;
else if (startPeriod < 0 || endPeriod >= p_data->nPeriods ||
endPeriod <= startPeriod) errorcode = 422;
// Check memory for outValues
else if MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) errorcode = 411;
else
{
// loop over and build time series
for (k = 0; k < length; k++)
temp[k] = getLinkValue(p_handle, startPeriod + k, linkIndex, attr);
*outValueSeries = temp;
*dim = length;
}
return set_error(p_data->error_handle, errorcode);
}
int DLLEXPORT ENR_getNodeAttribute(ENR_Handle p_handle, int periodIndex,
ENR_NodeAttribute attr, float** outValueArray, int* length)
//
// Purpose:
// For all nodes at given time, get a particular attribute
//
// Returns:
// Error code
// OutValueArray of results is indexed from 0 to nodeCount
//
// Warning:
// Caller must free memory allocated for outValueArray
//
// NOTE:
// The array returned is indexed from 0 to nnodes - 1. So to access
// node values by their EPANET index, the index value must be
// decremented by one.
//
{
F_OFF offset;
int errorcode = 0;
float * temp;
data_t* p_data;
p_data = (data_t*)p_handle;
if (p_data == NULL) return -1;
// if the time index is out of range return an error
else if (periodIndex < 0 || periodIndex >= p_data->nPeriods) errorcode = 422;
// Check memory for outValues
else if MEMCHECK(temp = newFloatArray(p_data->nodeCount)) errorcode = 411;
else
{
// calculate byte offset to start time for series
offset = p_data->outputStartPos + (periodIndex)*p_data->bytesPerPeriod;
// add offset for node and attribute
offset += ((attr - 1)*p_data->nodeCount)*WORDSIZE;
_fseek(p_data->file, offset, SEEK_SET);
fread(temp, WORDSIZE, p_data->nodeCount, p_data->file);
*outValueArray = temp;
*length = p_data->nodeCount;
}
return set_error(p_data->error_handle, errorcode);
}
int DLLEXPORT ENR_getLinkAttribute(ENR_Handle p_handle, int periodIndex,
ENR_LinkAttribute attr, float** outValueArray, int* length)
//
// Purpose:
// For all links at given time, get a particular attribute
//
// Returns:
// Error code
// OutValueArray of results is indexed from 0 to linkCount
//
// Warning:
// Caller must free memory allocated for outValueArray
//
// NOTE:
// The array returned is indexed from 0 to nlinks - 1. So to access
// link values by their EPANET index, the index value must be
// decremented by one.
//
{
F_OFF offset;
int errorcode = 0;
float* temp;
data_t* p_data;
p_data = (data_t*)p_handle;
if (p_data == NULL) return -1;
// if the time index is out of range return an error
else if (periodIndex < 0 || periodIndex >= p_data->nPeriods) errorcode = 422;
// Check memory for outValues
else if MEMCHECK(temp = newFloatArray(p_data->linkCount)) errorcode = 411;
else
{
// calculate byte offset to start time for series
offset = p_data->outputStartPos + (periodIndex)*p_data->bytesPerPeriod
+ (NNODERESULTS*p_data->nodeCount)*WORDSIZE;
// add offset for link and attribute
offset += ((attr - 1)*p_data->linkCount)*WORDSIZE;
_fseek(p_data->file, offset, SEEK_SET);
fread(temp, WORDSIZE, p_data->linkCount, p_data->file);
*outValueArray = temp;
*length = p_data->linkCount;
}
return set_error(p_data->error_handle, errorcode);
}
int DLLEXPORT ENR_getNodeResult(ENR_Handle p_handle, int periodIndex,
int nodeIndex, float** outValueArray, int* length)
//
// Purpose: For a node at given time, get all attributes.
//
// NOTE:
//
{
int j, errorcode = 0;
float* temp;
data_t* p_data;
p_data = (data_t*)p_handle;
if (p_data == NULL) return -1;
else if (periodIndex < 0 || periodIndex >= p_data->nPeriods) errorcode = 422;
else if (nodeIndex < 1 || nodeIndex > p_data->nodeCount) errorcode = 423;
else if MEMCHECK(temp = newFloatArray(NNODERESULTS)) errorcode = 411;
else
{
for (j = 0; j < NNODERESULTS; j++)
temp[j] = getNodeValue(p_handle, periodIndex, nodeIndex, j);
*outValueArray = temp;
*length = NNODERESULTS;
}
return set_error(p_data->error_handle, errorcode);
}
int DLLEXPORT ENR_getLinkResult(ENR_Handle p_handle, int periodIndex,
int linkIndex, float** outValueArray, int* length)
//
// Purpose: For a link at given time, get all attributes
//
{
int j, errorcode = 0;
float* temp;
data_t* p_data;
p_data = (data_t*)p_handle;
if (p_data == NULL) return -1;
else if (periodIndex < 0 || periodIndex >= p_data->nPeriods) errorcode = 422;
else if (linkIndex < 1 || linkIndex > p_data->linkCount) errorcode = 423;
else if MEMCHECK(temp = newFloatArray(NLINKRESULTS)) errorcode = 411;
else
{
for (j = 0; j < NLINKRESULTS; j++)
temp[j] = getLinkValue(p_handle, periodIndex, linkIndex, j);
*outValueArray = temp;
*length = NLINKRESULTS;
}
return set_error(p_data->error_handle, errorcode);
}
void DLLEXPORT ENR_clearError(ENR_Handle p_handle)
{
data_t* p_data;
p_data = (data_t*)p_handle;
clear_error(p_data->error_handle);
}
int DLLEXPORT ENR_checkError(ENR_Handle p_handle, char** msg_buffer)
{
int errorcode = 0;
char *temp = NULL;
data_t* p_data;
p_data = (data_t*)p_handle;
if (p_data == NULL) return -1;
else
{
errorcode = p_data->error_handle->error_status;
if (errorcode)
temp = check_error(p_data->error_handle);
*msg_buffer = temp;
}
return errorcode;
}
void errorLookup(int errcode, char* dest_msg, int dest_len)
//
// Purpose: takes error code returns error message
//
{
const char* msg;
switch (errcode)
{
case 10: msg = WARN10;
break;
case 411: msg = ERR411;
break;
case 412: msg = ERR412;
break;
case 421: msg = ERR421;
break;
case 422: msg = ERR422;
break;
case 423: msg = ERR423;
break;
case 434: msg = ERR434;
break;
case 435: msg = ERR435;
break;
case 436: msg = ERR436;
break;
default: msg = ERRERR;
}
strncpy(dest_msg, msg, MAXMSG);
}
int validateFile(ENR_Handle p_handle)
// Returns:
// Error code: 435, 436
// Warning code: 10
{
INT4 magic1, magic2, hydcode;
int errorcode = 0;
F_OFF filepos;
data_t* p_data;
p_data = (data_t*)p_handle;
// Read magic number from beginning of file
fseek(p_data->file, 0L, SEEK_SET);
fread(&magic1, WORDSIZE, 1, p_data->file);
// Fast forward to end and read file epilogue
fseek(p_data->file, -3*WORDSIZE, SEEK_END);
fread(&(p_data->nPeriods), WORDSIZE, 1, p_data->file);
fread(&hydcode, WORDSIZE, 1, p_data->file);
fread(&magic2, WORDSIZE, 1, p_data->file);
filepos = _ftell(p_data->file);
// Is the file an EPANET binary file?
if (magic1 != magic2) errorcode = 435;
// Does the binary file contain results?
else if (filepos < MINNREC*WORDSIZE || p_data->nPeriods == 0)
errorcode = 436;
// Issue warning if there were problems with the model run.
else if (hydcode != 0) errorcode = 10;
return errorcode;
}
float getNodeValue(ENR_Handle p_handle, int periodIndex, int nodeIndex,
int attr)
//
// Purpose: Retrieves an attribute value at a specified node and time
//
{
F_OFF offset;
REAL4 y;
data_t* p_data;
p_data = (data_t*)p_handle;
// calculate byte offset to start time for series
offset = p_data->outputStartPos + periodIndex*p_data->bytesPerPeriod;
// add byte position for attribute and node
offset += ((attr - 1)*p_data->nodeCount + (nodeIndex - 1))*WORDSIZE;
_fseek(p_data->file, offset, SEEK_SET);
fread(&y, WORDSIZE, 1, p_data->file);
return y;
}
float getLinkValue(ENR_Handle p_handle, int periodIndex, int linkIndex,
int attr)
//
// Purpose: Retrieves an attribute value at a specified link and time
//
{
F_OFF offset;
REAL4 y;
data_t* p_data;
p_data = (data_t*)p_handle;
// Calculate byte offset to start time for series
offset = p_data->outputStartPos + periodIndex*p_data->bytesPerPeriod
+ (NNODERESULTS*p_data->nodeCount)*WORDSIZE;
// add byte position for attribute and link
offset += ((attr - 1)*p_data->linkCount + (linkIndex - 1))*WORDSIZE;
_fseek(p_data->file, offset, SEEK_SET);
fread(&y, WORDSIZE, 1, p_data->file);
return y;
}
int _fopen(FILE **f, const char *name, const char *mode) {
//
// Purpose: Substitute for fopen_s on platforms where it doesn't exist
// Note: fopen_s is part of C++11 standard
//
int ret = 0;
#ifdef _WIN32
ret = (int)fopen_s(f, name, mode);
#else
*f = fopen(name, mode);
if (!*f)
ret = -1;
#endif
return ret;
}
int _fseek(FILE* stream, F_OFF offset, int whence)
//
// Purpose: Selects platform fseek() for large file support
//
{
#ifdef _WIN32 // Windows (32-bit and 64-bit)
#define FSEEK64 _fseeki64
#else // Other platforms
#define FSEEK64 fseeko
#endif
return FSEEK64(stream, offset, whence);
}
F_OFF _ftell(FILE* stream)
//
// Purpose: Selects platform ftell() for large file support
//
{
#ifdef _WIN32 // Windows (32-bit and 64-bit)
#define FTELL64 _ftelli64
#else // Other platforms
#define FTELL64 ftello
#endif
return FTELL64(stream);
}
float* newFloatArray(int n)
//
// Warning: Caller must free memory allocated by this function.
//
{
return (float*) malloc((n)*sizeof(float));
}
int* newIntArray(int n)
//
// Warning: Caller must free memory allocated by this function.
//
{
return (int*) malloc((n)*sizeof(int));
}
char* newCharArray(int n)
//
// Warning: Caller must free memory allocated by this function.
//
{
return (char*) malloc((n)*sizeof(char));
}

View File

@@ -0,0 +1,129 @@
/*
* epanet_output.h - EPANET Output API
*
* Created on: Jun 4, 2014
*
* Author: Michael E. Tryby
* US EPA - ORD/NRMRL
*/
#ifndef EPANET_OUTPUT_H_
#define EPANET_OUTPUT_H_
/* Epanet Results binary file API */
#define MAXFNAME 259 // Max characters in file name
#define MAXID 31 // Max characters in ID name
// This is an opaque pointer to struct. Do not access variables.
typedef void* ENR_Handle;
typedef enum {
ENR_node = 1,
ENR_link = 2
} ENR_ElementType;
typedef enum {
ENR_getSeries = 1,
ENR_getAttribute = 2,
ENR_getResult = 3,
ENR_getReacts = 4,
ENR_getEnergy = 5
} ENR_ApiFunction;
typedef enum {
ENR_flowUnits = 1,
ENR_pressUnits = 2
} ENR_Units;
typedef enum {
ENR_reportStart = 1,
ENR_reportStep = 2,
ENR_simDuration = 3,
ENR_numPeriods = 4
}ENR_Time;
typedef enum {
ENR_demand = 1,
ENR_head = 2,
ENR_pressure = 3,
ENR_quality = 4
} ENR_NodeAttribute;
typedef enum {
ENR_flow = 1,
ENR_velocity = 2,
ENR_headloss = 3,
ENR_avgQuality = 4,
ENR_status = 5,
ENR_setting = 6,
ENR_rxRate = 7,
ENR_frctnFctr = 8
} ENR_LinkAttribute;
#ifdef WINDOWS
#ifdef __cplusplus
#define DLLEXPORT __declspec(dllexport) __cdecl
#else
#define DLLEXPORT __declspec(dllexport) __stdcall
#endif
#else
#define DLLEXPORT
#endif
#ifdef __cplusplus
extern "C" {
#endif
int DLLEXPORT ENR_init(ENR_Handle* p_handle_out);
int DLLEXPORT ENR_open(ENR_Handle p_handle_in, const char* path);
int DLLEXPORT ENR_getVersion(ENR_Handle p_handle_in, int* int_out);
int DLLEXPORT ENR_getNetSize(ENR_Handle p_handle_in, int** int_out, int* int_dim);
int DLLEXPORT ENR_getUnits(ENR_Handle p_handle_in, ENR_Units t_enum, int* int_out);
int DLLEXPORT ENR_getTimes(ENR_Handle p_handle_in, ENR_Time t_enum, int* int_out);
int DLLEXPORT ENR_getElementName(ENR_Handle p_handle_in, ENR_ElementType t_enum,
int elementIndex, char** string_out, int* slen);
int DLLEXPORT ENR_getEnergyUsage(ENR_Handle p_handle_in, int pumpIndex,
int* int_out, float** float_out, int* int_dim);
int DLLEXPORT ENR_getNetReacts(ENR_Handle p_handle_in, float** float_out, int* int_dim);
int DLLEXPORT ENR_getNodeSeries(ENR_Handle p_handle_in, int nodeIndex, ENR_NodeAttribute t_enum,
int startPeriod, int endPeriod, float** outValueSeries, int* dim);
int DLLEXPORT ENR_getLinkSeries(ENR_Handle p_handle_in, int linkIndex, ENR_LinkAttribute t_enum,
int startPeriod, int endPeriod, float** outValueSeries, int* dim);
int DLLEXPORT ENR_getNodeAttribute(ENR_Handle p_handle_in, int periodIndex,
ENR_NodeAttribute t_enum, float** outValueArray, int* dim);
int DLLEXPORT ENR_getLinkAttribute(ENR_Handle p_handle_in, int periodIndex,
ENR_LinkAttribute t_enum, float** outValueArray, int* dim);
int DLLEXPORT ENR_getNodeResult(ENR_Handle p_handle_in, int periodIndex, int nodeIndex,
float** float_out, int* int_dim);
int DLLEXPORT ENR_getLinkResult(ENR_Handle p_handle_in, int periodIndex, int linkIndex,
float** float_out, int* int_dim);
int DLLEXPORT ENR_close(ENR_Handle* p_handle_out);
void DLLEXPORT ENR_free(void** array);
void DLLEXPORT ENR_clearError(ENR_Handle p_handle_in);
int DLLEXPORT ENR_checkError(ENR_Handle p_handle_in, char** msg_buffer);
#ifdef __cplusplus
}
#endif
#endif /* EPANET_OUTPUT_H_ */

View File

@@ -0,0 +1,254 @@
/*
* epanet_output.i - SWIG interface description file for EPANET Output API
*
* Created: 9/20/2017
*
* Author: Michael E. Tryby
* US EPA - ORD/NRMRL
*
*/
%module epanet_output
%{
#include "errormanager.h"
#include "messages.h"
#include "epanet_output.h"
#define SWIG_FILE_WITH_INIT
%}
%include "typemaps.i"
/* DEFINE AND TYPEDEF MUST BE INCLUDED */
#define MAXMSG 53
typedef void* ENR_Handle;
typedef enum {
ENR_node = 1,
ENR_link = 2
} ENR_ElementType;
/*
typedef enum {
ENR_nodeCount = 1,
ENR_tankCount = 2,
ENR_linkCount = 3,
ENR_pumpCount = 4,
ENR_valveCount = 5
} ENR_ElementCount;
*/
typedef enum {
ENR_flowUnits = 1,
ENR_pressUnits = 2
} ENR_Units;
typedef enum {
ENR_reportStart = 1,
ENR_reportStep = 2,
ENR_simDuration = 3,
ENR_numPeriods = 4
}ENR_Time;
typedef enum {
ENR_demand = 1,
ENR_head = 2,
ENR_pressure = 3,
ENR_quality = 4
} ENR_NodeAttribute;
typedef enum {
ENR_flow = 1,
ENR_velocity = 2,
ENR_headloss = 3,
ENR_avgQuality = 4,
ENR_status = 5,
ENR_setting = 6,
ENR_rxRate = 7,
ENR_frctnFctr = 8
} ENR_LinkAttribute;
#ifdef WINDOWS
#ifdef __cplusplus
#define DLLEXPORT __declspec(dllexport) __cdecl
#else
#define DLLEXPORT __declspec(dllexport) __stdcall
#endif
#else
#define DLLEXPORT
#endif
/* TYPEMAPS FOR OPAQUE POINTER */
/* Used for functions that output a new opaque pointer */
%typemap(in, numinputs=0) ENR_Handle* p_handle_out (ENR_Handle retval)
{
/* OUTPUT in */
retval = NULL;
$1 = &retval;
}
/* used for functions that take in an opaque pointer (or NULL)
and return a (possibly) different pointer */
%typemap(argout) ENR_Handle* p_handle_out
{
/* OUTPUT argout */
%append_output(SWIG_NewPointerObj(SWIG_as_voidptr(retval$argnum), $1_descriptor, 0));
}
/* No need for special IN typemap for opaque pointers, it works anyway */
/* TYPEMAP FOR IGNORING INT ERROR CODE RETURN VALUE */
%typemap(out) int {
$result = Py_None;
Py_INCREF($result);
}
/* TYPEMAPS FOR INT ARGUMENT AS RETURN VALUE */
%typemap(in, numinputs=0) int* int_out (int temp) {
$1 = &temp;
}
%typemap(argout) int* int_out {
%append_output(PyInt_FromLong(*$1));
}
/* TYPEMAP FOR MEMORY MANAGEMENT AND ENCODING OF STRINGS */
%typemap(in, numinputs=0)char** string_out (char* temp), int* slen (int temp){
$1 = &temp;
}
%typemap(argout)(char** string_out, int* slen) {
if (*$1) {
PyObject* o;
o = PyUnicode_FromStringAndSize(*$1, *$2);
$result = SWIG_Python_AppendOutput($result, o);
free(*$1);
}
}
/* TYPEMAPS FOR MEMORY MANAGEMNET OF FLOAT ARRAYS */
%typemap(in, numinputs=0)float** float_out (float* temp), int* int_dim (int temp){
$1 = &temp;
}
%typemap(argout) (float** float_out, int* int_dim) {
if (*$1) {
PyObject *o = PyList_New(*$2);
int i;
float* temp = *$1;
for(i=0; i<*$2; i++) {
PyList_SetItem(o, i, PyFloat_FromDouble((double)temp[i]));
}
$result = SWIG_Python_AppendOutput($result, o);
free(*$1);
}
}
/* TYPEMAPS FOR MEMORY MANAGEMENT OF INT ARRAYS */
%typemap(in, numinputs=0)int** int_out (long* temp), int* int_dim (int temp){
$1 = &temp;
}
%typemap(argout) (int** int_out, int* int_dim) {
if (*$1) {
PyObject *o = PyList_New(*$2);
int i;
long* temp = *$1;
for(i=0; i<*$2; i++) {
PyList_SetItem(o, i, PyInt_FromLong(temp[i]));
}
$result = SWIG_Python_AppendOutput($result, o);
free(*$1);
}
}
/* TYPEMAP FOR ENUMERATED TYPES */
%typemap(in) EnumeratedType (int val, int ecode = 0) {
if (PyObject_HasAttrString($input,"value")) {
PyObject* o;
o = PyObject_GetAttrString($input, "value");
ecode = SWIG_AsVal_int(o, &val);
}
else {
SWIG_exception_fail(SWIG_ArgError(ecode), "in method '" "$symname" "', argument " "$argnum"" of type '" "$ltype""'");
}
$1 = ($1_type)(val);
}
%apply EnumeratedType {ENR_ElementType, ENR_Units, ENR_Time, ENR_NodeAttribute, ENR_LinkAttribute}
/* RENAME FUNCTIONS PYTHON STYLE */
%rename("%(undercase)s") "";
/* INSERTS CUSTOM EXCEPTION HANDLING IN WRAPPER */
%exception
{
char* err_msg;
ENR_clearError(arg1);
$function
if (ENR_checkError(arg1, &err_msg))
{
PyErr_SetString(PyExc_Exception, err_msg);
SWIG_fail;
}
}
/* INSERT EXCEPTION HANDLING FOR THESE FUNCTIONS */
int DLLEXPORT ENR_open(ENR_Handle p_handle, const char* path);
int DLLEXPORT ENR_getVersion(ENR_Handle p_handle, int* int_out);
int DLLEXPORT ENR_getNetSize(ENR_Handle p_handle, int** int_out, int* int_dim);
int DLLEXPORT ENR_getUnits(ENR_Handle p_handle, ENR_Units t_enum, int* int_out);
int DLLEXPORT ENR_getTimes(ENR_Handle p_handle, ENR_Time t_enum, int* int_out);
int DLLEXPORT ENR_getElementName(ENR_Handle p_handle, ENR_ElementType t_enum,
int elementIndex, char** string_out, int* slen);
int DLLEXPORT ENR_getEnergyUsage(ENR_Handle p_handle, int pumpIndex,
int* int_out, float** float_out, int* int_dim);
int DLLEXPORT ENR_getNetReacts(ENR_Handle p_handle, float** float_out, int* int_dim);
int DLLEXPORT ENR_getNodeAttribute(ENR_Handle p_handle, int periodIndex,
ENR_NodeAttribute t_enum, float** float_out, int* int_dim);
int DLLEXPORT ENR_getLinkAttribute(ENR_Handle p_handle, int periodIndex,
ENR_LinkAttribute t_enum, float** float_out, int* int_dim);
%exception;
/* NO EXCEPTION HANDLING FOR THESE FUNCTIONS */
int DLLEXPORT ENR_init(ENR_Handle* p_handle_out);
int DLLEXPORT ENR_close(ENR_Handle* p_handle_out);
void DLLEXPORT ENR_free(void** array);
void DLLEXPORT ENR_clearError(ENR_Handle p_handle);
int DLLEXPORT ENR_checkError(ENR_Handle p_handle, char** msg_buffer);
/* CODE ADDED DIRECTLY TO SWIGGED INTERFACE MODULE */
%pythoncode%{
import enum
class ElementType(enum.Enum):
NODE = ENR_node
LINK = ENR_link
class Units(enum.Enum):
FLOW_UNIT = ENR_flowUnits
PRESS_UNIT = ENR_pressUnits
class Time(enum.Enum):
REPORT_START = ENR_reportStart
REPORT_STEP = ENR_reportStep
SIM_DURATION = ENR_simDuration
NUM_PERIODS = ENR_numPeriods
class NodeAttribute(enum.Enum):
DEMAND = ENR_demand
HEAD = ENR_head
PRESSURE = ENR_pressure
QUALITY = ENR_quality
class LinkAttribute(enum.Enum):
FLOW = ENR_flow
VELOCITY = ENR_velocity
HEADLOSS = ENR_headloss
AVG_QUALITY = ENR_avgQuality
STATUS = ENR_status
SETTING = ENR_setting
RX_RATE = ENR_rxRate
FRCTN_FCTR = ENR_frctnFctr
%}

View File

@@ -0,0 +1,74 @@
//-----------------------------------------------------------------------------
//
// errormanager.c
//
// Purpose: Provides a simple interface for managing runtime error messages.
//
// Date: 08/25/2017
//
// Author: Michael E. Tryby
// US EPA - ORD/NRMRL
//-----------------------------------------------------------------------------
#include <stdlib.h>
#include <string.h>
#include "errormanager.h"
error_handle_t* new_errormanager(void (*p_error_message)(int, char*, int))
//
// Purpose: Constructs a new error handle.
//
{
error_handle_t* error_handle;
error_handle = (error_handle_t*)calloc(1, sizeof(error_handle_t));
error_handle->p_msg_lookup = p_error_message;
return error_handle;
}
void dst_errormanager(error_handle_t* error_handle)
//
// Purpose: Destroys the error handle.
//
{
free(error_handle);
}
int set_error(error_handle_t* error_handle, int errorcode)
//
// Purpose: Sets an error code in the handle.
//
{
// If the error code is 0 no action is taken and 0 is returned.
// This is a feature not a bug.
if (errorcode)
error_handle->error_status = errorcode;
return errorcode;
}
char* check_error(error_handle_t* error_handle)
//
// Purpose: Returns the error message or NULL.
//
// Note: Caller must free memory allocated by check_error
//
{
char* temp = NULL;
if (error_handle->error_status != 0) {
temp = (char*) calloc(ERR_MAXMSG, sizeof(char));
if (temp)
error_handle->p_msg_lookup(error_handle->error_status, temp, ERR_MAXMSG);
}
return temp;
}
void clear_error(error_handle_t* error_handle)
//
// Purpose: Clears the error from the handle.
//
{
error_handle->error_status = 0;
}

View File

@@ -0,0 +1,27 @@
/*
* errormanager.h
*
* Created on: Aug 25, 2017
*
* Author: Michael E. Tryby
* US EPA - ORD/NRMRL
*/
#ifndef ERRORMANAGER_H_
#define ERRORMANAGER_H_
#define ERR_MAXMSG 256
typedef struct error_s {
int error_status;
void (*p_msg_lookup)(int, char*, int);
} error_handle_t;
error_handle_t* new_errormanager(void (*p_error_message)(int, char*, int));
void dst_errormanager(error_handle_t* error_handle);
int set_error(error_handle_t* error_handle, int errorcode);
char* check_error(error_handle_t* error_handle);
void clear_error(error_handle_t* error_handle);
#endif /* ERRORMANAGER_H_ */

View File

@@ -0,0 +1,29 @@
/*
* messages.h - EPANET
*
* Created on: June 1, 2017
*
* Author: Michael E. Tryby
* US EPA - ORD/NRMRL
*/
#ifndef MESSAGES_H_
#define MESSAGES_H_
/*------------------- Error Messages --------------------*/
#define MAXMSG 53
#define WARN10 "Warning: model run issued warnings"
#define ERR411 "Input Error 411: no memory allocated for results"
#define ERR412 "Input Error 412: binary file hasn't been opened"
#define ERR421 "Input Error 421: invalid parameter code"
#define ERR422 "Input Error 422: reporting period index out of range"
#define ERR423 "Input Error 423: element index out of range"
#define ERR434 "File Error 434: unable to open binary file"
#define ERR435 "File Error 435: invalid binary file type"
#define ERR436 "File Error 436: no results in binary file"
#define ERRERR "Error: An unknown error has occurred"
#endif /* MESSAGES_H_ */

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# __init__.py
#
# Created: 11/13/2017
# Author: Michael E. Tryby
# US EPA - ORD/NRMRL
#
import os
DATA_PATH = os.path.abspath(os.path.dirname(__file__))
OUTPUT_FILE_EXAMPLE1 = os.path.join(DATA_PATH, 'net1.out')

Binary file not shown.

View File

@@ -0,0 +1,102 @@
#
# epanet_output_test.py
#
# Created: 11/8/2017
# Author: Michael E. Tryby
# US EPA - ORD/NRMRL
#
# Unit testing for EPANET Output API using pytest.
#
import pytest
import numpy as np
import epanet_output as oapi
from data import OUTPUT_FILE_EXAMPLE1
@pytest.fixture()
def enr_handle(request):
_handle = oapi.enr_init()
oapi.enr_open(_handle, OUTPUT_FILE_EXAMPLE1)
def enr_close():
oapi.enr_close()
request.addfinalizer(enr_close)
return _handle
def test_get_times(enr_handle):
num_periods = oapi.enr_get_times(enr_handle, oapi.Time.NUM_PERIODS)
assert num_periods == 25
# def test_get_size(file_path):
# handle = oapi.enr_init()
# oapi.enr_open(handle, file_path)
#
# size = oapi.enr_get_net_size(handle)
#
# print(size)
#
# handle = oapi.enr_close()
#
# def test_get_names(file_path):
# handle = oapi.enr_init()
# oapi.enr_open(handle, file_path)
#
# name = oapi.enr_get_element_name(handle, oapi.ElementType.NODE, 10)
#
# print(name)
#
# handle = oapi.enr_close()
#
# def test_get_energy(file_path):
# handle = oapi.enr_init()
# oapi.enr_open(handle, file_path)
#
# result = oapi.enr_get_energy_usage(handle, 1)
#
# print(result)
#
# handle = oapi.enr_close()
#
# def test_get_react(file_path):
# handle = oapi.enr_init()
# oapi.enr_open(handle, file_path)
#
# result = oapi.enr_get_net_reacts(handle)
#
# print(result)
#
# handle = oapi.enr_close()
#
def test_get_node_attribute(enr_handle):
ref_array = np.array([ 1., 0.44407997, 0.43766347, 0.42827705, 0.41342604,
0.42804748, 0.44152543, 0.40502965, 0.38635802, 1., 0.96745253])
array = oapi.enr_get_node_attribute(enr_handle, 1, oapi.NodeAttribute.QUALITY)
assert len(array) == 11
assert np.allclose(array, ref_array)
def test_get_link_attribute(enr_handle):
ref_array = np.array([ 1848.58117676, 1220.42736816, 130.11161804,
187.68930054, 119.88839722, 40.46448898, -748.58111572, 478.15377808,
191.73458862, 30.11160851, 140.4644928, 59.53551483, 1848.58117676])
array = oapi.enr_get_link_attribute(enr_handle, 1, oapi.LinkAttribute.FLOW)
assert len(array) == 13
assert np.allclose(array, ref_array)
# if __name__ == "__main__":
#
# file_path = "M:\\net mydocuments\\EPA Projects\\EPAnet Examples\\net1.out"
# test_get_times(file_path)
# test_get_size(file_path)
# test_get_names(file_path)
# test_get_energy(file_path)
# test_get_react(file_path)
# test_get_node_attribute(file_path)
# test_get_link_attribute(file_path)
#

View File

@@ -0,0 +1,251 @@
/*
* test_epanet_output.cpp
*
* Created: 8/4/2017
* Author: Michael E. Tryby
* US EPA - ORD/NRMRL
*
* Unit testing for EPANET Output API using google test.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include "gtest/gtest.h"
#include "../src/epanet_output.h"
#define PROJECT_HOME "C:/Users/mtryby/Workspace/GitRepo/michaeltryby/epanet/"
#define DATA_PATH "tools/epanet-output/test/data/net1.out"
namespace {
TEST(ENR_init, InitTest) {
ENR_Handle p_handle;
int error = ENR_init(&p_handle);
ASSERT_EQ(0, error);
ASSERT_TRUE(p_handle != NULL);
}
TEST(ENR_open, OpenTest) {
std::string path = std::string(PROJECT_HOME) + std::string(DATA_PATH);
ENR_Handle p_handle;
ENR_init(&p_handle);
int error = ENR_open(p_handle, path.c_str());
ASSERT_EQ(0, error);
ENR_close(&p_handle);
}
TEST(ENR_close, CloseTest) {
ENR_Handle p_handle;
int error = ENR_init(&p_handle);
error = ENR_close(&p_handle);
ASSERT_EQ(-1, error);
ASSERT_TRUE(p_handle != NULL);
}
class OutputapiTest : public testing::Test {
protected:
// SetUp for OutputapiTest fixture
virtual void SetUp() {
std::string path = std::string(PROJECT_HOME) + std::string(DATA_PATH);
error = ENR_init(&p_handle);
ENR_clearError(p_handle);
error = ENR_open(p_handle, path.c_str());
}
// TearDown for OutputapiTest fixture
virtual void TearDown() {
ENR_free((void**)&array);
error = ENR_close(&p_handle);
}
int error = 0;
ENR_Handle p_handle = NULL;
float* array = NULL;
int array_dim = 0;
};
TEST_F(OutputapiTest, getNetSizeTest) {
int* i_array = NULL;
// nodes, tanks, links, pumps, valves
int ref_array[5] = {11,2,13,1,0};
error = ENR_getNetSize(p_handle, &i_array, &array_dim);
ASSERT_EQ(0, error);
for (int i = 0; i < array_dim; i++)
EXPECT_EQ(ref_array[i], i_array[i]);
ENR_free((void**)&i_array);
}
TEST_F(OutputapiTest, getElementName) {
char* name = new char[MAXID];
int length, index = 1;
error = ENR_getElementName(p_handle, ENR_node, index, &name, &length);
ASSERT_EQ(0, error);
EXPECT_STREQ("10", name);
delete(name);
}
TEST_F(OutputapiTest, getNodeAttributeTest) {
float ref_array[11] = { 1.0,
0.44407997,
0.43766347,
0.42827705,
0.41342604,
0.42804748,
0.44152543,
0.40502965,
0.38635802,
1.0,
0.96745253 };
error = ENR_getNodeAttribute(p_handle, 1, ENR_quality, &array, &array_dim);
ASSERT_EQ(0, error);
for (int i = 0; i < array_dim; i++)
EXPECT_FLOAT_EQ(ref_array[i], array[i]);
}
TEST_F(OutputapiTest, getLinkAttributeTest) {
float ref_array[13] = { 1848.5812,
1220.4274,
130.11162,
187.6893,
119.8884,
40.464489,
-748.58112,
478.15378,
191.73459,
30.111609,
140.46449,
59.535515,
1848.5812};
error = ENR_getLinkAttribute(p_handle, 1, ENR_flow, &array ,&array_dim);
ASSERT_EQ(0, error);
for (int i = 0; i < array_dim; i++)
EXPECT_FLOAT_EQ(ref_array[i], array[i]);
}
TEST_F(OutputapiTest, getNodeResultTest) {
float ref_array[4] = {0.041142918,
150.0,
987.98358,
120.45029};
error = ENR_getNodeResult(p_handle, 1, 2, &array, &array_dim);
ASSERT_EQ(0, error);
for (int i = 0; i < array_dim; i++)
EXPECT_FLOAT_EQ(ref_array[i], array[i]);
}
TEST_F(OutputapiTest, getLinkResultTest) {
float ref_array[8] = {0.58586824,
1892.2433,
0.0,
-200.71875,
1.0,
3.0,
1.0,
0.0};
error = ENR_getLinkResult(p_handle, 24, 13, &array, &array_dim);
ASSERT_EQ(0, error);
for (int i = 0; i < array_dim; i++)
EXPECT_FLOAT_EQ(ref_array[i], array[i]);
}
TEST_F(OutputapiTest, getNodeSeriesTest){
float ref_array[10] = {119.25731,
120.45029,
121.19854,
122.00622,
122.37414,
122.8122,
122.82034,
122.90379,
123.40434,
123.81807};
error = ENR_getNodeSeries(p_handle, 2, ENR_pressure, 0, 10, &array, &array_dim);
ASSERT_EQ(0, error);
for (int i = 0; i < array_dim; i++)
EXPECT_FLOAT_EQ(ref_array[i], array[i]);
}
TEST_F(OutputapiTest, getLinkSeriesTest) {
float ref_array[10] = {1234.2072,
1220.4274,
1164.4,
1154.8175,
1100.0635,
1094.759,
1041.7854,
1040.7617,
1087.556,
1082.5011};
error = ENR_getLinkSeries(p_handle, 2, ENR_flow, 0, 10, &array, &array_dim);
ASSERT_EQ(0, error);
for (int i = 0; i < array_dim; i++)
EXPECT_FLOAT_EQ(ref_array[i], array[i]);
}
TEST_F(OutputapiTest, getNetReactsTest) {
float ref_array[4] = {18806.59,
85424.438,
115174.05,
238972.66};
error = ENR_getNetReacts(p_handle, &array, &array_dim);
ASSERT_EQ(0, error);
for (int i = 0; i < array_dim; i++)
EXPECT_FLOAT_EQ(ref_array[i], array[i]);
}
TEST_F(OutputapiTest, getEnergyUsageTest) {
float ref_array[6] = {57.712959,
75.0,
880.41583,
96.254318,
96.707115,
0.0};
int linkIdx;
error = ENR_getEnergyUsage(p_handle, 1, &linkIdx, &array, &array_dim);
ASSERT_EQ(0, error);
for (int i = 0; i < array_dim; i++)
EXPECT_FLOAT_EQ(ref_array[i], array[i]);
}
}
GTEST_API_ int main(int argc, char **argv) {
printf("Running main() from gtest_main.cc\n");
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

46
tools/gen-config.sh Executable file
View File

@@ -0,0 +1,46 @@
#! /bin/bash
#
# gen-config.sh - Generates nrtest app configuration file for test executable
#
# Date Created: 10/16/2017
#
# Author: Michael E. Tryby
# US EPA - ORD/NRMRL
#
# Arguments:
# 1 - absolute path to test executable
#
# NOT IMPLEMENTED YET
# 2 - test executable version number
# 3 - build description
#
unameOut="$(uname -s)"
case "${unameOut}" in
Linux*) ;&
Darwin*) abs_build_path=$1
test_cmd="runepanet"
;;
MINGW*) ;&
MSYS*) # Remove leading '/c' from file path for nrtest
abs_build_path="$( echo "$1" | sed -e 's#/c##' )"
test_cmd="runepanet.exe"
;;
*) # Machine unknown
esac
version=""
build_description=""
cat<<EOF
{
"name" : "epanet",
"version" : "${version}",
"description" : "${build_description}",
"setup_script" : "",
"exe" : "${abs_build_path}/${test_cmd}"
}
EOF

136
tools/nrtest-epanet/main.py Normal file
View File

@@ -0,0 +1,136 @@
import numpy as np
import time
import cStringIO
import itertools as it
import epanet_reader as er
def result_compare(path_test, path_ref, comp_args):
isclose = True
close = 0
notclose = 0
equal = 0
total = 0
output = cStringIO.StringIO()
eps = np.finfo(float).eps
start = time.time()
test_reader = er.reader(path_test)
ref_reader = er.reader(path_ref)
for test, ref in it.izip(test_reader, ref_reader):
total += 1
if total%100000 == 0:
print(total)
if test.size != ref.size:
raise ValueError('Inconsistent lengths')
# Skip results if they are zero or equal
if np.array_equal(test, ref):
equal += 1
continue
else:
try:
np.testing.assert_allclose(test, ref, 1.0e-06, 2*eps)
close += 1
except AssertionError as ae:
notclose += 1
output.write(str(ae))
output.write('\n\n')
continue
stop = time.time()
print(output.getvalue())
output.close()
print('equal: %d close: %d notclose: %d total: %d in %f (sec)\n' %
(equal, close, notclose, total, (stop - start)))
if notclose > 0:
print('%d differences found\n' % notclose)
isclose = False
return isclose
from nrtest.testsuite import TestSuite
from nrtest.compare import compare_testsuite, validate_testsuite
def nrtest_compare(path_test, path_ref, (comp_args)):
ts_new = TestSuite.read_benchmark(path_test)
ts_old = TestSuite.read_benchmark(path_ref)
if not validate_testsuite(ts_new) or not validate_testsuite(ts_old):
exit(1)
try:
# logging.info('Found %i tests' % len(ts_new.tests))
compatible = compare_testsuite(ts_new, ts_old, comp_args[0], comp_args[1])
except KeyboardInterrupt:
# logging.warning('Process interrupted by user')
compatible = False
# else:
# logging.info('Finished')
# Non-zero exit code indicates failure
exit(not compatible)
def nrtest_execute(app_path, test_path, output_path):
import logging
import glob
from os import listdir
from os.path import exists, isfile, isdir, join
from nrtest.execute import execute_testsuite, validate_testsuite
# for path in test_path + [app_path]:
# if not exists(path):
# logging.error('Could not find path: "%s"' % path)
test_dirs = glob.glob(test_path)
test_files = [p for p in test_path if isfile(p)]
test_files += [p for d in test_dirs for p in glob.glob(d + '*.json')]
# if p.endswith('.json')]
test_files = list(set(test_files)) # remove duplicates
ts = TestSuite.read_config(app_path, test_files, output_path)
if not validate_testsuite(ts):
exit(1)
try:
logging.info('Found %i tests' % len(test_files))
success = execute_testsuite(ts)
ts.write_manifest()
except KeyboardInterrupt:
logging.warning('Process interrupted by user')
success = False
else:
logging.info('Finished')
# Non-zero exit code indicates failure
exit(not success)
if __name__ == "__main__":
# app_path = "apps\\swmm-5.1.11.json"
# test_path = "tests\\examples\\example1.json"
# output_path = "benchmarks\\test\\"
# nrtest_execute(app_path, test_path, output_path)
# test_path = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\epanet-testsuite\\benchmarks\\v2011a"
# ref_path = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\epanet-testsuite\\benchmarks\\v2012"
# print(nrtest_compare(test_path, ref_path, (0.001, 0.0)))
path_test = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\epanet-testsuite\\benchmarks\\v2011a\\Example_3\\example3.out"
path_ref = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\epanet-testsuite\\benchmarks\\v2012\\Example_3\\example3.out"
result_compare(path_test, path_ref, (0.001, 0.0))

View File

@@ -0,0 +1,121 @@
# -*- coding: utf-8 -*-
#
# __init__.py - nrtest_epanet module
#
# Author: Michael E. Tryby
# US EPA - ORD/NRMRL
#
'''
Numerical regression testing (nrtest) plugin for comparing EPANET binary results
files and EPANET text based report files.
'''
# system imports
import itertools as it
# third party imports
import header_detail_footer as hdf
import numpy as np
# project import
import nrtest_epanet.output_reader as ordr
__author__ = "Michael Tryby"
__copyright__ = "None"
__credits__ = "Colleen Barr, Maurizio Cingi, Mark Gray, David Hall, Bryant McDonnell"
__license__ = "CC0 1.0 Universal"
__version__ = "0.4.0"
__date__ = "September 6, 2017"
__maintainer__ = "Michael Tryby"
__email__ = "tryby.michael@epa.gov"
__status = "Development"
def epanet_allclose_compare(path_test, path_ref, rtol, atol):
'''
Compares results in two EPANET binary files. Using the comparison criteria
described in the numpy assert_allclose documentation.
(test_value - ref_value) <= atol + rtol * abs(ref_value)
Returns true if all of the results in the two binary files meet the
comparison criteria; otherwise, an AssertionError is thrown.
Numpy allclose is quite expensive to evaluate. Test and reference results
are checked to see if they are equal before being compared using the
allclose criteria. This reduces comparison times significantly.
Arguments:
path_test - path to result file being tested
path_ref - path to reference result file
rtol - relative tolerance
atol - absolute tolerance
Returns:
True
Raises:
ValueError()
AssertionError()
...
'''
for (test, ref) in it.izip(ordr.output_generator(path_test),
ordr.output_generator(path_ref)):
if len(test) != len(ref):
raise ValueError('Inconsistent lengths')
# Skip over arrays that are equal
if np.array_equal(test, ref):
continue
else:
np.testing.assert_allclose(test, ref, rtol, atol)
return True
# def epanet_better_compare(path_test, path_ref, rtol, atol):
# '''
# If you don't like assert_allclose you can add another function here.
# '''
# pass
def epanet_report_compare(path_test, path_ref, rtol, atol):
'''
Compares results in two report files ignoring contents of header and footer.
Note: Header is 11 lines with report summary turned off. This test will fail
if the report summary is turned on because a time stamp is being written
immediately after it.
Arguments:
path_test - path to result file being tested
path_ref - path to reference result file
rtol - ignored
atol - ignored
Returns:
True or False
Raises:
HeaderError()
FooterError()
RunTimeError()
...
'''
HEADER = 11
FOOTER = 3
with open(path_test ,'r') as ftest, open(path_ref, 'r') as fref:
for (test_line, ref_line) in it.izip(hdf.parse(ftest, HEADER, FOOTER)[1],
hdf.parse(fref, HEADER, FOOTER)[1]):
if test_line != ref_line:
return False
return True

View File

@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
#
# output_reader.py
#
# Date Created: Aug 31, 2016
#
# Author: Michael E. Tryby
# US EPA - ORD/NRMRL
#
'''
The module output_reader provides the class used to implement the output
generator.
'''
# project import
import epanet_output as oapi
def output_generator(path_ref):
'''
The output_generator is designed to iterate over an EPANET binary file and
yield element attributes. It is useful for comparing contents of binary
files for numerical regression testing.
The generator yields a Python list containing element attributes.
Arguments:
path_ref - path to result file
Raises:
Exception()
...
'''
with OutputReader(path_ref) as br:
for period_index in range(0, br.report_periods()):
for element_type in oapi.ElementType:
for attribute in br.elementAttributes[element_type]:
yield br.element_attribute(element_type, period_index, attribute)
class OutputReader():
'''
Provides a minimal API used to implement output_generator.
'''
def __init__(self, filename):
self.filepath = filename
self.handle = None
self.elementAttributes = {oapi.ElementType.NODE: oapi.NodeAttribute,
oapi.ElementType.LINK: oapi.LinkAttribute}
self.getElementAttribute = {oapi.ElementType.NODE: oapi.enr_get_node_attribute,
oapi.ElementType.LINK: oapi.enr_get_link_attribute}
def __enter__(self):
self.handle = oapi.enr_init()
oapi.enr_open(self.handle, self.filepath.encode())
return self
def __exit__(self, type, value, traceback):
self.handle = oapi.enr_close()
def report_periods(self):
return oapi.enr_get_times(self.handle, oapi.Time.NUM_PERIODS)
def element_attribute(self, element_type, time_index, attribute):
return self.getElementAttribute[element_type](self.handle, time_index, attribute)

View File

@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
#
# setup.py
#
# Created on Aug 30, 2016
# Author: Michael E. Tryby
# US EPA - ORD/NRMRL
#
''' Setup up script for nrtest_epanet package. '''
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
entry_points = {
'nrtest.compare': [
'epanet allclose = nrtest_epanet:epanet_allclose_compare',
'epanet report = nrtest_epanet:epanet_report_compare',
# Add entry point for new comparison functions here
]
}
setup(
name='nrtest-epanet',
version='0.3.0',
description="EPANET extension for nrtest",
author="Michael E. Tryby",
author_email='tryby.michael@epa.gov',
url='https://github.com/USEPA',
packages=['nrtest_epanet',],
entry_points=entry_points,
install_requires=[
'header_detail_footer>=2.3',
'nrtest>=0.2.0',
'numpy>=1.7.0',
'epanet_output>=0.4.0'
],
keywords='nrtest_epanet'
)

View File

@@ -1 +0,0 @@
'''

File diff suppressed because one or more lines are too long

View File

@@ -1,502 +0,0 @@
//-----------------------------------------------------------------------------
//
// outputapi.c -- API for reading results from EPANet binary output file
//
// Version: 0.10
// Date: 08/05/14
// Date: 05/21/14
//
// Author: Michael E. Tryby
// US EPA - NRMRL
//
// Purpose: Output API provides an interface for retrieving results from
// an EPANet binary output file.
//
//-----------------------------------------------------------------------------
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include "outputapi.h"
#define INT4 int
#define REAL4 float
#define RECORDSIZE 4 // number of bytes per file record
#define MEMCHECK(x) (((x) == NULL) ? 411 : 0 )
#define MINNREC 14 // minimum allowable number of records
#define NNODERESULTS 4 // number of result fields for nodes
#define NLINKRESULTS 8 // number of result fields for links
struct ENResultsAPI {
char name[MAXFNAME + 1]; // file path/name
bool isOpened; // current state (CLOSED = 0, OPEN = 1)
FILE *file; // FILE structure pointer
INT4 nodeCount, tankCount, linkCount, pumpCount, valveCount;
INT4 reportStart, reportStep, simDuration, nPeriods;
INT4 flowFlag, pressFlag;
INT4 outputStartPos; // starting file position of output data
INT4 bytesPerPeriod; // bytes saved per simulation time period
};
//-----------------------------------------------------------------------------
// Local functions
//-----------------------------------------------------------------------------
float getNodeValue(ENResultsAPI*, int, int, ENR_NodeAttribute);
float getLinkValue(ENResultsAPI*, int, int, ENR_LinkAttribute);
ENResultsAPI* DLLEXPORT ENR_alloc(void)
{
return malloc(sizeof(struct ENResultsAPI));
}
int DLLEXPORT ENR_open(ENResultsAPI* enrapi, const char* path)
//
// Purpose: Open the output binary file and read epilogue
//
{
int magic1, magic2, errCode, version;
strncpy(enrapi->name, path, MAXFNAME);
enrapi->isOpened = false;
// Attempt to open binary output file for reading only
if ((enrapi->file = fopen(path, "rb")) == NULL)
return 434;
else
enrapi->isOpened = true;
// Fast forward to end and check for minimum number of records
fseek(enrapi->file, 0L, SEEK_END);
if (ftell(enrapi->file) < MINNREC*RECORDSIZE) {
fclose(enrapi->file);
// Error run terminated no results in binary file
return 435;
}
// Fast forward to end and read file epilogue
fseek(enrapi->file, -3*RECORDSIZE, SEEK_END);
fread(&(enrapi->nPeriods), RECORDSIZE, 1, enrapi->file);
fread(&errCode, RECORDSIZE, 1, enrapi->file);
fread(&magic2, RECORDSIZE, 1, enrapi->file);
// Rewind and read magic number from beginning of file
fseek(enrapi->file, 0L, SEEK_SET);
fread(&magic1, RECORDSIZE, 1, enrapi->file);
// Perform error checks
if (magic1 != magic2 || errCode != 0 || enrapi->nPeriods == 0) {
fclose(enrapi->file);
// Error run terminated no results in binary file
return 435;
}
// Otherwise read network size
fread(&version, RECORDSIZE, 1, enrapi->file);
fread(&(enrapi->nodeCount), RECORDSIZE, 1, enrapi->file);
fread(&(enrapi->tankCount), RECORDSIZE, 1, enrapi->file);
fread(&(enrapi->linkCount), RECORDSIZE, 1, enrapi->file);
fread(&(enrapi->pumpCount), RECORDSIZE, 1, enrapi->file);
// Jump ahead and read flow and pressure units
fseek(enrapi->file, 3*RECORDSIZE, SEEK_CUR);
fread(&(enrapi->flowFlag), RECORDSIZE, 1, enrapi->file);
fread(&(enrapi->pressFlag), RECORDSIZE, 1, enrapi->file);
// Jump ahead and read time information
fseek(enrapi->file, RECORDSIZE, SEEK_CUR);
fread(&(enrapi->reportStart), RECORDSIZE, 1, enrapi->file);
fread(&(enrapi->reportStep), RECORDSIZE, 1, enrapi->file);
fread(&(enrapi->simDuration), RECORDSIZE, 1, enrapi->file);
// Compute positions and offsets for retrieving data
enrapi->outputStartPos = 884;
enrapi->outputStartPos += 32*enrapi->nodeCount + 32*enrapi->linkCount;
enrapi->outputStartPos += 12*enrapi->linkCount+ 8*enrapi->tankCount
+ 4*enrapi->nodeCount + 8*enrapi->linkCount;
enrapi->outputStartPos += 28*enrapi->pumpCount + 4;
enrapi->bytesPerPeriod = 16*enrapi->nodeCount + 32*enrapi->linkCount;
return 0;
}
int DLLEXPORT ENR_getNetSize(ENResultsAPI* enrapi, ENR_ElementCount code, int* count)
//
// Purpose: Returns network size
//
{
*count = -1;
if (enrapi->isOpened) {
switch (code)
{
case ENR_nodeCount: *count = enrapi->nodeCount; break;
case ENR_tankCount: *count = enrapi->tankCount; break;
case ENR_linkCount: *count = enrapi->linkCount; break;
case ENR_pumpCount: *count = enrapi->pumpCount; break;
case ENR_valveCount: *count = enrapi->valveCount; break;
default: return 421;
}
return 0;
}
return 412;
}
int DLLEXPORT ENR_getUnits(ENResultsAPI* enrapi, ENR_Unit code, int* unitFlag)
//
// Purpose: Returns pressure and flow units
//
{
*unitFlag = -1;
if (enrapi->isOpened) {
switch (code)
{
case ENR_flowUnits: *unitFlag = enrapi->flowFlag; break;
case ENR_pressUnits: *unitFlag = enrapi->pressFlag; break;
default: return 421;
}
return 0;
}
return 412;
}
int DLLEXPORT ENR_getTimes(ENResultsAPI* enrapi, ENR_Time code, int* time)
//
// Purpose: Returns report and simulation time related parameters.
//
{
*time = -1;
if (enrapi->isOpened) {
switch (code)
{
case ENR_reportStart: *time = enrapi->reportStart; break;
case ENR_reportStep: *time = enrapi->reportStep; break;
case ENR_simDuration: *time = enrapi->simDuration; break;
case ENR_numPeriods: *time = enrapi->nPeriods; break;
default: return 421;
}
return 0;
}
return 412;
}
float* ENR_newOutValueSeries(ENResultsAPI* enrapi, int seriesStart,
int seriesLength, int* length, int* errcode)
//
// Purpose: Allocates memory for outValue Series.
//
// Warning: Caller must free memory allocated by this function using ENR_free().
//
{
int size;
float* array;
if (enrapi->isOpened) {
size = seriesLength - seriesStart;
if (size > enrapi->nPeriods)
size = enrapi->nPeriods;
// Allocate memory for outValues
array = (float*) calloc(size + 1, sizeof(float));
*errcode = (MEMCHECK(array));
*length = size;
return array;
}
*errcode = 412;
return NULL;
}
float* ENR_newOutValueArray(ENResultsAPI* enrapi, ENR_ApiFunction func,
ENR_ElementType type, int* length, int* errcode)
//
// Purpose: Allocates memory for outValue Array.
//
// Warning: Caller must free memory allocated by this function using ENR_free().
//
{
int size;
float* array;
if (enrapi->isOpened) {
switch (func)
{
case ENR_getAttribute:
if (type == ENR_node)
size = enrapi->nodeCount;
else
size = enrapi->linkCount;
break;
case ENR_getResult:
if (type == ENR_node)
size = NNODERESULTS;
else
size = NLINKRESULTS;
break;
default: *errcode = 421;
return NULL;
}
// Allocate memory for outValues
array = (float*) calloc(size, sizeof(float));
*errcode = (MEMCHECK(array));
*length = size;
return array;
}
*errcode = 412;
return NULL;
}
int DLLEXPORT ENR_getNodeSeries(ENResultsAPI* enrapi, int nodeIndex, ENR_NodeAttribute attr,
int seriesStart, int seriesLength, float* outValueSeries, int* length)
//
// What if timeIndex 0? length 0?
//
// Purpose: Get time series results for particular attribute. Specify series
// start and length using seriesStart and seriesLength respectively.
//
{
int k;
if (enrapi->isOpened) {
// Check memory for outValues
if (outValueSeries == NULL) return 411;
// loop over and build time series
for (k = 0; k <= seriesLength; k++)
outValueSeries[k] = getNodeValue(enrapi, seriesStart + 1 + k,
nodeIndex, attr);
return 0;
}
// Error no results to report on binary file not opened
return 412;
}
int DLLEXPORT ENR_getLinkSeries(ENResultsAPI* enrapi, int linkIndex, ENR_LinkAttribute attr,
int seriesStart, int seriesLength, float* outValueSeries)
//
// What if timeIndex 0? length 0?
//
// Purpose: Get time series results for particular attribute. Specify series
// start and length using seriesStart and seriesLength respectively.
//
{
int k;
if (enrapi->isOpened) {
// Check memory for outValues
if (outValueSeries == NULL) return 411;
// loop over and build time series
for (k = 0; k <= seriesLength; k++)
outValueSeries[k] = getLinkValue(enrapi, seriesStart +1 + k,
linkIndex, attr);
return 0;
}
// Error no results to report on binary file not opened
return 412;
}
int DLLEXPORT ENR_getNodeAttribute(ENResultsAPI* enrapi, int timeIndex,
ENR_NodeAttribute attr, float* outValueArray)
//
// Purpose: For all nodes at given time, get a particular attribute
//
{
INT4 offset;
if (enrapi->isOpened) {
// Check memory for outValues
if (outValueArray == NULL) return 411;
// calculate byte offset to start time for series
offset = enrapi->outputStartPos + (timeIndex)*enrapi->bytesPerPeriod;
// add offset for node and attribute
offset += (attr*enrapi->nodeCount)*RECORDSIZE;
fseek(enrapi->file, offset, SEEK_SET);
fread(outValueArray, RECORDSIZE, enrapi->nodeCount, enrapi->file);
return 0;
}
// Error no results to report on binary file not opened
return 412;
}
int DLLEXPORT ENR_getLinkAttribute(ENResultsAPI* enrapi, int timeIndex,
ENR_LinkAttribute attr, float* outValueArray)
//
// Purpose: For all links at given time, get a particular attribute
//
{
INT4 offset;
if (enrapi->isOpened) {
// Check memory for outValues
if (outValueArray == NULL) return 411;
// calculate byte offset to start time for series
offset = enrapi->outputStartPos + (timeIndex)*enrapi->bytesPerPeriod
+ (NNODERESULTS*enrapi->nodeCount)*RECORDSIZE;
// add offset for link and attribute
offset += (attr*enrapi->linkCount)*RECORDSIZE;
fseek(enrapi->file, offset, SEEK_SET);
fread(outValueArray, RECORDSIZE, enrapi->linkCount, enrapi->file);
return 0;
}
// Error no results to report on binary file not opened
return 412;
}
int DLLEXPORT ENR_getNodeResult(ENResultsAPI* enrapi, int timeIndex, int nodeIndex,
float* outValueArray)
//
// Purpose: For a node at given time, get all attributes
//
{
int j;
if (enrapi->isOpened) {
// Check memory for outValues
if (outValueArray == NULL) return 411;
for (j = 0; j < NNODERESULTS; j++)
outValueArray[j] = getNodeValue(enrapi, timeIndex + 1, nodeIndex, j);
return 0;
}
// Error no results to report on binary file not opened
return 412;
}
int DLLEXPORT ENR_getLinkResult(ENResultsAPI* enrapi, int timeIndex, int linkIndex,
float* outValueArray)
//
// Purpose: For a link at given time, get all attributes
//
{
int j;
if (enrapi->isOpened) {
// Check memory for outValues
if (outValueArray == NULL) return 411;
for (j = 0; j < NLINKRESULTS; j++)
outValueArray[j] = getLinkValue(enrapi, timeIndex + 1, linkIndex, j);
return 0;
}
// Error no results to report on binary file not opened
return 412;
}
int DLLEXPORT ENR_free(float* array)
//
// Purpose: frees memory allocated using ENR_newOutValueSeries() or
// ENR_newOutValueArray()
//
{
if (array != NULL)
free(array);
return 0;
}
int DLLEXPORT ENR_close(ENResultsAPI* enrapi)
//
// Purpose: Clean up after and close Output API
//
{
if (enrapi->isOpened) {
fclose(enrapi->file);
free(enrapi);
}
// Error binary file not opened
else return 412;
return 0;
}
int DLLEXPORT ENR_errMessage(int errcode, char* errmsg, int n)
//
// Purpose: takes error code returns error message
//
// Input Error 411: no memory allocated for results
// Input Error 412: no results binary file hasn't been opened
// Input Error 421: invalid parameter code
// File Error 434: unable to open binary output file
// File Error 435: run terminated no results in binary file
{
switch (errcode)
{
case 411: strncpy(errmsg, ERR411, n); break;
case 412: strncpy(errmsg, ERR412, n); break;
case 421: strncpy(errmsg, ERR421, n); break;
case 434: strncpy(errmsg, ERR434, n); break;
case 435: strncpy(errmsg, ERR435, n); break;
default: return 421;
}
return 0;
}
float getNodeValue(ENResultsAPI* enrapi, int timeIndex, int nodeIndex,
ENR_NodeAttribute attr)
//
// Purpose: Retrieves an attribute value at a specified node and time
//
{
REAL4 y;
INT4 offset;
// calculate byte offset to start time for series
offset = enrapi->outputStartPos + (timeIndex - 1)*enrapi->bytesPerPeriod;
// add bytepos for node and attribute
offset += (nodeIndex + attr*enrapi->nodeCount)*RECORDSIZE;
fseek(enrapi->file, offset, SEEK_SET);
fread(&y, RECORDSIZE, 1, enrapi->file);
return y;
}
float getLinkValue(ENResultsAPI* enrapi, int timeIndex, int linkIndex,
ENR_LinkAttribute attr)
//
// Purpose: Retrieves an attribute value at a specified link and time
//
{
REAL4 y;
INT4 offset;
// Calculate byte offset to start time for series
offset = enrapi->outputStartPos + (timeIndex - 1)*enrapi->bytesPerPeriod
+ (NNODERESULTS*enrapi->nodeCount)*RECORDSIZE;
// add bytepos for link and attribute
offset += (linkIndex + attr*enrapi->linkCount)*RECORDSIZE;
fseek(enrapi->file, offset, SEEK_SET);
fread(&y, RECORDSIZE, 1, enrapi->file);
return y;
}

View File

@@ -1,122 +0,0 @@
/*
* outputapi.h
*
* Created on: Jun 4, 2014
* Author: mtryby
*/
#ifndef OUTPUTAPI_H_
#define OUTPUTAPI_H_
#define MAXFNAME 259
/*------------------- Error Messages --------------------*/
#define ERR411 "Input Error 411: no memory allocated for results."
#define ERR412 "Input Error 412: no results; binary file hasn't been opened."
#define ERR421 "Input Error 421: invalid parameter code."
#define ERR434 "File Error 434: unable to open binary output file."
#define ERR435 "File Error 435: run terminated; no results in binary file."
/* Epanet Results binary file API */
typedef struct ENResultsAPI ENResultsAPI; // opaque struct object
typedef enum {
ENR_node = 1,
ENR_link = 2
} ENR_ElementType;
typedef enum {
ENR_getSeries = 1,
ENR_getAttribute = 2,
ENR_getResult = 3
} ENR_ApiFunction;
typedef enum {
ENR_nodeCount = 1,
ENR_tankCount = 2,
ENR_linkCount = 3,
ENR_pumpCount = 4,
ENR_valveCount = 5
} ENR_ElementCount;
typedef enum {
ENR_flowUnits = 1,
ENR_pressUnits = 2
} ENR_Unit;
typedef enum {
ENR_reportStart = 1,
ENR_reportStep = 2,
ENR_simDuration = 3,
ENR_numPeriods = 4
}ENR_Time;
typedef enum {
ENR_demand = 0,
ENR_head = 1,
ENR_pressure = 2,
ENR_quality = 3
} ENR_NodeAttribute;
typedef enum {
ENR_flow = 0,
ENR_velocity = 1,
ENR_headloss = 2,
ENR_avgQuality = 3,
ENR_status = 4,
ENR_setting = 5,
ENR_rxRate = 6,
ENT_frctnFctr = 7
} ENR_LinkAttribute;
#ifdef WINDOWS
#ifdef __cplusplus
#define DLLEXPORT extern "C" __declspec(dllexport) __stdcall
#else
#define DLLEXPORT __declspec(dllexport) __stdcall
#endif
#else
#ifdef __cplusplus
#define DLLEXPORT extern "C"
#else
#define DLLEXPORT
#endif
#endif
ENResultsAPI* DLLEXPORT ENR_alloc(void);
int DLLEXPORT ENR_open(ENResultsAPI* enrapi, const char* path);
int DLLEXPORT ENR_getNetSize(ENResultsAPI* enrapi, ENR_ElementCount code, int* count);
int DLLEXPORT ENR_getUnits(ENResultsAPI* enrapi, ENR_Unit code, int* unitFlag);
float* ENR_newOutValueSeries(ENResultsAPI* enrapi, int seriesStart,
int seriesLength, int* length, int* errcode);
float* ENR_newOutValueArray(ENResultsAPI* enrapi, ENR_ApiFunction func,
ENR_ElementType type, int* length, int* errcode);
int DLLEXPORT ENR_getNodeSeries(ENResultsAPI* enrapi, int nodeIndex, ENR_NodeAttribute attr,
int timeIndex, int length, float* outValueSeries, int* len);
int DLLEXPORT ENR_getLinkSeries(ENResultsAPI* enrapi, int linkIndex, ENR_LinkAttribute attr,
int timeIndex, int length, float* outValueSeries);
int DLLEXPORT ENR_getNodeAttribute(ENResultsAPI* enrapi, int timeIndex,
ENR_NodeAttribute attr, float* outValueArray);
int DLLEXPORT ENT_getLinkAttribute(ENResultsAPI* enrapi, int timeIndex,
ENR_LinkAttribute attr, float* outValueArray);
int DLLEXPORT ENR_getNodeResult(ENResultsAPI* enrapi, int timeIndex, int nodeIndex,
float* outValueArray);
int DLLEXPORT ENR_getLinkResult(ENResultsAPI* enrapi, int timeIndex, int linkIndex,
float* outValueArray);
int DLLEXPORT ENR_free(float *array);
int DLLEXPORT ENR_close(ENResultsAPI* enrapi);
int DLLEXPORT ENR_errMessage(int errcode, char* errmsg, int n);
#endif /* OUTPUTAPI_H_ */

16
tools/requirements.txt Normal file
View File

@@ -0,0 +1,16 @@
#
# requirements.txt
#
# Date Created: 10/10/2017
# Author: Michael E. Tryby
# US EPA ORD/NRMRL
#
# Useful for configuring a python environment to run epanet-nrtestsuite.
#
# command:
# $ pip install --src build/packages -r tools/requirements.txt
#
-e git+https://github.com/OpenWaterAnalytics/nrtest.git@master#egg=nrtest
-e ./tools/epanet-output
-e ./tools/nrtest-epanet

58
tools/run-nrtest.sh Executable file
View File

@@ -0,0 +1,58 @@
#! /bin/bash
#
# run-nrtest.sh - Runs numerical regression test
#
# Date Created: 10/16/2017
#
# Author: Michael E. Tryby
# US EPA - ORD/NRMRL
#
# Arguments:
# 1 - test suite path
# 2 - version/build identifier
#
run-nrtest()
{
return_value=0
test_suite_path=$1
nrtest_execute_cmd="nrtest execute"
test_app_path="apps/epanet-$2.json"
tests="tests/examples"
test_output_path="benchmark/epanet-$2"
nrtest_compare_cmd="nrtest compare"
ref_output_path="benchmark/epanet-2012"
rtol_value=0.1
atol_value=0.0
# change current directory to test_suite
cd ${test_suite_path}
# clean test benchmark results
rm -rf ${test_output_path}
echo INFO: Creating test benchmark
nrtest_command="${nrtest_execute_cmd} ${test_app_path} ${tests} -o ${test_output_path}"
echo INFO: "$nrtest_command"
if ! [ $( $nrtest_command ) ]; then
echo
echo INFO: Comparing test and ref benchmarks
nrtest_command="${nrtest_compare_cmd} ${test_output_path} ${ref_output_path} --rtol ${rtol_value} --atol ${atol_value}"
echo INFO: "$nrtest_command"
return_value=$( $nrtest_command )
else
echo ERROR: Test benchmark creation failed
exit 1
fi
return $return_value
}
run-nrtest $1 $2