//----------------------------------------------------------------------------- // // epanet_output.c -- API for reading results from EPANET binary output file // // Version: 0.40 // Date 04/02/2019 // 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 for that purpose. // //----------------------------------------------------------------------------- #include #include #include #include "util/errormanager.h" #include "util/filemanager.h" #include "epanet_output.h" #include "messages.h" // NOTE: These depend on machine data model and may change when porting #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 // EPANET max characters in ID name PLUS 1 #define MAXMSG_P1 80 // EPANET max characters in message text PLUS 1 #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 { 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; file_handle_t *file_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); float *newFloatArray(int n); int *newIntArray(int n); char *newCharArray(int n); int EXPORT_OUT_API 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 = create_error_manager(&errorLookup); p_data->file_handle = create_file_manager(); *dp_handle = p_data; } else errorcode = -1; // TODO: Need to handle errors during initialization better. return errorcode; } int EXPORT_OUT_API 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_handle == NULL) errorcode = -1; else { close_file(p_data->file_handle); delete_error_manager(p_data->error_handle); delete_file_manager(p_data->file_handle); free(p_data); *p_handle = NULL; } return errorcode; } int EXPORT_OUT_API 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 { // Attempt to open binary output file for reading only if ((open_file(p_data->file_handle, 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 seek_file(p_data->file_handle, 2*WORDSIZE, SEEK_SET); read_file(&(p_data->nodeCount), WORDSIZE, 1, p_data->file_handle); read_file(&(p_data->tankCount), WORDSIZE, 1, p_data->file_handle); read_file(&(p_data->linkCount), WORDSIZE, 1, p_data->file_handle); read_file(&(p_data->pumpCount), WORDSIZE, 1, p_data->file_handle); read_file(&(p_data->valveCount), WORDSIZE, 1, p_data->file_handle); // 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 EXPORT_OUT_API 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 { seek_file(p_data->file_handle, 1*WORDSIZE, SEEK_SET); if (read_file(version, WORDSIZE, 1, p_data->file_handle) != 1) errorcode = 436; } return set_error(p_data->error_handle, errorcode); } int EXPORT_OUT_API 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 EXPORT_OUT_API 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; F_OFF offset; char temp[MAXID_P1]; data_t* p_data; *unitFlag = -1; p_data = (data_t*)p_handle; if (p_data == NULL) return -1; else { switch (code) { case ENR_flowUnits: seek_file(p_data->file_handle, 9*WORDSIZE, SEEK_SET); read_file(unitFlag, WORDSIZE, 1, p_data->file_handle); break; case ENR_pressUnits: seek_file(p_data->file_handle, 10*WORDSIZE, SEEK_SET); read_file(unitFlag, WORDSIZE, 1, p_data->file_handle); break; case ENR_qualUnits: offset = 7*WORDSIZE; seek_file(p_data->file_handle, offset, SEEK_SET); read_file(unitFlag, WORDSIZE, 1, p_data->file_handle); if (*unitFlag == 0) *unitFlag = ENR_NONE; else if (*unitFlag == 1) { offset = 15*WORDSIZE + 3*MAXMSG_P1 + 2*(MAXFNAME+1) + MAXID_P1; seek_file(p_data->file_handle, offset, SEEK_SET); read_file(temp, MAXID_P1, 1, p_data->file_handle); if (!strcmp(temp, "mg/L")) *unitFlag = ENR_MGL; else *unitFlag = ENR_UGL; } else if (*unitFlag == 2) *unitFlag = ENR_HOURS; else *unitFlag = ENR_PRCNT; break; default: errorcode = 421; } } return set_error(p_data->error_handle, errorcode); } int EXPORT_OUT_API 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: seek_file(p_data->file_handle, 12*WORDSIZE, SEEK_SET); read_file(time, WORDSIZE, 1, p_data->file_handle); break; case ENR_reportStep: seek_file(p_data->file_handle, 13*WORDSIZE, SEEK_SET); read_file(time, WORDSIZE, 1, p_data->file_handle); break; case ENR_simDuration: seek_file(p_data->file_handle, 14*WORDSIZE, SEEK_SET); read_file(time, WORDSIZE, 1, p_data->file_handle); break; case ENR_numPeriods: *time = p_data->nPeriods; break; default: errorcode = 421; } } return set_error(p_data->error_handle, errorcode); } int EXPORT_OUT_API ENR_getChemData(ENR_Handle p_handle, char** name, int* length) { return 0; } int EXPORT_OUT_API 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) { seek_file(p_data->file_handle, offset, SEEK_SET); read_file(temp, 1, MAXID_P1, p_data->file_handle); *name = temp; *length = MAXID_P1; } } return set_error(p_data->error_handle, errorcode); } int EXPORT_OUT_API 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 seek_file(p_data->file_handle, offset, SEEK_SET); read_file(linkIndex, WORDSIZE, 1, p_data->file_handle); read_file(temp, WORDSIZE, 6, p_data->file_handle); *outValues = temp; *length = NENERGYRESULTS; } return set_error(p_data->error_handle, errorcode); } int EXPORT_OUT_API 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; seek_file(p_data->file_handle, offset, SEEK_END); read_file(temp, WORDSIZE, 4, p_data->file_handle); *outValues = temp; *length = NREACTRESULTS; } return set_error(p_data->error_handle, errorcode); } void EXPORT_OUT_API ENR_free(void** array) // // Purpose: Frees memory allocated by API calls // { if (array != NULL) { free(*array); *array = NULL; } } int EXPORT_OUT_API 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 EXPORT_OUT_API 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 EXPORT_OUT_API 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; seek_file(p_data->file_handle, offset, SEEK_SET); read_file(temp, WORDSIZE, p_data->nodeCount, p_data->file_handle); *outValueArray = temp; *length = p_data->nodeCount; } return set_error(p_data->error_handle, errorcode); } int EXPORT_OUT_API 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; seek_file(p_data->file_handle, offset, SEEK_SET); read_file(temp, WORDSIZE, p_data->linkCount, p_data->file_handle); *outValueArray = temp; *length = p_data->linkCount; } return set_error(p_data->error_handle, errorcode); } int EXPORT_OUT_API 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 EXPORT_OUT_API 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 EXPORT_OUT_API ENR_clearError(ENR_Handle p_handle) { data_t* p_data; p_data = (data_t*)p_handle; clear_error(p_data->error_handle); } int EXPORT_OUT_API ENR_checkError(ENR_Handle p_handle, char** msg_buffer) { data_t* p_data; p_data = (data_t*)p_handle; if (p_data == NULL) return -1; return check_error(p_data->error_handle, msg_buffer); } 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, MSG_MAXLEN); } 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 seek_file(p_data->file_handle, 0L, SEEK_SET); read_file(&magic1, WORDSIZE, 1, p_data->file_handle); // Fast forward to end and read file epilogue seek_file(p_data->file_handle, -3*WORDSIZE, SEEK_END); read_file(&(p_data->nPeriods), WORDSIZE, 1, p_data->file_handle); read_file(&hydcode, WORDSIZE, 1, p_data->file_handle); read_file(&magic2, WORDSIZE, 1, p_data->file_handle); filepos = tell_file(p_data->file_handle); // 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; seek_file(p_data->file_handle, offset, SEEK_SET); read_file(&y, WORDSIZE, 1, p_data->file_handle); 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; seek_file(p_data->file_handle, offset, SEEK_SET); read_file(&y, WORDSIZE, 1, p_data->file_handle); return y; } 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)); }